commit f5447aee1cde8e47f8d8a4929648a2980614eceb Author: Doug McLain Date: Tue Nov 2 16:14:09 2021 -0400 Initial commit diff --git a/AboutTab.qml b/AboutTab.qml new file mode 100644 index 0000000..14fae1f --- /dev/null +++ b/AboutTab.qml @@ -0,0 +1,56 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 + +Item { + id: aboutTab + Rectangle{ + id: helpText + x: 20 + y: 20 + width: parent.width - 40 + height: parent.height - 40 + color: "#252424" + Flickable{ + anchors.fill: parent + contentWidth: parent.width + contentHeight: aboutText.y + aboutText.height + flickableDirection: Flickable.VerticalFlick + clip: true + Text { + id: aboutText + width: helpText.width + wrapMode: Text.WordWrap + color: "white" + text: qsTr( "\nDROID-Star git build " + droidstar.get_software_build() + + "\nPlatform:\t" + droidstar.get_platform() + + "\nArchitecture:\t" + droidstar.get_arch() + + "\nBuild ABI:\t" + droidstar.get_build_abi() + + "\n\nCopyright (C) 2019-2021 Doug McLain AD8DP\n" + + "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; version 2.\n\n" + + "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.\n\n" + + "You should have received a copy of the GNU General Public License along with this " + + "program. If not, see ") + } + } + } +} diff --git a/CRCenc.cpp b/CRCenc.cpp new file mode 100644 index 0000000..4433317 --- /dev/null +++ b/CRCenc.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CRCenc.h" + +//#include "Utils.h" +//#include "Log.h" + +#include +#include +#include +#include + +const uint8_t CRC8_TABLE[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, + 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, + 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, + 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, + 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, + 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, + 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, + 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, + 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, + 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, + 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, + 0xFA, 0xFD, 0xF4, 0xF3, 0x01 }; + +const uint16_t CCITT16_TABLE1[] = { + 0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU, + 0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U, + 0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU, + 0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U, + 0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU, + 0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U, + 0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU, + 0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U, + 0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU, + 0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U, + 0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU, + 0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U, + 0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U, + 0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U, + 0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U, + 0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U, + 0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U, + 0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU, + 0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U, + 0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU, + 0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U, + 0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU, + 0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U, + 0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU, + 0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U, + 0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU, + 0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U, + 0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU, + 0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U, + 0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U, + 0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U, + 0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U }; + +const uint16_t CCITT16_TABLE2[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; + +/* +bool CCRC::checkFiveBit(bool* in, unsigned int tcrc) +{ + assert(in != NULL); + + unsigned int crc; + //encodeFiveBit(in, crc); + + return crc == tcrc; +} +*/ + +void CCRC::bitsToByteBE(const bool* bits, unsigned char& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x80U : 0x00U; + byte |= bits[1U] ? 0x40U : 0x00U; + byte |= bits[2U] ? 0x20U : 0x00U; + byte |= bits[3U] ? 0x10U : 0x00U; + byte |= bits[4U] ? 0x08U : 0x00U; + byte |= bits[5U] ? 0x04U : 0x00U; + byte |= bits[6U] ? 0x02U : 0x00U; + byte |= bits[7U] ? 0x01U : 0x00U; +} + +void CCRC::encodeFiveBit(const bool* in, unsigned int& tcrc) +{ + assert(in != NULL); + + unsigned short total = 0U; + for (unsigned int i = 0U; i < 72U; i += 8U) { + unsigned char c; + bitsToByteBE(in + i, c); + total += c; + } + + total %= 31U; + + tcrc = total; +} + +void CCRC::addCCITT162(unsigned char *in, unsigned int length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0U; + + for (unsigned i = 0U; i < (length - 2U); i++) + crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]]; + + crc16 = ~crc16; + + in[length - 1U] = crc8[0U]; + in[length - 2U] = crc8[1U]; +} + +bool CCRC::checkCCITT162(const unsigned char *in, unsigned int length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0U; + + for (unsigned i = 0U; i < (length - 2U); i++) + crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]]; + + crc16 = ~crc16; + + return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U]; +} + +void CCRC::addCCITT161(unsigned char *in, unsigned int length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + + for (unsigned int i = 0U; i < (length - 2U); i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]]; + + crc16 = ~crc16; + + in[length - 2U] = crc8[0U]; + in[length - 1U] = crc8[1U]; +} + +bool CCRC::checkCCITT161(const unsigned char *in, unsigned int length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + + for (unsigned int i = 0U; i < (length - 2U); i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]]; + + crc16 = ~crc16; + + return crc8[0U] == in[length - 2U] && crc8[1U] == in[length - 1U]; +} + +unsigned char CCRC::crc8(const unsigned char *in, unsigned int length) +{ + assert(in != NULL); + + uint8_t crc = 0U; + + for (unsigned int i = 0U; i < length; i++) + crc = CRC8_TABLE[crc ^ in[i]]; + + return crc; +} + +unsigned char CCRC::addCRC(const unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + unsigned char crc = 0U; + + for (unsigned int i = 0U; i < length; i++) + crc += in[i]; + + return crc; +} + diff --git a/CRCenc.h b/CRCenc.h new file mode 100644 index 0000000..99d6d5f --- /dev/null +++ b/CRCenc.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(CRC_H) +#define CRC_H + +class CCRC +{ +public: + //static bool checkFiveBit(bool* in, unsigned int tcrc); + static void bitsToByteBE(const bool* bits, unsigned char& byte); + static void encodeFiveBit(const bool* in, unsigned int& tcrc); + + static void addCCITT161(unsigned char* in, unsigned int length); + static void addCCITT162(unsigned char* in, unsigned int length); + + static bool checkCCITT161(const unsigned char* in, unsigned int length); + static bool checkCCITT162(const unsigned char* in, unsigned int length); + + static unsigned char crc8(const unsigned char* in, unsigned int length); + + static unsigned char addCRC(const unsigned char* in, unsigned int length); +}; + +#endif diff --git a/DMRData.cpp b/DMRData.cpp new file mode 100644 index 0000000..ac361e6 --- /dev/null +++ b/DMRData.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DMRData.h" +#include "DMRDefines.h" + +#include +#include +#include + + +CDMRData::CDMRData(const CDMRData& data) : +m_slotNo(data.m_slotNo), +m_data(NULL), +m_srcId(data.m_srcId), +m_dstId(data.m_dstId), +m_flco(data.m_flco), +m_dataType(data.m_dataType), +m_seqNo(data.m_seqNo), +m_missing(data.m_missing), +m_n(data.m_n), +m_ber(data.m_ber), +m_rssi(data.m_rssi), +m_streamId(data.m_streamId) +{ + m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES]; + ::memcpy(m_data, data.m_data, 2U * DMR_FRAME_LENGTH_BYTES); +} + +CDMRData::CDMRData() : +m_slotNo(1U), +m_data(NULL), +m_srcId(0U), +m_dstId(0U), +m_flco(FLCO_GROUP), +m_dataType(0U), +m_seqNo(0U), +m_missing(false), +m_n(0U), +m_ber(0U), +m_rssi(0U), +m_streamId(0U) +{ + m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES]; +} + +CDMRData::~CDMRData() +{ + delete[] m_data; +} + +CDMRData& CDMRData::operator=(const CDMRData& data) +{ + if (this != &data) { + ::memcpy(m_data, data.m_data, DMR_FRAME_LENGTH_BYTES); + + m_slotNo = data.m_slotNo; + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + m_flco = data.m_flco; + m_dataType = data.m_dataType; + m_seqNo = data.m_seqNo; + m_missing = data.m_missing; + m_n = data.m_n; + m_ber = data.m_ber; + m_rssi = data.m_rssi; + m_streamId = data.m_streamId; + } + + return *this; +} + +unsigned int CDMRData::getSlotNo() const +{ + return m_slotNo; +} + +void CDMRData::setSlotNo(unsigned int slotNo) +{ + assert(slotNo == 1U || slotNo == 2U); + + m_slotNo = slotNo; +} + +unsigned char CDMRData::getDataType() const +{ + return m_dataType; +} + +void CDMRData::setDataType(unsigned char dataType) +{ + m_dataType = dataType; +} + +unsigned int CDMRData::getSrcId() const +{ + return m_srcId; +} + +void CDMRData::setSrcId(unsigned int id) +{ + m_srcId = id; +} + +unsigned int CDMRData::getDstId() const +{ + return m_dstId; +} + +void CDMRData::setDstId(unsigned int id) +{ + m_dstId = id; +} + +FLCO CDMRData::getFLCO() const +{ + return m_flco; +} + +void CDMRData::setFLCO(FLCO flco) +{ + m_flco = flco; +} + +unsigned char CDMRData::getSeqNo() const +{ + return m_seqNo; +} + +void CDMRData::setSeqNo(unsigned char seqNo) +{ + m_seqNo = seqNo; +} + +bool CDMRData::isMissing() const +{ + return m_missing; +} + +void CDMRData::setMissing(bool missing) +{ + m_missing = missing; +} + +unsigned char CDMRData::getN() const +{ + return m_n; +} + +void CDMRData::setN(unsigned char n) +{ + m_n = n; +} + +unsigned char CDMRData::getBER() const +{ + return m_ber; +} + +void CDMRData::setBER(unsigned char ber) +{ + m_ber = ber; +} + +unsigned char CDMRData::getRSSI() const +{ + return m_rssi; +} + +void CDMRData::setRSSI(unsigned char rssi) +{ + m_rssi = rssi; +} + +unsigned int CDMRData::getData(unsigned char* buffer) const +{ + assert(buffer != NULL); + + ::memcpy(buffer, m_data, DMR_FRAME_LENGTH_BYTES); + + return DMR_FRAME_LENGTH_BYTES; +} + +void CDMRData::setData(const unsigned char* buffer) +{ + assert(buffer != NULL); + + ::memcpy(m_data, buffer, DMR_FRAME_LENGTH_BYTES); +} + +unsigned int CDMRData::getStreamId() const +{ + return m_streamId; +} + +void CDMRData::setStreamId(unsigned int id) +{ + m_streamId = id; +} diff --git a/DMRData.h b/DMRData.h new file mode 100644 index 0000000..ed0ea55 --- /dev/null +++ b/DMRData.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DMRData_H +#define DMRData_H + +#include "DMRDefines.h" + +class CDMRData { +public: + CDMRData(const CDMRData& data); + CDMRData(); + ~CDMRData(); + + CDMRData& operator=(const CDMRData& data); + + unsigned int getSlotNo() const; + void setSlotNo(unsigned int slotNo); + + unsigned int getSrcId() const; + void setSrcId(unsigned int id); + + unsigned int getDstId() const; + void setDstId(unsigned int id); + + FLCO getFLCO() const; + void setFLCO(FLCO flco); + + unsigned char getN() const; + void setN(unsigned char n); + + unsigned char getSeqNo() const; + void setSeqNo(unsigned char seqNo); + + unsigned char getDataType() const; + void setDataType(unsigned char dataType); + + bool isMissing() const; + void setMissing(bool missing); + + unsigned char getBER() const; + void setBER(unsigned char ber); + + unsigned char getRSSI() const; + void setRSSI(unsigned char rssi); + + void setData(const unsigned char* buffer); + unsigned int getData(unsigned char* buffer) const; + + void setStreamId(unsigned int id); + unsigned int getStreamId() const; + +private: + unsigned int m_slotNo; + unsigned char* m_data; + unsigned int m_srcId; + unsigned int m_dstId; + FLCO m_flco; + unsigned char m_dataType; + unsigned char m_seqNo; + bool m_missing; + unsigned char m_n; + unsigned char m_ber; + unsigned char m_rssi; + unsigned int m_streamId; +}; + +#endif diff --git a/DMRDefines.h b/DMRDefines.h new file mode 100644 index 0000000..cc97151 --- /dev/null +++ b/DMRDefines.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(DMRDefines_H) +#define DMRDefines_H + +const unsigned char TAG_HEADER = 0x00U; +const unsigned char TAG_DATA = 0x01U; +const unsigned char TAG_LOST = 0x02U; +const unsigned char TAG_EOT = 0x03U; +const unsigned char TAG_NODATA = 0x04U; + +const unsigned int DMR_FRAME_LENGTH_BITS = 264U; +const unsigned int DMR_FRAME_LENGTH_BYTES = 33U; + +const unsigned int DMR_SYNC_LENGTH_BITS = 48U; +const unsigned int DMR_SYNC_LENGTH_BYTES = 6U; + +const unsigned int DMR_EMB_LENGTH_BITS = 8U; +const unsigned int DMR_EMB_LENGTH_BYTES = 1U; + +const unsigned int DMR_SLOT_TYPE_LENGTH_BITS = 8U; +const unsigned int DMR_SLOT_TYPE_LENGTH_BYTES = 1U; + +const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BITS = 32U; +const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BYTES = 4U; + +const unsigned int DMR_AMBE_LENGTH_BITS = 108U * 2U; +const unsigned int DMR_AMBE_LENGTH_BYTES = 27U; + +const unsigned char BS_SOURCED_AUDIO_SYNC[] = {0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U}; +const unsigned char BS_SOURCED_DATA_SYNC[] = {0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U}; + +const unsigned char MS_SOURCED_AUDIO_SYNC[] = {0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U}; +const unsigned char MS_SOURCED_DATA_SYNC[] = {0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U}; + +const unsigned char DIRECT_SLOT1_AUDIO_SYNC[] = {0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U}; +const unsigned char DIRECT_SLOT1_DATA_SYNC[] = {0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U}; + +const unsigned char DIRECT_SLOT2_AUDIO_SYNC[] = {0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U}; +const unsigned char DIRECT_SLOT2_DATA_SYNC[] = {0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U}; + +const unsigned char SYNC_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U}; + +// The PR FILL and Data Sync pattern. +const unsigned char DMR_IDLE_DATA[] = {TAG_DATA, 0x00U, + 0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U, + 0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U, + 0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U}; + +// A silence frame only +const unsigned char DMR_SILENCE_DATA[] = {0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U, + 0x81U, 0x52U, 0x60U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x73U, 0x00U, + 0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU}; + +const unsigned char PAYLOAD_LEFT_MASK[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U}; +const unsigned char PAYLOAD_RIGHT_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; + +const unsigned char VOICE_LC_HEADER_CRC_MASK[] = {0x96U, 0x96U, 0x96U}; +const unsigned char TERMINATOR_WITH_LC_CRC_MASK[] = {0x99U, 0x99U, 0x99U}; +const unsigned char PI_HEADER_CRC_MASK[] = {0x69U, 0x69U}; +const unsigned char DATA_HEADER_CRC_MASK[] = {0xCCU, 0xCCU}; +const unsigned char CSBK_CRC_MASK[] = {0xA5U, 0xA5U}; + +const unsigned int DMR_SLOT_TIME = 60U; +const unsigned int AMBE_PER_SLOT = 3U; + +const unsigned char DT_MASK = 0x0FU; +const unsigned char DT_VOICE_PI_HEADER = 0x00U; +const unsigned char DT_VOICE_LC_HEADER = 0x01U; +const unsigned char DT_TERMINATOR_WITH_LC = 0x02U; +const unsigned char DT_CSBK = 0x03U; +const unsigned char DT_DATA_HEADER = 0x06U; +const unsigned char DT_RATE_12_DATA = 0x07U; +const unsigned char DT_RATE_34_DATA = 0x08U; +const unsigned char DT_IDLE = 0x09U; +const unsigned char DT_RATE_1_DATA = 0x0AU; + +// Dummy values +const unsigned char DT_VOICE_SYNC = 0xF0U; +const unsigned char DT_VOICE = 0xF1U; + +const unsigned char DMR_IDLE_RX = 0x80U; +const unsigned char DMR_SYNC_DATA = 0x40U; +const unsigned char DMR_SYNC_AUDIO = 0x20U; + +const unsigned char DMR_SLOT1 = 0x00U; +const unsigned char DMR_SLOT2 = 0x80U; + +const unsigned char DPF_UDT = 0x00U; +const unsigned char DPF_RESPONSE = 0x01U; +const unsigned char DPF_UNCONFIRMED_DATA = 0x02U; +const unsigned char DPF_CONFIRMED_DATA = 0x03U; +const unsigned char DPF_DEFINED_SHORT = 0x0DU; +const unsigned char DPF_DEFINED_RAW = 0x0EU; +const unsigned char DPF_PROPRIETARY = 0x0FU; + +const unsigned char FID_ETSI = 0U; +const unsigned char FID_DMRA = 16U; + +enum FLCO { + FLCO_GROUP = 0, + FLCO_USER_USER = 3, + FLCO_TALKER_ALIAS_HEADER = 4, + FLCO_TALKER_ALIAS_BLOCK1 = 5, + FLCO_TALKER_ALIAS_BLOCK2 = 6, + FLCO_TALKER_ALIAS_BLOCK3 = 7, + FLCO_GPS_INFO = 8 +}; + +#endif diff --git a/DroidStar.pro b/DroidStar.pro new file mode 100644 index 0000000..1bff558 --- /dev/null +++ b/DroidStar.pro @@ -0,0 +1,138 @@ +QT += quick quickcontrols2 network multimedia +android:QT += androidextras +linux:QT += serialport +CONFIG += c++11 +LFLAGS += +android:INCLUDEPATH += $$(HOME)/Android/android-build/include +LIBS += -limbe_vocoder -ldl +macx:QT += serialport +macx::INCLUDEPATH += /usr/local/include +macx:LIBS += -L/usr/local/lib -framework AVFoundation +macx:QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 +macx:QMAKE_INFO_PLIST = Info.plist +ios:LIBS += -framework AVFoundation +ios:QMAKE_INFO_PLIST = Info.plist +VERSION_BUILD='$(shell cd $$PWD;git rev-parse --short HEAD)' +DEFINES += VERSION_NUMBER=\"\\\"$${VERSION_BUILD}\\\"\" +DEFINES += QT_DEPRECATED_WARNINGS + +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 +#DEFINES += USE_FLITE +DEFINES += AMBESW_SUPPORTED + +SOURCES += \ + CRCenc.cpp \ + DMRData.cpp \ + Golay24128.cpp \ + SHA256.cpp \ + YSFConvolution.cpp \ + YSFFICH.cpp \ + androidserialport.cpp \ + audioengine.cpp \ + cbptc19696.cpp \ + cgolay2087.cpp \ + chamming.cpp \ + codec.cpp \ + codec2/codebooks.cpp \ + codec2/codec2.cpp \ + codec2/kiss_fft.cpp \ + codec2/lpc.cpp \ + codec2/nlp.cpp \ + codec2/pack.cpp \ + codec2/qbase.cpp \ + codec2/quantise.cpp \ + crs129.cpp \ + dcscodec.cpp \ + dmrcodec.cpp \ + droidstar.cpp \ + httpmanager.cpp \ + iaxcodec.cpp \ + m17codec.cpp \ + main.cpp \ + nxdncodec.cpp \ + p25codec.cpp \ + refcodec.cpp \ + serialambe.cpp \ + serialmodem.cpp \ + xrfcodec.cpp \ + ysfcodec.cpp +macx:OBJECTIVE_SOURCES += micpermission.mm +ios:OBJECTIVE_SOURCES += micpermission.mm +RESOURCES += qml.qrc + +QML_IMPORT_PATH = +QML_DESIGNER_IMPORT_PATH = + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +DISTFILES += \ + android/AndroidManifest.xml \ + android/build.gradle \ + android/gradle/wrapper/gradle-wrapper.jar \ + android/gradle/wrapper/gradle-wrapper.properties \ + android/gradlew \ + android/gradlew.bat \ + android/res/values/libs.xml \ + images/log.png + +contains(ANDROID_TARGET_ARCH,armeabi-v7a) { + LIBS += -L$$(HOME)/Android/local/lib + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + OTHER_FILES += android/src +} + +contains(ANDROID_TARGET_ARCH,arm64-v8a) { + LIBS += -L$$(HOME)/Android/local/lib64 + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + OTHER_FILES += android/src +} + +ANDROID_ABIS = armeabi-v7a arm64-v8a + +HEADERS += \ + CRCenc.h \ + DMRData.h \ + DMRDefines.h \ + Golay24128.h \ + SHA256.h \ + YSFConvolution.h \ + YSFFICH.h \ + androidserialport.h \ + audioengine.h \ + cbptc19696.h \ + cgolay2087.h \ + chamming.h \ + codec.h \ + codec2/codec2.h \ + codec2/codec2_internal.h \ + codec2/defines.h \ + codec2/kiss_fft.h \ + codec2/lpc.h \ + codec2/nlp.h \ + codec2/qbase.h \ + codec2/quantise.h \ + crs129.h \ + dcscodec.h \ + dmrcodec.h \ + droidstar.h \ + httpmanager.h \ + iaxcodec.h \ + iaxdefines.h \ + m17codec.h \ + nxdncodec.h \ + p25codec.h \ + refcodec.h \ + serialambe.h \ + serialmodem.h \ + vocoder_plugin.h \ + xrfcodec.h \ + ysfcodec.h +macx:HEADERS += micpermission.h + +contains(DEFINES, USE_FLITE){ + LIBS += -lflite_cmu_us_slt -lflite_cmu_us_kal16 -lflite_cmu_us_awb -lflite_cmu_us_rms -lflite_usenglish -lflite_cmulex -lflite -lasound +} +ios:HEADERS += micpermission.h diff --git a/DroidStar.pro.user b/DroidStar.pro.user new file mode 100644 index 0000000..7b15b8f --- /dev/null +++ b/DroidStar.pro.user @@ -0,0 +1,488 @@ + + + + + + EnvironmentId + {1b40929c-f363-44e6-ae48-8bc2eb02f152} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + Builtin.BuildSystem + + true + true + Builtin.DefaultTidyAndClazy + 12 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 5.15.2 GCC 64bit + Desktop Qt 5.15.2 GCC 64bit + qt.qt5.5152.gcc_64_kit + 0 + 0 + 0 + + 0 + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Release + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Profile + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 2 + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:/mnt/data/src/qt-projects/DroidStar/DroidStar.pro + /mnt/data/src/qt-projects/DroidStar/DroidStar.pro + false + true + true + false + true + /mnt/data/src/qt-projects/build-DroidStar-Desktop_Qt_5_15_2_GCC_64bit-Debug + + 1 + + + + ProjectExplorer.Project.Target.1 + + Android.Device.Type + Android Qt 5.15.2 Clang Multi-Abi + Android Qt 5.15.2 Clang Multi-Abi + {955eaf34-57a0-4cce-b8ac-8097bc82fcb9} + 1 + 0 + 0 + + 0 + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Debug + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + armeabi-v7a + + + + true + Qt4ProjectManager.MakeStep + + + true + Copy application data + Qt4ProjectManager.AndroidPackageInstallationStep + + + android-31 + + true + Build Android APK + QmakeProjectManager.AndroidBuildApkStep + false + + 4 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Release + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Release + + + true + QtProjectManager.QMakeBuildStep + false + + armeabi-v7a + arm64-v8a + + + + true + Qt4ProjectManager.MakeStep + + + true + Copy application data + Qt4ProjectManager.AndroidPackageInstallationStep + + + android-29 + + true + Build Android APK + QmakeProjectManager.AndroidBuildApkStep + false + + 4 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Profile + /mnt/data/src/qt-projects/build-DroidStar-Android_Qt_5_15_2_Clang_Multi_Abi-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + + true + Copy application data + Qt4ProjectManager.AndroidPackageInstallationStep + + + android-31 + + true + Build Android APK + QmakeProjectManager.AndroidBuildApkStep + false + + 4 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + + true + Qt4ProjectManager.AndroidDeployQtStep + + 1 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + Qt4ProjectManager.AndroidDeployConfiguration2 + + 1 + + + arm64-v8a + armeabi-v7a + armeabi + + R38N502YWQJ + 30 + + + true + true + 2 + true + + + + + + + + 0 + + DroidStar + Qt4ProjectManager.AndroidRunConfiguration:/mnt/data/src/qt-projects/DroidStar/DroidStar.pro + /mnt/data/src/qt-projects/DroidStar/DroidStar.pro + false + true + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 2 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/Golay24128.cpp b/Golay24128.cpp new file mode 100644 index 0000000..417da00 --- /dev/null +++ b/Golay24128.cpp @@ -0,0 +1,1108 @@ +/* + * Copyright (C) 2010,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2002 by Robert H. Morelos-Zaragoza. All rights reserved. + */ + +#include "Golay24128.h" + +#include +#include + +const unsigned int ENCODING_TABLE_23127[] = { + 0x000000U, 0x0018EAU, 0x00293EU, 0x0031D4U, 0x004A96U, 0x00527CU, 0x0063A8U, 0x007B42U, 0x008DC6U, 0x00952CU, + 0x00A4F8U, 0x00BC12U, 0x00C750U, 0x00DFBAU, 0x00EE6EU, 0x00F684U, 0x010366U, 0x011B8CU, 0x012A58U, 0x0132B2U, + 0x0149F0U, 0x01511AU, 0x0160CEU, 0x017824U, 0x018EA0U, 0x01964AU, 0x01A79EU, 0x01BF74U, 0x01C436U, 0x01DCDCU, + 0x01ED08U, 0x01F5E2U, 0x0206CCU, 0x021E26U, 0x022FF2U, 0x023718U, 0x024C5AU, 0x0254B0U, 0x026564U, 0x027D8EU, + 0x028B0AU, 0x0293E0U, 0x02A234U, 0x02BADEU, 0x02C19CU, 0x02D976U, 0x02E8A2U, 0x02F048U, 0x0305AAU, 0x031D40U, + 0x032C94U, 0x03347EU, 0x034F3CU, 0x0357D6U, 0x036602U, 0x037EE8U, 0x03886CU, 0x039086U, 0x03A152U, 0x03B9B8U, + 0x03C2FAU, 0x03DA10U, 0x03EBC4U, 0x03F32EU, 0x040D98U, 0x041572U, 0x0424A6U, 0x043C4CU, 0x04470EU, 0x045FE4U, + 0x046E30U, 0x0476DAU, 0x04805EU, 0x0498B4U, 0x04A960U, 0x04B18AU, 0x04CAC8U, 0x04D222U, 0x04E3F6U, 0x04FB1CU, + 0x050EFEU, 0x051614U, 0x0527C0U, 0x053F2AU, 0x054468U, 0x055C82U, 0x056D56U, 0x0575BCU, 0x058338U, 0x059BD2U, + 0x05AA06U, 0x05B2ECU, 0x05C9AEU, 0x05D144U, 0x05E090U, 0x05F87AU, 0x060B54U, 0x0613BEU, 0x06226AU, 0x063A80U, + 0x0641C2U, 0x065928U, 0x0668FCU, 0x067016U, 0x068692U, 0x069E78U, 0x06AFACU, 0x06B746U, 0x06CC04U, 0x06D4EEU, + 0x06E53AU, 0x06FDD0U, 0x070832U, 0x0710D8U, 0x07210CU, 0x0739E6U, 0x0742A4U, 0x075A4EU, 0x076B9AU, 0x077370U, + 0x0785F4U, 0x079D1EU, 0x07ACCAU, 0x07B420U, 0x07CF62U, 0x07D788U, 0x07E65CU, 0x07FEB6U, 0x0803DAU, 0x081B30U, + 0x082AE4U, 0x08320EU, 0x08494CU, 0x0851A6U, 0x086072U, 0x087898U, 0x088E1CU, 0x0896F6U, 0x08A722U, 0x08BFC8U, + 0x08C48AU, 0x08DC60U, 0x08EDB4U, 0x08F55EU, 0x0900BCU, 0x091856U, 0x092982U, 0x093168U, 0x094A2AU, 0x0952C0U, + 0x096314U, 0x097BFEU, 0x098D7AU, 0x099590U, 0x09A444U, 0x09BCAEU, 0x09C7ECU, 0x09DF06U, 0x09EED2U, 0x09F638U, + 0x0A0516U, 0x0A1DFCU, 0x0A2C28U, 0x0A34C2U, 0x0A4F80U, 0x0A576AU, 0x0A66BEU, 0x0A7E54U, 0x0A88D0U, 0x0A903AU, + 0x0AA1EEU, 0x0AB904U, 0x0AC246U, 0x0ADAACU, 0x0AEB78U, 0x0AF392U, 0x0B0670U, 0x0B1E9AU, 0x0B2F4EU, 0x0B37A4U, + 0x0B4CE6U, 0x0B540CU, 0x0B65D8U, 0x0B7D32U, 0x0B8BB6U, 0x0B935CU, 0x0BA288U, 0x0BBA62U, 0x0BC120U, 0x0BD9CAU, + 0x0BE81EU, 0x0BF0F4U, 0x0C0E42U, 0x0C16A8U, 0x0C277CU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3EU, 0x0C6DEAU, 0x0C7500U, + 0x0C8384U, 0x0C9B6EU, 0x0CAABAU, 0x0CB250U, 0x0CC912U, 0x0CD1F8U, 0x0CE02CU, 0x0CF8C6U, 0x0D0D24U, 0x0D15CEU, + 0x0D241AU, 0x0D3CF0U, 0x0D47B2U, 0x0D5F58U, 0x0D6E8CU, 0x0D7666U, 0x0D80E2U, 0x0D9808U, 0x0DA9DCU, 0x0DB136U, + 0x0DCA74U, 0x0DD29EU, 0x0DE34AU, 0x0DFBA0U, 0x0E088EU, 0x0E1064U, 0x0E21B0U, 0x0E395AU, 0x0E4218U, 0x0E5AF2U, + 0x0E6B26U, 0x0E73CCU, 0x0E8548U, 0x0E9DA2U, 0x0EAC76U, 0x0EB49CU, 0x0ECFDEU, 0x0ED734U, 0x0EE6E0U, 0x0EFE0AU, + 0x0F0BE8U, 0x0F1302U, 0x0F22D6U, 0x0F3A3CU, 0x0F417EU, 0x0F5994U, 0x0F6840U, 0x0F70AAU, 0x0F862EU, 0x0F9EC4U, + 0x0FAF10U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD452U, 0x0FE586U, 0x0FFD6CU, 0x1007B4U, 0x101F5EU, 0x102E8AU, 0x103660U, + 0x104D22U, 0x1055C8U, 0x10641CU, 0x107CF6U, 0x108A72U, 0x109298U, 0x10A34CU, 0x10BBA6U, 0x10C0E4U, 0x10D80EU, + 0x10E9DAU, 0x10F130U, 0x1104D2U, 0x111C38U, 0x112DECU, 0x113506U, 0x114E44U, 0x1156AEU, 0x11677AU, 0x117F90U, + 0x118914U, 0x1191FEU, 0x11A02AU, 0x11B8C0U, 0x11C382U, 0x11DB68U, 0x11EABCU, 0x11F256U, 0x120178U, 0x121992U, + 0x122846U, 0x1230ACU, 0x124BEEU, 0x125304U, 0x1262D0U, 0x127A3AU, 0x128CBEU, 0x129454U, 0x12A580U, 0x12BD6AU, + 0x12C628U, 0x12DEC2U, 0x12EF16U, 0x12F7FCU, 0x13021EU, 0x131AF4U, 0x132B20U, 0x1333CAU, 0x134888U, 0x135062U, + 0x1361B6U, 0x13795CU, 0x138FD8U, 0x139732U, 0x13A6E6U, 0x13BE0CU, 0x13C54EU, 0x13DDA4U, 0x13EC70U, 0x13F49AU, + 0x140A2CU, 0x1412C6U, 0x142312U, 0x143BF8U, 0x1440BAU, 0x145850U, 0x146984U, 0x14716EU, 0x1487EAU, 0x149F00U, + 0x14AED4U, 0x14B63EU, 0x14CD7CU, 0x14D596U, 0x14E442U, 0x14FCA8U, 0x15094AU, 0x1511A0U, 0x152074U, 0x15389EU, + 0x1543DCU, 0x155B36U, 0x156AE2U, 0x157208U, 0x15848CU, 0x159C66U, 0x15ADB2U, 0x15B558U, 0x15CE1AU, 0x15D6F0U, + 0x15E724U, 0x15FFCEU, 0x160CE0U, 0x16140AU, 0x1625DEU, 0x163D34U, 0x164676U, 0x165E9CU, 0x166F48U, 0x1677A2U, + 0x168126U, 0x1699CCU, 0x16A818U, 0x16B0F2U, 0x16CBB0U, 0x16D35AU, 0x16E28EU, 0x16FA64U, 0x170F86U, 0x17176CU, + 0x1726B8U, 0x173E52U, 0x174510U, 0x175DFAU, 0x176C2EU, 0x1774C4U, 0x178240U, 0x179AAAU, 0x17AB7EU, 0x17B394U, + 0x17C8D6U, 0x17D03CU, 0x17E1E8U, 0x17F902U, 0x18046EU, 0x181C84U, 0x182D50U, 0x1835BAU, 0x184EF8U, 0x185612U, + 0x1867C6U, 0x187F2CU, 0x1889A8U, 0x189142U, 0x18A096U, 0x18B87CU, 0x18C33EU, 0x18DBD4U, 0x18EA00U, 0x18F2EAU, + 0x190708U, 0x191FE2U, 0x192E36U, 0x1936DCU, 0x194D9EU, 0x195574U, 0x1964A0U, 0x197C4AU, 0x198ACEU, 0x199224U, + 0x19A3F0U, 0x19BB1AU, 0x19C058U, 0x19D8B2U, 0x19E966U, 0x19F18CU, 0x1A02A2U, 0x1A1A48U, 0x1A2B9CU, 0x1A3376U, + 0x1A4834U, 0x1A50DEU, 0x1A610AU, 0x1A79E0U, 0x1A8F64U, 0x1A978EU, 0x1AA65AU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD18U, + 0x1AECCCU, 0x1AF426U, 0x1B01C4U, 0x1B192EU, 0x1B28FAU, 0x1B3010U, 0x1B4B52U, 0x1B53B8U, 0x1B626CU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E8U, 0x1BA53CU, 0x1BBDD6U, 0x1BC694U, 0x1BDE7EU, 0x1BEFAAU, 0x1BF740U, 0x1C09F6U, 0x1C111CU, + 0x1C20C8U, 0x1C3822U, 0x1C4360U, 0x1C5B8AU, 0x1C6A5EU, 0x1C72B4U, 0x1C8430U, 0x1C9CDAU, 0x1CAD0EU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64CU, 0x1CE798U, 0x1CFF72U, 0x1D0A90U, 0x1D127AU, 0x1D23AEU, 0x1D3B44U, 0x1D4006U, 0x1D58ECU, + 0x1D6938U, 0x1D71D2U, 0x1D8756U, 0x1D9FBCU, 0x1DAE68U, 0x1DB682U, 0x1DCDC0U, 0x1DD52AU, 0x1DE4FEU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D0U, 0x1E2604U, 0x1E3EEEU, 0x1E45ACU, 0x1E5D46U, 0x1E6C92U, 0x1E7478U, 0x1E82FCU, 0x1E9A16U, + 0x1EABC2U, 0x1EB328U, 0x1EC86AU, 0x1ED080U, 0x1EE154U, 0x1EF9BEU, 0x1F0C5CU, 0x1F14B6U, 0x1F2562U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E20U, 0x1F6FF4U, 0x1F771EU, 0x1F819AU, 0x1F9970U, 0x1FA8A4U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E6U, + 0x1FE232U, 0x1FFAD8U, 0x200F68U, 0x201782U, 0x202656U, 0x203EBCU, 0x2045FEU, 0x205D14U, 0x206CC0U, 0x20742AU, + 0x2082AEU, 0x209A44U, 0x20AB90U, 0x20B37AU, 0x20C838U, 0x20D0D2U, 0x20E106U, 0x20F9ECU, 0x210C0EU, 0x2114E4U, + 0x212530U, 0x213DDAU, 0x214698U, 0x215E72U, 0x216FA6U, 0x21774CU, 0x2181C8U, 0x219922U, 0x21A8F6U, 0x21B01CU, + 0x21CB5EU, 0x21D3B4U, 0x21E260U, 0x21FA8AU, 0x2209A4U, 0x22114EU, 0x22209AU, 0x223870U, 0x224332U, 0x225BD8U, + 0x226A0CU, 0x2272E6U, 0x228462U, 0x229C88U, 0x22AD5CU, 0x22B5B6U, 0x22CEF4U, 0x22D61EU, 0x22E7CAU, 0x22FF20U, + 0x230AC2U, 0x231228U, 0x2323FCU, 0x233B16U, 0x234054U, 0x2358BEU, 0x23696AU, 0x237180U, 0x238704U, 0x239FEEU, + 0x23AE3AU, 0x23B6D0U, 0x23CD92U, 0x23D578U, 0x23E4ACU, 0x23FC46U, 0x2402F0U, 0x241A1AU, 0x242BCEU, 0x243324U, + 0x244866U, 0x24508CU, 0x246158U, 0x2479B2U, 0x248F36U, 0x2497DCU, 0x24A608U, 0x24BEE2U, 0x24C5A0U, 0x24DD4AU, + 0x24EC9EU, 0x24F474U, 0x250196U, 0x25197CU, 0x2528A8U, 0x253042U, 0x254B00U, 0x2553EAU, 0x25623EU, 0x257AD4U, + 0x258C50U, 0x2594BAU, 0x25A56EU, 0x25BD84U, 0x25C6C6U, 0x25DE2CU, 0x25EFF8U, 0x25F712U, 0x26043CU, 0x261CD6U, + 0x262D02U, 0x2635E8U, 0x264EAAU, 0x265640U, 0x266794U, 0x267F7EU, 0x2689FAU, 0x269110U, 0x26A0C4U, 0x26B82EU, + 0x26C36CU, 0x26DB86U, 0x26EA52U, 0x26F2B8U, 0x27075AU, 0x271FB0U, 0x272E64U, 0x27368EU, 0x274DCCU, 0x275526U, + 0x2764F2U, 0x277C18U, 0x278A9CU, 0x279276U, 0x27A3A2U, 0x27BB48U, 0x27C00AU, 0x27D8E0U, 0x27E934U, 0x27F1DEU, + 0x280CB2U, 0x281458U, 0x28258CU, 0x283D66U, 0x284624U, 0x285ECEU, 0x286F1AU, 0x2877F0U, 0x288174U, 0x28999EU, + 0x28A84AU, 0x28B0A0U, 0x28CBE2U, 0x28D308U, 0x28E2DCU, 0x28FA36U, 0x290FD4U, 0x29173EU, 0x2926EAU, 0x293E00U, + 0x294542U, 0x295DA8U, 0x296C7CU, 0x297496U, 0x298212U, 0x299AF8U, 0x29AB2CU, 0x29B3C6U, 0x29C884U, 0x29D06EU, + 0x29E1BAU, 0x29F950U, 0x2A0A7EU, 0x2A1294U, 0x2A2340U, 0x2A3BAAU, 0x2A40E8U, 0x2A5802U, 0x2A69D6U, 0x2A713CU, + 0x2A87B8U, 0x2A9F52U, 0x2AAE86U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C4U, 0x2AE410U, 0x2AFCFAU, 0x2B0918U, 0x2B11F2U, + 0x2B2026U, 0x2B38CCU, 0x2B438EU, 0x2B5B64U, 0x2B6AB0U, 0x2B725AU, 0x2B84DEU, 0x2B9C34U, 0x2BADE0U, 0x2BB50AU, + 0x2BCE48U, 0x2BD6A2U, 0x2BE776U, 0x2BFF9CU, 0x2C012AU, 0x2C19C0U, 0x2C2814U, 0x2C30FEU, 0x2C4BBCU, 0x2C5356U, + 0x2C6282U, 0x2C7A68U, 0x2C8CECU, 0x2C9406U, 0x2CA5D2U, 0x2CBD38U, 0x2CC67AU, 0x2CDE90U, 0x2CEF44U, 0x2CF7AEU, + 0x2D024CU, 0x2D1AA6U, 0x2D2B72U, 0x2D3398U, 0x2D48DAU, 0x2D5030U, 0x2D61E4U, 0x2D790EU, 0x2D8F8AU, 0x2D9760U, + 0x2DA6B4U, 0x2DBE5EU, 0x2DC51CU, 0x2DDDF6U, 0x2DEC22U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0CU, 0x2E2ED8U, 0x2E3632U, + 0x2E4D70U, 0x2E559AU, 0x2E644EU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CAU, 0x2EA31EU, 0x2EBBF4U, 0x2EC0B6U, 0x2ED85CU, + 0x2EE988U, 0x2EF162U, 0x2F0480U, 0x2F1C6AU, 0x2F2DBEU, 0x2F3554U, 0x2F4E16U, 0x2F56FCU, 0x2F6728U, 0x2F7FC2U, + 0x2F8946U, 0x2F91ACU, 0x2FA078U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3AU, 0x2FEAEEU, 0x2FF204U, 0x3008DCU, 0x301036U, + 0x3021E2U, 0x303908U, 0x30424AU, 0x305AA0U, 0x306B74U, 0x30739EU, 0x30851AU, 0x309DF0U, 0x30AC24U, 0x30B4CEU, + 0x30CF8CU, 0x30D766U, 0x30E6B2U, 0x30FE58U, 0x310BBAU, 0x311350U, 0x312284U, 0x313A6EU, 0x31412CU, 0x3159C6U, + 0x316812U, 0x3170F8U, 0x31867CU, 0x319E96U, 0x31AF42U, 0x31B7A8U, 0x31CCEAU, 0x31D400U, 0x31E5D4U, 0x31FD3EU, + 0x320E10U, 0x3216FAU, 0x32272EU, 0x323FC4U, 0x324486U, 0x325C6CU, 0x326DB8U, 0x327552U, 0x3283D6U, 0x329B3CU, + 0x32AAE8U, 0x32B202U, 0x32C940U, 0x32D1AAU, 0x32E07EU, 0x32F894U, 0x330D76U, 0x33159CU, 0x332448U, 0x333CA2U, + 0x3347E0U, 0x335F0AU, 0x336EDEU, 0x337634U, 0x3380B0U, 0x33985AU, 0x33A98EU, 0x33B164U, 0x33CA26U, 0x33D2CCU, + 0x33E318U, 0x33FBF2U, 0x340544U, 0x341DAEU, 0x342C7AU, 0x343490U, 0x344FD2U, 0x345738U, 0x3466ECU, 0x347E06U, + 0x348882U, 0x349068U, 0x34A1BCU, 0x34B956U, 0x34C214U, 0x34DAFEU, 0x34EB2AU, 0x34F3C0U, 0x350622U, 0x351EC8U, + 0x352F1CU, 0x3537F6U, 0x354CB4U, 0x35545EU, 0x35658AU, 0x357D60U, 0x358BE4U, 0x35930EU, 0x35A2DAU, 0x35BA30U, + 0x35C172U, 0x35D998U, 0x35E84CU, 0x35F0A6U, 0x360388U, 0x361B62U, 0x362AB6U, 0x36325CU, 0x36491EU, 0x3651F4U, + 0x366020U, 0x3678CAU, 0x368E4EU, 0x3696A4U, 0x36A770U, 0x36BF9AU, 0x36C4D8U, 0x36DC32U, 0x36EDE6U, 0x36F50CU, + 0x3700EEU, 0x371804U, 0x3729D0U, 0x37313AU, 0x374A78U, 0x375292U, 0x376346U, 0x377BACU, 0x378D28U, 0x3795C2U, + 0x37A416U, 0x37BCFCU, 0x37C7BEU, 0x37DF54U, 0x37EE80U, 0x37F66AU, 0x380B06U, 0x3813ECU, 0x382238U, 0x383AD2U, + 0x384190U, 0x38597AU, 0x3868AEU, 0x387044U, 0x3886C0U, 0x389E2AU, 0x38AFFEU, 0x38B714U, 0x38CC56U, 0x38D4BCU, + 0x38E568U, 0x38FD82U, 0x390860U, 0x39108AU, 0x39215EU, 0x3939B4U, 0x3942F6U, 0x395A1CU, 0x396BC8U, 0x397322U, + 0x3985A6U, 0x399D4CU, 0x39AC98U, 0x39B472U, 0x39CF30U, 0x39D7DAU, 0x39E60EU, 0x39FEE4U, 0x3A0DCAU, 0x3A1520U, + 0x3A24F4U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB6U, 0x3A6E62U, 0x3A7688U, 0x3A800CU, 0x3A98E6U, 0x3AA932U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD270U, 0x3AE3A4U, 0x3AFB4EU, 0x3B0EACU, 0x3B1646U, 0x3B2792U, 0x3B3F78U, 0x3B443AU, 0x3B5CD0U, + 0x3B6D04U, 0x3B75EEU, 0x3B836AU, 0x3B9B80U, 0x3BAA54U, 0x3BB2BEU, 0x3BC9FCU, 0x3BD116U, 0x3BE0C2U, 0x3BF828U, + 0x3C069EU, 0x3C1E74U, 0x3C2FA0U, 0x3C374AU, 0x3C4C08U, 0x3C54E2U, 0x3C6536U, 0x3C7DDCU, 0x3C8B58U, 0x3C93B2U, + 0x3CA266U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD924U, 0x3CE8F0U, 0x3CF01AU, 0x3D05F8U, 0x3D1D12U, 0x3D2CC6U, 0x3D342CU, + 0x3D4F6EU, 0x3D5784U, 0x3D6650U, 0x3D7EBAU, 0x3D883EU, 0x3D90D4U, 0x3DA100U, 0x3DB9EAU, 0x3DC2A8U, 0x3DDA42U, + 0x3DEB96U, 0x3DF37CU, 0x3E0052U, 0x3E18B8U, 0x3E296CU, 0x3E3186U, 0x3E4AC4U, 0x3E522EU, 0x3E63FAU, 0x3E7B10U, + 0x3E8D94U, 0x3E957EU, 0x3EA4AAU, 0x3EBC40U, 0x3EC702U, 0x3EDFE8U, 0x3EEE3CU, 0x3EF6D6U, 0x3F0334U, 0x3F1BDEU, + 0x3F2A0AU, 0x3F32E0U, 0x3F49A2U, 0x3F5148U, 0x3F609CU, 0x3F7876U, 0x3F8EF2U, 0x3F9618U, 0x3FA7CCU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8EU, 0x3FED5AU, 0x3FF5B0U, 0x40063AU, 0x401ED0U, 0x402F04U, 0x4037EEU, 0x404CACU, 0x405446U, + 0x406592U, 0x407D78U, 0x408BFCU, 0x409316U, 0x40A2C2U, 0x40BA28U, 0x40C16AU, 0x40D980U, 0x40E854U, 0x40F0BEU, + 0x41055CU, 0x411DB6U, 0x412C62U, 0x413488U, 0x414FCAU, 0x415720U, 0x4166F4U, 0x417E1EU, 0x41889AU, 0x419070U, + 0x41A1A4U, 0x41B94EU, 0x41C20CU, 0x41DAE6U, 0x41EB32U, 0x41F3D8U, 0x4200F6U, 0x42181CU, 0x4229C8U, 0x423122U, + 0x424A60U, 0x42528AU, 0x42635EU, 0x427BB4U, 0x428D30U, 0x4295DAU, 0x42A40EU, 0x42BCE4U, 0x42C7A6U, 0x42DF4CU, + 0x42EE98U, 0x42F672U, 0x430390U, 0x431B7AU, 0x432AAEU, 0x433244U, 0x434906U, 0x4351ECU, 0x436038U, 0x4378D2U, + 0x438E56U, 0x4396BCU, 0x43A768U, 0x43BF82U, 0x43C4C0U, 0x43DC2AU, 0x43EDFEU, 0x43F514U, 0x440BA2U, 0x441348U, + 0x44229CU, 0x443A76U, 0x444134U, 0x4459DEU, 0x44680AU, 0x4470E0U, 0x448664U, 0x449E8EU, 0x44AF5AU, 0x44B7B0U, + 0x44CCF2U, 0x44D418U, 0x44E5CCU, 0x44FD26U, 0x4508C4U, 0x45102EU, 0x4521FAU, 0x453910U, 0x454252U, 0x455AB8U, + 0x456B6CU, 0x457386U, 0x458502U, 0x459DE8U, 0x45AC3CU, 0x45B4D6U, 0x45CF94U, 0x45D77EU, 0x45E6AAU, 0x45FE40U, + 0x460D6EU, 0x461584U, 0x462450U, 0x463CBAU, 0x4647F8U, 0x465F12U, 0x466EC6U, 0x46762CU, 0x4680A8U, 0x469842U, + 0x46A996U, 0x46B17CU, 0x46CA3EU, 0x46D2D4U, 0x46E300U, 0x46FBEAU, 0x470E08U, 0x4716E2U, 0x472736U, 0x473FDCU, + 0x47449EU, 0x475C74U, 0x476DA0U, 0x47754AU, 0x4783CEU, 0x479B24U, 0x47AAF0U, 0x47B21AU, 0x47C958U, 0x47D1B2U, + 0x47E066U, 0x47F88CU, 0x4805E0U, 0x481D0AU, 0x482CDEU, 0x483434U, 0x484F76U, 0x48579CU, 0x486648U, 0x487EA2U, + 0x488826U, 0x4890CCU, 0x48A118U, 0x48B9F2U, 0x48C2B0U, 0x48DA5AU, 0x48EB8EU, 0x48F364U, 0x490686U, 0x491E6CU, + 0x492FB8U, 0x493752U, 0x494C10U, 0x4954FAU, 0x49652EU, 0x497DC4U, 0x498B40U, 0x4993AAU, 0x49A27EU, 0x49BA94U, + 0x49C1D6U, 0x49D93CU, 0x49E8E8U, 0x49F002U, 0x4A032CU, 0x4A1BC6U, 0x4A2A12U, 0x4A32F8U, 0x4A49BAU, 0x4A5150U, + 0x4A6084U, 0x4A786EU, 0x4A8EEAU, 0x4A9600U, 0x4AA7D4U, 0x4ABF3EU, 0x4AC47CU, 0x4ADC96U, 0x4AED42U, 0x4AF5A8U, + 0x4B004AU, 0x4B18A0U, 0x4B2974U, 0x4B319EU, 0x4B4ADCU, 0x4B5236U, 0x4B63E2U, 0x4B7B08U, 0x4B8D8CU, 0x4B9566U, + 0x4BA4B2U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF0U, 0x4BEE24U, 0x4BF6CEU, 0x4C0878U, 0x4C1092U, 0x4C2146U, 0x4C39ACU, + 0x4C42EEU, 0x4C5A04U, 0x4C6BD0U, 0x4C733AU, 0x4C85BEU, 0x4C9D54U, 0x4CAC80U, 0x4CB46AU, 0x4CCF28U, 0x4CD7C2U, + 0x4CE616U, 0x4CFEFCU, 0x4D0B1EU, 0x4D13F4U, 0x4D2220U, 0x4D3ACAU, 0x4D4188U, 0x4D5962U, 0x4D68B6U, 0x4D705CU, + 0x4D86D8U, 0x4D9E32U, 0x4DAFE6U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A4U, 0x4DE570U, 0x4DFD9AU, 0x4E0EB4U, 0x4E165EU, + 0x4E278AU, 0x4E3F60U, 0x4E4422U, 0x4E5CC8U, 0x4E6D1CU, 0x4E75F6U, 0x4E8372U, 0x4E9B98U, 0x4EAA4CU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10EU, 0x4EE0DAU, 0x4EF830U, 0x4F0DD2U, 0x4F1538U, 0x4F24ECU, 0x4F3C06U, 0x4F4744U, 0x4F5FAEU, + 0x4F6E7AU, 0x4F7690U, 0x4F8014U, 0x4F98FEU, 0x4FA92AU, 0x4FB1C0U, 0x4FCA82U, 0x4FD268U, 0x4FE3BCU, 0x4FFB56U, + 0x50018EU, 0x501964U, 0x5028B0U, 0x50305AU, 0x504B18U, 0x5053F2U, 0x506226U, 0x507ACCU, 0x508C48U, 0x5094A2U, + 0x50A576U, 0x50BD9CU, 0x50C6DEU, 0x50DE34U, 0x50EFE0U, 0x50F70AU, 0x5102E8U, 0x511A02U, 0x512BD6U, 0x51333CU, + 0x51487EU, 0x515094U, 0x516140U, 0x5179AAU, 0x518F2EU, 0x5197C4U, 0x51A610U, 0x51BEFAU, 0x51C5B8U, 0x51DD52U, + 0x51EC86U, 0x51F46CU, 0x520742U, 0x521FA8U, 0x522E7CU, 0x523696U, 0x524DD4U, 0x52553EU, 0x5264EAU, 0x527C00U, + 0x528A84U, 0x52926EU, 0x52A3BAU, 0x52BB50U, 0x52C012U, 0x52D8F8U, 0x52E92CU, 0x52F1C6U, 0x530424U, 0x531CCEU, + 0x532D1AU, 0x5335F0U, 0x534EB2U, 0x535658U, 0x53678CU, 0x537F66U, 0x5389E2U, 0x539108U, 0x53A0DCU, 0x53B836U, + 0x53C374U, 0x53DB9EU, 0x53EA4AU, 0x53F2A0U, 0x540C16U, 0x5414FCU, 0x542528U, 0x543DC2U, 0x544680U, 0x545E6AU, + 0x546FBEU, 0x547754U, 0x5481D0U, 0x54993AU, 0x54A8EEU, 0x54B004U, 0x54CB46U, 0x54D3ACU, 0x54E278U, 0x54FA92U, + 0x550F70U, 0x55179AU, 0x55264EU, 0x553EA4U, 0x5545E6U, 0x555D0CU, 0x556CD8U, 0x557432U, 0x5582B6U, 0x559A5CU, + 0x55AB88U, 0x55B362U, 0x55C820U, 0x55D0CAU, 0x55E11EU, 0x55F9F4U, 0x560ADAU, 0x561230U, 0x5623E4U, 0x563B0EU, + 0x56404CU, 0x5658A6U, 0x566972U, 0x567198U, 0x56871CU, 0x569FF6U, 0x56AE22U, 0x56B6C8U, 0x56CD8AU, 0x56D560U, + 0x56E4B4U, 0x56FC5EU, 0x5709BCU, 0x571156U, 0x572082U, 0x573868U, 0x57432AU, 0x575BC0U, 0x576A14U, 0x5772FEU, + 0x57847AU, 0x579C90U, 0x57AD44U, 0x57B5AEU, 0x57CEECU, 0x57D606U, 0x57E7D2U, 0x57FF38U, 0x580254U, 0x581ABEU, + 0x582B6AU, 0x583380U, 0x5848C2U, 0x585028U, 0x5861FCU, 0x587916U, 0x588F92U, 0x589778U, 0x58A6ACU, 0x58BE46U, + 0x58C504U, 0x58DDEEU, 0x58EC3AU, 0x58F4D0U, 0x590132U, 0x5919D8U, 0x59280CU, 0x5930E6U, 0x594BA4U, 0x59534EU, + 0x59629AU, 0x597A70U, 0x598CF4U, 0x59941EU, 0x59A5CAU, 0x59BD20U, 0x59C662U, 0x59DE88U, 0x59EF5CU, 0x59F7B6U, + 0x5A0498U, 0x5A1C72U, 0x5A2DA6U, 0x5A354CU, 0x5A4E0EU, 0x5A56E4U, 0x5A6730U, 0x5A7FDAU, 0x5A895EU, 0x5A91B4U, + 0x5AA060U, 0x5AB88AU, 0x5AC3C8U, 0x5ADB22U, 0x5AEAF6U, 0x5AF21CU, 0x5B07FEU, 0x5B1F14U, 0x5B2EC0U, 0x5B362AU, + 0x5B4D68U, 0x5B5582U, 0x5B6456U, 0x5B7CBCU, 0x5B8A38U, 0x5B92D2U, 0x5BA306U, 0x5BBBECU, 0x5BC0AEU, 0x5BD844U, + 0x5BE990U, 0x5BF17AU, 0x5C0FCCU, 0x5C1726U, 0x5C26F2U, 0x5C3E18U, 0x5C455AU, 0x5C5DB0U, 0x5C6C64U, 0x5C748EU, + 0x5C820AU, 0x5C9AE0U, 0x5CAB34U, 0x5CB3DEU, 0x5CC89CU, 0x5CD076U, 0x5CE1A2U, 0x5CF948U, 0x5D0CAAU, 0x5D1440U, + 0x5D2594U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED6U, 0x5D6F02U, 0x5D77E8U, 0x5D816CU, 0x5D9986U, 0x5DA852U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD310U, 0x5DE2C4U, 0x5DFA2EU, 0x5E0900U, 0x5E11EAU, 0x5E203EU, 0x5E38D4U, 0x5E4396U, 0x5E5B7CU, + 0x5E6AA8U, 0x5E7242U, 0x5E84C6U, 0x5E9C2CU, 0x5EADF8U, 0x5EB512U, 0x5ECE50U, 0x5ED6BAU, 0x5EE76EU, 0x5EFF84U, + 0x5F0A66U, 0x5F128CU, 0x5F2358U, 0x5F3BB2U, 0x5F40F0U, 0x5F581AU, 0x5F69CEU, 0x5F7124U, 0x5F87A0U, 0x5F9F4AU, + 0x5FAE9EU, 0x5FB674U, 0x5FCD36U, 0x5FD5DCU, 0x5FE408U, 0x5FFCE2U, 0x600952U, 0x6011B8U, 0x60206CU, 0x603886U, + 0x6043C4U, 0x605B2EU, 0x606AFAU, 0x607210U, 0x608494U, 0x609C7EU, 0x60ADAAU, 0x60B540U, 0x60CE02U, 0x60D6E8U, + 0x60E73CU, 0x60FFD6U, 0x610A34U, 0x6112DEU, 0x61230AU, 0x613BE0U, 0x6140A2U, 0x615848U, 0x61699CU, 0x617176U, + 0x6187F2U, 0x619F18U, 0x61AECCU, 0x61B626U, 0x61CD64U, 0x61D58EU, 0x61E45AU, 0x61FCB0U, 0x620F9EU, 0x621774U, + 0x6226A0U, 0x623E4AU, 0x624508U, 0x625DE2U, 0x626C36U, 0x6274DCU, 0x628258U, 0x629AB2U, 0x62AB66U, 0x62B38CU, + 0x62C8CEU, 0x62D024U, 0x62E1F0U, 0x62F91AU, 0x630CF8U, 0x631412U, 0x6325C6U, 0x633D2CU, 0x63466EU, 0x635E84U, + 0x636F50U, 0x6377BAU, 0x63813EU, 0x6399D4U, 0x63A800U, 0x63B0EAU, 0x63CBA8U, 0x63D342U, 0x63E296U, 0x63FA7CU, + 0x6404CAU, 0x641C20U, 0x642DF4U, 0x64351EU, 0x644E5CU, 0x6456B6U, 0x646762U, 0x647F88U, 0x64890CU, 0x6491E6U, + 0x64A032U, 0x64B8D8U, 0x64C39AU, 0x64DB70U, 0x64EAA4U, 0x64F24EU, 0x6507ACU, 0x651F46U, 0x652E92U, 0x653678U, + 0x654D3AU, 0x6555D0U, 0x656404U, 0x657CEEU, 0x658A6AU, 0x659280U, 0x65A354U, 0x65BBBEU, 0x65C0FCU, 0x65D816U, + 0x65E9C2U, 0x65F128U, 0x660206U, 0x661AECU, 0x662B38U, 0x6633D2U, 0x664890U, 0x66507AU, 0x6661AEU, 0x667944U, + 0x668FC0U, 0x66972AU, 0x66A6FEU, 0x66BE14U, 0x66C556U, 0x66DDBCU, 0x66EC68U, 0x66F482U, 0x670160U, 0x67198AU, + 0x67285EU, 0x6730B4U, 0x674BF6U, 0x67531CU, 0x6762C8U, 0x677A22U, 0x678CA6U, 0x67944CU, 0x67A598U, 0x67BD72U, + 0x67C630U, 0x67DEDAU, 0x67EF0EU, 0x67F7E4U, 0x680A88U, 0x681262U, 0x6823B6U, 0x683B5CU, 0x68401EU, 0x6858F4U, + 0x686920U, 0x6871CAU, 0x68874EU, 0x689FA4U, 0x68AE70U, 0x68B69AU, 0x68CDD8U, 0x68D532U, 0x68E4E6U, 0x68FC0CU, + 0x6909EEU, 0x691104U, 0x6920D0U, 0x69383AU, 0x694378U, 0x695B92U, 0x696A46U, 0x6972ACU, 0x698428U, 0x699CC2U, + 0x69AD16U, 0x69B5FCU, 0x69CEBEU, 0x69D654U, 0x69E780U, 0x69FF6AU, 0x6A0C44U, 0x6A14AEU, 0x6A257AU, 0x6A3D90U, + 0x6A46D2U, 0x6A5E38U, 0x6A6FECU, 0x6A7706U, 0x6A8182U, 0x6A9968U, 0x6AA8BCU, 0x6AB056U, 0x6ACB14U, 0x6AD3FEU, + 0x6AE22AU, 0x6AFAC0U, 0x6B0F22U, 0x6B17C8U, 0x6B261CU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5EU, 0x6B6C8AU, 0x6B7460U, + 0x6B82E4U, 0x6B9A0EU, 0x6BABDAU, 0x6BB330U, 0x6BC872U, 0x6BD098U, 0x6BE14CU, 0x6BF9A6U, 0x6C0710U, 0x6C1FFAU, + 0x6C2E2EU, 0x6C36C4U, 0x6C4D86U, 0x6C556CU, 0x6C64B8U, 0x6C7C52U, 0x6C8AD6U, 0x6C923CU, 0x6CA3E8U, 0x6CBB02U, + 0x6CC040U, 0x6CD8AAU, 0x6CE97EU, 0x6CF194U, 0x6D0476U, 0x6D1C9CU, 0x6D2D48U, 0x6D35A2U, 0x6D4EE0U, 0x6D560AU, + 0x6D67DEU, 0x6D7F34U, 0x6D89B0U, 0x6D915AU, 0x6DA08EU, 0x6DB864U, 0x6DC326U, 0x6DDBCCU, 0x6DEA18U, 0x6DF2F2U, + 0x6E01DCU, 0x6E1936U, 0x6E28E2U, 0x6E3008U, 0x6E4B4AU, 0x6E53A0U, 0x6E6274U, 0x6E7A9EU, 0x6E8C1AU, 0x6E94F0U, + 0x6EA524U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE66U, 0x6EEFB2U, 0x6EF758U, 0x6F02BAU, 0x6F1A50U, 0x6F2B84U, 0x6F336EU, + 0x6F482CU, 0x6F50C6U, 0x6F6112U, 0x6F79F8U, 0x6F8F7CU, 0x6F9796U, 0x6FA642U, 0x6FBEA8U, 0x6FC5EAU, 0x6FDD00U, + 0x6FECD4U, 0x6FF43EU, 0x700EE6U, 0x70160CU, 0x7027D8U, 0x703F32U, 0x704470U, 0x705C9AU, 0x706D4EU, 0x7075A4U, + 0x708320U, 0x709BCAU, 0x70AA1EU, 0x70B2F4U, 0x70C9B6U, 0x70D15CU, 0x70E088U, 0x70F862U, 0x710D80U, 0x71156AU, + 0x7124BEU, 0x713C54U, 0x714716U, 0x715FFCU, 0x716E28U, 0x7176C2U, 0x718046U, 0x7198ACU, 0x71A978U, 0x71B192U, + 0x71CAD0U, 0x71D23AU, 0x71E3EEU, 0x71FB04U, 0x72082AU, 0x7210C0U, 0x722114U, 0x7239FEU, 0x7242BCU, 0x725A56U, + 0x726B82U, 0x727368U, 0x7285ECU, 0x729D06U, 0x72ACD2U, 0x72B438U, 0x72CF7AU, 0x72D790U, 0x72E644U, 0x72FEAEU, + 0x730B4CU, 0x7313A6U, 0x732272U, 0x733A98U, 0x7341DAU, 0x735930U, 0x7368E4U, 0x73700EU, 0x73868AU, 0x739E60U, + 0x73AFB4U, 0x73B75EU, 0x73CC1CU, 0x73D4F6U, 0x73E522U, 0x73FDC8U, 0x74037EU, 0x741B94U, 0x742A40U, 0x7432AAU, + 0x7449E8U, 0x745102U, 0x7460D6U, 0x74783CU, 0x748EB8U, 0x749652U, 0x74A786U, 0x74BF6CU, 0x74C42EU, 0x74DCC4U, + 0x74ED10U, 0x74F5FAU, 0x750018U, 0x7518F2U, 0x752926U, 0x7531CCU, 0x754A8EU, 0x755264U, 0x7563B0U, 0x757B5AU, + 0x758DDEU, 0x759534U, 0x75A4E0U, 0x75BC0AU, 0x75C748U, 0x75DFA2U, 0x75EE76U, 0x75F69CU, 0x7605B2U, 0x761D58U, + 0x762C8CU, 0x763466U, 0x764F24U, 0x7657CEU, 0x76661AU, 0x767EF0U, 0x768874U, 0x76909EU, 0x76A14AU, 0x76B9A0U, + 0x76C2E2U, 0x76DA08U, 0x76EBDCU, 0x76F336U, 0x7706D4U, 0x771E3EU, 0x772FEAU, 0x773700U, 0x774C42U, 0x7754A8U, + 0x77657CU, 0x777D96U, 0x778B12U, 0x7793F8U, 0x77A22CU, 0x77BAC6U, 0x77C184U, 0x77D96EU, 0x77E8BAU, 0x77F050U, + 0x780D3CU, 0x7815D6U, 0x782402U, 0x783CE8U, 0x7847AAU, 0x785F40U, 0x786E94U, 0x78767EU, 0x7880FAU, 0x789810U, + 0x78A9C4U, 0x78B12EU, 0x78CA6CU, 0x78D286U, 0x78E352U, 0x78FBB8U, 0x790E5AU, 0x7916B0U, 0x792764U, 0x793F8EU, + 0x7944CCU, 0x795C26U, 0x796DF2U, 0x797518U, 0x79839CU, 0x799B76U, 0x79AAA2U, 0x79B248U, 0x79C90AU, 0x79D1E0U, + 0x79E034U, 0x79F8DEU, 0x7A0BF0U, 0x7A131AU, 0x7A22CEU, 0x7A3A24U, 0x7A4166U, 0x7A598CU, 0x7A6858U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDCU, 0x7AAF08U, 0x7AB7E2U, 0x7ACCA0U, 0x7AD44AU, 0x7AE59EU, 0x7AFD74U, 0x7B0896U, 0x7B107CU, + 0x7B21A8U, 0x7B3942U, 0x7B4200U, 0x7B5AEAU, 0x7B6B3EU, 0x7B73D4U, 0x7B8550U, 0x7B9DBAU, 0x7BAC6EU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72CU, 0x7BE6F8U, 0x7BFE12U, 0x7C00A4U, 0x7C184EU, 0x7C299AU, 0x7C3170U, 0x7C4A32U, 0x7C52D8U, + 0x7C630CU, 0x7C7BE6U, 0x7C8D62U, 0x7C9588U, 0x7CA45CU, 0x7CBCB6U, 0x7CC7F4U, 0x7CDF1EU, 0x7CEECAU, 0x7CF620U, + 0x7D03C2U, 0x7D1B28U, 0x7D2AFCU, 0x7D3216U, 0x7D4954U, 0x7D51BEU, 0x7D606AU, 0x7D7880U, 0x7D8E04U, 0x7D96EEU, + 0x7DA73AU, 0x7DBFD0U, 0x7DC492U, 0x7DDC78U, 0x7DEDACU, 0x7DF546U, 0x7E0668U, 0x7E1E82U, 0x7E2F56U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5414U, 0x7E65C0U, 0x7E7D2AU, 0x7E8BAEU, 0x7E9344U, 0x7EA290U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D2U, + 0x7EE806U, 0x7EF0ECU, 0x7F050EU, 0x7F1DE4U, 0x7F2C30U, 0x7F34DAU, 0x7F4F98U, 0x7F5772U, 0x7F66A6U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9022U, 0x7FA1F6U, 0x7FB91CU, 0x7FC25EU, 0x7FDAB4U, 0x7FEB60U, 0x7FF38AU, 0x800C74U, 0x80149EU, + 0x80254AU, 0x803DA0U, 0x8046E2U, 0x805E08U, 0x806FDCU, 0x807736U, 0x8081B2U, 0x809958U, 0x80A88CU, 0x80B066U, + 0x80CB24U, 0x80D3CEU, 0x80E21AU, 0x80FAF0U, 0x810F12U, 0x8117F8U, 0x81262CU, 0x813EC6U, 0x814584U, 0x815D6EU, + 0x816CBAU, 0x817450U, 0x8182D4U, 0x819A3EU, 0x81ABEAU, 0x81B300U, 0x81C842U, 0x81D0A8U, 0x81E17CU, 0x81F996U, + 0x820AB8U, 0x821252U, 0x822386U, 0x823B6CU, 0x82402EU, 0x8258C4U, 0x826910U, 0x8271FAU, 0x82877EU, 0x829F94U, + 0x82AE40U, 0x82B6AAU, 0x82CDE8U, 0x82D502U, 0x82E4D6U, 0x82FC3CU, 0x8309DEU, 0x831134U, 0x8320E0U, 0x83380AU, + 0x834348U, 0x835BA2U, 0x836A76U, 0x83729CU, 0x838418U, 0x839CF2U, 0x83AD26U, 0x83B5CCU, 0x83CE8EU, 0x83D664U, + 0x83E7B0U, 0x83FF5AU, 0x8401ECU, 0x841906U, 0x8428D2U, 0x843038U, 0x844B7AU, 0x845390U, 0x846244U, 0x847AAEU, + 0x848C2AU, 0x8494C0U, 0x84A514U, 0x84BDFEU, 0x84C6BCU, 0x84DE56U, 0x84EF82U, 0x84F768U, 0x85028AU, 0x851A60U, + 0x852BB4U, 0x85335EU, 0x85481CU, 0x8550F6U, 0x856122U, 0x8579C8U, 0x858F4CU, 0x8597A6U, 0x85A672U, 0x85BE98U, + 0x85C5DAU, 0x85DD30U, 0x85ECE4U, 0x85F40EU, 0x860720U, 0x861FCAU, 0x862E1EU, 0x8636F4U, 0x864DB6U, 0x86555CU, + 0x866488U, 0x867C62U, 0x868AE6U, 0x86920CU, 0x86A3D8U, 0x86BB32U, 0x86C070U, 0x86D89AU, 0x86E94EU, 0x86F1A4U, + 0x870446U, 0x871CACU, 0x872D78U, 0x873592U, 0x874ED0U, 0x87563AU, 0x8767EEU, 0x877F04U, 0x878980U, 0x87916AU, + 0x87A0BEU, 0x87B854U, 0x87C316U, 0x87DBFCU, 0x87EA28U, 0x87F2C2U, 0x880FAEU, 0x881744U, 0x882690U, 0x883E7AU, + 0x884538U, 0x885DD2U, 0x886C06U, 0x8874ECU, 0x888268U, 0x889A82U, 0x88AB56U, 0x88B3BCU, 0x88C8FEU, 0x88D014U, + 0x88E1C0U, 0x88F92AU, 0x890CC8U, 0x891422U, 0x8925F6U, 0x893D1CU, 0x89465EU, 0x895EB4U, 0x896F60U, 0x89778AU, + 0x89810EU, 0x8999E4U, 0x89A830U, 0x89B0DAU, 0x89CB98U, 0x89D372U, 0x89E2A6U, 0x89FA4CU, 0x8A0962U, 0x8A1188U, + 0x8A205CU, 0x8A38B6U, 0x8A43F4U, 0x8A5B1EU, 0x8A6ACAU, 0x8A7220U, 0x8A84A4U, 0x8A9C4EU, 0x8AAD9AU, 0x8AB570U, + 0x8ACE32U, 0x8AD6D8U, 0x8AE70CU, 0x8AFFE6U, 0x8B0A04U, 0x8B12EEU, 0x8B233AU, 0x8B3BD0U, 0x8B4092U, 0x8B5878U, + 0x8B69ACU, 0x8B7146U, 0x8B87C2U, 0x8B9F28U, 0x8BAEFCU, 0x8BB616U, 0x8BCD54U, 0x8BD5BEU, 0x8BE46AU, 0x8BFC80U, + 0x8C0236U, 0x8C1ADCU, 0x8C2B08U, 0x8C33E2U, 0x8C48A0U, 0x8C504AU, 0x8C619EU, 0x8C7974U, 0x8C8FF0U, 0x8C971AU, + 0x8CA6CEU, 0x8CBE24U, 0x8CC566U, 0x8CDD8CU, 0x8CEC58U, 0x8CF4B2U, 0x8D0150U, 0x8D19BAU, 0x8D286EU, 0x8D3084U, + 0x8D4BC6U, 0x8D532CU, 0x8D62F8U, 0x8D7A12U, 0x8D8C96U, 0x8D947CU, 0x8DA5A8U, 0x8DBD42U, 0x8DC600U, 0x8DDEEAU, + 0x8DEF3EU, 0x8DF7D4U, 0x8E04FAU, 0x8E1C10U, 0x8E2DC4U, 0x8E352EU, 0x8E4E6CU, 0x8E5686U, 0x8E6752U, 0x8E7FB8U, + 0x8E893CU, 0x8E91D6U, 0x8EA002U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB40U, 0x8EEA94U, 0x8EF27EU, 0x8F079CU, 0x8F1F76U, + 0x8F2EA2U, 0x8F3648U, 0x8F4D0AU, 0x8F55E0U, 0x8F6434U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B0U, 0x8FA364U, 0x8FBB8EU, + 0x8FC0CCU, 0x8FD826U, 0x8FE9F2U, 0x8FF118U, 0x900BC0U, 0x90132AU, 0x9022FEU, 0x903A14U, 0x904156U, 0x9059BCU, + 0x906868U, 0x907082U, 0x908606U, 0x909EECU, 0x90AF38U, 0x90B7D2U, 0x90CC90U, 0x90D47AU, 0x90E5AEU, 0x90FD44U, + 0x9108A6U, 0x91104CU, 0x912198U, 0x913972U, 0x914230U, 0x915ADAU, 0x916B0EU, 0x9173E4U, 0x918560U, 0x919D8AU, + 0x91AC5EU, 0x91B4B4U, 0x91CFF6U, 0x91D71CU, 0x91E6C8U, 0x91FE22U, 0x920D0CU, 0x9215E6U, 0x922432U, 0x923CD8U, + 0x92479AU, 0x925F70U, 0x926EA4U, 0x92764EU, 0x9280CAU, 0x929820U, 0x92A9F4U, 0x92B11EU, 0x92CA5CU, 0x92D2B6U, + 0x92E362U, 0x92FB88U, 0x930E6AU, 0x931680U, 0x932754U, 0x933FBEU, 0x9344FCU, 0x935C16U, 0x936DC2U, 0x937528U, + 0x9383ACU, 0x939B46U, 0x93AA92U, 0x93B278U, 0x93C93AU, 0x93D1D0U, 0x93E004U, 0x93F8EEU, 0x940658U, 0x941EB2U, + 0x942F66U, 0x94378CU, 0x944CCEU, 0x945424U, 0x9465F0U, 0x947D1AU, 0x948B9EU, 0x949374U, 0x94A2A0U, 0x94BA4AU, + 0x94C108U, 0x94D9E2U, 0x94E836U, 0x94F0DCU, 0x95053EU, 0x951DD4U, 0x952C00U, 0x9534EAU, 0x954FA8U, 0x955742U, + 0x956696U, 0x957E7CU, 0x9588F8U, 0x959012U, 0x95A1C6U, 0x95B92CU, 0x95C26EU, 0x95DA84U, 0x95EB50U, 0x95F3BAU, + 0x960094U, 0x96187EU, 0x9629AAU, 0x963140U, 0x964A02U, 0x9652E8U, 0x96633CU, 0x967BD6U, 0x968D52U, 0x9695B8U, + 0x96A46CU, 0x96BC86U, 0x96C7C4U, 0x96DF2EU, 0x96EEFAU, 0x96F610U, 0x9703F2U, 0x971B18U, 0x972ACCU, 0x973226U, + 0x974964U, 0x97518EU, 0x97605AU, 0x9778B0U, 0x978E34U, 0x9796DEU, 0x97A70AU, 0x97BFE0U, 0x97C4A2U, 0x97DC48U, + 0x97ED9CU, 0x97F576U, 0x98081AU, 0x9810F0U, 0x982124U, 0x9839CEU, 0x98428CU, 0x985A66U, 0x986BB2U, 0x987358U, + 0x9885DCU, 0x989D36U, 0x98ACE2U, 0x98B408U, 0x98CF4AU, 0x98D7A0U, 0x98E674U, 0x98FE9EU, 0x990B7CU, 0x991396U, + 0x992242U, 0x993AA8U, 0x9941EAU, 0x995900U, 0x9968D4U, 0x99703EU, 0x9986BAU, 0x999E50U, 0x99AF84U, 0x99B76EU, + 0x99CC2CU, 0x99D4C6U, 0x99E512U, 0x99FDF8U, 0x9A0ED6U, 0x9A163CU, 0x9A27E8U, 0x9A3F02U, 0x9A4440U, 0x9A5CAAU, + 0x9A6D7EU, 0x9A7594U, 0x9A8310U, 0x9A9BFAU, 0x9AAA2EU, 0x9AB2C4U, 0x9AC986U, 0x9AD16CU, 0x9AE0B8U, 0x9AF852U, + 0x9B0DB0U, 0x9B155AU, 0x9B248EU, 0x9B3C64U, 0x9B4726U, 0x9B5FCCU, 0x9B6E18U, 0x9B76F2U, 0x9B8076U, 0x9B989CU, + 0x9BA948U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20AU, 0x9BE3DEU, 0x9BFB34U, 0x9C0582U, 0x9C1D68U, 0x9C2CBCU, 0x9C3456U, + 0x9C4F14U, 0x9C57FEU, 0x9C662AU, 0x9C7EC0U, 0x9C8844U, 0x9C90AEU, 0x9CA17AU, 0x9CB990U, 0x9CC2D2U, 0x9CDA38U, + 0x9CEBECU, 0x9CF306U, 0x9D06E4U, 0x9D1E0EU, 0x9D2FDAU, 0x9D3730U, 0x9D4C72U, 0x9D5498U, 0x9D654CU, 0x9D7DA6U, + 0x9D8B22U, 0x9D93C8U, 0x9DA21CU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95EU, 0x9DE88AU, 0x9DF060U, 0x9E034EU, 0x9E1BA4U, + 0x9E2A70U, 0x9E329AU, 0x9E49D8U, 0x9E5132U, 0x9E60E6U, 0x9E780CU, 0x9E8E88U, 0x9E9662U, 0x9EA7B6U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF4U, 0x9EED20U, 0x9EF5CAU, 0x9F0028U, 0x9F18C2U, 0x9F2916U, 0x9F31FCU, 0x9F4ABEU, 0x9F5254U, + 0x9F6380U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9504U, 0x9FA4D0U, 0x9FBC3AU, 0x9FC778U, 0x9FDF92U, 0x9FEE46U, 0x9FF6ACU, + 0xA0031CU, 0xA01BF6U, 0xA02A22U, 0xA032C8U, 0xA0498AU, 0xA05160U, 0xA060B4U, 0xA0785EU, 0xA08EDAU, 0xA09630U, + 0xA0A7E4U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA6U, 0xA0ED72U, 0xA0F598U, 0xA1007AU, 0xA11890U, 0xA12944U, 0xA131AEU, + 0xA14AECU, 0xA15206U, 0xA163D2U, 0xA17B38U, 0xA18DBCU, 0xA19556U, 0xA1A482U, 0xA1BC68U, 0xA1C72AU, 0xA1DFC0U, + 0xA1EE14U, 0xA1F6FEU, 0xA205D0U, 0xA21D3AU, 0xA22CEEU, 0xA23404U, 0xA24F46U, 0xA257ACU, 0xA26678U, 0xA27E92U, + 0xA28816U, 0xA290FCU, 0xA2A128U, 0xA2B9C2U, 0xA2C280U, 0xA2DA6AU, 0xA2EBBEU, 0xA2F354U, 0xA306B6U, 0xA31E5CU, + 0xA32F88U, 0xA33762U, 0xA34C20U, 0xA354CAU, 0xA3651EU, 0xA37DF4U, 0xA38B70U, 0xA3939AU, 0xA3A24EU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90CU, 0xA3E8D8U, 0xA3F032U, 0xA40E84U, 0xA4166EU, 0xA427BAU, 0xA43F50U, 0xA44412U, 0xA45CF8U, + 0xA46D2CU, 0xA475C6U, 0xA48342U, 0xA49BA8U, 0xA4AA7CU, 0xA4B296U, 0xA4C9D4U, 0xA4D13EU, 0xA4E0EAU, 0xA4F800U, + 0xA50DE2U, 0xA51508U, 0xA524DCU, 0xA53C36U, 0xA54774U, 0xA55F9EU, 0xA56E4AU, 0xA576A0U, 0xA58024U, 0xA598CEU, + 0xA5A91AU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D258U, 0xA5E38CU, 0xA5FB66U, 0xA60848U, 0xA610A2U, 0xA62176U, 0xA6399CU, + 0xA642DEU, 0xA65A34U, 0xA66BE0U, 0xA6730AU, 0xA6858EU, 0xA69D64U, 0xA6ACB0U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F2U, + 0xA6E626U, 0xA6FECCU, 0xA70B2EU, 0xA713C4U, 0xA72210U, 0xA73AFAU, 0xA741B8U, 0xA75952U, 0xA76886U, 0xA7706CU, + 0xA786E8U, 0xA79E02U, 0xA7AFD6U, 0xA7B73CU, 0xA7CC7EU, 0xA7D494U, 0xA7E540U, 0xA7FDAAU, 0xA800C6U, 0xA8182CU, + 0xA829F8U, 0xA83112U, 0xA84A50U, 0xA852BAU, 0xA8636EU, 0xA87B84U, 0xA88D00U, 0xA895EAU, 0xA8A43EU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7CU, 0xA8EEA8U, 0xA8F642U, 0xA903A0U, 0xA91B4AU, 0xA92A9EU, 0xA93274U, 0xA94936U, 0xA951DCU, + 0xA96008U, 0xA978E2U, 0xA98E66U, 0xA9968CU, 0xA9A758U, 0xA9BFB2U, 0xA9C4F0U, 0xA9DC1AU, 0xA9EDCEU, 0xA9F524U, + 0xAA060AU, 0xAA1EE0U, 0xAA2F34U, 0xAA37DEU, 0xAA4C9CU, 0xAA5476U, 0xAA65A2U, 0xAA7D48U, 0xAA8BCCU, 0xAA9326U, + 0xAAA2F2U, 0xAABA18U, 0xAAC15AU, 0xAAD9B0U, 0xAAE864U, 0xAAF08EU, 0xAB056CU, 0xAB1D86U, 0xAB2C52U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5710U, 0xAB66C4U, 0xAB7E2EU, 0xAB88AAU, 0xAB9040U, 0xABA194U, 0xABB97EU, 0xABC23CU, 0xABDAD6U, + 0xABEB02U, 0xABF3E8U, 0xAC0D5EU, 0xAC15B4U, 0xAC2460U, 0xAC3C8AU, 0xAC47C8U, 0xAC5F22U, 0xAC6EF6U, 0xAC761CU, + 0xAC8098U, 0xAC9872U, 0xACA9A6U, 0xACB14CU, 0xACCA0EU, 0xACD2E4U, 0xACE330U, 0xACFBDAU, 0xAD0E38U, 0xAD16D2U, + 0xAD2706U, 0xAD3FECU, 0xAD44AEU, 0xAD5C44U, 0xAD6D90U, 0xAD757AU, 0xAD83FEU, 0xAD9B14U, 0xADAAC0U, 0xADB22AU, + 0xADC968U, 0xADD182U, 0xADE056U, 0xADF8BCU, 0xAE0B92U, 0xAE1378U, 0xAE22ACU, 0xAE3A46U, 0xAE4104U, 0xAE59EEU, + 0xAE683AU, 0xAE70D0U, 0xAE8654U, 0xAE9EBEU, 0xAEAF6AU, 0xAEB780U, 0xAECCC2U, 0xAED428U, 0xAEE5FCU, 0xAEFD16U, + 0xAF08F4U, 0xAF101EU, 0xAF21CAU, 0xAF3920U, 0xAF4262U, 0xAF5A88U, 0xAF6B5CU, 0xAF73B6U, 0xAF8532U, 0xAF9DD8U, + 0xAFAC0CU, 0xAFB4E6U, 0xAFCFA4U, 0xAFD74EU, 0xAFE69AU, 0xAFFE70U, 0xB004A8U, 0xB01C42U, 0xB02D96U, 0xB0357CU, + 0xB04E3EU, 0xB056D4U, 0xB06700U, 0xB07FEAU, 0xB0896EU, 0xB09184U, 0xB0A050U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB12U, + 0xB0EAC6U, 0xB0F22CU, 0xB107CEU, 0xB11F24U, 0xB12EF0U, 0xB1361AU, 0xB14D58U, 0xB155B2U, 0xB16466U, 0xB17C8CU, + 0xB18A08U, 0xB192E2U, 0xB1A336U, 0xB1BBDCU, 0xB1C09EU, 0xB1D874U, 0xB1E9A0U, 0xB1F14AU, 0xB20264U, 0xB21A8EU, + 0xB22B5AU, 0xB233B0U, 0xB248F2U, 0xB25018U, 0xB261CCU, 0xB27926U, 0xB28FA2U, 0xB29748U, 0xB2A69CU, 0xB2BE76U, + 0xB2C534U, 0xB2DDDEU, 0xB2EC0AU, 0xB2F4E0U, 0xB30102U, 0xB319E8U, 0xB3283CU, 0xB330D6U, 0xB34B94U, 0xB3537EU, + 0xB362AAU, 0xB37A40U, 0xB38CC4U, 0xB3942EU, 0xB3A5FAU, 0xB3BD10U, 0xB3C652U, 0xB3DEB8U, 0xB3EF6CU, 0xB3F786U, + 0xB40930U, 0xB411DAU, 0xB4200EU, 0xB438E4U, 0xB443A6U, 0xB45B4CU, 0xB46A98U, 0xB47272U, 0xB484F6U, 0xB49C1CU, + 0xB4ADC8U, 0xB4B522U, 0xB4CE60U, 0xB4D68AU, 0xB4E75EU, 0xB4FFB4U, 0xB50A56U, 0xB512BCU, 0xB52368U, 0xB53B82U, + 0xB540C0U, 0xB5582AU, 0xB569FEU, 0xB57114U, 0xB58790U, 0xB59F7AU, 0xB5AEAEU, 0xB5B644U, 0xB5CD06U, 0xB5D5ECU, + 0xB5E438U, 0xB5FCD2U, 0xB60FFCU, 0xB61716U, 0xB626C2U, 0xB63E28U, 0xB6456AU, 0xB65D80U, 0xB66C54U, 0xB674BEU, + 0xB6823AU, 0xB69AD0U, 0xB6AB04U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D046U, 0xB6E192U, 0xB6F978U, 0xB70C9AU, 0xB71470U, + 0xB725A4U, 0xB73D4EU, 0xB7460CU, 0xB75EE6U, 0xB76F32U, 0xB777D8U, 0xB7815CU, 0xB799B6U, 0xB7A862U, 0xB7B088U, + 0xB7CBCAU, 0xB7D320U, 0xB7E2F4U, 0xB7FA1EU, 0xB80772U, 0xB81F98U, 0xB82E4CU, 0xB836A6U, 0xB84DE4U, 0xB8550EU, + 0xB864DAU, 0xB87C30U, 0xB88AB4U, 0xB8925EU, 0xB8A38AU, 0xB8BB60U, 0xB8C022U, 0xB8D8C8U, 0xB8E91CU, 0xB8F1F6U, + 0xB90414U, 0xB91CFEU, 0xB92D2AU, 0xB935C0U, 0xB94E82U, 0xB95668U, 0xB967BCU, 0xB97F56U, 0xB989D2U, 0xB99138U, + 0xB9A0ECU, 0xB9B806U, 0xB9C344U, 0xB9DBAEU, 0xB9EA7AU, 0xB9F290U, 0xBA01BEU, 0xBA1954U, 0xBA2880U, 0xBA306AU, + 0xBA4B28U, 0xBA53C2U, 0xBA6216U, 0xBA7AFCU, 0xBA8C78U, 0xBA9492U, 0xBAA546U, 0xBABDACU, 0xBAC6EEU, 0xBADE04U, + 0xBAEFD0U, 0xBAF73AU, 0xBB02D8U, 0xBB1A32U, 0xBB2BE6U, 0xBB330CU, 0xBB484EU, 0xBB50A4U, 0xBB6170U, 0xBB799AU, + 0xBB8F1EU, 0xBB97F4U, 0xBBA620U, 0xBBBECAU, 0xBBC588U, 0xBBDD62U, 0xBBECB6U, 0xBBF45CU, 0xBC0AEAU, 0xBC1200U, + 0xBC23D4U, 0xBC3B3EU, 0xBC407CU, 0xBC5896U, 0xBC6942U, 0xBC71A8U, 0xBC872CU, 0xBC9FC6U, 0xBCAE12U, 0xBCB6F8U, + 0xBCCDBAU, 0xBCD550U, 0xBCE484U, 0xBCFC6EU, 0xBD098CU, 0xBD1166U, 0xBD20B2U, 0xBD3858U, 0xBD431AU, 0xBD5BF0U, + 0xBD6A24U, 0xBD72CEU, 0xBD844AU, 0xBD9CA0U, 0xBDAD74U, 0xBDB59EU, 0xBDCEDCU, 0xBDD636U, 0xBDE7E2U, 0xBDFF08U, + 0xBE0C26U, 0xBE14CCU, 0xBE2518U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5AU, 0xBE6F8EU, 0xBE7764U, 0xBE81E0U, 0xBE990AU, + 0xBEA8DEU, 0xBEB034U, 0xBECB76U, 0xBED39CU, 0xBEE248U, 0xBEFAA2U, 0xBF0F40U, 0xBF17AAU, 0xBF267EU, 0xBF3E94U, + 0xBF45D6U, 0xBF5D3CU, 0xBF6CE8U, 0xBF7402U, 0xBF8286U, 0xBF9A6CU, 0xBFABB8U, 0xBFB352U, 0xBFC810U, 0xBFD0FAU, + 0xBFE12EU, 0xBFF9C4U, 0xC00A4EU, 0xC012A4U, 0xC02370U, 0xC03B9AU, 0xC040D8U, 0xC05832U, 0xC069E6U, 0xC0710CU, + 0xC08788U, 0xC09F62U, 0xC0AEB6U, 0xC0B65CU, 0xC0CD1EU, 0xC0D5F4U, 0xC0E420U, 0xC0FCCAU, 0xC10928U, 0xC111C2U, + 0xC12016U, 0xC138FCU, 0xC143BEU, 0xC15B54U, 0xC16A80U, 0xC1726AU, 0xC184EEU, 0xC19C04U, 0xC1ADD0U, 0xC1B53AU, + 0xC1CE78U, 0xC1D692U, 0xC1E746U, 0xC1FFACU, 0xC20C82U, 0xC21468U, 0xC225BCU, 0xC23D56U, 0xC24614U, 0xC25EFEU, + 0xC26F2AU, 0xC277C0U, 0xC28144U, 0xC299AEU, 0xC2A87AU, 0xC2B090U, 0xC2CBD2U, 0xC2D338U, 0xC2E2ECU, 0xC2FA06U, + 0xC30FE4U, 0xC3170EU, 0xC326DAU, 0xC33E30U, 0xC34572U, 0xC35D98U, 0xC36C4CU, 0xC374A6U, 0xC38222U, 0xC39AC8U, + 0xC3AB1CU, 0xC3B3F6U, 0xC3C8B4U, 0xC3D05EU, 0xC3E18AU, 0xC3F960U, 0xC407D6U, 0xC41F3CU, 0xC42EE8U, 0xC43602U, + 0xC44D40U, 0xC455AAU, 0xC4647EU, 0xC47C94U, 0xC48A10U, 0xC492FAU, 0xC4A32EU, 0xC4BBC4U, 0xC4C086U, 0xC4D86CU, + 0xC4E9B8U, 0xC4F152U, 0xC504B0U, 0xC51C5AU, 0xC52D8EU, 0xC53564U, 0xC54E26U, 0xC556CCU, 0xC56718U, 0xC57FF2U, + 0xC58976U, 0xC5919CU, 0xC5A048U, 0xC5B8A2U, 0xC5C3E0U, 0xC5DB0AU, 0xC5EADEU, 0xC5F234U, 0xC6011AU, 0xC619F0U, + 0xC62824U, 0xC630CEU, 0xC64B8CU, 0xC65366U, 0xC662B2U, 0xC67A58U, 0xC68CDCU, 0xC69436U, 0xC6A5E2U, 0xC6BD08U, + 0xC6C64AU, 0xC6DEA0U, 0xC6EF74U, 0xC6F79EU, 0xC7027CU, 0xC71A96U, 0xC72B42U, 0xC733A8U, 0xC748EAU, 0xC75000U, + 0xC761D4U, 0xC7793EU, 0xC78FBAU, 0xC79750U, 0xC7A684U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC6U, 0xC7EC12U, 0xC7F4F8U, + 0xC80994U, 0xC8117EU, 0xC820AAU, 0xC83840U, 0xC84302U, 0xC85BE8U, 0xC86A3CU, 0xC872D6U, 0xC88452U, 0xC89CB8U, + 0xC8AD6CU, 0xC8B586U, 0xC8CEC4U, 0xC8D62EU, 0xC8E7FAU, 0xC8FF10U, 0xC90AF2U, 0xC91218U, 0xC923CCU, 0xC93B26U, + 0xC94064U, 0xC9588EU, 0xC9695AU, 0xC971B0U, 0xC98734U, 0xC99FDEU, 0xC9AE0AU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D548U, + 0xC9E49CU, 0xC9FC76U, 0xCA0F58U, 0xCA17B2U, 0xCA2666U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D24U, 0xCA6CF0U, 0xCA741AU, + 0xCA829EU, 0xCA9A74U, 0xCAABA0U, 0xCAB34AU, 0xCAC808U, 0xCAD0E2U, 0xCAE136U, 0xCAF9DCU, 0xCB0C3EU, 0xCB14D4U, + 0xCB2500U, 0xCB3DEAU, 0xCB46A8U, 0xCB5E42U, 0xCB6F96U, 0xCB777CU, 0xCB81F8U, 0xCB9912U, 0xCBA8C6U, 0xCBB02CU, + 0xCBCB6EU, 0xCBD384U, 0xCBE250U, 0xCBFABAU, 0xCC040CU, 0xCC1CE6U, 0xCC2D32U, 0xCC35D8U, 0xCC4E9AU, 0xCC5670U, + 0xCC67A4U, 0xCC7F4EU, 0xCC89CAU, 0xCC9120U, 0xCCA0F4U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB6U, 0xCCEA62U, 0xCCF288U, + 0xCD076AU, 0xCD1F80U, 0xCD2E54U, 0xCD36BEU, 0xCD4DFCU, 0xCD5516U, 0xCD64C2U, 0xCD7C28U, 0xCD8AACU, 0xCD9246U, + 0xCDA392U, 0xCDBB78U, 0xCDC03AU, 0xCDD8D0U, 0xCDE904U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2AU, 0xCE2BFEU, 0xCE3314U, + 0xCE4856U, 0xCE50BCU, 0xCE6168U, 0xCE7982U, 0xCE8F06U, 0xCE97ECU, 0xCEA638U, 0xCEBED2U, 0xCEC590U, 0xCEDD7AU, + 0xCEECAEU, 0xCEF444U, 0xCF01A6U, 0xCF194CU, 0xCF2898U, 0xCF3072U, 0xCF4B30U, 0xCF53DAU, 0xCF620EU, 0xCF7AE4U, + 0xCF8C60U, 0xCF948AU, 0xCFA55EU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1CU, 0xCFEFC8U, 0xCFF722U, 0xD00DFAU, 0xD01510U, + 0xD024C4U, 0xD03C2EU, 0xD0476CU, 0xD05F86U, 0xD06E52U, 0xD076B8U, 0xD0803CU, 0xD098D6U, 0xD0A902U, 0xD0B1E8U, + 0xD0CAAAU, 0xD0D240U, 0xD0E394U, 0xD0FB7EU, 0xD10E9CU, 0xD11676U, 0xD127A2U, 0xD13F48U, 0xD1440AU, 0xD15CE0U, + 0xD16D34U, 0xD175DEU, 0xD1835AU, 0xD19BB0U, 0xD1AA64U, 0xD1B28EU, 0xD1C9CCU, 0xD1D126U, 0xD1E0F2U, 0xD1F818U, + 0xD20B36U, 0xD213DCU, 0xD22208U, 0xD23AE2U, 0xD241A0U, 0xD2594AU, 0xD2689EU, 0xD27074U, 0xD286F0U, 0xD29E1AU, + 0xD2AFCEU, 0xD2B724U, 0xD2CC66U, 0xD2D48CU, 0xD2E558U, 0xD2FDB2U, 0xD30850U, 0xD310BAU, 0xD3216EU, 0xD33984U, + 0xD342C6U, 0xD35A2CU, 0xD36BF8U, 0xD37312U, 0xD38596U, 0xD39D7CU, 0xD3ACA8U, 0xD3B442U, 0xD3CF00U, 0xD3D7EAU, + 0xD3E63EU, 0xD3FED4U, 0xD40062U, 0xD41888U, 0xD4295CU, 0xD431B6U, 0xD44AF4U, 0xD4521EU, 0xD463CAU, 0xD47B20U, + 0xD48DA4U, 0xD4954EU, 0xD4A49AU, 0xD4BC70U, 0xD4C732U, 0xD4DFD8U, 0xD4EE0CU, 0xD4F6E6U, 0xD50304U, 0xD51BEEU, + 0xD52A3AU, 0xD532D0U, 0xD54992U, 0xD55178U, 0xD560ACU, 0xD57846U, 0xD58EC2U, 0xD59628U, 0xD5A7FCU, 0xD5BF16U, + 0xD5C454U, 0xD5DCBEU, 0xD5ED6AU, 0xD5F580U, 0xD606AEU, 0xD61E44U, 0xD62F90U, 0xD6377AU, 0xD64C38U, 0xD654D2U, + 0xD66506U, 0xD67DECU, 0xD68B68U, 0xD69382U, 0xD6A256U, 0xD6BABCU, 0xD6C1FEU, 0xD6D914U, 0xD6E8C0U, 0xD6F02AU, + 0xD705C8U, 0xD71D22U, 0xD72CF6U, 0xD7341CU, 0xD74F5EU, 0xD757B4U, 0xD76660U, 0xD77E8AU, 0xD7880EU, 0xD790E4U, + 0xD7A130U, 0xD7B9DAU, 0xD7C298U, 0xD7DA72U, 0xD7EBA6U, 0xD7F34CU, 0xD80E20U, 0xD816CAU, 0xD8271EU, 0xD83FF4U, + 0xD844B6U, 0xD85C5CU, 0xD86D88U, 0xD87562U, 0xD883E6U, 0xD89B0CU, 0xD8AAD8U, 0xD8B232U, 0xD8C970U, 0xD8D19AU, + 0xD8E04EU, 0xD8F8A4U, 0xD90D46U, 0xD915ACU, 0xD92478U, 0xD93C92U, 0xD947D0U, 0xD95F3AU, 0xD96EEEU, 0xD97604U, + 0xD98080U, 0xD9986AU, 0xD9A9BEU, 0xD9B154U, 0xD9CA16U, 0xD9D2FCU, 0xD9E328U, 0xD9FBC2U, 0xDA08ECU, 0xDA1006U, + 0xDA21D2U, 0xDA3938U, 0xDA427AU, 0xDA5A90U, 0xDA6B44U, 0xDA73AEU, 0xDA852AU, 0xDA9DC0U, 0xDAAC14U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD756U, 0xDAE682U, 0xDAFE68U, 0xDB0B8AU, 0xDB1360U, 0xDB22B4U, 0xDB3A5EU, 0xDB411CU, 0xDB59F6U, + 0xDB6822U, 0xDB70C8U, 0xDB864CU, 0xDB9EA6U, 0xDBAF72U, 0xDBB798U, 0xDBCCDAU, 0xDBD430U, 0xDBE5E4U, 0xDBFD0EU, + 0xDC03B8U, 0xDC1B52U, 0xDC2A86U, 0xDC326CU, 0xDC492EU, 0xDC51C4U, 0xDC6010U, 0xDC78FAU, 0xDC8E7EU, 0xDC9694U, + 0xDCA740U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC02U, 0xDCEDD6U, 0xDCF53CU, 0xDD00DEU, 0xDD1834U, 0xDD29E0U, 0xDD310AU, + 0xDD4A48U, 0xDD52A2U, 0xDD6376U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F2U, 0xDDA426U, 0xDDBCCCU, 0xDDC78EU, 0xDDDF64U, + 0xDDEEB0U, 0xDDF65AU, 0xDE0574U, 0xDE1D9EU, 0xDE2C4AU, 0xDE34A0U, 0xDE4FE2U, 0xDE5708U, 0xDE66DCU, 0xDE7E36U, + 0xDE88B2U, 0xDE9058U, 0xDEA18CU, 0xDEB966U, 0xDEC224U, 0xDEDACEU, 0xDEEB1AU, 0xDEF3F0U, 0xDF0612U, 0xDF1EF8U, + 0xDF2F2CU, 0xDF37C6U, 0xDF4C84U, 0xDF546EU, 0xDF65BAU, 0xDF7D50U, 0xDF8BD4U, 0xDF933EU, 0xDFA2EAU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A8U, 0xDFE87CU, 0xDFF096U, 0xE00526U, 0xE01DCCU, 0xE02C18U, 0xE034F2U, 0xE04FB0U, 0xE0575AU, + 0xE0668EU, 0xE07E64U, 0xE088E0U, 0xE0900AU, 0xE0A1DEU, 0xE0B934U, 0xE0C276U, 0xE0DA9CU, 0xE0EB48U, 0xE0F3A2U, + 0xE10640U, 0xE11EAAU, 0xE12F7EU, 0xE13794U, 0xE14CD6U, 0xE1543CU, 0xE165E8U, 0xE17D02U, 0xE18B86U, 0xE1936CU, + 0xE1A2B8U, 0xE1BA52U, 0xE1C110U, 0xE1D9FAU, 0xE1E82EU, 0xE1F0C4U, 0xE203EAU, 0xE21B00U, 0xE22AD4U, 0xE2323EU, + 0xE2497CU, 0xE25196U, 0xE26042U, 0xE278A8U, 0xE28E2CU, 0xE296C6U, 0xE2A712U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC50U, + 0xE2ED84U, 0xE2F56EU, 0xE3008CU, 0xE31866U, 0xE329B2U, 0xE33158U, 0xE34A1AU, 0xE352F0U, 0xE36324U, 0xE37BCEU, + 0xE38D4AU, 0xE395A0U, 0xE3A474U, 0xE3BC9EU, 0xE3C7DCU, 0xE3DF36U, 0xE3EEE2U, 0xE3F608U, 0xE408BEU, 0xE41054U, + 0xE42180U, 0xE4396AU, 0xE44228U, 0xE45AC2U, 0xE46B16U, 0xE473FCU, 0xE48578U, 0xE49D92U, 0xE4AC46U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D704U, 0xE4E6D0U, 0xE4FE3AU, 0xE50BD8U, 0xE51332U, 0xE522E6U, 0xE53A0CU, 0xE5414EU, 0xE559A4U, + 0xE56870U, 0xE5709AU, 0xE5861EU, 0xE59EF4U, 0xE5AF20U, 0xE5B7CAU, 0xE5CC88U, 0xE5D462U, 0xE5E5B6U, 0xE5FD5CU, + 0xE60E72U, 0xE61698U, 0xE6274CU, 0xE63FA6U, 0xE644E4U, 0xE65C0EU, 0xE66DDAU, 0xE67530U, 0xE683B4U, 0xE69B5EU, + 0xE6AA8AU, 0xE6B260U, 0xE6C922U, 0xE6D1C8U, 0xE6E01CU, 0xE6F8F6U, 0xE70D14U, 0xE715FEU, 0xE7242AU, 0xE73CC0U, + 0xE74782U, 0xE75F68U, 0xE76EBCU, 0xE77656U, 0xE780D2U, 0xE79838U, 0xE7A9ECU, 0xE7B106U, 0xE7CA44U, 0xE7D2AEU, + 0xE7E37AU, 0xE7FB90U, 0xE806FCU, 0xE81E16U, 0xE82FC2U, 0xE83728U, 0xE84C6AU, 0xE85480U, 0xE86554U, 0xE87DBEU, + 0xE88B3AU, 0xE893D0U, 0xE8A204U, 0xE8BAEEU, 0xE8C1ACU, 0xE8D946U, 0xE8E892U, 0xE8F078U, 0xE9059AU, 0xE91D70U, + 0xE92CA4U, 0xE9344EU, 0xE94F0CU, 0xE957E6U, 0xE96632U, 0xE97ED8U, 0xE9885CU, 0xE990B6U, 0xE9A162U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA20U, 0xE9EBF4U, 0xE9F31EU, 0xEA0030U, 0xEA18DAU, 0xEA290EU, 0xEA31E4U, 0xEA4AA6U, 0xEA524CU, + 0xEA6398U, 0xEA7B72U, 0xEA8DF6U, 0xEA951CU, 0xEAA4C8U, 0xEABC22U, 0xEAC760U, 0xEADF8AU, 0xEAEE5EU, 0xEAF6B4U, + 0xEB0356U, 0xEB1BBCU, 0xEB2A68U, 0xEB3282U, 0xEB49C0U, 0xEB512AU, 0xEB60FEU, 0xEB7814U, 0xEB8E90U, 0xEB967AU, + 0xEBA7AEU, 0xEBBF44U, 0xEBC406U, 0xEBDCECU, 0xEBED38U, 0xEBF5D2U, 0xEC0B64U, 0xEC138EU, 0xEC225AU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5918U, 0xEC68CCU, 0xEC7026U, 0xEC86A2U, 0xEC9E48U, 0xECAF9CU, 0xECB776U, 0xECCC34U, 0xECD4DEU, + 0xECE50AU, 0xECFDE0U, 0xED0802U, 0xED10E8U, 0xED213CU, 0xED39D6U, 0xED4294U, 0xED5A7EU, 0xED6BAAU, 0xED7340U, + 0xED85C4U, 0xED9D2EU, 0xEDACFAU, 0xEDB410U, 0xEDCF52U, 0xEDD7B8U, 0xEDE66CU, 0xEDFE86U, 0xEE0DA8U, 0xEE1542U, + 0xEE2496U, 0xEE3C7CU, 0xEE473EU, 0xEE5FD4U, 0xEE6E00U, 0xEE76EAU, 0xEE806EU, 0xEE9884U, 0xEEA950U, 0xEEB1BAU, + 0xEECAF8U, 0xEED212U, 0xEEE3C6U, 0xEEFB2CU, 0xEF0ECEU, 0xEF1624U, 0xEF27F0U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB2U, + 0xEF6D66U, 0xEF758CU, 0xEF8308U, 0xEF9BE2U, 0xEFAA36U, 0xEFB2DCU, 0xEFC99EU, 0xEFD174U, 0xEFE0A0U, 0xEFF84AU, + 0xF00292U, 0xF01A78U, 0xF02BACU, 0xF03346U, 0xF04804U, 0xF050EEU, 0xF0613AU, 0xF079D0U, 0xF08F54U, 0xF097BEU, + 0xF0A66AU, 0xF0BE80U, 0xF0C5C2U, 0xF0DD28U, 0xF0ECFCU, 0xF0F416U, 0xF101F4U, 0xF1191EU, 0xF128CAU, 0xF13020U, + 0xF14B62U, 0xF15388U, 0xF1625CU, 0xF17AB6U, 0xF18C32U, 0xF194D8U, 0xF1A50CU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4EU, + 0xF1EF9AU, 0xF1F770U, 0xF2045EU, 0xF21CB4U, 0xF22D60U, 0xF2358AU, 0xF24EC8U, 0xF25622U, 0xF267F6U, 0xF27F1CU, + 0xF28998U, 0xF29172U, 0xF2A0A6U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE4U, 0xF2EA30U, 0xF2F2DAU, 0xF30738U, 0xF31FD2U, + 0xF32E06U, 0xF336ECU, 0xF34DAEU, 0xF35544U, 0xF36490U, 0xF37C7AU, 0xF38AFEU, 0xF39214U, 0xF3A3C0U, 0xF3BB2AU, + 0xF3C068U, 0xF3D882U, 0xF3E956U, 0xF3F1BCU, 0xF40F0AU, 0xF417E0U, 0xF42634U, 0xF43EDEU, 0xF4459CU, 0xF45D76U, + 0xF46CA2U, 0xF47448U, 0xF482CCU, 0xF49A26U, 0xF4ABF2U, 0xF4B318U, 0xF4C85AU, 0xF4D0B0U, 0xF4E164U, 0xF4F98EU, + 0xF50C6CU, 0xF51486U, 0xF52552U, 0xF53DB8U, 0xF546FAU, 0xF55E10U, 0xF56FC4U, 0xF5772EU, 0xF581AAU, 0xF59940U, + 0xF5A894U, 0xF5B07EU, 0xF5CB3CU, 0xF5D3D6U, 0xF5E202U, 0xF5FAE8U, 0xF609C6U, 0xF6112CU, 0xF620F8U, 0xF63812U, + 0xF64350U, 0xF65BBAU, 0xF66A6EU, 0xF67284U, 0xF68400U, 0xF69CEAU, 0xF6AD3EU, 0xF6B5D4U, 0xF6CE96U, 0xF6D67CU, + 0xF6E7A8U, 0xF6FF42U, 0xF70AA0U, 0xF7124AU, 0xF7239EU, 0xF73B74U, 0xF74036U, 0xF758DCU, 0xF76908U, 0xF771E2U, + 0xF78766U, 0xF79F8CU, 0xF7AE58U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51AU, 0xF7E4CEU, 0xF7FC24U, 0xF80148U, 0xF819A2U, + 0xF82876U, 0xF8309CU, 0xF84BDEU, 0xF85334U, 0xF862E0U, 0xF87A0AU, 0xF88C8EU, 0xF89464U, 0xF8A5B0U, 0xF8BD5AU, + 0xF8C618U, 0xF8DEF2U, 0xF8EF26U, 0xF8F7CCU, 0xF9022EU, 0xF91AC4U, 0xF92B10U, 0xF933FAU, 0xF948B8U, 0xF95052U, + 0xF96186U, 0xF9796CU, 0xF98FE8U, 0xF99702U, 0xF9A6D6U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD94U, 0xF9EC40U, 0xF9F4AAU, + 0xFA0784U, 0xFA1F6EU, 0xFA2EBAU, 0xFA3650U, 0xFA4D12U, 0xFA55F8U, 0xFA642CU, 0xFA7CC6U, 0xFA8A42U, 0xFA92A8U, + 0xFAA37CU, 0xFABB96U, 0xFAC0D4U, 0xFAD83EU, 0xFAE9EAU, 0xFAF100U, 0xFB04E2U, 0xFB1C08U, 0xFB2DDCU, 0xFB3536U, + 0xFB4E74U, 0xFB569EU, 0xFB674AU, 0xFB7FA0U, 0xFB8924U, 0xFB91CEU, 0xFBA01AU, 0xFBB8F0U, 0xFBC3B2U, 0xFBDB58U, + 0xFBEA8CU, 0xFBF266U, 0xFC0CD0U, 0xFC143AU, 0xFC25EEU, 0xFC3D04U, 0xFC4646U, 0xFC5EACU, 0xFC6F78U, 0xFC7792U, + 0xFC8116U, 0xFC99FCU, 0xFCA828U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36AU, 0xFCE2BEU, 0xFCFA54U, 0xFD0FB6U, 0xFD175CU, + 0xFD2688U, 0xFD3E62U, 0xFD4520U, 0xFD5DCAU, 0xFD6C1EU, 0xFD74F4U, 0xFD8270U, 0xFD9A9AU, 0xFDAB4EU, 0xFDB3A4U, + 0xFDC8E6U, 0xFDD00CU, 0xFDE1D8U, 0xFDF932U, 0xFE0A1CU, 0xFE12F6U, 0xFE2322U, 0xFE3BC8U, 0xFE408AU, 0xFE5860U, + 0xFE69B4U, 0xFE715EU, 0xFE87DAU, 0xFE9F30U, 0xFEAEE4U, 0xFEB60EU, 0xFECD4CU, 0xFED5A6U, 0xFEE472U, 0xFEFC98U, + 0xFF097AU, 0xFF1190U, 0xFF2044U, 0xFF38AEU, 0xFF43ECU, 0xFF5B06U, 0xFF6AD2U, 0xFF7238U, 0xFF84BCU, 0xFF9C56U, + 0xFFAD82U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C0U, 0xFFE714U, 0xFFFFFEU}; + +static const unsigned int ENCODING_TABLE_24128[] = { + 0x000000U, 0x0018EBU, 0x00293EU, 0x0031D5U, 0x004A97U, 0x00527CU, 0x0063A9U, 0x007B42U, 0x008DC6U, 0x00952DU, + 0x00A4F8U, 0x00BC13U, 0x00C751U, 0x00DFBAU, 0x00EE6FU, 0x00F684U, 0x010367U, 0x011B8CU, 0x012A59U, 0x0132B2U, + 0x0149F0U, 0x01511BU, 0x0160CEU, 0x017825U, 0x018EA1U, 0x01964AU, 0x01A79FU, 0x01BF74U, 0x01C436U, 0x01DCDDU, + 0x01ED08U, 0x01F5E3U, 0x0206CDU, 0x021E26U, 0x022FF3U, 0x023718U, 0x024C5AU, 0x0254B1U, 0x026564U, 0x027D8FU, + 0x028B0BU, 0x0293E0U, 0x02A235U, 0x02BADEU, 0x02C19CU, 0x02D977U, 0x02E8A2U, 0x02F049U, 0x0305AAU, 0x031D41U, + 0x032C94U, 0x03347FU, 0x034F3DU, 0x0357D6U, 0x036603U, 0x037EE8U, 0x03886CU, 0x039087U, 0x03A152U, 0x03B9B9U, + 0x03C2FBU, 0x03DA10U, 0x03EBC5U, 0x03F32EU, 0x040D99U, 0x041572U, 0x0424A7U, 0x043C4CU, 0x04470EU, 0x045FE5U, + 0x046E30U, 0x0476DBU, 0x04805FU, 0x0498B4U, 0x04A961U, 0x04B18AU, 0x04CAC8U, 0x04D223U, 0x04E3F6U, 0x04FB1DU, + 0x050EFEU, 0x051615U, 0x0527C0U, 0x053F2BU, 0x054469U, 0x055C82U, 0x056D57U, 0x0575BCU, 0x058338U, 0x059BD3U, + 0x05AA06U, 0x05B2EDU, 0x05C9AFU, 0x05D144U, 0x05E091U, 0x05F87AU, 0x060B54U, 0x0613BFU, 0x06226AU, 0x063A81U, + 0x0641C3U, 0x065928U, 0x0668FDU, 0x067016U, 0x068692U, 0x069E79U, 0x06AFACU, 0x06B747U, 0x06CC05U, 0x06D4EEU, + 0x06E53BU, 0x06FDD0U, 0x070833U, 0x0710D8U, 0x07210DU, 0x0739E6U, 0x0742A4U, 0x075A4FU, 0x076B9AU, 0x077371U, + 0x0785F5U, 0x079D1EU, 0x07ACCBU, 0x07B420U, 0x07CF62U, 0x07D789U, 0x07E65CU, 0x07FEB7U, 0x0803DAU, 0x081B31U, + 0x082AE4U, 0x08320FU, 0x08494DU, 0x0851A6U, 0x086073U, 0x087898U, 0x088E1CU, 0x0896F7U, 0x08A722U, 0x08BFC9U, + 0x08C48BU, 0x08DC60U, 0x08EDB5U, 0x08F55EU, 0x0900BDU, 0x091856U, 0x092983U, 0x093168U, 0x094A2AU, 0x0952C1U, + 0x096314U, 0x097BFFU, 0x098D7BU, 0x099590U, 0x09A445U, 0x09BCAEU, 0x09C7ECU, 0x09DF07U, 0x09EED2U, 0x09F639U, + 0x0A0517U, 0x0A1DFCU, 0x0A2C29U, 0x0A34C2U, 0x0A4F80U, 0x0A576BU, 0x0A66BEU, 0x0A7E55U, 0x0A88D1U, 0x0A903AU, + 0x0AA1EFU, 0x0AB904U, 0x0AC246U, 0x0ADAADU, 0x0AEB78U, 0x0AF393U, 0x0B0670U, 0x0B1E9BU, 0x0B2F4EU, 0x0B37A5U, + 0x0B4CE7U, 0x0B540CU, 0x0B65D9U, 0x0B7D32U, 0x0B8BB6U, 0x0B935DU, 0x0BA288U, 0x0BBA63U, 0x0BC121U, 0x0BD9CAU, + 0x0BE81FU, 0x0BF0F4U, 0x0C0E43U, 0x0C16A8U, 0x0C277DU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3FU, 0x0C6DEAU, 0x0C7501U, + 0x0C8385U, 0x0C9B6EU, 0x0CAABBU, 0x0CB250U, 0x0CC912U, 0x0CD1F9U, 0x0CE02CU, 0x0CF8C7U, 0x0D0D24U, 0x0D15CFU, + 0x0D241AU, 0x0D3CF1U, 0x0D47B3U, 0x0D5F58U, 0x0D6E8DU, 0x0D7666U, 0x0D80E2U, 0x0D9809U, 0x0DA9DCU, 0x0DB137U, + 0x0DCA75U, 0x0DD29EU, 0x0DE34BU, 0x0DFBA0U, 0x0E088EU, 0x0E1065U, 0x0E21B0U, 0x0E395BU, 0x0E4219U, 0x0E5AF2U, + 0x0E6B27U, 0x0E73CCU, 0x0E8548U, 0x0E9DA3U, 0x0EAC76U, 0x0EB49DU, 0x0ECFDFU, 0x0ED734U, 0x0EE6E1U, 0x0EFE0AU, + 0x0F0BE9U, 0x0F1302U, 0x0F22D7U, 0x0F3A3CU, 0x0F417EU, 0x0F5995U, 0x0F6840U, 0x0F70ABU, 0x0F862FU, 0x0F9EC4U, + 0x0FAF11U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD453U, 0x0FE586U, 0x0FFD6DU, 0x1007B4U, 0x101F5FU, 0x102E8AU, 0x103661U, + 0x104D23U, 0x1055C8U, 0x10641DU, 0x107CF6U, 0x108A72U, 0x109299U, 0x10A34CU, 0x10BBA7U, 0x10C0E5U, 0x10D80EU, + 0x10E9DBU, 0x10F130U, 0x1104D3U, 0x111C38U, 0x112DEDU, 0x113506U, 0x114E44U, 0x1156AFU, 0x11677AU, 0x117F91U, + 0x118915U, 0x1191FEU, 0x11A02BU, 0x11B8C0U, 0x11C382U, 0x11DB69U, 0x11EABCU, 0x11F257U, 0x120179U, 0x121992U, + 0x122847U, 0x1230ACU, 0x124BEEU, 0x125305U, 0x1262D0U, 0x127A3BU, 0x128CBFU, 0x129454U, 0x12A581U, 0x12BD6AU, + 0x12C628U, 0x12DEC3U, 0x12EF16U, 0x12F7FDU, 0x13021EU, 0x131AF5U, 0x132B20U, 0x1333CBU, 0x134889U, 0x135062U, + 0x1361B7U, 0x13795CU, 0x138FD8U, 0x139733U, 0x13A6E6U, 0x13BE0DU, 0x13C54FU, 0x13DDA4U, 0x13EC71U, 0x13F49AU, + 0x140A2DU, 0x1412C6U, 0x142313U, 0x143BF8U, 0x1440BAU, 0x145851U, 0x146984U, 0x14716FU, 0x1487EBU, 0x149F00U, + 0x14AED5U, 0x14B63EU, 0x14CD7CU, 0x14D597U, 0x14E442U, 0x14FCA9U, 0x15094AU, 0x1511A1U, 0x152074U, 0x15389FU, + 0x1543DDU, 0x155B36U, 0x156AE3U, 0x157208U, 0x15848CU, 0x159C67U, 0x15ADB2U, 0x15B559U, 0x15CE1BU, 0x15D6F0U, + 0x15E725U, 0x15FFCEU, 0x160CE0U, 0x16140BU, 0x1625DEU, 0x163D35U, 0x164677U, 0x165E9CU, 0x166F49U, 0x1677A2U, + 0x168126U, 0x1699CDU, 0x16A818U, 0x16B0F3U, 0x16CBB1U, 0x16D35AU, 0x16E28FU, 0x16FA64U, 0x170F87U, 0x17176CU, + 0x1726B9U, 0x173E52U, 0x174510U, 0x175DFBU, 0x176C2EU, 0x1774C5U, 0x178241U, 0x179AAAU, 0x17AB7FU, 0x17B394U, + 0x17C8D6U, 0x17D03DU, 0x17E1E8U, 0x17F903U, 0x18046EU, 0x181C85U, 0x182D50U, 0x1835BBU, 0x184EF9U, 0x185612U, + 0x1867C7U, 0x187F2CU, 0x1889A8U, 0x189143U, 0x18A096U, 0x18B87DU, 0x18C33FU, 0x18DBD4U, 0x18EA01U, 0x18F2EAU, + 0x190709U, 0x191FE2U, 0x192E37U, 0x1936DCU, 0x194D9EU, 0x195575U, 0x1964A0U, 0x197C4BU, 0x198ACFU, 0x199224U, + 0x19A3F1U, 0x19BB1AU, 0x19C058U, 0x19D8B3U, 0x19E966U, 0x19F18DU, 0x1A02A3U, 0x1A1A48U, 0x1A2B9DU, 0x1A3376U, + 0x1A4834U, 0x1A50DFU, 0x1A610AU, 0x1A79E1U, 0x1A8F65U, 0x1A978EU, 0x1AA65BU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD19U, + 0x1AECCCU, 0x1AF427U, 0x1B01C4U, 0x1B192FU, 0x1B28FAU, 0x1B3011U, 0x1B4B53U, 0x1B53B8U, 0x1B626DU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E9U, 0x1BA53CU, 0x1BBDD7U, 0x1BC695U, 0x1BDE7EU, 0x1BEFABU, 0x1BF740U, 0x1C09F7U, 0x1C111CU, + 0x1C20C9U, 0x1C3822U, 0x1C4360U, 0x1C5B8BU, 0x1C6A5EU, 0x1C72B5U, 0x1C8431U, 0x1C9CDAU, 0x1CAD0FU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64DU, 0x1CE798U, 0x1CFF73U, 0x1D0A90U, 0x1D127BU, 0x1D23AEU, 0x1D3B45U, 0x1D4007U, 0x1D58ECU, + 0x1D6939U, 0x1D71D2U, 0x1D8756U, 0x1D9FBDU, 0x1DAE68U, 0x1DB683U, 0x1DCDC1U, 0x1DD52AU, 0x1DE4FFU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D1U, 0x1E2604U, 0x1E3EEFU, 0x1E45ADU, 0x1E5D46U, 0x1E6C93U, 0x1E7478U, 0x1E82FCU, 0x1E9A17U, + 0x1EABC2U, 0x1EB329U, 0x1EC86BU, 0x1ED080U, 0x1EE155U, 0x1EF9BEU, 0x1F0C5DU, 0x1F14B6U, 0x1F2563U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E21U, 0x1F6FF4U, 0x1F771FU, 0x1F819BU, 0x1F9970U, 0x1FA8A5U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E7U, + 0x1FE232U, 0x1FFAD9U, 0x200F68U, 0x201783U, 0x202656U, 0x203EBDU, 0x2045FFU, 0x205D14U, 0x206CC1U, 0x20742AU, + 0x2082AEU, 0x209A45U, 0x20AB90U, 0x20B37BU, 0x20C839U, 0x20D0D2U, 0x20E107U, 0x20F9ECU, 0x210C0FU, 0x2114E4U, + 0x212531U, 0x213DDAU, 0x214698U, 0x215E73U, 0x216FA6U, 0x21774DU, 0x2181C9U, 0x219922U, 0x21A8F7U, 0x21B01CU, + 0x21CB5EU, 0x21D3B5U, 0x21E260U, 0x21FA8BU, 0x2209A5U, 0x22114EU, 0x22209BU, 0x223870U, 0x224332U, 0x225BD9U, + 0x226A0CU, 0x2272E7U, 0x228463U, 0x229C88U, 0x22AD5DU, 0x22B5B6U, 0x22CEF4U, 0x22D61FU, 0x22E7CAU, 0x22FF21U, + 0x230AC2U, 0x231229U, 0x2323FCU, 0x233B17U, 0x234055U, 0x2358BEU, 0x23696BU, 0x237180U, 0x238704U, 0x239FEFU, + 0x23AE3AU, 0x23B6D1U, 0x23CD93U, 0x23D578U, 0x23E4ADU, 0x23FC46U, 0x2402F1U, 0x241A1AU, 0x242BCFU, 0x243324U, + 0x244866U, 0x24508DU, 0x246158U, 0x2479B3U, 0x248F37U, 0x2497DCU, 0x24A609U, 0x24BEE2U, 0x24C5A0U, 0x24DD4BU, + 0x24EC9EU, 0x24F475U, 0x250196U, 0x25197DU, 0x2528A8U, 0x253043U, 0x254B01U, 0x2553EAU, 0x25623FU, 0x257AD4U, + 0x258C50U, 0x2594BBU, 0x25A56EU, 0x25BD85U, 0x25C6C7U, 0x25DE2CU, 0x25EFF9U, 0x25F712U, 0x26043CU, 0x261CD7U, + 0x262D02U, 0x2635E9U, 0x264EABU, 0x265640U, 0x266795U, 0x267F7EU, 0x2689FAU, 0x269111U, 0x26A0C4U, 0x26B82FU, + 0x26C36DU, 0x26DB86U, 0x26EA53U, 0x26F2B8U, 0x27075BU, 0x271FB0U, 0x272E65U, 0x27368EU, 0x274DCCU, 0x275527U, + 0x2764F2U, 0x277C19U, 0x278A9DU, 0x279276U, 0x27A3A3U, 0x27BB48U, 0x27C00AU, 0x27D8E1U, 0x27E934U, 0x27F1DFU, + 0x280CB2U, 0x281459U, 0x28258CU, 0x283D67U, 0x284625U, 0x285ECEU, 0x286F1BU, 0x2877F0U, 0x288174U, 0x28999FU, + 0x28A84AU, 0x28B0A1U, 0x28CBE3U, 0x28D308U, 0x28E2DDU, 0x28FA36U, 0x290FD5U, 0x29173EU, 0x2926EBU, 0x293E00U, + 0x294542U, 0x295DA9U, 0x296C7CU, 0x297497U, 0x298213U, 0x299AF8U, 0x29AB2DU, 0x29B3C6U, 0x29C884U, 0x29D06FU, + 0x29E1BAU, 0x29F951U, 0x2A0A7FU, 0x2A1294U, 0x2A2341U, 0x2A3BAAU, 0x2A40E8U, 0x2A5803U, 0x2A69D6U, 0x2A713DU, + 0x2A87B9U, 0x2A9F52U, 0x2AAE87U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C5U, 0x2AE410U, 0x2AFCFBU, 0x2B0918U, 0x2B11F3U, + 0x2B2026U, 0x2B38CDU, 0x2B438FU, 0x2B5B64U, 0x2B6AB1U, 0x2B725AU, 0x2B84DEU, 0x2B9C35U, 0x2BADE0U, 0x2BB50BU, + 0x2BCE49U, 0x2BD6A2U, 0x2BE777U, 0x2BFF9CU, 0x2C012BU, 0x2C19C0U, 0x2C2815U, 0x2C30FEU, 0x2C4BBCU, 0x2C5357U, + 0x2C6282U, 0x2C7A69U, 0x2C8CEDU, 0x2C9406U, 0x2CA5D3U, 0x2CBD38U, 0x2CC67AU, 0x2CDE91U, 0x2CEF44U, 0x2CF7AFU, + 0x2D024CU, 0x2D1AA7U, 0x2D2B72U, 0x2D3399U, 0x2D48DBU, 0x2D5030U, 0x2D61E5U, 0x2D790EU, 0x2D8F8AU, 0x2D9761U, + 0x2DA6B4U, 0x2DBE5FU, 0x2DC51DU, 0x2DDDF6U, 0x2DEC23U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0DU, 0x2E2ED8U, 0x2E3633U, + 0x2E4D71U, 0x2E559AU, 0x2E644FU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CBU, 0x2EA31EU, 0x2EBBF5U, 0x2EC0B7U, 0x2ED85CU, + 0x2EE989U, 0x2EF162U, 0x2F0481U, 0x2F1C6AU, 0x2F2DBFU, 0x2F3554U, 0x2F4E16U, 0x2F56FDU, 0x2F6728U, 0x2F7FC3U, + 0x2F8947U, 0x2F91ACU, 0x2FA079U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3BU, 0x2FEAEEU, 0x2FF205U, 0x3008DCU, 0x301037U, + 0x3021E2U, 0x303909U, 0x30424BU, 0x305AA0U, 0x306B75U, 0x30739EU, 0x30851AU, 0x309DF1U, 0x30AC24U, 0x30B4CFU, + 0x30CF8DU, 0x30D766U, 0x30E6B3U, 0x30FE58U, 0x310BBBU, 0x311350U, 0x312285U, 0x313A6EU, 0x31412CU, 0x3159C7U, + 0x316812U, 0x3170F9U, 0x31867DU, 0x319E96U, 0x31AF43U, 0x31B7A8U, 0x31CCEAU, 0x31D401U, 0x31E5D4U, 0x31FD3FU, + 0x320E11U, 0x3216FAU, 0x32272FU, 0x323FC4U, 0x324486U, 0x325C6DU, 0x326DB8U, 0x327553U, 0x3283D7U, 0x329B3CU, + 0x32AAE9U, 0x32B202U, 0x32C940U, 0x32D1ABU, 0x32E07EU, 0x32F895U, 0x330D76U, 0x33159DU, 0x332448U, 0x333CA3U, + 0x3347E1U, 0x335F0AU, 0x336EDFU, 0x337634U, 0x3380B0U, 0x33985BU, 0x33A98EU, 0x33B165U, 0x33CA27U, 0x33D2CCU, + 0x33E319U, 0x33FBF2U, 0x340545U, 0x341DAEU, 0x342C7BU, 0x343490U, 0x344FD2U, 0x345739U, 0x3466ECU, 0x347E07U, + 0x348883U, 0x349068U, 0x34A1BDU, 0x34B956U, 0x34C214U, 0x34DAFFU, 0x34EB2AU, 0x34F3C1U, 0x350622U, 0x351EC9U, + 0x352F1CU, 0x3537F7U, 0x354CB5U, 0x35545EU, 0x35658BU, 0x357D60U, 0x358BE4U, 0x35930FU, 0x35A2DAU, 0x35BA31U, + 0x35C173U, 0x35D998U, 0x35E84DU, 0x35F0A6U, 0x360388U, 0x361B63U, 0x362AB6U, 0x36325DU, 0x36491FU, 0x3651F4U, + 0x366021U, 0x3678CAU, 0x368E4EU, 0x3696A5U, 0x36A770U, 0x36BF9BU, 0x36C4D9U, 0x36DC32U, 0x36EDE7U, 0x36F50CU, + 0x3700EFU, 0x371804U, 0x3729D1U, 0x37313AU, 0x374A78U, 0x375293U, 0x376346U, 0x377BADU, 0x378D29U, 0x3795C2U, + 0x37A417U, 0x37BCFCU, 0x37C7BEU, 0x37DF55U, 0x37EE80U, 0x37F66BU, 0x380B06U, 0x3813EDU, 0x382238U, 0x383AD3U, + 0x384191U, 0x38597AU, 0x3868AFU, 0x387044U, 0x3886C0U, 0x389E2BU, 0x38AFFEU, 0x38B715U, 0x38CC57U, 0x38D4BCU, + 0x38E569U, 0x38FD82U, 0x390861U, 0x39108AU, 0x39215FU, 0x3939B4U, 0x3942F6U, 0x395A1DU, 0x396BC8U, 0x397323U, + 0x3985A7U, 0x399D4CU, 0x39AC99U, 0x39B472U, 0x39CF30U, 0x39D7DBU, 0x39E60EU, 0x39FEE5U, 0x3A0DCBU, 0x3A1520U, + 0x3A24F5U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB7U, 0x3A6E62U, 0x3A7689U, 0x3A800DU, 0x3A98E6U, 0x3AA933U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD271U, 0x3AE3A4U, 0x3AFB4FU, 0x3B0EACU, 0x3B1647U, 0x3B2792U, 0x3B3F79U, 0x3B443BU, 0x3B5CD0U, + 0x3B6D05U, 0x3B75EEU, 0x3B836AU, 0x3B9B81U, 0x3BAA54U, 0x3BB2BFU, 0x3BC9FDU, 0x3BD116U, 0x3BE0C3U, 0x3BF828U, + 0x3C069FU, 0x3C1E74U, 0x3C2FA1U, 0x3C374AU, 0x3C4C08U, 0x3C54E3U, 0x3C6536U, 0x3C7DDDU, 0x3C8B59U, 0x3C93B2U, + 0x3CA267U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD925U, 0x3CE8F0U, 0x3CF01BU, 0x3D05F8U, 0x3D1D13U, 0x3D2CC6U, 0x3D342DU, + 0x3D4F6FU, 0x3D5784U, 0x3D6651U, 0x3D7EBAU, 0x3D883EU, 0x3D90D5U, 0x3DA100U, 0x3DB9EBU, 0x3DC2A9U, 0x3DDA42U, + 0x3DEB97U, 0x3DF37CU, 0x3E0052U, 0x3E18B9U, 0x3E296CU, 0x3E3187U, 0x3E4AC5U, 0x3E522EU, 0x3E63FBU, 0x3E7B10U, + 0x3E8D94U, 0x3E957FU, 0x3EA4AAU, 0x3EBC41U, 0x3EC703U, 0x3EDFE8U, 0x3EEE3DU, 0x3EF6D6U, 0x3F0335U, 0x3F1BDEU, + 0x3F2A0BU, 0x3F32E0U, 0x3F49A2U, 0x3F5149U, 0x3F609CU, 0x3F7877U, 0x3F8EF3U, 0x3F9618U, 0x3FA7CDU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8FU, 0x3FED5AU, 0x3FF5B1U, 0x40063BU, 0x401ED0U, 0x402F05U, 0x4037EEU, 0x404CACU, 0x405447U, + 0x406592U, 0x407D79U, 0x408BFDU, 0x409316U, 0x40A2C3U, 0x40BA28U, 0x40C16AU, 0x40D981U, 0x40E854U, 0x40F0BFU, + 0x41055CU, 0x411DB7U, 0x412C62U, 0x413489U, 0x414FCBU, 0x415720U, 0x4166F5U, 0x417E1EU, 0x41889AU, 0x419071U, + 0x41A1A4U, 0x41B94FU, 0x41C20DU, 0x41DAE6U, 0x41EB33U, 0x41F3D8U, 0x4200F6U, 0x42181DU, 0x4229C8U, 0x423123U, + 0x424A61U, 0x42528AU, 0x42635FU, 0x427BB4U, 0x428D30U, 0x4295DBU, 0x42A40EU, 0x42BCE5U, 0x42C7A7U, 0x42DF4CU, + 0x42EE99U, 0x42F672U, 0x430391U, 0x431B7AU, 0x432AAFU, 0x433244U, 0x434906U, 0x4351EDU, 0x436038U, 0x4378D3U, + 0x438E57U, 0x4396BCU, 0x43A769U, 0x43BF82U, 0x43C4C0U, 0x43DC2BU, 0x43EDFEU, 0x43F515U, 0x440BA2U, 0x441349U, + 0x44229CU, 0x443A77U, 0x444135U, 0x4459DEU, 0x44680BU, 0x4470E0U, 0x448664U, 0x449E8FU, 0x44AF5AU, 0x44B7B1U, + 0x44CCF3U, 0x44D418U, 0x44E5CDU, 0x44FD26U, 0x4508C5U, 0x45102EU, 0x4521FBU, 0x453910U, 0x454252U, 0x455AB9U, + 0x456B6CU, 0x457387U, 0x458503U, 0x459DE8U, 0x45AC3DU, 0x45B4D6U, 0x45CF94U, 0x45D77FU, 0x45E6AAU, 0x45FE41U, + 0x460D6FU, 0x461584U, 0x462451U, 0x463CBAU, 0x4647F8U, 0x465F13U, 0x466EC6U, 0x46762DU, 0x4680A9U, 0x469842U, + 0x46A997U, 0x46B17CU, 0x46CA3EU, 0x46D2D5U, 0x46E300U, 0x46FBEBU, 0x470E08U, 0x4716E3U, 0x472736U, 0x473FDDU, + 0x47449FU, 0x475C74U, 0x476DA1U, 0x47754AU, 0x4783CEU, 0x479B25U, 0x47AAF0U, 0x47B21BU, 0x47C959U, 0x47D1B2U, + 0x47E067U, 0x47F88CU, 0x4805E1U, 0x481D0AU, 0x482CDFU, 0x483434U, 0x484F76U, 0x48579DU, 0x486648U, 0x487EA3U, + 0x488827U, 0x4890CCU, 0x48A119U, 0x48B9F2U, 0x48C2B0U, 0x48DA5BU, 0x48EB8EU, 0x48F365U, 0x490686U, 0x491E6DU, + 0x492FB8U, 0x493753U, 0x494C11U, 0x4954FAU, 0x49652FU, 0x497DC4U, 0x498B40U, 0x4993ABU, 0x49A27EU, 0x49BA95U, + 0x49C1D7U, 0x49D93CU, 0x49E8E9U, 0x49F002U, 0x4A032CU, 0x4A1BC7U, 0x4A2A12U, 0x4A32F9U, 0x4A49BBU, 0x4A5150U, + 0x4A6085U, 0x4A786EU, 0x4A8EEAU, 0x4A9601U, 0x4AA7D4U, 0x4ABF3FU, 0x4AC47DU, 0x4ADC96U, 0x4AED43U, 0x4AF5A8U, + 0x4B004BU, 0x4B18A0U, 0x4B2975U, 0x4B319EU, 0x4B4ADCU, 0x4B5237U, 0x4B63E2U, 0x4B7B09U, 0x4B8D8DU, 0x4B9566U, + 0x4BA4B3U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF1U, 0x4BEE24U, 0x4BF6CFU, 0x4C0878U, 0x4C1093U, 0x4C2146U, 0x4C39ADU, + 0x4C42EFU, 0x4C5A04U, 0x4C6BD1U, 0x4C733AU, 0x4C85BEU, 0x4C9D55U, 0x4CAC80U, 0x4CB46BU, 0x4CCF29U, 0x4CD7C2U, + 0x4CE617U, 0x4CFEFCU, 0x4D0B1FU, 0x4D13F4U, 0x4D2221U, 0x4D3ACAU, 0x4D4188U, 0x4D5963U, 0x4D68B6U, 0x4D705DU, + 0x4D86D9U, 0x4D9E32U, 0x4DAFE7U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A5U, 0x4DE570U, 0x4DFD9BU, 0x4E0EB5U, 0x4E165EU, + 0x4E278BU, 0x4E3F60U, 0x4E4422U, 0x4E5CC9U, 0x4E6D1CU, 0x4E75F7U, 0x4E8373U, 0x4E9B98U, 0x4EAA4DU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10FU, 0x4EE0DAU, 0x4EF831U, 0x4F0DD2U, 0x4F1539U, 0x4F24ECU, 0x4F3C07U, 0x4F4745U, 0x4F5FAEU, + 0x4F6E7BU, 0x4F7690U, 0x4F8014U, 0x4F98FFU, 0x4FA92AU, 0x4FB1C1U, 0x4FCA83U, 0x4FD268U, 0x4FE3BDU, 0x4FFB56U, + 0x50018FU, 0x501964U, 0x5028B1U, 0x50305AU, 0x504B18U, 0x5053F3U, 0x506226U, 0x507ACDU, 0x508C49U, 0x5094A2U, + 0x50A577U, 0x50BD9CU, 0x50C6DEU, 0x50DE35U, 0x50EFE0U, 0x50F70BU, 0x5102E8U, 0x511A03U, 0x512BD6U, 0x51333DU, + 0x51487FU, 0x515094U, 0x516141U, 0x5179AAU, 0x518F2EU, 0x5197C5U, 0x51A610U, 0x51BEFBU, 0x51C5B9U, 0x51DD52U, + 0x51EC87U, 0x51F46CU, 0x520742U, 0x521FA9U, 0x522E7CU, 0x523697U, 0x524DD5U, 0x52553EU, 0x5264EBU, 0x527C00U, + 0x528A84U, 0x52926FU, 0x52A3BAU, 0x52BB51U, 0x52C013U, 0x52D8F8U, 0x52E92DU, 0x52F1C6U, 0x530425U, 0x531CCEU, + 0x532D1BU, 0x5335F0U, 0x534EB2U, 0x535659U, 0x53678CU, 0x537F67U, 0x5389E3U, 0x539108U, 0x53A0DDU, 0x53B836U, + 0x53C374U, 0x53DB9FU, 0x53EA4AU, 0x53F2A1U, 0x540C16U, 0x5414FDU, 0x542528U, 0x543DC3U, 0x544681U, 0x545E6AU, + 0x546FBFU, 0x547754U, 0x5481D0U, 0x54993BU, 0x54A8EEU, 0x54B005U, 0x54CB47U, 0x54D3ACU, 0x54E279U, 0x54FA92U, + 0x550F71U, 0x55179AU, 0x55264FU, 0x553EA4U, 0x5545E6U, 0x555D0DU, 0x556CD8U, 0x557433U, 0x5582B7U, 0x559A5CU, + 0x55AB89U, 0x55B362U, 0x55C820U, 0x55D0CBU, 0x55E11EU, 0x55F9F5U, 0x560ADBU, 0x561230U, 0x5623E5U, 0x563B0EU, + 0x56404CU, 0x5658A7U, 0x566972U, 0x567199U, 0x56871DU, 0x569FF6U, 0x56AE23U, 0x56B6C8U, 0x56CD8AU, 0x56D561U, + 0x56E4B4U, 0x56FC5FU, 0x5709BCU, 0x571157U, 0x572082U, 0x573869U, 0x57432BU, 0x575BC0U, 0x576A15U, 0x5772FEU, + 0x57847AU, 0x579C91U, 0x57AD44U, 0x57B5AFU, 0x57CEEDU, 0x57D606U, 0x57E7D3U, 0x57FF38U, 0x580255U, 0x581ABEU, + 0x582B6BU, 0x583380U, 0x5848C2U, 0x585029U, 0x5861FCU, 0x587917U, 0x588F93U, 0x589778U, 0x58A6ADU, 0x58BE46U, + 0x58C504U, 0x58DDEFU, 0x58EC3AU, 0x58F4D1U, 0x590132U, 0x5919D9U, 0x59280CU, 0x5930E7U, 0x594BA5U, 0x59534EU, + 0x59629BU, 0x597A70U, 0x598CF4U, 0x59941FU, 0x59A5CAU, 0x59BD21U, 0x59C663U, 0x59DE88U, 0x59EF5DU, 0x59F7B6U, + 0x5A0498U, 0x5A1C73U, 0x5A2DA6U, 0x5A354DU, 0x5A4E0FU, 0x5A56E4U, 0x5A6731U, 0x5A7FDAU, 0x5A895EU, 0x5A91B5U, + 0x5AA060U, 0x5AB88BU, 0x5AC3C9U, 0x5ADB22U, 0x5AEAF7U, 0x5AF21CU, 0x5B07FFU, 0x5B1F14U, 0x5B2EC1U, 0x5B362AU, + 0x5B4D68U, 0x5B5583U, 0x5B6456U, 0x5B7CBDU, 0x5B8A39U, 0x5B92D2U, 0x5BA307U, 0x5BBBECU, 0x5BC0AEU, 0x5BD845U, + 0x5BE990U, 0x5BF17BU, 0x5C0FCCU, 0x5C1727U, 0x5C26F2U, 0x5C3E19U, 0x5C455BU, 0x5C5DB0U, 0x5C6C65U, 0x5C748EU, + 0x5C820AU, 0x5C9AE1U, 0x5CAB34U, 0x5CB3DFU, 0x5CC89DU, 0x5CD076U, 0x5CE1A3U, 0x5CF948U, 0x5D0CABU, 0x5D1440U, + 0x5D2595U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED7U, 0x5D6F02U, 0x5D77E9U, 0x5D816DU, 0x5D9986U, 0x5DA853U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD311U, 0x5DE2C4U, 0x5DFA2FU, 0x5E0901U, 0x5E11EAU, 0x5E203FU, 0x5E38D4U, 0x5E4396U, 0x5E5B7DU, + 0x5E6AA8U, 0x5E7243U, 0x5E84C7U, 0x5E9C2CU, 0x5EADF9U, 0x5EB512U, 0x5ECE50U, 0x5ED6BBU, 0x5EE76EU, 0x5EFF85U, + 0x5F0A66U, 0x5F128DU, 0x5F2358U, 0x5F3BB3U, 0x5F40F1U, 0x5F581AU, 0x5F69CFU, 0x5F7124U, 0x5F87A0U, 0x5F9F4BU, + 0x5FAE9EU, 0x5FB675U, 0x5FCD37U, 0x5FD5DCU, 0x5FE409U, 0x5FFCE2U, 0x600953U, 0x6011B8U, 0x60206DU, 0x603886U, + 0x6043C4U, 0x605B2FU, 0x606AFAU, 0x607211U, 0x608495U, 0x609C7EU, 0x60ADABU, 0x60B540U, 0x60CE02U, 0x60D6E9U, + 0x60E73CU, 0x60FFD7U, 0x610A34U, 0x6112DFU, 0x61230AU, 0x613BE1U, 0x6140A3U, 0x615848U, 0x61699DU, 0x617176U, + 0x6187F2U, 0x619F19U, 0x61AECCU, 0x61B627U, 0x61CD65U, 0x61D58EU, 0x61E45BU, 0x61FCB0U, 0x620F9EU, 0x621775U, + 0x6226A0U, 0x623E4BU, 0x624509U, 0x625DE2U, 0x626C37U, 0x6274DCU, 0x628258U, 0x629AB3U, 0x62AB66U, 0x62B38DU, + 0x62C8CFU, 0x62D024U, 0x62E1F1U, 0x62F91AU, 0x630CF9U, 0x631412U, 0x6325C7U, 0x633D2CU, 0x63466EU, 0x635E85U, + 0x636F50U, 0x6377BBU, 0x63813FU, 0x6399D4U, 0x63A801U, 0x63B0EAU, 0x63CBA8U, 0x63D343U, 0x63E296U, 0x63FA7DU, + 0x6404CAU, 0x641C21U, 0x642DF4U, 0x64351FU, 0x644E5DU, 0x6456B6U, 0x646763U, 0x647F88U, 0x64890CU, 0x6491E7U, + 0x64A032U, 0x64B8D9U, 0x64C39BU, 0x64DB70U, 0x64EAA5U, 0x64F24EU, 0x6507ADU, 0x651F46U, 0x652E93U, 0x653678U, + 0x654D3AU, 0x6555D1U, 0x656404U, 0x657CEFU, 0x658A6BU, 0x659280U, 0x65A355U, 0x65BBBEU, 0x65C0FCU, 0x65D817U, + 0x65E9C2U, 0x65F129U, 0x660207U, 0x661AECU, 0x662B39U, 0x6633D2U, 0x664890U, 0x66507BU, 0x6661AEU, 0x667945U, + 0x668FC1U, 0x66972AU, 0x66A6FFU, 0x66BE14U, 0x66C556U, 0x66DDBDU, 0x66EC68U, 0x66F483U, 0x670160U, 0x67198BU, + 0x67285EU, 0x6730B5U, 0x674BF7U, 0x67531CU, 0x6762C9U, 0x677A22U, 0x678CA6U, 0x67944DU, 0x67A598U, 0x67BD73U, + 0x67C631U, 0x67DEDAU, 0x67EF0FU, 0x67F7E4U, 0x680A89U, 0x681262U, 0x6823B7U, 0x683B5CU, 0x68401EU, 0x6858F5U, + 0x686920U, 0x6871CBU, 0x68874FU, 0x689FA4U, 0x68AE71U, 0x68B69AU, 0x68CDD8U, 0x68D533U, 0x68E4E6U, 0x68FC0DU, + 0x6909EEU, 0x691105U, 0x6920D0U, 0x69383BU, 0x694379U, 0x695B92U, 0x696A47U, 0x6972ACU, 0x698428U, 0x699CC3U, + 0x69AD16U, 0x69B5FDU, 0x69CEBFU, 0x69D654U, 0x69E781U, 0x69FF6AU, 0x6A0C44U, 0x6A14AFU, 0x6A257AU, 0x6A3D91U, + 0x6A46D3U, 0x6A5E38U, 0x6A6FEDU, 0x6A7706U, 0x6A8182U, 0x6A9969U, 0x6AA8BCU, 0x6AB057U, 0x6ACB15U, 0x6AD3FEU, + 0x6AE22BU, 0x6AFAC0U, 0x6B0F23U, 0x6B17C8U, 0x6B261DU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5FU, 0x6B6C8AU, 0x6B7461U, + 0x6B82E5U, 0x6B9A0EU, 0x6BABDBU, 0x6BB330U, 0x6BC872U, 0x6BD099U, 0x6BE14CU, 0x6BF9A7U, 0x6C0710U, 0x6C1FFBU, + 0x6C2E2EU, 0x6C36C5U, 0x6C4D87U, 0x6C556CU, 0x6C64B9U, 0x6C7C52U, 0x6C8AD6U, 0x6C923DU, 0x6CA3E8U, 0x6CBB03U, + 0x6CC041U, 0x6CD8AAU, 0x6CE97FU, 0x6CF194U, 0x6D0477U, 0x6D1C9CU, 0x6D2D49U, 0x6D35A2U, 0x6D4EE0U, 0x6D560BU, + 0x6D67DEU, 0x6D7F35U, 0x6D89B1U, 0x6D915AU, 0x6DA08FU, 0x6DB864U, 0x6DC326U, 0x6DDBCDU, 0x6DEA18U, 0x6DF2F3U, + 0x6E01DDU, 0x6E1936U, 0x6E28E3U, 0x6E3008U, 0x6E4B4AU, 0x6E53A1U, 0x6E6274U, 0x6E7A9FU, 0x6E8C1BU, 0x6E94F0U, + 0x6EA525U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE67U, 0x6EEFB2U, 0x6EF759U, 0x6F02BAU, 0x6F1A51U, 0x6F2B84U, 0x6F336FU, + 0x6F482DU, 0x6F50C6U, 0x6F6113U, 0x6F79F8U, 0x6F8F7CU, 0x6F9797U, 0x6FA642U, 0x6FBEA9U, 0x6FC5EBU, 0x6FDD00U, + 0x6FECD5U, 0x6FF43EU, 0x700EE7U, 0x70160CU, 0x7027D9U, 0x703F32U, 0x704470U, 0x705C9BU, 0x706D4EU, 0x7075A5U, + 0x708321U, 0x709BCAU, 0x70AA1FU, 0x70B2F4U, 0x70C9B6U, 0x70D15DU, 0x70E088U, 0x70F863U, 0x710D80U, 0x71156BU, + 0x7124BEU, 0x713C55U, 0x714717U, 0x715FFCU, 0x716E29U, 0x7176C2U, 0x718046U, 0x7198ADU, 0x71A978U, 0x71B193U, + 0x71CAD1U, 0x71D23AU, 0x71E3EFU, 0x71FB04U, 0x72082AU, 0x7210C1U, 0x722114U, 0x7239FFU, 0x7242BDU, 0x725A56U, + 0x726B83U, 0x727368U, 0x7285ECU, 0x729D07U, 0x72ACD2U, 0x72B439U, 0x72CF7BU, 0x72D790U, 0x72E645U, 0x72FEAEU, + 0x730B4DU, 0x7313A6U, 0x732273U, 0x733A98U, 0x7341DAU, 0x735931U, 0x7368E4U, 0x73700FU, 0x73868BU, 0x739E60U, + 0x73AFB5U, 0x73B75EU, 0x73CC1CU, 0x73D4F7U, 0x73E522U, 0x73FDC9U, 0x74037EU, 0x741B95U, 0x742A40U, 0x7432ABU, + 0x7449E9U, 0x745102U, 0x7460D7U, 0x74783CU, 0x748EB8U, 0x749653U, 0x74A786U, 0x74BF6DU, 0x74C42FU, 0x74DCC4U, + 0x74ED11U, 0x74F5FAU, 0x750019U, 0x7518F2U, 0x752927U, 0x7531CCU, 0x754A8EU, 0x755265U, 0x7563B0U, 0x757B5BU, + 0x758DDFU, 0x759534U, 0x75A4E1U, 0x75BC0AU, 0x75C748U, 0x75DFA3U, 0x75EE76U, 0x75F69DU, 0x7605B3U, 0x761D58U, + 0x762C8DU, 0x763466U, 0x764F24U, 0x7657CFU, 0x76661AU, 0x767EF1U, 0x768875U, 0x76909EU, 0x76A14BU, 0x76B9A0U, + 0x76C2E2U, 0x76DA09U, 0x76EBDCU, 0x76F337U, 0x7706D4U, 0x771E3FU, 0x772FEAU, 0x773701U, 0x774C43U, 0x7754A8U, + 0x77657DU, 0x777D96U, 0x778B12U, 0x7793F9U, 0x77A22CU, 0x77BAC7U, 0x77C185U, 0x77D96EU, 0x77E8BBU, 0x77F050U, + 0x780D3DU, 0x7815D6U, 0x782403U, 0x783CE8U, 0x7847AAU, 0x785F41U, 0x786E94U, 0x78767FU, 0x7880FBU, 0x789810U, + 0x78A9C5U, 0x78B12EU, 0x78CA6CU, 0x78D287U, 0x78E352U, 0x78FBB9U, 0x790E5AU, 0x7916B1U, 0x792764U, 0x793F8FU, + 0x7944CDU, 0x795C26U, 0x796DF3U, 0x797518U, 0x79839CU, 0x799B77U, 0x79AAA2U, 0x79B249U, 0x79C90BU, 0x79D1E0U, + 0x79E035U, 0x79F8DEU, 0x7A0BF0U, 0x7A131BU, 0x7A22CEU, 0x7A3A25U, 0x7A4167U, 0x7A598CU, 0x7A6859U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDDU, 0x7AAF08U, 0x7AB7E3U, 0x7ACCA1U, 0x7AD44AU, 0x7AE59FU, 0x7AFD74U, 0x7B0897U, 0x7B107CU, + 0x7B21A9U, 0x7B3942U, 0x7B4200U, 0x7B5AEBU, 0x7B6B3EU, 0x7B73D5U, 0x7B8551U, 0x7B9DBAU, 0x7BAC6FU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72DU, 0x7BE6F8U, 0x7BFE13U, 0x7C00A4U, 0x7C184FU, 0x7C299AU, 0x7C3171U, 0x7C4A33U, 0x7C52D8U, + 0x7C630DU, 0x7C7BE6U, 0x7C8D62U, 0x7C9589U, 0x7CA45CU, 0x7CBCB7U, 0x7CC7F5U, 0x7CDF1EU, 0x7CEECBU, 0x7CF620U, + 0x7D03C3U, 0x7D1B28U, 0x7D2AFDU, 0x7D3216U, 0x7D4954U, 0x7D51BFU, 0x7D606AU, 0x7D7881U, 0x7D8E05U, 0x7D96EEU, + 0x7DA73BU, 0x7DBFD0U, 0x7DC492U, 0x7DDC79U, 0x7DEDACU, 0x7DF547U, 0x7E0669U, 0x7E1E82U, 0x7E2F57U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5415U, 0x7E65C0U, 0x7E7D2BU, 0x7E8BAFU, 0x7E9344U, 0x7EA291U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D3U, + 0x7EE806U, 0x7EF0EDU, 0x7F050EU, 0x7F1DE5U, 0x7F2C30U, 0x7F34DBU, 0x7F4F99U, 0x7F5772U, 0x7F66A7U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9023U, 0x7FA1F6U, 0x7FB91DU, 0x7FC25FU, 0x7FDAB4U, 0x7FEB61U, 0x7FF38AU, 0x800C75U, 0x80149EU, + 0x80254BU, 0x803DA0U, 0x8046E2U, 0x805E09U, 0x806FDCU, 0x807737U, 0x8081B3U, 0x809958U, 0x80A88DU, 0x80B066U, + 0x80CB24U, 0x80D3CFU, 0x80E21AU, 0x80FAF1U, 0x810F12U, 0x8117F9U, 0x81262CU, 0x813EC7U, 0x814585U, 0x815D6EU, + 0x816CBBU, 0x817450U, 0x8182D4U, 0x819A3FU, 0x81ABEAU, 0x81B301U, 0x81C843U, 0x81D0A8U, 0x81E17DU, 0x81F996U, + 0x820AB8U, 0x821253U, 0x822386U, 0x823B6DU, 0x82402FU, 0x8258C4U, 0x826911U, 0x8271FAU, 0x82877EU, 0x829F95U, + 0x82AE40U, 0x82B6ABU, 0x82CDE9U, 0x82D502U, 0x82E4D7U, 0x82FC3CU, 0x8309DFU, 0x831134U, 0x8320E1U, 0x83380AU, + 0x834348U, 0x835BA3U, 0x836A76U, 0x83729DU, 0x838419U, 0x839CF2U, 0x83AD27U, 0x83B5CCU, 0x83CE8EU, 0x83D665U, + 0x83E7B0U, 0x83FF5BU, 0x8401ECU, 0x841907U, 0x8428D2U, 0x843039U, 0x844B7BU, 0x845390U, 0x846245U, 0x847AAEU, + 0x848C2AU, 0x8494C1U, 0x84A514U, 0x84BDFFU, 0x84C6BDU, 0x84DE56U, 0x84EF83U, 0x84F768U, 0x85028BU, 0x851A60U, + 0x852BB5U, 0x85335EU, 0x85481CU, 0x8550F7U, 0x856122U, 0x8579C9U, 0x858F4DU, 0x8597A6U, 0x85A673U, 0x85BE98U, + 0x85C5DAU, 0x85DD31U, 0x85ECE4U, 0x85F40FU, 0x860721U, 0x861FCAU, 0x862E1FU, 0x8636F4U, 0x864DB6U, 0x86555DU, + 0x866488U, 0x867C63U, 0x868AE7U, 0x86920CU, 0x86A3D9U, 0x86BB32U, 0x86C070U, 0x86D89BU, 0x86E94EU, 0x86F1A5U, + 0x870446U, 0x871CADU, 0x872D78U, 0x873593U, 0x874ED1U, 0x87563AU, 0x8767EFU, 0x877F04U, 0x878980U, 0x87916BU, + 0x87A0BEU, 0x87B855U, 0x87C317U, 0x87DBFCU, 0x87EA29U, 0x87F2C2U, 0x880FAFU, 0x881744U, 0x882691U, 0x883E7AU, + 0x884538U, 0x885DD3U, 0x886C06U, 0x8874EDU, 0x888269U, 0x889A82U, 0x88AB57U, 0x88B3BCU, 0x88C8FEU, 0x88D015U, + 0x88E1C0U, 0x88F92BU, 0x890CC8U, 0x891423U, 0x8925F6U, 0x893D1DU, 0x89465FU, 0x895EB4U, 0x896F61U, 0x89778AU, + 0x89810EU, 0x8999E5U, 0x89A830U, 0x89B0DBU, 0x89CB99U, 0x89D372U, 0x89E2A7U, 0x89FA4CU, 0x8A0962U, 0x8A1189U, + 0x8A205CU, 0x8A38B7U, 0x8A43F5U, 0x8A5B1EU, 0x8A6ACBU, 0x8A7220U, 0x8A84A4U, 0x8A9C4FU, 0x8AAD9AU, 0x8AB571U, + 0x8ACE33U, 0x8AD6D8U, 0x8AE70DU, 0x8AFFE6U, 0x8B0A05U, 0x8B12EEU, 0x8B233BU, 0x8B3BD0U, 0x8B4092U, 0x8B5879U, + 0x8B69ACU, 0x8B7147U, 0x8B87C3U, 0x8B9F28U, 0x8BAEFDU, 0x8BB616U, 0x8BCD54U, 0x8BD5BFU, 0x8BE46AU, 0x8BFC81U, + 0x8C0236U, 0x8C1ADDU, 0x8C2B08U, 0x8C33E3U, 0x8C48A1U, 0x8C504AU, 0x8C619FU, 0x8C7974U, 0x8C8FF0U, 0x8C971BU, + 0x8CA6CEU, 0x8CBE25U, 0x8CC567U, 0x8CDD8CU, 0x8CEC59U, 0x8CF4B2U, 0x8D0151U, 0x8D19BAU, 0x8D286FU, 0x8D3084U, + 0x8D4BC6U, 0x8D532DU, 0x8D62F8U, 0x8D7A13U, 0x8D8C97U, 0x8D947CU, 0x8DA5A9U, 0x8DBD42U, 0x8DC600U, 0x8DDEEBU, + 0x8DEF3EU, 0x8DF7D5U, 0x8E04FBU, 0x8E1C10U, 0x8E2DC5U, 0x8E352EU, 0x8E4E6CU, 0x8E5687U, 0x8E6752U, 0x8E7FB9U, + 0x8E893DU, 0x8E91D6U, 0x8EA003U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB41U, 0x8EEA94U, 0x8EF27FU, 0x8F079CU, 0x8F1F77U, + 0x8F2EA2U, 0x8F3649U, 0x8F4D0BU, 0x8F55E0U, 0x8F6435U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B1U, 0x8FA364U, 0x8FBB8FU, + 0x8FC0CDU, 0x8FD826U, 0x8FE9F3U, 0x8FF118U, 0x900BC1U, 0x90132AU, 0x9022FFU, 0x903A14U, 0x904156U, 0x9059BDU, + 0x906868U, 0x907083U, 0x908607U, 0x909EECU, 0x90AF39U, 0x90B7D2U, 0x90CC90U, 0x90D47BU, 0x90E5AEU, 0x90FD45U, + 0x9108A6U, 0x91104DU, 0x912198U, 0x913973U, 0x914231U, 0x915ADAU, 0x916B0FU, 0x9173E4U, 0x918560U, 0x919D8BU, + 0x91AC5EU, 0x91B4B5U, 0x91CFF7U, 0x91D71CU, 0x91E6C9U, 0x91FE22U, 0x920D0CU, 0x9215E7U, 0x922432U, 0x923CD9U, + 0x92479BU, 0x925F70U, 0x926EA5U, 0x92764EU, 0x9280CAU, 0x929821U, 0x92A9F4U, 0x92B11FU, 0x92CA5DU, 0x92D2B6U, + 0x92E363U, 0x92FB88U, 0x930E6BU, 0x931680U, 0x932755U, 0x933FBEU, 0x9344FCU, 0x935C17U, 0x936DC2U, 0x937529U, + 0x9383ADU, 0x939B46U, 0x93AA93U, 0x93B278U, 0x93C93AU, 0x93D1D1U, 0x93E004U, 0x93F8EFU, 0x940658U, 0x941EB3U, + 0x942F66U, 0x94378DU, 0x944CCFU, 0x945424U, 0x9465F1U, 0x947D1AU, 0x948B9EU, 0x949375U, 0x94A2A0U, 0x94BA4BU, + 0x94C109U, 0x94D9E2U, 0x94E837U, 0x94F0DCU, 0x95053FU, 0x951DD4U, 0x952C01U, 0x9534EAU, 0x954FA8U, 0x955743U, + 0x956696U, 0x957E7DU, 0x9588F9U, 0x959012U, 0x95A1C7U, 0x95B92CU, 0x95C26EU, 0x95DA85U, 0x95EB50U, 0x95F3BBU, + 0x960095U, 0x96187EU, 0x9629ABU, 0x963140U, 0x964A02U, 0x9652E9U, 0x96633CU, 0x967BD7U, 0x968D53U, 0x9695B8U, + 0x96A46DU, 0x96BC86U, 0x96C7C4U, 0x96DF2FU, 0x96EEFAU, 0x96F611U, 0x9703F2U, 0x971B19U, 0x972ACCU, 0x973227U, + 0x974965U, 0x97518EU, 0x97605BU, 0x9778B0U, 0x978E34U, 0x9796DFU, 0x97A70AU, 0x97BFE1U, 0x97C4A3U, 0x97DC48U, + 0x97ED9DU, 0x97F576U, 0x98081BU, 0x9810F0U, 0x982125U, 0x9839CEU, 0x98428CU, 0x985A67U, 0x986BB2U, 0x987359U, + 0x9885DDU, 0x989D36U, 0x98ACE3U, 0x98B408U, 0x98CF4AU, 0x98D7A1U, 0x98E674U, 0x98FE9FU, 0x990B7CU, 0x991397U, + 0x992242U, 0x993AA9U, 0x9941EBU, 0x995900U, 0x9968D5U, 0x99703EU, 0x9986BAU, 0x999E51U, 0x99AF84U, 0x99B76FU, + 0x99CC2DU, 0x99D4C6U, 0x99E513U, 0x99FDF8U, 0x9A0ED6U, 0x9A163DU, 0x9A27E8U, 0x9A3F03U, 0x9A4441U, 0x9A5CAAU, + 0x9A6D7FU, 0x9A7594U, 0x9A8310U, 0x9A9BFBU, 0x9AAA2EU, 0x9AB2C5U, 0x9AC987U, 0x9AD16CU, 0x9AE0B9U, 0x9AF852U, + 0x9B0DB1U, 0x9B155AU, 0x9B248FU, 0x9B3C64U, 0x9B4726U, 0x9B5FCDU, 0x9B6E18U, 0x9B76F3U, 0x9B8077U, 0x9B989CU, + 0x9BA949U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20BU, 0x9BE3DEU, 0x9BFB35U, 0x9C0582U, 0x9C1D69U, 0x9C2CBCU, 0x9C3457U, + 0x9C4F15U, 0x9C57FEU, 0x9C662BU, 0x9C7EC0U, 0x9C8844U, 0x9C90AFU, 0x9CA17AU, 0x9CB991U, 0x9CC2D3U, 0x9CDA38U, + 0x9CEBEDU, 0x9CF306U, 0x9D06E5U, 0x9D1E0EU, 0x9D2FDBU, 0x9D3730U, 0x9D4C72U, 0x9D5499U, 0x9D654CU, 0x9D7DA7U, + 0x9D8B23U, 0x9D93C8U, 0x9DA21DU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95FU, 0x9DE88AU, 0x9DF061U, 0x9E034FU, 0x9E1BA4U, + 0x9E2A71U, 0x9E329AU, 0x9E49D8U, 0x9E5133U, 0x9E60E6U, 0x9E780DU, 0x9E8E89U, 0x9E9662U, 0x9EA7B7U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF5U, 0x9EED20U, 0x9EF5CBU, 0x9F0028U, 0x9F18C3U, 0x9F2916U, 0x9F31FDU, 0x9F4ABFU, 0x9F5254U, + 0x9F6381U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9505U, 0x9FA4D0U, 0x9FBC3BU, 0x9FC779U, 0x9FDF92U, 0x9FEE47U, 0x9FF6ACU, + 0xA0031DU, 0xA01BF6U, 0xA02A23U, 0xA032C8U, 0xA0498AU, 0xA05161U, 0xA060B4U, 0xA0785FU, 0xA08EDBU, 0xA09630U, + 0xA0A7E5U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA7U, 0xA0ED72U, 0xA0F599U, 0xA1007AU, 0xA11891U, 0xA12944U, 0xA131AFU, + 0xA14AEDU, 0xA15206U, 0xA163D3U, 0xA17B38U, 0xA18DBCU, 0xA19557U, 0xA1A482U, 0xA1BC69U, 0xA1C72BU, 0xA1DFC0U, + 0xA1EE15U, 0xA1F6FEU, 0xA205D0U, 0xA21D3BU, 0xA22CEEU, 0xA23405U, 0xA24F47U, 0xA257ACU, 0xA26679U, 0xA27E92U, + 0xA28816U, 0xA290FDU, 0xA2A128U, 0xA2B9C3U, 0xA2C281U, 0xA2DA6AU, 0xA2EBBFU, 0xA2F354U, 0xA306B7U, 0xA31E5CU, + 0xA32F89U, 0xA33762U, 0xA34C20U, 0xA354CBU, 0xA3651EU, 0xA37DF5U, 0xA38B71U, 0xA3939AU, 0xA3A24FU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90DU, 0xA3E8D8U, 0xA3F033U, 0xA40E84U, 0xA4166FU, 0xA427BAU, 0xA43F51U, 0xA44413U, 0xA45CF8U, + 0xA46D2DU, 0xA475C6U, 0xA48342U, 0xA49BA9U, 0xA4AA7CU, 0xA4B297U, 0xA4C9D5U, 0xA4D13EU, 0xA4E0EBU, 0xA4F800U, + 0xA50DE3U, 0xA51508U, 0xA524DDU, 0xA53C36U, 0xA54774U, 0xA55F9FU, 0xA56E4AU, 0xA576A1U, 0xA58025U, 0xA598CEU, + 0xA5A91BU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D259U, 0xA5E38CU, 0xA5FB67U, 0xA60849U, 0xA610A2U, 0xA62177U, 0xA6399CU, + 0xA642DEU, 0xA65A35U, 0xA66BE0U, 0xA6730BU, 0xA6858FU, 0xA69D64U, 0xA6ACB1U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F3U, + 0xA6E626U, 0xA6FECDU, 0xA70B2EU, 0xA713C5U, 0xA72210U, 0xA73AFBU, 0xA741B9U, 0xA75952U, 0xA76887U, 0xA7706CU, + 0xA786E8U, 0xA79E03U, 0xA7AFD6U, 0xA7B73DU, 0xA7CC7FU, 0xA7D494U, 0xA7E541U, 0xA7FDAAU, 0xA800C7U, 0xA8182CU, + 0xA829F9U, 0xA83112U, 0xA84A50U, 0xA852BBU, 0xA8636EU, 0xA87B85U, 0xA88D01U, 0xA895EAU, 0xA8A43FU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7DU, 0xA8EEA8U, 0xA8F643U, 0xA903A0U, 0xA91B4BU, 0xA92A9EU, 0xA93275U, 0xA94937U, 0xA951DCU, + 0xA96009U, 0xA978E2U, 0xA98E66U, 0xA9968DU, 0xA9A758U, 0xA9BFB3U, 0xA9C4F1U, 0xA9DC1AU, 0xA9EDCFU, 0xA9F524U, + 0xAA060AU, 0xAA1EE1U, 0xAA2F34U, 0xAA37DFU, 0xAA4C9DU, 0xAA5476U, 0xAA65A3U, 0xAA7D48U, 0xAA8BCCU, 0xAA9327U, + 0xAAA2F2U, 0xAABA19U, 0xAAC15BU, 0xAAD9B0U, 0xAAE865U, 0xAAF08EU, 0xAB056DU, 0xAB1D86U, 0xAB2C53U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5711U, 0xAB66C4U, 0xAB7E2FU, 0xAB88ABU, 0xAB9040U, 0xABA195U, 0xABB97EU, 0xABC23CU, 0xABDAD7U, + 0xABEB02U, 0xABF3E9U, 0xAC0D5EU, 0xAC15B5U, 0xAC2460U, 0xAC3C8BU, 0xAC47C9U, 0xAC5F22U, 0xAC6EF7U, 0xAC761CU, + 0xAC8098U, 0xAC9873U, 0xACA9A6U, 0xACB14DU, 0xACCA0FU, 0xACD2E4U, 0xACE331U, 0xACFBDAU, 0xAD0E39U, 0xAD16D2U, + 0xAD2707U, 0xAD3FECU, 0xAD44AEU, 0xAD5C45U, 0xAD6D90U, 0xAD757BU, 0xAD83FFU, 0xAD9B14U, 0xADAAC1U, 0xADB22AU, + 0xADC968U, 0xADD183U, 0xADE056U, 0xADF8BDU, 0xAE0B93U, 0xAE1378U, 0xAE22ADU, 0xAE3A46U, 0xAE4104U, 0xAE59EFU, + 0xAE683AU, 0xAE70D1U, 0xAE8655U, 0xAE9EBEU, 0xAEAF6BU, 0xAEB780U, 0xAECCC2U, 0xAED429U, 0xAEE5FCU, 0xAEFD17U, + 0xAF08F4U, 0xAF101FU, 0xAF21CAU, 0xAF3921U, 0xAF4263U, 0xAF5A88U, 0xAF6B5DU, 0xAF73B6U, 0xAF8532U, 0xAF9DD9U, + 0xAFAC0CU, 0xAFB4E7U, 0xAFCFA5U, 0xAFD74EU, 0xAFE69BU, 0xAFFE70U, 0xB004A9U, 0xB01C42U, 0xB02D97U, 0xB0357CU, + 0xB04E3EU, 0xB056D5U, 0xB06700U, 0xB07FEBU, 0xB0896FU, 0xB09184U, 0xB0A051U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB13U, + 0xB0EAC6U, 0xB0F22DU, 0xB107CEU, 0xB11F25U, 0xB12EF0U, 0xB1361BU, 0xB14D59U, 0xB155B2U, 0xB16467U, 0xB17C8CU, + 0xB18A08U, 0xB192E3U, 0xB1A336U, 0xB1BBDDU, 0xB1C09FU, 0xB1D874U, 0xB1E9A1U, 0xB1F14AU, 0xB20264U, 0xB21A8FU, + 0xB22B5AU, 0xB233B1U, 0xB248F3U, 0xB25018U, 0xB261CDU, 0xB27926U, 0xB28FA2U, 0xB29749U, 0xB2A69CU, 0xB2BE77U, + 0xB2C535U, 0xB2DDDEU, 0xB2EC0BU, 0xB2F4E0U, 0xB30103U, 0xB319E8U, 0xB3283DU, 0xB330D6U, 0xB34B94U, 0xB3537FU, + 0xB362AAU, 0xB37A41U, 0xB38CC5U, 0xB3942EU, 0xB3A5FBU, 0xB3BD10U, 0xB3C652U, 0xB3DEB9U, 0xB3EF6CU, 0xB3F787U, + 0xB40930U, 0xB411DBU, 0xB4200EU, 0xB438E5U, 0xB443A7U, 0xB45B4CU, 0xB46A99U, 0xB47272U, 0xB484F6U, 0xB49C1DU, + 0xB4ADC8U, 0xB4B523U, 0xB4CE61U, 0xB4D68AU, 0xB4E75FU, 0xB4FFB4U, 0xB50A57U, 0xB512BCU, 0xB52369U, 0xB53B82U, + 0xB540C0U, 0xB5582BU, 0xB569FEU, 0xB57115U, 0xB58791U, 0xB59F7AU, 0xB5AEAFU, 0xB5B644U, 0xB5CD06U, 0xB5D5EDU, + 0xB5E438U, 0xB5FCD3U, 0xB60FFDU, 0xB61716U, 0xB626C3U, 0xB63E28U, 0xB6456AU, 0xB65D81U, 0xB66C54U, 0xB674BFU, + 0xB6823BU, 0xB69AD0U, 0xB6AB05U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D047U, 0xB6E192U, 0xB6F979U, 0xB70C9AU, 0xB71471U, + 0xB725A4U, 0xB73D4FU, 0xB7460DU, 0xB75EE6U, 0xB76F33U, 0xB777D8U, 0xB7815CU, 0xB799B7U, 0xB7A862U, 0xB7B089U, + 0xB7CBCBU, 0xB7D320U, 0xB7E2F5U, 0xB7FA1EU, 0xB80773U, 0xB81F98U, 0xB82E4DU, 0xB836A6U, 0xB84DE4U, 0xB8550FU, + 0xB864DAU, 0xB87C31U, 0xB88AB5U, 0xB8925EU, 0xB8A38BU, 0xB8BB60U, 0xB8C022U, 0xB8D8C9U, 0xB8E91CU, 0xB8F1F7U, + 0xB90414U, 0xB91CFFU, 0xB92D2AU, 0xB935C1U, 0xB94E83U, 0xB95668U, 0xB967BDU, 0xB97F56U, 0xB989D2U, 0xB99139U, + 0xB9A0ECU, 0xB9B807U, 0xB9C345U, 0xB9DBAEU, 0xB9EA7BU, 0xB9F290U, 0xBA01BEU, 0xBA1955U, 0xBA2880U, 0xBA306BU, + 0xBA4B29U, 0xBA53C2U, 0xBA6217U, 0xBA7AFCU, 0xBA8C78U, 0xBA9493U, 0xBAA546U, 0xBABDADU, 0xBAC6EFU, 0xBADE04U, + 0xBAEFD1U, 0xBAF73AU, 0xBB02D9U, 0xBB1A32U, 0xBB2BE7U, 0xBB330CU, 0xBB484EU, 0xBB50A5U, 0xBB6170U, 0xBB799BU, + 0xBB8F1FU, 0xBB97F4U, 0xBBA621U, 0xBBBECAU, 0xBBC588U, 0xBBDD63U, 0xBBECB6U, 0xBBF45DU, 0xBC0AEAU, 0xBC1201U, + 0xBC23D4U, 0xBC3B3FU, 0xBC407DU, 0xBC5896U, 0xBC6943U, 0xBC71A8U, 0xBC872CU, 0xBC9FC7U, 0xBCAE12U, 0xBCB6F9U, + 0xBCCDBBU, 0xBCD550U, 0xBCE485U, 0xBCFC6EU, 0xBD098DU, 0xBD1166U, 0xBD20B3U, 0xBD3858U, 0xBD431AU, 0xBD5BF1U, + 0xBD6A24U, 0xBD72CFU, 0xBD844BU, 0xBD9CA0U, 0xBDAD75U, 0xBDB59EU, 0xBDCEDCU, 0xBDD637U, 0xBDE7E2U, 0xBDFF09U, + 0xBE0C27U, 0xBE14CCU, 0xBE2519U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5BU, 0xBE6F8EU, 0xBE7765U, 0xBE81E1U, 0xBE990AU, + 0xBEA8DFU, 0xBEB034U, 0xBECB76U, 0xBED39DU, 0xBEE248U, 0xBEFAA3U, 0xBF0F40U, 0xBF17ABU, 0xBF267EU, 0xBF3E95U, + 0xBF45D7U, 0xBF5D3CU, 0xBF6CE9U, 0xBF7402U, 0xBF8286U, 0xBF9A6DU, 0xBFABB8U, 0xBFB353U, 0xBFC811U, 0xBFD0FAU, + 0xBFE12FU, 0xBFF9C4U, 0xC00A4EU, 0xC012A5U, 0xC02370U, 0xC03B9BU, 0xC040D9U, 0xC05832U, 0xC069E7U, 0xC0710CU, + 0xC08788U, 0xC09F63U, 0xC0AEB6U, 0xC0B65DU, 0xC0CD1FU, 0xC0D5F4U, 0xC0E421U, 0xC0FCCAU, 0xC10929U, 0xC111C2U, + 0xC12017U, 0xC138FCU, 0xC143BEU, 0xC15B55U, 0xC16A80U, 0xC1726BU, 0xC184EFU, 0xC19C04U, 0xC1ADD1U, 0xC1B53AU, + 0xC1CE78U, 0xC1D693U, 0xC1E746U, 0xC1FFADU, 0xC20C83U, 0xC21468U, 0xC225BDU, 0xC23D56U, 0xC24614U, 0xC25EFFU, + 0xC26F2AU, 0xC277C1U, 0xC28145U, 0xC299AEU, 0xC2A87BU, 0xC2B090U, 0xC2CBD2U, 0xC2D339U, 0xC2E2ECU, 0xC2FA07U, + 0xC30FE4U, 0xC3170FU, 0xC326DAU, 0xC33E31U, 0xC34573U, 0xC35D98U, 0xC36C4DU, 0xC374A6U, 0xC38222U, 0xC39AC9U, + 0xC3AB1CU, 0xC3B3F7U, 0xC3C8B5U, 0xC3D05EU, 0xC3E18BU, 0xC3F960U, 0xC407D7U, 0xC41F3CU, 0xC42EE9U, 0xC43602U, + 0xC44D40U, 0xC455ABU, 0xC4647EU, 0xC47C95U, 0xC48A11U, 0xC492FAU, 0xC4A32FU, 0xC4BBC4U, 0xC4C086U, 0xC4D86DU, + 0xC4E9B8U, 0xC4F153U, 0xC504B0U, 0xC51C5BU, 0xC52D8EU, 0xC53565U, 0xC54E27U, 0xC556CCU, 0xC56719U, 0xC57FF2U, + 0xC58976U, 0xC5919DU, 0xC5A048U, 0xC5B8A3U, 0xC5C3E1U, 0xC5DB0AU, 0xC5EADFU, 0xC5F234U, 0xC6011AU, 0xC619F1U, + 0xC62824U, 0xC630CFU, 0xC64B8DU, 0xC65366U, 0xC662B3U, 0xC67A58U, 0xC68CDCU, 0xC69437U, 0xC6A5E2U, 0xC6BD09U, + 0xC6C64BU, 0xC6DEA0U, 0xC6EF75U, 0xC6F79EU, 0xC7027DU, 0xC71A96U, 0xC72B43U, 0xC733A8U, 0xC748EAU, 0xC75001U, + 0xC761D4U, 0xC7793FU, 0xC78FBBU, 0xC79750U, 0xC7A685U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC7U, 0xC7EC12U, 0xC7F4F9U, + 0xC80994U, 0xC8117FU, 0xC820AAU, 0xC83841U, 0xC84303U, 0xC85BE8U, 0xC86A3DU, 0xC872D6U, 0xC88452U, 0xC89CB9U, + 0xC8AD6CU, 0xC8B587U, 0xC8CEC5U, 0xC8D62EU, 0xC8E7FBU, 0xC8FF10U, 0xC90AF3U, 0xC91218U, 0xC923CDU, 0xC93B26U, + 0xC94064U, 0xC9588FU, 0xC9695AU, 0xC971B1U, 0xC98735U, 0xC99FDEU, 0xC9AE0BU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D549U, + 0xC9E49CU, 0xC9FC77U, 0xCA0F59U, 0xCA17B2U, 0xCA2667U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D25U, 0xCA6CF0U, 0xCA741BU, + 0xCA829FU, 0xCA9A74U, 0xCAABA1U, 0xCAB34AU, 0xCAC808U, 0xCAD0E3U, 0xCAE136U, 0xCAF9DDU, 0xCB0C3EU, 0xCB14D5U, + 0xCB2500U, 0xCB3DEBU, 0xCB46A9U, 0xCB5E42U, 0xCB6F97U, 0xCB777CU, 0xCB81F8U, 0xCB9913U, 0xCBA8C6U, 0xCBB02DU, + 0xCBCB6FU, 0xCBD384U, 0xCBE251U, 0xCBFABAU, 0xCC040DU, 0xCC1CE6U, 0xCC2D33U, 0xCC35D8U, 0xCC4E9AU, 0xCC5671U, + 0xCC67A4U, 0xCC7F4FU, 0xCC89CBU, 0xCC9120U, 0xCCA0F5U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB7U, 0xCCEA62U, 0xCCF289U, + 0xCD076AU, 0xCD1F81U, 0xCD2E54U, 0xCD36BFU, 0xCD4DFDU, 0xCD5516U, 0xCD64C3U, 0xCD7C28U, 0xCD8AACU, 0xCD9247U, + 0xCDA392U, 0xCDBB79U, 0xCDC03BU, 0xCDD8D0U, 0xCDE905U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2BU, 0xCE2BFEU, 0xCE3315U, + 0xCE4857U, 0xCE50BCU, 0xCE6169U, 0xCE7982U, 0xCE8F06U, 0xCE97EDU, 0xCEA638U, 0xCEBED3U, 0xCEC591U, 0xCEDD7AU, + 0xCEECAFU, 0xCEF444U, 0xCF01A7U, 0xCF194CU, 0xCF2899U, 0xCF3072U, 0xCF4B30U, 0xCF53DBU, 0xCF620EU, 0xCF7AE5U, + 0xCF8C61U, 0xCF948AU, 0xCFA55FU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1DU, 0xCFEFC8U, 0xCFF723U, 0xD00DFAU, 0xD01511U, + 0xD024C4U, 0xD03C2FU, 0xD0476DU, 0xD05F86U, 0xD06E53U, 0xD076B8U, 0xD0803CU, 0xD098D7U, 0xD0A902U, 0xD0B1E9U, + 0xD0CAABU, 0xD0D240U, 0xD0E395U, 0xD0FB7EU, 0xD10E9DU, 0xD11676U, 0xD127A3U, 0xD13F48U, 0xD1440AU, 0xD15CE1U, + 0xD16D34U, 0xD175DFU, 0xD1835BU, 0xD19BB0U, 0xD1AA65U, 0xD1B28EU, 0xD1C9CCU, 0xD1D127U, 0xD1E0F2U, 0xD1F819U, + 0xD20B37U, 0xD213DCU, 0xD22209U, 0xD23AE2U, 0xD241A0U, 0xD2594BU, 0xD2689EU, 0xD27075U, 0xD286F1U, 0xD29E1AU, + 0xD2AFCFU, 0xD2B724U, 0xD2CC66U, 0xD2D48DU, 0xD2E558U, 0xD2FDB3U, 0xD30850U, 0xD310BBU, 0xD3216EU, 0xD33985U, + 0xD342C7U, 0xD35A2CU, 0xD36BF9U, 0xD37312U, 0xD38596U, 0xD39D7DU, 0xD3ACA8U, 0xD3B443U, 0xD3CF01U, 0xD3D7EAU, + 0xD3E63FU, 0xD3FED4U, 0xD40063U, 0xD41888U, 0xD4295DU, 0xD431B6U, 0xD44AF4U, 0xD4521FU, 0xD463CAU, 0xD47B21U, + 0xD48DA5U, 0xD4954EU, 0xD4A49BU, 0xD4BC70U, 0xD4C732U, 0xD4DFD9U, 0xD4EE0CU, 0xD4F6E7U, 0xD50304U, 0xD51BEFU, + 0xD52A3AU, 0xD532D1U, 0xD54993U, 0xD55178U, 0xD560ADU, 0xD57846U, 0xD58EC2U, 0xD59629U, 0xD5A7FCU, 0xD5BF17U, + 0xD5C455U, 0xD5DCBEU, 0xD5ED6BU, 0xD5F580U, 0xD606AEU, 0xD61E45U, 0xD62F90U, 0xD6377BU, 0xD64C39U, 0xD654D2U, + 0xD66507U, 0xD67DECU, 0xD68B68U, 0xD69383U, 0xD6A256U, 0xD6BABDU, 0xD6C1FFU, 0xD6D914U, 0xD6E8C1U, 0xD6F02AU, + 0xD705C9U, 0xD71D22U, 0xD72CF7U, 0xD7341CU, 0xD74F5EU, 0xD757B5U, 0xD76660U, 0xD77E8BU, 0xD7880FU, 0xD790E4U, + 0xD7A131U, 0xD7B9DAU, 0xD7C298U, 0xD7DA73U, 0xD7EBA6U, 0xD7F34DU, 0xD80E20U, 0xD816CBU, 0xD8271EU, 0xD83FF5U, + 0xD844B7U, 0xD85C5CU, 0xD86D89U, 0xD87562U, 0xD883E6U, 0xD89B0DU, 0xD8AAD8U, 0xD8B233U, 0xD8C971U, 0xD8D19AU, + 0xD8E04FU, 0xD8F8A4U, 0xD90D47U, 0xD915ACU, 0xD92479U, 0xD93C92U, 0xD947D0U, 0xD95F3BU, 0xD96EEEU, 0xD97605U, + 0xD98081U, 0xD9986AU, 0xD9A9BFU, 0xD9B154U, 0xD9CA16U, 0xD9D2FDU, 0xD9E328U, 0xD9FBC3U, 0xDA08EDU, 0xDA1006U, + 0xDA21D3U, 0xDA3938U, 0xDA427AU, 0xDA5A91U, 0xDA6B44U, 0xDA73AFU, 0xDA852BU, 0xDA9DC0U, 0xDAAC15U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD757U, 0xDAE682U, 0xDAFE69U, 0xDB0B8AU, 0xDB1361U, 0xDB22B4U, 0xDB3A5FU, 0xDB411DU, 0xDB59F6U, + 0xDB6823U, 0xDB70C8U, 0xDB864CU, 0xDB9EA7U, 0xDBAF72U, 0xDBB799U, 0xDBCCDBU, 0xDBD430U, 0xDBE5E5U, 0xDBFD0EU, + 0xDC03B9U, 0xDC1B52U, 0xDC2A87U, 0xDC326CU, 0xDC492EU, 0xDC51C5U, 0xDC6010U, 0xDC78FBU, 0xDC8E7FU, 0xDC9694U, + 0xDCA741U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC03U, 0xDCEDD6U, 0xDCF53DU, 0xDD00DEU, 0xDD1835U, 0xDD29E0U, 0xDD310BU, + 0xDD4A49U, 0xDD52A2U, 0xDD6377U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F3U, 0xDDA426U, 0xDDBCCDU, 0xDDC78FU, 0xDDDF64U, + 0xDDEEB1U, 0xDDF65AU, 0xDE0574U, 0xDE1D9FU, 0xDE2C4AU, 0xDE34A1U, 0xDE4FE3U, 0xDE5708U, 0xDE66DDU, 0xDE7E36U, + 0xDE88B2U, 0xDE9059U, 0xDEA18CU, 0xDEB967U, 0xDEC225U, 0xDEDACEU, 0xDEEB1BU, 0xDEF3F0U, 0xDF0613U, 0xDF1EF8U, + 0xDF2F2DU, 0xDF37C6U, 0xDF4C84U, 0xDF546FU, 0xDF65BAU, 0xDF7D51U, 0xDF8BD5U, 0xDF933EU, 0xDFA2EBU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A9U, 0xDFE87CU, 0xDFF097U, 0xE00526U, 0xE01DCDU, 0xE02C18U, 0xE034F3U, 0xE04FB1U, 0xE0575AU, + 0xE0668FU, 0xE07E64U, 0xE088E0U, 0xE0900BU, 0xE0A1DEU, 0xE0B935U, 0xE0C277U, 0xE0DA9CU, 0xE0EB49U, 0xE0F3A2U, + 0xE10641U, 0xE11EAAU, 0xE12F7FU, 0xE13794U, 0xE14CD6U, 0xE1543DU, 0xE165E8U, 0xE17D03U, 0xE18B87U, 0xE1936CU, + 0xE1A2B9U, 0xE1BA52U, 0xE1C110U, 0xE1D9FBU, 0xE1E82EU, 0xE1F0C5U, 0xE203EBU, 0xE21B00U, 0xE22AD5U, 0xE2323EU, + 0xE2497CU, 0xE25197U, 0xE26042U, 0xE278A9U, 0xE28E2DU, 0xE296C6U, 0xE2A713U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC51U, + 0xE2ED84U, 0xE2F56FU, 0xE3008CU, 0xE31867U, 0xE329B2U, 0xE33159U, 0xE34A1BU, 0xE352F0U, 0xE36325U, 0xE37BCEU, + 0xE38D4AU, 0xE395A1U, 0xE3A474U, 0xE3BC9FU, 0xE3C7DDU, 0xE3DF36U, 0xE3EEE3U, 0xE3F608U, 0xE408BFU, 0xE41054U, + 0xE42181U, 0xE4396AU, 0xE44228U, 0xE45AC3U, 0xE46B16U, 0xE473FDU, 0xE48579U, 0xE49D92U, 0xE4AC47U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D705U, 0xE4E6D0U, 0xE4FE3BU, 0xE50BD8U, 0xE51333U, 0xE522E6U, 0xE53A0DU, 0xE5414FU, 0xE559A4U, + 0xE56871U, 0xE5709AU, 0xE5861EU, 0xE59EF5U, 0xE5AF20U, 0xE5B7CBU, 0xE5CC89U, 0xE5D462U, 0xE5E5B7U, 0xE5FD5CU, + 0xE60E72U, 0xE61699U, 0xE6274CU, 0xE63FA7U, 0xE644E5U, 0xE65C0EU, 0xE66DDBU, 0xE67530U, 0xE683B4U, 0xE69B5FU, + 0xE6AA8AU, 0xE6B261U, 0xE6C923U, 0xE6D1C8U, 0xE6E01DU, 0xE6F8F6U, 0xE70D15U, 0xE715FEU, 0xE7242BU, 0xE73CC0U, + 0xE74782U, 0xE75F69U, 0xE76EBCU, 0xE77657U, 0xE780D3U, 0xE79838U, 0xE7A9EDU, 0xE7B106U, 0xE7CA44U, 0xE7D2AFU, + 0xE7E37AU, 0xE7FB91U, 0xE806FCU, 0xE81E17U, 0xE82FC2U, 0xE83729U, 0xE84C6BU, 0xE85480U, 0xE86555U, 0xE87DBEU, + 0xE88B3AU, 0xE893D1U, 0xE8A204U, 0xE8BAEFU, 0xE8C1ADU, 0xE8D946U, 0xE8E893U, 0xE8F078U, 0xE9059BU, 0xE91D70U, + 0xE92CA5U, 0xE9344EU, 0xE94F0CU, 0xE957E7U, 0xE96632U, 0xE97ED9U, 0xE9885DU, 0xE990B6U, 0xE9A163U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA21U, 0xE9EBF4U, 0xE9F31FU, 0xEA0031U, 0xEA18DAU, 0xEA290FU, 0xEA31E4U, 0xEA4AA6U, 0xEA524DU, + 0xEA6398U, 0xEA7B73U, 0xEA8DF7U, 0xEA951CU, 0xEAA4C9U, 0xEABC22U, 0xEAC760U, 0xEADF8BU, 0xEAEE5EU, 0xEAF6B5U, + 0xEB0356U, 0xEB1BBDU, 0xEB2A68U, 0xEB3283U, 0xEB49C1U, 0xEB512AU, 0xEB60FFU, 0xEB7814U, 0xEB8E90U, 0xEB967BU, + 0xEBA7AEU, 0xEBBF45U, 0xEBC407U, 0xEBDCECU, 0xEBED39U, 0xEBF5D2U, 0xEC0B65U, 0xEC138EU, 0xEC225BU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5919U, 0xEC68CCU, 0xEC7027U, 0xEC86A3U, 0xEC9E48U, 0xECAF9DU, 0xECB776U, 0xECCC34U, 0xECD4DFU, + 0xECE50AU, 0xECFDE1U, 0xED0802U, 0xED10E9U, 0xED213CU, 0xED39D7U, 0xED4295U, 0xED5A7EU, 0xED6BABU, 0xED7340U, + 0xED85C4U, 0xED9D2FU, 0xEDACFAU, 0xEDB411U, 0xEDCF53U, 0xEDD7B8U, 0xEDE66DU, 0xEDFE86U, 0xEE0DA8U, 0xEE1543U, + 0xEE2496U, 0xEE3C7DU, 0xEE473FU, 0xEE5FD4U, 0xEE6E01U, 0xEE76EAU, 0xEE806EU, 0xEE9885U, 0xEEA950U, 0xEEB1BBU, + 0xEECAF9U, 0xEED212U, 0xEEE3C7U, 0xEEFB2CU, 0xEF0ECFU, 0xEF1624U, 0xEF27F1U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB3U, + 0xEF6D66U, 0xEF758DU, 0xEF8309U, 0xEF9BE2U, 0xEFAA37U, 0xEFB2DCU, 0xEFC99EU, 0xEFD175U, 0xEFE0A0U, 0xEFF84BU, + 0xF00292U, 0xF01A79U, 0xF02BACU, 0xF03347U, 0xF04805U, 0xF050EEU, 0xF0613BU, 0xF079D0U, 0xF08F54U, 0xF097BFU, + 0xF0A66AU, 0xF0BE81U, 0xF0C5C3U, 0xF0DD28U, 0xF0ECFDU, 0xF0F416U, 0xF101F5U, 0xF1191EU, 0xF128CBU, 0xF13020U, + 0xF14B62U, 0xF15389U, 0xF1625CU, 0xF17AB7U, 0xF18C33U, 0xF194D8U, 0xF1A50DU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4FU, + 0xF1EF9AU, 0xF1F771U, 0xF2045FU, 0xF21CB4U, 0xF22D61U, 0xF2358AU, 0xF24EC8U, 0xF25623U, 0xF267F6U, 0xF27F1DU, + 0xF28999U, 0xF29172U, 0xF2A0A7U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE5U, 0xF2EA30U, 0xF2F2DBU, 0xF30738U, 0xF31FD3U, + 0xF32E06U, 0xF336EDU, 0xF34DAFU, 0xF35544U, 0xF36491U, 0xF37C7AU, 0xF38AFEU, 0xF39215U, 0xF3A3C0U, 0xF3BB2BU, + 0xF3C069U, 0xF3D882U, 0xF3E957U, 0xF3F1BCU, 0xF40F0BU, 0xF417E0U, 0xF42635U, 0xF43EDEU, 0xF4459CU, 0xF45D77U, + 0xF46CA2U, 0xF47449U, 0xF482CDU, 0xF49A26U, 0xF4ABF3U, 0xF4B318U, 0xF4C85AU, 0xF4D0B1U, 0xF4E164U, 0xF4F98FU, + 0xF50C6CU, 0xF51487U, 0xF52552U, 0xF53DB9U, 0xF546FBU, 0xF55E10U, 0xF56FC5U, 0xF5772EU, 0xF581AAU, 0xF59941U, + 0xF5A894U, 0xF5B07FU, 0xF5CB3DU, 0xF5D3D6U, 0xF5E203U, 0xF5FAE8U, 0xF609C6U, 0xF6112DU, 0xF620F8U, 0xF63813U, + 0xF64351U, 0xF65BBAU, 0xF66A6FU, 0xF67284U, 0xF68400U, 0xF69CEBU, 0xF6AD3EU, 0xF6B5D5U, 0xF6CE97U, 0xF6D67CU, + 0xF6E7A9U, 0xF6FF42U, 0xF70AA1U, 0xF7124AU, 0xF7239FU, 0xF73B74U, 0xF74036U, 0xF758DDU, 0xF76908U, 0xF771E3U, + 0xF78767U, 0xF79F8CU, 0xF7AE59U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51BU, 0xF7E4CEU, 0xF7FC25U, 0xF80148U, 0xF819A3U, + 0xF82876U, 0xF8309DU, 0xF84BDFU, 0xF85334U, 0xF862E1U, 0xF87A0AU, 0xF88C8EU, 0xF89465U, 0xF8A5B0U, 0xF8BD5BU, + 0xF8C619U, 0xF8DEF2U, 0xF8EF27U, 0xF8F7CCU, 0xF9022FU, 0xF91AC4U, 0xF92B11U, 0xF933FAU, 0xF948B8U, 0xF95053U, + 0xF96186U, 0xF9796DU, 0xF98FE9U, 0xF99702U, 0xF9A6D7U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD95U, 0xF9EC40U, 0xF9F4ABU, + 0xFA0785U, 0xFA1F6EU, 0xFA2EBBU, 0xFA3650U, 0xFA4D12U, 0xFA55F9U, 0xFA642CU, 0xFA7CC7U, 0xFA8A43U, 0xFA92A8U, + 0xFAA37DU, 0xFABB96U, 0xFAC0D4U, 0xFAD83FU, 0xFAE9EAU, 0xFAF101U, 0xFB04E2U, 0xFB1C09U, 0xFB2DDCU, 0xFB3537U, + 0xFB4E75U, 0xFB569EU, 0xFB674BU, 0xFB7FA0U, 0xFB8924U, 0xFB91CFU, 0xFBA01AU, 0xFBB8F1U, 0xFBC3B3U, 0xFBDB58U, + 0xFBEA8DU, 0xFBF266U, 0xFC0CD1U, 0xFC143AU, 0xFC25EFU, 0xFC3D04U, 0xFC4646U, 0xFC5EADU, 0xFC6F78U, 0xFC7793U, + 0xFC8117U, 0xFC99FCU, 0xFCA829U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36BU, 0xFCE2BEU, 0xFCFA55U, 0xFD0FB6U, 0xFD175DU, + 0xFD2688U, 0xFD3E63U, 0xFD4521U, 0xFD5DCAU, 0xFD6C1FU, 0xFD74F4U, 0xFD8270U, 0xFD9A9BU, 0xFDAB4EU, 0xFDB3A5U, + 0xFDC8E7U, 0xFDD00CU, 0xFDE1D9U, 0xFDF932U, 0xFE0A1CU, 0xFE12F7U, 0xFE2322U, 0xFE3BC9U, 0xFE408BU, 0xFE5860U, + 0xFE69B5U, 0xFE715EU, 0xFE87DAU, 0xFE9F31U, 0xFEAEE4U, 0xFEB60FU, 0xFECD4DU, 0xFED5A6U, 0xFEE473U, 0xFEFC98U, + 0xFF097BU, 0xFF1190U, 0xFF2045U, 0xFF38AEU, 0xFF43ECU, 0xFF5B07U, 0xFF6AD2U, 0xFF7239U, 0xFF84BDU, 0xFF9C56U, + 0xFFAD83U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C1U, 0xFFE714U, 0xFFFFFFU}; + +static const unsigned int DECODING_TABLE_23127[] = { + 0x000000U, 0x000001U, 0x000002U, 0x000003U, 0x000004U, 0x000005U, 0x000006U, 0x000007U, 0x000008U, 0x000009U, + 0x00000AU, 0x00000BU, 0x00000CU, 0x00000DU, 0x00000EU, 0x024020U, 0x000010U, 0x000011U, 0x000012U, 0x000013U, + 0x000014U, 0x000015U, 0x000016U, 0x412000U, 0x000018U, 0x000019U, 0x00001AU, 0x180800U, 0x00001CU, 0x200300U, + 0x048040U, 0x001480U, 0x000020U, 0x000021U, 0x000022U, 0x000023U, 0x000024U, 0x000025U, 0x000026U, 0x024008U, + 0x000028U, 0x000029U, 0x00002AU, 0x024004U, 0x00002CU, 0x024002U, 0x024001U, 0x024000U, 0x000030U, 0x000031U, + 0x000032U, 0x008180U, 0x000034U, 0x000C40U, 0x301000U, 0x0C0200U, 0x000038U, 0x043000U, 0x400600U, 0x210040U, + 0x090080U, 0x508000U, 0x002900U, 0x024010U, 0x000040U, 0x000041U, 0x000042U, 0x000043U, 0x000044U, 0x000045U, + 0x000046U, 0x280080U, 0x000048U, 0x000049U, 0x00004AU, 0x002500U, 0x00004CU, 0x111000U, 0x048010U, 0x400A00U, + 0x000050U, 0x000051U, 0x000052U, 0x021200U, 0x000054U, 0x000C20U, 0x048008U, 0x104100U, 0x000058U, 0x404080U, + 0x048004U, 0x210020U, 0x048002U, 0x0A2000U, 0x048000U, 0x048001U, 0x000060U, 0x000061U, 0x000062U, 0x540000U, + 0x000064U, 0x000C10U, 0x010300U, 0x00B000U, 0x000068U, 0x088200U, 0x001880U, 0x210010U, 0x602000U, 0x040180U, + 0x180400U, 0x024040U, 0x000070U, 0x000C04U, 0x086000U, 0x210008U, 0x000C01U, 0x000C00U, 0x420080U, 0x000C02U, + 0x120100U, 0x210002U, 0x210001U, 0x210000U, 0x005200U, 0x000C08U, 0x048020U, 0x210004U, 0x000080U, 0x000081U, + 0x000082U, 0x000083U, 0x000084U, 0x000085U, 0x000086U, 0x280040U, 0x000088U, 0x000089U, 0x00008AU, 0x050200U, + 0x00008CU, 0x00A800U, 0x500100U, 0x001410U, 0x000090U, 0x000091U, 0x000092U, 0x008120U, 0x000094U, 0x160000U, + 0x004A00U, 0x001408U, 0x000098U, 0x404040U, 0x222000U, 0x001404U, 0x090020U, 0x001402U, 0x001401U, 0x001400U, + 0x0000A0U, 0x0000A1U, 0x0000A2U, 0x008110U, 0x0000A4U, 0x401200U, 0x042400U, 0x110800U, 0x0000A8U, 0x300400U, + 0x001840U, 0x482000U, 0x090010U, 0x040140U, 0x208200U, 0x024080U, 0x0000B0U, 0x008102U, 0x008101U, 0x008100U, + 0x090008U, 0x206000U, 0x420040U, 0x008104U, 0x090004U, 0x020A00U, 0x144000U, 0x008108U, 0x090000U, 0x090001U, + 0x090002U, 0x001420U, 0x0000C0U, 0x0000C1U, 0x0000C2U, 0x280004U, 0x0000C4U, 0x280002U, 0x280001U, 0x280000U, + 0x0000C8U, 0x404010U, 0x001820U, 0x128000U, 0x020600U, 0x040120U, 0x016000U, 0x280008U, 0x0000D0U, 0x404008U, + 0x110400U, 0x042800U, 0x003100U, 0x018200U, 0x420020U, 0x280010U, 0x404001U, 0x404000U, 0x080300U, 0x404002U, + 0x300800U, 0x404004U, 0x048080U, 0x001440U, 0x0000E0U, 0x032000U, 0x001808U, 0x004600U, 0x10C000U, 0x040108U, + 0x420010U, 0x280020U, 0x001802U, 0x040104U, 0x001800U, 0x001801U, 0x040101U, 0x040100U, 0x001804U, 0x040102U, + 0x240200U, 0x181000U, 0x420004U, 0x008140U, 0x420002U, 0x000C80U, 0x420000U, 0x420001U, 0x00A400U, 0x404020U, + 0x001810U, 0x210080U, 0x090040U, 0x040110U, 0x420008U, 0x102200U, 0x000100U, 0x000101U, 0x000102U, 0x000103U, + 0x000104U, 0x000105U, 0x000106U, 0x041800U, 0x000108U, 0x000109U, 0x00010AU, 0x002440U, 0x00010CU, 0x200210U, + 0x500080U, 0x098000U, 0x000110U, 0x000111U, 0x000112U, 0x0080A0U, 0x000114U, 0x200208U, 0x0A0400U, 0x104040U, + 0x000118U, 0x200204U, 0x015000U, 0x460000U, 0x200201U, 0x200200U, 0x002820U, 0x200202U, 0x000120U, 0x000121U, + 0x000122U, 0x008090U, 0x000124U, 0x182000U, 0x010240U, 0x600400U, 0x000128U, 0x410800U, 0x2C0000U, 0x101200U, + 0x009400U, 0x0400C0U, 0x002810U, 0x024100U, 0x000130U, 0x008082U, 0x008081U, 0x008080U, 0x444000U, 0x031000U, + 0x002808U, 0x008084U, 0x120040U, 0x084400U, 0x002804U, 0x008088U, 0x002802U, 0x200220U, 0x002800U, 0x002801U, + 0x000140U, 0x000141U, 0x000142U, 0x002408U, 0x000144U, 0x428000U, 0x010220U, 0x104010U, 0x000148U, 0x002402U, + 0x002401U, 0x002400U, 0x084800U, 0x0400A0U, 0x221000U, 0x002404U, 0x000150U, 0x0D0000U, 0x600800U, 0x104004U, + 0x003080U, 0x104002U, 0x104001U, 0x104000U, 0x120020U, 0x009800U, 0x080280U, 0x002410U, 0x410400U, 0x200240U, + 0x048100U, 0x104008U, 0x000160U, 0x205000U, 0x010204U, 0x0A0800U, 0x010202U, 0x040088U, 0x010200U, 0x010201U, + 0x120010U, 0x040084U, 0x40C000U, 0x002420U, 0x040081U, 0x040080U, 0x010208U, 0x040082U, 0x120008U, 0x402200U, + 0x041400U, 0x0080C0U, 0x288000U, 0x000D00U, 0x010210U, 0x104020U, 0x120000U, 0x120001U, 0x120002U, 0x210100U, + 0x120004U, 0x040090U, 0x002840U, 0x481000U, 0x000180U, 0x000181U, 0x000182U, 0x008030U, 0x000184U, 0x014400U, + 0x500008U, 0x022200U, 0x000188U, 0x0A1000U, 0x500004U, 0x204800U, 0x500002U, 0x040060U, 0x500000U, 0x500001U, + 0x000190U, 0x008022U, 0x008021U, 0x008020U, 0x003040U, 0x480800U, 0x250000U, 0x008024U, 0x040C00U, 0x112000U, + 0x080240U, 0x008028U, 0x02C000U, 0x200280U, 0x500010U, 0x001500U, 0x0001A0U, 0x008012U, 0x008011U, 0x008010U, + 0x220800U, 0x040048U, 0x085000U, 0x008014U, 0x006200U, 0x040044U, 0x030400U, 0x008018U, 0x040041U, 0x040040U, + 0x500020U, 0x040042U, 0x008003U, 0x008002U, 0x008001U, 0x008000U, 0x100600U, 0x008006U, 0x008005U, 0x008004U, + 0x601000U, 0x00800AU, 0x008009U, 0x008008U, 0x090100U, 0x040050U, 0x002880U, 0x00800CU, 0x0001C0U, 0x100A00U, + 0x064000U, 0x411000U, 0x003010U, 0x040028U, 0x008C00U, 0x280100U, 0x218000U, 0x040024U, 0x080210U, 0x002480U, + 0x040021U, 0x040020U, 0x500040U, 0x040022U, 0x003004U, 0x220400U, 0x080208U, 0x008060U, 0x003000U, 0x003001U, + 0x003002U, 0x104080U, 0x080202U, 0x404100U, 0x080200U, 0x080201U, 0x003008U, 0x040030U, 0x080204U, 0x030800U, + 0x480400U, 0x04000CU, 0x302000U, 0x008050U, 0x040009U, 0x040008U, 0x010280U, 0x04000AU, 0x040005U, 0x040004U, + 0x001900U, 0x040006U, 0x040001U, 0x040000U, 0x040003U, 0x040002U, 0x014800U, 0x008042U, 0x008041U, 0x008040U, + 0x003020U, 0x040018U, 0x420100U, 0x008044U, 0x120080U, 0x040014U, 0x080220U, 0x008048U, 0x040011U, 0x040010U, + 0x204400U, 0x040012U, 0x000200U, 0x000201U, 0x000202U, 0x000203U, 0x000204U, 0x000205U, 0x000206U, 0x108400U, + 0x000208U, 0x000209U, 0x00020AU, 0x050080U, 0x00020CU, 0x200110U, 0x083000U, 0x400840U, 0x000210U, 0x000211U, + 0x000212U, 0x021040U, 0x000214U, 0x200108U, 0x004880U, 0x0C0020U, 0x000218U, 0x200104U, 0x400420U, 0x00E000U, + 0x200101U, 0x200100U, 0x130000U, 0x200102U, 0x000220U, 0x000221U, 0x000222U, 0x202800U, 0x000224U, 0x401080U, + 0x010140U, 0x0C0010U, 0x000228U, 0x088040U, 0x400410U, 0x101100U, 0x140800U, 0x012400U, 0x208080U, 0x024200U, + 0x000230U, 0x114000U, 0x400408U, 0x0C0004U, 0x02A000U, 0x0C0002U, 0x0C0001U, 0x0C0000U, 0x400402U, 0x020880U, + 0x400400U, 0x400401U, 0x005040U, 0x200120U, 0x400404U, 0x0C0008U, 0x000240U, 0x000241U, 0x000242U, 0x021010U, + 0x000244U, 0x046000U, 0x010120U, 0x400808U, 0x000248U, 0x088020U, 0x304000U, 0x400804U, 0x020480U, 0x400802U, + 0x400801U, 0x400800U, 0x000250U, 0x021002U, 0x021001U, 0x021000U, 0x580000U, 0x018080U, 0x202400U, 0x021004U, + 0x012800U, 0x140400U, 0x080180U, 0x021008U, 0x005020U, 0x200140U, 0x048200U, 0x400810U, 0x000260U, 0x088008U, + 0x010104U, 0x004480U, 0x010102U, 0x320000U, 0x010100U, 0x010101U, 0x088001U, 0x088000U, 0x062000U, 0x088002U, + 0x005010U, 0x088004U, 0x010108U, 0x400820U, 0x240080U, 0x402100U, 0x108800U, 0x021020U, 0x005008U, 0x000E00U, + 0x010110U, 0x0C0040U, 0x005004U, 0x088010U, 0x400440U, 0x210200U, 0x005000U, 0x005001U, 0x005002U, 0x102080U, + 0x000280U, 0x000281U, 0x000282U, 0x050008U, 0x000284U, 0x401020U, 0x004810U, 0x022100U, 0x000288U, 0x050002U, + 0x050001U, 0x050000U, 0x020440U, 0x184000U, 0x208020U, 0x050004U, 0x000290U, 0x082400U, 0x004804U, 0x700000U, + 0x004802U, 0x018040U, 0x004800U, 0x004801U, 0x109000U, 0x020820U, 0x080140U, 0x050010U, 0x442000U, 0x200180U, + 0x004808U, 0x001600U, 0x0002A0U, 0x401004U, 0x1A0000U, 0x004440U, 0x401001U, 0x401000U, 0x208008U, 0x401002U, + 0x006100U, 0x020810U, 0x208004U, 0x050020U, 0x208002U, 0x401008U, 0x208000U, 0x208001U, 0x240040U, 0x020808U, + 0x013000U, 0x008300U, 0x100500U, 0x401010U, 0x004820U, 0x0C0080U, 0x020801U, 0x020800U, 0x400480U, 0x020802U, + 0x090200U, 0x020804U, 0x208010U, 0x102040U, 0x0002C0U, 0x100900U, 0x40A000U, 0x004420U, 0x020408U, 0x018010U, + 0x141000U, 0x280200U, 0x020404U, 0x203000U, 0x080110U, 0x050040U, 0x020400U, 0x020401U, 0x020402U, 0x400880U, + 0x240020U, 0x018004U, 0x080108U, 0x021080U, 0x018001U, 0x018000U, 0x004840U, 0x018002U, 0x080102U, 0x404200U, + 0x080100U, 0x080101U, 0x020410U, 0x018008U, 0x080104U, 0x102020U, 0x240010U, 0x004402U, 0x004401U, 0x004400U, + 0x082800U, 0x401040U, 0x010180U, 0x004404U, 0x510000U, 0x088080U, 0x001A00U, 0x004408U, 0x020420U, 0x040300U, + 0x208040U, 0x102010U, 0x240000U, 0x240001U, 0x240002U, 0x004410U, 0x240004U, 0x018020U, 0x420200U, 0x102008U, + 0x240008U, 0x020840U, 0x080120U, 0x102004U, 0x005080U, 0x102002U, 0x102001U, 0x102000U, 0x000300U, 0x000301U, + 0x000302U, 0x484000U, 0x000304U, 0x200018U, 0x010060U, 0x022080U, 0x000308U, 0x200014U, 0x028800U, 0x101020U, + 0x200011U, 0x200010U, 0x044400U, 0x200012U, 0x000310U, 0x20000CU, 0x142000U, 0x010C00U, 0x200009U, 0x200008U, + 0x409000U, 0x20000AU, 0x200005U, 0x200004U, 0x0800C0U, 0x200006U, 0x200001U, 0x200000U, 0x200003U, 0x200002U, + 0x000320U, 0x060400U, 0x010044U, 0x101008U, 0x010042U, 0x00C800U, 0x010040U, 0x010041U, 0x006080U, 0x101002U, + 0x101001U, 0x101000U, 0x4A0000U, 0x200030U, 0x010048U, 0x101004U, 0x081800U, 0x402040U, 0x224000U, 0x008280U, + 0x100480U, 0x200028U, 0x010050U, 0x0C0100U, 0x058000U, 0x200024U, 0x400500U, 0x101010U, 0x200021U, 0x200020U, + 0x002A00U, 0x200022U, 0x000340U, 0x100880U, 0x010024U, 0x248000U, 0x010022U, 0x081400U, 0x010020U, 0x010021U, + 0x441000U, 0x034000U, 0x080090U, 0x002600U, 0x10A000U, 0x200050U, 0x010028U, 0x400900U, 0x00C400U, 0x402020U, + 0x080088U, 0x021100U, 0x060800U, 0x200048U, 0x010030U, 0x104200U, 0x080082U, 0x200044U, 0x080080U, 0x080081U, + 0x200041U, 0x200040U, 0x080084U, 0x200042U, 0x010006U, 0x402010U, 0x010004U, 0x010005U, 0x010002U, 0x010003U, + 0x010000U, 0x010001U, 0x200C00U, 0x088100U, 0x01000CU, 0x101040U, 0x01000AU, 0x040280U, 0x010008U, 0x010009U, + 0x402001U, 0x402000U, 0x010014U, 0x402002U, 0x010012U, 0x402004U, 0x010010U, 0x010011U, 0x120200U, 0x402008U, + 0x0800A0U, 0x044800U, 0x005100U, 0x200060U, 0x010018U, 0x028400U, 0x000380U, 0x100840U, 0x201400U, 0x022004U, + 0x0C8000U, 0x022002U, 0x022001U, 0x022000U, 0x006020U, 0x408400U, 0x080050U, 0x050100U, 0x011800U, 0x200090U, + 0x500200U, 0x022008U, 0x430000U, 0x045000U, 0x080048U, 0x008220U, 0x100420U, 0x200088U, 0x004900U, 0x022010U, + 0x080042U, 0x200084U, 0x080040U, 0x080041U, 0x200081U, 0x200080U, 0x080044U, 0x200082U, 0x006008U, 0x290000U, + 0x440800U, 0x008210U, 0x100410U, 0x401100U, 0x0100C0U, 0x022020U, 0x006000U, 0x006001U, 0x006002U, 0x101080U, + 0x006004U, 0x040240U, 0x208100U, 0x080C00U, 0x100404U, 0x008202U, 0x008201U, 0x008200U, 0x100400U, 0x100401U, + 0x100402U, 0x008204U, 0x006010U, 0x020900U, 0x080060U, 0x008208U, 0x100408U, 0x2000A0U, 0x061000U, 0x414000U, + 0x100801U, 0x100800U, 0x080018U, 0x100802U, 0x604000U, 0x100804U, 0x0100A0U, 0x022040U, 0x080012U, 0x100808U, + 0x080010U, 0x080011U, 0x020500U, 0x040220U, 0x080014U, 0x00D000U, 0x08000AU, 0x100810U, 0x080008U, 0x080009U, + 0x003200U, 0x018100U, 0x08000CU, 0x440400U, 0x080002U, 0x080003U, 0x080000U, 0x080001U, 0x080006U, 0x2000C0U, + 0x080004U, 0x080005U, 0x029000U, 0x100820U, 0x010084U, 0x004500U, 0x010082U, 0x040208U, 0x010080U, 0x010081U, + 0x006040U, 0x040204U, 0x080030U, 0x620000U, 0x040201U, 0x040200U, 0x010088U, 0x040202U, 0x240100U, 0x402080U, + 0x080028U, 0x008240U, 0x100440U, 0x0A4000U, 0x010090U, 0x201800U, 0x080022U, 0x011400U, 0x080020U, 0x080021U, + 0x408800U, 0x040210U, 0x080024U, 0x102100U, 0x000400U, 0x000401U, 0x000402U, 0x000403U, 0x000404U, 0x000405U, + 0x000406U, 0x108200U, 0x000408U, 0x000409U, 0x00040AU, 0x002140U, 0x00040CU, 0x4C0000U, 0x210800U, 0x001090U, + 0x000410U, 0x000411U, 0x000412U, 0x244000U, 0x000414U, 0x000860U, 0x0A0100U, 0x001088U, 0x000418U, 0x038000U, + 0x400220U, 0x001084U, 0x106000U, 0x001082U, 0x001081U, 0x001080U, 0x000420U, 0x000421U, 0x000422U, 0x091000U, + 0x000424U, 0x000850U, 0x042080U, 0x600100U, 0x000428U, 0x300080U, 0x400210U, 0x048800U, 0x009100U, 0x012200U, + 0x180040U, 0x024400U, 0x000430U, 0x000844U, 0x400208U, 0x122000U, 0x000841U, 0x000840U, 0x01C000U, 0x000842U, + 0x400202U, 0x084100U, 0x400200U, 0x400201U, 0x260000U, 0x000848U, 0x400204U, 0x0010A0U, 0x000440U, 0x000441U, + 0x000442U, 0x002108U, 0x000444U, 0x000830U, 0x405000U, 0x070000U, 0x000448U, 0x002102U, 0x002101U, 0x002100U, + 0x020280U, 0x20C000U, 0x180020U, 0x002104U, 0x000450U, 0x000824U, 0x110080U, 0x488000U, 0x000821U, 0x000820U, + 0x202200U, 0x000822U, 0x281000U, 0x140200U, 0x024800U, 0x002110U, 0x410100U, 0x000828U, 0x048400U, 0x0010C0U, + 0x000460U, 0x000814U, 0x228000U, 0x004280U, 0x000811U, 0x000810U, 0x180008U, 0x000812U, 0x054000U, 0x421000U, + 0x180004U, 0x002120U, 0x180002U, 0x000818U, 0x180000U, 0x180001U, 0x000805U, 0x000804U, 0x041100U, 0x000806U, + 0x000801U, 0x000800U, 0x000803U, 0x000802U, 0x00A080U, 0x00080CU, 0x400240U, 0x210400U, 0x000809U, 0x000808U, + 0x180010U, 0x00080AU, 0x000480U, 0x000481U, 0x000482U, 0x420800U, 0x000484U, 0x014100U, 0x042020U, 0x001018U, + 0x000488U, 0x300020U, 0x08C000U, 0x001014U, 0x020240U, 0x001012U, 0x001011U, 0x001010U, 0x000490U, 0x082200U, + 0x110040U, 0x00100CU, 0x608000U, 0x00100AU, 0x001009U, 0x001008U, 0x040900U, 0x001006U, 0x001005U, 0x001004U, + 0x001003U, 0x001002U, 0x001001U, 0x001000U, 0x0004A0U, 0x300008U, 0x042004U, 0x004240U, 0x042002U, 0x0A8000U, + 0x042000U, 0x042001U, 0x300001U, 0x300000U, 0x030100U, 0x300002U, 0x404800U, 0x300004U, 0x042008U, 0x001030U, + 0x025000U, 0x450000U, 0x280800U, 0x008500U, 0x100300U, 0x0008C0U, 0x042010U, 0x001028U, 0x00A040U, 0x300010U, + 0x400280U, 0x001024U, 0x090400U, 0x001022U, 0x001021U, 0x001020U, 0x0004C0U, 0x049000U, 0x110010U, 0x004220U, + 0x020208U, 0x502000U, 0x008900U, 0x280400U, 0x020204U, 0x090800U, 0x640000U, 0x002180U, 0x020200U, 0x020201U, + 0x020202U, 0x001050U, 0x110002U, 0x220100U, 0x110000U, 0x110001U, 0x0C4000U, 0x0008A0U, 0x110004U, 0x001048U, + 0x00A020U, 0x404400U, 0x110008U, 0x001044U, 0x020210U, 0x001042U, 0x001041U, 0x001040U, 0x480100U, 0x004202U, + 0x004201U, 0x004200U, 0x211000U, 0x000890U, 0x042040U, 0x004204U, 0x00A010U, 0x300040U, 0x001C00U, 0x004208U, + 0x020220U, 0x040500U, 0x180080U, 0x418000U, 0x00A008U, 0x000884U, 0x110020U, 0x004210U, 0x000881U, 0x000880U, + 0x420400U, 0x000882U, 0x00A000U, 0x00A001U, 0x00A002U, 0x0E0000U, 0x00A004U, 0x000888U, 0x204100U, 0x001060U, + 0x000500U, 0x000501U, 0x000502U, 0x002048U, 0x000504U, 0x014080U, 0x0A0010U, 0x600020U, 0x000508U, 0x002042U, + 0x002041U, 0x002040U, 0x009020U, 0x120800U, 0x044200U, 0x002044U, 0x000510U, 0x501000U, 0x0A0004U, 0x010A00U, + 0x0A0002U, 0x04A000U, 0x0A0000U, 0x0A0001U, 0x040880U, 0x084020U, 0x308000U, 0x002050U, 0x410040U, 0x200600U, + 0x0A0008U, 0x001180U, 0x000520U, 0x060200U, 0x104800U, 0x600004U, 0x009008U, 0x600002U, 0x600001U, 0x600000U, + 0x009004U, 0x084010U, 0x030080U, 0x002060U, 0x009000U, 0x009001U, 0x009002U, 0x600008U, 0x212000U, 0x084008U, + 0x041040U, 0x008480U, 0x100280U, 0x000940U, 0x0A0020U, 0x600010U, 0x084001U, 0x084000U, 0x400300U, 0x084002U, + 0x009010U, 0x084004U, 0x002C00U, 0x150000U, 0x000540U, 0x00200AU, 0x002009U, 0x002008U, 0x340000U, 0x081200U, + 0x008880U, 0x00200CU, 0x002003U, 0x002002U, 0x002001U, 0x002000U, 0x410010U, 0x002006U, 0x002005U, 0x002004U, + 0x00C200U, 0x220080U, 0x041020U, 0x002018U, 0x410008U, 0x000920U, 0x0A0040U, 0x104400U, 0x410004U, 0x002012U, + 0x002011U, 0x002010U, 0x410000U, 0x410001U, 0x410002U, 0x002014U, 0x480080U, 0x118000U, 0x041010U, 0x002028U, + 0x026000U, 0x000910U, 0x010600U, 0x600040U, 0x200A00U, 0x002022U, 0x002021U, 0x002020U, 0x009040U, 0x040480U, + 0x180100U, 0x002024U, 0x041002U, 0x000904U, 0x041000U, 0x041001U, 0x000901U, 0x000900U, 0x041004U, 0x000902U, + 0x120400U, 0x084040U, 0x041008U, 0x002030U, 0x410020U, 0x000908U, 0x204080U, 0x028200U, 0x000580U, 0x014004U, + 0x201200U, 0x1C0000U, 0x014001U, 0x014000U, 0x008840U, 0x014002U, 0x040810U, 0x408200U, 0x030020U, 0x0020C0U, + 0x282000U, 0x014008U, 0x500400U, 0x001110U, 0x040808U, 0x220040U, 0x406000U, 0x008420U, 0x100220U, 0x014010U, + 0x0A0080U, 0x001108U, 0x040800U, 0x040801U, 0x040802U, 0x001104U, 0x040804U, 0x001102U, 0x001101U, 0x001100U, + 0x480040U, 0x003800U, 0x030008U, 0x008410U, 0x100210U, 0x014020U, 0x042100U, 0x600080U, 0x030002U, 0x300100U, + 0x030000U, 0x030001U, 0x009080U, 0x040440U, 0x030004U, 0x080A00U, 0x100204U, 0x008402U, 0x008401U, 0x008400U, + 0x100200U, 0x100201U, 0x100202U, 0x008404U, 0x040820U, 0x084080U, 0x030010U, 0x008408U, 0x100208U, 0x422000U, + 0x204040U, 0x001120U, 0x480020U, 0x220010U, 0x008804U, 0x002088U, 0x008802U, 0x014040U, 0x008800U, 0x008801U, + 0x105000U, 0x002082U, 0x002081U, 0x002080U, 0x020300U, 0x040420U, 0x008808U, 0x002084U, 0x220001U, 0x220000U, + 0x110100U, 0x220002U, 0x003400U, 0x220004U, 0x008810U, 0x440200U, 0x040840U, 0x220008U, 0x080600U, 0x002090U, + 0x410080U, 0x188000U, 0x204020U, 0x001140U, 0x480000U, 0x480001U, 0x480002U, 0x004300U, 0x480004U, 0x040408U, + 0x008820U, 0x121000U, 0x480008U, 0x040404U, 0x030040U, 0x0020A0U, 0x040401U, 0x040400U, 0x204010U, 0x040402U, + 0x480010U, 0x220020U, 0x041080U, 0x008440U, 0x100240U, 0x000980U, 0x204008U, 0x092000U, 0x00A100U, 0x011200U, + 0x204004U, 0x500800U, 0x204002U, 0x040410U, 0x204000U, 0x204001U, 0x000600U, 0x000601U, 0x000602U, 0x108004U, + 0x000604U, 0x108002U, 0x108001U, 0x108000U, 0x000608U, 0x005800U, 0x400030U, 0x2A0000U, 0x0200C0U, 0x012020U, + 0x044100U, 0x108008U, 0x000610U, 0x082080U, 0x400028U, 0x010900U, 0x051000U, 0x424000U, 0x202040U, 0x108010U, + 0x400022U, 0x140040U, 0x400020U, 0x400021U, 0x088800U, 0x200500U, 0x400024U, 0x001280U, 0x000620U, 0x060100U, + 0x400018U, 0x0040C0U, 0x284000U, 0x012008U, 0x021800U, 0x108020U, 0x400012U, 0x012004U, 0x400010U, 0x400011U, + 0x012001U, 0x012000U, 0x400014U, 0x012002U, 0x40000AU, 0x209000U, 0x400008U, 0x400009U, 0x100180U, 0x000A40U, + 0x40000CU, 0x0C0400U, 0x400002U, 0x400003U, 0x400000U, 0x400001U, 0x400006U, 0x012010U, 0x400004U, 0x400005U, + 0x000640U, 0x610000U, 0x0C0800U, 0x0040A0U, 0x020088U, 0x081100U, 0x202010U, 0x108040U, 0x020084U, 0x140010U, + 0x019000U, 0x002300U, 0x020080U, 0x020081U, 0x020082U, 0x400C00U, 0x00C100U, 0x140008U, 0x202004U, 0x021400U, + 0x202002U, 0x000A20U, 0x202000U, 0x202001U, 0x140001U, 0x140000U, 0x400060U, 0x140002U, 0x020090U, 0x140004U, + 0x202008U, 0x094000U, 0x103000U, 0x004082U, 0x004081U, 0x004080U, 0x448000U, 0x000A10U, 0x010500U, 0x004084U, + 0x200900U, 0x088400U, 0x400050U, 0x004088U, 0x0200A0U, 0x012040U, 0x180200U, 0x241000U, 0x0B0000U, 0x000A04U, + 0x400048U, 0x004090U, 0x000A01U, 0x000A00U, 0x202020U, 0x000A02U, 0x400042U, 0x140020U, 0x400040U, 0x400041U, + 0x005400U, 0x000A08U, 0x400044U, 0x028100U, 0x000680U, 0x082010U, 0x201100U, 0x004060U, 0x020048U, 0x240800U, + 0x490000U, 0x108080U, 0x020044U, 0x408100U, 0x102800U, 0x050400U, 0x020040U, 0x020041U, 0x020042U, 0x001210U, + 0x082001U, 0x082000U, 0x068000U, 0x082002U, 0x100120U, 0x082004U, 0x004C00U, 0x001208U, 0x214000U, 0x082008U, + 0x4000A0U, 0x001204U, 0x020050U, 0x001202U, 0x001201U, 0x001200U, 0x018800U, 0x004042U, 0x004041U, 0x004040U, + 0x100110U, 0x401400U, 0x042200U, 0x004044U, 0x0C1000U, 0x300200U, 0x400090U, 0x004048U, 0x020060U, 0x012080U, + 0x208400U, 0x080900U, 0x100104U, 0x082020U, 0x400088U, 0x004050U, 0x100100U, 0x100101U, 0x100102U, 0x230000U, + 0x400082U, 0x020C00U, 0x400080U, 0x400081U, 0x100108U, 0x04C000U, 0x400084U, 0x001220U, 0x02000CU, 0x004022U, + 0x004021U, 0x004020U, 0x020008U, 0x020009U, 0x02000AU, 0x004024U, 0x020004U, 0x020005U, 0x020006U, 0x004028U, + 0x020000U, 0x020001U, 0x020002U, 0x020003U, 0x401800U, 0x082040U, 0x110200U, 0x004030U, 0x020018U, 0x018400U, + 0x202080U, 0x440100U, 0x020014U, 0x140080U, 0x080500U, 0x208800U, 0x020010U, 0x020011U, 0x020012U, 0x001240U, + 0x004003U, 0x004002U, 0x004001U, 0x004000U, 0x020028U, 0x004006U, 0x004005U, 0x004004U, 0x020024U, 0x00400AU, + 0x004009U, 0x004008U, 0x020020U, 0x020021U, 0x020022U, 0x00400CU, 0x240400U, 0x004012U, 0x004011U, 0x004010U, + 0x100140U, 0x000A80U, 0x089000U, 0x004014U, 0x00A200U, 0x011100U, 0x4000C0U, 0x004018U, 0x020030U, 0x680000U, + 0x050800U, 0x102400U, 0x000700U, 0x060020U, 0x201080U, 0x010810U, 0x402800U, 0x081040U, 0x044008U, 0x108100U, + 0x190000U, 0x408080U, 0x044004U, 0x002240U, 0x044002U, 0x200410U, 0x044000U, 0x044001U, 0x00C040U, 0x010802U, + 0x010801U, 0x010800U, 0x1000A0U, 0x200408U, 0x0A0200U, 0x010804U, 0x023000U, 0x200404U, 0x400120U, 0x010808U, + 0x200401U, 0x200400U, 0x044010U, 0x200402U, 0x060001U, 0x060000U, 0x08A000U, 0x060002U, 0x100090U, 0x060004U, + 0x010440U, 0x600200U, 0x200840U, 0x060008U, 0x400110U, 0x101400U, 0x009200U, 0x012100U, 0x044020U, 0x080880U, + 0x100084U, 0x060010U, 0x400108U, 0x010820U, 0x100080U, 0x100081U, 0x100082U, 0x007000U, 0x400102U, 0x084200U, + 0x400100U, 0x400101U, 0x100088U, 0x200420U, 0x400104U, 0x028040U, 0x00C010U, 0x081004U, 0x520000U, 0x002208U, + 0x081001U, 0x081000U, 0x010420U, 0x081002U, 0x200820U, 0x002202U, 0x002201U, 0x002200U, 0x020180U, 0x081008U, + 0x044040U, 0x002204U, 0x00C000U, 0x00C001U, 0x00C002U, 0x010840U, 0x00C004U, 0x081010U, 0x202100U, 0x440080U, + 0x00C008U, 0x140100U, 0x080480U, 0x002210U, 0x410200U, 0x200440U, 0x101800U, 0x028020U, 0x200808U, 0x060040U, + 0x010404U, 0x004180U, 0x010402U, 0x081020U, 0x010400U, 0x010401U, 0x200800U, 0x200801U, 0x200802U, 0x002220U, + 0x200804U, 0x504000U, 0x010408U, 0x028010U, 0x00C020U, 0x402400U, 0x041200U, 0x380000U, 0x1000C0U, 0x000B00U, + 0x010410U, 0x028008U, 0x200810U, 0x011080U, 0x400140U, 0x028004U, 0x0C2000U, 0x028002U, 0x028001U, 0x028000U, + 0x201002U, 0x408008U, 0x201000U, 0x201001U, 0x100030U, 0x014200U, 0x201004U, 0x022400U, 0x408001U, 0x408000U, + 0x201008U, 0x408002U, 0x020140U, 0x408004U, 0x044080U, 0x080820U, 0x100024U, 0x082100U, 0x201010U, 0x010880U, + 0x100020U, 0x100021U, 0x100022U, 0x440040U, 0x040A00U, 0x408010U, 0x080440U, 0x124000U, 0x100028U, 0x200480U, + 0x01A000U, 0x001300U, 0x100014U, 0x060080U, 0x201020U, 0x004140U, 0x100010U, 0x100011U, 0x100012U, 0x080808U, + 0x006400U, 0x408020U, 0x030200U, 0x080804U, 0x100018U, 0x080802U, 0x080801U, 0x080800U, 0x100004U, 0x100005U, + 0x100006U, 0x008600U, 0x100000U, 0x100001U, 0x100002U, 0x100003U, 0x10000CU, 0x011040U, 0x400180U, 0x242000U, + 0x100008U, 0x100009U, 0x10000AU, 0x080810U, 0x052000U, 0x100C00U, 0x201040U, 0x004120U, 0x020108U, 0x081080U, + 0x008A00U, 0x440010U, 0x020104U, 0x408040U, 0x080410U, 0x002280U, 0x020100U, 0x020101U, 0x020102U, 0x310000U, + 0x00C080U, 0x220200U, 0x080408U, 0x440004U, 0x100060U, 0x440002U, 0x440001U, 0x440000U, 0x080402U, 0x011020U, + 0x080400U, 0x080401U, 0x020110U, 0x006800U, 0x080404U, 0x440008U, 0x480200U, 0x004102U, 0x004101U, 0x004100U, + 0x100050U, 0x20A000U, 0x010480U, 0x004104U, 0x200880U, 0x011010U, 0x148000U, 0x004108U, 0x020120U, 0x040600U, + 0x403000U, 0x080840U, 0x100044U, 0x011008U, 0x022800U, 0x004110U, 0x100040U, 0x100041U, 0x100042U, 0x440020U, + 0x011001U, 0x011000U, 0x080420U, 0x011002U, 0x100048U, 0x011004U, 0x204200U, 0x028080U}; + +#define X22 0x00400000 /* vector representation of X^{22} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK12 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +static unsigned int get_syndrome_23127(unsigned int pattern) +/* + * Compute the syndrome corresponding to the given pattern, i.e., the + * remainder after dividing the pattern (when considering it as the vector + * representation of a polynomial) by the generator polynomial, GENPOL. + * In the program this pattern has several meanings: (1) pattern = infomation + * bits, when constructing the encoding table; (2) pattern = error pattern, + * when constructing the decoding table; and (3) pattern = received vector, to + * obtain its syndrome in decoding. + */ +{ + unsigned int aux = X22; + + if (pattern >= X11) { + while (pattern & MASK12) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} + +unsigned int CGolay24128::encode23127(unsigned int data) +{ + return ENCODING_TABLE_23127[data]; +} + +unsigned int CGolay24128::encode24128(unsigned int data) +{ + return ENCODING_TABLE_24128[data]; +} + +unsigned int CGolay24128::decode23127(unsigned int code) +{ + unsigned int syndrome = ::get_syndrome_23127(code); + unsigned int error_pattern = DECODING_TABLE_23127[syndrome]; + + code ^= error_pattern; + + return code >> 11; +} + +unsigned int CGolay24128::decode24128(unsigned int code) +{ + return decode23127(code >> 1); +} + +unsigned int CGolay24128::decode24128(unsigned char* bytes) +{ + assert(bytes != NULL); + + unsigned int code = bytes[0U]; + code <<= 8; + code |= bytes[1U]; + code <<= 8; + code |= bytes[2U]; + + return decode23127(code >> 1); +} diff --git a/Golay24128.h b/Golay24128.h new file mode 100644 index 0000000..1ac7852 --- /dev/null +++ b/Golay24128.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Golay24128_H +#define Golay24128_H + +class CGolay24128 { +public: + static unsigned int encode23127(unsigned int data); + static unsigned int encode24128(unsigned int data); + + static unsigned int decode23127(unsigned int code); + static unsigned int decode24128(unsigned int code); + static unsigned int decode24128(unsigned char* bytes); +}; + +#endif diff --git a/HostsTab.qml b/HostsTab.qml new file mode 100644 index 0000000..7300d06 --- /dev/null +++ b/HostsTab.qml @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 +import QtQuick.Controls 2.3 + +Item { + id: hostsTab + property alias hostsTextEdit: hostsTxtEdit + Rectangle{ + id: hostsList + x: 20 + y: 20 + width: parent.width - 40 + height: parent.height - 40 + color: "#252424" + Flickable{ + anchors.fill: parent + contentWidth: parent.width + contentHeight: hostsTxtEdit.y + + hostsTxtEdit.height + flickableDirection: Flickable.VerticalFlick + clip: true + Text { + id: hostsText + width: hostsText.width + wrapMode: Text.WordWrap + color: "white" + text: qsTr("Custom hostfile format:\n" + + " \n" + + "Example: REF REF123 192.168.1.1 20001\n" + + "Example: DMR MyNet 192.168.1.1 62030 passw0rd") + } + TextArea { + id: hostsTxtEdit + x: 0 + y: hostsText.height + 5 + width: hostsList.width + height: 500 + background: Rectangle { + color: "#000000" + radius: 5 + } + wrapMode: TextArea.WordWrap + text: qsTr("") + onEditingFinished: { + droidstar.update_custom_hosts(hostsTxtEdit.text); + } + } + } + } +} diff --git a/Info.plist b/Info.plist new file mode 100755 index 0000000..d21492b --- /dev/null +++ b/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleExecutable + DroidStar + CFBundleIconFile + + CFBundleIdentifier + com.dudetronics.droidstar + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSMicrophoneUsageDescription + You know you want to... + NOTE + This file was generated by Qt/QMake. + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/LogTab.qml b/LogTab.qml new file mode 100644 index 0000000..e6462ee --- /dev/null +++ b/LogTab.qml @@ -0,0 +1,59 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 +import QtQuick.Controls 2.3 + +Item { + id: logTab + property alias logText: logTxt + Button { + id: clearLogButton + x: 10 + y: 5 + width: 100 + height: 30 + text: qsTr("Clear") + onClicked: { + logTxt.clear(); + } + } + Rectangle{ + id: logTextBox + x: 20 + y: 40 + width: parent.width - 40 + height: parent.height - 40 + color: "#252424" + Flickable{ + id: logflick + anchors.fill: parent + contentWidth: parent.width + contentHeight: logTxt.y + + logTxt.height + flickableDirection: Flickable.VerticalFlick + clip: true + TextArea { + id: logTxt + width: logTextBox.width + readOnly: true + wrapMode: TextArea.WordWrap + text: qsTr("") + } + } + } +} diff --git a/MainTab.qml b/MainTab.qml new file mode 100644 index 0000000..e1890d0 --- /dev/null +++ b/MainTab.qml @@ -0,0 +1,593 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 +//import QtQuick.Window 2.15 +import QtQuick.Controls 2.3 +//import QtQuick.Dialogs 1.3 +//import org.dudetronics.droidstar 1.0 + +Item { + id: mainTab + property alias element1: _element1 + property alias element3: _element3 + property alias element4: _element4 + property alias label1: _label1 + property alias label2: _label2 + property alias label3: _label3 + property alias label4: _label4 + property alias label5: _label5 + property alias label6: _label6 + property alias status: _status + property alias levelMeter: _levelMeter + property alias uitimer: _uitimer + property alias comboMode: _comboMode + property alias comboHost: _comboHost + property alias editIAXDTMF: _editIAXDTMF + property alias dtmfsendbutton: _dtmfsendbutton + property alias comboModule: _comboModule + property alias dmrtgidEdit: _dmrtgidEdit + property alias privateBox: _privateBox + property alias connectbutton: _connectbutton + property alias data1: _data1 + property alias data2: _data2 + property alias data3: _data3 + property alias data4: _data4 + property alias data5: _data5 + property alias data6: _data6 + property alias txtimer: _txtimer + property alias buttonTX: _buttonTX + property alias btntxt: _btntxt + property alias swtxBox: _swtxBox + property alias swrxBox: _swrxBox + property alias agcBox: _agcBox + + Text { + id: element + x: 10 + y: 0 + width: parent.width / 4 + height: parent.height / 15; + text: qsTr("Mode") + color: "white" + font.pixelSize: parent.height / 30; + verticalAlignment: Text.AlignVCenter + } + + Text { + id: _element1 + x: 10 + y: (parent.height / 15 + 3) * 1; + width: parent.width / 4 + height: parent.height / 15; + text: qsTr("Host") + color: "white" + font.pixelSize: parent.height / 30; + verticalAlignment: Text.AlignVCenter + } + + Text { + id: _element4 + x: 10 + y: (parent.height / 15 + 3) * 2; + width: parent.width / 5 + height: parent.height / 15; + text: qsTr("Mod") + color: "white" + font.pixelSize: parent.height / 30; + verticalAlignment: Text.AlignVCenter + } + + Text { + id: _element3 + x: 10 + y: (parent.height / 15 + 3) * 3; + width: parent.width / 4 + height: parent.height / 15; + text: qsTr("TG ID") + color: "white" + font.pixelSize: parent.height / 30; + verticalAlignment: Text.AlignVCenter + } + + Text { + id: _label1 + x: 10 + y: (parent.height / 15 + 3) * 4; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("MYCALL") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _label2 + x: 10 + y: (parent.height / 15 + 3) * 5; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("URCALL") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _label3 + x: 10 + y: (parent.height / 15 + 3) * 6; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("RPTR1") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _label4 + x: 10 + y: (parent.height / 15 + 3) * 7; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("RPTR2") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _label5 + x: 10 + y: (parent.height / 15 + 3) * 8; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("StrmID") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _label6 + x: 10 + y: (parent.height / 15 + 3) * 9; + width: parent.width / 3 + height: parent.height / 20; + text: qsTr("Text") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _status + x: 10 + y: (parent.height / 15 + 3) * 10; + width: parent.width - 20 + height: parent.height / 20; + text: qsTr("Not Connected") + color: "white" + font.pixelSize: parent.height / 35; + } + Rectangle { + x: 10 + y: (parent.height / 15 + 3) * 11; + width: parent.width - 20 + height: parent.height / 20; + color: "black" + border.color: "black" + border.width: 2 + radius: 5 + } + Rectangle { + id: _levelMeter + x: 10 + y: (parent.height / 15 + 3) * 11; + width: 0 + height: parent.height / 20; + color: "#80C342" + border.color: "black" + border.width: 2 + radius: 5 + } + Timer { + id: _uitimer + interval: 20; running: true; repeat: true + property var cnt: 0; + property var rxcnt: 0; + property var last_rxcnt: 0; + onTriggered: update_level(); + + function update_level(){ + if(cnt >= 20){ + if(rxcnt == last_rxcnt){ + droidstar.set_output_level(0); + rxcnt = 0; + //console.log("TIMEOUT"); + } + else{ + last_rxcnt = rxcnt; + } + + cnt = 0; + } + else{ + ++cnt; + } + + var l = (parent.width - 20) * droidstar.get_output_level() / 32767.0; + if(l > _levelMeter.width){ + _levelMeter.width = l; + } + else{ + if(_levelMeter.width > 0) + _levelMeter.width -= 8; + else + _levelMeter.width = 0; + } + } + } + ComboBox { + id: _comboMode + property bool loaded: false + x: parent.width / 4 + y: 0 + width: (parent.width * 3 / 8) - 4 + height: parent.height / 15; + font.pixelSize: parent.height / 35 + model: ["M17", "YSF", "FCS", "DMR", "P25", "NXDN", "REF", "XRF", "DCS", "IAX"] + contentItem: Text { + text: _comboMode.displayText + font: _comboMode.font + leftPadding: 10 + verticalAlignment: Text.AlignVCenter + color: _comboMode.enabled ? "white" : "darkgrey" + } + onCurrentTextChanged: { + if(_comboMode.loaded){ + droidstar.process_mode_change(_comboMode.currentText); + } + } + } + + ComboBox { + id: _comboHost + x: (parent.width / 4) + y: (parent.height / 15 + 3) * 1; + width: ((parent.width * 3) / 4) - 5 + height: parent.height / 15; + font.pixelSize: parent.height / 35 + contentItem: Text { + text: _comboHost.displayText + font: _comboHost.font + leftPadding: 10 + verticalAlignment: Text.AlignVCenter + color: _comboHost.enabled ? "white" : "darkgrey" + } + onCurrentTextChanged: { + if(!droidstar.get_modelchange()){ + droidstar.process_host_change(_comboHost.currentText); + } + } + } + TextField { + id: _editIAXDTMF + x: (parent.width / 4) + y: (parent.height / 15 + 3) * 1; + width: (parent.width * 3 / 8) - 4; + height: parent.height / 15; + font.pixelSize: parent.height / 35 + inputMethodHints: "ImhPreferNumbers" + } + Button { + id: _dtmfsendbutton + x: (parent.width * 5 / 8) + y: (parent.height / 15 + 3) * 1; + width: (parent.width * 3 / 8) - 5; + height: parent.height / 15; + text: qsTr("Send") + font.pixelSize: parent.height / 30 + onClicked: { + droidstar.dtmf_send_clicked(editIAXDTMF.text); + } + } + + ComboBox { + id: _comboModule + x: parent.width / 5 + y: (parent.height / 15 + 3) * 2 + width: parent.width / 5 + height: parent.height / 15; + font.pixelSize: parent.height / 35 + model: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] + contentItem: Text { + text: _comboModule.displayText + font: _comboModule.font + leftPadding: 10 + verticalAlignment: Text.AlignVCenter + color: _comboModule.enabled ? "white" : "darkgrey" + } + onCurrentTextChanged: { + if(_comboMode.loaded){ + droidstar.set_module(_comboModule.currentText); + } + } + } + + CheckBox { + id: _swtxBox + x: (parent.width * 2 / 5) + y: (parent.height / 15 + 3) * 2; + width: parent.width / 4 + height: parent.height / 15 + text: qsTr("SWTX") + onClicked:{ + droidstar.set_swtx(_swtxBox.checked) + } + } + + CheckBox { + id: _swrxBox + x: (parent.width * 3 / 5) + y: (parent.height / 15 + 3) * 2; + width: parent.width / 4 + height: parent.height / 15 + text: qsTr("SWRX") + onClicked:{ + droidstar.set_swrx(_swrxBox.checked) + } + } + + CheckBox { + id: _agcBox + x: (parent.width * 4 / 5) + y: (parent.height / 15 + 3) * 2; + width: parent.width / 4 + height: parent.height / 15 + text: qsTr("AGC") + onClicked:{ + droidstar.set_agc(_agcBox.checked) + } + } + + TextField { + visible: false + id: _dmrtgidEdit + x: parent.width / 4 + y: (parent.height / 15 + 3) * 3 + width: (parent.width * 3 / 8) - 4 + height: parent.height / 15 + font.pixelSize: parent.height / 30 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + text: qsTr("") + onEditingFinished: { + droidstar.tgid_text_changed(dmrtgidEdit.text) + } + } + CheckBox { + id: _privateBox + x: (parent.width * 5 / 8) + y: (parent.height / 15 + 3) * 3; + width: (parent.width * 3 / 8) - 5 + height: parent.height / 15 + text: qsTr("Private") + onClicked:{ + droidstar.set_dmr_pc(privateBox.checked) + //console.log("screen size ", parent.width, " x ", parent.height); + } + } + + Button { + id: _connectbutton + x: (parent.width * 5 / 8) + y: 0 + width: (parent.width * 3 / 8) - 5 + height: parent.height / 15 + text: qsTr("Connect") + font.pixelSize: parent.height / 30 + onClicked: { + //settingsTab.callsignEdit.text = settingsTab.callsignEdit.text.toUpperCase(); + droidstar.set_callsign(settingsTab.callsignEdit.text.toUpperCase()); + //droidstar.set_host(comboHost.currentText); + droidstar.set_module(comboModule.currentText); + droidstar.set_protocol(comboMode.currentText); + droidstar.set_dmrtgid(dmrtgidEdit.text); + droidstar.set_dmrid(settingsTab.dmridEdit.text); + droidstar.set_essid(settingsTab.comboEssid.currentText); + droidstar.set_bm_password(settingsTab.bmpwEdit.text); + droidstar.set_tgif_password(settingsTab.tgifpwEdit.text); + droidstar.set_latitude(settingsTab.latEdit.text); + droidstar.set_longitude(settingsTab.lonEdit.text); + droidstar.set_location(settingsTab.locEdit.text); + droidstar.set_description(settingsTab.descEdit.text); + droidstar.set_url(settingsTab.urlEdit.text); + droidstar.set_swid(settingsTab.swidEdit.text); + droidstar.set_pkgid(settingsTab.pkgidEdit.text); + droidstar.set_dmr_options(settingsTab.dmroptsEdit.text); + droidstar.set_iaxuser(settingsTab.iaxuserEdit.text); + droidstar.set_iaxpass(settingsTab.iaxpassEdit.text); + droidstar.set_iaxnode(settingsTab.iaxnodeEdit.text); + droidstar.set_iaxhost(settingsTab.iaxhostEdit.text); + droidstar.set_iaxport(settingsTab.iaxportEdit.text); + droidstar.set_txtimeout(settingsTab.txtimerEdit.text); + //droidstar.set_toggletx(toggleTX.checked); + droidstar.set_xrf2ref(settingsTab.xrf2ref.checked); + droidstar.set_ipv6(settingsTab.ipv6.checked); + droidstar.set_vocoder(settingsTab.comboVocoder.currentText); + droidstar.set_modem(settingsTab.comboModem.currentText); + droidstar.set_playback(settingsTab.comboPlayback.currentText); + droidstar.set_capture(settingsTab.comboCapture.currentText); + + droidstar.set_modemRxFreq(settingsTab.modemRXFreqEdit.text); + droidstar.set_modemTxFreq(settingsTab.modemTXFreqEdit.text); + droidstar.set_modemRxOffset(settingsTab.modemRXOffsetEdit.text); + droidstar.set_modemTxOffset(settingsTab.modemTXOffsetEdit.text); + droidstar.set_modemRxDCOffset(settingsTab.modemRXDCOffsetEdit.text); + droidstar.set_modemTxDCOffset(settingsTab.modemTXDCOffsetEdit.text); + droidstar.set_modemRxLevel(settingsTab.modemRXLevelEdit.text); + droidstar.set_modemTxLevel(settingsTab.modemRXLevelEdit.text); + droidstar.set_modemRFLevel(settingsTab.modemRFLevelEdit.text); + droidstar.set_modemTxDelay(settingsTab.modemTXDelayEdit.text); + droidstar.set_modemCWIdTxLevel(settingsTab.modemCWIdTXLevelEdit.text); + droidstar.set_modemDstarTxLevel(settingsTab.modemDStarTXLevelEdit.text); + droidstar.set_modemDMRTxLevel(settingsTab.modemDMRTXLevelEdit.text); + droidstar.set_modemYSFTxLevel(settingsTab.modemYSFTXLevelEdit.text); + droidstar.set_modemP25TxLevel(settingsTab.modemYSFTXLevelEdit.text); + droidstar.set_modemNXDNTxLevel(settingsTab.modemNXDNTXLevelEdit.text); + + droidstar.process_connect(); + } + } + + Text { + id: _data1 + x: parent.width / 3 + y: (parent.height / 15 + 3) * 4; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _data2 + x: parent.width / 3 + y: (parent.height / 15 + 3) * 5; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _data3 + x: parent.width / 3 + y: (parent.height / 15 + 3) * 6; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _data4 + x: parent.width / 3 + y: (parent.height / 15 + 3) * 7; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _data5 + x: parent.width / 3 + y: (parent.height / 15 + 3) * 8; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Text { + id: _data6 + x: parent.width / 3 + y:(parent.height / 15 + 3) * 9; + width: (parent.width * 2) / 3 + height: parent.height / 20; + text: qsTr("") + color: "white" + font.pixelSize: parent.height / 30; + } + + Button { + Timer { + id: _txtimer + repeat: true; + onTriggered: { + ++buttonTX.cnt; + btntxt.text = "TX: " + buttonTX.cnt; + if(buttonTX.cnt >= parseInt(settingsTab.txtimerEdit.text)){ + buttonTX.tx = false; + droidstar.click_tx(buttonTX.tx); + _txtimer.running = false; + _btntxt.text = "TX"; + } + } + } + + property bool tx: false + property int cnt: 0 + visible: true + enabled: false + id: _buttonTX + background: Rectangle { + color: _buttonTX.tx ? "#800000" : "steelblue" //"#80c342" + radius: 10 + Text { + id:_btntxt + anchors.centerIn: parent + font.pointSize: 18 + text: qsTr("TX") + } + } + x: 10 + y: (parent.height / 15 + 3) * 12; + //y: parent.height - ((parent.height / 5) + 5); + width: parent.width - 20 + height: parent.height - y - 10 + //text: qsTr("TX") + font.pointSize: 24 + onClicked: { + if(settingsTab.toggleTX.checked){ + tx = !tx; + droidstar.click_tx(tx); + if(tx){ + cnt = 0; + _txtimer.running = true; + _btntxt.color = "white"; + } + else{ + _txtimer.running = false; + btntxt.color = "black"; + _btntxt.text = "TX"; + } + } + } + onPressed: { + if(!settingsTab.toggleTX.checked){ + tx = true; + droidstar.press_tx(); + } + } + onReleased: { + if(!settingsTab.toggleTX.checked){ + tx = false; + droidstar.release_tx(); + } + } + onCanceled: { + if(!settingsTab.toggleTX.checked){ + tx = false; + droidstar.release_tx(); + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..48316af --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# DroidStar +This software connects to M17, Fusion (YSF/FCS, DN and VW modes are supported), DMR, P25, NXDN, D-STAR (REF/XRF/DCS) reflectors and AllStar nodes (as an IAX2 client) over UDP. It is compatible with all of the AMBE USB devices out there (ThumbDV, DVstick 30, DVSI, etc). It also supports MMDVM modems and can be used as a hotspot, or as a stand-alone transceiver via direct mode to the MMDVM device. This software is open source and uses the cross platform C++ library called Qt. It will build and run on Linux, Windows, MacOS, Android, and iOS. No USB device support for iOS though (AMBE vocoder or MMDVM). It should also build and run on any other posix platform that has Qt avilable (xxxBSD, Solaris, etc). This software is provided *as-is* and no support is available. + +This software makes use of software from a number of other open source software projects, including MMDVMHost, MMDVM_CM, mvoice, and others. Not only is software from these projects being used directly, but learning about the various network protocols and encoding/decoding of the various protocols was only possible thanks to the authors of all of these software projects. + +# DudeStar, DroidStar, and Qt +The DudeStar application used the Qt Widgets UI, while DroidStar uses the Qt Quick UI. All of the back end C/C++ source code for both projects has always been identical, but because of the different UI APIs, two repositories had to be maintained for the same project. Even though I prefer the Qt widgets UI over the Qt Quick UI for desktop applications, I have combined both projects into a single entity which is now simply called 'DroidStar'. My dudestar repo has been removed from github, but there are plenty of forks of it on github, in case anyone wishes to continue development of that version. + +# M17 support +The Codec2 vocoder library is open source and is included as a C++ implementation of the original C library taken from the mvoice project. More info on M17 can be found here: https://m17project.org/ + +# MMDVM support -- work in progress +DroidStar supports MMDVM and MMDVM_HS (hotspot) modems, with basic (possibly buggy) support for D-STAR, Fusion, and DMR. Support for M17, P25, and NXDN coming soon. When connecting to a digital mode reflector/DMR server and selecting an MMDVM device under Modems, then DroidStar acts as a hotspot/repeater. When 'MMDVM Direct' is selected as the reflector/server, then DroidStar becomes a stand-alone transceiver. + +# Software vocoder plugin API +There is a vocoder plugin API available for loading of vocoder software. Any vocoder plugin used with DroidStar should be properly licensed by the user if any copyright patents apply. Do not use any patented vocoder plugin that you are not licensed to use. I have no information regarding aquiring a software vocoder. + +# Loading a vocoder plugin +A vocoder plugin is placed in the standard Download location for the given platform: +Linux: ~/Downloads +MacOS: ~/Downloads +Windows: C:/Users//Documents +Android: /storage/emulated/0/Download (typically referred to as Internal storage -> Download + +The vocoder plugin filename must be named vocoder_plugin.. where platform and arch can be any of the following: +platform: linux, darwin, winnt, android, ios +arch: x86_64, arm, arm64 + +There are no software vocoder plugins available in this repository. + +# Optional FLite Text-to-speech build +I added Flite TTS TX capability so I didn't have to talk to myself all of the time during development and testing. To build DroidStar with Flite TTS support, uncomment the line 'DEFINES += USE_FLITE' from the top of DroidStar.pro (and run/re-run qmake). You will need the Flite library and development header files installed on your system. When built with Flite support, 3 TTS options and a Mic in option will be available at the bottom of the window. TTS1-TTS3 are 3 voice choices, and Mic in turns off TTS and uses the microphone for input. The text to be converted to speech and transmitted goes in the text box under the TTS options. + +# Usage +Linux users with USB AMBE and/or MMDVM dongles will need to make sure they have permission to use the USB serial device, and disable the archaic ModeManager service that still exists on many Linux systems. On most systems this means adding your user to the 'dialout' group, and running 'sudo systemctl disable ModemManager.service' and rebooting. This is a requirement for any serial device to be accessed. + +Host/Mod: Select the desired host and module (for D-STAR and M17) from the selections. + +Callsign: Enter your amateur radio callsign. A valid license is required to use this software. + +DMRID: A valid DMR ID is required to connect to DMR servers. +Latitude/Longitude/Location/Description: These are DMR config options, sent to the DMR server during connect. Some servers require specific values here, some do not. This is specific to the server you are connecting to, so please dont ask what these values should be. + +DMR+ IPSC2 hosts: The format for the DMR+ options string is the complete string including 'Options='. Create your options string and check 'Send DMR+ options on connect' before connecting. A description of the DMR+ options string can be found here: https://github.com/g4klx/MMDVMHost/blob/master/DMRplus_startup_options.md . + +Talkgroup: For DMR, enter the talkgroup ID number. A very active TG for testing functionality on Brandmeister is 91 (Brandmeister Worldwide). You must TX with a talkgroup entered to link to that talkgroup, just like a real radio. Any ststics you have defined in BM selfcare will work the same way they do if you were using a hotspot/radio. + +MYCALL/URCALL/RPTR1/RPTR2 are for Dstar modes REF/DCS/XRF. These fields need to be entered correctly before attempting to TX on any DSTAR reflector. All fields are populated with suggested values upon connect, but can still be modified for advanced users. RPT2 is always overwritten with the current reflector upon connected. + +# IAX Client for AllStar +Dudestar can connect to an AllStar node as an IAX(2) client. See the AllStar wiki and other AllStar, Asterisk, and IAX2 protocal related websites for the technical details of IAX2 for AllStar. This is a basic client and currently only uLaw audio codec is supported. This is the default codec on most AllStar nodes. + +Username: Defined in your nodes iax.conf file, usually iaxclient + +Password: Defined as 'secret' in your iax.conf + +Node[@Context]: ID and context of your AllStar node. The context is optional and if not specified, defaults to iax-client. + +Host: hostname or IP address of node. + +Port: UDP port of node, usually 4569. + +Add DTMF commands like \*3node, \*1node, \*70, etc in the IAX DTMF box and hit send to send the DTMF string. The asterisk (*) character is already added on the Droidstar app, so only input the numeric portion of the command (70 instead of *70, etc). Details on various commands can be found at the AllStar wiki and others. + +# Building +This software is written primarily in C++ on Linux and requires Qt5, and natually the devel packages to build. Java, QML (Javascript based), and C# code is also used where necessary. + +Qt5 and Qt5-Quick development packages are required to build this software from source. With these requirements met, run the following: +``` +qmake +make +``` +qmake may have a different name on your distribution i.e. on Fedora it's called qmake-qt5 + +Notes for building/running Debian/Raspbian: In addition to the Linux build requirements, there are some additional requirements for running this QT application in order for the audio devices to be correctly detected: +``` +sudo apt-get install libqt5multimedia5-plugins libqt5serialport5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 +``` +And if pulseaudio is not currently installed: +``` +sudo apt-get install pulseaudio +``` +My primary development platform is Fedora Linux. With a proper build environment, the build instructions apply to all other platforms/distributions, including Windows and macOS. + +All of the gradle build files are provided to create an APK file ready to be installed on an Android device. A proper Android build system including the Android NDK is required and beyond the scope of this document. + +# No builds are available +No builds for any platform are available here. This is and always will be an open source project, to be used for educational and development purposes only. + diff --git a/SHA256.cpp b/SHA256.cpp new file mode 100644 index 0000000..b3366e0 --- /dev/null +++ b/SHA256.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2005, 2006, 2008 Free Software Foundation, Inc. + * Copyright (C) 2011,2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SHA256.h" + +#include +#include +#include + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#endif + +#define BLOCKSIZE 4096 +#if BLOCKSIZE % 64 != 0 +# error "invalid BLOCKSIZE" +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* + Takes a pointer to a 256 bit block of data (eight 32 bit ints) and + intializes it to the start constants of the SHA256 algorithm. This + must be called before using hash in the call to sha256_hash +*/ +CSHA256::CSHA256() : +m_state(NULL), +m_total(NULL), +m_buflen(0U), +m_buffer(NULL) +{ + m_state = new uint32_t[8U]; + m_total = new uint32_t[2U]; + m_buffer = new uint32_t[32U]; + + init(); +} + +CSHA256::~CSHA256() +{ + delete[] m_state; + delete[] m_total; + delete[] m_buffer; +} + +void CSHA256::init() +{ + m_state[0] = 0x6a09e667UL; + m_state[1] = 0xbb67ae85UL; + m_state[2] = 0x3c6ef372UL; + m_state[3] = 0xa54ff53aUL; + m_state[4] = 0x510e527fUL; + m_state[5] = 0x9b05688cUL; + m_state[6] = 0x1f83d9abUL; + m_state[7] = 0x5be0cd19UL; + + m_total[0] = m_total[1] = 0; + m_buflen = 0; +} + +/* Copy the value from v into the memory location pointed to by *cp, + If your architecture allows unaligned access this is equivalent to + * (uint32_t *) cp = v */ +static inline void set_uint32(unsigned char* cp, uint32_t v) +{ + assert(cp != NULL); + + ::memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 32 bytes following RESBUF. The result + must be in little endian byte order. */ +unsigned char* CSHA256::read(unsigned char* resbuf) +{ + assert(resbuf != NULL); + + for (unsigned int i = 0U; i < 8U; i++) + set_uint32(resbuf + i * sizeof(m_state[0]), SWAP(m_state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +void CSHA256::conclude() +{ + /* Take yet unprocessed bytes into account. */ + unsigned int bytes = m_buflen; + unsigned int size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + m_total[0] += bytes; + if (m_total[0] < bytes) + ++m_total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. + Use set_uint32 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint32((unsigned char*)&m_buffer[size - 2], SWAP((m_total[1] << 3) | (m_total[0] >> 29))); + set_uint32((unsigned char*)&m_buffer[size - 1], SWAP(m_total[0] << 3)); + + ::memcpy(&((char*)m_buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + processBlock((unsigned char*)m_buffer, size * 4); +} + +unsigned char* CSHA256::finish(unsigned char* resbuf) +{ + assert(resbuf != NULL); + + conclude(); + + return read(resbuf); +} + +/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +unsigned char* CSHA256::buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock) +{ + assert(buffer != NULL); + assert(resblock != NULL); + + /* Initialize the computation context. */ + init(); + + /* Process whole buffer but last len % 64 bytes. */ + processBytes(buffer, len); + + /* Put result in desired memory area. */ + return finish(resblock); +} + +void CSHA256::processBytes(const unsigned char* buffer, unsigned int len) +{ + assert(buffer != NULL); + + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (m_buflen != 0U) { + unsigned int left_over = m_buflen; + unsigned int add = 128U - left_over > len ? len : 128U - left_over; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, add); + m_buflen += add; + + if (m_buflen > 64U) { + processBlock((unsigned char*)m_buffer, m_buflen & ~63U); + + m_buflen &= 63U; + + /* The regions in the following copy operation cannot overlap. */ + ::memcpy(m_buffer, &((char*)m_buffer)[(left_over + add) & ~63U], m_buflen); + } + + buffer += add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64U) { +//#if !_STRING_ARCH_unaligned +//# define alignof(type) offsetof (struct { char c; type x; }, x) +//# define UNALIGNED_P(p) (((unsigned int) p) % alignof (uint32_t) != 0) +// if (UNALIGNED_P (buffer)) { +// while (len > 64U) { +// ::memcpy(m_buffer, buffer, 64U); +// processBlock((unsigned char*)m_buffer, 64U); +// buffer += 64U; +// len -= 64U; +// } +// } else +//#endif + { + processBlock(buffer, len & ~63U); + buffer += (len & ~63U); + len &= 63U; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0U) { + unsigned int left_over = m_buflen; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, len); + left_over += len; + + if (left_over >= 64U) { + processBlock((unsigned char*)m_buffer, 64U); + left_over -= 64U; + ::memcpy(m_buffer, &m_buffer[16], left_over); + } + + m_buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha256.c --- */ + +/* SHA256 round constants */ +#define K(I) roundConstants[I] +static const uint32_t roundConstants[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +/* Round functions. */ +#define F2(A,B,C) ( ( A & B ) | ( C & ( A | B ) ) ) +#define F1(E,F,G) ( G ^ ( E & ( F ^ G ) ) ) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void CSHA256::processBlock(const unsigned char* buffer, unsigned int len) +{ + assert(buffer != NULL); + + const uint32_t* words = (uint32_t*)buffer; + unsigned int nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = m_state[0]; + uint32_t b = m_state[1]; + uint32_t c = m_state[2]; + uint32_t d = m_state[3]; + uint32_t e = m_state[4]; + uint32_t f = m_state[5]; + uint32_t g = m_state[6]; + uint32_t h = m_state[7]; + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + m_total[0] += len; + if (m_total[0] < len) + ++m_total[1]; + + #define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + #define S0(x) (rol(x,25)^rol(x,14)^(x>>3)) + #define S1(x) (rol(x,15)^rol(x,13)^(x>>10)) + #define SS0(x) (rol(x,30)^rol(x,19)^rol(x,10)) + #define SS1(x) (rol(x,26)^rol(x,21)^rol(x,7)) + + #define M(I) (tm = S1(x[(I-2)&0x0f]) + x[(I-7)&0x0f] + S0(x[(I-15)&0x0f]) + x[I&0x0f], x[I&0x0f] = tm) + + #define R(A,B,C,D,E,F,G,H,K,M) do { t0 = SS0(A) + F2(A,B,C); \ + t1 = H + SS1(E) + F1(E,F,G) + K + M; \ + D += t1; H = t0 + t1; \ + } while(0) + + while (words < endp) { + uint32_t tm; + uint32_t t0, t1; + /* FIXME: see sha1.c for a better implementation. */ + for (unsigned int t = 0U; t < 16U; t++) { + x[t] = SWAP(*words); + words++; + } + + R( a, b, c, d, e, f, g, h, K( 0), x[ 0] ); + R( h, a, b, c, d, e, f, g, K( 1), x[ 1] ); + R( g, h, a, b, c, d, e, f, K( 2), x[ 2] ); + R( f, g, h, a, b, c, d, e, K( 3), x[ 3] ); + R( e, f, g, h, a, b, c, d, K( 4), x[ 4] ); + R( d, e, f, g, h, a, b, c, K( 5), x[ 5] ); + R( c, d, e, f, g, h, a, b, K( 6), x[ 6] ); + R( b, c, d, e, f, g, h, a, K( 7), x[ 7] ); + R( a, b, c, d, e, f, g, h, K( 8), x[ 8] ); + R( h, a, b, c, d, e, f, g, K( 9), x[ 9] ); + R( g, h, a, b, c, d, e, f, K(10), x[10] ); + R( f, g, h, a, b, c, d, e, K(11), x[11] ); + R( e, f, g, h, a, b, c, d, K(12), x[12] ); + R( d, e, f, g, h, a, b, c, K(13), x[13] ); + R( c, d, e, f, g, h, a, b, K(14), x[14] ); + R( b, c, d, e, f, g, h, a, K(15), x[15] ); + R( a, b, c, d, e, f, g, h, K(16), M(16) ); + R( h, a, b, c, d, e, f, g, K(17), M(17) ); + R( g, h, a, b, c, d, e, f, K(18), M(18) ); + R( f, g, h, a, b, c, d, e, K(19), M(19) ); + R( e, f, g, h, a, b, c, d, K(20), M(20) ); + R( d, e, f, g, h, a, b, c, K(21), M(21) ); + R( c, d, e, f, g, h, a, b, K(22), M(22) ); + R( b, c, d, e, f, g, h, a, K(23), M(23) ); + R( a, b, c, d, e, f, g, h, K(24), M(24) ); + R( h, a, b, c, d, e, f, g, K(25), M(25) ); + R( g, h, a, b, c, d, e, f, K(26), M(26) ); + R( f, g, h, a, b, c, d, e, K(27), M(27) ); + R( e, f, g, h, a, b, c, d, K(28), M(28) ); + R( d, e, f, g, h, a, b, c, K(29), M(29) ); + R( c, d, e, f, g, h, a, b, K(30), M(30) ); + R( b, c, d, e, f, g, h, a, K(31), M(31) ); + R( a, b, c, d, e, f, g, h, K(32), M(32) ); + R( h, a, b, c, d, e, f, g, K(33), M(33) ); + R( g, h, a, b, c, d, e, f, K(34), M(34) ); + R( f, g, h, a, b, c, d, e, K(35), M(35) ); + R( e, f, g, h, a, b, c, d, K(36), M(36) ); + R( d, e, f, g, h, a, b, c, K(37), M(37) ); + R( c, d, e, f, g, h, a, b, K(38), M(38) ); + R( b, c, d, e, f, g, h, a, K(39), M(39) ); + R( a, b, c, d, e, f, g, h, K(40), M(40) ); + R( h, a, b, c, d, e, f, g, K(41), M(41) ); + R( g, h, a, b, c, d, e, f, K(42), M(42) ); + R( f, g, h, a, b, c, d, e, K(43), M(43) ); + R( e, f, g, h, a, b, c, d, K(44), M(44) ); + R( d, e, f, g, h, a, b, c, K(45), M(45) ); + R( c, d, e, f, g, h, a, b, K(46), M(46) ); + R( b, c, d, e, f, g, h, a, K(47), M(47) ); + R( a, b, c, d, e, f, g, h, K(48), M(48) ); + R( h, a, b, c, d, e, f, g, K(49), M(49) ); + R( g, h, a, b, c, d, e, f, K(50), M(50) ); + R( f, g, h, a, b, c, d, e, K(51), M(51) ); + R( e, f, g, h, a, b, c, d, K(52), M(52) ); + R( d, e, f, g, h, a, b, c, K(53), M(53) ); + R( c, d, e, f, g, h, a, b, K(54), M(54) ); + R( b, c, d, e, f, g, h, a, K(55), M(55) ); + R( a, b, c, d, e, f, g, h, K(56), M(56) ); + R( h, a, b, c, d, e, f, g, K(57), M(57) ); + R( g, h, a, b, c, d, e, f, K(58), M(58) ); + R( f, g, h, a, b, c, d, e, K(59), M(59) ); + R( e, f, g, h, a, b, c, d, K(60), M(60) ); + R( d, e, f, g, h, a, b, c, K(61), M(61) ); + R( c, d, e, f, g, h, a, b, K(62), M(62) ); + R( b, c, d, e, f, g, h, a, K(63), M(63) ); + + a = m_state[0] += a; + b = m_state[1] += b; + c = m_state[2] += c; + d = m_state[3] += d; + e = m_state[4] += e; + f = m_state[5] += f; + g = m_state[6] += g; + h = m_state[7] += h; + } +} diff --git a/SHA256.h b/SHA256.h new file mode 100644 index 0000000..7c48f19 --- /dev/null +++ b/SHA256.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005, 2006, 2008, 2009 Free Software Foundation, Inc. + * Copyright (C) 2011,2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SHA256_H +#define SHA256_H + +#include + +enum { + SHA256_DIGEST_SIZE = 256 / 8 +}; + +class CSHA256 { +public: + CSHA256(); + ~CSHA256(); + + /* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ + void processBlock(const unsigned char* buffer, unsigned int len); + + /* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ + void processBytes(const unsigned char* buffer, unsigned int len); + + /* Process the remaining bytes in the buffer and put result from CTX + in first 32 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ + unsigned char* finish(unsigned char* resbuf); + + /* Put result from CTX in first 32 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ + unsigned char* read(unsigned char* resbuf); + + /* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ + unsigned char* buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock); + +private: + uint32_t* m_state; + uint32_t* m_total; + unsigned int m_buflen; + uint32_t* m_buffer; + + void init(); + void conclude(); +}; + +#endif diff --git a/SettingsTab.qml b/SettingsTab.qml new file mode 100644 index 0000000..5d167d4 --- /dev/null +++ b/SettingsTab.qml @@ -0,0 +1,1030 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 +import QtQuick.Controls 2.3 + +Item { + id: settingsTab + property alias callsignEdit: csedit + property alias dmridEdit: dmridedit + property alias comboEssid: comboessid + property alias bmpwEdit: bmpwedit + property alias tgifpwEdit: tgifpwedit + property alias latEdit: latedit + property alias lonEdit: lonedit + property alias locEdit: locedit + property alias descEdit: descedit + property alias urlEdit: urledit + property alias swidEdit: swidedit + property alias pkgidEdit: pkgidedit + property alias dmroptsEdit: dmroptsedit + property alias iaxuserEdit: iaxuseredit + property alias iaxpassEdit: iaxpassedit + property alias iaxnodeEdit: iaxnodeedit + property alias iaxhostEdit: iaxhostedit + property alias iaxportEdit: iaxportedit + property alias m173200: m17_3200 + property alias m171600: m17_1600 + property alias sliderMicGain: slidermicGain + property alias mycallEdit: mycalledit + property alias urcallEdit: urcalledit + property alias rptr1Edit: rptr1edit + property alias rptr2Edit: rptr2edit + property alias txtimerEdit: txtimeredit + property alias toggleTX: toggletx + property alias xrf2ref: xrf2Ref + property alias ipv6: ipV6 + property alias comboVocoder: _comboVocoder + property alias comboModem: _comboModem + property alias comboPlayback: _comboPlayback + property alias comboCapture: _comboCapture + property alias modemRXFreqEdit: _modemRXFreqEdit + property alias modemTXFreqEdit: _modemTXFreqEdit + property alias modemRXOffsetEdit: _modemRXOffsetEdit + property alias modemTXOffsetEdit: _modemTXOffsetEdit + property alias modemRXDCOffsetEdit: _modemRXDCOffsetEdit + property alias modemTXDCOffsetEdit: _modemTXDCOffsetEdit + property alias modemRXLevelEdit: _modemRXLevelEdit + property alias modemTXLevelEdit: _modemTXLevelEdit + property alias modemRFLevelEdit: _modemRFLevelEdit + property alias modemTXDelayEdit: _modemTXDelayEdit + property alias modemCWIdTXLevelEdit: _modemCWIdTXLevelEdit + property alias modemDStarTXLevelEdit: _modemDStarTXLevelEdit + property alias modemDMRTXLevelEdit: _modemDMRTXLevelEdit + property alias modemYSFTXLevelEdit: _modemYSFTXLevelEdit + property alias modemP25TXLevelEdit: _modemP25TXLevelEdit + property alias modemNXDNTXLevelEdit: _modemNXDNTXLevelEdit + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: _modemNXDNTXLevelLabel.y + + _modemNXDNTXLevelLabel.height + 10 + flickableDirection: Flickable.VerticalFlick + clip: true + + Text { + id: csLabel + x: 10 + y: 0 + width: 80 + height: 25 + text: qsTr("Callsign") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: csedit + x: 100 + y: 0 + width: 125 + height: 25 + text: qsTr("") + font.capitalization: Font.AllUppercase + selectByMouse: true + } + Text { + id: dmridLabel + x: 10 + y: 30 + width: 80 + height: 25 + text: qsTr("DMRID") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: dmridedit + x: 100 + y: 30 + width: 125 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: essidLabel + x: 10 + y: 60 + width: 80 + height: 25 + text: qsTr("ESSID") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ComboBox { + id: comboessid + x: 100 + y: 60 + width: 60 + height: 30 + function build_model(){ + console.log("build_model() called"); + var ids = ["None"]; + for(var i = 0; i < 100; ++i){ + ids[i+1] = i.toString().padStart(2, "0"); + } + comboessid.model = ids; + comboessid.currentIndex = comboessid.find(droidstar.get_essid()); + } + + Component.onCompleted: build_model(); + onCurrentTextChanged: { + //console.log("set essid called"); + //droidstar.set_essid(comboessid.currentText); + } + } + Text { + id: bmpwLabel + x: 10 + y: 95 + width: 80 + height: 25 + text: qsTr("BM Pass") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: bmpwedit + x: 100 + y: 95 + width: parent.width - 110 + height: 25 + selectByMouse: true + echoMode: TextInput.Password + } + Text { + id: tgifpwLabel + x: 10 + y: 130 + width: 80 + height: 25 + text: qsTr("TGIF Pass") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: tgifpwedit + x: 100 + y: 130 + width: parent.width - 110 + height: 25 + selectByMouse: true + echoMode: TextInput.Password + } + Text { + id: latLabel + x: 10 + y: 160 + width: 80 + height: 25 + text: qsTr("Latitude") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: latedit + x: 100 + y: 160 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: lonLabel + x: 10 + y: 190 + width: 80 + height: 25 + text: qsTr("Longitude") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: lonedit + x: 100 + y: 190 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: locLabel + x: 10 + y: 220 + width: 80 + height: 25 + text: qsTr("Location") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: locedit + x: 100 + y: 220 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: descLabel + x: 10 + y: 250 + width: 80 + height: 25 + text: qsTr("Description") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: descedit + x: 100 + y: 250 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: urlLabel + x: 10 + y: 280 + width: 80 + height: 25 + text: qsTr("URL") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: urledit + x: 100 + y: 280 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: swidLabel + x: 10 + y: 310 + width: 80 + height: 25 + text: qsTr("SoftwareID") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: swidedit + x: 100 + y: 310 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: pkgidLabel + x: 10 + y: 340 + width: 80 + height: 25 + text: qsTr("PackageID") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: pkgidedit + x: 100 + y: 340 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: dmroptslabel + x: 10 + y: 370 + width: 80 + height: 25 + text: qsTr("DMR+ Opts") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: dmroptsedit + x: 100 + y: 370 + width: parent.width - 110 + height: 25 + selectByMouse: true + } + Text { + id: iaxuserlabel + x: 10 + y: 400 + width: 80 + height: 25 + text: qsTr("IAX User") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: iaxuseredit + x: 100 + y: 400 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: iaxpasslabel + x: 10 + y: 430 + width: 80 + height: 25 + text: qsTr("IAX secret") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: iaxpassedit + x: 100 + y: 430 + width: 125 + height: 25 + selectByMouse: true + echoMode: TextInput.Password + } + Text { + id: iaxnodelabel + x: 10 + y: 460 + width: 80 + height: 25 + text: qsTr("IAX Node") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: iaxnodeedit + x: 100 + y: 460 + width: 125 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: iaxhostlabel + x: 10 + y: 490 + width: 80 + height: 25 + text: qsTr("IAX Host") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: iaxhostedit + x: 100 + y: 490 + width: 125 + height: 25 + selectByMouse: true + } + Text { + id: iaxportlabel + x: 10 + y: 520 + width: 80 + height: 25 + text: qsTr("IAX Port") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: iaxportedit + x: 100 + y: 520 + width: 125 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: m17rateLabel + x: 10 + y: 550 + width: 100 + height: 25 + text: qsTr("M17/YSF rate") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ButtonGroup { + id: m17rateGroup + onClicked: { + button.text == "Voice Full" ? droidstar.m17_rate_changed(true) : droidstar.m17_rate_changed(false) + } + } + CheckBox { + id: m17_3200 + x: 120 + y: 550 + //width: 100 + height: 25 + spacing: 1 + text: qsTr("Voice Full") + checked: true + ButtonGroup.group: m17rateGroup + } + CheckBox { + id: m17_1600 + x: 220 + y: 550 + //width: 100 + height: 25 + spacing: 1 + text: qsTr("Voice/Data") + ButtonGroup.group: m17rateGroup + } + Text { + id: micgain_label + x: 10 + y: 590 + width: 80 + height: 25 + text: qsTr("Mic gain") + color: "white" + verticalAlignment: Text.AlignVCenter + } + Slider { + visible: true + id: slidermicGain + x: 72 + y: 590 + width: parent.parent.width - 80 + height: parent.parent.height / 15 + value: 0.5 + onValueChanged: { + droidstar.set_input_volume(value); + } + } + Button { + id: updatehostsButton + x: 10 + y: 630 + width: 150 + height: 30 + text: qsTr("Update hosts") + onClicked: { + droidstar.update_host_files() + updateDialog.open() + } + } + Button { + id: updatedmridsButton + x: 170 + y: 630 + width: 150 + height: 30 + text: qsTr("Update ID files") + onClicked: { + droidstar.update_dmr_ids() + updateDialog.open() + } + } + Button { + id: vocoderButton + x: 10 + y: 670 + width: 150 + height: 30 + text: qsTr("Vocoder Plugin") + onClicked: { + //vocoderDialog.open() + } + } + Text { + id: mycallLabel + x: 10 + y: 700 + width: 80 + height: 25 + text: qsTr("MYCALL") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: mycalledit + x: 100 + y: 700 + width: 125 + height: 25 + selectByMouse: true + font.capitalization: Font.AllUppercase + onEditingFinished: { + droidstar.set_mycall(mycalledit.text.toUpperCase()) + } + } + Text { + id: urcallLabel + x: 10 + y: 730 + width: 80 + height: 25 + text: qsTr("URCALL") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: urcalledit + x: 100 + y: 730 + width: 125 + height: 25 + selectByMouse: true + font.capitalization: Font.AllUppercase + onEditingFinished: { + droidstar.set_urcall(urcalledit.text.toUpperCase()) + } + } + Text { + id: rptr1Label + x: 10 + y: 760 + width: 80 + height: 25 + text: qsTr("RPTR1") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: rptr1edit + x: 100 + y: 760 + width: 125 + height: 25 + selectByMouse: true + font.capitalization: Font.AllUppercase + onEditingFinished: { + droidstar.set_rptr1(rptr1edit.text.toUpperCase()) + } + } + Text { + id: rptr2Label + x: 10 + y: 790 + width: 80 + height: 25 + text: qsTr("RPTR2") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: rptr2edit + x: 100 + y: 790 + width: 125 + height: 25 + selectByMouse: true + font.capitalization: Font.AllUppercase + onEditingFinished: { + droidstar.set_rptr2(rptr2edit.text.toUpperCase()) + } + } + Text { + id: txtimerLabel + x: 10 + y: 820 + width: 80 + height: 25 + text: qsTr("TX Timeout") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: txtimeredit + x: 100 + y: 820 + width: 125 + height: 25 + selectByMouse: true + } + CheckBox { + id: toggletx + x: 10 + y: 850 + //width: 100 + height: 25 + spacing: 1 + text: qsTr("Enable TX toggle mode") + onClicked:{ + droidstar.set_toggletx(toggleTX.checked); + } + } + CheckBox { + id: xrf2Ref + x: 10 + y: 880 + //width: 100 + height: 25 + spacing: 1 + text: qsTr("Use REF for XRF") + } + CheckBox { + id: ipV6 + x: 10 + y: 910 + //width: 100 + height: 25 + spacing: 1 + text: qsTr("Use IPv6 when available") + } + Text { + id: vocoderLabel + x: 10 + y: 940 + width: 80 + height: 25 + text: qsTr("Vocoder") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ComboBox { + id: _comboVocoder + x: 100 + y: 940 + width: parent.width - 110 + height: 30 + } + Text { + id: modemLabel + x: 10 + y: 970 + width: 80 + height: 25 + text: qsTr("Modem") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ComboBox { + id: _comboModem + x: 100 + y: 970 + width: parent.width - 110 + height: 30 + } + Text { + id: playbackLabel + x: 10 + y: 1000 + width: 80 + height: 25 + text: qsTr("Playback") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ComboBox { + id: _comboPlayback + x: 100 + y: 1000 + width: parent.width - 110 + height: 30 + } + Text { + id: captureLabel + x: 10 + y: 1030 + width: 80 + height: 25 + text: qsTr("Capture") + color: "white" + verticalAlignment: Text.AlignVCenter + } + ComboBox { + id: _comboCapture + x: 100 + y: 1030 + width: parent.width - 110 + height: 30 + } + Text { + id: _modemRXFreqLabel + x: 10 + y: 1070 + width: 80 + height: 25 + text: qsTr("RX Freq") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemRXFreqEdit + x: 100 + y: 1070 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemTXFreqLabel + x: 10 + y: 1100 + width: 80 + height: 25 + text: qsTr("TX Freq") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemTXFreqEdit + x: 100 + y: 1100 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemRXOffsetLabel + x: 10 + y: 1130 + width: 80 + height: 25 + text: qsTr("RX Offset") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemRXOffsetEdit + x: 100 + y: 1130 + width: parent.width - 110 + height: 25 + selectByMouse: true + //inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemTXOffsetLabel + x: 10 + y: 1160 + width: 80 + height: 25 + text: qsTr("TX Offset") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemTXOffsetEdit + x: 100 + y: 1160 + width: parent.width - 110 + height: 25 + selectByMouse: true + //inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemRXLevelLabel + x: 10 + y: 1190 + width: 80 + height: 25 + text: qsTr("RX Level") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemRXLevelEdit + x: 100 + y: 1190 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemTXLevelLabel + x: 10 + y: 1220 + width: 80 + height: 25 + text: qsTr("TX Level") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemTXLevelEdit + x: 100 + y: 1220 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemRXDCOffsetLabel + x: 10 + y: 1250 + width: 80 + height: 25 + text: qsTr("RX DC Offset") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemRXDCOffsetEdit + x: 100 + y: 1250 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemTXDCOffsetLabel + x: 10 + y: 1280 + width: 80 + height: 25 + text: qsTr("TX DC Offset") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemTXDCOffsetEdit + x: 100 + y: 1280 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemRFLevelLabel + x: 10 + y: 1310 + width: 80 + height: 25 + text: qsTr("RF Level") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemRFLevelEdit + x: 100 + y: 1310 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemTXDelayLabel + x: 10 + y: 1340 + width: 80 + height: 25 + text: qsTr("TX Delay") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemTXDelayEdit + x: 100 + y: 1340 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemCWIdTXLevelLabel + x: 10 + y: 1370 + width: 80 + height: 25 + text: qsTr("CWIdTXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemCWIdTXLevelEdit + x: 100 + y: 1370 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemDStarTXLevelLabel + x: 10 + y: 1400 + width: 80 + height: 25 + text: qsTr("DStarTXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemDStarTXLevelEdit + x: 100 + y: 1400 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemDMRTXLevelLabel + x: 10 + y: 1430 + width: 80 + height: 25 + text: qsTr("DMRTXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemDMRTXLevelEdit + x: 100 + y: 1430 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemYSFTXLevelLabel + x: 10 + y: 1460 + width: 80 + height: 25 + text: qsTr("YSFTXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemYSFTXLevelEdit + x: 100 + y: 1460 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemP25TXLevelLabel + x: 10 + y: 1490 + width: 80 + height: 25 + text: qsTr("P25TXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemP25TXLevelEdit + x: 100 + y: 1490 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + Text { + id: _modemNXDNTXLevelLabel + x: 10 + y: 1520 + width: 80 + height: 25 + text: qsTr("NXDNTXLevel") + color: "white" + verticalAlignment: Text.AlignVCenter + } + TextField { + id: _modemNXDNTXLevelEdit + x: 100 + y: 1520 + width: parent.width - 110 + height: 25 + selectByMouse: true + inputMethodHints: "ImhPreferNumbers" + } + } +} diff --git a/YSFConvolution.cpp b/YSFConvolution.cpp new file mode 100644 index 0000000..8e40854 --- /dev/null +++ b/YSFConvolution.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2009-2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "YSFConvolution.h" + +#include +#include +#include + +const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +const uint8_t BRANCH_TABLE1[] = {0U, 0U, 0U, 0U, 1U, 1U, 1U, 1U}; +const uint8_t BRANCH_TABLE2[] = {0U, 1U, 1U, 0U, 0U, 1U, 1U, 0U}; + +const unsigned int NUM_OF_STATES_D2 = 8U; +const unsigned int NUM_OF_STATES = 16U; +const uint32_t M = 2U; +const unsigned int K = 5U; + +CYSFConvolution::CYSFConvolution() : +m_metrics1(nullptr), +m_metrics2(nullptr), +m_oldMetrics(nullptr), +m_newMetrics(nullptr), +m_decisions(nullptr), +m_dp(nullptr) +{ + m_metrics1 = new uint16_t[16U]; + m_metrics2 = new uint16_t[16U]; + m_decisions = new uint64_t[180U]; +} + +CYSFConvolution::~CYSFConvolution() +{ + delete[] m_metrics1; + delete[] m_metrics2; + delete[] m_decisions; +} + +void CYSFConvolution::start() +{ + ::memset(m_metrics1, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); + ::memset(m_metrics2, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); + + m_oldMetrics = m_metrics1; + m_newMetrics = m_metrics2; + m_dp = m_decisions; +} + +void CYSFConvolution::decode(uint8_t s0, uint8_t s1) +{ + *m_dp = 0U; + + for (uint8_t i = 0U; i < NUM_OF_STATES_D2; i++) { + uint8_t j = i * 2U; + + uint16_t metric = (BRANCH_TABLE1[i] ^ s0) + (BRANCH_TABLE2[i] ^ s1); + + uint16_t m0 = m_oldMetrics[i] + metric; + uint16_t m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + (M - metric); + uint8_t decision0 = (m0 >= m1) ? 1U : 0U; + m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0; + + m0 = m_oldMetrics[i] + (M - metric); + m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + metric; + uint8_t decision1 = (m0 >= m1) ? 1U : 0U; + m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0; + + *m_dp |= (uint64_t(decision1) << (j + 1U)) | (uint64_t(decision0) << (j + 0U)); + } + + ++m_dp; + + assert((m_dp - m_decisions) <= 180); + + uint16_t* tmp = m_oldMetrics; + m_oldMetrics = m_newMetrics; + m_newMetrics = tmp; +} + +void CYSFConvolution::chainback(unsigned char* out, unsigned int nBits) +{ + assert(out != NULL); + + uint32_t state = 0U; + + while (nBits-- > 0) { + --m_dp; + + uint32_t i = state >> (9 - K); + uint8_t bit = uint8_t(*m_dp >> i) & 1; + state = (bit << 7) | (state >> 1); + + WRITE_BIT1(out, nBits, bit != 0U); + } +} + +void CYSFConvolution::encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const +{ + assert(in != NULL); + assert(out != NULL); + assert(nBits > 0U); + + uint8_t d1 = 0U, d2 = 0U, d3 = 0U, d4 = 0U; + uint32_t k = 0U; + for (unsigned int i = 0U; i < nBits; i++) { + uint8_t d = READ_BIT1(in, i) ? 1U : 0U; + + uint8_t g1 = (d + d3 + d4) & 1; + uint8_t g2 = (d + d1 + d2 + d4) & 1; + + d4 = d3; + d3 = d2; + d2 = d1; + d1 = d; + + WRITE_BIT1(out, k, g1 != 0U); + k++; + + WRITE_BIT1(out, k, g2 != 0U); + k++; + } +} diff --git a/YSFConvolution.h b/YSFConvolution.h new file mode 100644 index 0000000..457103c --- /dev/null +++ b/YSFConvolution.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(YSFConvolution_H) +#define YSFConvolution_H + +#include + +class CYSFConvolution { +public: + CYSFConvolution(); + ~CYSFConvolution(); + + void start(); + void decode(uint8_t s0, uint8_t s1); + void chainback(unsigned char* out, unsigned int nBits); + + void encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const; + +private: + uint16_t* m_metrics1; + uint16_t* m_metrics2; + uint16_t* m_oldMetrics; + uint16_t* m_newMetrics; + uint64_t* m_decisions; + uint64_t* m_dp; +}; + +#endif + diff --git a/YSFFICH.cpp b/YSFFICH.cpp new file mode 100644 index 0000000..475b8d5 --- /dev/null +++ b/YSFFICH.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2009-2016 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "YSFConvolution.h" +#include "Golay24128.h" +#include "YSFFICH.h" +#include "CRCenc.h" + +#include +#include +#include + +const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +const unsigned int INTERLEAVE_TABLE[] = { + 0U, 40U, 80U, 120U, 160U, + 2U, 42U, 82U, 122U, 162U, + 4U, 44U, 84U, 124U, 164U, + 6U, 46U, 86U, 126U, 166U, + 8U, 48U, 88U, 128U, 168U, + 10U, 50U, 90U, 130U, 170U, + 12U, 52U, 92U, 132U, 172U, + 14U, 54U, 94U, 134U, 174U, + 16U, 56U, 96U, 136U, 176U, + 18U, 58U, 98U, 138U, 178U, + 20U, 60U, 100U, 140U, 180U, + 22U, 62U, 102U, 142U, 182U, + 24U, 64U, 104U, 144U, 184U, + 26U, 66U, 106U, 146U, 186U, + 28U, 68U, 108U, 148U, 188U, + 30U, 70U, 110U, 150U, 190U, + 32U, 72U, 112U, 152U, 192U, + 34U, 74U, 114U, 154U, 194U, + 36U, 76U, 116U, 156U, 196U, + 38U, 78U, 118U, 158U, 198U}; + +const unsigned int YSF_SYNC_LENGTH_BYTES = 5U; + +CYSFFICH::CYSFFICH() +//m_fich(NULL) +{ + m_fich[0] = 0; + m_fich[1] = 0; + m_fich[2] = 0; + m_fich[3] = 0; + m_fich[4] = 0; + m_fich[5] = 0; +} + +CYSFFICH::~CYSFFICH() +{ + //delete[] m_fich; +} + +bool CYSFFICH::decode(const unsigned char* bytes) +{ + assert(bytes != NULL); + + // Skip the sync bytes + bytes += YSF_SYNC_LENGTH_BYTES; + + CYSFConvolution viterbi; + viterbi.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 100U; i++) { + unsigned int n = INTERLEAVE_TABLE[i]; + uint8_t s0 = READ_BIT1(bytes, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(bytes, n) ? 1U : 0U; + + viterbi.decode(s0, s1); + } + + unsigned char output[13U]; + viterbi.chainback(output, 96U); + + unsigned int b0 = CGolay24128::decode24128(output + 0U); + unsigned int b1 = CGolay24128::decode24128(output + 3U); + unsigned int b2 = CGolay24128::decode24128(output + 6U); + unsigned int b3 = CGolay24128::decode24128(output + 9U); + + m_fich[0U] = (b0 >> 4) & 0xFFU; + m_fich[1U] = ((b0 << 4) & 0xF0U) | ((b1 >> 8) & 0x0FU); + m_fich[2U] = (b1 >> 0) & 0xFFU; + m_fich[3U] = (b2 >> 4) & 0xFFU; + m_fich[4U] = ((b2 << 4) & 0xF0U) | ((b3 >> 8) & 0x0FU); + m_fich[5U] = (b3 >> 0) & 0xFFU; + + return CCRC::checkCCITT162(m_fich, 6U); +} + +void CYSFFICH::encode(unsigned char* bytes) +{ + assert(bytes != NULL); + + // Skip the sync bytes + bytes += YSF_SYNC_LENGTH_BYTES; + + CCRC::addCCITT162(m_fich, 6U); + + unsigned int b0 = ((m_fich[0U] << 4) & 0xFF0U) | ((m_fich[1U] >> 4) & 0x00FU); + unsigned int b1 = ((m_fich[1U] << 8) & 0xF00U) | ((m_fich[2U] >> 0) & 0x0FFU); + unsigned int b2 = ((m_fich[3U] << 4) & 0xFF0U) | ((m_fich[4U] >> 4) & 0x00FU); + unsigned int b3 = ((m_fich[4U] << 8) & 0xF00U) | ((m_fich[5U] >> 0) & 0x0FFU); + + unsigned int c0 = CGolay24128::encode24128(b0); + unsigned int c1 = CGolay24128::encode24128(b1); + unsigned int c2 = CGolay24128::encode24128(b2); + unsigned int c3 = CGolay24128::encode24128(b3); + + unsigned char conv[13U]; + conv[0U] = (c0 >> 16) & 0xFFU; + conv[1U] = (c0 >> 8) & 0xFFU; + conv[2U] = (c0 >> 0) & 0xFFU; + conv[3U] = (c1 >> 16) & 0xFFU; + conv[4U] = (c1 >> 8) & 0xFFU; + conv[5U] = (c1 >> 0) & 0xFFU; + conv[6U] = (c2 >> 16) & 0xFFU; + conv[7U] = (c2 >> 8) & 0xFFU; + conv[8U] = (c2 >> 0) & 0xFFU; + conv[9U] = (c3 >> 16) & 0xFFU; + conv[10U] = (c3 >> 8) & 0xFFU; + conv[11U] = (c3 >> 0) & 0xFFU; + conv[12U] = 0x00U; + + CYSFConvolution convolution; + unsigned char convolved[25U]; + convolution.encode(conv, convolved, 100U); + + unsigned int j = 0U; + for (unsigned int i = 0U; i < 100U; i++) { + unsigned int n = INTERLEAVE_TABLE[i]; + + bool s0 = READ_BIT1(convolved, j) != 0U; + j++; + + bool s1 = READ_BIT1(convolved, j) != 0U; + j++; + + WRITE_BIT1(bytes, n, s0); + + n++; + WRITE_BIT1(bytes, n, s1); + } +} + +unsigned char CYSFFICH::getFI() const +{ + return (m_fich[0U] >> 6) & 0x03U; +} + +unsigned char CYSFFICH::getCS() const +{ + return (m_fich[0U] >> 4) & 0x03U; +} + +unsigned char CYSFFICH::getCM() const +{ + return (m_fich[0U] >> 2) & 0x03U; +} + +unsigned char CYSFFICH::getBN() const +{ + return m_fich[0U] & 0x03U; +} + +unsigned char CYSFFICH::getBT() const +{ + return (m_fich[1U] >> 6) & 0x03U; +} + +unsigned char CYSFFICH::getFN() const +{ + return (m_fich[1U] >> 3) & 0x07U; +} + +unsigned char CYSFFICH::getFT() const +{ + return m_fich[1U] & 0x07U; +} + +unsigned char CYSFFICH::getDT() const +{ + return m_fich[2U] & 0x03U; +} + +unsigned char CYSFFICH::getMR() const +{ + return (m_fich[2U] >> 3) & 0x03U; +} + +bool CYSFFICH::getVoIP() const +{ + return (m_fich[2U] & 0x04U) == 0x04U; +} + +bool CYSFFICH::getDev() const +{ + return (m_fich[2U] & 0x40U) == 0x40U; +} + +bool CYSFFICH::getSQL() const +{ + return (m_fich[3U] & 0x80U) == 0x80U; +} + +unsigned char CYSFFICH::getSQ() const +{ + return m_fich[3U] & 0x7FU; +} + +void CYSFFICH::setFI(unsigned char fi) +{ + m_fich[0U] &= 0x3FU; + m_fich[0U] |= (fi << 6) & 0xC0U; +} + +void CYSFFICH::setCS(unsigned char cs) +{ + m_fich[0U] &= 0xCFU; + m_fich[0U] |= (cs << 4) & 0x30U; +} + +void CYSFFICH::setCM(unsigned char cm) +{ + m_fich[0U] &= 0xF3U; + m_fich[0U] |= (cm << 2) & 0x0CU; +} + +void CYSFFICH::setFN(unsigned char fn) +{ + m_fich[1U] &= 0xC7U; + m_fich[1U] |= (fn << 3) & 0x38U; +} + +void CYSFFICH::setFT(unsigned char ft) +{ + m_fich[1U] &= 0xF8U; + m_fich[1U] |= ft & 0x07U; +} + +void CYSFFICH::setMR(unsigned char mr) +{ + m_fich[2U] &= 0xC7U; + m_fich[2U] |= (mr << 3) & 0x38U; +} + +void CYSFFICH::setVoIP(bool on) +{ + if (on) + m_fich[2U] |= 0x04U; + else + m_fich[2U] &= 0xFBU; +} + +void CYSFFICH::setDev(bool on) +{ + if (on) + m_fich[2U] |= 0x40U; + else + m_fich[2U] &= 0xBFU; +} + +void CYSFFICH::setDT(unsigned char dt) +{ + m_fich[2U] &= 0xFCU; + m_fich[2U] |= dt & 0x03U; +} + +void CYSFFICH::setSQL(bool on) +{ + if (on) + m_fich[3U] |= 0x80U; + else + m_fich[3U] &= 0x7FU; +} + +void CYSFFICH::setSQ(unsigned char sq) +{ + m_fich[3U] &= 0x80U; + m_fich[3U] |= sq & 0x7FU; +} + +void CYSFFICH::setBN(unsigned char bn) +{ + m_fich[0U] &= 0xFCU; + m_fich[0U] |= bn & 0x03U; +} + +void CYSFFICH::setBT(unsigned char bt) +{ + m_fich[1U] &= 0x3FU; + m_fich[1U] |= (bt << 6) & 0xC0U; +} + +void CYSFFICH::load(const unsigned char* fich) +{ + assert(fich != NULL); + + ::memcpy(m_fich, fich, 4U); +} + diff --git a/YSFFICH.h b/YSFFICH.h new file mode 100644 index 0000000..96c6e5f --- /dev/null +++ b/YSFFICH.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(YSFFICH_H) +#define YSFFICH_H + +class CYSFFICH { +public: + CYSFFICH(); + ~CYSFFICH(); + + bool decode(const unsigned char* bytes); + + void encode(unsigned char* bytes); + + unsigned char getFI() const; + unsigned char getCS() const; + unsigned char getCM() const; + unsigned char getBN() const; + unsigned char getBT() const; + unsigned char getFN() const; + unsigned char getFT() const; + unsigned char getDT() const; + unsigned char getMR() const; + bool getVoIP() const; + bool getDev() const; + bool getSQL() const; + unsigned char getSQ() const; + + void setFI(unsigned char fi); + void setCS(unsigned char cs); + void setCM(unsigned char cm); + void setFN(unsigned char fn); + void setFT(unsigned char ft); + void setBN(unsigned char bn); + void setBT(unsigned char bt); + void setDT(unsigned char dt); + void setMR(unsigned char mr); + void setVoIP(bool set); + void setDev(bool set); + void setSQL(bool set); + void setSQ(unsigned char sq); + + void load(const unsigned char* fich); + +private: + unsigned char m_fich[6U]; + //m_fich = new unsigned char[6U]; +}; + +#endif diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 0000000..90948ef --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..3087d08 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,62 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +repositories { + google() + jcenter() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion '28.0.3' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['resources'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } + + // Do not compress Qt binary resources file + aaptOptions { + noCompress 'rcc' + } +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..646c51b --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4b7e1f3 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..8667b6a Binary files /dev/null and b/android/res/drawable-hdpi/icon.png differ diff --git a/android/res/values/libs.xml b/android/res/values/libs.xml new file mode 100644 index 0000000..6b1a4a2 --- /dev/null +++ b/android/res/values/libs.xml @@ -0,0 +1,22 @@ + + + + https://download.qt.io/ministro/android/qt5/qt-5.14 + + + + + + + + + + + + + + + + + diff --git a/android/res/xml/device_filter.xml b/android/res/xml/device_filter.xml new file mode 100644 index 0000000..9c97cab --- /dev/null +++ b/android/res/xml/device_filter.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/src/main/java/USBSerialWrapper.java b/android/src/main/java/USBSerialWrapper.java new file mode 100644 index 0000000..0f6eaeb --- /dev/null +++ b/android/src/main/java/USBSerialWrapper.java @@ -0,0 +1,191 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +package DroidStar; +import java.util.Arrays; +import java.util.List; +import android.content.Context; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbDeviceConnection; +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialPort; +import com.hoho.android.usbserial.driver.UsbSerialProber; +import com.hoho.android.usbserial.util.SerialInputOutputManagerTest; + +public class USBSerialWrapper implements SerialInputOutputManagerTest.Listener { + public static int counter = 0; + public int id; + public Context m_context; + public int m_baudrate = 115200; + public int m_databits = 8; + public int m_stopbits = 1; + public int m_parity = 0; + public int m_flowcontrol = 0; + public int m_rts = 0; + + UsbSerialPort m_port; + SerialInputOutputManagerTest usbIoManager; + + public USBSerialWrapper() { + this.id = counter; + System.out.println("Created USBSerialWrapper object with id: " + this.id); + counter++; + } + + public void set_baud_rate(int br) + { + m_baudrate = br; + System.out.println("Java Baudrate: " + br); + } + + public void set_data_bits(int db) + { + m_databits = db; + System.out.println("Java DataBits: " + db); + } + + public void set_stop_bits(int sb) + { + m_stopbits = sb; + System.out.println("Java StopBits: " + sb); + } + + public void set_parity(int p) + { + m_parity = p; + System.out.println("Java Parity: " + p); + } + + public void set_flow_control(int fc) + { + m_flowcontrol = fc; + System.out.println("Java FlowControl: " + fc); + } + + public void set_rts(int rts) + { + m_rts = rts; + System.out.println("Java RTS: " + rts); + } + + public String[] discover_devices(Context c) + { + System.out.println("Java: discover_devices()"); + UsbManager manager = (UsbManager) c.getSystemService(Context.USB_SERVICE); + List availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager); + String[] devices = new String[availableDrivers.size()]; + + if (availableDrivers.isEmpty()) { + System.out.println("No drivers found"); + } + else { + System.out.println(availableDrivers.size() + " found"); + + for(int i = 0; i < availableDrivers.size(); ++i){ + devices[i] = availableDrivers.get(i).getDevice().getProductName(); + System.out.println("USB device getDeviceName() == " + availableDrivers.get(i).getDevice().getDeviceName()); + System.out.println("USB device getProductName() == " + availableDrivers.get(i).getDevice().getProductName()); + System.out.println("USB device getProductId() == " + availableDrivers.get(i).getDevice().getProductId()); + System.out.println("USB device getVendorId() == " + availableDrivers.get(i).getDevice().getVendorId()); + } + } + return devices; + } + + public String setup_serial(Context c) + { + m_context = c; + UsbManager manager = (UsbManager) m_context.getSystemService(Context.USB_SERVICE); + List availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager); + + if (availableDrivers.isEmpty()) { + System.out.println("No drivers found"); + return "returning..."; + } + else { + System.out.println(availableDrivers.size() + " found"); + for(int i = 0; i < availableDrivers.size(); ++i){ + System.out.println("USB device getDeviceName() == " + availableDrivers.get(i).getDevice().getDeviceName()); + System.out.println("USB device getProductName() == " + availableDrivers.get(i).getDevice().getProductName()); + System.out.println("USB device getProductId() == " + availableDrivers.get(i).getDevice().getProductId()); + System.out.println("USB device getVendorId() == " + availableDrivers.get(i).getDevice().getVendorId()); + } + } + + UsbSerialDriver driver = availableDrivers.get(0); + UsbDeviceConnection connection = manager.openDevice(driver.getDevice()); + + if (connection == null) { + // add UsbManager.requestPermission(driver.getDevice(), ..) handling here + return "No connection, need permission?"; + } + + m_port = driver.getPorts().get(0); // Most devices have just one port (port 0) + + try { + m_port.open(connection); + m_port.setParameters(m_baudrate, m_databits, m_stopbits, m_parity); + m_port.setRTS(true); + + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + + System.out.println("Created UsbManager"); + + usbIoManager = new SerialInputOutputManagerTest(m_port, this); + usbIoManager.start(); + + return "Yipee!"; + } + + public void write(byte[] data) + { + try { + m_port.write(data, 0); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + } + + public byte[] read() + { + byte[] buffer = new byte[1024]; + int r = 0; + + try { + r = m_port.read(buffer, 0); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + + byte[] returnbytes = Arrays.copyOf(buffer, r); + return returnbytes; + } + + @Override + public void onNewData(byte[] data) { + data_received(data); + } + + @Override + public void onRunError(Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + + private static native void data_received(byte[] data); +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/android/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java new file mode 100644 index 0000000..87eb73c --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -0,0 +1,327 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * USB CDC/ACM serial driver implementation. + * + * @author mike wakerly (opensource@hoho.com) + * @see Universal + * Serial Bus Class Definitions for Communication Devices, v1.1 + */ +public class CdcAcmSerialDriver implements UsbSerialDriver { + + private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public CdcAcmSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + for( int i = 0; i < device.getInterfaceCount(); i++) { + if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) + controlInterfaceCount++; + if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) + dataInterfaceCount++; + } + for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { + mPorts.add(new CdcAcmSerialPort(mDevice, port)); + } + if(mPorts.size() == 0) { + mPorts.add(new CdcAcmSerialPort(mDevice, -1)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class CdcAcmSerialPort extends CommonUsbSerialPort { + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + + private int mControlIndex; + + private boolean mRts = false; + private boolean mDtr = false; + + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 + private static final int GET_LINE_CODING = 0x21; + private static final int SET_CONTROL_LINE_STATE = 0x22; + private static final int SEND_BREAK = 0x23; + + public CdcAcmSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return CdcAcmSerialDriver.this; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + if (mPortNumber == -1) { + Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); + openSingleInterface(); + } else { + Log.d(TAG,"trying default interface logic"); + openInterface(); + } + } + + private void openSingleInterface() throws IOException { + // the following code is inspired by the cdc-acm driver in the linux kernel + + mControlIndex = 0; + mControlInterface = mDevice.getInterface(0); + mDataInterface = mDevice.getInterface(0); + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim shared control/data interface"); + } + + for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) { + UsbEndpoint ep = mControlInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { + mControlEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mWriteEndpoint = ep; + } + } + if (mControlEndpoint == null) { + throw new IOException("No control endpoint"); + } + } + + private void openInterface() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + mControlInterface = null; + mDataInterface = null; + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { + if(controlInterfaceCount == mPortNumber) { + mControlIndex = i; + mControlInterface = usbInterface; + } + controlInterfaceCount++; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + if(dataInterfaceCount == mPortNumber) { + mDataInterface = usbInterface; + } + dataInterfaceCount++; + } + } + + if(mControlInterface == null) { + throw new IOException("No control interface"); + } + Log.d(TAG, "Control iface=" + mControlInterface); + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim control interface"); + } + + mControlEndpoint = mControlInterface.getEndpoint(0); + if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { + throw new IOException("Invalid control endpoint"); + } + + if(mDataInterface == null) { + throw new IOException("No data interface"); + } + Log.d(TAG, "data iface=" + mDataInterface); + + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim data interface"); + } + + for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { + UsbEndpoint ep = mDataInterface.getEndpoint(i); + if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mReadEndpoint = ep; + if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mWriteEndpoint = ep; + } + } + + private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { + int len = mConnection.controlTransfer( + USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); + if(len < 0) { + throw new IOException("controlTransfer failed"); + } + return len; + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mControlInterface); + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + byte stopBitsByte; + switch (stopBits) { + case STOPBITS_1: stopBitsByte = 0; break; + case STOPBITS_1_5: stopBitsByte = 1; break; + case STOPBITS_2: stopBitsByte = 2; break; + default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + byte parityBitesByte; + switch (parity) { + case 0: parityBitesByte = 0; break; + case 1: parityBitesByte = 1; break; + case 2: parityBitesByte = 2; break; + case 3: parityBitesByte = 3; break; + case 4: parityBitesByte = 4; break; + default: throw new IllegalArgumentException("Invalid parity: " + parity); + } + byte[] msg = { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + (byte) dataBits}; + sendAcmControlMessage(SET_LINE_CODING, 0, msg); + } + + @Override + public boolean getDTR() throws IOException { + return mDtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + mDtr = value; + setDtrRts(); + } + + @Override + public boolean getRTS() throws IOException { + return mRts; + } + + @Override + public void setRTS(boolean value) throws IOException { + mRts = value; + setDtrRts(); + } + + private void setDtrRts() throws IOException { + int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); + sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); + } + + @Override + public EnumSet getControlLines() throws IOException { + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(mRts) set.add(ControlLine.RTS); + if(mDtr) set.add(ControlLine.DTR); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.of(ControlLine.RTS, ControlLine.DTR); + } + + @Override + public void setBreak(boolean value) throws IOException { + sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null); + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_ARDUINO, + new int[] { + UsbId.ARDUINO_UNO, + UsbId.ARDUINO_UNO_R3, + UsbId.ARDUINO_MEGA_2560, + UsbId.ARDUINO_MEGA_2560_R3, + UsbId.ARDUINO_SERIAL_ADAPTER, + UsbId.ARDUINO_SERIAL_ADAPTER_R3, + UsbId.ARDUINO_MEGA_ADK, + UsbId.ARDUINO_MEGA_ADK_R3, + UsbId.ARDUINO_LEONARDO, + UsbId.ARDUINO_MICRO, + }); + supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, + new int[] { + UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, + }); + supportedDevices.put(UsbId.VENDOR_ATMEL, + new int[] { + UsbId.ATMEL_LUFA_CDC_DEMO_APP, + }); + supportedDevices.put(UsbId.VENDOR_LEAFLABS, + new int[] { + UsbId.LEAFLABS_MAPLE, + }); + supportedDevices.put(UsbId.VENDOR_ARM, + new int[] { + UsbId.ARM_MBED, + }); + supportedDevices.put(UsbId.VENDOR_ST, + new int[] { + UsbId.ST_CDC, + }); + return supportedDevices; + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java b/android/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java new file mode 100644 index 0000000..9ef08a4 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java @@ -0,0 +1,301 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbRequest; +import android.util.Log; + +import com.hoho.android.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.EnumSet; + +/** + * A base class shared by several driver implementations. + * + * @author mike wakerly (opensource@hoho.com) + */ +public abstract class CommonUsbSerialPort implements UsbSerialPort { + + private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); + private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; + private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit + + protected final UsbDevice mDevice; + protected final int mPortNumber; + + // non-null when open() + protected UsbDeviceConnection mConnection = null; + protected UsbEndpoint mReadEndpoint; + protected UsbEndpoint mWriteEndpoint; + protected UsbRequest mUsbRequest; + + protected final Object mWriteBufferLock = new Object(); + /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ + protected byte[] mWriteBuffer; + + public CommonUsbSerialPort(UsbDevice device, int portNumber) { + mDevice = device; + mPortNumber = portNumber; + + mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE]; + } + + @Override + public String toString() { + return String.format("<%s device_name=%s device_id=%s port_number=%s>", + getClass().getSimpleName(), mDevice.getDeviceName(), + mDevice.getDeviceId(), mPortNumber); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public int getPortNumber() { + return mPortNumber; + } + + @Override + public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; } + + @Override + public UsbEndpoint getReadEndpoint() { return mReadEndpoint; } + + /** + * Returns the device serial number + * @return serial number + */ + @Override + public String getSerial() { + return mConnection.getSerial(); + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for write operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public void open(UsbDeviceConnection connection) throws IOException { + if (mConnection != null) { + throw new IOException("Already open"); + } + if(connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + mConnection = connection; + try { + openInt(connection); + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read & write endpoints"); + } + mUsbRequest = new UsbRequest(); + mUsbRequest.initialize(mConnection, mReadEndpoint); + } catch(Exception e) { + try { + close(); + } catch(Exception ignored) {} + throw e; + } + } + + protected abstract void openInt(UsbDeviceConnection connection) throws IOException; + + @Override + public void close() throws IOException { + if (mConnection == null) { + throw new IOException("Already closed"); + } + try { + mUsbRequest.cancel(); + } catch(Exception ignored) {} + mUsbRequest = null; + try { + closeInt(); + } catch(Exception ignored) {} + try { + mConnection.close(); + } catch(Exception ignored) {} + mConnection = null; + } + + protected abstract void closeInt(); + + /** + * use simple USB request supported by all devices to test if connection is still valid + */ + protected void testConnection() throws IOException { + byte[] buf = new byte[2]; + int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); + if(len < 0) + throw new IOException("USB get_status request failed"); + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + return read(dest, timeout, true); + } + + protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } + if(dest.length <= 0) { + throw new IllegalArgumentException("Read buffer to small"); + } + final int nread; + if (timeout != 0) { + // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer + // https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data + // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, + // as it crashes with short timeout, e.g. + // A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test) + // /system/lib64/libusbhost.so (usb_request_wait+192) + // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) + // data loss / crashes were observed with timeout up to 200 msec + long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; + int readMax = Math.min(dest.length, MAX_READ_SIZE); + nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); + // Android error propagation is improvable: + // nread == -1 can be: timeout, connection lost, buffer to small, ??? + if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) + testConnection(); + + } else { + final ByteBuffer buf = ByteBuffer.wrap(dest); + if (!mUsbRequest.queue(buf, dest.length)) { + throw new IOException("Queueing USB request failed"); + } + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Waiting for USB request failed"); + } + nread = buf.position(); + // Android error propagation is improvable: + // response != null & nread == 0 can be: connection lost, buffer to small, ??? + if(nread == 0) { + testConnection(); + } + } + return Math.max(nread, 0); + } + + @Override + public void write(final byte[] src, final int timeout) throws IOException { + int offset = 0; + final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); + + if(mConnection == null) { + throw new IOException("Connection closed"); + } + while (offset < src.length) { + int requestTimeout; + final int requestLength; + final int actualLength; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + requestLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, requestLength); + writeBuffer = mWriteBuffer; + } + if (timeout == 0 || offset == 0) { + requestTimeout = timeout; + } else { + requestTimeout = (int)(endTime - MonotonicClock.millis()); + if(requestTimeout == 0) + requestTimeout = -1; + } + if (requestTimeout < 0) { + actualLength = -2; + } else { + actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); + } + } + Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); + if (actualLength <= 0) { + if (timeout != 0 && MonotonicClock.millis() >= endTime) { + SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); + ex.bytesTransferred = offset; + throw ex; + } else { + throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); + } + } + offset += actualLength; + } + } + + @Override + public boolean isOpen() { + return mConnection != null; + } + + @Override + public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + @Override + public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRI() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public abstract EnumSet getControlLines() throws IOException; + + @Override + public abstract EnumSet getSupportedControlLines() throws IOException; + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/android/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java new file mode 100644 index 0000000..99d7257 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java @@ -0,0 +1,335 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Cp21xxSerialDriver implements UsbSerialDriver { + + private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public Cp21xxSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new Cp21xxSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class Cp21xxSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + /* + * Configuration Request Types + */ + private static final int REQTYPE_HOST_TO_DEVICE = 0x41; + private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; + + /* + * Configuration Request Codes + */ + private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; + private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; + private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; + private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; + private static final int SILABSER_SET_BAUDRATE = 0x1E; + private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; + private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; + + private static final int FLUSH_READ_CODE = 0x0a; + private static final int FLUSH_WRITE_CODE = 0x05; + + /* + * SILABSER_IFC_ENABLE_REQUEST_CODE + */ + private static final int UART_ENABLE = 0x0001; + private static final int UART_DISABLE = 0x0000; + + /* + * SILABSER_SET_MHS_REQUEST_CODE + */ + private static final int DTR_ENABLE = 0x101; + private static final int DTR_DISABLE = 0x100; + private static final int RTS_ENABLE = 0x202; + private static final int RTS_DISABLE = 0x200; + + /* + * SILABSER_GET_MDMSTS_REQUEST_CODE + */ + private static final int STATUS_CTS = 0x10; + private static final int STATUS_DSR = 0x20; + private static final int STATUS_RI = 0x40; + private static final int STATUS_CD = 0x80; + + + private boolean dtr = false; + private boolean rts = false; + + // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity + // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored + private boolean mIsRestrictedPort; + + public Cp21xxSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Cp21xxSerialDriver.this; + } + + private void setConfigSingle(int request, int value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); + } + return buffer[0]; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; + if(mPortNumber >= mDevice.getInterfaceCount()) { + throw new IOException("Unknown port number"); + } + UsbInterface dataIface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(dataIface, true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); + } + + @Override + protected void closeInt() { + try { + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + private void setBaudRate(int baudRate) throws IOException { + byte[] data = new byte[] { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff) + }; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, + 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); + if (ret < 0) { + throw new IOException("Error setting baud rate"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int configDataBits = 0; + switch (dataBits) { + case DATABITS_5: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0500; + break; + case DATABITS_6: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0600; + break; + case DATABITS_7: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0700; + break; + case DATABITS_8: + configDataBits |= 0x0800; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case 0: + break; + case 1: + configDataBits |= 0x0010; + break; + case 2: + configDataBits |= 0x0020; + break; + case 3: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: mark"); + configDataBits |= 0x0030; + break; + case 4: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: space"); + configDataBits |= 0x0040; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported stop bits: 2"); + configDataBits |= 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); + } + + @Override + public EnumSet getControlLines() throws IOException { + byte status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + // note: only working on some devices, on other devices ignored w/o error + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) { + setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_SILABS, + new int[] { + UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 + UsbId.SILABS_CP2105, + UsbId.SILABS_CP2108, + }); + return supportedDevices; + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/android/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java new file mode 100644 index 0000000..5d1fa9c --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -0,0 +1,431 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * Copyright 2020 kai morich + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import com.hoho.android.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/* + * driver is implemented from various information scattered over FTDI documentation + * + * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf + * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf + * device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice + * + */ + +public class FtdiSerialDriver implements UsbSerialDriver { + + private static final String TAG = FtdiSerialPort.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public FtdiSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new FtdiSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class FtdiSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS + + private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + + private static final int RESET_REQUEST = 0; + private static final int MODEM_CONTROL_REQUEST = 1; + private static final int SET_BAUD_RATE_REQUEST = 3; + private static final int SET_DATA_REQUEST = 4; + private static final int GET_MODEM_STATUS_REQUEST = 5; + private static final int SET_LATENCY_TIMER_REQUEST = 9; + private static final int GET_LATENCY_TIMER_REQUEST = 10; + + private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; + private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; + private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; + private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; + private static final int MODEM_STATUS_CTS = 0x10; + private static final int MODEM_STATUS_DSR = 0x20; + private static final int MODEM_STATUS_RI = 0x40; + private static final int MODEM_STATUS_CD = 0x80; + private static final int RESET_ALL = 0; + private static final int RESET_PURGE_RX = 1; + private static final int RESET_PURGE_TX = 2; + + private boolean baudRateWithPort = false; + private boolean dtr = false; + private boolean rts = false; + private int breakConfig = 0; + + public FtdiSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return FtdiSerialDriver.this; + } + + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { + throw new IOException("Not enough endpoints"); + } + mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); + mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Reset failed: result=" + result); + } + result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | + (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), + mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Init RTS,DTR failed: result=" + result); + } + + // mDevice.getVersion() would require API 23 + byte[] rawDescriptors = connection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int deviceType = rawDescriptors[13]; + baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices + || mDevice.getInterfaceCount() > 1; // FT2232C + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + if(dest.length <= READ_HEADER_LENGTH) { + throw new IllegalArgumentException("Read buffer to small"); + // could allocate larger buffer, including space for 2 header bytes, but this would + // result in buffers not being 64 byte aligned any more, causing data loss at continuous + // data transfer at high baud rates when buffers are fully filled. + } + int nread; + if (timeout != 0) { + long endTime = MonotonicClock.millis() + timeout; + do { + nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); + } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); + if(nread <= 0 && MonotonicClock.millis() < endTime) + testConnection(); + } else { + do { + nread = super.read(dest, timeout, false); + } while (nread == READ_HEADER_LENGTH); + } + return readFilter(dest, nread); + } + + protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException { + final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); + int destPos = 0; + for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { + int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); + if (length < 0) + throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); + System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); + destPos += length; + } + //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); + return destPos; + } + + private void setBaudrate(int baudRate) throws IOException { + int divisor, subdivisor, effectiveBaudRate; + if (baudRate > 3500000) { + throw new UnsupportedOperationException("Baud rate to high"); + } else if(baudRate >= 2500000) { + divisor = 0; + subdivisor = 0; + effectiveBaudRate = 3000000; + } else if(baudRate >= 1750000) { + divisor = 1; + subdivisor = 0; + effectiveBaudRate = 2000000; + } else { + divisor = (24000000 << 1) / baudRate; + divisor = (divisor + 1) >> 1; // round + subdivisor = divisor & 0x07; + divisor >>= 3; + if (divisor > 0x3fff) // exceeds bit 13 at 183 baud + throw new UnsupportedOperationException("Baud rate to low"); + effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); + effectiveBaudRate = (effectiveBaudRate +1) >> 1; + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // can happen only > 1.5Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + int value = divisor; + int index = 0; + switch(subdivisor) { + case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 + case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 + case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 + case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 + case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 + case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625 + case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75 + case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 + } + if(baudRateWithPort) { + index <<= 8; + index |= mPortNumber+1; + } + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", + baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, + value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting baudrate failed: result=" + result); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudrate(baudRate); + + int config = 0; + switch (dataBits) { + case DATABITS_5: + case DATABITS_6: + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + case DATABITS_7: + case DATABITS_8: + config |= dataBits; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case 0: + break; + case 1: + config |= 0x100; + break; + case 2: + config |= 0x200; + break; + case 3: + config |= 0x300; + break; + case 4: + config |= 0x400; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + config |= 0x1000; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting parameters failed: result=" + result); + } + breakConfig = config; + } + + private int getStatus() throws IOException { + byte[] data = new byte[2]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 2) { + throw new IOException("Get modem status failed: result=" + result); + } + return data[0]; + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & MODEM_STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & MODEM_STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & MODEM_STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + dtr = value; + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & MODEM_STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + rts = value; + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge write buffer failed: result=" + result); + } + } + + if (purgeReadBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge read buffer failed: result=" + result); + } + } + } + + @Override + public void setBreak(boolean value) throws IOException { + int config = breakConfig; + if(value) config |= 0x4000; + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting BREAK failed: result=" + result); + } + } + + public void setLatencyTimer(int latencyTime) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST, + latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set latency timer failed: result=" + result); + } + } + + public int getLatencyTimer() throws IOException { + byte[] data = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Get latency timer failed: result=" + result); + } + return data[0]; + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_FTDI, + new int[] { + UsbId.FTDI_FT232R, + UsbId.FTDI_FT232H, + UsbId.FTDI_FT2232H, + UsbId.FTDI_FT4232H, + UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD + }); + return supportedDevices; + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java b/android/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java new file mode 100644 index 0000000..615e046 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java @@ -0,0 +1,87 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.util.Pair; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Maps (vendor id, product id) pairs to the corresponding serial driver. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class ProbeTable { + + private final Map, Class> mProbeTable = + new LinkedHashMap<>(); + + /** + * Adds or updates a (vendor, product) pair in the table. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @param driverClass the driver class responsible for this pair + * @return {@code this}, for chaining + */ + public ProbeTable addProduct(int vendorId, int productId, + Class driverClass) { + mProbeTable.put(Pair.create(vendorId, productId), driverClass); + return this; + } + + /** + * Internal method to add all supported products from + * {@code getSupportedProducts} static method. + * + * @param driverClass + * @return + */ + @SuppressWarnings("unchecked") + ProbeTable addDriver(Class driverClass) { + final Method method; + + try { + method = driverClass.getMethod("getSupportedDevices"); + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + final Map devices; + try { + devices = (Map) method.invoke(null); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + for (Map.Entry entry : devices.entrySet()) { + final int vendorId = entry.getKey(); + for (int productId : entry.getValue()) { + addProduct(vendorId, productId, driverClass); + } + } + + return this; + } + + /** + * Returns the driver for the given (vendor, product) pair, or {@code null} + * if no match. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @return the driver class matching this pair, or {@code null} + */ + public Class findDriver(int vendorId, int productId) { + final Pair pair = Pair.create(vendorId, productId); + return mProbeTable.get(pair); + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java b/android/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java new file mode 100644 index 0000000..12fc0ec --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java @@ -0,0 +1,15 @@ +package com.hoho.android.usbserial.driver; + +import java.io.InterruptedIOException; + +/** + * Signals that a timeout has occurred on serial write. + * Similar to SocketTimeoutException. + * + * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred + */ +public class SerialTimeoutException extends InterruptedIOException { + public SerialTimeoutException(String s) { + super(s); + } +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/android/src/main/java/com/hoho/android/usbserial/driver/UsbId.java new file mode 100644 index 0000000..c67e92a --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/UsbId.java @@ -0,0 +1,76 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +/** + * Registry of USB vendor/product ID constants. + * + * Culled from various sources; see + * usb.ids for one listing. + * + * @author mike wakerly (opensource@hoho.com) + */ +public final class UsbId { + + public static final int VENDOR_FTDI = 0x0403; + public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_FT2232H = 0x6010; + public static final int FTDI_FT4232H = 0x6011; + public static final int FTDI_FT232H = 0x6014; + public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD + + public static final int VENDOR_ATMEL = 0x03EB; + public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; + + public static final int VENDOR_ARDUINO = 0x2341; + public static final int ARDUINO_UNO = 0x0001; + public static final int ARDUINO_MEGA_2560 = 0x0010; + public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; + public static final int ARDUINO_MEGA_ADK = 0x003f; + public static final int ARDUINO_MEGA_2560_R3 = 0x0042; + public static final int ARDUINO_UNO_R3 = 0x0043; + public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; + public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; + public static final int ARDUINO_LEONARDO = 0x8036; + public static final int ARDUINO_MICRO = 0x8037; + + public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; + public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; + + public static final int VENDOR_LEAFLABS = 0x1eaf; + public static final int LEAFLABS_MAPLE = 0x0004; + + public static final int VENDOR_SILABS = 0x10c4; + public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 + public static final int SILABS_CP2105 = 0xea70; + public static final int SILABS_CP2108 = 0xea71; + + public static final int VENDOR_PROLIFIC = 0x067b; + public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX + public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN + public static final int PROLIFIC_PL2303GB = 0x23b3; // " + public static final int PROLIFIC_PL2303GT = 0x23c3; // " + public static final int PROLIFIC_PL2303GL = 0x23d3; // " + public static final int PROLIFIC_PL2303GE = 0x23e3; // " + public static final int PROLIFIC_PL2303GS = 0x23f3; // " + + public static final int VENDOR_QINHENG = 0x1a86; + public static final int QINHENG_CH340 = 0x7523; + public static final int QINHENG_CH341A = 0x5523; + + // at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids + public static final int VENDOR_ARM = 0x0d28; + public static final int ARM_MBED = 0x0204; + + public static final int VENDOR_ST = 0x0483; + public static final int ST_CDC = 0x5740; + + private UsbId() { + throw new IllegalAccessError("Non-instantiable class"); + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java new file mode 100644 index 0000000..d6539b2 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java @@ -0,0 +1,33 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; + +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialDriver { + + /** + * Returns the raw {@link UsbDevice} backing this port. + * + * @return the device + */ + UsbDevice getDevice(); + + /** + * Returns all available ports for this device. This list must have at least + * one entry. + * + * @return the ports + */ + List getPorts(); +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java new file mode 100644 index 0000000..6741a17 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java @@ -0,0 +1,261 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; + +//import androidx.annotation.IntDef; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.EnumSet; + +/** + * Interface for a single serial port. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialPort extends Closeable { + + /** 5 data bits. */ + int DATABITS_5 = 5; + /** 6 data bits. */ + int DATABITS_6 = 6; + /** 7 data bits. */ + int DATABITS_7 = 7; + /** 8 data bits. */ + int DATABITS_8 = 8; + + /** Values for setParameters(..., parity) */ + //@Retention(RetentionPolicy.SOURCE) + //@IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) + //@interface Parity {} + /** No parity. */ + int PARITY_NONE = 0; + /** Odd parity. */ + int PARITY_ODD = 1; + /** Even parity. */ + int PARITY_EVEN = 2; + /** Mark parity. */ + int PARITY_MARK = 3; + /** Space parity. */ + int PARITY_SPACE = 4; + + /** 1 stop bit. */ + int STOPBITS_1 = 1; + /** 1.5 stop bits. */ + int STOPBITS_1_5 = 3; + /** 2 stop bits. */ + int STOPBITS_2 = 2; + + /** Values for get[Supported]ControlLines() */ + enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } + + /** + * Returns the driver used by this port. + */ + UsbSerialDriver getDriver(); + + /** + * Returns the currently-bound USB device. + */ + UsbDevice getDevice(); + + /** + * Port number within driver. + */ + int getPortNumber(); + + /** + * Returns the write endpoint. + * @return write endpoint + */ + UsbEndpoint getWriteEndpoint(); + + /** + * Returns the read endpoint. + * @return read endpoint + */ + UsbEndpoint getReadEndpoint(); + + /** + * The serial number of the underlying UsbDeviceConnection, or {@code null}. + * + * @return value from {@link UsbDeviceConnection#getSerial()} + * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted + */ + String getSerial(); + + /** + * Opens and initializes the port. Upon success, caller must ensure that + * {@link #close()} is eventually called. + * + * @param connection an open device connection, acquired with + * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)} + * @throws IOException on error opening or initializing the port. + */ + void open(UsbDeviceConnection connection) throws IOException; + + /** + * Closes the port and {@link UsbDeviceConnection} + * + * @throws IOException on error closing the port. + */ + void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeout the timeout for reading in milliseconds, 0 is infinite + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + int read(final byte[] dest, final int timeout) throws IOException; + + /** + * Writes as many bytes as possible from the source buffer. + * + * @param src the source byte buffer + * @param timeout the timeout for writing in milliseconds, 0 is infinite + * @throws SerialTimeoutException if timeout reached before sending all data. + * ex.bytesTransferred may contain bytes transferred + * @throws IOException if an error occurred during writing + */ + void write(final byte[] src, final int timeout) throws IOException; + + /** + * Sets various serial port parameters. + * + * @param baudRate baud rate as an integer, for example {@code 115200}. + * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. + * @throws IOException on error setting the port parameters + * @throws UnsupportedOperationException if values are not supported by a specific device + */ + void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDTR() throws IOException; + + /** + * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRTS() throws IOException; + + /** + * Sets the RTS (Request To Send) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setRTS(boolean value) throws IOException; + + /** + * Gets all control line values from the underlying UART, if supported. + * Requires less USB calls than calling getRTS() + ... + getRI() individually. + * + * @return EnumSet.contains(...) is {@code true} if set, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getControlLines() throws IOException; + + /** + * Gets all control line supported flags. + * + * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getSupportedControlLines() throws IOException; + + /** + * Purge non-transmitted output data and / or non-read input data. + * + * @param purgeWriteBuffers {@code true} to discard non-transmitted output data + * @param purgeReadBuffers {@code true} to discard non-read input data + * @throws IOException if an error occurred during flush + * @throws UnsupportedOperationException if not supported + */ + void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; + + /** + * send BREAK condition. + * + * @param value set/reset + */ + void setBreak(boolean value) throws IOException; + + /** + * Returns the current state of the connection. + */ + boolean isOpen(); + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java new file mode 100644 index 0000000..1ff8ecb --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java @@ -0,0 +1,92 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public class UsbSerialProber { + + private final ProbeTable mProbeTable; + + public UsbSerialProber(ProbeTable probeTable) { + mProbeTable = probeTable; + } + + public static UsbSerialProber getDefaultProber() { + return new UsbSerialProber(getDefaultProbeTable()); + } + + public static ProbeTable getDefaultProbeTable() { + final ProbeTable probeTable = new ProbeTable(); + probeTable.addDriver(CdcAcmSerialDriver.class); + probeTable.addDriver(Cp21xxSerialDriver.class); + probeTable.addDriver(FtdiSerialDriver.class); + //probeTable.addDriver(ProlificSerialDriver.class); + //probeTable.addDriver(Ch34xSerialDriver.class); + return probeTable; + } + + /** + * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} + * from the currently-attached {@link UsbDevice} hierarchy. This method does + * not require permission from the Android USB system, since it does not + * open any of the devices. + * + * @param usbManager usb manager + * @return a list, possibly empty, of all compatible drivers + */ + public List findAllDrivers(final UsbManager usbManager) { + final List result = new ArrayList<>(); + + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + final UsbSerialDriver driver = probeDevice(usbDevice); + if (driver != null) { + result.add(driver); + } + } + return result; + } + + /** + * Probes a single device for a compatible driver. + * + * @param usbDevice the usb device to probe + * @return a new {@link UsbSerialDriver} compatible with this device, or + * {@code null} if none available. + */ + public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { + final int vendorId = usbDevice.getVendorId(); + final int productId = usbDevice.getProductId(); + + final Class driverClass = + mProbeTable.findDriver(vendorId, productId); + if (driverClass != null) { + final UsbSerialDriver driver; + try { + final Constructor ctor = + driverClass.getConstructor(UsbDevice.class); + driver = ctor.newInstance(usbDevice); + } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return driver; + } + return null; + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java b/android/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java new file mode 100644 index 0000000..befc3dc --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java @@ -0,0 +1,14 @@ +package com.hoho.android.usbserial.util; + +public final class MonotonicClock { + + private static final long NS_PER_MS = 1_000_000; + + private MonotonicClock() { + } + + public static long millis() { + return System.nanoTime() / NS_PER_MS; + } + +} diff --git a/android/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManagerTest.java b/android/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManagerTest.java new file mode 100644 index 0000000..8a46ac0 --- /dev/null +++ b/android/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManagerTest.java @@ -0,0 +1,254 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.util; + +import android.os.Process; +import android.util.Log; + +import com.hoho.android.usbserial.driver.UsbSerialPort; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class SerialInputOutputManagerTest implements Runnable { + + private static final String TAG = SerialInputOutputManagerTest.class.getSimpleName(); + public static boolean DEBUG = false; + private static final int BUFSIZ = 8192; + + /** + * default read timeout is infinite, to avoid data loss with bulkTransfer API + */ + private int mReadTimeout = 0; + private int mWriteTimeout = 0; + + private final Object mReadBufferLock = new Object(); + private final Object mWriteBufferLock = new Object(); + + private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() + private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); + + public enum State { + STOPPED, + RUNNING, + STOPPING + } + + private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; + private State mState = State.STOPPED; // Synchronized by 'this' + private Listener mListener; // Synchronized by 'this' + private final UsbSerialPort mSerialPort; + + public interface Listener { + /** + * Called when new incoming data is available. + */ + void onNewData(byte[] data); + + /** + * Called when {@link SerialInputOutputManager#run()} aborts due to an error. + */ + void onRunError(Exception e); + } + + public SerialInputOutputManagerTest(UsbSerialPort serialPort) { + mSerialPort = serialPort; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public SerialInputOutputManagerTest(UsbSerialPort serialPort, Listener listener) { + mSerialPort = serialPort; + mListener = listener; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public synchronized void setListener(Listener listener) { + mListener = listener; + } + + public synchronized Listener getListener() { + return mListener; + } + + /** + * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss + * + * @param threadPriority see {@link Process#setThreadPriority(int)} + * */ + public void setThreadPriority(int threadPriority) { + if (mState != State.STOPPED) + throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); + mThreadPriority = threadPriority; + } + + /** + * read/write timeout + */ + public void setReadTimeout(int timeout) { + // when set if already running, read already blocks and the new value will not become effective now + if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) + throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); + mReadTimeout = timeout; + } + + public int getReadTimeout() { + return mReadTimeout; + } + + public void setWriteTimeout(int timeout) { + mWriteTimeout = timeout; + } + + public int getWriteTimeout() { + return mWriteTimeout; + } + + /** + * read/write buffer size + */ + public void setReadBufferSize(int bufferSize) { + if (getReadBufferSize() == bufferSize) + return; + synchronized (mReadBufferLock) { + mReadBuffer = ByteBuffer.allocate(bufferSize); + } + } + + public int getReadBufferSize() { + return mReadBuffer.capacity(); + } + + public void setWriteBufferSize(int bufferSize) { + if(getWriteBufferSize() == bufferSize) + return; + synchronized (mWriteBufferLock) { + ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); + if(mWriteBuffer.position() > 0) + newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); + mWriteBuffer = newWriteBuffer; + } + } + + public int getWriteBufferSize() { + return mWriteBuffer.capacity(); + } + + /** + * when using writeAsync, it is recommended to use readTimeout != 0, + * else the write will be delayed until read data is available + */ + public void writeAsync(byte[] data) { + synchronized (mWriteBufferLock) { + mWriteBuffer.put(data); + } + } + + /** + * start SerialInputOutputManager in separate thread + */ + public void start() { + if(mState != State.STOPPED) + throw new IllegalStateException("already started"); + new Thread(this, this.getClass().getSimpleName()).start(); + } + + /** + * stop SerialInputOutputManager thread + * + * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to + * interrupt blocking read + */ + public synchronized void stop() { + if (getState() == State.RUNNING) { + Log.i(TAG, "Stop requested"); + mState = State.STOPPING; + } + } + + public synchronized State getState() { + return mState; + } + + /** + * Continuously services the read and write buffers until {@link #stop()} is + * called, or until a driver exception is raised. + */ + @Override + public void run() { + synchronized (this) { + if (getState() != State.STOPPED) { + throw new IllegalStateException("Already running"); + } + mState = State.RUNNING; + } + Log.i(TAG, "Running ..."); + try { + if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) + Process.setThreadPriority(mThreadPriority); + while (true) { + if (getState() != State.RUNNING) { + Log.i(TAG, "Stopping mState=" + getState()); + break; + } + step(); + } + } catch (Exception e) { + Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); + final Listener listener = getListener(); + if (listener != null) { + listener.onRunError(e); + } + } finally { + synchronized (this) { + mState = State.STOPPED; + Log.i(TAG, "Stopped"); + } + } + } + + private void step() throws IOException { + // Handle incoming data. + byte[] buffer; + synchronized (mReadBufferLock) { + buffer = mReadBuffer.array(); + } + int len = mSerialPort.read(buffer, mReadTimeout); + if (len > 0) { + if (DEBUG) Log.d(TAG, "Read data len=" + len); + final Listener listener = getListener(); + if (listener != null) { + final byte[] data = new byte[len]; + System.arraycopy(buffer, 0, data, 0, len); + listener.onNewData(data); + } + } + + // Handle outgoing data. + buffer = null; + synchronized (mWriteBufferLock) { + len = mWriteBuffer.position(); + if (len > 0) { + buffer = new byte[len]; + mWriteBuffer.rewind(); + mWriteBuffer.get(buffer, 0, len); + mWriteBuffer.clear(); + } + } + if (buffer != null) { + if (DEBUG) { + Log.d(TAG, "Writing data len=" + len); + } + mSerialPort.write(buffer, mWriteTimeout); + } + } + +} diff --git a/androidserialport.cpp b/androidserialport.cpp new file mode 100644 index 0000000..e7e2654 --- /dev/null +++ b/androidserialport.cpp @@ -0,0 +1,132 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "androidserialport.h" + +#ifdef Q_OS_ANDROID + +AndroidSerialPort::AndroidSerialPort(QObject * parent) +{ + if(QAndroidJniObject::isClassAvailable("com.hoho.android.usbserial.driver/UsbSerialDriver")) { + qDebug() << "com.hoho.android.usbserial.driver/UsbSerialDriver available"; + serialJavaObject = QAndroidJniObject("DroidStar/USBSerialWrapper"); + QAndroidJniEnvironment env; + JNINativeMethod methods[] = { {"data_received", "([B)V", reinterpret_cast(java_data_received)} }; + jclass objectClass = env->GetObjectClass(serialJavaObject.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + } + else { + qDebug() << "com.hoho.android.usbserial.driver/UsbSerialDriver not available"; + } +} + +QStringList AndroidSerialPort::discover_devices() +{ + QAndroidJniEnvironment env; + QStringList l; + l.clear(); + qDebug() << "AndroidSerialPort::discover_devices()"; + QAndroidJniObject d = serialJavaObject.callObjectMethod("discover_devices", "(Landroid/content/Context;)[Ljava/lang/String;", QtAndroid::androidContext().object()); + jobjectArray devices = d.object(); + int size = env->GetArrayLength(devices); + + for (int i=0; i < size; ++i) { + jstring s = (jstring) env->GetObjectArrayElement(devices, i); + l.append(env->GetStringUTFChars(s, JNI_FALSE)); + } + return l; +} + +int AndroidSerialPort::open(int p) +{ + qDebug() << serialJavaObject.callObjectMethod("setup_serial", "(Landroid/content/Context;)Ljava/lang/String;", QtAndroid::androidContext().object()).toString(); + return 1; +} + +int AndroidSerialPort::write(char *data, int s) +{ + QAndroidJniEnvironment env; + jbyteArray buffer = env->NewByteArray(s); + env->SetByteArrayRegion(buffer, 0, s, (jbyte *)data); + serialJavaObject.callMethod("write", "([B)V", buffer); + return 0; +} + +int AndroidSerialPort::write(QByteArray data) +{ + QAndroidJniEnvironment env; + jbyteArray buffer = env->NewByteArray(data.size()); + env->SetByteArrayRegion(buffer, 0, data.size(), (jbyte *)data.data()); + serialJavaObject.callMethod("write", "([B)V", buffer); + return 0; +} + +void AndroidSerialPort::setPortName(QString s) +{ + qDebug() << "setPortName() == " << s; +} + +void AndroidSerialPort::setBaudRate(int br) +{ + serialJavaObject.callMethod("set_baud_rate", "(I)V", br); +} + +void AndroidSerialPort::setDataBits(int db) +{ + serialJavaObject.callMethod("set_data_bits", "(I)V", db); +} + +void AndroidSerialPort::setStopBits(int sb) +{ + serialJavaObject.callMethod("set_stop_bits", "(I)V", sb); +} + +void AndroidSerialPort::setParity(int p) +{ + serialJavaObject.callMethod("set_parity", "(I)V", p); +} + +void AndroidSerialPort::setFlowControl(int fc) +{ + serialJavaObject.callMethod("set_flow_control", "(I)V", fc); +} + +void AndroidSerialPort::setRequestToSend(int rts) +{ + serialJavaObject.callMethod("set_rts", "(I)V", rts); +} + +QByteArray AndroidSerialPort::readAll() +{ + QByteArray r; + r.replace(0, m_received.size(), m_received); + m_received.clear(); + return r; +} + +void AndroidSerialPort::java_data_received(JNIEnv *env, jobject t, jbyteArray data) +{ + QByteArray r; + jboolean copy; + jsize s = env->GetArrayLength(data); + jbyte *p_data = env->GetByteArrayElements(data, ©); + r.append((char *)p_data, s); + emit AndroidSerialPort::GetInstance().data_received(r); +} + +#endif diff --git a/androidserialport.h b/androidserialport.h new file mode 100644 index 0000000..52368d5 --- /dev/null +++ b/androidserialport.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef ANDROIDSERIALPORT_H +#define ANDROIDSERIALPORT_H + +#include +#ifdef Q_OS_ANDROID +#include + +class AndroidSerialPort : public QObject +{ + Q_OBJECT +public: + static AndroidSerialPort & GetInstance() + { + static AndroidSerialPort instance; + return instance; + } + QByteArray readAll(); + QStringList discover_devices(); + + int open(int); + void close(){} + void setPortName(QString); + void setBaudRate(int); + void setDataBits(int); + void setStopBits(int); + void setParity(int); + void setFlowControl(int); + void setRequestToSend(int); + int write(QByteArray); + int write(char *, int); +signals: + void readyRead(); + void data_received(QByteArray); +private: + explicit AndroidSerialPort(QObject * parent = nullptr); + static void java_data_received(JNIEnv *env, jobject t, jbyteArray data); + QAndroidJniObject serialJavaObject; + QByteArray m_received; +}; +#endif +#endif // ANDROIDSERIALPORT_H diff --git a/audioengine.cpp b/audioengine.cpp new file mode 100644 index 0000000..91e15c3 --- /dev/null +++ b/audioengine.cpp @@ -0,0 +1,398 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "audioengine.h" +#include +#include + +#ifdef Q_OS_MACOS +#define MACHAK 1 +#else +#define MACHAK 0 +#endif + +//AudioEngine::AudioEngine(QObject *parent) : QObject(parent) +AudioEngine::AudioEngine(QString in, QString out) : + m_outputdevice(out), + m_inputdevice(in), + m_out(nullptr), + m_in(nullptr), + m_srm(1) +{ + m_audio_out_temp_buf_p = m_audio_out_temp_buf; + memset(m_aout_max_buf, 0, sizeof(float) * 200); + m_aout_max_buf_p = m_aout_max_buf; + m_aout_max_buf_idx = 0; + m_aout_gain = 100; + m_volume = 1.0f; +} + +AudioEngine::~AudioEngine() +{ + //m_indev->disconnect(); + //m_in->stop(); + //m_outdev->disconnect(); + //m_out->stop(); + //delete m_in; + //delete m_out; +} + +QStringList AudioEngine::discover_audio_devices(uint8_t d) +{ + QStringList list; + QAudio::Mode m = (d) ? QAudio::AudioOutput : QAudio::AudioInput; + QList devices = QAudioDeviceInfo::availableDevices(m); + + for (QList::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) { + //fprintf(stderr, "Playback device name = %s\n", (*it).deviceName().toStdString().c_str());fflush(stderr); + list.append((*it).deviceName()); + } + return list; +} + +void AudioEngine::init() +{ + QAudioFormat format; + QAudioFormat tempformat; + format.setSampleRate(8000); + format.setChannelCount(1); + format.setSampleSize(16); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + + m_agc = true; + + QList devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + + if(devices.size() == 0){ + fprintf(stderr, "No audio playback hardware found\n");fflush(stderr); + } + else{ + QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); + for (QList::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) { + if(MACHAK){ + qDebug() << "Playback device name = " << (*it).deviceName(); + qDebug() << (*it).supportedByteOrders(); + qDebug() << (*it).supportedChannelCounts(); + qDebug() << (*it).supportedCodecs(); + qDebug() << (*it).supportedSampleRates(); + qDebug() << (*it).supportedSampleSizes(); + qDebug() << (*it).supportedSampleTypes(); + qDebug() << (*it).preferredFormat(); + } + if((*it).deviceName() == m_outputdevice){ + info = *it; + } + } + if (!info.isFormatSupported(format)) { + qWarning() << "Raw audio format not supported by backend, trying nearest format."; + tempformat = info.nearestFormat(format); + qWarning() << "Format now set to " << format.sampleRate() << ":" << format.sampleSize(); + } + else{ + tempformat = format; + } + fprintf(stderr, "Using playback device %s\n", info.deviceName().toStdString().c_str());fflush(stderr); + + m_out = new QAudioOutput(info, tempformat, this); + m_out->setBufferSize(19200); + connect(m_out, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State))); + //m_outdev = m_out->start(); + } + + devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + + if(devices.size() == 0){ + fprintf(stderr, "No audio recording hardware found\n");fflush(stderr); + } + else{ + QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice()); + for (QList::ConstIterator it = devices.constBegin(); it != devices.constEnd(); ++it ) { + if(MACHAK){ + qDebug() << "Capture device name = " << (*it).deviceName(); + qDebug() << (*it).supportedByteOrders(); + qDebug() << (*it).supportedChannelCounts(); + qDebug() << (*it).supportedCodecs(); + qDebug() << (*it).supportedSampleRates(); + qDebug() << (*it).supportedSampleSizes(); + qDebug() << (*it).supportedSampleTypes(); + qDebug() << (*it).preferredFormat(); + } + if((*it).deviceName() == m_inputdevice){ + info = *it; + } + } + if (!info.isFormatSupported(format)) { + qWarning() << "Raw audio format not supported by backend, trying nearest format."; + tempformat = info.nearestFormat(format); + qWarning() << "Format now set to " << format.sampleRate() << ":" << format.sampleSize(); + } + else{ + tempformat = format; + } + + int sr = 8000; + if(MACHAK){ + sr = info.preferredFormat().sampleRate(); + m_srm = (float)sr / 8000.0; + } + format.setSampleRate(sr); + m_in = new QAudioInput(info, format, this); + fprintf(stderr, "Capture device: %s SR: %d resample factor: %f\n", info.deviceName().toStdString().c_str(), sr, m_srm);fflush(stderr); + } +} + +void AudioEngine::start_capture() +{ + m_audioinq.clear(); + if(m_in != nullptr){ + m_indev = m_in->start(); + connect(m_indev, SIGNAL(readyRead()), SLOT(input_data_received())); + } +} + +void AudioEngine::stop_capture() +{ + if(m_in != nullptr){ + m_indev->disconnect(); + m_in->stop(); + } +} + +void AudioEngine::start_playback() +{ + //m_out->reset(); + m_outdev = m_out->start(); +} + +void AudioEngine::stop_playback() +{ + m_out->stop(); +} + +void AudioEngine::input_data_received() +{ + QByteArray data; + qint64 len = m_in->bytesReady(); + + if (len > 0){ + data.resize(len); + m_indev->read(data.data(), len); +/* + fprintf(stderr, "AUDIOIN: "); + for(int i = 0; i < len; ++i){ + fprintf(stderr, "%02x ", (unsigned char)data.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +*/ + if(MACHAK){ + std::vector samples; + for(int i = 0; i < len; i += 2){ + samples.push_back(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff)); + } + for(float i = 0; i < (float)len/2; i += m_srm){ + m_audioinq.enqueue(samples[i]); + } + } + else{ + for(int i = 0; i < len; i += (2 * m_srm)){ + m_audioinq.enqueue(((data.data()[i+1] << 8) & 0xff00) | (data.data()[i] & 0xff)); + } + } + } +} + +void AudioEngine::write(int16_t *pcm, size_t s) +{ + m_maxlevel = 0; +/* + fprintf(stderr, "AUDIOOUT: "); + for(int i = 0; i < s; ++i){ + fprintf(stderr, "%04x ", (uint16_t)pcm[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +*/ + if(m_agc){ + process_audio(pcm, s); + } + + m_outdev->write((const char *) pcm, sizeof(int16_t) * s); + for(uint32_t i = 0; i < s; ++i){ + if(pcm[i] > m_maxlevel){ + m_maxlevel = pcm[i]; + } + } +} + +uint16_t AudioEngine::read(int16_t *pcm, int s) +{ + m_maxlevel = 0; + + if(m_audioinq.size() >= s){ + for(int i = 0; i < s; ++i){ + pcm[i] = m_audioinq.dequeue(); + if(pcm[i] > m_maxlevel){ + m_maxlevel = pcm[i]; + } + } + return 1; + } + else if(m_in == nullptr){ + memset(pcm, 0, sizeof(int16_t) * s); + return 1; + } + else{ + //fprintf(stderr, "audio frame not avail size == %d\n", m_audioinq.size()); + return 0; + } +} + +uint16_t AudioEngine::read(int16_t *pcm) +{ + int s; + m_maxlevel = 0; + + if(m_audioinq.size() >= 160){ + s = 160; + } + else{ + s = m_audioinq.size(); + } + + for(int i = 0; i < s; ++i){ + pcm[i] = m_audioinq.dequeue(); + if(pcm[i] > m_maxlevel){ + m_maxlevel = pcm[i]; + } + } + + return s; +} + +// process_audio() based on code from DSD https://github.com/szechyjs/dsd +void AudioEngine::process_audio(int16_t *pcm, size_t s) +{ + float aout_abs, max, gainfactor, gaindelta, maxbuf; + + for(size_t i = 0; i < s; ++i){ + m_audio_out_temp_buf[i] = static_cast(pcm[i]); + } + + // detect max level + max = 0; + m_audio_out_temp_buf_p = m_audio_out_temp_buf; + + for (size_t i = 0; i < s; i++){ + aout_abs = fabsf(*m_audio_out_temp_buf_p); + + if (aout_abs > max){ + max = aout_abs; + } + + m_audio_out_temp_buf_p++; + } + + *m_aout_max_buf_p = max; + m_aout_max_buf_p++; + m_aout_max_buf_idx++; + + if (m_aout_max_buf_idx > 24){ + m_aout_max_buf_idx = 0; + m_aout_max_buf_p = m_aout_max_buf; + } + + // lookup max history + for (size_t i = 0; i < 25; i++){ + maxbuf = m_aout_max_buf[i]; + + if (maxbuf > max){ + max = maxbuf; + } + } + + // determine optimal gain level + if (max > static_cast(0)){ + gainfactor = (static_cast(30000) / max); + } + else{ + gainfactor = static_cast(50); + } + + if (gainfactor < m_aout_gain){ + m_aout_gain = gainfactor; + gaindelta = static_cast(0); + } + else{ + if (gainfactor > static_cast(50)){ + gainfactor = static_cast(50); + } + + gaindelta = gainfactor - m_aout_gain; + + if (gaindelta > (static_cast(0.05) * m_aout_gain)){ + gaindelta = (static_cast(0.05) * m_aout_gain); + } + } + + gaindelta /= static_cast(160); + + // adjust output gain + m_audio_out_temp_buf_p = m_audio_out_temp_buf; + + for (size_t i = 0; i < 160; i++){ + *m_audio_out_temp_buf_p = (m_aout_gain + (static_cast(i) * gaindelta)) * (*m_audio_out_temp_buf_p); + m_audio_out_temp_buf_p++; + } + + m_aout_gain += (static_cast(s) * gaindelta); + m_audio_out_temp_buf_p = m_audio_out_temp_buf; + + for (size_t i = 0; i < s; i++){ + *m_audio_out_temp_buf_p *= m_volume; + if (*m_audio_out_temp_buf_p > static_cast(32760)){ + *m_audio_out_temp_buf_p = static_cast(32760); + } + else if (*m_audio_out_temp_buf_p < static_cast(-32760)){ + *m_audio_out_temp_buf_p = static_cast(-32760); + } + pcm[i] = static_cast(*m_audio_out_temp_buf_p); + m_audio_out_temp_buf_p++; + } +} + +void AudioEngine::handleStateChanged(QAudio::State newState) +{ + switch (newState) { + case QAudio::ActiveState: + //qDebug() << "AudioOut state active"; + break; + case QAudio::SuspendedState: + //qDebug() << "AudioOut state suspended"; + break; + case QAudio::IdleState: + //qDebug() << "AudioOut state idle"; + break; + case QAudio::StoppedState: + //qDebug() << "AudioOut state stopped"; + break; + default: + break; + } +} diff --git a/audioengine.h b/audioengine.h new file mode 100644 index 0000000..967abfb --- /dev/null +++ b/audioengine.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef AUDIOENGINE_H +#define AUDIOENGINE_H + +#include +#include +#include +#include +#include +#include + +#define AUDIO_OUT 1 +#define AUDIO_IN 0 + +class AudioEngine : public QObject +{ + Q_OBJECT +public: + //explicit AudioEngine(QObject *parent = nullptr); + AudioEngine(QString in, QString out); + ~AudioEngine(); + static QStringList discover_audio_devices(uint8_t d); + void init(); + void start_capture(); + void stop_capture(); + void start_playback(); + void stop_playback(); + void write(int16_t *, size_t); + void set_output_buffer_size(uint32_t b) { m_out->setBufferSize(b); } + void set_input_buffer_size(uint32_t b) { if(m_in != nullptr) m_in->setBufferSize(b); } + void set_output_volume(qreal v){ m_out->setVolume(v); } + void set_input_volume(qreal v){ m_in->setVolume(v); } + void set_agc(bool agc) { m_agc = agc; } + bool frame_available() { return (m_audioinq.size() >= 320) ? true : false; } + uint16_t read(int16_t *, int); + uint16_t read(int16_t *); + uint16_t level() { return m_maxlevel; } +signals: + +private: + QString m_outputdevice; + QString m_inputdevice; + QAudioOutput *m_out; + QAudioInput *m_in; + QIODevice *m_outdev; + QIODevice *m_indev; + QQueue m_audioinq; + uint16_t m_maxlevel; + bool m_agc; + float m_srm; // sample rate multiplier for macOS HACK + + float m_audio_out_temp_buf[160]; //!< output of decoder + float *m_audio_out_temp_buf_p; + + //float m_audio_out_float_buf[1120]; //!< output of upsampler - 1 frame of 160 samples upampled up to 7 times + //float *m_audio_out_float_buf_p; + + float m_aout_max_buf[200]; + float *m_aout_max_buf_p; + int m_aout_max_buf_idx; + + //short m_audio_out_buf[2*48000]; //!< final result - 1s of L+R S16LE samples + //short *m_audio_out_buf_p; + //int m_audio_out_nb_samples; + //int m_audio_out_buf_size; + //int m_audio_out_idx; + //int m_audio_out_idx2; + + float m_aout_gain; + float m_volume; + +private slots: + void input_data_received(); + void process_audio(int16_t *pcm, size_t s); + void handleStateChanged(QAudio::State newState); +}; + +#endif // AUDIOENGINE_H diff --git a/cbptc19696.cpp b/cbptc19696.cpp new file mode 100644 index 0000000..13d1715 --- /dev/null +++ b/cbptc19696.cpp @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2012 by Ian Wraith + * Copyright (C) 2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "cbptc19696.h" + +#include "chamming.h" +//#include "cutils.h" + +#include +#include +#include + +CBPTC19696::CBPTC19696() +{ +} + +CBPTC19696::~CBPTC19696() +{ +} + +// The main decode function +void CBPTC19696::decode(const unsigned char* in, unsigned char* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Get the raw binary + decodeExtractBinary(in); + + // Deinterleave + decodeDeInterleave(); + + // Error check + decodeErrorCheck(); + + // Extract Data + decodeExtractData(out); +} + +// The main encode function +void CBPTC19696::encode(const unsigned char* in, unsigned char* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Extract Data + encodeExtractData(in); + + // Error check + encodeErrorCheck(); + + // Deinterleave + encodeInterleave(); + + // Get the raw binary + encodeExtractBinary(out); +} + +void CBPTC19696::byteToBitsBE(unsigned char byte, bool* bits) +{ + assert(bits != NULL); + + bits[0U] = (byte & 0x80U) == 0x80U; + bits[1U] = (byte & 0x40U) == 0x40U; + bits[2U] = (byte & 0x20U) == 0x20U; + bits[3U] = (byte & 0x10U) == 0x10U; + bits[4U] = (byte & 0x08U) == 0x08U; + bits[5U] = (byte & 0x04U) == 0x04U; + bits[6U] = (byte & 0x02U) == 0x02U; + bits[7U] = (byte & 0x01U) == 0x01U; +} + +void CBPTC19696::bitsToByteBE(bool* bits, unsigned char& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x80U : 0x00U; + byte |= bits[1U] ? 0x40U : 0x00U; + byte |= bits[2U] ? 0x20U : 0x00U; + byte |= bits[3U] ? 0x10U : 0x00U; + byte |= bits[4U] ? 0x08U : 0x00U; + byte |= bits[5U] ? 0x04U : 0x00U; + byte |= bits[6U] ? 0x02U : 0x00U; + byte |= bits[7U] ? 0x01U : 0x00U; +} + +void CBPTC19696::decodeExtractBinary(const unsigned char* in) +{ + // First block + byteToBitsBE(in[0U], m_rawData + 0U); + byteToBitsBE(in[1U], m_rawData + 8U); + byteToBitsBE(in[2U], m_rawData + 16U); + byteToBitsBE(in[3U], m_rawData + 24U); + byteToBitsBE(in[4U], m_rawData + 32U); + byteToBitsBE(in[5U], m_rawData + 40U); + byteToBitsBE(in[6U], m_rawData + 48U); + byteToBitsBE(in[7U], m_rawData + 56U); + byteToBitsBE(in[8U], m_rawData + 64U); + byteToBitsBE(in[9U], m_rawData + 72U); + byteToBitsBE(in[10U], m_rawData + 80U); + byteToBitsBE(in[11U], m_rawData + 88U); + byteToBitsBE(in[12U], m_rawData + 96U); + + // Handle the two bits + bool bits[8U]; + byteToBitsBE(in[20U], bits); + m_rawData[98U] = bits[6U]; + m_rawData[99U] = bits[7U]; + + // Second block + byteToBitsBE(in[21U], m_rawData + 100U); + byteToBitsBE(in[22U], m_rawData + 108U); + byteToBitsBE(in[23U], m_rawData + 116U); + byteToBitsBE(in[24U], m_rawData + 124U); + byteToBitsBE(in[25U], m_rawData + 132U); + byteToBitsBE(in[26U], m_rawData + 140U); + byteToBitsBE(in[27U], m_rawData + 148U); + byteToBitsBE(in[28U], m_rawData + 156U); + byteToBitsBE(in[29U], m_rawData + 164U); + byteToBitsBE(in[30U], m_rawData + 172U); + byteToBitsBE(in[31U], m_rawData + 180U); + byteToBitsBE(in[32U], m_rawData + 188U); +} + +// Deinterleave the raw data +void CBPTC19696::decodeDeInterleave() +{ + for (unsigned int i = 0U; i < 196U; i++) + m_deInterData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (unsigned int a = 0U; a < 196U; a++) { + // Calculate the interleave sequence + unsigned int interleaveSequence = (a * 181U) % 196U; + // Shuffle the data + m_deInterData[a] = m_rawData[interleaveSequence]; + } +} + +// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code +void CBPTC19696::decodeErrorCheck() +{ + bool fixing; + unsigned int count = 0U; + do { + fixing = false; + + // Run through each of the 15 columns + bool col[13U]; + for (unsigned int c = 0U; c < 15U; c++) { + unsigned int pos = c + 1U; + for (unsigned int a = 0U; a < 13U; a++) { + col[a] = m_deInterData[pos]; + pos = pos + 15U; + } + + if (CHamming::decode1393(col)) { + unsigned int pos = c + 1U; + for (unsigned int a = 0U; a < 13U; a++) { + m_deInterData[pos] = col[a]; + pos = pos + 15U; + } + + fixing = true; + } + } + + // Run through each of the 9 rows containing data + for (unsigned int r = 0U; r < 9U; r++) { + unsigned int pos = (r * 15U) + 1U; + if (CHamming::decode15113_2(m_deInterData + pos)) + fixing = true; + } + + count++; + } while (fixing && count < 5U); +} + +// Extract the 96 bits of payload +void CBPTC19696::decodeExtractData(unsigned char* data) +{ + bool bData[96U]; + unsigned int pos = 0U; + for (unsigned int a = 4U; a <= 11U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 16U; a <= 26U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 31U; a <= 41U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 46U; a <= 56U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 61U; a <= 71U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 76U; a <= 86U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 91U; a <= 101U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 106U; a <= 116U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (unsigned int a = 121U; a <= 131U; a++, pos++) + bData[pos] = m_deInterData[a]; + + bitsToByteBE(bData + 0U, data[0U]); + bitsToByteBE(bData + 8U, data[1U]); + bitsToByteBE(bData + 16U, data[2U]); + bitsToByteBE(bData + 24U, data[3U]); + bitsToByteBE(bData + 32U, data[4U]); + bitsToByteBE(bData + 40U, data[5U]); + bitsToByteBE(bData + 48U, data[6U]); + bitsToByteBE(bData + 56U, data[7U]); + bitsToByteBE(bData + 64U, data[8U]); + bitsToByteBE(bData + 72U, data[9U]); + bitsToByteBE(bData + 80U, data[10U]); + bitsToByteBE(bData + 88U, data[11U]); +} + +// Extract the 96 bits of payload +void CBPTC19696::encodeExtractData(const unsigned char* in) +{ + bool bData[96U]; + byteToBitsBE(in[0U], bData + 0U); + byteToBitsBE(in[1U], bData + 8U); + byteToBitsBE(in[2U], bData + 16U); + byteToBitsBE(in[3U], bData + 24U); + byteToBitsBE(in[4U], bData + 32U); + byteToBitsBE(in[5U], bData + 40U); + byteToBitsBE(in[6U], bData + 48U); + byteToBitsBE(in[7U], bData + 56U); + byteToBitsBE(in[8U], bData + 64U); + byteToBitsBE(in[9U], bData + 72U); + byteToBitsBE(in[10U], bData + 80U); + byteToBitsBE(in[11U], bData + 88U); + + for (unsigned int i = 0U; i < 196U; i++) + m_deInterData[i] = false; + + unsigned int pos = 0U; + for (unsigned int a = 4U; a <= 11U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 16U; a <= 26U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 31U; a <= 41U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 46U; a <= 56U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 61U; a <= 71U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 76U; a <= 86U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 91U; a <= 101U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 106U; a <= 116U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (unsigned int a = 121U; a <= 131U; a++, pos++) + m_deInterData[a] = bData[pos]; +} + +// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code +void CBPTC19696::encodeErrorCheck() +{ + + // Run through each of the 9 rows containing data + for (unsigned int r = 0U; r < 9U; r++) { + unsigned int pos = (r * 15U) + 1U; + CHamming::encode15113_2(m_deInterData + pos); + } + + // Run through each of the 15 columns + bool col[13U]; + for (unsigned int c = 0U; c < 15U; c++) { + unsigned int pos = c + 1U; + for (unsigned int a = 0U; a < 13U; a++) { + col[a] = m_deInterData[pos]; + pos = pos + 15U; + } + + CHamming::encode1393(col); + + pos = c + 1U; + for (unsigned int a = 0U; a < 13U; a++) { + m_deInterData[pos] = col[a]; + pos = pos + 15U; + } + } +} + +// Interleave the raw data +void CBPTC19696::encodeInterleave() +{ + for (unsigned int i = 0U; i < 196U; i++) + m_rawData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (unsigned int a = 0U; a < 196U; a++) { + // Calculate the interleave sequence + unsigned int interleaveSequence = (a * 181U) % 196U; + // Unshuffle the data + m_rawData[interleaveSequence] = m_deInterData[a]; + } +} + +void CBPTC19696::encodeExtractBinary(unsigned char* data) +{ + // First block + bitsToByteBE(m_rawData + 0U, data[0U]); + bitsToByteBE(m_rawData + 8U, data[1U]); + bitsToByteBE(m_rawData + 16U, data[2U]); + bitsToByteBE(m_rawData + 24U, data[3U]); + bitsToByteBE(m_rawData + 32U, data[4U]); + bitsToByteBE(m_rawData + 40U, data[5U]); + bitsToByteBE(m_rawData + 48U, data[6U]); + bitsToByteBE(m_rawData + 56U, data[7U]); + bitsToByteBE(m_rawData + 64U, data[8U]); + bitsToByteBE(m_rawData + 72U, data[9U]); + bitsToByteBE(m_rawData + 80U, data[10U]); + bitsToByteBE(m_rawData + 88U, data[11U]); + + // Handle the two bits + unsigned char byte; + bitsToByteBE(m_rawData + 96U, byte); + data[12U] = (data[12U] & 0x3FU) | ((byte >> 0) & 0xC0U); + data[20U] = (data[20U] & 0xFCU) | ((byte >> 4) & 0x03U); + + // Second block + bitsToByteBE(m_rawData + 100U, data[21U]); + bitsToByteBE(m_rawData + 108U, data[22U]); + bitsToByteBE(m_rawData + 116U, data[23U]); + bitsToByteBE(m_rawData + 124U, data[24U]); + bitsToByteBE(m_rawData + 132U, data[25U]); + bitsToByteBE(m_rawData + 140U, data[26U]); + bitsToByteBE(m_rawData + 148U, data[27U]); + bitsToByteBE(m_rawData + 156U, data[28U]); + bitsToByteBE(m_rawData + 164U, data[29U]); + bitsToByteBE(m_rawData + 172U, data[30U]); + bitsToByteBE(m_rawData + 180U, data[31U]); + bitsToByteBE(m_rawData + 188U, data[32U]); +} diff --git a/cbptc19696.h b/cbptc19696.h new file mode 100644 index 0000000..9f5ee61 --- /dev/null +++ b/cbptc19696.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(BPTC19696_H) +#define BPTC19696_H + +class CBPTC19696 +{ +public: + CBPTC19696(); + ~CBPTC19696(); + + void decode(const unsigned char* in, unsigned char* out); + + void encode(const unsigned char* in, unsigned char* out); + +private: + bool m_rawData[196]; + bool m_deInterData[196]; + + void decodeExtractBinary(const unsigned char* in); + void decodeErrorCheck(); + void decodeDeInterleave(); + void decodeExtractData(unsigned char* data); + + void encodeExtractData(const unsigned char* in); + void encodeInterleave(); + void encodeErrorCheck(); + void encodeExtractBinary(unsigned char* data); + void byteToBitsBE(unsigned char byte, bool* bits); + void bitsToByteBE(bool* bits, unsigned char& byte); +}; + +#endif diff --git a/cgolay2087.cpp b/cgolay2087.cpp new file mode 100644 index 0000000..7ff6f92 --- /dev/null +++ b/cgolay2087.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "cgolay2087.h" + +#include +#include + +const unsigned int ENCODING_TABLE_2087[] = +{0x0000U, 0xB08EU, 0xE093U, 0x501DU, 0x70A9U, 0xC027U, 0x903AU, 0x20B4U, 0x60DCU, 0xD052U, 0x804FU, 0x30C1U, + 0x1075U, 0xA0FBU, 0xF0E6U, 0x4068U, 0x7036U, 0xC0B8U, 0x90A5U, 0x202BU, 0x009FU, 0xB011U, 0xE00CU, 0x5082U, + 0x10EAU, 0xA064U, 0xF079U, 0x40F7U, 0x6043U, 0xD0CDU, 0x80D0U, 0x305EU, 0xD06CU, 0x60E2U, 0x30FFU, 0x8071U, + 0xA0C5U, 0x104BU, 0x4056U, 0xF0D8U, 0xB0B0U, 0x003EU, 0x5023U, 0xE0ADU, 0xC019U, 0x7097U, 0x208AU, 0x9004U, + 0xA05AU, 0x10D4U, 0x40C9U, 0xF047U, 0xD0F3U, 0x607DU, 0x3060U, 0x80EEU, 0xC086U, 0x7008U, 0x2015U, 0x909BU, + 0xB02FU, 0x00A1U, 0x50BCU, 0xE032U, 0x90D9U, 0x2057U, 0x704AU, 0xC0C4U, 0xE070U, 0x50FEU, 0x00E3U, 0xB06DU, + 0xF005U, 0x408BU, 0x1096U, 0xA018U, 0x80ACU, 0x3022U, 0x603FU, 0xD0B1U, 0xE0EFU, 0x5061U, 0x007CU, 0xB0F2U, + 0x9046U, 0x20C8U, 0x70D5U, 0xC05BU, 0x8033U, 0x30BDU, 0x60A0U, 0xD02EU, 0xF09AU, 0x4014U, 0x1009U, 0xA087U, + 0x40B5U, 0xF03BU, 0xA026U, 0x10A8U, 0x301CU, 0x8092U, 0xD08FU, 0x6001U, 0x2069U, 0x90E7U, 0xC0FAU, 0x7074U, + 0x50C0U, 0xE04EU, 0xB053U, 0x00DDU, 0x3083U, 0x800DU, 0xD010U, 0x609EU, 0x402AU, 0xF0A4U, 0xA0B9U, 0x1037U, + 0x505FU, 0xE0D1U, 0xB0CCU, 0x0042U, 0x20F6U, 0x9078U, 0xC065U, 0x70EBU, 0xA03DU, 0x10B3U, 0x40AEU, 0xF020U, + 0xD094U, 0x601AU, 0x3007U, 0x8089U, 0xC0E1U, 0x706FU, 0x2072U, 0x90FCU, 0xB048U, 0x00C6U, 0x50DBU, 0xE055U, + 0xD00BU, 0x6085U, 0x3098U, 0x8016U, 0xA0A2U, 0x102CU, 0x4031U, 0xF0BFU, 0xB0D7U, 0x0059U, 0x5044U, 0xE0CAU, + 0xC07EU, 0x70F0U, 0x20EDU, 0x9063U, 0x7051U, 0xC0DFU, 0x90C2U, 0x204CU, 0x00F8U, 0xB076U, 0xE06BU, 0x50E5U, + 0x108DU, 0xA003U, 0xF01EU, 0x4090U, 0x6024U, 0xD0AAU, 0x80B7U, 0x3039U, 0x0067U, 0xB0E9U, 0xE0F4U, 0x507AU, + 0x70CEU, 0xC040U, 0x905DU, 0x20D3U, 0x60BBU, 0xD035U, 0x8028U, 0x30A6U, 0x1012U, 0xA09CU, 0xF081U, 0x400FU, + 0x30E4U, 0x806AU, 0xD077U, 0x60F9U, 0x404DU, 0xF0C3U, 0xA0DEU, 0x1050U, 0x5038U, 0xE0B6U, 0xB0ABU, 0x0025U, + 0x2091U, 0x901FU, 0xC002U, 0x708CU, 0x40D2U, 0xF05CU, 0xA041U, 0x10CFU, 0x307BU, 0x80F5U, 0xD0E8U, 0x6066U, + 0x200EU, 0x9080U, 0xC09DU, 0x7013U, 0x50A7U, 0xE029U, 0xB034U, 0x00BAU, 0xE088U, 0x5006U, 0x001BU, 0xB095U, + 0x9021U, 0x20AFU, 0x70B2U, 0xC03CU, 0x8054U, 0x30DAU, 0x60C7U, 0xD049U, 0xF0FDU, 0x4073U, 0x106EU, 0xA0E0U, + 0x90BEU, 0x2030U, 0x702DU, 0xC0A3U, 0xE017U, 0x5099U, 0x0084U, 0xB00AU, 0xF062U, 0x40ECU, 0x10F1U, 0xA07FU, + 0x80CBU, 0x3045U, 0x6058U, 0xD0D6U}; + +const unsigned int DECODING_TABLE_1987[] = +{0x00000U, 0x00001U, 0x00002U, 0x00003U, 0x00004U, 0x00005U, 0x00006U, 0x00007U, 0x00008U, 0x00009U, 0x0000AU, 0x0000BU, 0x0000CU, + 0x0000DU, 0x0000EU, 0x24020U, 0x00010U, 0x00011U, 0x00012U, 0x00013U, 0x00014U, 0x00015U, 0x00016U, 0x00017U, 0x00018U, 0x00019U, + 0x0001AU, 0x0001BU, 0x0001CU, 0x0001DU, 0x48040U, 0x01480U, 0x00020U, 0x00021U, 0x00022U, 0x00023U, 0x00024U, 0x00025U, 0x00026U, + 0x24008U, 0x00028U, 0x00029U, 0x0002AU, 0x24004U, 0x0002CU, 0x24002U, 0x24001U, 0x24000U, 0x00030U, 0x00031U, 0x00032U, 0x08180U, + 0x00034U, 0x00C40U, 0x00036U, 0x00C42U, 0x00038U, 0x43000U, 0x0003AU, 0x43002U, 0x02902U, 0x24012U, 0x02900U, 0x24010U, 0x00040U, + 0x00041U, 0x00042U, 0x00043U, 0x00044U, 0x00045U, 0x00046U, 0x00047U, 0x00048U, 0x00049U, 0x0004AU, 0x02500U, 0x0004CU, 0x0004DU, + 0x48010U, 0x48011U, 0x00050U, 0x00051U, 0x00052U, 0x21200U, 0x00054U, 0x00C20U, 0x48008U, 0x48009U, 0x00058U, 0x00059U, 0x48004U, + 0x48005U, 0x48002U, 0x48003U, 0x48000U, 0x48001U, 0x00060U, 0x00061U, 0x00062U, 0x00063U, 0x00064U, 0x00C10U, 0x10300U, 0x0B000U, + 0x00068U, 0x00069U, 0x01880U, 0x01881U, 0x40181U, 0x40180U, 0x24041U, 0x24040U, 0x00070U, 0x00C04U, 0x00072U, 0x00C06U, 0x00C01U, + 0x00C00U, 0x00C03U, 0x00C02U, 0x05204U, 0x00C0CU, 0x48024U, 0x48025U, 0x05200U, 0x00C08U, 0x48020U, 0x48021U, 0x00080U, 0x00081U, + 0x00082U, 0x00083U, 0x00084U, 0x00085U, 0x00086U, 0x00087U, 0x00088U, 0x00089U, 0x0008AU, 0x50200U, 0x0008CU, 0x0A800U, 0x01411U, + 0x01410U, 0x00090U, 0x00091U, 0x00092U, 0x08120U, 0x00094U, 0x00095U, 0x04A00U, 0x01408U, 0x00098U, 0x00099U, 0x01405U, 0x01404U, + 0x01403U, 0x01402U, 0x01401U, 0x01400U, 0x000A0U, 0x000A1U, 0x000A2U, 0x08110U, 0x000A4U, 0x000A5U, 0x42400U, 0x42401U, 0x000A8U, + 0x000A9U, 0x01840U, 0x01841U, 0x40141U, 0x40140U, 0x24081U, 0x24080U, 0x000B0U, 0x08102U, 0x08101U, 0x08100U, 0x000B4U, 0x08106U, + 0x08105U, 0x08104U, 0x20A01U, 0x20A00U, 0x08109U, 0x08108U, 0x01423U, 0x01422U, 0x01421U, 0x01420U, 0x000C0U, 0x000C1U, 0x000C2U, + 0x000C3U, 0x000C4U, 0x000C5U, 0x000C6U, 0x000C7U, 0x000C8U, 0x000C9U, 0x01820U, 0x01821U, 0x20600U, 0x40120U, 0x16000U, 0x16001U, + 0x000D0U, 0x000D1U, 0x42801U, 0x42800U, 0x03100U, 0x18200U, 0x03102U, 0x18202U, 0x000D8U, 0x000D9U, 0x48084U, 0x01444U, 0x48082U, + 0x01442U, 0x48080U, 0x01440U, 0x000E0U, 0x32000U, 0x01808U, 0x04600U, 0x40109U, 0x40108U, 0x0180CU, 0x4010AU, 0x01802U, 0x40104U, + 0x01800U, 0x01801U, 0x40101U, 0x40100U, 0x01804U, 0x40102U, 0x0A408U, 0x08142U, 0x08141U, 0x08140U, 0x00C81U, 0x00C80U, 0x00C83U, + 0x00C82U, 0x0A400U, 0x0A401U, 0x01810U, 0x01811U, 0x40111U, 0x40110U, 0x01814U, 0x40112U, 0x00100U, 0x00101U, 0x00102U, 0x00103U, + 0x00104U, 0x00105U, 0x00106U, 0x41800U, 0x00108U, 0x00109U, 0x0010AU, 0x02440U, 0x0010CU, 0x0010DU, 0x0010EU, 0x02444U, 0x00110U, + 0x00111U, 0x00112U, 0x080A0U, 0x00114U, 0x00115U, 0x00116U, 0x080A4U, 0x00118U, 0x00119U, 0x15000U, 0x15001U, 0x02822U, 0x02823U, + 0x02820U, 0x02821U, 0x00120U, 0x00121U, 0x00122U, 0x08090U, 0x00124U, 0x00125U, 0x10240U, 0x10241U, 0x00128U, 0x00129U, 0x0012AU, + 0x24104U, 0x09400U, 0x400C0U, 0x02810U, 0x24100U, 0x00130U, 0x08082U, 0x08081U, 0x08080U, 0x31001U, 0x31000U, 0x02808U, 0x08084U, + 0x02806U, 0x0808AU, 0x02804U, 0x08088U, 0x02802U, 0x02803U, 0x02800U, 0x02801U, 0x00140U, 0x00141U, 0x00142U, 0x02408U, 0x00144U, + 0x00145U, 0x10220U, 0x10221U, 0x00148U, 0x02402U, 0x02401U, 0x02400U, 0x400A1U, 0x400A0U, 0x02405U, 0x02404U, 0x00150U, 0x00151U, + 0x00152U, 0x02418U, 0x03080U, 0x03081U, 0x03082U, 0x03083U, 0x09801U, 0x09800U, 0x02411U, 0x02410U, 0x48102U, 0x09804U, 0x48100U, + 0x48101U, 0x00160U, 0x00161U, 0x10204U, 0x10205U, 0x10202U, 0x40088U, 0x10200U, 0x10201U, 0x40085U, 0x40084U, 0x02421U, 0x02420U, + 0x40081U, 0x40080U, 0x10208U, 0x40082U, 0x41402U, 0x080C2U, 0x41400U, 0x080C0U, 0x00D01U, 0x00D00U, 0x10210U, 0x10211U, 0x40095U, + 0x40094U, 0x02844U, 0x080C8U, 0x40091U, 0x40090U, 0x02840U, 0x02841U, 0x00180U, 0x00181U, 0x00182U, 0x08030U, 0x00184U, 0x14400U, + 0x22201U, 0x22200U, 0x00188U, 0x00189U, 0x0018AU, 0x08038U, 0x40061U, 0x40060U, 0x40063U, 0x40062U, 0x00190U, 0x08022U, 0x08021U, + 0x08020U, 0x03040U, 0x03041U, 0x08025U, 0x08024U, 0x40C00U, 0x40C01U, 0x08029U, 0x08028U, 0x2C000U, 0x2C001U, 0x01501U, 0x01500U, + 0x001A0U, 0x08012U, 0x08011U, 0x08010U, 0x40049U, 0x40048U, 0x08015U, 0x08014U, 0x06200U, 0x40044U, 0x30400U, 0x08018U, 0x40041U, + 0x40040U, 0x40043U, 0x40042U, 0x08003U, 0x08002U, 0x08001U, 0x08000U, 0x08007U, 0x08006U, 0x08005U, 0x08004U, 0x0800BU, 0x0800AU, + 0x08009U, 0x08008U, 0x40051U, 0x40050U, 0x02880U, 0x0800CU, 0x001C0U, 0x001C1U, 0x64000U, 0x64001U, 0x03010U, 0x40028U, 0x08C00U, + 0x08C01U, 0x40025U, 0x40024U, 0x02481U, 0x02480U, 0x40021U, 0x40020U, 0x40023U, 0x40022U, 0x03004U, 0x03005U, 0x08061U, 0x08060U, + 0x03000U, 0x03001U, 0x03002U, 0x03003U, 0x0300CU, 0x40034U, 0x30805U, 0x30804U, 0x03008U, 0x40030U, 0x30801U, 0x30800U, 0x4000DU, + 0x4000CU, 0x08051U, 0x08050U, 0x40009U, 0x40008U, 0x10280U, 0x4000AU, 0x40005U, 0x40004U, 0x01900U, 0x40006U, 0x40001U, 0x40000U, + 0x40003U, 0x40002U, 0x14800U, 0x08042U, 0x08041U, 0x08040U, 0x03020U, 0x40018U, 0x08045U, 0x08044U, 0x40015U, 0x40014U, 0x08049U, + 0x08048U, 0x40011U, 0x40010U, 0x40013U, 0x40012U, 0x00200U, 0x00201U, 0x00202U, 0x00203U, 0x00204U, 0x00205U, 0x00206U, 0x00207U, + 0x00208U, 0x00209U, 0x0020AU, 0x50080U, 0x0020CU, 0x0020DU, 0x0020EU, 0x50084U, 0x00210U, 0x00211U, 0x00212U, 0x21040U, 0x00214U, + 0x00215U, 0x04880U, 0x04881U, 0x00218U, 0x00219U, 0x0E001U, 0x0E000U, 0x0021CU, 0x0021DU, 0x04888U, 0x0E004U, 0x00220U, 0x00221U, + 0x00222U, 0x00223U, 0x00224U, 0x00225U, 0x10140U, 0x10141U, 0x00228U, 0x00229U, 0x0022AU, 0x24204U, 0x12401U, 0x12400U, 0x24201U, + 0x24200U, 0x00230U, 0x00231U, 0x00232U, 0x21060U, 0x2A000U, 0x2A001U, 0x2A002U, 0x2A003U, 0x20881U, 0x20880U, 0x20883U, 0x20882U, + 0x05040U, 0x05041U, 0x05042U, 0x24210U, 0x00240U, 0x00241U, 0x00242U, 0x21010U, 0x00244U, 0x46000U, 0x10120U, 0x10121U, 0x00248U, + 0x00249U, 0x0024AU, 0x21018U, 0x20480U, 0x20481U, 0x20482U, 0x20483U, 0x00250U, 0x21002U, 0x21001U, 0x21000U, 0x18081U, 0x18080U, + 0x21005U, 0x21004U, 0x12800U, 0x12801U, 0x21009U, 0x21008U, 0x05020U, 0x05021U, 0x48200U, 0x48201U, 0x00260U, 0x00261U, 0x10104U, + 0x04480U, 0x10102U, 0x10103U, 0x10100U, 0x10101U, 0x62002U, 0x62003U, 0x62000U, 0x62001U, 0x05010U, 0x05011U, 0x10108U, 0x10109U, + 0x0500CU, 0x21022U, 0x21021U, 0x21020U, 0x05008U, 0x00E00U, 0x10110U, 0x10111U, 0x05004U, 0x05005U, 0x05006U, 0x21028U, 0x05000U, + 0x05001U, 0x05002U, 0x05003U, 0x00280U, 0x00281U, 0x00282U, 0x50008U, 0x00284U, 0x00285U, 0x04810U, 0x22100U, 0x00288U, 0x50002U, + 0x50001U, 0x50000U, 0x20440U, 0x20441U, 0x50005U, 0x50004U, 0x00290U, 0x00291U, 0x04804U, 0x04805U, 0x04802U, 0x18040U, 0x04800U, + 0x04801U, 0x20821U, 0x20820U, 0x50011U, 0x50010U, 0x0480AU, 0x01602U, 0x04808U, 0x01600U, 0x002A0U, 0x002A1U, 0x04441U, 0x04440U, + 0x002A4U, 0x002A5U, 0x04830U, 0x04444U, 0x06100U, 0x20810U, 0x50021U, 0x50020U, 0x06104U, 0x20814U, 0x50025U, 0x50024U, 0x20809U, + 0x20808U, 0x13000U, 0x08300U, 0x04822U, 0x2080CU, 0x04820U, 0x04821U, 0x20801U, 0x20800U, 0x20803U, 0x20802U, 0x20805U, 0x20804U, + 0x04828U, 0x20806U, 0x002C0U, 0x002C1U, 0x04421U, 0x04420U, 0x20408U, 0x18010U, 0x2040AU, 0x18012U, 0x20404U, 0x20405U, 0x50041U, + 0x50040U, 0x20400U, 0x20401U, 0x20402U, 0x20403U, 0x18005U, 0x18004U, 0x21081U, 0x21080U, 0x18001U, 0x18000U, 0x04840U, 0x18002U, + 0x20414U, 0x1800CU, 0x21089U, 0x21088U, 0x20410U, 0x18008U, 0x20412U, 0x1800AU, 0x04403U, 0x04402U, 0x04401U, 0x04400U, 0x10182U, + 0x04406U, 0x10180U, 0x04404U, 0x01A02U, 0x0440AU, 0x01A00U, 0x04408U, 0x20420U, 0x40300U, 0x20422U, 0x40302U, 0x04413U, 0x04412U, + 0x04411U, 0x04410U, 0x18021U, 0x18020U, 0x10190U, 0x18022U, 0x20841U, 0x20840U, 0x01A10U, 0x20842U, 0x05080U, 0x05081U, 0x05082U, + 0x05083U, 0x00300U, 0x00301U, 0x00302U, 0x00303U, 0x00304U, 0x00305U, 0x10060U, 0x22080U, 0x00308U, 0x00309U, 0x28800U, 0x28801U, + 0x44402U, 0x44403U, 0x44400U, 0x44401U, 0x00310U, 0x00311U, 0x10C01U, 0x10C00U, 0x00314U, 0x00315U, 0x10070U, 0x10C04U, 0x00318U, + 0x00319U, 0x28810U, 0x10C08U, 0x44412U, 0x00000U, 0x44410U, 0x44411U, 0x00320U, 0x60400U, 0x10044U, 0x10045U, 0x10042U, 0x0C800U, + 0x10040U, 0x10041U, 0x06080U, 0x06081U, 0x06082U, 0x06083U, 0x1004AU, 0x0C808U, 0x10048U, 0x10049U, 0x58008U, 0x08282U, 0x08281U, + 0x08280U, 0x10052U, 0x0C810U, 0x10050U, 0x10051U, 0x58000U, 0x58001U, 0x58002U, 0x08288U, 0x02A02U, 0x02A03U, 0x02A00U, 0x02A01U, + 0x00340U, 0x00341U, 0x10024U, 0x10025U, 0x10022U, 0x10023U, 0x10020U, 0x10021U, 0x34001U, 0x34000U, 0x02601U, 0x02600U, 0x1002AU, + 0x34004U, 0x10028U, 0x10029U, 0x0C400U, 0x0C401U, 0x21101U, 0x21100U, 0x60800U, 0x60801U, 0x10030U, 0x10031U, 0x0C408U, 0x34010U, + 0x21109U, 0x21108U, 0x60808U, 0x60809U, 0x10038U, 0x28420U, 0x10006U, 0x10007U, 0x10004U, 0x10005U, 0x10002U, 0x10003U, 0x10000U, + 0x10001U, 0x1000EU, 0x40284U, 0x1000CU, 0x1000DU, 0x1000AU, 0x40280U, 0x10008U, 0x10009U, 0x10016U, 0x10017U, 0x10014U, 0x10015U, + 0x10012U, 0x10013U, 0x10010U, 0x10011U, 0x05104U, 0x44802U, 0x44801U, 0x44800U, 0x05100U, 0x05101U, 0x10018U, 0x28400U, 0x00380U, + 0x00381U, 0x22005U, 0x22004U, 0x22003U, 0x22002U, 0x22001U, 0x22000U, 0x06020U, 0x06021U, 0x50101U, 0x50100U, 0x11800U, 0x11801U, + 0x22009U, 0x22008U, 0x45001U, 0x45000U, 0x08221U, 0x08220U, 0x04902U, 0x22012U, 0x04900U, 0x22010U, 0x06030U, 0x45008U, 0x08229U, + 0x08228U, 0x11810U, 0x11811U, 0x04908U, 0x22018U, 0x06008U, 0x06009U, 0x08211U, 0x08210U, 0x100C2U, 0x22022U, 0x100C0U, 0x22020U, + 0x06000U, 0x06001U, 0x06002U, 0x06003U, 0x06004U, 0x40240U, 0x06006U, 0x40242U, 0x08203U, 0x08202U, 0x08201U, 0x08200U, 0x08207U, + 0x08206U, 0x08205U, 0x08204U, 0x06010U, 0x20900U, 0x08209U, 0x08208U, 0x61002U, 0x20904U, 0x61000U, 0x61001U, 0x29020U, 0x29021U, + 0x100A4U, 0x22044U, 0x100A2U, 0x22042U, 0x100A0U, 0x22040U, 0x20504U, 0x40224U, 0x0D005U, 0x0D004U, 0x20500U, 0x40220U, 0x0D001U, + 0x0D000U, 0x03204U, 0x18104U, 0x08261U, 0x08260U, 0x03200U, 0x18100U, 0x03202U, 0x18102U, 0x11421U, 0x11420U, 0x00000U, 0x11422U, + 0x03208U, 0x18108U, 0x0D011U, 0x0D010U, 0x29000U, 0x29001U, 0x10084U, 0x04500U, 0x10082U, 0x40208U, 0x10080U, 0x10081U, 0x06040U, + 0x40204U, 0x06042U, 0x40206U, 0x40201U, 0x40200U, 0x10088U, 0x40202U, 0x29010U, 0x08242U, 0x08241U, 0x08240U, 0x10092U, 0x40218U, + 0x10090U, 0x10091U, 0x11401U, 0x11400U, 0x11403U, 0x11402U, 0x40211U, 0x40210U, 0x10098U, 0x40212U, 0x00400U, 0x00401U, 0x00402U, + 0x00403U, 0x00404U, 0x00405U, 0x00406U, 0x00407U, 0x00408U, 0x00409U, 0x0040AU, 0x02140U, 0x0040CU, 0x0040DU, 0x01091U, 0x01090U, + 0x00410U, 0x00411U, 0x00412U, 0x00413U, 0x00414U, 0x00860U, 0x01089U, 0x01088U, 0x00418U, 0x38000U, 0x01085U, 0x01084U, 0x01083U, + 0x01082U, 0x01081U, 0x01080U, 0x00420U, 0x00421U, 0x00422U, 0x00423U, 0x00424U, 0x00850U, 0x42080U, 0x42081U, 0x00428U, 0x00429U, + 0x48801U, 0x48800U, 0x09100U, 0x12200U, 0x24401U, 0x24400U, 0x00430U, 0x00844U, 0x00432U, 0x00846U, 0x00841U, 0x00840U, 0x1C000U, + 0x00842U, 0x00438U, 0x0084CU, 0x010A5U, 0x010A4U, 0x00849U, 0x00848U, 0x010A1U, 0x010A0U, 0x00440U, 0x00441U, 0x00442U, 0x02108U, + 0x00444U, 0x00830U, 0x70001U, 0x70000U, 0x00448U, 0x02102U, 0x02101U, 0x02100U, 0x20280U, 0x20281U, 0x02105U, 0x02104U, 0x00450U, + 0x00824U, 0x00452U, 0x00826U, 0x00821U, 0x00820U, 0x00823U, 0x00822U, 0x24802U, 0x02112U, 0x24800U, 0x02110U, 0x00829U, 0x00828U, + 0x48400U, 0x010C0U, 0x00460U, 0x00814U, 0x04281U, 0x04280U, 0x00811U, 0x00810U, 0x00813U, 0x00812U, 0x54000U, 0x54001U, 0x02121U, + 0x02120U, 0x00819U, 0x00818U, 0x0081BU, 0x0081AU, 0x00805U, 0x00804U, 0x41100U, 0x00806U, 0x00801U, 0x00800U, 0x00803U, 0x00802U, + 0x0A080U, 0x0080CU, 0x0A082U, 0x0080EU, 0x00809U, 0x00808U, 0x0080BU, 0x0080AU, 0x00480U, 0x00481U, 0x00482U, 0x00483U, 0x00484U, + 0x14100U, 0x42020U, 0x01018U, 0x00488U, 0x00489U, 0x01015U, 0x01014U, 0x20240U, 0x01012U, 0x01011U, 0x01010U, 0x00490U, 0x00491U, + 0x0100DU, 0x0100CU, 0x0100BU, 0x0100AU, 0x01009U, 0x01008U, 0x40900U, 0x01006U, 0x01005U, 0x01004U, 0x01003U, 0x01002U, 0x01001U, + 0x01000U, 0x004A0U, 0x004A1U, 0x42004U, 0x04240U, 0x42002U, 0x42003U, 0x42000U, 0x42001U, 0x30102U, 0x30103U, 0x30100U, 0x30101U, + 0x4200AU, 0x01032U, 0x42008U, 0x01030U, 0x25000U, 0x25001U, 0x08501U, 0x08500U, 0x008C1U, 0x008C0U, 0x42010U, 0x01028U, 0x0A040U, + 0x0A041U, 0x01025U, 0x01024U, 0x01023U, 0x01022U, 0x01021U, 0x01020U, 0x004C0U, 0x49000U, 0x04221U, 0x04220U, 0x20208U, 0x20209U, + 0x08900U, 0x08901U, 0x20204U, 0x20205U, 0x02181U, 0x02180U, 0x20200U, 0x20201U, 0x20202U, 0x01050U, 0x0A028U, 0x008A4U, 0x0104DU, + 0x0104CU, 0x008A1U, 0x008A0U, 0x01049U, 0x01048U, 0x0A020U, 0x0A021U, 0x01045U, 0x01044U, 0x20210U, 0x01042U, 0x01041U, 0x01040U, + 0x04203U, 0x04202U, 0x04201U, 0x04200U, 0x00891U, 0x00890U, 0x42040U, 0x04204U, 0x0A010U, 0x0A011U, 0x01C00U, 0x04208U, 0x20220U, + 0x40500U, 0x20222U, 0x40502U, 0x0A008U, 0x00884U, 0x04211U, 0x04210U, 0x00881U, 0x00880U, 0x00883U, 0x00882U, 0x0A000U, 0x0A001U, + 0x0A002U, 0x0A003U, 0x0A004U, 0x00888U, 0x01061U, 0x01060U, 0x00500U, 0x00501U, 0x00502U, 0x02048U, 0x00504U, 0x14080U, 0x00506U, + 0x14082U, 0x00508U, 0x02042U, 0x02041U, 0x02040U, 0x09020U, 0x09021U, 0x44200U, 0x02044U, 0x00510U, 0x00511U, 0x10A01U, 0x10A00U, + 0x4A001U, 0x4A000U, 0x4A003U, 0x4A002U, 0x40880U, 0x40881U, 0x02051U, 0x02050U, 0x40884U, 0x01182U, 0x01181U, 0x01180U, 0x00520U, + 0x60200U, 0x00522U, 0x60202U, 0x09008U, 0x09009U, 0x0900AU, 0x0900BU, 0x09004U, 0x09005U, 0x30080U, 0x02060U, 0x09000U, 0x09001U, + 0x09002U, 0x09003U, 0x41042U, 0x08482U, 0x41040U, 0x08480U, 0x00941U, 0x00940U, 0x41044U, 0x00942U, 0x09014U, 0x09015U, 0x02C04U, + 0x08488U, 0x09010U, 0x09011U, 0x02C00U, 0x02C01U, 0x00540U, 0x0200AU, 0x02009U, 0x02008U, 0x08882U, 0x0200EU, 0x08880U, 0x0200CU, + 0x02003U, 0x02002U, 0x02001U, 0x02000U, 0x02007U, 0x02006U, 0x02005U, 0x02004U, 0x0C200U, 0x0C201U, 0x41020U, 0x02018U, 0x00921U, + 0x00920U, 0x41024U, 0x00922U, 0x02013U, 0x02012U, 0x02011U, 0x02010U, 0x02017U, 0x02016U, 0x02015U, 0x02014U, 0x41012U, 0x0202AU, + 0x41010U, 0x02028U, 0x26000U, 0x00910U, 0x10600U, 0x10601U, 0x02023U, 0x02022U, 0x02021U, 0x02020U, 0x09040U, 0x40480U, 0x02025U, + 0x02024U, 0x41002U, 0x00904U, 0x41000U, 0x41001U, 0x00901U, 0x00900U, 0x41004U, 0x00902U, 0x4100AU, 0x02032U, 0x41008U, 0x02030U, + 0x00909U, 0x00908U, 0x28201U, 0x28200U, 0x00580U, 0x14004U, 0x00582U, 0x14006U, 0x14001U, 0x14000U, 0x08840U, 0x14002U, 0x40810U, + 0x40811U, 0x30020U, 0x020C0U, 0x14009U, 0x14008U, 0x01111U, 0x01110U, 0x40808U, 0x40809U, 0x08421U, 0x08420U, 0x14011U, 0x14010U, + 0x01109U, 0x01108U, 0x40800U, 0x40801U, 0x40802U, 0x01104U, 0x40804U, 0x01102U, 0x01101U, 0x01100U, 0x03801U, 0x03800U, 0x30008U, + 0x08410U, 0x14021U, 0x14020U, 0x42100U, 0x42101U, 0x30002U, 0x30003U, 0x30000U, 0x30001U, 0x09080U, 0x40440U, 0x30004U, 0x30005U, + 0x08403U, 0x08402U, 0x08401U, 0x08400U, 0x08407U, 0x08406U, 0x08405U, 0x08404U, 0x40820U, 0x40821U, 0x30010U, 0x08408U, 0x40824U, + 0x01122U, 0x01121U, 0x01120U, 0x08806U, 0x0208AU, 0x08804U, 0x02088U, 0x08802U, 0x14040U, 0x08800U, 0x08801U, 0x02083U, 0x02082U, + 0x02081U, 0x02080U, 0x20300U, 0x40420U, 0x08808U, 0x02084U, 0x03404U, 0x03405U, 0x08814U, 0x02098U, 0x03400U, 0x03401U, 0x08810U, + 0x08811U, 0x40840U, 0x40841U, 0x02091U, 0x02090U, 0x40844U, 0x01142U, 0x01141U, 0x01140U, 0x04303U, 0x04302U, 0x04301U, 0x04300U, + 0x40409U, 0x40408U, 0x08820U, 0x08821U, 0x40405U, 0x40404U, 0x30040U, 0x020A0U, 0x40401U, 0x40400U, 0x40403U, 0x40402U, 0x41082U, + 0x08442U, 0x41080U, 0x08440U, 0x00981U, 0x00980U, 0x41084U, 0x00982U, 0x0A100U, 0x11200U, 0x0A102U, 0x11202U, 0x40411U, 0x40410U, + 0x40413U, 0x40412U, 0x00600U, 0x00601U, 0x00602U, 0x00603U, 0x00604U, 0x00605U, 0x00606U, 0x00607U, 0x00608U, 0x05800U, 0x0060AU, + 0x05802U, 0x200C0U, 0x12020U, 0x44100U, 0x44101U, 0x00610U, 0x00611U, 0x10901U, 0x10900U, 0x51000U, 0x51001U, 0x51002U, 0x10904U, + 0x00618U, 0x05810U, 0x01285U, 0x01284U, 0x51008U, 0x01282U, 0x01281U, 0x01280U, 0x00620U, 0x60100U, 0x040C1U, 0x040C0U, 0x12009U, + 0x12008U, 0x21800U, 0x21801U, 0x12005U, 0x12004U, 0x12007U, 0x12006U, 0x12001U, 0x12000U, 0x12003U, 0x12002U, 0x00630U, 0x00A44U, + 0x040D1U, 0x040D0U, 0x00A41U, 0x00A40U, 0x21810U, 0x00A42U, 0x12015U, 0x12014U, 0x00000U, 0x12016U, 0x12011U, 0x12010U, 0x12013U, + 0x12012U, 0x00640U, 0x00641U, 0x040A1U, 0x040A0U, 0x20088U, 0x20089U, 0x2008AU, 0x040A4U, 0x20084U, 0x20085U, 0x19000U, 0x02300U, + 0x20080U, 0x20081U, 0x20082U, 0x20083U, 0x0C100U, 0x0C101U, 0x21401U, 0x21400U, 0x00A21U, 0x00A20U, 0x00A23U, 0x00A22U, 0x20094U, + 0x20095U, 0x19010U, 0x21408U, 0x20090U, 0x20091U, 0x20092U, 0x28120U, 0x04083U, 0x04082U, 0x04081U, 0x04080U, 0x00A11U, 0x00A10U, + 0x10500U, 0x04084U, 0x200A4U, 0x0408AU, 0x04089U, 0x04088U, 0x200A0U, 0x12040U, 0x200A2U, 0x12042U, 0x00A05U, 0x00A04U, 0x04091U, + 0x04090U, 0x00A01U, 0x00A00U, 0x00A03U, 0x00A02U, 0x05404U, 0x00A0CU, 0x28105U, 0x28104U, 0x05400U, 0x00A08U, 0x28101U, 0x28100U, + 0x00680U, 0x00681U, 0x04061U, 0x04060U, 0x20048U, 0x20049U, 0x2004AU, 0x04064U, 0x20044U, 0x20045U, 0x50401U, 0x50400U, 0x20040U, + 0x20041U, 0x20042U, 0x01210U, 0x68002U, 0x68003U, 0x68000U, 0x68001U, 0x04C02U, 0x0120AU, 0x04C00U, 0x01208U, 0x20054U, 0x01206U, + 0x01205U, 0x01204U, 0x20050U, 0x01202U, 0x01201U, 0x01200U, 0x18800U, 0x04042U, 0x04041U, 0x04040U, 0x42202U, 0x04046U, 0x42200U, + 0x04044U, 0x20064U, 0x0404AU, 0x04049U, 0x04048U, 0x20060U, 0x12080U, 0x20062U, 0x12082U, 0x18810U, 0x04052U, 0x04051U, 0x04050U, + 0x4C009U, 0x4C008U, 0x42210U, 0x04054U, 0x20C01U, 0x20C00U, 0x20C03U, 0x20C02U, 0x4C001U, 0x4C000U, 0x01221U, 0x01220U, 0x2000CU, + 0x04022U, 0x04021U, 0x04020U, 0x20008U, 0x20009U, 0x2000AU, 0x04024U, 0x20004U, 0x20005U, 0x20006U, 0x04028U, 0x20000U, 0x20001U, + 0x20002U, 0x20003U, 0x2001CU, 0x04032U, 0x04031U, 0x04030U, 0x20018U, 0x18400U, 0x2001AU, 0x18402U, 0x20014U, 0x20015U, 0x20016U, + 0x01244U, 0x20010U, 0x20011U, 0x20012U, 0x01240U, 0x04003U, 0x04002U, 0x04001U, 0x04000U, 0x20028U, 0x04006U, 0x04005U, 0x04004U, + 0x20024U, 0x0400AU, 0x04009U, 0x04008U, 0x20020U, 0x20021U, 0x20022U, 0x0400CU, 0x04013U, 0x04012U, 0x04011U, 0x04010U, 0x00A81U, + 0x00A80U, 0x04015U, 0x04014U, 0x0A200U, 0x11100U, 0x04019U, 0x04018U, 0x20030U, 0x20031U, 0x50800U, 0x50801U, 0x00700U, 0x60020U, + 0x10811U, 0x10810U, 0x4400AU, 0x60024U, 0x44008U, 0x44009U, 0x44006U, 0x02242U, 0x44004U, 0x02240U, 0x44002U, 0x44003U, 0x44000U, + 0x44001U, 0x0C040U, 0x10802U, 0x10801U, 0x10800U, 0x0C044U, 0x10806U, 0x10805U, 0x10804U, 0x23000U, 0x23001U, 0x10809U, 0x10808U, + 0x44012U, 0x44013U, 0x44010U, 0x44011U, 0x60001U, 0x60000U, 0x60003U, 0x60002U, 0x60005U, 0x60004U, 0x10440U, 0x10441U, 0x60009U, + 0x60008U, 0x44024U, 0x6000AU, 0x09200U, 0x12100U, 0x44020U, 0x44021U, 0x60011U, 0x60010U, 0x10821U, 0x10820U, 0x07003U, 0x07002U, + 0x07001U, 0x07000U, 0x23020U, 0x60018U, 0x28045U, 0x28044U, 0x09210U, 0x28042U, 0x28041U, 0x28040U, 0x0C010U, 0x0C011U, 0x02209U, + 0x02208U, 0x10422U, 0x10423U, 0x10420U, 0x10421U, 0x02203U, 0x02202U, 0x02201U, 0x02200U, 0x20180U, 0x20181U, 0x44040U, 0x02204U, + 0x0C000U, 0x0C001U, 0x0C002U, 0x10840U, 0x0C004U, 0x0C005U, 0x0C006U, 0x10844U, 0x0C008U, 0x0C009U, 0x02211U, 0x02210U, 0x0C00CU, + 0x28022U, 0x28021U, 0x28020U, 0x60041U, 0x60040U, 0x10404U, 0x04180U, 0x10402U, 0x10403U, 0x10400U, 0x10401U, 0x02223U, 0x02222U, + 0x02221U, 0x02220U, 0x1040AU, 0x28012U, 0x10408U, 0x28010U, 0x0C020U, 0x0C021U, 0x41200U, 0x41201U, 0x00B01U, 0x00B00U, 0x10410U, + 0x28008U, 0x11081U, 0x11080U, 0x28005U, 0x28004U, 0x28003U, 0x28002U, 0x28001U, 0x28000U, 0x52040U, 0x14204U, 0x22405U, 0x22404U, + 0x14201U, 0x14200U, 0x22401U, 0x22400U, 0x20144U, 0x20145U, 0x44084U, 0x022C0U, 0x20140U, 0x20141U, 0x44080U, 0x44081U, 0x40A08U, + 0x10882U, 0x10881U, 0x10880U, 0x14211U, 0x14210U, 0x1A008U, 0x10884U, 0x40A00U, 0x40A01U, 0x40A02U, 0x01304U, 0x1A002U, 0x01302U, + 0x1A000U, 0x01300U, 0x60081U, 0x60080U, 0x04141U, 0x04140U, 0x60085U, 0x60084U, 0x104C0U, 0x04144U, 0x06400U, 0x06401U, 0x30200U, + 0x30201U, 0x06404U, 0x40640U, 0x30204U, 0x30205U, 0x08603U, 0x08602U, 0x08601U, 0x08600U, 0x00000U, 0x08606U, 0x08605U, 0x08604U, + 0x11041U, 0x11040U, 0x30210U, 0x11042U, 0x11045U, 0x11044U, 0x1A020U, 0x01320U, 0x52000U, 0x52001U, 0x04121U, 0x04120U, 0x20108U, + 0x20109U, 0x08A00U, 0x08A01U, 0x20104U, 0x20105U, 0x02281U, 0x02280U, 0x20100U, 0x20101U, 0x20102U, 0x20103U, 0x0C080U, 0x0C081U, + 0x0C082U, 0x04130U, 0x0C084U, 0x06808U, 0x08A10U, 0x08A11U, 0x11021U, 0x11020U, 0x11023U, 0x11022U, 0x20110U, 0x06800U, 0x20112U, + 0x06802U, 0x04103U, 0x04102U, 0x04101U, 0x04100U, 0x10482U, 0x04106U, 0x10480U, 0x04104U, 0x11011U, 0x11010U, 0x04109U, 0x04108U, + 0x20120U, 0x40600U, 0x20122U, 0x40602U, 0x11009U, 0x11008U, 0x22800U, 0x04110U, 0x1100DU, 0x1100CU, 0x22804U, 0x04114U, 0x11001U, + 0x11000U, 0x11003U, 0x11002U, 0x11005U, 0x11004U, 0x28081U, 0x28080U}; + +#define X18 0x00040000 /* vector representation of X^{18} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK8 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +unsigned int CGolay2087::getSyndrome1987(unsigned int pattern) +/* + * Compute the syndrome corresponding to the given pattern, i.e., the + * remainder after dividing the pattern (when considering it as the vector + * representation of a polynomial) by the generator polynomial, GENPOL. + * In the program this pattern has several meanings: (1) pattern = infomation + * bits, when constructing the encoding table; (2) pattern = error pattern, + * when constructing the decoding table; and (3) pattern = received vector, to + * obtain its syndrome in decoding. + */ +{ + unsigned int aux = X18; + + if (pattern >= X11) { + while (pattern & MASK8) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} + +unsigned char CGolay2087::decode(const unsigned char* data) +{ + assert(data != NULL); + + unsigned int code = (data[0U] << 11) + (data[1U] << 3) + (data[2U] >> 5); + unsigned int syndrome = getSyndrome1987(code); + unsigned int error_pattern = DECODING_TABLE_1987[syndrome]; + + if (error_pattern != 0x00U) + code ^= error_pattern; + + return code >> 11; +} + +void CGolay2087::encode(unsigned char* data) +{ + assert(data != NULL); + + unsigned int value = data[0U]; + + unsigned int cksum = ENCODING_TABLE_2087[value]; + + data[1U] = cksum & 0xFFU; + data[2U] = cksum >> 8; +} diff --git a/cgolay2087.h b/cgolay2087.h new file mode 100644 index 0000000..22c5c16 --- /dev/null +++ b/cgolay2087.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Golay2087_H +#define Golay2087_H + +class CGolay2087 { +public: + static void encode(unsigned char* data); + + static unsigned char decode(const unsigned char* data); + +private: + static unsigned int getSyndrome1987(unsigned int pattern); +}; + +#endif diff --git a/chamming.cpp b/chamming.cpp new file mode 100644 index 0000000..8a9b5f8 --- /dev/null +++ b/chamming.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "chamming.h" + +#include +#include + +// Hamming (15,11,3) check a boolean data array +bool CHamming::decode15113_1(bool* d) +{ + assert(d != NULL); + + // Calculate the parity it should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9]; + bool c2 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10]; + bool c3 = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10]; + + unsigned char n = 0U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + + switch (n) + { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + + // Data bit errors + case 0x0FU: d[0] = !d[0]; return true; + case 0x07U: d[1] = !d[1]; return true; + case 0x0BU: d[2] = !d[2]; return true; + case 0x03U: d[3] = !d[3]; return true; + case 0x0DU: d[4] = !d[4]; return true; + case 0x05U: d[5] = !d[5]; return true; + case 0x09U: d[6] = !d[6]; return true; + case 0x0EU: d[7] = !d[7]; return true; + case 0x06U: d[8] = !d[8]; return true; + case 0x0AU: d[9] = !d[9]; return true; + case 0x0CU: d[10] = !d[10]; return true; + + // No bit errors + default: return false; + } +} + +void CHamming::encode15113_1(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6]; + d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9]; + d[13] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10]; + d[14] = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10]; +} + +// Hamming (15,11,3) check a boolean data array +bool CHamming::decode15113_2(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + + unsigned char n = 0x00U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + + // Data bit errors + case 0x09U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x0FU: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x05U: d[5] = !d[5]; return true; + case 0x0AU: d[6] = !d[6]; return true; + case 0x0DU: d[7] = !d[7]; return true; + case 0x03U: d[8] = !d[8]; return true; + case 0x06U: d[9] = !d[9]; return true; + case 0x0CU: d[10] = !d[10]; return true; + + // No bit errors + default: return false; + } +} + +void CHamming::encode15113_2(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; +} + +// Hamming (13,9,3) check a boolean data array +bool CHamming::decode1393(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7]; + bool c2 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c3 = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8]; + + unsigned char n = 0x00U; + n |= (c0 != d[9]) ? 0x01U : 0x00U; + n |= (c1 != d[10]) ? 0x02U : 0x00U; + n |= (c2 != d[11]) ? 0x04U : 0x00U; + n |= (c3 != d[12]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[9] = !d[9]; return true; + case 0x02U: d[10] = !d[10]; return true; + case 0x04U: d[11] = !d[11]; return true; + case 0x08U: d[12] = !d[12]; return true; + + // Data bit erros + case 0x0FU: d[0] = !d[0]; return true; + case 0x07U: d[1] = !d[1]; return true; + case 0x0EU: d[2] = !d[2]; return true; + case 0x05U: d[3] = !d[3]; return true; + case 0x0AU: d[4] = !d[4]; return true; + case 0x0DU: d[5] = !d[5]; return true; + case 0x03U: d[6] = !d[6]; return true; + case 0x06U: d[7] = !d[7]; return true; + case 0x0CU: d[8] = !d[8]; return true; + + // No bit errors + default: return false; + } +} + +void CHamming::encode1393(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + d[9] = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6]; + d[10] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7]; + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8]; +} + +// Hamming (10,6,3) check a boolean data array +bool CHamming::decode1063(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[5]; + bool c1 = d[0] ^ d[1] ^ d[3] ^ d[5]; + bool c2 = d[0] ^ d[2] ^ d[3] ^ d[4]; + bool c3 = d[1] ^ d[2] ^ d[3] ^ d[4]; + + unsigned char n = 0x00U; + n |= (c0 != d[6]) ? 0x01U : 0x00U; + n |= (c1 != d[7]) ? 0x02U : 0x00U; + n |= (c2 != d[8]) ? 0x04U : 0x00U; + n |= (c3 != d[9]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[6] = !d[6]; return true; + case 0x02U: d[7] = !d[7]; return true; + case 0x04U: d[8] = !d[8]; return true; + case 0x08U: d[9] = !d[9]; return true; + + // Data bit erros + case 0x07U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x0DU: d[2] = !d[2]; return true; + case 0x0EU: d[3] = !d[3]; return true; + case 0x0CU: d[4] = !d[4]; return true; + case 0x03U: d[5] = !d[5]; return true; + + // No bit errors + default: return false; + } +} + +void CHamming::encode1063(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + d[6] = d[0] ^ d[1] ^ d[2] ^ d[5]; + d[7] = d[0] ^ d[1] ^ d[3] ^ d[5]; + d[8] = d[0] ^ d[2] ^ d[3] ^ d[4]; + d[9] = d[1] ^ d[2] ^ d[3] ^ d[4]; +} + +// A Hamming (16,11,4) Check +bool CHamming::decode16114(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + bool c4 = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10]; + + // Compare these with the actual bits + unsigned char n = 0x00U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + n |= (c4 != d[15]) ? 0x10U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + case 0x10U: d[15] = !d[15]; return true; + + // Data bit errors + case 0x19U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x1FU: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x15U: d[5] = !d[5]; return true; + case 0x1AU: d[6] = !d[6]; return true; + case 0x0DU: d[7] = !d[7]; return true; + case 0x13U: d[8] = !d[8]; return true; + case 0x16U: d[9] = !d[9]; return true; + case 0x1CU: d[10] = !d[10]; return true; + + // No bit errors + case 0x00U: return true; + + // Unrecoverable errors + default: return false; + } +} + +void CHamming::encode16114(bool* d) +{ + assert(d != NULL); + + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + d[15] = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10]; +} + +// A Hamming (17,12,3) Check +bool CHamming::decode17123(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10]; + bool c2 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11]; + bool c3 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10]; + bool c4 = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11]; + + // Compare these with the actual bits + unsigned char n = 0x00U; + n |= (c0 != d[12]) ? 0x01U : 0x00U; + n |= (c1 != d[13]) ? 0x02U : 0x00U; + n |= (c2 != d[14]) ? 0x04U : 0x00U; + n |= (c3 != d[15]) ? 0x08U : 0x00U; + n |= (c4 != d[16]) ? 0x10U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[12] = !d[12]; return true; + case 0x02U: d[13] = !d[13]; return true; + case 0x04U: d[14] = !d[14]; return true; + case 0x08U: d[15] = !d[15]; return true; + case 0x10U: d[16] = !d[16]; return true; + + // Data bit errors + case 0x1BU: d[0] = !d[0]; return true; + case 0x1FU: d[1] = !d[1]; return true; + case 0x17U: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x1CU: d[5] = !d[5]; return true; + case 0x11U: d[6] = !d[6]; return true; + case 0x0BU: d[7] = !d[7]; return true; + case 0x16U: d[8] = !d[8]; return true; + case 0x05U: d[9] = !d[9]; return true; + case 0x0AU: d[10] = !d[10]; return true; + case 0x14U: d[11] = !d[11]; return true; + + // No bit errors + case 0x00U: return true; + + // Unrecoverable errors + default: return false; + } +} + +void CHamming::encode17123(bool* d) +{ + assert(d != NULL); + + d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9]; + d[13] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10]; + d[14] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11]; + d[15] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10]; + d[16] = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11]; +} diff --git a/chamming.h b/chamming.h new file mode 100644 index 0000000..bf6b626 --- /dev/null +++ b/chamming.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Hamming_H +#define Hamming_H + +class CHamming { +public: + static void encode15113_1(bool* d); + static bool decode15113_1(bool* d); + + static void encode15113_2(bool* d); + static bool decode15113_2(bool* d); + + static void encode1393(bool* d); + static bool decode1393(bool* d); + + static void encode1063(bool* d); + static bool decode1063(bool* d); + + static void encode16114(bool* d); + static bool decode16114(bool* d); + + static void encode17123(bool* d); + static bool decode17123(bool* d); +}; + +#endif diff --git a/codec.cpp b/codec.cpp new file mode 100644 index 0000000..ef0a31c --- /dev/null +++ b/codec.cpp @@ -0,0 +1,199 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ +#include "codec.h" +#include +#include + +#ifdef USE_FLITE +extern "C" { +extern cst_voice * register_cmu_us_slt(const char *); +extern cst_voice * register_cmu_us_kal16(const char *); +extern cst_voice * register_cmu_us_awb(const char *); +} +#endif + +Codec::Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + m_module(module), + m_hostname(hostname), + m_tx(false), + m_ttsid(0), + m_audioin(audioin), + m_audioout(audioout), + m_rxwatchdog(0), +#ifdef Q_OS_WIN + m_rxtimerint(19), +#else + m_rxtimerint(20), +#endif + m_txtimerint(19), + m_vocoder(vocoder), + m_modemport(modem), + m_modem(nullptr), + m_ambedev(nullptr), + m_hwrx(false), + m_hwtx(false), + m_ipv6(ipv6) +{ + m_modeinfo.callsign = callsign; + m_modeinfo.gwid = 0; + m_modeinfo.srcid = 0; + m_modeinfo.dstid = 0; + m_modeinfo.host = host; + m_modeinfo.port = port; + m_modeinfo.count = 0; + m_modeinfo.frame_number = 0; + m_modeinfo.frame_total = 0; + m_modeinfo.streamid = 0; + m_modeinfo.stream_state = STREAM_IDLE; + m_modeinfo.vocoder_loaded = false; +#ifdef USE_FLITE + flite_init(); + voice_slt = register_cmu_us_slt(nullptr); + voice_kal = register_cmu_us_kal16(nullptr); + voice_awb = register_cmu_us_awb(nullptr); +#endif +} + +Codec::~Codec() +{ +} + +void Codec::in_audio_vol_changed(qreal v) +{ + m_audio->set_input_volume(v); +} + +void Codec::out_audio_vol_changed(qreal v) +{ + m_audio->set_output_volume(v); +} + +void Codec::agc_state_changed(int s) +{ + qDebug() << "Codec::agc_state_changed() called s == " << s; + m_audio->set_agc(s); +} + +void Codec::send_connect() +{ + m_modeinfo.status = CONNECTING; + if(m_ipv6 && (m_modeinfo.host != "none")){ + qDebug() << "Host == " << m_modeinfo.host; + QList h; + QHostInfo i; + h.append(QHostAddress(m_modeinfo.host)); + i.setAddresses(h); + hostname_lookup(i); + } + else{ + QHostInfo::lookupHost(m_modeinfo.host, this, SLOT(hostname_lookup(QHostInfo))); + } +} + +void Codec::toggle_tx(bool tx) +{ + tx ? start_tx() : stop_tx(); +} + +void Codec::start_tx() +{ + if(m_hwtx){ + m_ambedev->clear_queue(); + } + m_txcodecq.clear(); + m_tx = true; + m_txcnt = 0; + m_ttscnt = 0; + m_rxtimer->stop(); + m_modeinfo.streamid = 0; + m_modeinfo.stream_state = TRANSMITTING; +#ifdef USE_FLITE + + if(m_ttsid == 1){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_kal); + } + else if(m_ttsid == 2){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_awb); + } + else if(m_ttsid == 3){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_slt); + } +#endif + if(!m_txtimer->isActive()){ + if(m_ttsid == 0){ + m_audio->set_input_buffer_size(640); + m_audio->start_capture(); + //audioin->start(&audio_buffer); + } + m_txtimer->start(m_txtimerint); + } +} + +void Codec::stop_tx() +{ + m_tx = false; +} + +bool Codec::load_vocoder_plugin() +{ + QString config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WIN) + config_path += "/dudetronics"; +#endif +#if defined(Q_OS_ANDROID) + QString voc = config_path + "/vocoder_plugin." + QSysInfo::productType() + "." + QSysInfo::currentCpuArchitecture(); +#else + QString voc = config_path + "/vocoder_plugin." + QSysInfo::kernelType() + "." + QSysInfo::currentCpuArchitecture(); +#endif + //QString voc = "/mnt/data/src/mbe_vocoder/vocoder_plugin.linux.x86_64.so"; + void* a = dlopen(voc.toLocal8Bit(), RTLD_LAZY); + if (!a) { + qDebug() << "Cannot load library: " << QString::fromLocal8Bit(dlerror()); + return false; + } + dlerror(); + + create_t* create_a = (create_t*) dlsym(a, "create"); + const char* dlsym_error = dlerror(); + + if (dlsym_error) { + qDebug() << "Cannot load symbol create: " << QString::fromLocal8Bit(dlsym_error); + return false; + } + + m_mbevocoder = create_a(); + qDebug() << voc + " loaded"; + return true; +} + +void Codec::deleteLater() +{ + if(m_modeinfo.status == CONNECTED_RW){ + m_udp->disconnect(); + m_ping_timer->stop(); + send_disconnect(); + delete m_audio; + if(m_hwtx){ + delete m_ambedev; + } + if(m_modem){ + delete m_modem; + } + } + m_modeinfo.count = 0; + QObject::deleteLater(); +} diff --git a/codec.h b/codec.h new file mode 100644 index 0000000..81f0207 --- /dev/null +++ b/codec.h @@ -0,0 +1,206 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef CODEC_H +#define CODEC_H + +#include +#include +#ifdef USE_FLITE +#include +#endif +#include +#include "vocoder_plugin.h" +#include "audioengine.h" +#include "serialambe.h" +#include "serialmodem.h" + +class Codec : public QObject +{ + Q_OBJECT +public: + Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~Codec(); + void set_modem_flags(bool rxInvert, bool txInvert, bool pttInvert, bool useCOSAsLockout, bool duplex) { m_rxInvert = rxInvert; m_txInvert = txInvert; m_pttInvert = pttInvert; m_useCOSAsLockout = useCOSAsLockout; m_duplex = duplex; } + void set_modem_params(uint32_t rxfreq, uint32_t txfreq, uint32_t txDelay, float rxLevel, float rfLevel, uint32_t ysfTXHang, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float pocsagTXLevel) + { + m_rxfreq = rxfreq; + m_txfreq = txfreq; + m_txDelay = txDelay; + m_rxLevel = rxLevel; + m_rfLevel = rfLevel; + m_ysfTXHang = ysfTXHang; + m_cwIdTXLevel = cwIdTXLevel; + m_dstarTXLevel = dstarTXLevel; + m_dmrTXLevel = dmrTXLevel; + m_ysfTXLevel = ysfTXLevel; + m_p25TXLevel = p25TXLevel; + m_nxdnTXLevel = nxdnTXLevel; + m_pocsagTXLevel = pocsagTXLevel; + } + bool get_hwrx() { return m_hwrx; } + bool get_hwtx() { return m_hwtx; } + void set_hostname(std::string); + void set_callsign(std::string); + void set_input_src(uint8_t s, QString t) { m_ttsid = s; m_ttstext = t; } + struct MODEINFO { + qint64 ts; + int status; + int stream_state; + QString callsign; + QString gw; + QString gw2; + QString src; + QString dst; + QString usertxt; + QString netmsg; + uint32_t gwid; + uint32_t srcid; + uint32_t dstid; + QString host; + int port; + bool path; + char type; + uint16_t frame_number; + uint8_t frame_total; + int count; + uint32_t streamid; + bool mode; + bool vocoder_loaded; + } m_modeinfo; + enum{ + DISCONNECTED, + CLOSED, + CONNECTING, + DMR_AUTH, + DMR_CONF, + DMR_OPTS, + CONNECTED_RW, + CONNECTED_RO + }; + enum{ + STREAM_NEW, + STREAMING, + STREAM_END, + STREAM_LOST, + STREAM_IDLE, + TRANSMITTING, + TRANSMITTING_MODEM, + STREAM_UNKNOWN + }; +signals: + void update(Codec::MODEINFO); + void update_output_level(unsigned short); +protected slots: + virtual void send_disconnect(){} + virtual void hostname_lookup(QHostInfo){} + void send_connect(); + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } + void start_tx(); + void stop_tx(); + void toggle_tx(bool); + void deleteLater(); + void in_audio_vol_changed(qreal); + void out_audio_vol_changed(qreal); + bool load_vocoder_plugin(); + void swrx_state_changed(int s) {m_hwrx = !s; } + void swtx_state_changed(int s) {m_hwtx = !s; } + void agc_state_changed(int s); + void mycall_changed(QString mc) { m_txmycall = mc; } + void urcall_changed(QString uc) { m_txurcall = uc; } + void rptr1_changed(QString r1) { m_txrptr1 = r1; } + void rptr2_changed(QString r2) { m_txrptr2 = r2; } + void module_changed(char m) { m_module = m; m_modeinfo.streamid = 0; qDebug() << "Codec::module_changed() m == " << m; } +protected: + QUdpSocket *m_udp = nullptr; + QHostAddress m_address; + char m_module; + QString m_hostname; + bool m_tx; + uint16_t m_txcnt; + uint16_t m_ttscnt; + uint8_t m_ttsid; + QString m_ttstext; + QString m_txmycall; + QString m_txurcall; + QString m_txrptr1; + QString m_txrptr2; +#ifdef USE_FLITE + cst_voice *voice_slt; + cst_voice *voice_kal; + cst_voice *voice_awb; + cst_wave *tts_audio; +#endif + QTimer *m_ping_timer; + QTimer *m_txtimer; + QTimer *m_rxtimer; + AudioEngine *m_audio; + QString m_audioin; + QString m_audioout; + uint32_t m_rxwatchdog; + uint8_t m_rxtimerint; + uint8_t m_txtimerint; + QQueue m_rxcodecq; + QQueue m_txcodecq; + QQueue m_rxmodemq; + imbe_vocoder vocoder; + Vocoder *m_mbevocoder; + QString m_vocoder; + QString m_modemport; + SerialModem *m_modem; + SerialAMBE *m_ambedev; + bool m_hwrx; + bool m_hwtx; + bool m_ipv6; + + uint32_t m_rxfreq; + uint32_t m_txfreq; + uint32_t m_dmrColorCode; + bool m_ysfLoDev; + uint32_t m_ysfTXHang; + uint32_t m_p25TXHang; + uint32_t m_nxdnTXHang; + bool m_duplex; + bool m_rxInvert; + bool m_txInvert; + bool m_pttInvert; + uint32_t m_txDelay; + uint32_t m_dmrDelay; + float m_rxLevel; + float m_rfLevel; + float m_cwIdTXLevel; + float m_dstarTXLevel; + float m_dmrTXLevel; + float m_ysfTXLevel; + float m_p25TXLevel; + float m_nxdnTXLevel; + float m_pocsagTXLevel; + float m_fmTXLevel; + bool m_debug; + bool m_useCOSAsLockout; + bool m_dstarEnabled; + bool m_dmrEnabled; + bool m_ysfEnabled; + bool m_p25Enabled; + bool m_nxdnEnabled; + bool m_pocsagEnabled; + bool m_fmEnabled; + int m_rxDCOffset; + int m_txDCOffset; +}; + +#endif // CODEC_H diff --git a/codec2/codebooks.cpp b/codec2/codebooks.cpp new file mode 100644 index 0000000..bf319ce --- /dev/null +++ b/codec2/codebooks.cpp @@ -0,0 +1,964 @@ +/* + * This intermediary file and the files that used to create it are under + * The LGPL. See the file COPYING. + */ + +#include "defines.h" + +/* codebook/lsp1.txt */ +static float codes00[] = +{ + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600 +}; +/* codebook/lsp2.txt */ +static float codes01[] = +{ + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700 +}; +/* codebook/lsp3.txt */ +static float codes02[] = +{ + 500, + 550, + 600, + 650, + 700, + 750, + 800, + 850, + 900, + 950, + 1000, + 1050, + 1100, + 1150, + 1200, + 1250 +}; +/* codebook/lsp4.txt */ +static float codes03[] = +{ + 700, + 800, + 900, + 1000, + 1100, + 1200, + 1300, + 1400, + 1500, + 1600, + 1700, + 1800, + 1900, + 2000, + 2100, + 2200 +}; +/* codebook/lsp5.txt */ +static float codes04[] = +{ + 950, + 1050, + 1150, + 1250, + 1350, + 1450, + 1550, + 1650, + 1750, + 1850, + 1950, + 2050, + 2150, + 2250, + 2350, + 2450 +}; +/* codebook/lsp6.txt */ +static float codes05[] = +{ + 1100, + 1200, + 1300, + 1400, + 1500, + 1600, + 1700, + 1800, + 1900, + 2000, + 2100, + 2200, + 2300, + 2400, + 2500, + 2600 +}; +/* codebook/lsp7.txt */ +static float codes06[] = +{ + 1500, + 1600, + 1700, + 1800, + 1900, + 2000, + 2100, + 2200, + 2300, + 2400, + 2500, + 2600, + 2700, + 2800, + 2900, + 3000 +}; +/* codebook/lsp8.txt */ +static float codes07[] = +{ + 2300, + 2400, + 2500, + 2600, + 2700, + 2800, + 2900, + 3000 +}; +/* codebook/lsp9.txt */ +static float codes08[] = +{ + 2500, + 2600, + 2700, + 2800, + 2900, + 3000, + 3100, + 3200 +}; +/* codebook/lsp10.txt */ +static float codes09[] = +{ + 2900, + 3100, + 3300, + 3500 +}; + +const struct lsp_codebook lsp_cb[] = +{ + /* codebook/lsp1.txt */ + { + 1, + 4, + 16, + codes00 + }, + /* codebook/lsp2.txt */ + { + 1, + 4, + 16, + codes01 + }, + /* codebook/lsp3.txt */ + { + 1, + 4, + 16, + codes02 + }, + /* codebook/lsp4.txt */ + { + 1, + 4, + 16, + codes03 + }, + /* codebook/lsp5.txt */ + { + 1, + 4, + 16, + codes04 + }, + /* codebook/lsp6.txt */ + { + 1, + 4, + 16, + codes05 + }, + /* codebook/lsp7.txt */ + { + 1, + 4, + 16, + codes06 + }, + /* codebook/lsp8.txt */ + { + 1, + 3, + 8, + codes07 + }, + /* codebook/lsp9.txt */ + { + 1, + 3, + 8, + codes08 + }, + /* codebook/lsp10.txt */ + { + 1, + 2, + 4, + codes09 + }, + { 0, 0, 0, 0 } +}; + +/* codebook/dlsp1.txt */ +static float codes10[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp2.txt */ +static float codes11[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp3.txt */ +static float codes12[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp4.txt */ +static float codes13[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 550, + 600, + 650, + 700, + 750, + 800, + 850, + 900, + 950, + 1000, + 1050, + 1100, + 1150, + 1200, + 1250, + 1300, + 1350, + 1400 +}; +/* codebook/dlsp5.txt */ +static float codes14[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 550, + 600, + 650, + 700, + 750, + 800, + 850, + 900, + 950, + 1000, + 1050, + 1100, + 1150, + 1200, + 1250, + 1300, + 1350, + 1400 +}; +/* codebook/dlsp6.txt */ +static float codes15[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 550, + 600, + 650, + 700, + 750, + 800, + 850, + 900, + 950, + 1000, + 1050, + 1100, + 1150, + 1200, + 1250, + 1300, + 1350, + 1400 +}; +/* codebook/dlsp7.txt */ +static float codes16[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp8.txt */ +static float codes17[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp9.txt */ +static float codes18[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; +/* codebook/dlsp10.txt */ +static float codes19[] = +{ + 25, + 50, + 75, + 100, + 125, + 150, + 175, + 200, + 225, + 250, + 275, + 300, + 325, + 350, + 375, + 400, + 425, + 450, + 475, + 500, + 525, + 550, + 575, + 600, + 625, + 650, + 675, + 700, + 725, + 750, + 775, + 800 +}; + +const struct lsp_codebook lsp_cbd[] = +{ + /* codebook/dlsp1.txt */ + { + 1, + 5, + 32, + codes10 + }, + /* codebook/dlsp2.txt */ + { + 1, + 5, + 32, + codes11 + }, + /* codebook/dlsp3.txt */ + { + 1, + 5, + 32, + codes12 + }, + /* codebook/dlsp4.txt */ + { + 1, + 5, + 32, + codes13 + }, + /* codebook/dlsp5.txt */ + { + 1, + 5, + 32, + codes14 + }, + /* codebook/dlsp6.txt */ + { + 1, + 5, + 32, + codes15 + }, + /* codebook/dlsp7.txt */ + { + 1, + 5, + 32, + codes16 + }, + /* codebook/dlsp8.txt */ + { + 1, + 5, + 32, + codes17 + }, + /* codebook/dlsp9.txt */ + { + 1, + 5, + 32, + codes18 + }, + /* codebook/dlsp10.txt */ + { + 1, + 5, + 32, + codes19 + }, + { 0, 0, 0, 0 } +}; + + +/* codebook/gecb.txt */ +static float codes30[] = +{ + 2.71, 12.0184, + 0.04675, -2.73881, + 0.120993, 8.38895, + -1.58028, -0.892307, + 1.19307, -1.91561, + 0.187101, -3.27679, + 0.332251, -7.66455, + -1.47944, 31.2461, + 1.52761, 27.7095, + -0.524379, 5.25012, + 0.55333, 7.4388, + -0.843451, -1.95299, + 2.26389, 8.61029, + 0.143143, 2.36549, + 0.616506, 1.28427, + -1.71133, 22.0967, + 1.00813, 17.3965, + -0.106718, 1.41891, + -0.136246, 14.2736, + -1.70909, -20.5319, + 1.65787, -3.39107, + 0.138049, -4.95785, + 0.536729, -1.94375, + 0.196307, 36.8519, + 1.27248, 22.5565, + -0.670219, -1.90604, + 0.382092, 6.40113, + -0.756911, -4.90102, + 1.82931, 4.6138, + 0.318794, 0.73683, + 0.612815, -2.07505, + -0.410151, 24.7871, + 1.77602, 13.1909, + 0.106457, -0.104492, + 0.192206, 10.1838, + -1.82442, -7.71565, + 0.931346, 4.34835, + 0.308813, -4.086, + 0.397143, -11.8089, + -0.048715, 41.2273, + 0.877342, 35.8503, + -0.759794, 0.476634, + 0.978593, 7.67467, + -1.19506, 3.03883, + 2.63989, -3.41106, + 0.191127, 3.60351, + 0.402932, 1.0843, + -2.15202, 18.1076, + 1.5468, 8.32271, + -0.143089, -4.07592, + -0.150142, 5.86674, + -1.40844, -3.2507, + 1.56615, -10.4132, + 0.178171, -10.2267, + 0.362164, -0.028556, + -0.070125, 24.3907, + 0.594752, 17.4828, + -0.28698, -6.90407, + 0.464818, 10.2055, + -1.00684, -14.3572, + 2.32957, -3.69161, + 0.335745, 2.40714, + 1.01966, -3.15565, + -1.25945, 7.9919, + 2.38369, 19.6806, + -0.094947, -2.41374, + 0.20933, 6.66477, + -2.22103, 1.37986, + 1.29239, 2.04633, + 0.243626, -0.890741, + 0.428773, -7.19366, + -1.11374, 41.3414, + 2.6098, 31.1405, + -0.446468, 2.53419, + 0.490104, 4.62757, + -1.11723, -3.24174, + 1.79156, 8.41493, + 0.156012, 0.183336, + 0.532447, 3.15455, + -0.764484, 18.514, + 0.952395, 11.7713, + -0.332567, 0.346987, + 0.202165, 14.7168, + -2.12924, -15.559, + 1.35358, -1.92679, + -0.010963, -16.3364, + 0.399053, -2.79057, + 0.750657, 31.1483, + 0.655743, 24.4819, + -0.45321, -0.735879, + 0.2869, 6.5467, + -0.715673, -12.3578, + 1.54849, 3.87217, + 0.271874, 0.802339, + 0.502073, -4.85485, + -0.497037, 17.7619, + 1.19116, 13.9544, + 0.01563, 1.33157, + 0.341867, 8.93537, + -2.31601, -5.39506, + 0.75861, 1.9645, + 0.24132, -3.23769, + 0.267151, -11.2344, + -0.273126, 32.6248, + 1.75352, 40.432, + -0.784011, 3.04576, + 0.705987, 5.66118, + -1.3864, 1.35356, + 2.37646, 1.67485, + 0.242973, 4.73218, + 0.491227, 0.354061, + -1.60676, 8.65895, + 1.16711, 5.9871, + -0.137601, -12.0417, + -0.251375, 10.3972, + -1.43151, -8.90411, + 0.98828, -13.209, + 0.261484, -6.35497, + 0.395932, -0.702529, + 0.283704, 26.8996, + 0.420959, 15.4418, + -0.355804, -13.7278, + 0.527372, 12.3985, + -1.16956, -15.9985, + 1.90669, -5.81605, + 0.354492, 3.85157, + 0.82576, -4.16264, + -0.49019, 13.0572, + 2.25577, 13.5264, + -0.004956, -3.23713, + 0.026709, 7.86645, + -1.81037, -0.451183, + 1.08383, -0.18362, + 0.135836, -2.26658, + 0.375812, -5.51225, + -1.96644, 38.6829, + 1.97799, 24.5655, + -0.704656, 6.35881, + 0.480786, 7.05175, + -0.976417, -2.42273, + 2.50215, 6.75935, + 0.083588, 3.2588, + 0.543629, 0.910013, + -1.23196, 23.0915, + 0.785492, 14.807, + -0.213554, 1.688, + 0.004748, 18.1718, + -1.54719, -16.1168, + 1.50104, -3.28114, + 0.080133, -4.63472, + 0.476592, -2.18093, + 0.44247, 40.304, + 1.07277, 27.592, + -0.594738, -4.16681, + 0.42248, 7.61609, + -0.927521, -7.27441, + 1.99162, 1.29636, + 0.291307, 2.39878, + 0.721081, -1.95062, + -0.804256, 24.9295, + 1.64839, 19.1197, + 0.060852, -0.590639, + 0.266085, 9.10325, + -1.9574, -2.88461, + 1.11693, 2.6724, + 0.35458, -2.74854, + 0.330733, -14.1561, + -0.527851, 39.5756, + 0.991152, 43.195, + -0.589619, 1.26919, + 0.787401, 8.73071, + -1.0138, 1.02507, + 2.8254, 1.89538, + 0.24089, 2.74557, + 0.427195, 2.54446, + -1.95311, 12.244, + 1.44862, 12.0607, + -0.210492, -3.37906, + -0.056713, 10.204, + -1.65237, -5.10274, + 1.29475, -12.2708, + 0.111608, -8.67592, + 0.326634, -1.16763, + 0.021781, 31.1258, + 0.455335, 21.4684, + -0.37544, -3.37121, + 0.39362, 11.302, + -0.851456, -19.4149, + 2.10703, -2.22886, + 0.373233, 1.92406, + 0.884438, -1.72058, + -0.975127, 9.84013, + 2.0033, 17.3954, + -0.036915, -1.11137, + 0.148456, 5.39997, + -1.91441, 4.77382, + 1.44791, 0.537122, + 0.194979, -1.03818, + 0.495771, -9.95502, + -1.05899, 32.9471, + 2.01122, 32.4544, + -0.30965, 4.71911, + 0.436082, 4.63552, + -1.23711, -1.25428, + 2.02274, 9.42834, + 0.190342, 1.46077, + 0.479017, 2.48479, + -1.07848, 16.2217, + 1.20764, 9.65421, + -0.258087, -1.67236, + 0.071852, 13.416, + -1.87723, -16.072, + 1.28957, -4.87118, + 0.067713, -13.4427, + 0.435551, -4.1655, + 0.46614, 30.5895, + 0.904895, 21.598, + -0.518369, -2.53205, + 0.337363, 5.63726, + -0.554975, -17.4005, + 1.69188, 1.14574, + 0.227934, 0.889297, + 0.587303, -5.72973, + -0.262133, 18.6666, + 1.39505, 17.0029, + -0.01909, 4.30838, + 0.304235, 12.6699, + -2.07406, -6.46084, + 0.920546, 1.21296, + 0.284927, -1.78547, + 0.209724, -16.024, + -0.636067, 31.5768, + 1.34989, 34.6775, + -0.971625, 5.30086, + 0.590249, 4.44971, + -1.56787, 3.60239, + 2.1455, 4.51666, + 0.296022, 4.12017, + 0.445299, 0.868772, + -1.44193, 14.1284, + 1.35575, 6.0074, + -0.012814, -7.49657, + -0.43, 8.50012, + -1.20469, -7.11326, + 1.10102, -6.83682, + 0.196463, -6.234, + 0.436747, -1.12979, + 0.141052, 22.8549, + 0.290821, 18.8114, + -0.529536, -7.73251, + 0.63428, 10.7898, + -1.33472, -20.3258, + 1.81564, -1.90332, + 0.394778, 3.79758, + 0.732682, -8.18382, + -0.741244, 11.7683 +}; + +const struct lsp_codebook ge_cb[] = +{ + /* codebook/gecb.txt */ + { + 2, + 8, + 256, + codes30 + }, + { 0, 0, 0, 0 } +}; diff --git a/codec2/codec2.cpp b/codec2/codec2.cpp new file mode 100644 index 0000000..afa037b --- /dev/null +++ b/codec2/codec2.cpp @@ -0,0 +1,1745 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: codec2.c + AUTHOR......: David Rowe + DATE CREATED: 21/8/2010 + + Codec2 fully quantised encoder and decoder functions. If you want use + codec2, the codec2_xxx functions are for you. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2010 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "nlp.h" +#include "lpc.h" +#include "quantise.h" +#include "codec2.h" +#include "codec2_internal.h" + +#define HPF_BETA 0.125 +#define BPF_N 101 + +CKissFFT kiss; + +/*---------------------------------------------------------------------------* \ + + FUNCTION HEADERS + +\*---------------------------------------------------------------------------*/ + + + + +/*---------------------------------------------------------------------------*\ + + FUNCTIONS + +\*---------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: codec2_create + AUTHOR......: David Rowe + DATE CREATED: 21/8/2010 + + Create and initialise an instance of the codec. Returns a pointer + to the codec states or NULL on failure. One set of states is + sufficient for a full duuplex codec (i.e. an encoder and decoder). + You don't need separate states for encoders and decoders. See + c2enc.c and c2dec.c for examples. + +\*---------------------------------------------------------------------------*/ + +CCodec2::CCodec2(bool is_3200) +{ + c2.mode = is_3200 ? 3200 : 1600; + + /* store constants in a few places for convenience */ + + c2.c2const = c2const_create(8000, N_S); + c2.Fs = c2.c2const.Fs; + int n_samp = c2.n_samp = c2.c2const.n_samp; + int m_pitch = c2.m_pitch = c2.c2const.m_pitch; + + c2.Pn.resize(2*n_samp); + c2.Sn_.resize(2*n_samp); + c2.w.resize(m_pitch); + c2.Sn.resize(m_pitch); + + for(int i=0; i Aw[FFT_ENC]; + + /* only need to zero these out due to (unused) snr calculation */ + + for(i=0; i<2; i++) + for(j=1; j<=MAX_AMP; j++) + model[i].A[j] = 0.0; + + /* unpack bits from channel ------------------------------------*/ + + /* this will partially fill the model params for the 2 x 10ms + frames */ + + model[0].voiced = qt.unpack(bits, &nbit, 1); + model[1].voiced = qt.unpack(bits, &nbit, 1); + + Wo_index = qt.unpack(bits, &nbit, WO_BITS); + model[1].Wo = qt.decode_Wo(&c2.c2const, Wo_index, WO_BITS); + model[1].L = PI/model[1].Wo; + + e_index = qt.unpack(bits, &nbit, E_BITS); + e[1] = qt.decode_energy(e_index, E_BITS); + + for(i=0; i Aw[FFT_ENC]; + + /* only need to zero these out due to (unused) snr calculation */ + + for(i=0; i<4; i++) + for(j=1; j<=MAX_AMP; j++) + model[i].A[j] = 0.0; + + /* unpack bits from channel ------------------------------------*/ + + /* this will partially fill the model params for the 4 x 10ms + frames */ + + model[0].voiced = qt.unpack(bits, &nbit, 1); + + model[1].voiced = qt.unpack(bits, &nbit, 1); + Wo_index = qt.unpack(bits, &nbit, WO_BITS); + model[1].Wo = qt.decode_Wo(&c2.c2const, Wo_index, WO_BITS); + model[1].L = PI/model[1].Wo; + + e_index = qt.unpack(bits, &nbit, E_BITS); + e[1] = qt.decode_energy(e_index, E_BITS); + + model[2].voiced = qt.unpack(bits, &nbit, 1); + + model[3].voiced = qt.unpack(bits, &nbit, 1); + Wo_index = qt.unpack(bits, &nbit, WO_BITS); + model[3].Wo = qt.decode_Wo(&c2.c2const, Wo_index, WO_BITS); + model[3].L = PI/model[3].Wo; + + e_index = qt.unpack(bits, &nbit, E_BITS); + e[3] = qt.decode_energy(e_index, E_BITS); + + for(i=0; i Aw[], float gain) +{ + int i; + + /* LPC based phase synthesis */ + std::complex H[MAX_AMP+1]; + sample_phase(model, H, Aw); + phase_synth_zero_order(c2.n_samp, model, &c2.ex_phase, H); + + postfilter(model, &c2.bg_est); + synthesise(c2.n_samp, &(c2.fftr_inv_cfg), c2.Sn_.data(), model, c2.Pn.data(), 1); + + for(i=0; i 32767.0) + speech[i] = 32767; + else if (c2.Sn_[i] < -32767.0) + speech[i] = -32767; + else + speech[i] = c2.Sn_[i]; + } + +} + + +/*---------------------------------------------------------------------------* \ + + FUNCTION....: analyse_one_frame() + AUTHOR......: David Rowe + DATE CREATED: 23/8/2010 + + Extract sinusoidal model parameters from 80 speech samples (10ms of + speech). + +\*---------------------------------------------------------------------------*/ + +void CCodec2::analyse_one_frame(MODEL *model, const short *speech) +{ + std::complex Sw[FFT_ENC]; + float pitch; + int i; + int n_samp = c2.n_samp; + int m_pitch = c2.m_pitch; + + /* Read input speech */ + + for(i=0; iWo = TWO_PI/pitch; + model->L = PI/model->Wo; + + /* estimate model parameters */ + two_stage_pitch_refinement(&c2.c2const, model, Sw); + + /* estimate phases when doing ML experiments */ + estimate_amplitudes(model, Sw, 0); + est_voicing_mbe(&c2.c2const, model, Sw, c2.W); +} + + +/*---------------------------------------------------------------------------* \ + + FUNCTION....: ear_protection() + AUTHOR......: David Rowe + DATE CREATED: Nov 7 2012 + + Limits output level to protect ears when there are bit errors or the input + is overdriven. This doesn't correct or mask bit errors, just reduces the + worst of their damage. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::ear_protection(float in_out[], int n) +{ + float max_sample, over, gain; + int i; + + /* find maximum sample in frame */ + + max_sample = 0.0; + for(i=0; i max_sample) + max_sample = in_out[i]; + + /* determine how far above set point */ + + over = max_sample/30000.0; + + /* If we are x dB over set point we reduce level by 2x dB, this + attenuates major excursions in amplitude (likely to be caused + by bit errors) more than smaller ones */ + + if (over > 1.0) + { + gain = 1.0/(over*over); + for(i=0; i H[], + std::complex A[] /* LPC analysis filter in freq domain */ +) +{ + int m, b; + float r; + + r = TWO_PI/(FFT_ENC); + + /* Sample phase at harmonics */ + + for(m=1; m<=model->L; m++) + { + b = (int)(m*model->Wo/r + 0.5); + H[m] = std::conj(A[b]); + } +} + + +/*---------------------------------------------------------------------------*\ + + phase_synth_zero_order() + + Synthesises phases based on SNR and a rule based approach. No phase + parameters are required apart from the SNR (which can be reduced to a + 1 bit V/UV decision per frame). + + The phase of each harmonic is modelled as the phase of a synthesis + filter excited by an impulse. In many Codec 2 modes the synthesis + filter is a LPC filter. Unlike the first order model the position + of the impulse is not transmitted, so we create an excitation pulse + train using a rule based approach. + + Consider a pulse train with a pulse starting time n=0, with pulses + repeated at a rate of Wo, the fundamental frequency. A pulse train + in the time domain is equivalent to harmonics in the frequency + domain. We can make an excitation pulse train using a sum of + sinsusoids: + + for(m=1; m<=L; m++) + ex[n] = cos(m*Wo*n) + + Note: the Octave script ../octave/phase.m is an example of this if + you would like to try making a pulse train. + + The phase of each excitation harmonic is: + + arg(E[m]) = mWo + + where E[m] are the complex excitation (freq domain) samples, + arg(x), just returns the phase of a complex sample x. + + As we don't transmit the pulse position for this model, we need to + synthesise it. Now the excitation pulses occur at a rate of Wo. + This means the phase of the first harmonic advances by N_SAMP samples + over a synthesis frame of N_SAMP samples. For example if Wo is pi/20 + (200 Hz), then over a 10ms frame (N_SAMP=80 samples), the phase of the + first harmonic would advance (pi/20)*80 = 4*pi or two complete + cycles. + + We generate the excitation phase of the fundamental (first + harmonic): + + arg[E[1]] = Wo*N_SAMP; + + We then relate the phase of the m-th excitation harmonic to the + phase of the fundamental as: + + arg(E[m]) = m*arg(E[1]) + + This E[m] then gets passed through the LPC synthesis filter to + determine the final harmonic phase. + + Comparing to speech synthesised using original phases: + + - Through headphones speech synthesised with this model is not as + good. Through a loudspeaker it is very close to original phases. + + - If there are voicing errors, the speech can sound clicky or + staticy. If V speech is mistakenly declared UV, this model tends to + synthesise impulses or clicks, as there is usually very little shift or + dispersion through the LPC synthesis filter. + + - When combined with LPC amplitude modelling there is an additional + drop in quality. I am not sure why, theory is interformant energy + is raised making any phase errors more obvious. + + NOTES: + + 1/ This synthesis model is effectively the same as a simple LPC-10 + vocoders, and yet sounds much better. Why? Conventional wisdom + (AMBE, MELP) says mixed voicing is required for high quality + speech. + + 2/ I am pretty sure the Lincoln Lab sinusoidal coding guys (like xMBE + also from MIT) first described this zero phase model, I need to look + up the paper. + + 3/ Note that this approach could cause some discontinuities in + the phase at the edge of synthesis frames, as no attempt is made + to make sure that the phase tracks are continuous (the excitation + phases are continuous, but not the final phases after filtering + by the LPC spectra). Technically this is a bad thing. However + this may actually be a good thing, disturbing the phase tracks a + bit. More research needed, e.g. test a synthesis model that adds + a small delta-W to make phase tracks line up for voiced + harmonics. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::phase_synth_zero_order( + int n_samp, + MODEL *model, + float *ex_phase, /* excitation phase of fundamental */ + std::complex H[] /* L synthesis filter freq domain samples */ + +) +{ + int m; + float new_phi; + std::complex Ex[MAX_AMP+1]; /* excitation samples */ + std::complex A_[MAX_AMP+1]; /* synthesised harmonic samples */ + + /* + Update excitation fundamental phase track, this sets the position + of each pitch pulse during voiced speech. After much experiment + I found that using just this frame's Wo improved quality for UV + sounds compared to interpolating two frames Wo like this: + + ex_phase[0] += (*prev_Wo+model->Wo)*N_SAMP/2; + */ + + ex_phase[0] += (model->Wo)*n_samp; + ex_phase[0] -= TWO_PI*floorf(ex_phase[0]/TWO_PI + 0.5); + + for(m=1; m<=model->L; m++) + { + + /* generate excitation */ + + if (model->voiced) + { + Ex[m] = std::polar(1.0f, ex_phase[0] * m); + } + else + { + + /* When a few samples were tested I found that LPC filter + phase is not needed in the unvoiced case, but no harm in + keeping it. + */ + float phi = TWO_PI*(float)codec2_rand()/CODEC2_RAND_MAX; + Ex[m] = std::polar(1.0f, phi); + } + + /* filter using LPC filter */ + + A_[m].real(H[m].real() * Ex[m].real() - H[m].imag() * Ex[m].imag()); + A_[m].imag(H[m].imag() * Ex[m].real() + H[m].real() * Ex[m].imag()); + + /* modify sinusoidal phase */ + + new_phi = atan2f(A_[m].imag(), A_[m].real()+1E-12); + model->phi[m] = new_phi; + } + +} + +/*---------------------------------------------------------------------------*\ + + postfilter() + + The post filter is designed to help with speech corrupted by + background noise. The zero phase model tends to make speech with + background noise sound "clicky". With high levels of background + noise the low level inter-formant parts of the spectrum will contain + noise rather than speech harmonics, so modelling them as voiced + (i.e. a continuous, non-random phase track) is inaccurate. + + Some codecs (like MBE) have a mixed voicing model that breaks the + spectrum into voiced and unvoiced regions. Several bits/frame + (5-12) are required to transmit the frequency selective voicing + information. Mixed excitation also requires accurate voicing + estimation (parameter estimators always break occasionally under + exceptional conditions). + + In our case we use a post filter approach which requires no + additional bits to be transmitted. The decoder measures the average + level of the background noise during unvoiced frames. If a harmonic + is less than this level it is made unvoiced by randomising it's + phases. + + This idea is rather experimental. Some potential problems that may + happen: + + 1/ If someone says "aaaaaaaahhhhhhhhh" will background estimator track + up to speech level? This would be a bad thing. + + 2/ If background noise suddenly dissapears from the source speech does + estimate drop quickly? What is noise suddenly re-appears? + + 3/ Background noise with a non-flat sepctrum. Current algorithm just + comsiders spectrum as a whole, but this could be broken up into + bands, each with their own estimator. + + 4/ Males and females with the same level of background noise. Check + performance the same. Changing Wo affects width of each band, may + affect bg energy estimates. + + 5/ Not sure what happens during long periods of voiced speech + e.g. "sshhhhhhh" + +\*---------------------------------------------------------------------------*/ + +#define BG_THRESH 40.0 // only consider low levels signals for bg_est +#define BG_BETA 0.1 // averaging filter constant +#define BG_MARGIN 6.0 // harmonics this far above BG noise are + // randomised. Helped make bg noise less + // spikey (impulsive) for mmt1, but speech was + // perhaps a little rougher. + +void CCodec2::postfilter( MODEL *model, float *bg_est ) +{ + int m, uv; + float e, thresh; + + /* determine average energy across spectrum */ + + e = 1E-12; + for(m=1; m<=model->L; m++) + e += model->A[m]*model->A[m]; + + assert(e > 0.0); + e = 10.0*log10f(e/model->L); + + /* If beneath threhold, update bg estimate. The idea + of the threshold is to prevent updating during high level + speech. */ + + if ((e < BG_THRESH) && !model->voiced) + *bg_est = *bg_est*(1.0 - BG_BETA) + e*BG_BETA; + + /* now mess with phases during voiced frames to make any harmonics + less then our background estimate unvoiced. + */ + + uv = 0; + thresh = exp10f((*bg_est + BG_MARGIN)/20.0); + if (model->voiced) + for(m=1; m<=model->L; m++) + if (model->A[m] < thresh) + { + model->phi[m] = (TWO_PI/CODEC2_RAND_MAX)*(float)codec2_rand(); + uv++; + } +} + +C2CONST CCodec2::c2const_create(int Fs, float framelength_s) +{ + C2CONST c2const; + + assert((Fs == 8000) || (Fs = 16000)); + c2const.Fs = Fs; + c2const.n_samp = round(Fs*framelength_s); + c2const.max_amp = floor(Fs*P_MAX_S/2); + c2const.p_min = floor(Fs*P_MIN_S); + c2const.p_max = floor(Fs*P_MAX_S); + c2const.m_pitch = floor(Fs*M_PITCH_S); + c2const.Wo_min = TWO_PI/c2const.p_max; + c2const.Wo_max = TWO_PI/c2const.p_min; + + if (Fs == 8000) + { + c2const.nw = 279; + } + else + { + c2const.nw = 511; /* actually a bit shorter in time but lets us maintain constant FFT size */ + } + + c2const.tw = Fs*TW_S; + + /* + fprintf(stderr, "max_amp: %d m_pitch: %d\n", c2const.n_samp, c2const.m_pitch); + fprintf(stderr, "p_min: %d p_max: %d\n", c2const.p_min, c2const.p_max); + fprintf(stderr, "Wo_min: %f Wo_max: %f\n", c2const.Wo_min, c2const.Wo_max); + fprintf(stderr, "nw: %d tw: %d\n", c2const.nw, c2const.tw); + */ + + return c2const; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: make_analysis_window + AUTHOR......: David Rowe + DATE CREATED: 11/5/94 + + Init function that generates the time domain analysis window and it's DFT. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::make_analysis_window(C2CONST *c2const, FFT_STATE *fft_fwd_cfg, float w[], float W[]) +{ + float m; + std::complex wshift[FFT_ENC]; + int i,j; + int m_pitch = c2const->m_pitch; + int nw = c2const->nw; + + /* + Generate Hamming window centered on M-sample pitch analysis window + + 0 M/2 M-1 + |-------------|-------------| + |-------|-------| + nw samples + + All our analysis/synthsis is centred on the M/2 sample. + */ + + m = 0.0; + for(i=0; i temp[FFT_ENC]; + + for(i=0; i(0.0f, 0.0f); + } + for(i=0; i Sw[], float Sn[], float w[]) +{ + int i; + int m_pitch = c2const->m_pitch; + int nw = c2const->nw; + + for(i=0; i(0.0f, 0.0f); + } + + /* Centre analysis window on time axis, we need to arrange input + to FFT this way to make FFT phases correct */ + + /* move 2nd half to start of FFT input vector */ + + for(i=0; i Sw[]) +{ + float pmin,pmax,pstep; /* pitch refinment minimum, maximum and step */ + + /* Coarse refinement */ + + pmax = TWO_PI/model->Wo + 5; + pmin = TWO_PI/model->Wo - 5; + pstep = 1.0; + hs_pitch_refinement(model, Sw, pmin, pmax, pstep); + + /* Fine refinement */ + + pmax = TWO_PI/model->Wo + 1; + pmin = TWO_PI/model->Wo - 1; + pstep = 0.25; + hs_pitch_refinement(model,Sw,pmin,pmax,pstep); + + /* Limit range */ + + if (model->Wo < TWO_PI/c2const->p_max) + model->Wo = TWO_PI/c2const->p_max; + if (model->Wo > TWO_PI/c2const->p_min) + model->Wo = TWO_PI/c2const->p_min; + + model->L = floorf(PI/model->Wo); + + /* trap occasional round off issues with floorf() */ + if (model->Wo*model->L >= 0.95*PI) + { + model->L--; + } + assert(model->Wo*model->L < PI); +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: hs_pitch_refinement + AUTHOR......: David Rowe + DATE CREATED: 27/5/94 + + Harmonic sum pitch refinement function. + + pmin pitch search range minimum + pmax pitch search range maximum + step pitch search step size + model current pitch estimate in model.Wo + + model refined pitch estimate in model.Wo + +\*---------------------------------------------------------------------------*/ + +void CCodec2::hs_pitch_refinement(MODEL *model, std::complex Sw[], float pmin, float pmax, float pstep) +{ + int m; /* loop variable */ + int b; /* bin for current harmonic centre */ + float E; /* energy for current pitch*/ + float Wo; /* current "test" fundamental freq. */ + float Wom; /* Wo that maximises E */ + float Em; /* mamimum energy */ + float r, one_on_r; /* number of rads/bin */ + float p; /* current pitch */ + + /* Initialisation */ + + model->L = PI/model->Wo; /* use initial pitch est. for L */ + Wom = model->Wo; + Em = 0.0; + r = TWO_PI/FFT_ENC; + one_on_r = 1.0/r; + + /* Determine harmonic sum for a range of Wo values */ + + for(p=pmin; p<=pmax; p+=pstep) + { + E = 0.0; + Wo = TWO_PI/p; + + /* Sum harmonic magnitudes */ + for(m=1; m<=model->L; m++) + { + b = (int)(m*Wo*one_on_r + 0.5); + E += Sw[b].real() * Sw[b].real() + Sw[b].imag() * Sw[b].imag(); + } + /* Compare to see if this is a maximum */ + + if (E > Em) + { + Em = E; + Wom = Wo; + } + } + + model->Wo = Wom; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: estimate_amplitudes + AUTHOR......: David Rowe + DATE CREATED: 27/5/94 + + Estimates the complex amplitudes of the harmonics. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::estimate_amplitudes(MODEL *model, std::complex Sw[], int est_phase) +{ + int i,m; /* loop variables */ + int am,bm; /* bounds of current harmonic */ + float den; /* denominator of amplitude expression */ + + float r = TWO_PI/FFT_ENC; + float one_on_r = 1.0/r; + + for(m=1; m<=model->L; m++) + { + /* Estimate ampltude of harmonic */ + + den = 0.0; + am = (int)((m - 0.5)*model->Wo*one_on_r + 0.5); + bm = (int)((m + 0.5)*model->Wo*one_on_r + 0.5); + + for(i=am; iA[m] = sqrtf(den); + + if (est_phase) + { + int b = (int)(m*model->Wo/r + 0.5); /* DFT bin of centre of current harmonic */ + + /* Estimate phase of harmonic, this is expensive in CPU for + embedded devicesso we make it an option */ + + model->phi[m] = atan2f(Sw[b].imag(), Sw[b].real()); + } + } +} + +/*---------------------------------------------------------------------------*\ + + est_voicing_mbe() + + Returns the error of the MBE cost function for a fiven F0. + + Note: I think a lot of the operations below can be simplified as + W[].imag = 0 and has been normalised such that den always equals 1. + +\*---------------------------------------------------------------------------*/ + +float CCodec2::est_voicing_mbe( C2CONST *c2const, MODEL *model, std::complex Sw[], float W[]) +{ + int l,al,bl,m; /* loop variables */ + std::complex Am; /* amplitude sample for this band */ + int offset; /* centers Hw[] about current harmonic */ + float den; /* denominator of Am expression */ + float error; /* accumulated error between original and synthesised */ + float Wo; + float sig, snr; + float elow, ehigh, eratio; + float sixty; + std::complex Ew(0, 0); + + int l_1000hz = model->L*1000.0/(c2const->Fs/2); + sig = 1E-4; + for(l=1; l<=l_1000hz; l++) + { + sig += model->A[l]*model->A[l]; + } + + Wo = model->Wo; + error = 1E-4; + + /* Just test across the harmonics in the first 1000 Hz */ + + for(l=1; l<=l_1000hz; l++) + { + Am = std::complex(0.0f, 0.0f); + den = 0.0; + al = ceilf((l - 0.5)*Wo*FFT_ENC/TWO_PI); + bl = ceilf((l + 0.5)*Wo*FFT_ENC/TWO_PI); + + /* Estimate amplitude of harmonic assuming harmonic is totally voiced */ + + offset = FFT_ENC/2 - l*Wo*FFT_ENC/TWO_PI + 0.5; + for(m=al; m V_THRESH) + model->voiced = 1; + else + model->voiced = 0; + + /* post processing, helps clean up some voicing errors ------------------*/ + + /* + Determine the ratio of low freqency to high frequency energy, + voiced speech tends to be dominated by low frequency energy, + unvoiced by high frequency. This measure can be used to + determine if we have made any gross errors. + */ + + int l_2000hz = model->L*2000.0/(c2const->Fs/2); + int l_4000hz = model->L*4000.0/(c2const->Fs/2); + elow = ehigh = 1E-4; + for(l=1; l<=l_2000hz; l++) + { + elow += model->A[l]*model->A[l]; + } + for(l=l_2000hz; l<=l_4000hz; l++) + { + ehigh += model->A[l]*model->A[l]; + } + eratio = 10.0*log10f(elow/ehigh); + + /* Look for Type 1 errors, strongly V speech that has been + accidentally declared UV */ + + if (model->voiced == 0) + if (eratio > 10.0) + model->voiced = 1; + + /* Look for Type 2 errors, strongly UV speech that has been + accidentally declared V */ + + if (model->voiced == 1) + { + if (eratio < -10.0) + model->voiced = 0; + + /* A common source of Type 2 errors is the pitch estimator + gives a low (50Hz) estimate for UV speech, which gives a + good match with noise due to the close harmoonic spacing. + These errors are much more common than people with 50Hz3 + pitch, so we have just a small eratio threshold. */ + + sixty = 60.0*TWO_PI/c2const->Fs; + if ((eratio < -4.0) && (model->Wo <= sixty)) + model->voiced = 0; + } + //printf(" v: %d snr: %f eratio: %3.2f %f\n",model->voiced,snr,eratio,dF0); + + return snr; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: make_synthesis_window + AUTHOR......: David Rowe + DATE CREATED: 11/5/94 + + Init function that generates the trapezoidal (Parzen) sythesis window. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::make_synthesis_window(C2CONST *c2const, float Pn[]) +{ + int i; + float win; + int n_samp = c2const->n_samp; + int tw = c2const->tw; + + /* Generate Parzen window in time domain */ + + win = 0.0; + for(i=0; i Sw_[FFT_DEC/2+1]; /* DFT of synthesised signal */ + float sw_[FFT_DEC]; /* synthesised signal */ + + if (shift) + { + /* Update memories */ + for(i=0; iL; l++) + { + b = (int)(l*model->Wo*FFT_DEC/TWO_PI + 0.5); + if (b > ((FFT_DEC/2)-1)) + { + b = (FFT_DEC/2)-1; + } + Sw_[b] = std::polar(model->A[l], model->phi[l]); + } + + /* Perform inverse DFT */ + + kiss.fftri(*fftr_inv_cfg, Sw_,sw_); + + /* Overlap add to previous samples */ + + for(i=0; ivoiced && !prev->voiced && !next->voiced) + { + interp->voiced = 0; + } + + /* Wo depends on voicing of this and adjacent frames */ + + if (interp->voiced) + { + if (prev->voiced && next->voiced) + interp->Wo = (1.0 - weight)*prev->Wo + weight*next->Wo; + if (!prev->voiced && next->voiced) + interp->Wo = next->Wo; + if (prev->voiced && !next->voiced) + interp->Wo = prev->Wo; + } + else + { + interp->Wo = Wo_min; + } + interp->L = PI/interp->Wo; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: interp_energy() + AUTHOR......: David Rowe + DATE CREATED: 22 May 2012 + + Interpolates centre 10ms sample of energy given two samples 20ms + apart. + +\*---------------------------------------------------------------------------*/ + +float CCodec2::interp_energy(float prev_e, float next_e) +{ + //return powf(10.0, (log10f(prev_e) + log10f(next_e))/2.0); + return sqrtf(prev_e * next_e); //looks better is math. identical and faster math +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: interpolate_lsp_ver2() + AUTHOR......: David Rowe + DATE CREATED: 22 May 2012 + + Weighted interpolation of LSPs. + +\*---------------------------------------------------------------------------*/ + +void CCodec2::interpolate_lsp_ver2(float interp[], float prev[], float next[], float weight, int order) +{ + int i; + + for(i=0; i. +*/ + +#ifndef __CODEC2__ +#define __CODEC2__ + +#include + +#include "codec2_internal.h" +#include "defines.h" +#include "kiss_fft.h" +#include "nlp.h" +#include "quantise.h" + +#define CODEC2_MODE_3200 0 +#define CODEC2_MODE_1600 2 + +#ifndef CODEC2_MODE_EN_DEFAULT +#define CODEC2_MODE_EN_DEFAULT 1 +#endif + +#define CODEC2_RAND_MAX 32767 + +class CCodec2 +{ +public: + CCodec2(bool is_3200); + ~CCodec2(); + void codec2_encode(unsigned char *bits, const short *speech_in); + void codec2_decode(short *speech_out, const unsigned char *bits); + void codec2_set_mode(bool); + bool codec2_get_mode() {return (c2.mode == 3200); }; + int codec2_samples_per_frame(); + int codec2_bits_per_frame(); + void set_decode_gain(float g){ m_decode_gain = g; } + +private: + // merged from other files + void sample_phase(MODEL *model, std::complex filter_phase[], std::complex A[]); + void phase_synth_zero_order(int n_samp, MODEL *model, float *ex_phase, std::complex filter_phase[]); + void postfilter(MODEL *model, float *bg_est); + + C2CONST c2const_create(int Fs, float framelength_ms); + + void make_analysis_window(C2CONST *c2const, FFT_STATE *fft_fwd_cfg, float w[], float W[]); + void dft_speech(C2CONST *c2const, FFT_STATE &fft_fwd_cfg, std::complex Sw[], float Sn[], float w[]); + void two_stage_pitch_refinement(C2CONST *c2const, MODEL *model, std::complex Sw[]); + void estimate_amplitudes(MODEL *model, std::complex Sw[], int est_phase); + float est_voicing_mbe(C2CONST *c2const, MODEL *model, std::complex Sw[], float W[]); + void make_synthesis_window(C2CONST *c2const, float Pn[]); + void synthesise(int n_samp, FFTR_STATE *fftr_inv_cfg, float Sn_[], MODEL *model, float Pn[], int shift); + int codec2_rand(void); + void hs_pitch_refinement(MODEL *model, std::complex Sw[], float pmin, float pmax, float pstep); + + void interp_Wo(MODEL *interp, MODEL *prev, MODEL *next, float Wo_min); + void interp_Wo2(MODEL *interp, MODEL *prev, MODEL *next, float weight, float Wo_min); + float interp_energy(float prev, float next); + void interpolate_lsp_ver2(float interp[], float prev[], float next[], float weight, int order); + + void analyse_one_frame(MODEL *model, const short *speech); + void synthesise_one_frame(short speech[], MODEL *model, std::complex Aw[], float gain); + void codec2_encode_3200(unsigned char *bits, const short *speech); + void codec2_encode_1600(unsigned char *bits, const short *speech); + void codec2_decode_3200(short *speech, const unsigned char *bits); + void codec2_decode_1600(short *speech, const unsigned char *bits); + void ear_protection(float in_out[], int n); + void lsp_to_lpc(float *freq, float *ak, int lpcrdr); + + void (CCodec2::*encode)(unsigned char *bits, const short *speech); + void (CCodec2::*decode)(short *speech, const unsigned char *bits); + Cnlp nlp; + CQuantize qt; + CODEC2 c2; + float m_decode_gain; +}; + +#endif diff --git a/codec2/codec2_internal.h b/codec2/codec2_internal.h new file mode 100644 index 0000000..1b037e1 --- /dev/null +++ b/codec2/codec2_internal.h @@ -0,0 +1,67 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: codec2_internal.h + AUTHOR......: David Rowe + DATE CREATED: April 16 2012 + + Header file for Codec2 internal states, exposed via this header + file to assist in testing. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2012 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef __CODEC2_INTERNAL__ +#define __CODEC2_INTERNAL__ + +#include "kiss_fft.h" + +using CODEC2 = struct codec2_tag { + int mode; + int Fs; + int n_samp; + int m_pitch; + int gray; /* non-zero for gray encoding */ + int lpc_pf; /* LPC post filter on */ + int bass_boost; /* LPC post filter bass boost */ + int smoothing; /* enable smoothing for channels with errors */ + float ex_phase; /* excitation model phase track */ + float bg_est; /* background noise estimate for post filter */ + float prev_f0_enc; /* previous frame's f0 estimate */ + float prev_e_dec; /* previous frame's LPC energy */ + float beta; /* LPC post filter parameters */ + float gamma; + float xq_enc[2]; /* joint pitch and energy VQ states */ + float xq_dec[2]; + float W[FFT_ENC]; /* DFT of w[] */ + float hpf_states[2]; /* high pass filter states */ + float prev_lsps_dec[LPC_ORD]; /* previous frame's LSPs */ + float *softdec; /* optional soft decn bits from demod */ + MODEL prev_model_dec; /* previous frame's model parameters */ + C2CONST c2const; + FFT_STATE fft_fwd_cfg; /* forward FFT config */ + FFTR_STATE fftr_fwd_cfg; /* forward real FFT config */ + FFTR_STATE fftr_inv_cfg; /* inverse FFT config */ + std::vector w; /* [m_pitch] time domain hamming window */ + std::vector Pn; /* [2*n_samp] trapezoidal synthesis window */ + std::vector Sn; /* [m_pitch] input speech */ + std::vector Sn_; /* [2*n_samp] synthesised output speech */ + std::vector bpf_buf; /* buffer for band pass filter */ +}; + +#endif diff --git a/codec2/defines.h b/codec2/defines.h new file mode 100644 index 0000000..928c298 --- /dev/null +++ b/codec2/defines.h @@ -0,0 +1,132 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: defines.h + AUTHOR......: David Rowe + DATE CREATED: 23/4/93 + + Defines and structures used throughout the codec. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2009 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef __DEFINES__ +#define __DEFINES__ + +#include +#include + +/*---------------------------------------------------------------------------*\ + + DEFINES + +\*---------------------------------------------------------------------------*/ + +/* General defines */ + +#define N_S 0.01 /* internal proc frame length in secs */ +#define TW_S 0.005 /* trapezoidal synth window overlap */ +#define MAX_AMP 160 /* maximum number of harmonics */ +#ifndef PI +#define PI 3.141592654 /* mathematical constant */ +#endif +#define TWO_PI 6.283185307 /* mathematical constant */ +#define MAX_STR 2048 /* maximum string size */ + +#define FFT_ENC 512 /* size of FFT used for encoder */ +#define FFT_DEC 512 /* size of FFT used in decoder */ +#define V_THRESH 6.0 /* voicing threshold in dB */ +#define LPC_ORD 10 /* LPC order */ +#define LPC_ORD_LOW 6 /* LPC order for lower rates */ + +/* Pitch estimation defines */ + +#define M_PITCH_S 0.0400 /* pitch analysis window in s */ +#define P_MIN_S 0.0025 /* minimum pitch period in s */ +#define P_MAX_S 0.0200 /* maximum pitch period in s */ +#define MAXFACTORS 32 // e.g. an fft of length 128 has 4 factors + // as far as kissfft is concerned 4*4*4*2 + +/*---------------------------------------------------------------------------*\ + + TYPEDEFS + +\*---------------------------------------------------------------------------*/ + +/* Structure to hold constants calculated at run time based on sample rate */ + +using C2CONST = struct c2const_tag +{ + int Fs; /* sample rate of this instance */ + int n_samp; /* number of samples per 10ms frame at Fs */ + int max_amp; /* maximum number of harmonics */ + int m_pitch; /* pitch estimation window size in samples */ + int p_min; /* minimum pitch period in samples */ + int p_max; /* maximum pitch period in samples */ + float Wo_min; + float Wo_max; + int nw; /* analysis window size in samples */ + int tw; /* trapezoidal synthesis window overlap */ +}; + +/* Structure to hold model parameters for one frame */ + +using MODEL = struct model_tag +{ + float Wo; /* fundamental frequency estimate in radians */ + int L; /* number of harmonics */ + float A[MAX_AMP+1]; /* amplitiude of each harmonic */ + float phi[MAX_AMP+1]; /* phase of each harmonic */ + int voiced; /* non-zero if this frame is voiced */ +}; + +/* describes each codebook */ + +struct lsp_codebook +{ + int k; /* dimension of vector */ + int log2m; /* number of bits in m */ + int m; /* elements in codebook */ + float *cb; /* The elements */ +}; + +using FFT_STATE = struct fft_state_tag +{ + int nfft; + bool inverse; + int factors[2*MAXFACTORS]; + std::vector> twiddles; +}; + +using FFTR_STATE = struct fftr_state_tag +{ + FFT_STATE substate; + std::vector> tmpbuf; + std::vector> super_twiddles; +}; + +extern const struct lsp_codebook lsp_cb[]; +extern const struct lsp_codebook lsp_cbd[]; +extern const struct lsp_codebook ge_cb[]; + +inline float exp10f(float val) +{ + return pow(10.0, val); +} + +#endif diff --git a/codec2/kiss_fft.cpp b/codec2/kiss_fft.cpp new file mode 100644 index 0000000..cefb846 --- /dev/null +++ b/codec2/kiss_fft.cpp @@ -0,0 +1,435 @@ +/* +Copyright (c) 2003-2010, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "defines.h" +#include "kiss_fft.h" + +void CKissFFT::kf_bfly2(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m) +{ + std::complex *Fout2; + std::complex *tw1 = st.twiddles.data(); + std::complex t; + Fout2 = Fout + m; + do + { + t = *Fout2 * *tw1; + tw1 += fstride; + *Fout2 = *Fout - t; + *Fout += t; + ++Fout2; + ++Fout; + } + while (--m); +} + +void CKissFFT::kf_bfly3(std::complex * Fout, const size_t fstride, FFT_STATE &st, int m) +{ + const size_t m2 = 2 * m; + std::complex *tw1,*tw2; + std::complex scratch[5]; + std::complex epi3; + epi3 = st.twiddles[fstride*m]; + + tw1 = tw2 = st.twiddles.data(); + + do + { + scratch[1] = Fout[m] * *tw1; + scratch[2] = Fout[m2] * *tw2; + + scratch[3] = scratch[1] + scratch[2]; + scratch[0] = scratch[1] - scratch[2]; + tw1 += fstride; + tw2 += fstride*2; + + Fout[m] = *Fout - (0.5f * scratch[3]); + + scratch[0] *= epi3.imag(); + + *Fout += scratch[3]; + + Fout[m2].real(Fout[m].real() + scratch[0].imag()); + Fout[m2].imag(Fout[m].imag() - scratch[0].real()); + + Fout[m].real(Fout[m].real() - scratch[0].imag()); + Fout[m].imag(Fout[m].imag() + scratch[0].real()); + + ++Fout; + } + while(--m); +} + +void CKissFFT::kf_bfly4(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m) +{ + std::complex *tw1,*tw2,*tw3; + std::complex scratch[6]; + int k = m; + const int m2 = 2 * m; + const int m3 = 3 * m; + + + tw3 = tw2 = tw1 = st.twiddles.data(); + + do + { + scratch[0] = Fout[m] * *tw1; + scratch[1] = Fout[m2] * *tw2; + scratch[2] = Fout[m3] * *tw3; + + scratch[5] = *Fout - scratch[1]; + *Fout += scratch[1]; + scratch[3] = scratch[0] + scratch[2]; + scratch[4] = scratch[0] - scratch[2]; + Fout[m2] = *Fout - scratch[3]; + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + *Fout += scratch[3]; + + if(st.inverse) + { + Fout[m].real(scratch[5].real() - scratch[4].imag()); + Fout[m].imag(scratch[5].imag() + scratch[4].real()); + Fout[m3].real(scratch[5].real() + scratch[4].imag()); + Fout[m3].imag(scratch[5].imag() - scratch[4].real()); + } + else + { + Fout[m].real(scratch[5].real() + scratch[4].imag()); + Fout[m].imag(scratch[5].imag() - scratch[4].real()); + Fout[m3].real(scratch[5].real() - scratch[4].imag()); + Fout[m3].imag(scratch[5].imag() + scratch[4].real()); + } + ++Fout; + } + while(--k); +} + +void CKissFFT::kf_bfly5(std::complex * Fout, const size_t fstride, FFT_STATE &st, int m) +{ + std::complex scratch[13]; + std::complex *twiddles = st.twiddles.data(); + auto ya = twiddles[fstride*m]; + auto yb = twiddles[fstride*2*m]; + + auto Fout0 = Fout; + auto Fout1 = Fout0 + m; + auto Fout2 = Fout0 + 2 * m; + auto Fout3 = Fout0 + 3 * m; + auto Fout4 = Fout0 + 4 * m; + + auto tw = st.twiddles.data(); + for (int u=0; u *Fout, const size_t fstride, FFT_STATE &st, int m, int p) +{ + auto twiddles = st.twiddles.data(); + std::complex t; + int Norig = st.nfft; + + std::vector> scratch(p); + + for (int u=0; u= Norig) twidx-=Norig; + t = scratch[q] * twiddles[twidx]; + Fout[k] += t; + } + k += m; + } + } + scratch.clear(); +} + +void CKissFFT::kf_work(std::complex *Fout, const std::complex *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st) +{ + auto Fout_beg = Fout; + const int p = *factors++; /* the radix */ + const int m = *factors++; /* stage's fft length/p */ + const std::complex *Fout_end = Fout + p*m; + + if (m==1) + { + do + { + *Fout = *f; + f += fstride*in_stride; + } + while( ++Fout != Fout_end ); + } + else + { + do + { + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work( Fout, f, fstride*p, in_stride, factors, st); + f += fstride*in_stride; + } + while( (Fout += m) != Fout_end ); + } + + Fout=Fout_beg; + + // recombine the p smaller DFTs + switch (p) + { + case 2: + kf_bfly2(Fout,fstride,st,m); + break; + case 3: + kf_bfly3(Fout,fstride,st,m); + break; + case 4: + kf_bfly4(Fout,fstride,st,m); + break; + case 5: + kf_bfly5(Fout,fstride,st,m); + break; + default: + kf_bfly_generic(Fout,fstride,st,m,p); + break; + } +} + +/* facbuf is populated by p1,m1,p2,m2, ... + where + p[i] * m[i] = m[i-1] + m0 = n */ +void CKissFFT::kf_factor(int n,int * facbuf) +{ + int p=4; + double floor_sqrt; + floor_sqrt = floorf( sqrtf((double)n) ); + + /*factor out powers of 4, powers of 2, then any remaining primes */ + do + { + while (n % p) + { + switch (p) + { + case 4: + p = 2; + break; + case 2: + p = 3; + break; + default: + p += 2; + break; + } + if (p > floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } + while (n > 1); +} + +void CKissFFT::fft_alloc(FFT_STATE &state, const int nfft, bool inverse_fft) +{ + state.twiddles.resize(nfft); + + state.nfft = nfft; + state.inverse = inverse_fft; + + for (int i=0; i *fin, std::complex *fout, int in_stride) +{ + if (fin == fout) + { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + std::vector> tmpbuf(st.nfft); + kf_work(tmpbuf.data(), fin, true, in_stride, st.factors, st); + memcpy(fout, tmpbuf.data(), sizeof(std::complex)*st.nfft); + tmpbuf.clear(); + } + else + { + kf_work(fout, fin, 1, in_stride, st.factors, st); + } +} + +void CKissFFT::fft(FFT_STATE &cfg, const std::complex *fin, std::complex *fout) +{ + fft_stride(cfg, fin, fout, 1); +} + +int CKissFFT::fft_next_fast_size(int n) +{ + while(1) + { + int m = n; + while ( (m % 2) == 0 ) m /= 2; + while ( (m % 3) == 0 ) m /= 3; + while ( (m % 5) == 0 ) m /= 5; + if (m <= 1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +} + +void CKissFFT::fftr_alloc(FFTR_STATE &st, int nfft, const bool inverse_fft) +{ + nfft >>= 1; + + fft_alloc(st.substate, nfft, inverse_fft); + st.tmpbuf.resize(nfft); + st.super_twiddles.resize(nfft); + + for (int i=0; i *freqdata) +{ + assert(st.substate.inverse == false); + + auto ncfft = st.substate.nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + fft( st.substate, (const std::complex*)timedata, st.tmpbuf.data()); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + auto tdc = st.tmpbuf[0]; + freqdata[0].real(tdc.real() + tdc.imag()); + freqdata[ncfft].real(tdc.real() - tdc.imag()); + freqdata[ncfft].imag(0.f); + freqdata[0].imag(0.f); + + for (int k=1; k <= ncfft/2; ++k) + { + auto fpk = st.tmpbuf[k]; + auto fpnk = std::conj(st.tmpbuf[ncfft-k]); + + auto f1k = fpk + fpnk; + auto f2k = fpk - fpnk; + auto tw = f2k * st.super_twiddles[k-1]; + + freqdata[k] = 0.5f * (f1k + tw); + freqdata[ncfft-k].real(0.5f * (f1k.real() - tw.real())); + freqdata[ncfft-k].imag(0.5f * (tw.imag() - f1k.imag())); + } +} + +void CKissFFT::fftri(FFTR_STATE &st, const std::complex *freqdata, float *timedata) +{ + assert(st.substate.inverse == true); + + auto ncfft = st.substate.nfft; + + st.tmpbuf[0].real(freqdata[0].real() + freqdata[ncfft].real()); + st.tmpbuf[0].imag(freqdata[0].real() - freqdata[ncfft].real()); + + for (int k=1; k <= ncfft/2; ++k) + { + auto fk = freqdata[k]; + auto fnkc = std::conj(freqdata[ncfft - k]); + + auto fek = fk + fnkc; + auto tmp = fk - fnkc; + auto fok = tmp * st.super_twiddles[k-1]; + st.tmpbuf[k] = fek + fok; + st.tmpbuf[ncfft - k] = std::conj(fek - fok); + } + fft (st.substate, st.tmpbuf.data(), (std::complex *)timedata); +} diff --git a/codec2/kiss_fft.h b/codec2/kiss_fft.h new file mode 100644 index 0000000..7626617 --- /dev/null +++ b/codec2/kiss_fft.h @@ -0,0 +1,35 @@ +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include + +#include +#include +#include +#include + +#include "defines.h" + +/* for real ffts, we need an even size */ +#define kiss_fftr_next_fast_size_real(n) (kiss_fft_next_fast_size( ((n)+1) >> 1) << 1 ) + +class CKissFFT +{ +public: + void fft_alloc(FFT_STATE &state, const int nfft, const bool inverse_fft); + void fft(FFT_STATE &cfg, const std::complex *fin, std::complex *fout); + void fft_stride(FFT_STATE &cfg, const std::complex *fin, std::complex *fout, int fin_stride); + int fft_next_fast_size(int n); + void fftr_alloc(FFTR_STATE &state, int nfft, const bool inverse_fft); + void fftr(FFTR_STATE &cfg,const float *timedata,std::complex *freqdata); + void fftri(FFTR_STATE &cfg,const std::complex *freqdata,float *timedata); +private: + void kf_bfly2(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m); + void kf_bfly3(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m); + void kf_bfly4(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m); + void kf_bfly5(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m); + void kf_bfly_generic(std::complex *Fout, const size_t fstride, FFT_STATE &st, int m, int p); + void kf_work(std::complex *Fout, const std::complex *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st); + void kf_factor(int n, int *facbuf); +}; +#endif diff --git a/codec2/lpc.cpp b/codec2/lpc.cpp new file mode 100644 index 0000000..10f2c09 --- /dev/null +++ b/codec2/lpc.cpp @@ -0,0 +1,311 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: lpc.c + AUTHOR......: David Rowe + DATE CREATED: 30 Sep 1990 (!) + + Linear Prediction functions written in C. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2009-2012 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#define LPC_MAX_N 512 /* maximum no. of samples in frame */ +#define PI 3.141592654 /* mathematical constant */ + +#define ALPHA 1.0 +#define BETA 0.94 + +#include +#include +#include "defines.h" +#include "lpc.h" + +/*---------------------------------------------------------------------------*\ + + pre_emp() + + Pre-emphasise (high pass filter with zero close to 0 Hz) a frame of + speech samples. Helps reduce dynamic range of LPC spectrum, giving + greater weight and hense a better match to low energy formants. + + Should be balanced by de-emphasis of the output speech. + +\*---------------------------------------------------------------------------*/ + +void Clpc::pre_emp( + float Sn_pre[], /* output frame of speech samples */ + float Sn[], /* input frame of speech samples */ + float *mem, /* Sn[-1]single sample memory */ + int Nsam /* number of speech samples to use */ +) +{ + int i; + + for(i=0; i 1.0) + k = 0.0; + + a[i][i] = k; + + for(j=1; j<=i-1; j++) + a[i][j] = a[i-1][j] + k*a[i-1][i-j]; /* Equation 38c, Makhoul */ + + e *= (1-k*k); /* Equation 38d, Makhoul */ + } + + for(i=1; i<=order; i++) + lpcs[i] = a[order][i]; + lpcs[0] = 1.0; +} + +/*---------------------------------------------------------------------------*\ + + inverse_filter() + + Inverse Filter, A(z). Produces an array of residual samples from an array + of input samples and linear prediction coefficients. + + The filter memory is stored in the first order samples of the input array. + +\*---------------------------------------------------------------------------*/ + +void Clpc::inverse_filter( + float Sn[], /* Nsam input samples */ + float a[], /* LPCs for this frame of samples */ + int Nsam, /* number of samples */ + float res[], /* Nsam residual samples */ + int order /* order of LPC */ +) +{ + int i,j; /* loop variables */ + + for(i=0; i. +*/ + +#ifndef __LPC__ +#define __LPC__ + +#define LPC_MAX_ORDER 20 + +class Clpc { +public: + void autocorrelate(float Sn[], float Rn[], int Nsam, int order); + void levinson_durbin(float R[], float lpcs[], int order); +private: + void pre_emp(float Sn_pre[], float Sn[], float *mem, int Nsam); + void de_emp(float Sn_se[], float Sn[], float *mem, int Nsam); + void hanning_window(float Sn[], float Wn[], int Nsam); + void inverse_filter(float Sn[], float a[], int Nsam, float res[], int order); + void synthesis_filter(float res[], float a[], int Nsam, int order, float Sn_[]); + void find_aks(float Sn[], float a[], int Nsam, int order, float *E); + void weight(float ak[], float gamma, int order, float akw[]); +}; + +#endif diff --git a/codec2/nlp.cpp b/codec2/nlp.cpp new file mode 100644 index 0000000..753e96b --- /dev/null +++ b/codec2/nlp.cpp @@ -0,0 +1,520 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: nlp.c + AUTHOR......: David Rowe + DATE CREATED: 23/3/93 + + Non Linear Pitch (NLP) estimation functions. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2009 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include +#include + +#include "defines.h" +#include "nlp.h" +#include "kiss_fft.h" + +extern CKissFFT kiss; + +/*---------------------------------------------------------------------------*\ + + GLOBALS + +\*---------------------------------------------------------------------------*/ + +/* 48 tap 600Hz low pass FIR filter coefficients */ + +static const float nlp_fir[] = +{ + -1.0818124e-03, + -1.1008344e-03, + -9.2768838e-04, + -4.2289438e-04, + 5.5034190e-04, + 2.0029849e-03, + 3.7058509e-03, + 5.1449415e-03, + 5.5924666e-03, + 4.3036754e-03, + 8.0284511e-04, + -4.8204610e-03, + -1.1705810e-02, + -1.8199275e-02, + -2.2065282e-02, + -2.0920610e-02, + -1.2808831e-02, + 3.2204775e-03, + 2.6683811e-02, + 5.5520624e-02, + 8.6305944e-02, + 1.1480192e-01, + 1.3674206e-01, + 1.4867556e-01, + 1.4867556e-01, + 1.3674206e-01, + 1.1480192e-01, + 8.6305944e-02, + 5.5520624e-02, + 2.6683811e-02, + 3.2204775e-03, + -1.2808831e-02, + -2.0920610e-02, + -2.2065282e-02, + -1.8199275e-02, + -1.1705810e-02, + -4.8204610e-03, + 8.0284511e-04, + 4.3036754e-03, + 5.5924666e-03, + 5.1449415e-03, + 3.7058509e-03, + 2.0029849e-03, + 5.5034190e-04, + -4.2289438e-04, + -9.2768838e-04, + -1.1008344e-03, + -1.0818124e-03 +}; + +static const float fdmdv_os_filter[]= { + -0.0008215855034550382, + -0.0007833023901802921, + 0.001075563790768233, + 0.001199092367787555, + -0.001765309502928316, + -0.002055372115328064, + 0.002986877604154257, + 0.003462567920638414, + -0.004856570111126334, + -0.005563143845031497, + 0.007533613299748122, + 0.008563932468880897, + -0.01126857129039911, + -0.01280782411693687, + 0.01651443896361847, + 0.01894875110322284, + -0.02421604439474981, + -0.02845107338464062, + 0.03672973563400258, + 0.04542046150312214, + -0.06189165826716491, + -0.08721876380763803, + 0.1496157094199961, + 0.4497962274137046, + 0.4497962274137046, + 0.1496157094199961, + -0.08721876380763803, + -0.0618916582671649, + 0.04542046150312216, + 0.03672973563400257, + -0.02845107338464062, + -0.02421604439474984, + 0.01894875110322284, + 0.01651443896361848, + -0.01280782411693687, + -0.0112685712903991, + 0.008563932468880899, + 0.007533613299748123, + -0.005563143845031501, + -0.004856570111126346, + 0.003462567920638419, + 0.002986877604154259, + -0.002055372115328063, + -0.001765309502928318, + 0.001199092367787557, + 0.001075563790768233, + -0.0007833023901802925, + -0.0008215855034550383 +}; + +/*---------------------------------------------------------------------------*\ + + nlp_create() + + Initialisation function for NLP pitch estimator. + +\*---------------------------------------------------------------------------*/ + +void Cnlp::nlp_create(C2CONST *c2const) +{ + int i; + int m = c2const->m_pitch; + int Fs = c2const->Fs; + + assert((Fs == 8000) || (Fs == 16000)); + snlp.Fs = Fs; + + snlp.m = m; + + /* if running at 16kHz allocate storage for decimating filter memory */ + + if (Fs == 16000) + { + snlp.Sn16k.resize(FDMDV_OS_TAPS_16K + c2const->n_samp); + for(i=0; i Sw[], /* Freq domain version of Sn[] */ +// float W[], /* Freq domain window */ + float *prev_f0 /* previous pitch f0 in Hz, memory for pitch tracking */ +) +{ + float notch; /* current notch filter output */ + std::complex Fw[PE_FFT_SIZE]; /* DFT of squared signal (input/output) */ + float gmax; + int gmax_bin; + int m, i, j; + float best_f0; + + m = snlp.m; + + /* Square, notch filter at DC, and LP filter vector */ + + /* If running at 16 kHz decimate to 8 kHz, as NLP ws designed for + Fs = 8kHz. The decimating filter introduces about 3ms of delay, + that shouldn't be a problem as pitch changes slowly. */ + + if (snlp.Fs == 8000) + { + /* Square latest input samples */ + + for(i=m-n; i gmax) + { + gmax = Fw[i].real(); + gmax_bin = i; + } + } + + best_f0 = post_process_sub_multiples(Fw, pmax, gmax, gmax_bin, prev_f0); + + /* Shift samples in buffer to make room for new samples */ + + for(i=0; i Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0) +{ + int min_bin, cmax_bin; + int mult; + float thresh, best_f0; + int b, bmin, bmax, lmax_bin; + float lmax; + int prev_f0_bin; + + /* post process estimate by searching submultiples */ + + mult = 2; + min_bin = PE_FFT_SIZE*DEC/pmax; + cmax_bin = gmax_bin; + prev_f0_bin = *prev_f0*(PE_FFT_SIZE*DEC)/SAMPLE_RATE; + + while(gmax_bin/mult >= min_bin) + { + + b = gmax_bin/mult; /* determine search interval */ + bmin = 0.8*b; + bmax = 1.2*b; + if (bmin < min_bin) + bmin = min_bin; + + /* lower threshold to favour previous frames pitch estimate, + this is a form of pitch tracking */ + + if ((prev_f0_bin > bmin) && (prev_f0_bin < bmax)) + thresh = CNLP*0.5*gmax; + else + thresh = CNLP*gmax; + + lmax = 0; + lmax_bin = bmin; + for (b=bmin; b<=bmax; b++) /* look for maximum in interval */ + if (Fw[b].real() > lmax) + { + lmax = Fw[b].real(); + lmax_bin = b; + } + + if (lmax > thresh) + if ((lmax > Fw[lmax_bin-1].real()) && (lmax > Fw[lmax_bin+1].real())) + { + cmax_bin = lmax_bin; + } + + mult++; + } + + best_f0 = (float)cmax_bin*SAMPLE_RATE/(PE_FFT_SIZE*DEC); + + return best_f0; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_16_to_8() + AUTHOR......: David Rowe + DATE CREATED: 9 May 2012 + + Changes the sample rate of a signal from 16 to 8 kHz. + + n is the number of samples at the 8 kHz rate, there are FDMDV_OS*n + samples at the 48 kHz rate. As above however a memory of + FDMDV_OS_TAPS samples is reqd for in16k[] (see t16_8.c unit test as example). + + Low pass filter the 16 kHz signal at 4 kHz using the same filter as + the upsampler, then just output every FDMDV_OS-th filtered sample. + + Note: this function copied from fdmdv.c, included in nlp.c as a convenience + to avoid linking with another source file. + +\*---------------------------------------------------------------------------*/ + +void Cnlp::fdmdv_16_to_8(float out8k[], float in16k[], int n) +{ + float acc; + int i,j,k; + + for(i=0, k=0; k *inout) +{ + std::complex in[512]; + // decide whether to use the local stack based buffer for in + // or to allow kiss_fft to allocate RAM + // second part is just to play safe since first method + // is much faster and uses less RAM + if (cfg.nfft <= 512) + { + memcpy(in, inout, cfg.nfft*sizeof(std::complex)); + kiss.fft(cfg, in, inout); + } + else + { + kiss.fft(cfg, inout, inout); + } +} diff --git a/codec2/nlp.h b/codec2/nlp.h new file mode 100644 index 0000000..f480b24 --- /dev/null +++ b/codec2/nlp.h @@ -0,0 +1,87 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: nlp.c + AUTHOR......: David Rowe + DATE CREATED: 23/3/93 + + Non Linear Pitch (NLP) estimation functions. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2009 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef __NLP__ +#define __NLP__ + +#include +#include + +#include "defines.h" + +/*---------------------------------------------------------------------------*\ + + DEFINES + +\*---------------------------------------------------------------------------*/ + +#define PMAX_M 320 /* maximum NLP analysis window size */ +#define COEFF 0.95 /* notch filter parameter */ +#define PE_FFT_SIZE 512 /* DFT size for pitch estimation */ +#define DEC 5 /* decimation factor */ +#define SAMPLE_RATE 8000 +#define PI 3.141592654 /* mathematical constant */ +//#define T 0.1 /* threshold for local minima candidate */ +#define F0_MAX 500 +#define CNLP 0.3 /* post processor constant */ +#define NLP_NTAP 48 /* Decimation LPF order */ + +/* 8 to 16 kHz sample rate conversion */ + +#define FDMDV_OS 2 /* oversampling rate */ +#define FDMDV_OS_TAPS_16K 48 /* number of OS filter taps at 16kHz */ +#define FDMDV_OS_TAPS_8K (FDMDV_OS_TAPS_16K/FDMDV_OS) /* number of OS filter taps at 8kHz */ + + +using NLP = struct nlp_tag +{ + int Fs; /* sample rate in Hz */ + int m; + float w[PMAX_M/DEC]; /* DFT window */ + float sq[PMAX_M]; /* squared speech samples */ + float mem_x,mem_y; /* memory for notch filter */ + float mem_fir[NLP_NTAP]; /* decimation FIR filter memory */ + FFT_STATE fft_cfg; /* kiss FFT config */ + std::vector Sn16k; /* Fs=16kHz input speech vector */ +}; + + +class Cnlp { +public: + void nlp_create(C2CONST *c2const); + void nlp_destroy(); + float nlp(float Sn[], int n, float *pitch_samples, float *prev_f0); + void codec2_fft_inplace(FFT_STATE &cfg, std::complex *inout); + +private: + float post_process_sub_multiples(std::complex Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0); + void fdmdv_16_to_8(float out8k[], float in16k[], int n); + + NLP snlp; +}; + +#endif diff --git a/codec2/pack.cpp b/codec2/pack.cpp new file mode 100644 index 0000000..8382f95 --- /dev/null +++ b/codec2/pack.cpp @@ -0,0 +1,139 @@ +/* + Copyright (C) 2010 Perens LLC + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. 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 Lesser General Public License + along with this program; if not, see . +*/ + +#include "defines.h" +#include "quantise.h" +#include + +/* Compile-time constants */ +/* Size of unsigned char in bits. Assumes 8 bits-per-char. */ +static const unsigned int WordSize = 8; + +/* Mask to pick the bit component out of bitIndex. */ +static const unsigned int IndexMask = 0x7; + +/* Used to pick the word component out of bitIndex. */ +static const unsigned int ShiftRight = 3; + +/** Pack a bit field into a bit string, encoding the field in Gray code. + * + * The output is an array of unsigned char data. The fields are efficiently + * packed into the bit string. The Gray coding is a naive attempt to reduce + * the effect of single-bit errors, we expect to do a better job as the + * codec develops. + * + * This code would be simpler if it just set one bit at a time in the string, + * but would hit the same cache line more often. I'm not sure the complexity + * gains us anything here. + * + * Although field is currently of int type rather than unsigned for + * compatibility with the rest of the code, indices are always expected to + * be >= 0. + */ +void CQuantize::pack( + unsigned char *bitArray, /* The output bit string. */ + unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/ + int field, /* The bit field to be packed. */ + unsigned int fieldWidth /* Width of the field in BITS, not bytes. */ +) +{ + pack_natural_or_gray(bitArray, bitIndex, field, fieldWidth, 1); +} + +void CQuantize::pack_natural_or_gray( + unsigned char *bitArray, /* The output bit string. */ + unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/ + int field, /* The bit field to be packed. */ + unsigned int fieldWidth, /* Width of the field in BITS, not bytes. */ + unsigned int gray /* non-zero for gray coding */ +) +{ + if (gray) + { + /* Convert the field to Gray code */ + field = (field >> 1) ^ field; + } + + do + { + unsigned int bI = *bitIndex; + unsigned int bitsLeft = WordSize - (bI & IndexMask); + unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth; + unsigned int wordIndex = bI >> ShiftRight; + + bitArray[wordIndex] |= ((unsigned char)((field >> (fieldWidth - sliceWidth)) << (bitsLeft - sliceWidth))); + + *bitIndex = bI + sliceWidth; + fieldWidth -= sliceWidth; + } + while ( fieldWidth != 0 ); +} + +/** Unpack a field from a bit string, converting from Gray code to binary. + * + */ +int CQuantize::unpack( + const unsigned char *bitArray, /* The input bit string. */ + unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/ + unsigned int fieldWidth/* Width of the field in BITS, not bytes. */ +) +{ + return unpack_natural_or_gray(bitArray, bitIndex, fieldWidth, 1); +} + +/** Unpack a field from a bit string, to binary, optionally using + * natural or Gray code. + * + */ +int CQuantize::unpack_natural_or_gray( + const unsigned char *bitArray, /* The input bit string. */ + unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/ + unsigned int fieldWidth,/* Width of the field in BITS, not bytes. */ + unsigned int gray /* non-zero for Gray coding */ +) +{ + unsigned int field = 0; + unsigned int t; + + do + { + unsigned int bI = *bitIndex; + unsigned int bitsLeft = WordSize - (bI & IndexMask); + unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth; + + field |= (((bitArray[bI >> ShiftRight] >> (bitsLeft - sliceWidth)) & ((1 << sliceWidth) - 1)) << (fieldWidth - sliceWidth)); + + *bitIndex = bI + sliceWidth; + fieldWidth -= sliceWidth; + } + while ( fieldWidth != 0 ); + + if (gray) + { + /* Convert from Gray code to binary. Works for maximum 8-bit fields. */ + t = field ^ (field >> 8); + t ^= (t >> 4); + t ^= (t >> 2); + t ^= (t >> 1); + } + else + { + t = field; + } + + return t; +} diff --git a/codec2/qbase.cpp b/codec2/qbase.cpp new file mode 100644 index 0000000..a94ef46 --- /dev/null +++ b/codec2/qbase.cpp @@ -0,0 +1,247 @@ +#include +#include + +#include "qbase.h" + +/*---------------------------------------------------------------------------*\ + + quantise + + Quantises vec by choosing the nearest vector in codebook cb, and + returns the vector index. The squared error of the quantised vector + is added to se. + +\*---------------------------------------------------------------------------*/ + +long CQbase::quantise(const float *cb, float vec[], float w[], int k, int m, float *se) +/* float cb[][K]; current VQ codebook */ +/* float vec[]; vector to quantise */ +/* float w[]; weighting vector */ +/* int k; dimension of vectors */ +/* int m; size of codebook */ +/* float *se; accumulated squared error */ +{ + float e; /* current error */ + long besti; /* best index so far */ + float beste; /* best error so far */ + long j; + int i; + float diff; + + besti = 0; + beste = 1E32; + for(j=0; jWo/PI)*4000.0/50.0)/log10f(2); + x[1] = 10.0*log10f(1e-4 + e); + + compute_weights2(x, xq, w); + for (i=0; iWo_min; + float Wo_max = c2const->Wo_max; + + for (i=0; iWo = powf(2.0, xq[0])*(PI*50.0)/4000.0; + + /* bit errors can make us go out of range leading to all sorts of + probs like seg faults */ + + if (model->Wo > Wo_max) model->Wo = Wo_max; + if (model->Wo < Wo_min) model->Wo = Wo_min; + + model->L = PI/model->Wo; /* if we quantise Wo re-compute L */ + + *e = exp10f(xq[1]/10.0); +} + +void CQbase::compute_weights2(const float *x, const float *xp, float *w) +{ + w[0] = 30; + w[1] = 1; + if (x[1]<0) + { + w[0] *= .6; + w[1] *= .3; + } + if (x[1]<-10) + { + w[0] *= .3; + w[1] *= .3; + } + /* Higher weight if pitch is stable */ + if (fabsf(x[0]-xp[0])<.2) + { + w[0] *= 2; + w[1] *= 1.5; + } + else if (fabsf(x[0]-xp[0])>.5) /* Lower if not stable */ + { + w[0] *= .5; + } + + /* Lower weight for low energy */ + if (x[1] < xp[1]-10) + { + w[1] *= .5; + } + if (x[1] < xp[1]-20) + { + w[1] *= .5; + } + + //w[0] = 30; + //w[1] = 1; + + /* Square the weights because it's applied on the squared error */ + w[0] *= w[0]; + w[1] *= w[1]; + +} + +int CQbase::find_nearest_weighted(const float *codebook, int nb_entries, float *x, const float *w, int ndim) +{ + int i, j; + float min_dist = 1e15; + int nearest = 0; + + for (i=0; iWo_min; + float Wo_max = c2const->Wo_max; + float norm; + + norm = (log10f(Wo) - log10f(Wo_min))/(log10f(Wo_max) - log10f(Wo_min)); + index = floorf(Wo_levels * norm + 0.5); + if (index < 0 ) index = 0; + if (index > (Wo_levels-1)) index = Wo_levels-1; + + return index; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: decode_log_Wo() + AUTHOR......: David Rowe + DATE CREATED: 22/8/2010 + + Decodes Wo using a WO_LEVELS quantiser in the log domain. + +\*---------------------------------------------------------------------------*/ + +float CQbase::decode_log_Wo(C2CONST *c2const, int index, int bits) +{ + float Wo_min = c2const->Wo_min; + float Wo_max = c2const->Wo_max; + float step; + float Wo; + int Wo_levels = 1<. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "defines.h" +#include "quantise.h" +#include "lpc.h" +#include "kiss_fft.h" + +extern CKissFFT kiss; + +#define LSP_DELTA1 0.01 /* grid spacing for LSP root searches */ + +/*---------------------------------------------------------------------------*\ + + FUNCTIONS + +\*---------------------------------------------------------------------------*/ + +int CQuantize::lsp_bits(int i) +{ + return lsp_cb[i].log2m; +} + +int CQuantize::lspd_bits(int i) +{ + return lsp_cbd[i].log2m; +} + + + +/*---------------------------------------------------------------------------*\ + + encode_lspds_scalar() + + Scalar/VQ LSP difference quantiser. + +\*---------------------------------------------------------------------------*/ + +void CQuantize::encode_lspds_scalar(int indexes[], float lsp[], int order) +{ + int i,k,m; + float lsp_hz[order]; + float lsp__hz[order]; + float dlsp[order]; + float dlsp_[order]; + float wt[order]; + const float *cb; + float se; + + for(i=0; i Ww[FFT_ENC/2+1]; /* weighting spectrum */ + float Rw[FFT_ENC/2+1]; /* R = WA */ + float e_before, e_after, gain; + float Pfw; + float max_Rw, min_Rw; + float coeff; + + /* Determine weighting filter spectrum W(exp(jw)) ---------------*/ + + for(i=0; i max_Rw) + max_Rw = Rw[i]; + if (Rw[i] < min_Rw) + min_Rw = Rw[i]; + + } + + /* create post filter mag spectrum and apply ------------------*/ + + /* measure energy before post filtering */ + + e_before = 1E-4; + for(i=0; i Aw[] /* output power spectrum */ +) +{ + int i,m; /* loop variables */ + int am,bm; /* limits of current band */ + float r; /* no. rads/bin */ + float Em; /* energy in band */ + float Am; /* spectral amplitude sample */ + float signal, noise; + + r = TWO_PI/(FFT_ENC); + + /* Determine DFT of A(exp(jw)) --------------------------------------------*/ + { + float a[FFT_ENC]; /* input to FFT for power spectrum */ + + for(i=0; iL; m++) + { + am = (int)((m - 0.5)*model->Wo/r + 0.5); + bm = (int)((m + 0.5)*model->Wo/r + 0.5); + + // FIXME: With arm_rfft_fast_f32 we have to use this + // otherwise sometimes a to high bm is calculated + // which causes trouble later in the calculation + // chain + // it seems for some reason model->Wo is calculated somewhat too high + if (bm>FFT_ENC/2) + { + bm = FFT_ENC/2; + } + Em = 0.0; + + for(i=am; iA[m]*model->A[m]; + noise += (model->A[m] - Am)*(model->A[m] - Am); + + /* This code significantly improves perf of LPC model, in + particular when combined with phase0. The LPC spectrum tends + to track just under the peaks of the spectral envelope, and + just above nulls. This algorithm does the reverse to + compensate - raising the amplitudes of spectral peaks, while + attenuating the null. This enhances the formants, and + supresses the energy between formants. */ + + if (sim_pf) + { + if (Am > model->A[m]) + Am *= 0.7; + if (Am < model->A[m]) + Am *= 1.4; + } + model->A[m] = Am; + } + *snr = 10.0*log10f(signal/noise); +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: encode_Wo() + AUTHOR......: David Rowe + DATE CREATED: 22/8/2010 + + Encodes Wo using a WO_LEVELS quantiser. + +\*---------------------------------------------------------------------------*/ + +int CQuantize::encode_Wo(C2CONST *c2const, float Wo, int bits) +{ + int index, Wo_levels = 1<Wo_min; + float Wo_max = c2const->Wo_max; + float norm; + + norm = (Wo - Wo_min)/(Wo_max - Wo_min); + index = floorf(Wo_levels * norm + 0.5); + if (index < 0 ) index = 0; + if (index > (Wo_levels-1)) index = Wo_levels-1; + + return index; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: decode_Wo() + AUTHOR......: David Rowe + DATE CREATED: 22/8/2010 + + Decodes Wo using a WO_LEVELS quantiser. + +\*---------------------------------------------------------------------------*/ + +float CQuantize::decode_Wo(C2CONST *c2const, int index, int bits) +{ + float Wo_min = c2const->Wo_min; + float Wo_max = c2const->Wo_max; + float step; + float Wo; + int Wo_levels = 1<Wo < (PI*150.0/4000)) + { + model->A[1] *= 0.032; + } +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: encode_energy() + AUTHOR......: David Rowe + DATE CREATED: 22/8/2010 + + Encodes LPC energy using an E_LEVELS quantiser. + +\*---------------------------------------------------------------------------*/ + +int CQuantize::encode_energy(float e, int bits) +{ + int index, e_levels = 1< (e_levels-1)) index = e_levels-1; + + return index; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: decode_energy() + AUTHOR......: David Rowe + DATE CREATED: 22/8/2010 + + Decodes energy using a E_LEVELS quantiser. + +\*---------------------------------------------------------------------------*/ + +float CQuantize::decode_energy(int index, int bits) +{ + float e_min = E_MIN_DB; + float e_max = E_MAX_DB; + float step; + float e; + int e_levels = 1<= -1.0)) + { + xr = xl - delta ; /* interval spacing */ + psumr = cheb_poly_eva(pt,xr,order);/* poly(xl-delta_x) */ + temp_psumr = psumr; + temp_xr = xr; + + /* if no sign change increment xr and re-evaluate + poly(xr). Repeat til sign change. if a sign change has + occurred the interval is bisected and then checked again + for a sign change which determines in which interval the + zero lies in. If there is no sign change between poly(xm) + and poly(xl) set interval between xm and xr else set + interval between xl and xr and repeat till root is located + within the specified limits */ + + if(((psumr*psuml)<0.0) || (psumr == 0.0)) + { + roots++; + + psumm=psuml; + for(k=0; k<=nb; k++) + { + xm = (xl+xr)/2; /* bisect the interval */ + psumm=cheb_poly_eva(pt,xm,order); + if(psumm*psuml>0.) + { + psuml=psumm; + xl=xm; + } + else + { + psumr=psumm; + xr=xm; + } + } + + /* once zero is found, reset initial interval to xr */ + freq[j] = (xm); + xl = xm; + flag = 0; /* reset flag for next search */ + } + else + { + psuml=temp_psumr; + xl=temp_xr; + } + } + } + + /* convert from x domain to radians */ + + for(i=0; i. +*/ + +#ifndef __QUANTISE__ +#define __QUANTISE__ + +#include + +#include "qbase.h" + +class CQuantize : public CQbase { +public: + void aks_to_M2(FFTR_STATE *fftr_fwd_cfg, float ak[], int order, MODEL *model, float E, float *snr, int sim_pf, int pf, int bass_boost, float beta, float gamma, std::complex Aw[]); + + int encode_Wo(C2CONST *c2const, float Wo, int bits); + float decode_Wo(C2CONST *c2const, int index, int bits); + void encode_lsps_scalar(int indexes[], float lsp[], int order); + void decode_lsps_scalar(float lsp[], int indexes[], int order); + void encode_lspds_scalar(int indexes[], float lsp[], int order); + void decode_lspds_scalar(float lsp[], int indexes[], int order); + + int encode_energy(float e, int bits); + float decode_energy(int index, int bits); + + void pack(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits); + void pack_natural_or_gray(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits, unsigned int gray); + int unpack(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits); + int unpack_natural_or_gray(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits, unsigned int gray); + + int lsp_bits(int i); + int lspd_bits(int i); + + void apply_lpc_correction(MODEL *model); + float speech_to_uq_lsps(float lsp[], float ak[], float Sn[], float w[], int m_pitch, int order); + int check_lsp_order(float lsp[], int lpc_order); + void bw_expand_lsps(float lsp[], int order, float min_sep_low, float min_sep_high); + +private: + void compute_weights(const float *x, float *w, int ndim); + int find_nearest(const float *codebook, int nb_entries, float *x, int ndim); + void lpc_post_filter(FFTR_STATE *fftr_fwd_cfg, float Pw[], float ak[], int order, float beta, float gamma, int bass_boost, float E); + int lpc_to_lsp (float *a, int lpcrdr, float *freq, int nb, float delta); + float cheb_poly_eva(float *coef,float x,int order); +}; + +#endif diff --git a/crs129.cpp b/crs129.cpp new file mode 100644 index 0000000..182a438 --- /dev/null +++ b/crs129.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "crs129.h" + +#include +#include +#include + +const unsigned int NPAR = 3U; + +/* Maximum degree of various polynomials. */ +//const unsigned int MAXDEG = NPAR * 2U; + +/* Generator Polynomial */ +const unsigned char POLY[] = {64U, 56U, 14U, 1U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U}; + +const unsigned char EXP_TABLE[] = { + 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U, + 0x4CU, 0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U, + 0x9DU, 0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U, + 0x46U, 0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U, + 0x5FU, 0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U, + 0xFDU, 0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U, + 0xD9U, 0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU, + 0x81U, 0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU, + 0x85U, 0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U, + 0xA8U, 0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U, + 0xE6U, 0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU, + 0xE3U, 0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U, + 0x82U, 0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U, + 0x51U, 0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U, + 0x12U, 0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U, + 0x2CU, 0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U, + 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U, 0x4CU, + 0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U, 0x9DU, + 0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U, 0x46U, + 0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U, 0x5FU, + 0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U, 0xFDU, + 0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U, 0xD9U, + 0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU, 0x81U, + 0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU, 0x85U, + 0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U, 0xA8U, + 0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U, 0xE6U, + 0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU, 0xE3U, + 0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U, 0x82U, + 0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U, 0x51U, + 0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U, 0x12U, + 0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U, 0x2CU, + 0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U, 0x00U}; + +const unsigned char LOG_TABLE[] = { + 0x00U, 0x00U, 0x01U, 0x19U, 0x02U, 0x32U, 0x1AU, 0xC6U, 0x03U, 0xDFU, 0x33U, 0xEEU, 0x1BU, 0x68U, 0xC7U, 0x4BU, + 0x04U, 0x64U, 0xE0U, 0x0EU, 0x34U, 0x8DU, 0xEFU, 0x81U, 0x1CU, 0xC1U, 0x69U, 0xF8U, 0xC8U, 0x08U, 0x4CU, 0x71U, + 0x05U, 0x8AU, 0x65U, 0x2FU, 0xE1U, 0x24U, 0x0FU, 0x21U, 0x35U, 0x93U, 0x8EU, 0xDAU, 0xF0U, 0x12U, 0x82U, 0x45U, + 0x1DU, 0xB5U, 0xC2U, 0x7DU, 0x6AU, 0x27U, 0xF9U, 0xB9U, 0xC9U, 0x9AU, 0x09U, 0x78U, 0x4DU, 0xE4U, 0x72U, 0xA6U, + 0x06U, 0xBFU, 0x8BU, 0x62U, 0x66U, 0xDDU, 0x30U, 0xFDU, 0xE2U, 0x98U, 0x25U, 0xB3U, 0x10U, 0x91U, 0x22U, 0x88U, + 0x36U, 0xD0U, 0x94U, 0xCEU, 0x8FU, 0x96U, 0xDBU, 0xBDU, 0xF1U, 0xD2U, 0x13U, 0x5CU, 0x83U, 0x38U, 0x46U, 0x40U, + 0x1EU, 0x42U, 0xB6U, 0xA3U, 0xC3U, 0x48U, 0x7EU, 0x6EU, 0x6BU, 0x3AU, 0x28U, 0x54U, 0xFAU, 0x85U, 0xBAU, 0x3DU, + 0xCAU, 0x5EU, 0x9BU, 0x9FU, 0x0AU, 0x15U, 0x79U, 0x2BU, 0x4EU, 0xD4U, 0xE5U, 0xACU, 0x73U, 0xF3U, 0xA7U, 0x57U, + 0x07U, 0x70U, 0xC0U, 0xF7U, 0x8CU, 0x80U, 0x63U, 0x0DU, 0x67U, 0x4AU, 0xDEU, 0xEDU, 0x31U, 0xC5U, 0xFEU, 0x18U, + 0xE3U, 0xA5U, 0x99U, 0x77U, 0x26U, 0xB8U, 0xB4U, 0x7CU, 0x11U, 0x44U, 0x92U, 0xD9U, 0x23U, 0x20U, 0x89U, 0x2EU, + 0x37U, 0x3FU, 0xD1U, 0x5BU, 0x95U, 0xBCU, 0xCFU, 0xCDU, 0x90U, 0x87U, 0x97U, 0xB2U, 0xDCU, 0xFCU, 0xBEU, 0x61U, + 0xF2U, 0x56U, 0xD3U, 0xABU, 0x14U, 0x2AU, 0x5DU, 0x9EU, 0x84U, 0x3CU, 0x39U, 0x53U, 0x47U, 0x6DU, 0x41U, 0xA2U, + 0x1FU, 0x2DU, 0x43U, 0xD8U, 0xB7U, 0x7BU, 0xA4U, 0x76U, 0xC4U, 0x17U, 0x49U, 0xECU, 0x7FU, 0x0CU, 0x6FU, 0xF6U, + 0x6CU, 0xA1U, 0x3BU, 0x52U, 0x29U, 0x9DU, 0x55U, 0xAAU, 0xFBU, 0x60U, 0x86U, 0xB1U, 0xBBU, 0xCCU, 0x3EU, 0x5AU, + 0xCBU, 0x59U, 0x5FU, 0xB0U, 0x9CU, 0xA9U, 0xA0U, 0x51U, 0x0BU, 0xF5U, 0x16U, 0xEBU, 0x7AU, 0x75U, 0x2CU, 0xD7U, + 0x4FU, 0xAEU, 0xD5U, 0xE9U, 0xE6U, 0xE7U, 0xADU, 0xE8U, 0x74U, 0xD6U, 0xF4U, 0xEAU, 0xA8U, 0x50U, 0x58U, 0xAFU}; + +/* multiplication using logarithms */ +static unsigned char gmult(unsigned char a, unsigned char b) +{ + if (a == 0U || b == 0U) + return 0U; + + unsigned int i = LOG_TABLE[a]; + unsigned int j = LOG_TABLE[b]; + + return EXP_TABLE[i + j]; +} + +/* Simulate a LFSR with generator polynomial for n byte RS code. + * Pass in a pointer to the data array, and amount of data. + * + * The parity bytes are deposited into parity. + */ +void CRS129::encode(const unsigned char* msg, unsigned int nbytes, unsigned char* parity) +{ + assert(msg != NULL); + assert(parity != NULL); + + for (unsigned int i = 0U; i < NPAR + 1U; i++) + parity[i] = 0x00U; + + for (unsigned int i = 0U; i < nbytes; i++) { + unsigned char dbyte = msg[i] ^ parity[NPAR - 1U]; + + for (int j = NPAR - 1; j > 0; j--) + parity[j] = parity[j - 1] ^ ::gmult(POLY[j], dbyte); + + parity[0] = ::gmult(POLY[0], dbyte); + } +} + +// Reed-Solomon (12,9) check +bool CRS129::check(const unsigned char* in) +{ + assert(in != NULL); + + unsigned char parity[4U]; + encode(in, 9U, parity); + + return in[9U] == parity[2U] && in[10U] == parity[1U] && in[11U] == parity[0U]; +} + diff --git a/crs129.h b/crs129.h new file mode 100644 index 0000000..57fb848 --- /dev/null +++ b/crs129.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(RS129_H) +#define RS129_H + +class CRS129 +{ +public: + static bool check(const unsigned char* in); + + static void encode(const unsigned char* msg, unsigned int nbytes, unsigned char* parity); +}; + +#endif diff --git a/dcscodec.cpp b/dcscodec.cpp new file mode 100755 index 0000000..9d9fc9a --- /dev/null +++ b/dcscodec.cpp @@ -0,0 +1,610 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ +#include +#include +#include "dcscodec.h" +#include "CRCenc.h" + +#define DEBUG + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +DCSCodec::DCSCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, module, hostname, host, port, ipv6, vocoder, modem, audioin, audioout) +{ +} + +DCSCodec::~DCSCodec() +{ +} + +void DCSCodec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + static bool sd_sync = 0; + static int sd_seq = 0; + static char user_data[21]; + buf.resize(200); + //qDebug() << "buf size before == " << buf.size(); + //buf.resize(m_udp->pendingDatagramSize()); + //qDebug() << "buf size after == " << buf.size(); + int size = m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < size; ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if(size == 22){ //2 way keep alive ping + m_modeinfo.count++; + m_modeinfo.netmsg.clear(); + if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + } + + if( (m_modeinfo.status == CONNECTING) && (size == 14) && (!memcmp(buf.data()+10, "ACK", 3)) ){ + qDebug() << "Connected to DCS"; + m_modeinfo.status = CONNECTED_RW; + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("DCS"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + else{ + m_hwrx = false; + m_hwtx = false; + } + if(m_modemport != ""){ + m_modem = new SerialModem("DCS"); + m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex); + m_modem->set_modem_params(m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel); + m_modem->connect_to_serial(m_modemport); + connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray))); + } + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(2000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + //fprintf(stderr, "m_vocoder == %s m_hwtx:m_hwrx == %d:%d\n", m_vocoder.toStdString().c_str(), m_hwtx, m_hwrx);fflush(stderr); + } + + if(m_modeinfo.status != CONNECTED_RW) return; + if(size == 35){ + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + m_modeinfo.netmsg = QString(buf.data()); + } + if((size == 100) && (!memcmp(buf.data(), "0001", 4)) ){ + m_rxwatchdog = 0; + //qDebug() << "m_streamid == " << m_streamid << ":" << m_hwrx << ":" << m_tx; + uint16_t streamid = (buf.data()[43] << 8) | (buf.data()[44] & 0xff); + + if(!m_tx && (m_modeinfo.streamid == 0)){ + m_modeinfo.streamid = streamid; + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + m_rxcodecq.clear(); + } + + char temp[9]; + memcpy(temp, buf.data() + 7, 8); temp[8] = '\0'; + m_modeinfo.gw2 = QString(temp); + memcpy(temp, buf.data() + 15, 8); temp[8] = '\0'; + m_modeinfo.gw = QString(temp); + memcpy(temp, buf.data() + 23, 8); temp[8] = '\0'; + m_modeinfo.dst = QString(temp); + memcpy(temp, buf.data() + 31, 8); temp[8] = '\0'; + m_modeinfo.src = QString(temp); + QString h = m_hostname + " " + m_module; + + if(m_modem){ + uint8_t out[44]; + out[0] = 0xe0; + out[1] = 44; + out[2] = MMDVM_DSTAR_HEADER; + out[3] = 0x40; + out[4] = 0; + out[5] = 0; + memcpy(out + 6, m_modeinfo.gw2.toLocal8Bit().data(), 8); + memcpy(out + 14, m_modeinfo.gw.toLocal8Bit().data(), 8); + memcpy(out + 22, m_modeinfo.dst.toLocal8Bit().data(), 8); + memcpy(out + 30, m_modeinfo.src.toLocal8Bit().data(), 8); + memcpy(out + 38, buf.data() + 52, 4); + CCRC::addCCITT161((uint8_t *)out + 3, 41); + for(int i = 0; i < 44; ++i){ + m_rxmodemq.append(out[i]); + } + //m_modem->write(out); + } + qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16); + } + else{ + m_modeinfo.stream_state = STREAMING; + } + + m_modeinfo.frame_number = buf.data()[0x2d]; + + if((buf.data()[45] == 0) && (buf.data()[55] == 0x55) && (buf.data()[56] == 0x2d) && (buf.data()[57] == 0x16)){ + sd_sync = 1; + sd_seq = 1; + } + if(sd_sync && (sd_seq == 1) && (buf.data()[45] == 1) && (buf.data()[55] == 0x30)){ + user_data[0] = buf.data()[56] ^ 0x4f; + user_data[1] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 2) && (buf.data()[45] == 2)){ + user_data[2] = buf.data()[55] ^ 0x70; + user_data[3] = buf.data()[56] ^ 0x4f; + user_data[4] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 3) && (buf.data()[45] == 3) && (buf.data()[55] == 0x31)){ + user_data[5] = buf.data()[56] ^ 0x4f; + user_data[6] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 4) && (buf.data()[45] == 4)){ + user_data[7] = buf.data()[55] ^ 0x70; + user_data[8] = buf.data()[56] ^ 0x4f; + user_data[9] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 5) && (buf.data()[45] == 5) && (buf.data()[55] == 0x32)){ + user_data[10] = buf.data()[56] ^ 0x4f; + user_data[11] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 6) && (buf.data()[45] == 6)){ + user_data[12] = buf.data()[55] ^ 0x70; + user_data[13] = buf.data()[56] ^ 0x4f; + user_data[14] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 7) && (buf.data()[45] == 7) && (buf.data()[55] == 0x33)){ + user_data[15] = buf.data()[56] ^ 0x4f; + user_data[16] = buf.data()[57] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 8) && (buf.data()[45] == 8)){ + user_data[17] = buf.data()[55] ^ 0x70; + user_data[18] = buf.data()[56] ^ 0x4f; + user_data[19] = buf.data()[57] ^ 0x93; + user_data[20] = '\0'; + sd_sync = 0; + sd_seq = 0; + m_modeinfo.usertxt = QString(user_data); + } + if(buf.data()[45] & 0x40){ + qDebug() << "DCS RX stream ended "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(3); + m_rxmodemq.append(MMDVM_DSTAR_EOT); + } + } + else if(m_modeinfo.stream_state == STREAMING){ + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(15); + m_rxmodemq.append(MMDVM_DSTAR_DATA); + for(int i = 0; i < 12; ++i){ + m_rxmodemq.append(buf.data()[46+i]); + } + } + } + for(int i = 0; i < 9; ++i){ + m_rxcodecq.append(buf.data()[46+i]); + } + } + emit update(m_modeinfo); +} + +void DCSCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.resize(519); + memcpy(out.data(), m_modeinfo.callsign.toStdString().c_str(), m_modeinfo.callsign.size()); + memset(out.data() + m_modeinfo.callsign.size(), ' ', 8 - m_modeinfo.callsign.size()); + out[8] = m_module; + out[9] = m_module; + out[10] = 11; + + //out.append(m_modeinfo.callsign.toUtf8()); + //out.append(8 - m_modeinfo.callsign.size(), ' '); + //out.append(m_module); + //out.append(m_module); + //out.append(11); + //out.append(508, 0); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void DCSCodec::send_ping() +{ + static QByteArray out; + out.clear(); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(7 - m_modeinfo.callsign.size(), ' '); + out.append(m_module); + out.append('\x00'); + out.append(m_hostname.toUtf8()); + out.append('\x00'); + out.append(m_module); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DCSCodec::send_disconnect() +{ + QByteArray out; + out.append(m_modeinfo.callsign.toUtf8()); + out.append(8 - m_modeinfo.callsign.size(), ' '); + out.append(m_module); + out.append(' '); + out.append('\x00'); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DCSCodec::format_callsign(QString &s) +{ + QStringList l = s.simplified().split(' '); + + if(l.size() > 1){ + s = l.at(0).simplified(); + while(s.size() < 7){ + s.append(' '); + } + s += l.at(1).simplified(); + } + else{ + while(s.size() < 8){ + s.append(' '); + } + } +} + +void DCSCodec::process_modem_data(QByteArray d) +{ + QByteArray txdata; + char cs[9]; + uint8_t ambe[9]; + + uint8_t *p_frame = (uint8_t *)(d.data()); + if(p_frame[2] == MMDVM_DSTAR_HEADER){ + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + cs[8] = 0; + memcpy(cs, p_frame + 22, 8); + m_txurcall = QString(cs); + memcpy(cs, p_frame + 30, 8); + m_txmycall = QString(cs); + m_modeinfo.stream_state = TRANSMITTING_MODEM; + m_tx = true; + } + else if( (p_frame[2] == MMDVM_DSTAR_EOT) || (p_frame[2] == MMDVM_DSTAR_LOST) ){ + m_tx = false; + } + else if(p_frame[2] == MMDVM_DSTAR_DATA){ + memcpy(ambe, p_frame + 3, 9); + } + send_frame(ambe); +} + +void DCSCodec::toggle_tx(bool tx) +{ + tx ? start_tx() : stop_tx(); +} + +void DCSCodec::start_tx() +{ + format_callsign(m_txmycall); + format_callsign(m_txurcall); + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + Codec::start_tx(); +} + +void DCSCodec::transmit() +{ + unsigned char ambe[9]; + uint8_t ambe_frame[72]; + int16_t pcm[160]; + memset(ambe_frame, 0, 72); + memset(ambe, 0, 9); + +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + //audiotx_cnt = 0; + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + if(m_hwtx){ + m_ambedev->encode(pcm); + if(m_tx && (m_txcodecq.size() >= 9)){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_txcodecq.dequeue(); + } + send_frame(ambe); + } + else if(!m_tx){ + send_frame(ambe); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2400x1200(pcm, ambe); + } + send_frame(ambe); + } +} + +void DCSCodec::send_frame(uint8_t *ambe) +{ + QByteArray txdata; + static uint16_t txstreamid = 0; + + txdata.clear(); + txdata.append(100, 0); + + if(txstreamid == 0){ + txstreamid = static_cast((::rand() & 0xFFFF)); + } + + txdata.replace(0, 4, "0001"); + txdata.replace(7, 8, m_txrptr2.toLocal8Bit().data()); + txdata.replace(15, 8, m_txrptr1.toLocal8Bit().data()); + txdata.replace(23, 8, m_txurcall.toLocal8Bit().data()); + txdata.replace(31, 8, m_txmycall.toLocal8Bit().data()); + txdata.replace(39, 4, "AMBE"); + txdata[43] = txstreamid & 0xff; + txdata[44] = (txstreamid >> 8) & 0xff; + txdata[45] = (m_txcnt % 21) & 0xff; + memcpy(txdata.data() + 46, ambe, 9); + + switch(txdata.data()[45]){ + case 0: + txdata[55] = 0x55; + txdata[56] = 0x2d; + txdata[57] = 0x16; + break; + case 1: + txdata[55] = 0x40 ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[0] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[1] ^ 0x93; + break; + case 2: + txdata[55] = m_txusrtxt.toLocal8Bit().data()[2] ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[3] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[4] ^ 0x93; + break; + case 3: + txdata[55] = 0x41 ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[5] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[6] ^ 0x93; + break; + case 4: + txdata[55] = m_txusrtxt.toLocal8Bit().data()[7] ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[8] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[9] ^ 0x93; + break; + case 5: + txdata[55] = 0x42 ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[10] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[11] ^ 0x93; + break; + case 6: + txdata[55] = m_txusrtxt.toLocal8Bit().data()[12] ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[13] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[14] ^ 0x93; + break; + case 7: + txdata[55] = 0x43 ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[15] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[16] ^ 0x93; + break; + case 8: + txdata[55] = m_txusrtxt.toLocal8Bit().data()[17] ^ 0x70; + txdata[56] = m_txusrtxt.toLocal8Bit().data()[18] ^ 0x4f; + txdata[57] = m_txusrtxt.toLocal8Bit().data()[19] ^ 0x93; + break; + default: + txdata[55] = 0x16; + txdata[56] = 0x29; + txdata[57] = 0xf5; + break; + } + + txdata[58] = m_txcnt & 0xff; + txdata[59] = (m_txcnt >> 8) & 0xff; + txdata[60] = (m_txcnt >> 16) & 0xff; + txdata[61] = 0x01; + + m_modeinfo.src = m_txmycall; + m_modeinfo.dst = m_txurcall; + m_modeinfo.gw = m_txrptr1; + m_modeinfo.gw2 = m_txrptr2; + m_modeinfo.streamid = txstreamid; + m_modeinfo.frame_number = m_txcnt; + + if(m_tx){ + m_txcnt++; + } + else{ + uint8_t last_frame[9] = {0xdc, 0x8e, 0x0a, 0x40, 0xad, 0xed, 0xad, 0x39, 0x6e}; + txdata[45] = (txdata[45] | 0x40); + txdata.replace(46, 9, (char *)last_frame); + m_txcnt = 0; + txstreamid = 0; + m_modeinfo.streamid = 0; + m_txtimer->stop(); + + if((m_ttsid == 0) && (m_modeinfo.stream_state == TRANSMITTING) ){ + m_audio->stop_capture(); + } + m_ttscnt = 0; + } + + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + emit update_output_level(m_audio->level()); + update(m_modeinfo); + +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DCSCodec::get_ambe() +{ + uint8_t ambe[9]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 9; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void DCSCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[9]; + + if(m_rxwatchdog++ > 100){ + qDebug() << "DCS RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + if(m_rxmodemq.size() > 2){ + QByteArray out; + int s = m_rxmodemq[1]; + if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){ + for(int i = 0; i < s; ++i){ + out.append(m_rxmodemq.dequeue()); + } + m_modem->write(out); + } + } + + if((!m_tx) && (m_rxcodecq.size() > 8) ){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2400x1200(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "DCS playback stopped"; + return; + } +} diff --git a/dcscodec.h b/dcscodec.h new file mode 100755 index 0000000..dcf8c1c --- /dev/null +++ b/dcscodec.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef DCSCODEC_H +#define DCSCODEC_H + +#include "codec.h" + +class DCSCodec : public Codec +{ + Q_OBJECT +public: + DCSCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~DCSCodec(); + unsigned char * get_frame(unsigned char *ambe); +private: + QString m_txusrtxt; + uint8_t packet_size; +private slots: + void toggle_tx(bool); + void start_tx(); + void process_udp(); + void process_modem_data(QByteArray); + void process_rx_data(); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void format_callsign(QString &s); + void hostname_lookup(QHostInfo i); + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } + void module_changed(int m) { m_module = 0x41 + m; m_modeinfo.streamid = 0; } + void usrtxt_changed(QString t) { m_txusrtxt = t; } + void send_frame(uint8_t *); +}; + +#endif // DCSCODEC_H diff --git a/dmrcodec.cpp b/dmrcodec.cpp new file mode 100755 index 0000000..2015628 --- /dev/null +++ b/dmrcodec.cpp @@ -0,0 +1,980 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include "dmrcodec.h" +#include "cgolay2087.h" +#include "crs129.h" +#include "SHA256.h" +#include "CRCenc.h" + +#define DEBUG + +const unsigned char MMDVM_DMR_DATA1 = 0x18U; +const unsigned char MMDVM_DMR_LOST1 = 0x19U; +const unsigned char MMDVM_DMR_DATA2 = 0x1AU; +const unsigned char MMDVM_DMR_LOST2 = 0x1BU; +const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; +const unsigned char MMDVM_DMR_START = 0x1DU; +const unsigned char MMDVM_DMR_ABORT = 0x1EU; + +const uint32_t ENCODING_TABLE_1676[] = + {0x0000U, 0x0273U, 0x04E5U, 0x0696U, 0x09C9U, 0x0BBAU, 0x0D2CU, 0x0F5FU, 0x11E2U, 0x1391U, 0x1507U, 0x1774U, + 0x182BU, 0x1A58U, 0x1CCEU, 0x1EBDU, 0x21B7U, 0x23C4U, 0x2552U, 0x2721U, 0x287EU, 0x2A0DU, 0x2C9BU, 0x2EE8U, + 0x3055U, 0x3226U, 0x34B0U, 0x36C3U, 0x399CU, 0x3BEFU, 0x3D79U, 0x3F0AU, 0x411EU, 0x436DU, 0x45FBU, 0x4788U, + 0x48D7U, 0x4AA4U, 0x4C32U, 0x4E41U, 0x50FCU, 0x528FU, 0x5419U, 0x566AU, 0x5935U, 0x5B46U, 0x5DD0U, 0x5FA3U, + 0x60A9U, 0x62DAU, 0x644CU, 0x663FU, 0x6960U, 0x6B13U, 0x6D85U, 0x6FF6U, 0x714BU, 0x7338U, 0x75AEU, 0x77DDU, + 0x7882U, 0x7AF1U, 0x7C67U, 0x7E14U, 0x804FU, 0x823CU, 0x84AAU, 0x86D9U, 0x8986U, 0x8BF5U, 0x8D63U, 0x8F10U, + 0x91ADU, 0x93DEU, 0x9548U, 0x973BU, 0x9864U, 0x9A17U, 0x9C81U, 0x9EF2U, 0xA1F8U, 0xA38BU, 0xA51DU, 0xA76EU, + 0xA831U, 0xAA42U, 0xACD4U, 0xAEA7U, 0xB01AU, 0xB269U, 0xB4FFU, 0xB68CU, 0xB9D3U, 0xBBA0U, 0xBD36U, 0xBF45U, + 0xC151U, 0xC322U, 0xC5B4U, 0xC7C7U, 0xC898U, 0xCAEBU, 0xCC7DU, 0xCE0EU, 0xD0B3U, 0xD2C0U, 0xD456U, 0xD625U, + 0xD97AU, 0xDB09U, 0xDD9FU, 0xDFECU, 0xE0E6U, 0xE295U, 0xE403U, 0xE670U, 0xE92FU, 0xEB5CU, 0xEDCAU, 0xEFB9U, + 0xF104U, 0xF377U, 0xF5E1U, 0xF792U, 0xF8CDU, 0xFABEU, 0xFC28U, 0xFE5BU}; + +DMRCodec::DMRCodec(QString callsign, uint32_t dmrid, uint8_t essid, QString password, QString lat, QString lon, QString location, QString desc, QString freq, QString url, QString swid, QString pkid, QString options, uint32_t dstid, QString host, uint32_t port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, 0, NULL, host, port, ipv6, vocoder, modem, audioin, audioout), + m_dmrid(dmrid), + m_password(password), + m_lat(lat), + m_lon(lon), + m_location(location), + m_desc(desc), + m_freq(freq), + m_url(url), + m_swid(swid), + m_pkid(pkid), + m_txdstid(dstid), + m_pc(false), + m_options(options) +{ + m_dmrcnt = 0; + m_flco = FLCO(0); + + if (essid){ + m_essid = m_dmrid * 100 + (essid-1); + } + else{ + m_essid = m_dmrid; + } +} + +DMRCodec::~DMRCodec() +{ +} + +void DMRCodec::process_udp() +{ + QByteArray buf; + QByteArray in; + QByteArray out; + QHostAddress sender; + quint16 senderPort; + CSHA256 sha256; + char buffer[400U]; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data() + 3, "NAK", 3U) == 0)){ + //m_udp->disconnect(); + //m_udp->close(); + //delete m_udp; + m_modeinfo.status = DISCONNECTED; + } + if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data(), "MSTCL", 5U) == 0)){ + //m_udp->disconnect(); + //m_udp->close(); + //delete m_udp; + m_modeinfo.status = CLOSED; + } + if((m_modeinfo.status != CONNECTED_RW) && (::memcmp(buf.data(), "RPTACK", 6U) == 0)){ + switch(m_modeinfo.status){ + case CONNECTING: + m_modeinfo.status = DMR_AUTH; + in.append(buf[6]); + in.append(buf[7]); + in.append(buf[8]); + in.append(buf[9]); + in.append(m_password.toUtf8()); + + out.clear(); + out.resize(40); + out[0] = 'R'; + out[1] = 'P'; + out[2] = 'T'; + out[3] = 'K'; + out[4] = (m_essid >> 24) & 0xff; + out[5] = (m_essid >> 16) & 0xff; + out[6] = (m_essid >> 8) & 0xff; + out[7] = (m_essid >> 0) & 0xff; + sha256.buffer((unsigned char *)in.data(), (unsigned int)(m_password.size() + sizeof(uint32_t)), (unsigned char *)out.data() + 8U); + break; + case DMR_AUTH: + out.clear(); + buffer[0] = 'R'; + buffer[1] = 'P'; + buffer[2] = 'T'; + buffer[3] = 'C'; + buffer[4] = (m_essid >> 24) & 0xff; + buffer[5] = (m_essid >> 16) & 0xff; + buffer[6] = (m_essid >> 8) & 0xff; + buffer[7] = (m_essid >> 0) & 0xff; + + m_modeinfo.status = DMR_CONF; + char latitude[20U]; + char longitude[20U]; + + sprintf(latitude, "%08f", m_lat.toFloat()); + sprintf(longitude, "%09f", m_lon.toFloat()); + + char *p; + if((p = strchr(latitude, ',')) != NULL){ + *p = '.'; + } + if((p = strchr(longitude, ',')) != NULL){ + *p = '.'; + } + ::sprintf(buffer + 8U, "%-8.8s%09u%09u%02u%02u%8.8s%9.9s%03d%-20.20s%-19.19s%c%-124.124s%-40.40s%-40.40s", m_modeinfo.callsign.toStdString().c_str(), + m_freq.toUInt(), m_freq.toUInt(), 1, 1, latitude, longitude, 0, m_location.toStdString().c_str(), m_desc.toStdString().c_str(), '4', + m_url.toStdString().c_str(), m_swid.toStdString().c_str(), m_pkid.toStdString().c_str()); + out.append(buffer, 302); + break; + case DMR_CONF: + setup_connection(); + if(m_options.size()){ + out.clear(); + out.append('R'); + out.append('P'); + out.append('T'); + out.append('O'); + out.append((m_essid >> 24) & 0xff); + out.append((m_essid >> 16) & 0xff); + out.append((m_essid >> 8) & 0xff); + out.append((m_essid >> 0) & 0xff); + out.append(m_options.toUtf8()); + //m_status = DMR_OPTS; + } + break; + case DMR_OPTS: + //setup_connection(); + break; + default: + break; + } + m_udp->writeDatagram(out, m_address, m_modeinfo.port); + } + if((buf.size() == 11) && (::memcmp(buf.data(), "MSTPONG", 7U) == 0)){ + m_modeinfo.count++; + } + if((buf.size() != 55) && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) )){ + m_modeinfo.stream_state = STREAM_IDLE; + } + if((buf.size() == 55) && + (::memcmp(buf.data(), "DMRD", 4U) == 0) && + ((uint8_t)buf.data()[15] & 0x20) && + (m_modeinfo.status == CONNECTED_RW)) + { + m_rxwatchdog = 0; + uint8_t t; + if((uint8_t)buf.data()[15] & 0x02){ + qDebug() << "DMR RX EOT"; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + m_modeinfo.streamid = 0; + t = 0x42; + } + else if((uint8_t)buf.data()[15] & 0x01){ + m_audio->start_playback(); + if(!m_rxtimer->isActive()){ + m_rxtimer->start(m_rxtimerint); + } + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + m_modeinfo.srcid = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff)); + m_modeinfo.dstid = (uint32_t)((buf.data()[8] << 16) | ((buf.data()[9] << 8) & 0xff00) | (buf.data()[10] & 0xff)); + m_modeinfo.gwid = (uint32_t)((buf.data()[11] << 24) | ((buf.data()[12] << 16) & 0xff0000) | ((buf.data()[13] << 8) & 0xff00) | (buf.data()[14] & 0xff)); + m_modeinfo.streamid = (uint32_t)((buf.data()[16] << 24) | ((buf.data()[17] << 16) & 0xff0000) | ((buf.data()[18] << 8) & 0xff00) | (buf.data()[19] & 0xff)); + m_modeinfo.frame_number = buf.data()[4]; + t = 0x41; + qDebug() << "New DMR stream from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid; + } + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(0x25); + m_rxmodemq.append(MMDVM_DMR_DATA2); + m_rxmodemq.append(t); + + for(int i = 0; i < 33; ++i){ + m_rxmodemq.append(buf.data()[20+i]); + }; + //m_rxmodemq.append('\x00'); + //m_rxmodemq.append(0x2f); + } + } + if((buf.size() == 55) && + (::memcmp(buf.data(), "DMRD", 4U) == 0) && + !((uint8_t)buf.data()[15] & 0x20) && + (m_modeinfo.status == CONNECTED_RW)) + { + if(!m_tx && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_IDLE) )){ + m_audio->start_playback(); + if(!m_rxtimer->isActive()){ + m_rxtimer->start(m_rxtimerint); + } + m_modeinfo.stream_state = STREAM_NEW; + } + else{ + m_modeinfo.stream_state = STREAMING; + } + m_rxwatchdog = 0; + + uint8_t dmrframe[33]; + uint8_t dmr3ambe[27]; + uint8_t dmrsync[7]; + // get the 33 bytes ambe + memcpy(dmrframe, &(buf.data()[20]), 33); + // extract the 3 ambe frames + memcpy(dmr3ambe, dmrframe, 14); + dmr3ambe[13] &= 0xF0; + dmr3ambe[13] |= (dmrframe[19] & 0x0F); + memcpy(&dmr3ambe[14], &dmrframe[20], 13); + // extract sync + dmrsync[0] = dmrframe[13] & 0x0F; + ::memcpy(&dmrsync[1], &dmrframe[14], 5); + dmrsync[6] = dmrframe[19] & 0xF0; + m_modeinfo.srcid = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff)); + m_modeinfo.dstid = (uint32_t)((buf.data()[8] << 16) | ((buf.data()[9] << 8) & 0xff00) | (buf.data()[10] & 0xff)); + m_modeinfo.gwid = (uint32_t)((buf.data()[11] << 24) | ((buf.data()[12] << 16) & 0xff0000) | ((buf.data()[13] << 8) & 0xff00) | (buf.data()[14] & 0xff)); + m_modeinfo.streamid = (uint32_t)((buf.data()[16] << 24) | ((buf.data()[17] << 16) & 0xff0000) | ((buf.data()[18] << 8) & 0xff00) | (buf.data()[19] & 0xff)); + m_modeinfo.frame_number = buf.data()[4]; + + if(m_modem){ + uint8_t t = ((uint8_t)buf.data()[15] & 0x0f); + if(!t) t = 0x20; + + m_rxmodemq.append(0xe0); + m_rxmodemq.append(0x25); + m_rxmodemq.append(MMDVM_DMR_DATA2); + m_rxmodemq.append(t); + + for(int i = 0; i < 33; ++i){ + m_rxmodemq.append(buf.data()[20+i]); + } + } + + for(int i = 0; i < 3; ++i){ + for(int j = 0; j < 9; ++j){ + m_rxcodecq.append(dmr3ambe[j + (9*i)]); + } + } + //uint32_t id = (uint32_t)((buf.data()[5] << 16) | ((buf.data()[6] << 8) & 0xff00) | (buf.data()[7] & 0xff)); + } + emit update(m_modeinfo); +#ifdef DEBUG + if(out.size() > 0){ + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } +#endif +} + +void DMRCodec::setup_connection() +{ + m_modeinfo.status = CONNECTED_RW; + //m_mbeenc->set_gain_adjust(2.5); + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(5000); + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("DMR"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + else{ + m_hwrx = false; + m_hwtx = false; + } + if(m_modemport != ""){ + m_modem = new SerialModem("DMR"); + m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex); + m_modem->set_modem_params(m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel); + m_modem->connect_to_serial(m_modemport); + connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray))); + } + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); +} + +void DMRCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.append('R'); + out.append('P'); + out.append('T'); + out.append('L'); + out.append((m_essid >> 24) & 0xff); + out.append((m_essid >> 16) & 0xff); + out.append((m_essid >> 8) & 0xff); + out.append((m_essid >> 0) & 0xff); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void DMRCodec::send_ping() +{ + QByteArray out; + char tag[] = { 'R','P','T','P','I','N','G' }; + out.append(tag, 7); + out.append((m_essid >> 24) & 0xff); + out.append((m_essid >> 16) & 0xff); + out.append((m_essid >> 8) & 0xff); + out.append((m_essid >> 0) & 0xff); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DMRCodec::send_disconnect() +{ + QByteArray out; + out.append('R'); + out.append('P'); + out.append('T'); + out.append('C'); + out.append('L'); + out.append((m_essid >> 24) & 0xff); + out.append((m_essid >> 16) & 0xff); + out.append((m_essid >> 8) & 0xff); + out.append((m_essid >> 0) & 0xff); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DMRCodec::process_modem_data(QByteArray d) +{ + QByteArray txdata; + uint8_t lcData[12U]; + + uint8_t *p_frame = (uint8_t *)(d.data()); + m_dataType = p_frame[3U] & 0x0f; + + if ((p_frame[3U] & DMR_SYNC_DATA) == DMR_SYNC_DATA){ + if((m_dataType == DT_VOICE_LC_HEADER) && (m_modeinfo.stream_state == STREAM_IDLE)){ + m_modeinfo.stream_state = TRANSMITTING_MODEM; + //qDebug() << "ids == " << m_txsrcid << ":" << m_txdstid; + } + else if(m_dataType == DT_TERMINATOR_WITH_LC){ + m_modeinfo.stream_state = STREAM_IDLE; + } + + m_dmrcnt = 0; + m_bptc.decode(p_frame + 4, lcData); + m_txdstid = lcData[3U] << 16 | lcData[4U] << 8 | lcData[5U]; + m_txsrcid = lcData[6U] << 16 | lcData[7U] << 8 | lcData[8U]; + m_flco = FLCO(lcData[0U] & 0x3FU); + build_frame(); + ::memcpy(m_dmrFrame + 20U, p_frame + 4, 33U); + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + } + else { + m_dataType = (m_dmrcnt % 6U) ? DT_VOICE : DT_VOICE_SYNC; + build_frame(); + ::memcpy(m_dmrFrame + 20U, p_frame + 4, 33U); + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + ++m_dmrcnt; + } +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void DMRCodec::transmit() +{ + uint8_t ambe[72]; + int16_t pcm[160]; + +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + + if(m_hwtx){ + m_ambedev->encode(pcm); + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2450x1150(pcm, ambe); + } + for(int i = 0; i < 9; ++i){ + m_txcodecq.append(ambe[i]); + } + } + + if(m_tx && (m_txcodecq.size() >= 27)){ + for(int i = 0; i < 27; ++i){ + m_ambe[i] = m_txcodecq.dequeue(); + } + send_frame(); + } + else if(m_tx == false){ + send_frame(); + } +} + +void DMRCodec::send_frame() +{ + QByteArray txdata; + if(m_pc){ + set_calltype(3); + } + else{ + set_calltype(0); + } + m_txsrcid = m_dmrid; + if(m_tx){ + m_modeinfo.stream_state = TRANSMITTING; + + if(!m_dmrcnt){ + encode_header(DT_VOICE_LC_HEADER); + m_txstreamid = static_cast(::rand()); + } + else{ + ::memcpy(m_dmrFrame + 20U, m_ambe, 13U); + m_dmrFrame[33U] = m_ambe[13U] & 0xF0U; + m_dmrFrame[39U] = m_ambe[13U] & 0x0FU; + ::memcpy(m_dmrFrame + 40U, &m_ambe[14U], 13U); + encode_data(); + } + + build_frame(); + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + ++m_dmrcnt; +/* + if(!m_dmrcnt){ + for (int i = 0U; i < 3; i++) { + m_dmrFrame[4U] = m_dmrcnt; + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + m_dmrcnt++; + } + + } + else{ + ++m_dmrcnt; + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + } +*/ + } + else{ + //fprintf(stderr, "DMR TX stopped\n"); + get_eot(); + build_frame(); + m_ttscnt = 0; + txdata.append((char *)m_dmrFrame, 55); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + m_txtimer->stop(); + + if(m_ttsid == 0){ + m_audio->stop_capture(); + } + + m_modeinfo.stream_state = STREAM_IDLE; + } + emit update_output_level(m_audio->level()); + emit update(m_modeinfo); +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +unsigned char * DMRCodec::get_eot() +{ + encode_header(DT_TERMINATOR_WITH_LC); + m_dmrcnt = 0; + return m_dmrFrame; +} + +void DMRCodec::build_frame() +{ + m_dmrFrame[0U] = 'D'; + m_dmrFrame[1U] = 'M'; + m_dmrFrame[2U] = 'R'; + m_dmrFrame[3U] = 'D'; + + m_dmrFrame[5U] = m_txsrcid >> 16; + m_dmrFrame[6U] = m_txsrcid >> 8; + m_dmrFrame[7U] = m_txsrcid >> 0; + m_dmrFrame[8U] = m_txdstid >> 16; + m_dmrFrame[9U] = m_txdstid >> 8; + m_dmrFrame[10U] = m_txdstid >> 0; + m_dmrFrame[11U] = m_essid >> 24; + m_dmrFrame[12U] = m_essid >> 16; + m_dmrFrame[13U] = m_essid >> 8; + m_dmrFrame[14U] = m_essid >> 0; + + m_dmrFrame[15U] = (m_slot == 1U) ? 0x00U : 0x80U; + m_dmrFrame[15U] |= (m_flco == FLCO_GROUP) ? 0x00U : 0x40U; + + if (m_dataType == DT_VOICE_SYNC) { + m_dmrFrame[15U] |= 0x10U; + } else if (m_dataType == DT_VOICE) { + m_dmrFrame[15U] |= ((m_dmrcnt - 1) % 6U); + } else { + m_dmrFrame[15U] |= (0x20U | m_dataType); + } + + m_dmrFrame[4U] = m_dmrcnt; + ::memcpy(m_dmrFrame + 16U, &m_txstreamid, 4U); + + m_dmrFrame[53U] = 0; //data.getBER(); + m_dmrFrame[54U] = 0; //data.getRSSI(); + + m_modeinfo.srcid = m_txsrcid; + m_modeinfo.dstid = m_txdstid; + m_modeinfo.gwid = m_essid; + m_modeinfo.frame_number = m_dmrcnt; +} + +void DMRCodec::encode_header(uint8_t t) +{ + addDMRDataSync(m_dmrFrame+20, 0); + m_dataType = t; + full_lc_encode(m_dmrFrame+20, t); +} + +void DMRCodec::encode_data() +{ + unsigned int n_dmr = (m_dmrcnt - 1) % 6U; + + if (!n_dmr) { + m_dataType = DT_VOICE_SYNC; + addDMRAudioSync(m_dmrFrame+20, 0); + encode_embedded_data(); + } + else { + m_dataType = DT_VOICE; + uint8_t lcss = get_embedded_data(m_dmrFrame+20, n_dmr); + get_emb_data(m_dmrFrame+20, lcss); + } +} + +void DMRCodec::encode16114(bool* d) +{ + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + d[15] = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10]; +} + +void DMRCodec::encode_qr1676(uint8_t* data) +{ + uint32_t value = (data[0U] >> 1) & 0x7FU; + uint32_t cksum = ENCODING_TABLE_1676[value]; + + data[0U] = cksum >> 8; + data[1U] = cksum & 0xFFU; +} + +void DMRCodec::get_emb_data(uint8_t* data, uint8_t lcss) +{ + uint8_t DMREMB[2U]; + DMREMB[0U] = (m_colorcode << 4) & 0xF0U; + //DMREMB[0U] |= m_PI ? 0x08U : 0x00U; + DMREMB[0U] |= (lcss << 1) & 0x06U; + DMREMB[1U] = 0x00U; + + encode_qr1676(DMREMB); + + data[13U] = (data[13U] & 0xF0U) | ((DMREMB[0U] >> 4U) & 0x0FU); + data[14U] = (data[14U] & 0x0FU) | ((DMREMB[0U] << 4U) & 0xF0U); + data[18U] = (data[18U] & 0xF0U) | ((DMREMB[1U] >> 4U) & 0x0FU); + data[19U] = (data[19U] & 0x0FU) | ((DMREMB[1U] << 4U) & 0xF0U); +} + +uint8_t DMRCodec::get_embedded_data(uint8_t* data, uint8_t n) +{ + if (n >= 1U && n < 5U) { + n--; + + bool bits[40U]; + ::memset(bits, 0x00U, 40U * sizeof(bool)); + ::memcpy(bits + 4U, m_raw + n * 32U, 32U * sizeof(bool)); + + uint8_t bytes[5U]; + bitsToByteBE(bits + 0U, bytes[0U]); + bitsToByteBE(bits + 8U, bytes[1U]); + bitsToByteBE(bits + 16U, bytes[2U]); + bitsToByteBE(bits + 24U, bytes[3U]); + bitsToByteBE(bits + 32U, bytes[4U]); + + data[14U] = (data[14U] & 0xF0U) | (bytes[0U] & 0x0FU); + data[15U] = bytes[1U]; + data[16U] = bytes[2U]; + data[17U] = bytes[3U]; + data[18U] = (data[18U] & 0x0FU) | (bytes[4U] & 0xF0U); + + switch (n) { + case 0U: + return 1U; + case 3U: + return 2U; + default: + return 3U; + } + } else { + data[14U] &= 0xF0U; + data[15U] = 0x00U; + data[16U] = 0x00U; + data[17U] = 0x00U; + data[18U] &= 0x0FU; + + return 0U; + } +} + +void DMRCodec::encode_embedded_data() +{ + uint32_t crc; + lc_get_data(m_data); + CCRC::encodeFiveBit(m_data, crc); + + bool data[128U]; + ::memset(data, 0x00U, 128U * sizeof(bool)); + + data[106U] = (crc & 0x01U) == 0x01U; + data[90U] = (crc & 0x02U) == 0x02U; + data[74U] = (crc & 0x04U) == 0x04U; + data[58U] = (crc & 0x08U) == 0x08U; + data[42U] = (crc & 0x10U) == 0x10U; + + uint32_t b = 0U; + for (uint32_t a = 0U; a < 11U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 16U; a < 27U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 32U; a < 42U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 48U; a < 58U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 64U; a < 74U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 80U; a < 90U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 96U; a < 106U; a++, b++) + data[a] = m_data[b]; + + // Hamming (16,11,4) check each row except the last one + for (uint32_t a = 0U; a < 112U; a += 16U) + encode16114(data + a); + + // Add the parity bits for each column + for (uint32_t a = 0U; a < 16U; a++) + data[a + 112U] = data[a + 0U] ^ data[a + 16U] ^ data[a + 32U] ^ data[a + 48U] ^ data[a + 64U] ^ data[a + 80U] ^ data[a + 96U]; + + // The data is packed downwards in columns + b = 0U; + for (uint32_t a = 0U; a < 128U; a++) { + m_raw[a] = data[b]; + b += 16U; + if (b > 127U) + b -= 127U; + } +} + +void DMRCodec::bitsToByteBE(const bool* bits, uint8_t& byte) +{ + byte = bits[0U] ? 0x80U : 0x00U; + byte |= bits[1U] ? 0x40U : 0x00U; + byte |= bits[2U] ? 0x20U : 0x00U; + byte |= bits[3U] ? 0x10U : 0x00U; + byte |= bits[4U] ? 0x08U : 0x00U; + byte |= bits[5U] ? 0x04U : 0x00U; + byte |= bits[6U] ? 0x02U : 0x00U; + byte |= bits[7U] ? 0x01U : 0x00U; +} + +void DMRCodec::byteToBitsBE(uint8_t byte, bool* bits) +{ + bits[0U] = (byte & 0x80U) == 0x80U; + bits[1U] = (byte & 0x40U) == 0x40U; + bits[2U] = (byte & 0x20U) == 0x20U; + bits[3U] = (byte & 0x10U) == 0x10U; + bits[4U] = (byte & 0x08U) == 0x08U; + bits[5U] = (byte & 0x04U) == 0x04U; + bits[6U] = (byte & 0x02U) == 0x02U; + bits[7U] = (byte & 0x01U) == 0x01U; +} + +void DMRCodec::lc_get_data(bool* bits) +{ + uint8_t bytes[9U]; + memset(bytes, 0, 9); + lc_get_data(bytes); + + byteToBitsBE(bytes[0U], bits + 0U); + byteToBitsBE(bytes[1U], bits + 8U); + byteToBitsBE(bytes[2U], bits + 16U); + byteToBitsBE(bytes[3U], bits + 24U); + byteToBitsBE(bytes[4U], bits + 32U); + byteToBitsBE(bytes[5U], bits + 40U); + byteToBitsBE(bytes[6U], bits + 48U); + byteToBitsBE(bytes[7U], bits + 56U); + byteToBitsBE(bytes[8U], bits + 64U); +} + +void DMRCodec::lc_get_data(uint8_t *bytes) +{ + bool pf, r; + uint8_t fid, options; + bytes[0U] = (uint8_t)m_flco; + + pf = (bytes[0U] & 0x80U) == 0x80U; + r = (bytes[0U] & 0x40U) == 0x40U; + //m_flco = FLCO(bytes[0U] & 0x3FU); + fid = bytes[1U]; + options = bytes[2U]; + + //bytes[0U] = (uint8_t)m_flco; + + if (pf) + bytes[0U] |= 0x80U; + + if (r) + bytes[0U] |= 0x40U; + + bytes[1U] = fid; + bytes[2U] = options; + bytes[3U] = m_txdstid >> 16; + bytes[4U] = m_txdstid >> 8; + bytes[5U] = m_txdstid >> 0; + bytes[6U] = m_dmrid >> 16; + bytes[7U] = m_dmrid >> 8; + bytes[8U] = m_dmrid >> 0; +} + +void DMRCodec::full_lc_encode(uint8_t* data, uint8_t type) // for header +{ + uint8_t lcData[12U]; + uint8_t parity[4U]; + ::memset(lcData, 0, sizeof(lcData)); + lc_get_data(lcData); + + CRS129::encode(lcData, 9U, parity); + + switch (type) { + case DT_VOICE_LC_HEADER: + lcData[9U] = parity[2U] ^ VOICE_LC_HEADER_CRC_MASK[0U]; + lcData[10U] = parity[1U] ^ VOICE_LC_HEADER_CRC_MASK[1U]; + lcData[11U] = parity[0U] ^ VOICE_LC_HEADER_CRC_MASK[2U]; + break; + + case DT_TERMINATOR_WITH_LC: + lcData[9U] = parity[2U] ^ TERMINATOR_WITH_LC_CRC_MASK[0U]; + lcData[10U] = parity[1U] ^ TERMINATOR_WITH_LC_CRC_MASK[1U]; + lcData[11U] = parity[0U] ^ TERMINATOR_WITH_LC_CRC_MASK[2U]; + break; + + default: + return; + } + get_slot_data(data); + m_bptc.encode(lcData, data); +} + +void DMRCodec::get_slot_data(uint8_t* data) +{ + uint8_t DMRSlotType[3U]; + DMRSlotType[0U] = (m_colorcode << 4) & 0xF0U; + DMRSlotType[0U] |= (m_dataType << 0) & 0x0FU; + DMRSlotType[1U] = 0x00U; + DMRSlotType[2U] = 0x00U; + + CGolay2087::encode(DMRSlotType); + + data[12U] = (data[12U] & 0xC0U) | ((DMRSlotType[0U] >> 2) & 0x3FU); + data[13U] = (data[13U] & 0x0FU) | ((DMRSlotType[0U] << 6) & 0xC0U) | ((DMRSlotType[1U] >> 2) & 0x30U); + data[19U] = (data[19U] & 0xF0U) | ((DMRSlotType[1U] >> 2) & 0x0FU); + data[20U] = (data[20U] & 0x03U) | ((DMRSlotType[1U] << 6) & 0xC0U) | ((DMRSlotType[2U] >> 2) & 0x3CU); +} + + +void DMRCodec::addDMRDataSync(uint8_t* data, bool duplex) +{ + if (duplex) { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_DATA_SYNC[i]; + } else { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_DATA_SYNC[i]; + } +} + +void DMRCodec::addDMRAudioSync(uint8_t* data, bool duplex) +{ + if (duplex) { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_AUDIO_SYNC[i]; + } else { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_AUDIO_SYNC[i]; + } +} + +void DMRCodec::get_ambe() +{ + uint8_t ambe[9]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 9; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void DMRCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[9]; + + if(m_rxwatchdog++ > 100){ + qDebug() << "DMR RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + if(m_rxmodemq.size() > 2){ + QByteArray out; + int s = m_rxmodemq[1]; + if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){ + for(int i = 0; i < s; ++i){ + out.append(m_rxmodemq.dequeue()); + } + m_modem->write(out); + } + } + + if((!m_tx) && (m_rxcodecq.size() > 8) ){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2450x1150(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "DMR playback stopped"; + return; + } +} diff --git a/dmrcodec.h b/dmrcodec.h new file mode 100755 index 0000000..49866d7 --- /dev/null +++ b/dmrcodec.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef DMRCODEC_H +#define DMRCODEC_H + +#include "codec.h" +//#include +#include "DMRData.h" +#include "cbptc19696.h" + +class DMRCodec : public Codec +{ + Q_OBJECT +public: + DMRCodec(QString callsign, uint32_t dmrid, uint8_t essid, QString password, QString lat, QString lon, QString location, QString desc, QString freq, QString url, QString swid, QString pkid, QString options, uint32_t dstid, QString host, uint32_t port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~DMRCodec(); + unsigned char * get_eot(); + void set_cc(uint32_t cc){m_colorcode = cc;} + void set_slot(uint32_t s){m_slot = s;} + void set_calltype(uint8_t c){m_flco = FLCO(c);} +private slots: + void process_udp(); + void process_rx_data(); + void process_modem_data(QByteArray); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void hostname_lookup(QHostInfo i); + void dmr_tgid_changed(unsigned int id) { m_txdstid = id; } + void dmr_cc_changed(int cc) {m_colorcode = cc + 1; } + void dmr_slot_changed(int s) {m_slot = s + 1; } + void dmrpc_state_changed(int pc){ m_pc = (pc ? true : false); } + void send_frame(); +private: + uint32_t m_dmrid; + uint32_t m_essid; + QString m_password; + QString m_lat; + QString m_lon; + QString m_location; + QString m_desc; + QString m_freq; + QString m_url; + QString m_swid; + QString m_pkid; + uint32_t m_txsrcid; + uint32_t m_txdstid; + uint32_t m_txstreamid; + uint8_t packet_size; + uint8_t m_ambe[27]; + uint32_t m_defsrcid; + uint8_t m_dmrFrame[55]; + uint8_t m_dataType; + uint32_t m_colorcode; + uint32_t m_slot; + uint32_t m_dmrcnt; + bool m_pc; + FLCO m_flco; + CBPTC19696 m_bptc; + bool m_raw[128U]; + bool m_data[72U]; + QString m_options; + + void byteToBitsBE(uint8_t byte, bool* bits); + void bitsToByteBE(const bool* bits, uint8_t& byte); + void build_frame(); + void encode_header(uint8_t); + void encode_data(); + void encode16114(bool* d); + void encode_qr1676(uint8_t* data); + void get_slot_data(uint8_t* data); + void lc_get_data(uint8_t*); + void lc_get_data(bool* bits); + void encode_embedded_data(); + uint8_t get_embedded_data(uint8_t* data, uint8_t n); + void get_emb_data(uint8_t* data, uint8_t lcss); + void full_lc_encode(uint8_t* data, uint8_t type); + void addDMRDataSync(uint8_t* data, bool duplex); + void addDMRAudioSync(uint8_t* data, bool duplex); + void setup_connection(); +}; + +#endif // DMRCODEC_H diff --git a/droidstar.cpp b/droidstar.cpp new file mode 100644 index 0000000..6e48eb0 --- /dev/null +++ b/droidstar.cpp @@ -0,0 +1,1797 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "droidstar.h" +#include "httpmanager.h" +#include "SHA256.h" +#include "crs129.h" +#include "cbptc19696.h" +#include "cgolay2087.h" +#ifdef Q_OS_ANDROID +#include +#endif +#ifdef Q_OS_IOS +#include "micpermission.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +DroidStar::DroidStar(QObject *parent) : + QObject(parent), + m_dmrid(0), + m_essid(0), + m_dmr_destid(0), + m_outlevel(0) +{ + qRegisterMetaType("Codec::MODEINFO"); + m_settings_processed = false; + m_modelchange = false; + connect_status = Codec::DISCONNECTED; + m_settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, "dudetronics", "droidstar", this); + config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + qDebug() << "Config path == " << config_path; + qDebug() << "Download path == " << QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WIN) + config_path += "/dudetronics"; +#endif +#if defined(Q_OS_ANDROID) + keepScreenOn(); + const QString permission("android.permission.READ_EXTERNAL_STORAGE"); + if(QtAndroid::checkPermission(permission) != QtAndroid::PermissionResult::Granted){ + auto resultHash = QtAndroid::requestPermissionsSync(QStringList({permission})); + if(resultHash[permission] == QtAndroid::PermissionResult::Denied){ + qDebug() << "Storage read permissions denied"; + } + } +#endif + + check_host_files(); + discover_devices(); + process_settings(); + + qDebug() << "CPU arch: " << QSysInfo::currentCpuArchitecture(); + qDebug() << "Build ABI: " << QSysInfo::buildAbi(); + qDebug() << "boot ID: " << QSysInfo::bootUniqueId(); + qDebug() << "Pretty name: " << QSysInfo::prettyProductName(); + qDebug() << "Type: " << QSysInfo::productType(); + qDebug() << "Version: " << QSysInfo::productVersion(); + qDebug() << "Kernel type: " << QSysInfo::kernelType(); + qDebug() << "Kernel version: " << QSysInfo::kernelVersion(); + qDebug() << "Software version: " << VERSION_NUMBER; +} + +DroidStar::~DroidStar() +{ +} + +#ifdef Q_OS_ANDROID +void DroidStar::keepScreenOn() +{ + char const * const action = "addFlags"; + QtAndroid::runOnAndroidThread([action](){ + QAndroidJniObject activity = QtAndroid::androidActivity(); + if (activity.isValid()) { + QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + + if (window.isValid()) { + const int FLAG_KEEP_SCREEN_ON = 128; + window.callMethod("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON); + } + }}); +} +#endif + +void DroidStar::discover_devices() +{ + m_playbacks.clear(); + m_captures.clear(); + m_vocoders.clear(); + m_modems.clear(); + m_playbacks.append("OS Default"); + m_captures.append("OS Default"); + m_vocoders.append("Software vocoder"); + m_modems.append("None"); + m_playbacks.append(AudioEngine::discover_audio_devices(AUDIO_OUT)); + m_captures.append(AudioEngine::discover_audio_devices(AUDIO_IN)); + + QMap l = SerialAMBE::discover_devices(); + QMap::const_iterator i = l.constBegin(); + + while (i != l.constEnd()) { + m_vocoders.append(i.value()); + m_modems.append(i.value()); + ++i; + } +} + +void DroidStar::download_file(QString f) +{ + HttpManager *http = new HttpManager(f); + QThread *httpThread = new QThread; + http->moveToThread(httpThread); + connect(httpThread, SIGNAL(started()), http, SLOT(process())); + connect(http, SIGNAL(file_downloaded(QString)), this, SLOT(file_downloaded(QString))); + connect(httpThread, SIGNAL(finished()), http, SLOT(deleteLater())); + httpThread->start(); +} + +void DroidStar::file_downloaded(QString filename) +{ + qDebug() << "DudeStar::file_downloaded() " << filename; + emit update_log("Updated " + filename); + { + if(filename == "dplus.txt" && m_protocol == "REF"){ + process_ref_hosts(); + } + else if(filename == "dextra.txt" && m_protocol == "XRF"){ + process_xrf_hosts(); + } + else if(filename == "dcs.txt" && m_protocol == "DCS"){ + process_dcs_hosts(); + } + else if(filename == "YSFHosts.txt" && m_protocol == "YSF"){ + process_ysf_hosts(); + } + else if(filename == "FCSHosts.txt" && m_protocol == "FCS"){ + process_fcs_rooms(); + } + else if(filename == "P25Hosts.txt" && m_protocol == "P25"){ + process_p25_hosts(); + } + else if(filename == "DMRHosts.txt" && m_protocol == "DMR"){ + process_dmr_hosts(); + } + else if(filename == "NXDNHosts.txt" && m_protocol == "NXDN"){ + process_nxdn_hosts(); + } + else if(filename == "M17Hosts-full.csv" && m_protocol == "M17"){ + process_m17_hosts(); + } + else if(filename == "DMRIDs.dat"){ + process_dmr_ids(); + } + else if(filename == "NXDN.csv"){ + process_nxdn_ids(); + } + } +} + +void DroidStar::dtmf_send_clicked(QString dtmf) +{ + QByteArray tx(dtmf.simplified().toUtf8(), dtmf.simplified().size()); + tx.prepend('*'); + emit send_dtmf(tx); +} + +void DroidStar::process_connect() +{ + //QSize size = qApp->screens()[0]->size(); + if(connect_status != Codec::DISCONNECTED){ + connect_status = Codec::DISCONNECTED; + m_modethread->quit(); + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + emit connect_status_changed(0); + emit update_log("Disconnected"); + } + else{ +#ifdef Q_OS_IOS + MicPermission::check_permission(); +#endif + if( (m_callsign.size() < 4) || + (m_dmrid < 250000) || + (m_callsign != m_dmrids[m_dmrid])) + { + emit connect_status_changed(4); + return; + } + + if(m_protocol == "REF"){ + m_host = m_saved_refhost; + } + else if(m_protocol == "DCS"){ + m_host = m_saved_dcshost; + } + else if(m_protocol == "XRF"){ + m_host = m_saved_xrfhost; + } + else if(m_protocol == "YSF"){ + m_host = m_saved_ysfhost; + } + else if(m_protocol == "FCS"){ + m_host = m_saved_fcshost; + } + else if(m_protocol == "DMR"){ + m_host = m_saved_dmrhost; + } + else if(m_protocol == "P25"){ + m_host = m_saved_p25host; + } + else if(m_protocol == "NXDN"){ + m_host = m_saved_nxdnhost; + } + else if(m_protocol == "M17"){ + m_host = m_saved_m17host; + } + + emit connect_status_changed(1); + connect_status = Codec::CONNECTING; + QStringList sl; + + if(m_protocol != "IAX"){ + m_hostname = m_hostmap[m_host]; + sl = m_hostname.split(','); + + if( (m_protocol == "M17") && (m_ipv6) && (sl.size() > 2) && (sl.at(2) != "none") ){ + m_hostname = sl.at(2).simplified(); + m_port = sl.at(1).toInt(); + } + else if(sl.size() > 1){ + m_hostname = sl.at(0).simplified(); + m_port = sl.at(1).toInt(); + } + else{ + m_errortxt = "Invalid host selection"; + connect_status = Codec::DISCONNECTED; + emit connect_status_changed(5); + return; + } + } + + QString vocoder = ""; + if( (m_vocoder != "Software vocoder") && (m_vocoder.contains(':')) ){ + QStringList vl = m_vocoder.split(':'); + vocoder = vl.at(0); + } + QString modem = ""; + if( (m_modem != "None") && (m_modem.contains(':')) ){ + QStringList ml = m_modem.split(':'); + modem = ml.at(0); + } + //vocoder = "USB"; + //modem = "USB"; + const bool rxInvert = true; + const bool txInvert = false; + const bool pttInvert = false; + const bool useCOSAsLockout = 0; + const uint32_t ysfTXHang = 4; + const float pocsagTXLevel = 50; + const bool duplex = m_modemRxFreq.toUInt() != m_modemTxFreq.toUInt(); + + emit update_log("Connecting to " + m_hostname + ":" + QString::number(m_port) + "..."); + if( (m_protocol == "REF") || ((m_protocol == "XRF") && m_xrf2ref) ){ + m_ref = new REFCodec(m_callsign, m_host, m_module, m_hostname, 20001, false, vocoder, modem, m_playback, m_capture); + m_ref->set_modem_flags(rxInvert, txInvert, pttInvert, useCOSAsLockout, duplex); + m_ref->set_modem_params(m_modemRxFreq.toInt(), m_modemTxFreq.toInt(), m_modemTxDelay.toInt(), m_modemRxLevel.toFloat(), m_modemRFLevel.toFloat(), ysfTXHang, m_modemCWIdTxLevel.toFloat(), m_modemDstarTxLevel.toFloat(), m_modemDMRTxLevel.toFloat(), m_modemYSFTxLevel.toFloat(), m_modemP25TxLevel.toFloat(), m_modemNXDNTxLevel.toFloat(), pocsagTXLevel); + m_modethread = new QThread; + m_ref->moveToThread(m_modethread); + connect(this, SIGNAL(module_changed(char)), m_ref, SLOT(module_changed(char))); + connect(m_ref, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_ref_data(Codec::MODEINFO))); + connect(m_ref, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_ref, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_ref, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_ref, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_ref, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_ref, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_ref, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_ref, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_ref, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_ref, SLOT(in_audio_vol_changed(qreal))); + connect(this, SIGNAL(mycall_changed(QString)), m_ref, SLOT(mycall_changed(QString))); + connect(this, SIGNAL(urcall_changed(QString)), m_ref, SLOT(urcall_changed(QString))); + connect(this, SIGNAL(rptr1_changed(QString)), m_ref, SLOT(rptr1_changed(QString))); + connect(this, SIGNAL(rptr2_changed(QString)), m_ref, SLOT(rptr2_changed(QString))); + emit module_changed(m_module); + emit mycall_changed(m_mycall); + emit urcall_changed(m_urcall); + emit rptr1_changed(m_rptr1); + emit rptr2_changed(m_rptr2); + m_modethread->start(); + } + if(m_protocol == "DCS"){ + m_dcs = new DCSCodec(m_callsign, m_host, m_module, m_hostname, m_port, false, vocoder, modem, m_playback, m_capture); + m_dcs->set_modem_flags(rxInvert, txInvert, pttInvert, useCOSAsLockout, duplex); + m_dcs->set_modem_params(m_modemRxFreq.toInt(), m_modemTxFreq.toInt(), m_modemTxDelay.toInt(), m_modemRxLevel.toFloat(), m_modemRFLevel.toFloat(), ysfTXHang, m_modemCWIdTxLevel.toFloat(), m_modemDstarTxLevel.toFloat(), m_modemDMRTxLevel.toFloat(), m_modemYSFTxLevel.toFloat(), m_modemP25TxLevel.toFloat(), m_modemNXDNTxLevel.toFloat(), pocsagTXLevel); + m_modethread = new QThread; + m_dcs->moveToThread(m_modethread); + //connect(this, SIGNAL(module_changed(char)), m_dcs, SLOT(module_changed(char))); + connect(m_dcs, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_dcs_data(Codec::MODEINFO))); + connect(m_dcs, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_dcs, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_dcs, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_dcs, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_dcs, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_dcs, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_dcs, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_dcs, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_dcs, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_dcs, SLOT(in_audio_vol_changed(qreal))); + connect(this, SIGNAL(mycall_changed(QString)), m_dcs, SLOT(mycall_changed(QString))); + connect(this, SIGNAL(urcall_changed(QString)), m_dcs, SLOT(urcall_changed(QString))); + connect(this, SIGNAL(rptr1_changed(QString)), m_dcs, SLOT(rptr1_changed(QString))); + connect(this, SIGNAL(rptr2_changed(QString)), m_dcs, SLOT(rptr2_changed(QString))); + //emit module_changed(m_module); + emit mycall_changed(m_mycall); + emit urcall_changed(m_urcall); + emit rptr1_changed(m_rptr1); + emit rptr2_changed(m_rptr2); + m_modethread->start(); + } + if( (m_protocol == "XRF") && (m_xrf2ref == false) ){ + m_xrf = new XRFCodec(m_callsign, m_host, m_module, m_hostname, m_port, false, vocoder, modem, m_playback, m_capture); + m_xrf->set_modem_flags(rxInvert, txInvert, pttInvert, useCOSAsLockout, duplex); + m_xrf->set_modem_params(m_modemRxFreq.toInt(), m_modemTxFreq.toInt(), m_modemTxDelay.toInt(), m_modemRxLevel.toFloat(), m_modemRFLevel.toFloat(), ysfTXHang, m_modemCWIdTxLevel.toFloat(), m_modemDstarTxLevel.toFloat(), m_modemDMRTxLevel.toFloat(), m_modemYSFTxLevel.toFloat(), m_modemP25TxLevel.toFloat(), m_modemNXDNTxLevel.toFloat(), pocsagTXLevel); + m_modethread = new QThread; + m_xrf->moveToThread(m_modethread); + //connect(this, SIGNAL(module_changed(char)), m_xrf, SLOT(module_changed(char))); + connect(m_xrf, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_xrf_data(Codec::MODEINFO))); + connect(m_xrf, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_xrf, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_xrf, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_xrf, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_xrf, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_xrf, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_xrf, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_xrf, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_xrf, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_xrf, SLOT(in_audio_vol_changed(qreal))); + connect(this, SIGNAL(mycall_changed(QString)), m_xrf, SLOT(mycall_changed(QString))); + connect(this, SIGNAL(urcall_changed(QString)), m_xrf, SLOT(urcall_changed(QString))); + connect(this, SIGNAL(rptr1_changed(QString)), m_xrf, SLOT(rptr1_changed(QString))); + connect(this, SIGNAL(rptr2_changed(QString)), m_xrf, SLOT(rptr2_changed(QString))); + //emit module_changed(m_module); + emit mycall_changed(m_mycall); + emit urcall_changed(m_urcall); + emit rptr1_changed(m_rptr1); + emit rptr2_changed(m_rptr2); + m_modethread->start(); + } + if(m_protocol == "DMR"){ + QString dmrpass = sl.at(2).simplified(); + + if((m_host.size() > 2) && (m_host.left(2) == "BM")){ + if(!m_bm_password.isEmpty()){ + dmrpass = m_bm_password; + } + } + + if((m_host.size() > 4) && (m_host.left(4) == "TGIF")){ + if(!m_tgif_password.isEmpty()){ + dmrpass = m_tgif_password; + } + } + + m_dmr = new DMRCodec(m_callsign, m_dmrid, m_essid, dmrpass, m_latitude, m_longitude, m_location, m_description, m_freq, m_url, m_swid, m_pkgid, m_dmropts, m_dmr_destid, m_hostname, m_port, false, vocoder, modem, m_playback, m_capture); + m_dmr->set_modem_flags(rxInvert, txInvert, pttInvert, useCOSAsLockout, duplex); + m_dmr->set_modem_params(m_modemRxFreq.toInt(), m_modemTxFreq.toInt(), m_modemTxDelay.toInt(), m_modemRxLevel.toFloat(), m_modemRFLevel.toFloat(), ysfTXHang, m_modemCWIdTxLevel.toFloat(), m_modemDstarTxLevel.toFloat(), m_modemDMRTxLevel.toFloat(), m_modemYSFTxLevel.toFloat(), m_modemP25TxLevel.toFloat(), m_modemNXDNTxLevel.toFloat(), pocsagTXLevel); + m_dmr->set_cc(1); + m_modethread = new QThread; + m_dmr->moveToThread(m_modethread); + connect(m_dmr, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_dmr_data(Codec::MODEINFO))); + connect(m_dmr, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_dmr, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_dmr, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_dmr, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_dmr, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_dmr, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_dmr, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_dmr, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_dmr, SLOT(stop_tx())); + connect(this, SIGNAL(dmr_tgid_changed(unsigned int)), m_dmr, SLOT(dmr_tgid_changed(unsigned int))); + connect(this, SIGNAL(dmrpc_state_changed(int)), m_dmr, SLOT(dmrpc_state_changed(int))); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_dmr, SLOT(in_audio_vol_changed(qreal))); + m_modethread->start(); + } + if( (m_protocol == "YSF") || (m_protocol == "FCS") ){ + m_ysf = new YSFCodec(m_callsign, m_host, m_hostname, m_port, false, vocoder, modem, m_playback, m_capture); + m_ysf->set_modem_flags(rxInvert, txInvert, pttInvert, useCOSAsLockout, duplex); + m_ysf->set_modem_params(m_modemRxFreq.toInt(), m_modemTxFreq.toInt(), m_modemTxDelay.toInt(), m_modemRxLevel.toFloat(), m_modemRFLevel.toFloat(), ysfTXHang, m_modemCWIdTxLevel.toFloat(), m_modemDstarTxLevel.toFloat(), m_modemDMRTxLevel.toFloat(), m_modemYSFTxLevel.toFloat(), m_modemP25TxLevel.toFloat(), m_modemNXDNTxLevel.toFloat(), pocsagTXLevel); + m_modethread = new QThread; + m_ysf->moveToThread(m_modethread); + connect(m_ysf, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_ysf_data(Codec::MODEINFO))); + connect(m_ysf, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(this, SIGNAL(m17_rate_changed(int)), m_ysf, SLOT(rate_changed(int))); + connect(m_modethread, SIGNAL(started()), m_ysf, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_ysf, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_ysf, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_ysf, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_ysf, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_ysf, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_ysf, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_ysf, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_ysf, SLOT(in_audio_vol_changed(qreal))); + m_modethread->start(); + } + if(m_protocol == "P25"){ + m_dmrid = m_dmrids.key(m_callsign); + m_dmr_destid = m_host.toUInt(); + m_p25 = new P25Codec(m_callsign, m_dmrid, m_dmr_destid, m_hostname, m_port, false, modem, m_playback, m_capture); + m_modethread = new QThread; + m_p25->moveToThread(m_modethread); + connect(m_p25, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_p25_data(Codec::MODEINFO))); + connect(m_p25, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_p25, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_p25, SLOT(deleteLater())); + connect(this, SIGNAL(agc_state_changed(int)), m_p25, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_p25, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_p25, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_p25, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_p25, SLOT(in_audio_vol_changed(qreal))); + m_modethread->start(); + } + if(m_protocol == "NXDN"){ + m_dmr_destid = m_host.toUInt(); + m_nxdn = new NXDNCodec(m_callsign, m_nxdnids.key(m_callsign), m_dmr_destid, m_hostname, m_port, false, vocoder, modem, m_playback, m_capture); + m_modethread = new QThread; + m_nxdn->moveToThread(m_modethread); + connect(m_nxdn, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_nxdn_data(Codec::MODEINFO))); + connect(m_nxdn, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_nxdn, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_nxdn, SLOT(deleteLater())); + connect(this, SIGNAL(swrx_state_changed(int)), m_nxdn, SLOT(swrx_state_changed(int))); + connect(this, SIGNAL(swtx_state_changed(int)), m_nxdn, SLOT(swtx_state_changed(int))); + connect(this, SIGNAL(agc_state_changed(int)), m_nxdn, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_nxdn, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_nxdn, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_nxdn, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_nxdn, SLOT(in_audio_vol_changed(qreal))); + m_modethread->start(); + } + if(m_protocol == "M17"){ + m_m17 = new M17Codec(m_callsign, m_module, m_host, m_hostname, m_port, false, modem, m_playback, m_capture); + m_modethread = new QThread; + m_m17->moveToThread(m_modethread); + connect(m_m17, SIGNAL(update(Codec::MODEINFO)), this, SLOT(update_m17_data(Codec::MODEINFO))); + connect(m_m17, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(this, SIGNAL(m17_rate_changed(int)), m_m17, SLOT(rate_changed(int))); + connect(m_modethread, SIGNAL(started()), m_m17, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_m17, SLOT(deleteLater())); + connect(this, SIGNAL(agc_state_changed(int)), m_m17, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_m17, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_m17, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_m17, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_m17, SLOT(in_audio_vol_changed(qreal))); + m_modethread->start(); + } + if(m_protocol == "IAX"){ + m_iax = new IAXCodec(m_callsign, m_iaxuser, m_iaxpassword, m_iaxnode, m_iaxhost, m_iaxport, m_playback, m_capture); + m_modethread = new QThread; + m_iax->moveToThread(m_modethread); + connect(m_iax, SIGNAL(update()), this, SLOT(update_iax_data())); + connect(m_iax, SIGNAL(update_output_level(unsigned short)), this, SLOT(update_output_level(unsigned short))); + connect(m_modethread, SIGNAL(started()), m_iax, SLOT(send_connect())); + connect(m_modethread, SIGNAL(finished()), m_iax, SLOT(deleteLater())); + //connect(this, SIGNAL(agc_state_changed(int)), m_xrf, SLOT(agc_state_changed(int))); + connect(this, SIGNAL(tx_clicked(bool)), m_iax, SLOT(toggle_tx(bool))); + connect(this, SIGNAL(tx_pressed()), m_iax, SLOT(start_tx())); + connect(this, SIGNAL(tx_released()), m_iax, SLOT(stop_tx())); + connect(this, SIGNAL(in_audio_vol_changed(qreal)), m_iax, SLOT(in_audio_vol_changed(qreal))); + connect(this, SIGNAL(send_dtmf(QByteArray)), m_iax, SLOT(send_dtmf(QByteArray))); + m_modethread->start(); + } + } + qDebug() << "process_connect called m_callsign == " << m_callsign; + qDebug() << "process_connect called m_dmrid == " << m_dmrid; + qDebug() << "process_connect called m_bm_password == " << m_bm_password; + qDebug() << "process_connect called m_tgif_password == " << m_tgif_password; + qDebug() << "process_connect called m_dmropts == " << m_dmropts; + qDebug() << "process_connect called m_host == " << m_host; + qDebug() << "process_connect called m_hostname == " << m_hostname; + qDebug() << "process_connect called m_module == " << m_module; + qDebug() << "process_connect called m_protocol == " << m_protocol; + qDebug() << "process_connect called m_port == " << m_port; +} + +void DroidStar::process_host_change(const QString &h) +{ + if(m_protocol == "REF"){ + m_saved_refhost = h.simplified(); + } + if(m_protocol == "DCS"){ + m_saved_dcshost = h.simplified(); + } + if(m_protocol == "XRF"){ + m_saved_xrfhost = h.simplified(); + } + if(m_protocol == "YSF"){ + m_saved_ysfhost = h.simplified(); + } + if(m_protocol == "FCS"){ + m_saved_fcshost = h.simplified(); + } + if(m_protocol == "DMR"){ + m_saved_dmrhost = h.simplified(); + } + if(m_protocol == "P25"){ + m_saved_p25host = h.simplified(); + } + if(m_protocol == "NXDN"){ + m_saved_nxdnhost = h.simplified(); + } + if(m_protocol == "M17"){ + m_saved_m17host = h.simplified(); + } + if(m_protocol == "IAX"){ + m_saved_iaxhost = h.simplified(); + } + save_settings(); +} + +void DroidStar::process_mode_change(const QString &m) +{ + m_protocol = m; + if(m == "REF"){ + process_ref_hosts(); + m_label1 = "MYCALL"; + m_label2 = "URCALL"; + m_label3 = "RPTR1"; + m_label4 = "RPTR2"; + m_label5 = "Stream ID"; + m_label6 = "User txt"; + } + if(m == "DCS"){ + process_dcs_hosts(); + m_label1 = "MYCALL"; + m_label2 = "URCALL"; + m_label3 = "RPTR1"; + m_label4 = "RPTR2"; + m_label5 = "Stream ID"; + m_label6 = "User txt"; + } + if(m == "XRF"){ + process_xrf_hosts(); + m_label1 = "MYCALL"; + m_label2 = "URCALL"; + m_label3 = "RPTR1"; + m_label4 = "RPTR2"; + m_label5 = "Stream ID"; + m_label6 = "User txt"; + } + if(m == "YSF"){ + process_ysf_hosts(); + m_label1 = "Gateway"; + m_label2 = "Callsign"; + m_label3 = "Dest"; + m_label4 = "Type"; + m_label5 = "Path"; + m_label6 = "Frame#"; + } + if(m == "FCS"){ + process_fcs_rooms(); + m_label1 = "Gateway"; + m_label2 = "Callsign"; + m_label3 = "Dest"; + m_label4 = "Type"; + m_label5 = "Path"; + m_label6 = "Frame#"; + } + if(m == "DMR"){ + process_dmr_hosts(); + //process_dmr_ids(); + m_label1 = "Callsign"; + m_label2 = "SrcID"; + m_label3 = "DestID"; + m_label4 = "GWID"; + m_label5 = "Seq#"; + m_label6 = ""; + } + if(m == "P25"){ + process_p25_hosts(); + m_label1 = "Callsign"; + m_label2 = "SrcID"; + m_label3 = "DestID"; + m_label4 = "GWID"; + m_label5 = "Seq#"; + m_label6 = ""; + } + if(m == "NXDN"){ + process_nxdn_hosts(); + m_label1 = "Callsign"; + m_label2 = "SrcID"; + m_label3 = "DestID"; + m_label4 = "GWID"; + m_label5 = "Seq#"; + m_label6 = ""; + } + if(m == "M17"){ + process_m17_hosts(); + m_label1 = "SrcID"; + m_label2 = "DstID"; + m_label3 = "Type"; + m_label4 = "Frame#"; + m_label5 = "StreamID"; + m_label6 = ""; + } + if(m == "IAX"){ + m_label1 = ""; + m_label2 = ""; + m_label3 = ""; + m_label4 = ""; + m_label5 = ""; + m_label6 = ""; + } + emit mode_changed(); +} + +void DroidStar::save_settings() +{ + //m_settings->setValue("PLAYBACK", ui->comboPlayback->currentText()); + //m_settings->setValue("CAPTURE", ui->comboCapture->currentText()); + m_settings->setValue("IPV6", m_ipv6 ? "true" : "false"); + m_settings->setValue("MODE", m_protocol); + m_settings->setValue("REFHOST", m_saved_refhost); + m_settings->setValue("DCSHOST", m_saved_dcshost); + m_settings->setValue("XRFHOST", m_saved_xrfhost); + m_settings->setValue("YSFHOST", m_saved_ysfhost); + m_settings->setValue("FCSHOST", m_saved_fcshost); + m_settings->setValue("DMRHOST", m_saved_dmrhost); + m_settings->setValue("P25HOST", m_saved_p25host); + m_settings->setValue("NXDNHOST", m_saved_nxdnhost); + m_settings->setValue("M17HOST", m_saved_m17host); + m_settings->setValue("MODULE", QString(m_module)); + m_settings->setValue("CALLSIGN", m_callsign); + m_settings->setValue("DMRID", m_dmrid); + m_settings->setValue("ESSID", m_essid); + m_settings->setValue("BMPASSWORD", m_bm_password); + m_settings->setValue("TGIFPASSWORD", m_tgif_password); + m_settings->setValue("DMRTGID", m_dmr_destid); + m_settings->setValue("DMRLAT", m_latitude); + m_settings->setValue("DMRLONG", m_longitude); + m_settings->setValue("DMRLOC", m_location); + m_settings->setValue("DMRDESC", m_description); + m_settings->setValue("DMRFREQ", m_freq); + m_settings->setValue("DMRURL", m_url); + m_settings->setValue("DMRSWID", m_swid); + m_settings->setValue("DMRPKGID", m_pkgid); + m_settings->setValue("DMROPTS", m_dmropts); + m_settings->setValue("MYCALL", m_mycall); + m_settings->setValue("URCALL", m_urcall); + m_settings->setValue("RPTR1", m_rptr1); + m_settings->setValue("RPTR2", m_rptr2); + m_settings->setValue("TXTIMEOUT", m_txtimeout); + m_settings->setValue("TXTOGGLE", m_toggletx ? "true" : "false"); + m_settings->setValue("XRF2REF", m_xrf2ref ? "true" : "false"); + m_settings->setValue("USRTXT", m_dstarusertxt); + m_settings->setValue("IAXUSER", m_iaxuser); + m_settings->setValue("IAXPASS", m_iaxpassword); + m_settings->setValue("IAXNODE", m_iaxnode); + m_settings->setValue("IAXHOST", m_iaxhost); + m_settings->setValue("IAXPORT", m_iaxport); + + m_settings->setValue("ModemRxFreq", m_modemRxFreq); + m_settings->setValue("ModemTxFreq", m_modemTxFreq); + m_settings->setValue("ModemRxOffset", m_modemRxOffset); + m_settings->setValue("ModemTxOffset", m_modemTxOffset); + m_settings->setValue("ModemRxDCOffset", m_modemRxDCOffset); + m_settings->setValue("ModemTxDCOffset", m_modemTxDCOffset); + m_settings->setValue("ModemRxLevel", m_modemRxLevel); + m_settings->setValue("ModemTxLevel", m_modemTxLevel); + m_settings->setValue("ModemRFLevel", m_modemRFLevel); + m_settings->setValue("ModemTxDelay", m_modemTxDelay); + m_settings->setValue("ModemCWIdTxLevel", m_modemCWIdTxLevel); + m_settings->setValue("ModemDstarTxLevel", m_modemDstarTxLevel); + m_settings->setValue("ModemDMRTxLevel", m_modemDMRTxLevel); + m_settings->setValue("ModemYSFTxLevel", m_modemYSFTxLevel); + m_settings->setValue("ModemP25TxLevel", m_modemP25TxLevel); + m_settings->setValue("ModemNXDNTxLevel", m_modemNXDNTxLevel); + m_settings->setValue("ModemTxInvert", m_modemTxInvert ? "true" : "false"); + m_settings->setValue("ModemRxInvert", m_modemRxInvert ? "true" : "false"); + m_settings->setValue("ModemPTTInvert", m_modemPTTInvert ? "true" : "false"); +} + +void DroidStar::process_settings() +{ + m_ipv6 = (m_settings->value("IPV6").toString().simplified() == "true") ? true : false; + process_mode_change(m_settings->value("MODE").toString().simplified()); + m_saved_refhost = m_settings->value("REFHOST").toString().simplified(); + m_saved_dcshost =m_settings->value("DCSHOST").toString().simplified(); + m_saved_xrfhost = m_settings->value("XRFHOST").toString().simplified(); + m_saved_ysfhost = m_settings->value("YSFHOST").toString().simplified(); + m_saved_fcshost = m_settings->value("FCSHOST").toString().simplified(); + m_saved_dmrhost = m_settings->value("DMRHOST").toString().simplified(); + m_saved_p25host = m_settings->value("P25HOST").toString().simplified(); + m_saved_nxdnhost = m_settings->value("NXDNHOST").toString().simplified(); + m_saved_m17host = m_settings->value("M17HOST").toString().simplified(); + m_module = m_settings->value("MODULE").toString().toStdString()[0]; + m_callsign = m_settings->value("CALLSIGN").toString().simplified(); + m_dmrid = m_settings->value("DMRID").toString().simplified().toUInt(); + m_essid = m_settings->value("ESSID").toString().simplified().toUInt(); + m_bm_password = m_settings->value("BMPASSWORD").toString().simplified(); + m_tgif_password = m_settings->value("TGIFPASSWORD").toString().simplified(); + m_latitude = m_settings->value("DMRLAT", "0").toString().simplified(); + m_longitude = m_settings->value("DMRLONG", "0").toString().simplified(); + m_location = m_settings->value("DMRLOC").toString().simplified(); + m_description = m_settings->value("DMRDESC", "DroidStar").toString().simplified(); + m_freq = m_settings->value("DMRFREQ", "438800000").toString().simplified(); + m_url = m_settings->value("DMRURL", "www.qrz.com").toString().simplified(); + m_swid = m_settings->value("DMRSWID", "20200922").toString().simplified(); + m_pkgid = m_settings->value("DMRPKGID", "MMDVM_MMDVM_HS_Hat").toString().simplified(); + m_dmropts = m_settings->value("DMROPTS").toString().simplified(); + m_dmr_destid = m_settings->value("DMRTGID", "4000").toString().simplified().toUInt(); + m_mycall = m_settings->value("MYCALL").toString().simplified(); + m_urcall = m_settings->value("URCALL", "CQCQCQ").toString().simplified(); + m_rptr1 = m_settings->value("RPTR1").toString().simplified(); + m_rptr2 = m_settings->value("RPTR2").toString().simplified(); + m_txtimeout = m_settings->value("TXTIMEOUT", "300").toString().simplified().toUInt(); + m_toggletx = (m_settings->value("TXTOGGLE", "true").toString().simplified() == "true") ? true : false; + m_dstarusertxt = m_settings->value("USRTXT").toString().simplified(); + m_xrf2ref = (m_settings->value("XRF2REF").toString().simplified() == "true") ? true : false; + m_iaxuser = m_settings->value("IAXUSER").toString().simplified(); + m_iaxpassword = m_settings->value("IAXPASS").toString().simplified(); + m_iaxnode = m_settings->value("IAXNODE").toString().simplified(); + m_saved_iaxhost = m_iaxhost = m_settings->value("IAXHOST").toString().simplified(); + m_iaxport = m_settings->value("IAXPORT", "4569").toString().simplified().toUInt(); + m_localhosts = m_settings->value("LOCALHOSTS").toString(); + + m_modemRxFreq = m_settings->value("ModemRxFreq", "438800000").toString().simplified(); + m_modemTxFreq = m_settings->value("ModemTxFreq", "438800000").toString().simplified(); + m_modemRxOffset = m_settings->value("ModemRxOffset", "0").toString().simplified(); + m_modemTxOffset = m_settings->value("ModemTxOffset", "0").toString().simplified(); + m_modemRxDCOffset = m_settings->value("ModemRxDCOffset", "0").toString().simplified(); + m_modemTxDCOffset = m_settings->value("ModemTxDCOffset", "0").toString().simplified(); + m_modemRxLevel = m_settings->value("ModemRxLevel", "50").toString().simplified(); + m_modemTxLevel = m_settings->value("ModemTxLevel", "50").toString().simplified(); + m_modemRFLevel = m_settings->value("ModemRFLevel", "100").toString().simplified(); + m_modemTxDelay = m_settings->value("ModemTxDelay", "100").toString().simplified(); + m_modemCWIdTxLevel = m_settings->value("ModemCWIdTxLevel", "50").toString().simplified(); + m_modemDstarTxLevel = m_settings->value("ModemDstarTxLevel", "50").toString().simplified(); + m_modemDMRTxLevel = m_settings->value("ModemDMRTxLevel", "50").toString().simplified(); + m_modemYSFTxLevel = m_settings->value("ModemYSFTxLevel", "50").toString().simplified(); + m_modemP25TxLevel = m_settings->value("ModemP25TxLevel", "50").toString().simplified(); + m_modemNXDNTxLevel = m_settings->value("ModemNXDNTxLevel", "50").toString().simplified(); + m_modemTxInvert = (m_settings->value("ModemTxInvert", "true").toString().simplified() == "true") ? true : false; + m_modemRxInvert = (m_settings->value("ModemRxInvert", "false").toString().simplified() == "true") ? true : false; + m_modemPTTInvert = (m_settings->value("ModemPTTInvert", "false").toString().simplified() == "true") ? true : false; + emit update_settings(); +} + +void DroidStar::update_custom_hosts(QString h) +{ + m_settings->setValue("LOCALHOSTS", h); + m_localhosts = m_settings->value("LOCALHOSTS").toString(); +} + +void DroidStar::process_ref_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/dplus.txt"); + + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/dplus.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.split('\t'); + if(ll.size() > 1){ + m_hostmap[ll.at(0).simplified()] = ll.at(1).simplified() + ",20001"; + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "REF"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/dplus.txt"); + } +} + +void DroidStar::process_dcs_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/dcs.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/dcs.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.split('\t'); + if(ll.size() > 1){ + m_hostmap[ll.at(0).simplified()] = ll.at(1).simplified() + ",30051"; + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "DCS"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/dcs.txt"); + } +} + +void DroidStar::process_xrf_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/dextra.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/dextra.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.split('\t'); + if(ll.size() > 1){ + m_hostmap[ll.at(0).simplified()] = ll.at(1).simplified() + ",30001"; + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "XRF"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/dextra.txt"); + } +} + +void DroidStar::process_ysf_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/YSFHosts.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/YSFHosts.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.split(';'); + if(ll.size() > 4){ + m_hostmap[ll.at(1).simplified()] = ll.at(3) + "," + ll.at(4); + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "YSF"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + //process_fcs_rooms(); + } + else{ + download_file("/YSFHosts.txt"); + } +} + +void DroidStar::process_fcs_rooms() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/FCSHosts.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/FCSHosts.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.split(';'); + if(ll.size() > 4){ + if(ll.at(1).simplified() != "nn"){ + m_hostmap[ll.at(0).simplified() + " - " + ll.at(1).simplified()] = ll.at(2).left(6).toLower() + ".xreflector.net,62500"; + } + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "FCS"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/FCSHosts.txt"); + } +} + +void DroidStar::process_dmr_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/DMRHosts.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/DMRHosts.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.simplified().split(' '); + if(ll.size() > 4){ + if( (ll.at(0).simplified() != "DMRGateway") + && (ll.at(0).simplified() != "DMR2YSF") + && (ll.at(0).simplified() != "DMR2NXDN")) + { + m_hostmap[ll.at(0).simplified()] = ll.at(2) + "," + ll.at(4) + "," + ll.at(3); + } + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "DMR"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified() + "," + line.at(4).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/DMRHosts.txt"); + } +} + +void DroidStar::process_p25_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/P25Hosts.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/P25Hosts.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.simplified().split(' '); + if(ll.size() > 2){ + m_hostmap[ll.at(0).simplified()] = ll.at(1) + "," + ll.at(2); + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "P25"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/P25Hosts.txt"); + } +} + +void DroidStar::process_nxdn_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/NXDNHosts.txt"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/NXDNHosts.txt"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.simplified().split(' '); + if(ll.size() > 2){ + m_hostmap[ll.at(0).simplified()] = ll.at(1) + "," + ll.at(2); + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "NXDN"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/NXDNHosts.txt"); + } +} + +void DroidStar::process_m17_hosts() +{ + m_hostmap.clear(); + m_hostsmodel.clear(); + QFileInfo check_file(config_path + "/M17Hosts-full.csv"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/M17Hosts-full.csv"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString l = f.readLine(); + if(l.at(0) == '#'){ + continue; + } + QStringList ll = l.simplified().split(','); + if(ll.size() > 3){ + m_hostmap[ll.at(0).simplified()] = ll.at(2) + "," + ll.at(4) + "," + ll.at(3); + } + } + + m_customhosts = m_localhosts.split('\n'); + for (const auto& i : m_customhosts){ + QStringList line = i.simplified().split(' '); + + if(line.at(0) == "M17"){ + m_hostmap[line.at(1).simplified()] = line.at(2).simplified() + "," + line.at(3).simplified(); + } + } + + QMap::const_iterator i = m_hostmap.constBegin(); + while (i != m_hostmap.constEnd()) { + m_hostsmodel.append(i.key()); + ++i; + } + } + f.close(); + } + else{ + download_file("/M17Hosts-full.csv"); + } +} + +void DroidStar::process_dmr_ids() +{ + QFileInfo check_file(config_path + "/DMRIDs.dat"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/DMRIDs.dat"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString lids = f.readLine(); + if(lids.at(0) == '#'){ + continue; + } + QStringList llids = lids.simplified().split(' '); + + if(llids.size() >= 2){ + m_dmrids[llids.at(0).toUInt()] = llids.at(1); + } + } + } + f.close(); + } + else{ + download_file("/DMRIDs.dat"); + } +} + +void DroidStar::update_dmr_ids() +{ + QFileInfo check_file(config_path + "/DMRIDs.dat"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/DMRIDs.dat"); + f.remove(); + } + process_dmr_ids(); + update_nxdn_ids(); +} + +void DroidStar::process_nxdn_ids() +{ + QFileInfo check_file(config_path + "/NXDN.csv"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/NXDN.csv"); + if(f.open(QIODevice::ReadOnly)){ + while(!f.atEnd()){ + QString lids = f.readLine(); + if(lids.at(0) == '#'){ + continue; + } + QStringList llids = lids.simplified().split(','); + + if(llids.size() > 1){ + m_nxdnids[llids.at(0).toUInt()] = llids.at(1); + } + } + } + f.close(); + } + else{ + download_file("/NXDN.csv"); + } +} + +void DroidStar::update_nxdn_ids() +{ + QFileInfo check_file(config_path + "/NXDN.csv"); + if(check_file.exists() && check_file.isFile()){ + QFile f(config_path + "/NXDN.csv"); + f.remove(); + } + process_nxdn_ids(); +} + +void DroidStar::update_host_files() +{ + m_update_host_files = true; + check_host_files(); +} + +void DroidStar::check_host_files() +{ + if(!QDir(config_path).exists()){ + QDir().mkdir(config_path); + } + + QFileInfo check_file(config_path + "/dplus.txt"); + if( (!check_file.exists() && !(check_file.isFile())) || m_update_host_files ){ + download_file("/dplus.txt"); + } + + check_file.setFile(config_path + "/dextra.txt"); + if( (!check_file.exists() && !check_file.isFile() ) || m_update_host_files ){ + download_file("/dextra.txt"); + } + + check_file.setFile(config_path + "/dcs.txt"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file( "/dcs.txt"); + } + + check_file.setFile(config_path + "/YSFHosts.txt"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/YSFHosts.txt"); + } + + check_file.setFile(config_path + "/FCSHosts.txt"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/FCSHosts.txt"); + } + + check_file.setFile(config_path + "/DMRHosts.txt"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/DMRHosts.txt"); + } + + check_file.setFile(config_path + "/P25Hosts.txt"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/P25Hosts.txt"); + } + + check_file.setFile(config_path + "/NXDNHosts.txt"); + if((!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/NXDNHosts.txt"); + } + + check_file.setFile(config_path + "/M17Hosts-full.csv"); + if( (!check_file.exists() && !check_file.isFile()) || m_update_host_files ){ + download_file("/M17Hosts-full.csv"); + } + + check_file.setFile(config_path + "/DMRIDs.dat"); + if(!check_file.exists() && !check_file.isFile()){ + download_file("/DMRIDs.dat"); + } + else { + process_dmr_ids(); + } + + check_file.setFile(config_path + "/NXDN.csv"); + if(!check_file.exists() && !check_file.isFile()){ + download_file("/NXDN.csv"); + } + else{ + process_nxdn_ids(); + } + m_update_host_files = false; + //process_mode_change(ui->modeCombo->currentText().simplified()); +#if defined(Q_OS_ANDROID) + QString vocname = "/vocoder_plugin." + QSysInfo::productType() + "." + QSysInfo::currentCpuArchitecture(); +#else + QString vocname = "/vocoder_plugin." + QSysInfo::kernelType() + "." + QSysInfo::currentCpuArchitecture(); +#endif + QString newvoc = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + vocname; + QString voc = config_path + vocname; + check_file.setFile(newvoc); + qDebug() << "newvoc == " << newvoc; + qDebug() << "voc == " << voc; + if(check_file.exists() && check_file.isFile()){ + qDebug() << newvoc << " found"; + if(QFile::exists(voc)){ + qDebug() << voc << " found"; + if(QFile::remove(voc)){ + qDebug() << voc << " deleted"; + } + else{ + qDebug() << voc << " not deleted"; + } + } + if(QFile::copy(newvoc, voc)){ + qDebug() << newvoc << " copied"; + } + else{ + qDebug() << "Could not copy " << newvoc; + } + } + else{ + qDebug() << newvoc << " not found"; + } +} + +void DroidStar::update_ref_data(Codec::MODEINFO info) +{ + if((connect_status == Codec::CONNECTING) && (info.status == Codec::DISCONNECTED)){ + process_connect(); + return; + } + if( (connect_status == Codec::CONNECTING) && (info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.0); + emit swtx_state(!m_ref->get_hwtx()); + emit swrx_state(!m_ref->get_hwrx()); + emit rptr2_changed(m_host + " " + m_module); + if(m_mycall.isEmpty()) set_mycall(m_callsign); + if(m_urcall.isEmpty()) set_urcall("CQCQCQ"); + if(m_rptr1.isEmpty()) set_rptr1(m_callsign + " " + m_module); + emit update_log("Connected to DStar " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + if(info.streamid){ + m_data1 = info.src; + m_data2 = info.dst; + m_data3 = info.gw; + m_data4 = info.gw2; + m_data5 = QString::number(info.streamid, 16) + " " + QString::number(info.frame_number, 16); + m_data6 = info.usertxt; + } + else{ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " XRF RX started id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " XRF RX ended id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " XRF RX lost id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + emit update_data(); +} + +void DroidStar::update_dcs_data(Codec::MODEINFO info) +{ + if((connect_status == Codec::CONNECTING) && (info.status == Codec::DISCONNECTED)){ + process_connect(); + return; + } + if( (connect_status == Codec::CONNECTING) && (info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.0); + emit swtx_state(!m_dcs->get_hwtx()); + emit swrx_state(!m_dcs->get_hwrx()); + emit rptr2_changed(m_host + " " + m_module); + if(m_mycall.isEmpty()) set_mycall(m_callsign); + if(m_urcall.isEmpty()) set_urcall("CQCQCQ"); + if(m_rptr1.isEmpty()) set_rptr1(m_callsign + " " + m_module); + emit update_log("Connected to DStar " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.status); + if(info.streamid){ + m_data1 = info.src; + m_data2 = info.dst; + m_data3 = info.gw; + m_data4 = info.gw2; + m_data5 = QString::number(info.streamid, 16) + " " + QString::number(info.frame_number, 16); + m_data6 = info.usertxt; + } + else{ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " XRF RX started id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " XRF RX ended id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " XRF RX lost id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + + emit update_data(); +} + +void DroidStar::update_xrf_data(Codec::MODEINFO info) +{ + if((connect_status == Codec::CONNECTING) && (info.status == Codec::DISCONNECTED)){ + process_connect(); + return; + } + if( (connect_status == Codec::CONNECTING) && (info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.0); + emit swtx_state(!m_xrf->get_hwtx()); + emit swrx_state(!m_xrf->get_hwrx()); + emit rptr2_changed(m_host + " " + m_module); + if(m_mycall.isEmpty()) set_mycall(m_callsign); + if(m_urcall.isEmpty()) set_urcall("CQCQCQ"); + if(m_rptr1.isEmpty()) set_rptr1(m_callsign + " " + m_module); + emit update_log("Connected to DStar " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + if(info.streamid){ + m_data1 = info.src; + m_data2 = info.dst; + m_data3 = info.gw; + m_data4 = info.gw2; + m_data5 = QString::number(info.streamid, 16) + " " + QString::number(info.frame_number, 16); + m_data6 = info.usertxt; + } + else{ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " XRF RX started id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " XRF RX ended id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " XRF RX lost id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.gw2); + } + emit update_data(); +} + +void DroidStar::update_nxdn_data(Codec::MODEINFO info) +{ + if( (connect_status == Codec::CONNECTING) && ( info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.3); + emit swtx_state(!m_nxdn->get_hwtx()); + emit swrx_state(!m_nxdn->get_hwrx()); + emit update_log("Connected to " + m_protocol + " " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + if(info.stream_state == Codec::STREAM_IDLE){ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + else{ + if(info.srcid){ + m_data1 = m_nxdnids[info.srcid]; + m_data2 = QString::number(info.srcid); + } + m_data3 = QString::number(info.dstid); + + if(info.frame_number){ + QString n = QString("%1").arg(info.frame_number, 4, 16, QChar('0')); + m_data5 = n; + } + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " NXDN RX started id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " NXDN RX ended id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " NXDN RX lost id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + emit update_data(); +} + +void DroidStar::update_dmr_data(Codec::MODEINFO info) +{ + if((connect_status == Codec::CONNECTING) && (info.status == Codec::DISCONNECTED)){ + m_errortxt = "Connection refused by DMR server. You must have a valid DMR ID, matching callsign, and if you are already connected with this DMR ID on another device, you must use a unique ESSID. Some servers also require specific Lat/Long/Location/Decsription settings. It is up to you to determine the connect requirements for the server you are connecting to."; + emit connect_status_changed(5); + //process_connect(); + return; + } + if(info.status == Codec::CLOSED){ + m_errortxt = "DMR server closing down. Try a different server or try again later."; + emit connect_status_changed(5); + //process_connect(); + return; + } + if( (connect_status == Codec::CONNECTING) && ( info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.3); + emit swtx_state(!m_dmr->get_hwtx()); + emit swrx_state(!m_dmr->get_hwrx()); + emit update_log("Connected to " + m_protocol + " " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_host + " Cnt: " + QString::number(info.count); + if(info.stream_state == Codec::STREAM_IDLE){ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + else{ + m_data1 = m_dmrids[info.srcid]; + m_data2 = info.srcid ? QString::number(info.srcid) : ""; + m_data3 = info.dstid ? QString::number(info.dstid) : ""; + m_data4 = info.gwid ? QString::number(info.gwid) : ""; + + if(info.frame_number){ + QString n = QString("%1").arg(info.frame_number, 4, 16, QChar('0')); + m_data5 = n; + } + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " DMR RX started id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " DMR RX ended id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " DMR RX lost id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + emit update_data(); +} + +void DroidStar::update_ysf_data(Codec::MODEINFO info) +{ + if( (connect_status == Codec::CONNECTING) && (info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.2); + emit swtx_state(!m_ysf->get_hwtx()); + emit swrx_state(!m_ysf->get_hwrx()); + emit update_log("Connected to " + m_protocol + " " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + + if(info.vocoder_loaded){ + emit update_log("Vocoder plugin loaded"); + } + else{ + emit update_log("No vocoder plugin found"); + } + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + if(info.stream_state == Codec::STREAM_IDLE){ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + else{ + m_data1 = info.gw; + m_data2 =info.src; + m_data3 = info.dst; + + if(info.type == 0){ + m_data4 = "V/D mode 1"; + } + else if(info.type == 1){ + m_data4 = "Data Full Rate"; + } + else if(info.type == 2){ + m_data4 = "V/D mode 2"; + } + else if(info.type == 3){ + m_data4 = "Voice Full Rate"; + } + else{ + m_data4 = ""; + } + if(info.type >= 0){ + m_data5 = info.path ? "Internet" : "Local"; + m_data6 = QString::number(info.frame_number) + "/" + QString::number(info.frame_total); + } + else{ + m_data5 = m_data6 = ""; + } + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " YSF RX started"); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " YSF RX ended"); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " YSF RX lost"); + } + emit update_data(); +} + +void DroidStar::update_p25_data(Codec::MODEINFO info) +{ + if( (connect_status == Codec::CONNECTING) && (info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.3); + emit update_log("Connected to " + m_protocol + " " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + if(info.stream_state == Codec::STREAM_IDLE){ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + m_data6.clear(); + } + else{ + m_data1 = m_dmrids[info.srcid]; + m_data2 = info.srcid ? QString::number(info.srcid) : ""; + m_data3 = info.dstid ? QString::number(info.dstid) : ""; + m_data4 = info.srcid ? QString::number(info.srcid) : ""; + if(info.frame_number){ + QString n = QString("%1").arg(info.frame_number, 4, 16, QChar('0')); + m_data5 = n; + } + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " P25 RX started id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " P25 RX ended id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " P25 RX lost id: " + QString::number(info.streamid, 16) + " src: " + QString::number(info.srcid) + " dst: " + QString::number(info.dstid)); + } + emit update_data(); +} + +void DroidStar::update_m17_data(M17Codec::MODEINFO info) +{ + if( (connect_status == Codec::CONNECTING) && ( info.status == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.5); + emit update_log("Connected to " + m_protocol + " " + m_host + " " + m_hostname + ":" + QString::number(m_port)); + } + m_statustxt = "Host: " + m_hostname + ":" + QString::number(m_port) + " Cnt: " + QString::number(info.count); + + if(info.streamid){ + m_data1 = info.src; + m_data2 = info.dst; + m_data3 = info.type ? "3200 Voice" : "1600 V/D"; + if(info.frame_number){ + QString n = QString("%1").arg(info.frame_number, 4, 16, QChar('0')); + m_data4 = n; + } + m_data5 = QString::number(info.streamid, 16); + } + else{ + m_data1.clear(); + m_data2.clear(); + m_data3.clear(); + m_data4.clear(); + m_data5.clear(); + } + QString t = QDateTime::fromMSecsSinceEpoch(info.ts).toString("yyyy.MM.dd hh:mm:ss.zzz"); + if(info.stream_state == Codec::STREAM_NEW){ + emit update_log(t + " M17 RX started id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.dst); + } + if(info.stream_state == Codec::STREAM_END){ + emit update_log(t + " M17 RX ended id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.dst); + } + if(info.stream_state == Codec::STREAM_LOST){ + emit update_log(t + " M17 RX lost id: " + QString::number(info.streamid, 16) + " src: " + info.src + " dst: " + info.dst); + } + emit update_data(); +} + +void DroidStar::update_iax_data() +{ + if((connect_status == Codec::CONNECTING) && (m_iax->get_status() == Codec::DISCONNECTED)){ + m_errortxt = "Connection refused by. Check your AllStar/IAX settings and make sure that you have permission to connect to this node."; + emit connect_status_changed(5); + return; + } + if( (connect_status == Codec::CONNECTING) && ( m_iax->get_status() == Codec::CONNECTED_RW)){ + connect_status = Codec::CONNECTED_RW; + emit connect_status_changed(2); + emit in_audio_vol_changed(0.5); + emit update_log("Connected to " + m_protocol + " " + m_iaxhost + ":" + QString::number(m_iaxport)); + } + m_statustxt = "Host: " + m_iaxhost + ":" + QString::number(m_iaxport) + " Cnt: " + QString::number(m_iax->get_cnt()); + + emit update_data(); +} + +void DroidStar::set_input_volume(qreal v) +{ + emit in_audio_vol_changed(v); + //audioin->setVolume(v * 0.01); +} + +void DroidStar::press_tx() +{ + emit tx_pressed(); +} + +void DroidStar::release_tx() +{ + emit tx_released(); +} + +void DroidStar::click_tx(bool tx) +{ + emit tx_clicked(tx); +} diff --git a/droidstar.h b/droidstar.h new file mode 100644 index 0000000..9bfe307 --- /dev/null +++ b/droidstar.h @@ -0,0 +1,385 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef DROIDSTAR_H +#define DROIDSTAR_H + +#include +#include +#include "refcodec.h" +#include "dcscodec.h" +#include "xrfcodec.h" +#include "ysfcodec.h" +#include "dmrcodec.h" +#include "p25codec.h" +#include "nxdncodec.h" +#include "m17codec.h" +#include "iaxcodec.h" + +class DroidStar : public QObject +{ + Q_OBJECT + //Q_PROPERTY(QString mode READ mode WRITE set_mode NOTIFY mode_changed) +public: + explicit DroidStar(QObject *parent = nullptr); + ~DroidStar(); + +signals: + void mode_changed(); + void module_changed(char); + void update_data(); + void update_log(QString); + void update_settings(); + void connect_status_changed(int c); + void in_audio_vol_changed(qreal); + void tx_pressed(); + void tx_released(); + void tx_clicked(bool); + void dmrpc_state_changed(int); + void dmr_tgid_changed(unsigned int); + void m17_rate_changed(int); + void send_dtmf(QByteArray); + void swtx_state_changed(int); + void swrx_state_changed(int); + void swtx_state(int); + void swrx_state(int); + void agc_state(int); + void agc_state_changed(int); + void rptr1_changed(QString); + void rptr2_changed(QString); + void mycall_changed(QString); + void urcall_changed(QString); +public slots: + void set_callsign(const QString &callsign) { m_callsign = callsign.simplified(); save_settings(); } + void set_dmrtgid(const QString &dmrtgid) { m_dmr_destid = dmrtgid.simplified().toUInt(); save_settings(); } + void tgid_text_changed(QString s){ qDebug() << "dmrid_text_changed() called s == " << s; emit dmr_tgid_changed(s.toUInt());} + void set_dmrid(const QString &dmrid) { m_dmrid = dmrid.simplified().toUInt(); save_settings(); } + void set_essid(const QString &essid) + { + if (essid != "None") { + m_essid = essid.simplified().toUInt() + 1; + save_settings(); + } + else{ + m_essid = 0; + } + } + void set_bm_password(const QString &bmpwd) { m_bm_password = bmpwd; save_settings(); } + void set_tgif_password(const QString &tgifpwd) { m_tgif_password = tgifpwd; save_settings(); } + void set_latitude(const QString &lat){ m_latitude = lat; save_settings(); } + void set_longitude(const QString &lon){ m_longitude = lon; save_settings(); } + void set_location(const QString &loc){ m_location = loc; save_settings(); } + void set_description(const QString &desc){ m_description = desc; save_settings(); } + void set_freq(const QString &freq){ m_freq = freq; save_settings(); } + void set_url(const QString &url){ m_url = url; save_settings(); } + void set_swid(const QString &swid){ m_swid = swid; save_settings(); } + void set_pkgid(const QString &pkgid){ m_pkgid = pkgid; save_settings(); } + void set_dmr_options(const QString &dmropts) { m_dmropts = dmropts; save_settings(); } + void set_dmr_pc(int pc) { emit dmrpc_state_changed(pc); } + //void set_host(const QString &host) { m_host = host; save_settings(); } + void set_module(const QString &module) { m_module = module.toStdString()[0]; save_settings(); emit module_changed(m_module);} + void set_protocol(const QString &protocol) { m_protocol = protocol; save_settings(); } + void set_input_volume(qreal v); + void set_modelchange(bool t){ m_modelchange = t; } + void set_iaxuser(const QString &user){ m_iaxuser = user; save_settings(); } + void set_iaxpass(const QString &pass){ m_iaxpassword = pass; save_settings(); } + void set_iaxnode(const QString &node){ m_iaxnode = node; save_settings(); } + void set_iaxhost(const QString &host){ m_iaxhost = host; save_settings(); } + void set_mycall(const QString &mycall) { m_mycall = mycall; save_settings(); emit mycall_changed(mycall); } + void set_urcall(const QString &urcall) { m_urcall = urcall; save_settings(); emit urcall_changed(urcall); } + void set_rptr1(const QString &rptr1) { m_rptr1 = rptr1; save_settings(); emit rptr1_changed(rptr1); } + void set_rptr2(const QString &rptr2) { m_rptr2 = rptr2; save_settings(); emit rptr2_changed(rptr2); } + void set_txtimeout(const QString &t) { m_txtimeout = t.simplified().toUInt(); save_settings();} + void set_toggletx(bool x) { m_toggletx = x; save_settings(); } + void set_xrf2ref(bool x) { m_xrf2ref = x; save_settings(); } + void set_ipv6(bool ipv6) { m_ipv6 = ipv6; save_settings(); } + void set_vocoder(QString vocoder) { m_vocoder = vocoder; } + void set_modem(QString modem) { m_modem = modem; } + void set_playback(QString playback) { m_playback = playback; } + void set_capture(QString capture) { m_capture = capture; } + void set_swtx(bool swtx) { emit swtx_state_changed(swtx); } + void set_swrx(bool swrx) { emit swrx_state_changed(swrx); } + void set_agc(bool agc) { emit agc_state_changed(agc); } + void set_iaxport(const QString &port){ m_iaxport = port.simplified().toUInt(); save_settings(); } + + void set_modemRxFreq(QString m) { m_modemRxFreq = m; save_settings(); } + void set_modemTxFreq(QString m) { m_modemTxFreq = m; save_settings(); } + void set_modemRxOffset(QString m) { m_modemRxOffset = m; save_settings(); } + void set_modemTxOffset(QString m) { m_modemTxOffset = m; save_settings(); } + void set_modemRxDCOffset(QString m) { m_modemRxDCOffset = m; save_settings(); } + void set_modemTxDCOffset(QString m) { m_modemTxDCOffset = m; save_settings(); } + void set_modemRxLevel(QString m) { m_modemRxLevel = m; save_settings(); } + void set_modemTxLevel(QString m) { m_modemTxLevel = m; save_settings(); } + void set_modemRFLevel(QString m) { m_modemRFLevel = m; save_settings(); } + void set_modemTxDelay(QString m) { m_modemTxDelay = m; save_settings(); } + void set_modemCWIdTxLevel(QString m) { m_modemCWIdTxLevel = m; save_settings(); } + void set_modemDstarTxLevel(QString m) { m_modemDstarTxLevel = m; save_settings(); } + void set_modemDMRTxLevel(QString m) { m_modemDMRTxLevel = m; save_settings(); } + void set_modemYSFTxLevel(QString m) { m_modemYSFTxLevel = m; save_settings(); } + void set_modemP25TxLevel(QString m) { m_modemP25TxLevel = m; save_settings(); } + void set_modemNXDNTxLevel(QString m) { m_modemNXDNTxLevel = m; save_settings(); } + + void m17_rate_changed(bool r) { qDebug() << "m17_rate_changed() r == " << r; emit m17_rate_changed((int)r); } + void process_connect(); + void press_tx(); + void release_tx(); + void click_tx(bool); + void process_settings(); + void check_host_files(); + void update_host_files(); + void update_custom_hosts(QString); + void update_dmr_ids(); + void update_nxdn_ids(); + void process_mode_change(const QString &m); + void process_host_change(const QString &h); + void dtmf_send_clicked(QString); + bool get_modelchange(){ return m_modelchange; } + QString get_label1() { return m_label1; } + QString get_label2() { return m_label2; } + QString get_label3() { return m_label3; } + QString get_label4() { return m_label4; } + QString get_label5() { return m_label5; } + QString get_label6() { return m_label6; } + QString get_data1() { return m_data1; } + QString get_data2() { return m_data2; } + QString get_data3() { return m_data3; } + QString get_data4() { return m_data4; } + QString get_data5() { return m_data5; } + QString get_data6() { return m_data6; } + QString get_statustxt() { return m_statustxt; } + QString get_mode() { return m_protocol; } + QString get_host() { return m_host; } + QString get_module() { return QString(m_module); } + QString get_callsign() { return m_callsign; } + QString get_dmrid() { return m_dmrid ? QString::number(m_dmrid) : ""; } + QString get_essid() { return m_essid ? QString("%1").arg(m_essid - 1, 2, 10, QChar('0')) : "None"; } + QString get_bm_password() { return m_bm_password; } + QString get_tgif_password() { return m_tgif_password; } + QString get_latitude() { return m_latitude; } + QString get_longitude() { return m_longitude; } + QString get_location() { return m_location; } + QString get_description() { return m_description; } + QString get_freq() { return m_freq; } + QString get_url() { return m_url; } + QString get_swid() { return m_swid; } + QString get_pkgid() { return m_pkgid; } + QString get_dmr_options() { return m_dmropts; } + QString get_dmrtgid() { return m_dmr_destid ? QString::number(m_dmr_destid) : ""; } + QStringList get_hosts() { return m_hostsmodel; } + QString get_ref_host() { return m_saved_refhost; } + QString get_dcs_host() { return m_saved_dcshost; } + QString get_xrf_host() { return m_saved_xrfhost; } + QString get_ysf_host() { return m_saved_ysfhost; } + QString get_fcs_host() { return m_saved_fcshost; } + QString get_dmr_host() { return m_saved_dmrhost; } + QString get_p25_host() { return m_saved_p25host; } + QString get_nxdn_host() { return m_saved_nxdnhost; } + QString get_m17_host() { return m_saved_m17host; } + QString get_iax_host() { return m_saved_iaxhost; } + QString get_iax_user() { return m_iaxuser; } + QString get_iax_pass() { return m_iaxpassword; } + QString get_iax_node() { return m_iaxnode; } + QString get_iax_port() { return QString::number(m_iaxport); } + QString get_mycall() { return m_mycall; } + QString get_urcall() { return m_urcall; } + QString get_rptr1() { return m_rptr1; } + QString get_rptr2() { return m_rptr2; } + QString get_txtimeout() { return QString::number(m_txtimeout); } + QString get_error_text() { return m_errortxt; } + bool get_toggletx() { return m_toggletx; } + bool get_ipv6() { return m_ipv6; } + bool get_xrf2ref() { return m_xrf2ref; } + QString get_local_hosts(){ return m_localhosts; } + QStringList get_vocoders() { return m_vocoders; } + QStringList get_modems() { return m_modems; } + QStringList get_playbacks() { return m_playbacks; } + QStringList get_captures() { return m_captures; } + QString get_modemRxFreq() { return m_modemRxFreq; } + QString get_modemTxFreq() { return m_modemTxFreq; } + QString get_modemRxOffset() { return m_modemRxOffset; } + QString get_modemTxOffset() { return m_modemTxOffset; } + QString get_modemRxDCOffset() { return m_modemRxDCOffset; } + QString get_modemTxDCOffset() { return m_modemTxDCOffset; } + QString get_modemRxLevel() { return m_modemRxLevel; } + QString get_modemTxLevel() { return m_modemTxLevel; } + QString get_modemRFLevel() { return m_modemRFLevel; } + QString get_modemTxDelay() { return m_modemTxDelay; } + QString get_modemCWIdTxLevel() { return m_modemCWIdTxLevel; } + QString get_modemDstarTxLevel() { return m_modemDstarTxLevel; } + QString get_modemDMRTxLevel() { return m_modemDMRTxLevel; } + QString get_modemYSFTxLevel() { return m_modemYSFTxLevel; } + QString get_modemP25TxLevel() { return m_modemP25TxLevel; } + QString get_modemNXDNTxLevel() { return m_modemNXDNTxLevel; } +#if defined(Q_OS_ANDROID) + QString get_platform() { return QSysInfo::productType(); } +#else + QString get_platform() { return QSysInfo::kernelType(); } +#endif + QString get_arch() { return QSysInfo::currentCpuArchitecture(); } + QString get_build_abi() { return QSysInfo::buildAbi(); } + QString get_software_build() { return VERSION_NUMBER; } + + void download_file(QString); + void file_downloaded(QString); + unsigned short get_output_level(){ return m_outlevel; } + void set_output_level(unsigned short l){ m_outlevel = l; } +private: + int connect_status; + bool m_update_host_files; + QSettings *m_settings; + QString config_path; + QString hosts_filename; + QString m_callsign; + QString m_host; + QString m_hostname; + QString m_protocol; + QString m_bm_password; + QString m_tgif_password; + QString m_latitude; + QString m_longitude; + QString m_location; + QString m_description; + QString m_freq; + QString m_url; + QString m_swid; + QString m_pkgid; + QString m_dmropts; + QString m_saved_refhost; + QString m_saved_dcshost; + QString m_saved_xrfhost; + QString m_saved_ysfhost; + QString m_saved_fcshost; + QString m_saved_dmrhost; + QString m_saved_p25host; + QString m_saved_nxdnhost; + QString m_saved_m17host; + QString m_saved_iaxhost; + uint32_t m_dmrid; + uint8_t m_essid; + uint32_t m_dmr_srcid; + uint32_t m_dmr_destid; + QMap m_dmrids; + QMap m_nxdnids; + char m_module; + int m_port; + QString m_label1; + QString m_label2; + QString m_label3; + QString m_label4; + QString m_label5; + QString m_label6; + QString m_data1; + QString m_data2; + QString m_data3; + QString m_data4; + QString m_data5; + QString m_data6; + QString m_statustxt; + QString m_mycall; + QString m_urcall; + QString m_rptr1; + QString m_rptr2; + int m_txtimeout; + bool m_toggletx; + QString m_dstarusertxt; + QStringList m_hostsmodel; + QMap m_hostmap; + QStringList m_customhosts; + QThread *m_modethread; + REFCodec *m_ref; + DCSCodec *m_dcs; + XRFCodec *m_xrf; + YSFCodec *m_ysf; + DMRCodec *m_dmr; + P25Codec *m_p25; + NXDNCodec *m_nxdn; + M17Codec *m_m17; + IAXCodec *m_iax; + QByteArray user_data; + QString m_iaxuser; + QString m_iaxpassword; + QString m_iaxnode; + QString m_iaxhost; + QString m_localhosts; + int m_iaxport; + bool m_settings_processed; + bool m_modelchange; + const unsigned char header[5] = {0x80,0x44,0x53,0x56,0x54}; //DVSI packet header + uint16_t m_outlevel; + QString m_errortxt; + bool m_xrf2ref; + bool m_ipv6; + QString m_vocoder; + QString m_modem; + QString m_playback; + QString m_capture; + QStringList m_vocoders; + QStringList m_modems; + QStringList m_playbacks; + QStringList m_captures; + + QString m_modemRxFreq; + QString m_modemTxFreq; + QString m_modemRxOffset; + QString m_modemTxOffset; + QString m_modemRxDCOffset; + QString m_modemTxDCOffset; + QString m_modemRxLevel; + QString m_modemTxLevel; + QString m_modemRFLevel; + QString m_modemTxDelay; + QString m_modemCWIdTxLevel; + QString m_modemDstarTxLevel; + QString m_modemDMRTxLevel; + QString m_modemYSFTxLevel; + QString m_modemP25TxLevel; + QString m_modemNXDNTxLevel; + bool m_modemTxInvert; + bool m_modemRxInvert; + bool m_modemPTTInvert; + +private slots: +#ifdef Q_OS_ANDROID + void keepScreenOn(); +#endif + void discover_devices(); + void process_ref_hosts(); + void process_dcs_hosts(); + void process_xrf_hosts(); + void process_ysf_hosts(); + void process_fcs_rooms(); + void process_dmr_hosts(); + void process_p25_hosts(); + void process_nxdn_hosts(); + void process_m17_hosts(); + void process_dmr_ids(); + void process_nxdn_ids(); + void update_dmr_data(Codec::MODEINFO); + void update_ref_data(Codec::MODEINFO); + void update_dcs_data(Codec::MODEINFO); + void update_xrf_data(Codec::MODEINFO); + void update_nxdn_data(Codec::MODEINFO); + void update_p25_data(Codec::MODEINFO); + void update_ysf_data(Codec::MODEINFO); + void update_m17_data(Codec::MODEINFO); + void update_iax_data(); + void save_settings(); + void update_output_level(unsigned short l){ m_outlevel = l;} + //void load_md380_fw(); +}; + +#endif // DROIDSTAR_H diff --git a/httpmanager.cpp b/httpmanager.cpp new file mode 100644 index 0000000..8174213 --- /dev/null +++ b/httpmanager.cpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "httpmanager.h" + +HttpManager::HttpManager(QString f) : QObject(nullptr) +{ + m_qnam = new QNetworkAccessManager(this); + QObject::connect(m_qnam, SIGNAL(finished(QNetworkReply*)), this, SLOT(http_finished(QNetworkReply*))); + m_config_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_WIN) + m_config_path += "/dudetronics"; +#endif + m_filename = f; +} + +void HttpManager::process() +{ + QMetaObject::invokeMethod(this,"doRequest"); + //qDebug() << "process() called"; + //send to the event loop +} + +void HttpManager::doRequest() +{ + m_qnam->get(QNetworkRequest(QUrl("http://www.dudetronics.com/ar-dns" + m_filename))); + //qDebug() << "doRequest() called m_filename == " << m_filename; +} + +void HttpManager::http_finished(QNetworkReply *reply) +{ + qDebug() << "http_finished() called"; + if (reply->error()) { + reply->deleteLater(); + reply = nullptr; + qDebug() << "http_finished() error()"; + return; + } + else{ + QFile *hosts_file = new QFile(m_config_path + m_filename); + hosts_file->open(QIODevice::WriteOnly); + QFileInfo fileInfo(hosts_file->fileName()); + QString filename(fileInfo.fileName()); + hosts_file->write(reply->readAll()); + hosts_file->flush(); + hosts_file->close(); + delete hosts_file; + emit file_downloaded(filename); + //fprintf(stderr, "Downloaded %s\n", filename.toStdString().c_str());fflush(stderr); + } + QThread::currentThread()->quit(); +} diff --git a/httpmanager.h b/httpmanager.h new file mode 100644 index 0000000..1222964 --- /dev/null +++ b/httpmanager.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef HTTPMANAGER_H +#define HTTPMANAGER_H + +#include +#include + +class HttpManager : public QObject +{ + Q_OBJECT +public: + explicit HttpManager(QString); + //void start_request(QString file); + +signals: + void file_downloaded(QString); + +private: + QString m_filename; + QString m_config_path; + QNetworkAccessManager *m_qnam; + +private slots: + void process(); + void doRequest(); + void http_finished(QNetworkReply *reply); +}; + +#endif // HTTPMANAGER_H diff --git a/iaxcodec.cpp b/iaxcodec.cpp new file mode 100644 index 0000000..547976c --- /dev/null +++ b/iaxcodec.cpp @@ -0,0 +1,840 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "iaxcodec.h" +#include "iaxdefines.h" +#ifdef Q_OS_WIN +#include +#else +#include +#endif +#define DEBUG + +#ifdef USE_FLITE +extern "C" { +extern cst_voice * register_cmu_us_slt(const char *); +extern cst_voice * register_cmu_us_kal16(const char *); +extern cst_voice * register_cmu_us_awb(const char *); +} +#endif + +IAXCodec::IAXCodec(QString callsign, QString username, QString password, QString node, QString host, int port, QString audioin, QString audioout) : + m_callsign(callsign), + m_username(username), + m_password(password), + m_node(node), + m_host(host), + m_port(port), + m_scallno(0), + m_dcallno(0), + m_regscallno(0x7fff), + m_regdcallno(0), + m_audioin(audioin), + m_audioout(audioout), + m_iseq(0), + m_oseq(0), + m_tx(false), + m_rxjitter(0), + m_rxloss(1), + m_rxframes(0), + m_rxdelay(0), + m_rxdropped(0), + m_rxooo(0), + m_ttsid(0), + m_cnt(0) +{ +#ifdef USE_FLITE + flite_init(); + voice_slt = register_cmu_us_slt(nullptr); + voice_kal = register_cmu_us_kal16(nullptr); + voice_awb = register_cmu_us_awb(nullptr); +#endif + QStringList l = m_node.split('@'); + if(l.size() == 2){ + m_node = l.at(0).simplified(); + m_context = l.at(1).simplified(); + } + else{ + m_context = "iax-client"; + } +} + +IAXCodec::~IAXCodec() +{ +} + +int16_t ulaw_decode(int8_t number) +{ + const uint16_t MULAW_BIAS = 33; + uint8_t sign = 0, position = 0; + int16_t decoded = 0; + number = ~number; + if (number & 0x80) + { + number &= ~(1 << 7); + sign = -1; + } + position = ((number & 0xF0) >> 4) + 5; + decoded = ((1 << position) | ((number & 0x0F) << (position - 4)) + | (1 << (position - 5))) - MULAW_BIAS; + return (sign == 0) ? (decoded) : (-(decoded)); +} + +int8_t ulaw_encode(int16_t number) +{ + const uint16_t MULAW_MAX = 0x1FFF; + const uint16_t MULAW_BIAS = 33; + uint16_t mask = 0x1000; + uint8_t sign = 0; + uint8_t position = 12; + uint8_t lsb = 0; + if (number < 0) + { + number = -number; + sign = 0x80; + } + number += MULAW_BIAS; + if (number > MULAW_MAX) + { + number = MULAW_MAX; + } + for (; ((number & mask) != mask && position >= 5); mask >>= 1, position--) + ; + lsb = (number >> (position - 4)) & 0x0f; + return (~(sign | ((position - 5) << 4) | lsb)); +} + +void IAXCodec::send_call() +{ + uint16_t scall = htons(++m_scallno | 0x8000); + m_oseq = m_iseq = 0; + QByteArray out; + out.append((char *)&scall, 2); + out.append('\x00'); + out.append('\x00'); + out.append('\x00'); + out.append('\x00'); + out.append('\x00'); + out.append('\x00'); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_NEW); + out.append(IAX_IE_VERSION); + out.append(sizeof(short)); + out.append('\x00'); + out.append(IAX_PROTO_VERSION); + out.append(IAX_IE_CALLED_NUMBER); + out.append(m_node.size()); + out.append(m_node.toUtf8(), m_node.size()); + out.append(IAX_IE_CALLING_NUMBER); + out.append('\x00'); + out.append(IAX_IE_CALLING_NAME); + out.append(m_callsign.size()); + out.append(m_callsign.toUtf8(), m_callsign.size()); + out.append(IAX_IE_USERNAME); + out.append(m_username.size()); + out.append(m_username.toUtf8(), m_username.size()); + out.append(IAX_IE_FORMAT); + out.append(sizeof(int)); + out.append('\x00'); + out.append('\x00'); + out.append('\x00'); + out.append(AST_FORMAT_ULAW); + m_timestamp = QDateTime::currentMSecsSinceEpoch(); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_call_auth() +{ + QByteArray out; + m_md5seed.append(m_password.toUtf8()); + QByteArray result = QCryptographicHash::hash(m_md5seed, QCryptographicHash::Md5); + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_AUTHREP); + out.append(IAX_IE_MD5_RESULT); + out.append(result.toHex().size()); + out.append(result.toHex()); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_dtmf(QByteArray dtmf) +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + //uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp) + 3); + for(int i = 0; i < dtmf.size(); ++i){ + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp) + 3 + (i*3)); + out.clear(); + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq+i); + out.append(m_iseq); + out.append(AST_FRAME_DTMF); + out.append(dtmf.data()[i]); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void IAXCodec::send_radio_key(bool key) +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + out.clear(); + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_CONTROL); + out.append(key ? AST_CONTROL_KEY : AST_CONTROL_UNKEY); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_ping() +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_PING); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_pong() +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + uint32_t jitter = htonl(m_rxjitter); + //uint32_t loss = htonl(m_rxloss); + uint32_t frames = htonl(m_rxframes); + uint16_t delay = htons(m_rxdelay); + uint32_t dropped = htonl(m_rxdropped); + uint32_t ooo = htonl(m_rxooo); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_PONG); + out.append(IAX_IE_RR_JITTER); + out.append(sizeof(jitter)); + out.append((char *)&jitter, sizeof(jitter)); + out.append(IAX_IE_RR_LOSS); + out.append(sizeof(m_rxloss)); + out.append((char *)&m_rxloss, sizeof(m_rxloss)); + out.append(IAX_IE_RR_PKTS); + out.append(sizeof(frames)); + out.append((char *)&frames, sizeof(frames)); + out.append(IAX_IE_RR_DELAY); + out.append(sizeof(delay)); + out.append((char *)&delay, sizeof(delay)); + out.append(IAX_IE_RR_DROPPED); + out.append(sizeof(dropped)); + out.append((char *)&dropped, sizeof(dropped)); + out.append(IAX_IE_RR_OOO); + out.append(sizeof(ooo)); + out.append((char *)&ooo, sizeof(ooo)); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_ack(uint16_t scall, uint16_t dcall, uint8_t oseq, uint8_t iseq) +{ + QByteArray out; + scall = htons(scall | 0x8000); + dcall = htons(dcall); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(oseq); + out.append(iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_ACK); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_lag_response() +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_LAGRP); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_voice_frame(int16_t *f) +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_VOICE); + out.append(AST_FORMAT_ULAW); + + for(int i = 0; i < 160; ++i){ + out.append(ulaw_encode(f[i])); + } + + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_registration(uint16_t dcall) +{ + //static qint64 time = QDateTime::currentMSecsSinceEpoch(); + uint32_t ts; + uint8_t seq; + + if(dcall){ + dcall = htons(dcall); + seq = 1; + ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + } + else{ + --m_regscallno; + seq = 0; + //ts = htonl(3); + ts = 0; + m_md5seed.clear(); + } + + uint16_t scall = htons(m_regscallno | 0x8000); + uint16_t refresh = htons(60); + QByteArray out; + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(seq); + out.append(seq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_REGREQ); + out.append(IAX_IE_USERNAME); + out.append(m_username.size()); + out.append(m_username.toUtf8(), m_username.size()); + + if(dcall){ + m_md5seed.append(m_password.toUtf8()); + QByteArray result = QCryptographicHash::hash(m_md5seed, QCryptographicHash::Md5); + out.append(IAX_IE_MD5_RESULT); + out.append(result.toHex().size()); + out.append(result.toHex()); + } + + out.append(IAX_IE_REFRESH); + out.append(0x02); + out.append((char *)&refresh, 2); // refresh time = 60 secs + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::send_disconnect() +{ + QByteArray out; + uint16_t scall = htons(m_scallno | 0x8000); + uint16_t dcall = htons(m_dcallno); + uint32_t ts = htonl((QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3); + QString bye("BuhBye Dudesters"); + out.append((char *)&scall, 2); + out.append((char *)&dcall, 2); + out.append((char *)&ts, 4); + out.append(m_oseq); + out.append(m_iseq); + out.append(AST_FRAME_IAX); + out.append(IAX_COMMAND_HANGUP); + out.append(IAX_IE_CAUSE); + out.append(bye.size()); + out.append(bye.toUtf8(), bye.size()); + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + m_regtimer = new QTimer(); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + connect(m_regtimer, SIGNAL(timeout()), this, SLOT(send_registration())); + m_timestamp = QDateTime::currentMSecsSinceEpoch(); + send_registration(0); + m_regtimer->start(60000); + } +} + +void IAXCodec::send_connect() +{ + m_status = CONNECTING; + qDebug() << "lookup IP = " << m_host << ":" << m_port; + QHostInfo::lookupHost(m_host, this, SLOT(hostname_lookup(QHostInfo))); +} + +void IAXCodec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + if(buf.data()[0] & 0x80){ + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } +#endif + if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_REGAUTH) && + (buf.data()[12] == IAX_IE_AUTHMETHODS) && + ((buf.data()[15] & 0x02) == IAX_AUTH_MD5) && + (buf.data()[16] == IAX_IE_CHALLENGE) ) + { + uint16_t dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_md5seed.clear(); + m_md5seed.append(buf.mid(18, buf.data()[17])); + send_registration(dcallno); + } + else if((buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_REGACK) ) + { + uint16_t dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + uint16_t scallno = (((buf.data()[2] & 0x7f) << 8) | ((uint8_t)buf.data()[3])); + send_ack(scallno, dcallno, 2, 2); + if(m_status == CONNECTING){ + send_call(); + } + } + else if((buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_REGREJ) ) + { + m_status = DISCONNECTED; + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_AUTHREQ) && + (buf.data()[12] == IAX_IE_AUTHMETHODS) && + (buf.data()[15] == IAX_AUTH_MD5) && + (buf.data()[16] == IAX_IE_CHALLENGE) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_md5seed.clear(); + m_md5seed.append(buf.mid(18, buf.data()[17])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_call_auth(); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_ACK) ) + { + uint16_t scall = ((buf.data()[2] << 8) | ((uint8_t)buf.data()[3])); + if(scall == m_scallno){ + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + } + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_ACCEPT) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_REJECT) ) + { + m_status = DISCONNECTED; + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_CONTROL) && + (buf.data()[11] == AST_CONTROL_RINGING) ) + { + //int16_t zeropcm[160]; + //memset(zeropcm, 0, 160 * sizeof(int16_t)); + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + //send_voice_frame(zeropcm); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_CONTROL) && + (buf.data()[11] == AST_CONTROL_ANSWER) ) + { + if(m_status == CONNECTING){ + m_status = CONNECTED_RW; + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_rxtimer->start(19); + m_pingtimer = new QTimer(); + connect(m_pingtimer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_pingtimer->start(2000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + m_audio->start_playback(); + m_audio->set_input_buffer_size(640); + m_audio->start_capture(); + //m_txtimer->start(19); + } + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_PING) ) + { + ++m_rxframes; + ++m_cnt; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + send_pong(); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_PONG) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_VNAK) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_VOICE) && + (buf.data()[11] == AST_FORMAT_ULAW) ) + { + int16_t zeropcm[160]; + memset(zeropcm, 0, 160 * sizeof(int16_t)); + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + for(int i = 0; i < buf.size() - 12; ++i){ + m_audioq.append(ulaw_decode(buf.data()[12+i])); + } + send_voice_frame(zeropcm); + if(!m_txtimer->isActive()){ + m_txtimer->start(19); + } + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_TEXT) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_CONTROL) && + (buf.data()[11] == AST_CONTROL_OPTION) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + } + else if( (buf.data()[0] & 0x80) && + (buf.data()[10] == AST_FRAME_IAX) && + (buf.data()[11] == IAX_COMMAND_LAGRQ) ) + { + ++m_rxframes; + m_dcallno = (((buf.data()[0] & 0x7f) << 8) | ((uint8_t)buf.data()[1])); + m_iseq = buf.data()[8] + 1; + m_oseq = buf.data()[9]; + send_ack(m_scallno, m_dcallno, m_oseq, m_iseq); + send_lag_response(); + } + else if(!(buf.data()[0] & 0x80)){ + uint16_t dcallno = ((buf.data()[0] << 8) | ((uint8_t)buf.data()[1])); + if(dcallno == m_dcallno){ + for(int i = 0; i < buf.size() - 4; ++i){ + m_audioq.append(ulaw_decode(buf.data()[4+i])); + } + } + } + emit update(); +} + +void IAXCodec::process_rx_data() +{ + int16_t pcm[160]; + + if(m_audioq.size() > 160){ + for(int i = 0; i < 160; ++i){ + pcm[i] = (qreal)m_audioq.dequeue(); // * m_rxgain; + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + else return; +} + +void IAXCodec::toggle_tx(bool tx) +{ + qDebug() << "IAXCodec::toggle_tx(bool tx) == " << tx; + tx ? start_tx() : stop_tx(); +} + +void IAXCodec::start_tx() +{ + //std::cerr << "Pressed TX buffersize == " << audioin->bufferSize() << std::endl; + //QByteArray tx("*99", 3); + //send_dtmf(tx); + send_radio_key(true); + m_ttscnt = 0; + qDebug() << "start_tx() " << m_ttsid << " " << m_ttstext; + m_tx = true; +#ifdef USE_FLITE + if(m_ttsid == 1){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_kal); + } + else if(m_ttsid == 2){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_awb); + } + else if(m_ttsid == 3){ + tts_audio = flite_text_to_wave(m_ttstext.toStdString().c_str(), voice_slt); + } +#endif +} + +void IAXCodec::stop_tx() +{ + m_tx = false; + send_radio_key(false); + //QByteArray tx("#", 1); + //send_dtmf(tx); +} + +void IAXCodec::transmit() +{ + QByteArray out; + int16_t pcm[160]; + uint16_t s = 0; +#ifdef USE_FLITE + if(m_ttsid > 0){ + m_audio->read(pcm); + s = 160; + if(m_tx){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + //audiotx_cnt = 0; + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } + } +#endif + if(m_ttsid == 0){ + s = m_audio->read(pcm); + } + if (s == 0) return; + + uint16_t scall = htons(m_scallno); + uint16_t ts = htons( (QDateTime::currentMSecsSinceEpoch() - m_timestamp));// + 3 ); + out.append((char *)&scall, 2); + out.append((char *)&ts, 2); + for(int i = 0; i < s; ++i){ + out.append(ulaw_encode(pcm[i])); + } + m_udp->writeDatagram(out, m_address, m_port); +#ifdef DEBUGG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void IAXCodec::deleteLater() +{ + if(m_status == CONNECTED_RW){ + m_udp->disconnect(); + m_txtimer->stop(); + m_rxtimer->stop(); + m_regtimer->stop(); + send_disconnect(); + //delete m_audio; + } + //m_cnt = 0; + QObject::deleteLater(); +} diff --git a/iaxcodec.h b/iaxcodec.h new file mode 100644 index 0000000..135b203 --- /dev/null +++ b/iaxcodec.h @@ -0,0 +1,124 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef IAXCODEC_H +#define IAXCODEC_H + +#include +#include +#include "audioengine.h" +#ifdef USE_FLITE +#include +#endif + +class IAXCodec : public QObject +{ + Q_OBJECT +public: + IAXCodec(QString callsign, QString username, QString password, QString node, QString host, int port, QString audioin, QString audioout); + ~IAXCodec(); + uint8_t get_status(){ return m_status; } + QString get_host() { return m_host; } + int get_port() { return m_port; } + int get_cnt() { return m_cnt; } + void set_input_src(uint8_t s, QString t) { m_ttsid = s; m_ttstext = t; } +signals: + void update(); + void update_output_level(unsigned short); +private slots: + void deleteLater(); + void process_udp(); + void send_connect(); + void send_disconnect(); + void hostname_lookup(QHostInfo i); + void send_registration(uint16_t dcall = 0); + void send_call(); + void send_call_auth(); + void send_ack(uint16_t, uint16_t, uint8_t, uint8_t); + void send_lag_response(); + void send_ping(); + void send_pong(); + void toggle_tx(bool); + void start_tx(); + void stop_tx(); + void transmit(); + void process_rx_data(); + void send_voice_frame(int16_t *); + void send_dtmf(QByteArray); + void send_radio_key(bool); + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } + void in_audio_vol_changed(qreal v){ m_audio->set_input_volume(v); } + void out_audio_vol_changed(qreal v){ m_audio->set_output_volume(v); } +private: + enum{ + DISCONNECTED, + CLOSED, + CONNECTING, + DMR_AUTH, + DMR_CONF, + DMR_OPTS, + CONNECTED_RW, + CONNECTED_RO + } m_status; + QUdpSocket *m_udp = nullptr; + QHostAddress m_address; + QString m_callsign; + QString m_username; + QString m_password; + QString m_node; + QString m_context; + QString m_host; + int m_port; + uint16_t m_scallno; + uint16_t m_dcallno; + uint16_t m_regscallno; + uint16_t m_regdcallno; + int m_id; + QString m_audioin; + QString m_audioout; + qint64 m_timestamp; + uint8_t m_regstat; + QByteArray m_md5seed; + QTimer *m_regtimer; + QTimer *m_pingtimer; + QTimer *m_rxtimer; + QTimer *m_txtimer; + AudioEngine *m_audio; + uint8_t m_iseq; + uint8_t m_oseq; + QQueue m_audioq; + bool m_tx; + uint32_t m_rxjitter; + uint32_t m_rxloss; + uint32_t m_rxframes; + uint16_t m_rxdelay; + uint32_t m_rxdropped; + uint32_t m_rxooo; + uint8_t m_ttsid; + QString m_ttstext; + uint16_t m_ttscnt; + int m_cnt; + //qreal m_rxgain; +#ifdef USE_FLITE + cst_voice *voice_slt; + cst_voice *voice_kal; + cst_voice *voice_awb; + cst_wave *tts_audio; +#endif +}; + +#endif // IAXCODEC_H diff --git a/iaxdefines.h b/iaxdefines.h new file mode 100644 index 0000000..33272d4 --- /dev/null +++ b/iaxdefines.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . + + These macros were derived from the iaxclient library + https://www.voip-info.org/iaxclient/ +*/ + +#ifndef IAXDEFINES_H +#define IAXDEFINES_H + +#define IAX_PROTO_VERSION 2 + +#define AST_FRAME_DTMF 1 +#define AST_FRAME_VOICE 2 +#define AST_FRAME_CONTROL 4 +#define AST_FRAME_IAX 6 +#define AST_FRAME_TEXT 7 + +#define AST_CONTROL_HANGUP 1 +#define AST_CONTROL_RING 2 +#define AST_CONTROL_RINGING 3 +#define AST_CONTROL_ANSWER 4 +#define AST_CONTROL_OPTION 11 +#define AST_CONTROL_KEY 12 +#define AST_CONTROL_UNKEY 13 + +#define AST_FORMAT_ULAW 4 +#define IAX_AUTH_MD5 2 + +#define IAX_COMMAND_NEW 1 +#define IAX_COMMAND_PING 2 +#define IAX_COMMAND_PONG 3 +#define IAX_COMMAND_ACK 4 +#define IAX_COMMAND_HANGUP 5 +#define IAX_COMMAND_REJECT 6 +#define IAX_COMMAND_ACCEPT 7 +#define IAX_COMMAND_AUTHREQ 8 +#define IAX_COMMAND_AUTHREP 9 +#define IAX_COMMAND_INVAL 10 +#define IAX_COMMAND_LAGRQ 11 +#define IAX_COMMAND_LAGRP 12 +#define IAX_COMMAND_REGREQ 13 +#define IAX_COMMAND_REGAUTH 14 +#define IAX_COMMAND_REGACK 15 +#define IAX_COMMAND_REGREJ 16 +#define IAX_COMMAND_VNAK 18 + +#define IAX_IE_CALLED_NUMBER 1 +#define IAX_IE_CALLING_NUMBER 2 +#define IAX_IE_CALLING_NAME 4 +#define IAX_IE_CALLED_CONTEXT 5 +#define IAX_IE_USERNAME 6 +#define IAX_IE_PASSWORD 7 +#define IAX_IE_CAPABILITY 8 +#define IAX_IE_FORMAT 9 +#define IAX_IE_VERSION 11 +#define IAX_IE_DNID 13 +#define IAX_IE_AUTHMETHODS 14 +#define IAX_IE_CHALLENGE 15 +#define IAX_IE_MD5_RESULT 16 +#define IAX_IE_APPARENT_ADDR 18 +#define IAX_IE_REFRESH 19 +#define IAX_IE_CAUSE 22 +#define IAX_IE_DATETIME 31 +#define IAX_IE_RR_JITTER 46 +#define IAX_IE_RR_LOSS 47 +#define IAX_IE_RR_PKTS 48 +#define IAX_IE_RR_DELAY 49 +#define IAX_IE_RR_DROPPED 50 +#define IAX_IE_RR_OOO 51 + +#endif // IAXDEFINES_H diff --git a/images/droidstar.png b/images/droidstar.png new file mode 100644 index 0000000..10f0ebf Binary files /dev/null and b/images/droidstar.png differ diff --git a/m17codec.cpp b/m17codec.cpp new file mode 100755 index 0000000..5af52d0 --- /dev/null +++ b/m17codec.cpp @@ -0,0 +1,491 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include "m17codec.h" +#define M17CHARACTERS " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/." + +#define DEBUG + +M17Codec::M17Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout) : + Codec(callsign, module, hostname, host, port, ipv6, NULL, modem, audioin, audioout), + m_txrate(1) +{ + m_modeinfo.callsign = callsign; + m_modeinfo.host = host; + m_modeinfo.port = port; + m_modeinfo.count = 0; + m_modeinfo.frame_number = 0; + m_modeinfo.streamid = 0; +#ifdef Q_OS_WIN + m_txtimerint = 30; // Qt timers on windows seem to be slower than desired value + +#else + m_txtimerint = 36; +#endif +} + +M17Codec::~M17Codec() +{ +} + +void M17Codec::encode_callsign(uint8_t *callsign) +{ + const std::string m17_alphabet(M17CHARACTERS); + char cs[10]; + memset(cs, 0, sizeof(cs)); + memcpy(cs, callsign, strlen((char *)callsign)); + uint64_t encoded = 0; + for(int i = std::strlen((char *)callsign)-1; i >= 0; i--) { + auto pos = m17_alphabet.find(cs[i]); + if (pos == std::string::npos) { + pos = 0; + } + encoded *= 40; + encoded += pos; + } + for (int i=0; i<6; i++) { + callsign[i] = (encoded >> (8*(5-i)) & 0xFFU); + } +} + +void M17Codec::decode_callsign(uint8_t *callsign) +{ + const std::string m17_alphabet(M17CHARACTERS); + uint8_t code[6]; + uint64_t coded = callsign[0]; + for (int i=1; i<6; i++) + coded = (coded << 8) | callsign[i]; + if (coded > 0xee6b27ffffffu) { + std::cerr << "Callsign code is too large, 0x" << std::hex << coded << std::endl; + return; + } + memcpy(code, callsign, 6); + memset(callsign, 0, 10); + int i = 0; + while (coded) { + if(i < 10){ + callsign[i++] = m17_alphabet[coded % 40]; + } + coded /= 40; + } +} + +void M17Codec::decode_c2(int16_t *audio, uint8_t *c) +{ + m_c2->codec2_decode(audio, c); +} + +void M17Codec::encode_c2(int16_t *audio, uint8_t *c) +{ + m_c2->codec2_encode(c, audio); +} + +void M17Codec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if((m_modeinfo.status != CONNECTED_RW) && (buf.size() == 4) && (::memcmp(buf.data(), "NACK", 4U) == 0)){ + m_modeinfo.status = DISCONNECTED; + } + if((buf.size() == 4) && (::memcmp(buf.data(), "ACKN", 4U) == 0)){ + if(m_modeinfo.status == CONNECTING){ + m_modeinfo.status = CONNECTED_RW; + m_c2 = new CCodec2(true); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(8000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + } + emit update(m_modeinfo); + } + if((buf.size() == 10) && (::memcmp(buf.data(), "PING", 4U) == 0)){ + if(m_modeinfo.streamid == 0){ + m_modeinfo.stream_state = STREAM_IDLE; + } + m_modeinfo.count++; + emit update(m_modeinfo); + } + if((buf.size() == 54) && (::memcmp(buf.data(), "M17 ", 4U) == 0)){ + uint16_t streamid = (buf.data()[4] << 8) | (buf.data()[5] & 0xff); + if( (m_modeinfo.streamid != 0) && (streamid != m_modeinfo.streamid) ){ + qDebug() << "New streamid received before timeout"; + m_modeinfo.streamid = 0; + m_audio->stop_playback(); + } + if( !m_tx && (m_modeinfo.streamid == 0) ){ + uint8_t cs[10]; + ::memcpy(cs, &(buf.data()[12]), 6); + decode_callsign(cs); + m_modeinfo.src = QString((char *)cs); + ::memcpy(cs, &(buf.data()[6]), 6); + decode_callsign(cs); + m_modeinfo.dst = QString((char *)cs); + m_modeinfo.streamid = streamid; + m_audio->start_playback(); + + if((buf.data()[19] & 0x06U) == 0x04U){ + m_modeinfo.type = 1;//"3200 Voice"; + set_mode(true); + } + else{ + m_modeinfo.type = 0;//"1600 V/D"; + set_mode(false); + } + + if(!m_rxtimer->isActive()){ +#ifdef Q_OS_WIN + m_rxtimer->start(m_modeinfo.type ? m_rxtimerint : 32); +#else + m_rxtimer->start(m_modeinfo.type ? m_rxtimerint : m_rxtimerint*2); +#endif + } + + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16); + } + else{ + m_modeinfo.stream_state = STREAMING; + } + + m_modeinfo.frame_number = (buf.data()[34] << 8) | (buf.data()[35] & 0xff); + m_rxwatchdog = 0; + int s = 8; + if(get_mode()){ + s = 16; + } + + for(int i = 0; i < s; ++i){ + m_rxcodecq.append((uint8_t )buf.data()[36+i]); + } + + if(m_modeinfo.frame_number & 0x8000){ // EOT + qDebug() << "M17 stream ended"; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + else{ + emit update(m_modeinfo); + } + } + //emit update(m_modeinfo); +} + +void M17Codec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + uint8_t cs[10]; + memset(cs, ' ', 9); + memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size()); + cs[8] = 'D'; + cs[9] = 0x00; + M17Codec::encode_callsign(cs); + out.append('C'); + out.append('O'); + out.append('N'); + out.append('N'); + out.append((char *)cs, 6); + out.append(m_module); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void M17Codec::send_ping() +{ + QByteArray out; + uint8_t cs[10]; + memset(cs, ' ', 9); + memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size()); + cs[8] = 'D'; + cs[9] = 0x00; + encode_callsign(cs); + out.append('P'); + out.append('O'); + out.append('N'); + out.append('G'); + out.append((char *)cs, 6); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void M17Codec::send_disconnect() +{ + qDebug() << "send_disconnect()"; + QByteArray out; + uint8_t cs[10]; + memset(cs, ' ', 9); + memcpy(cs, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size()); + cs[8] = 'D'; + cs[9] = 0x00; + encode_callsign(cs); + out.append('D'); + out.append('I'); + out.append('S'); + out.append('C'); + out.append((char *)cs, 6); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void M17Codec::toggle_tx(bool tx) +{ + qDebug() << "M17Codec::toggle_tx(bool tx) == " << tx; + tx ? start_tx() : stop_tx(); +} + +void M17Codec::start_tx() +{ + m_c2->codec2_set_mode(m_txrate); + Codec::start_tx(); +} + +void M17Codec::transmit() +{ + QByteArray txframe; + static uint16_t txstreamid = 0; + static uint16_t tx_cnt = 0; + int16_t pcm[320]; + uint8_t c2[16]; +#ifdef USE_FLITE + static uint16_t ttscnt = 0; + if(m_ttsid > 0){ + for(int i = 0; i < 320; ++i){ + if(ttscnt >= tts_audio->num_samples/2){ + //audiotx_cnt = 0; + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[ttscnt*2] / 2; + ttscnt++; + } + } + m_c2->codec2_encode(c2, pcm); + if(get_mode()){ + m_c2->codec2_encode(c2+8, pcm+160); + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 320)){ + m_c2->codec2_encode(c2, pcm); + if(get_mode()){ + m_c2->codec2_encode(c2+8, pcm+160); + } + } + else{ + return; + } + } + + txframe.clear(); + emit update_output_level(m_audio->level()); + int r = get_mode() ? 0x05 : 0x07; + if(m_tx){ + if(txstreamid == 0){ + txstreamid = static_cast((::rand() & 0xFFFF)); + //std::cerr << "txstreamid == " << txstreamid << std::endl; + } + uint8_t src[10]; + uint8_t dst[10]; + memset(dst, ' ', 9); + memcpy(dst, m_hostname.toLocal8Bit(), m_hostname.size()); + dst[8] = m_module; + dst[9] = 0x00; + encode_callsign(dst); + memset(src, ' ', 9); + memcpy(src, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size()); + src[8] = 'D'; + src[9] = 0x00; + encode_callsign(src); + + txframe.append('M'); + txframe.append('1'); + txframe.append('7'); + txframe.append(' '); + txframe.append(txstreamid >> 8); + txframe.append(txstreamid & 0xff); + txframe.append((char *)dst, 6); + txframe.append((char *)src, 6); + txframe.append('\x00'); + txframe.append(r); // Frame type voice only + txframe.append(14, 0x00); //Blank nonce + txframe.append((char)(tx_cnt >> 8)); + txframe.append((char)tx_cnt & 0xff); + txframe.append((char *)c2, 16); + txframe.append(2, 0x00); + + //QString ss = QString("%1").arg(txstreamid, 4, 16, QChar('0')); + //QString n = QString("TX %1").arg(tx_cnt, 4, 16, QChar('0')); + + m_udp->writeDatagram(txframe, m_address, m_modeinfo.port); + ++tx_cnt; + m_modeinfo.src = m_modeinfo.callsign; + m_modeinfo.dst = m_hostname; + m_modeinfo.type = get_mode();// ? "3200 Voice" : "1600 V/D"; + m_modeinfo.frame_number = tx_cnt; + m_modeinfo.streamid = txstreamid; + emit update(m_modeinfo); + + fprintf(stderr, "SEND:%d: ", txframe.size()); + for(int i = 0; i < txframe.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txframe.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } + else{ + const uint8_t quiet3200[] = { 0x00, 0x01, 0x43, 0x09, 0xe4, 0x9c, 0x08, 0x21 }; + const uint8_t quiet1600[] = { 0x01, 0x00, 0x04, 0x00, 0x25, 0x75, 0xdd, 0xf2 }; + const uint8_t *quiet = (get_mode()) ? quiet3200 : quiet1600; + uint8_t src[10]; + uint8_t dst[10]; + memset(dst, ' ', 9); + memcpy(dst, m_hostname.toLocal8Bit(), m_hostname.size()); + dst[8] = m_module; + dst[9] = 0x00; + encode_callsign(dst); + memset(src, ' ', 9); + memcpy(src, m_modeinfo.callsign.toLocal8Bit(), m_modeinfo.callsign.size()); + src[8] = 'D'; + src[9] = 0x00; + M17Codec::encode_callsign(src); + tx_cnt |= 0x8000u; + + txframe.append('M'); + txframe.append('1'); + txframe.append('7'); + txframe.append(' '); + txframe.append(txstreamid >> 8); + txframe.append(txstreamid & 0xff); + txframe.append((char *)dst, 6); + txframe.append((char *)src, 6); + txframe.append('\x00'); + txframe.append(r); // Frame type voice only + txframe.append(14, 0x00); //Blank nonce + txframe.append((char)(tx_cnt >> 8)); + txframe.append((char)tx_cnt & 0xff); + txframe.append((char *)quiet, 8); + txframe.append((char *)quiet, 8); + txframe.append(2, 0x00); + + //QString n = QString("%1").arg(tx_cnt, 4, 16, QChar('0')); + m_udp->writeDatagram(txframe, m_address, m_modeinfo.port); + txstreamid = 0; + tx_cnt = 0; +#ifdef USE_FLITE + ttscnt = 0; +#endif + m_txtimer->stop(); + if(m_ttsid == 0){ + m_audio->stop_capture(); + } + m_modeinfo.src = m_modeinfo.callsign; + m_modeinfo.dst = m_hostname; + m_modeinfo.type = get_mode();// ? "3200 Voice" : "1600 V/D"; + m_modeinfo.frame_number = tx_cnt; + m_modeinfo.streamid = txstreamid; + emit update(m_modeinfo); + fprintf(stderr, "LAST:%d: ", txframe.size()); + for(int i = 0; i < txframe.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txframe.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +void M17Codec::process_rx_data() +{ + int16_t pcm[320]; + uint8_t codec2[8]; + + if(m_rxwatchdog++ > 50){ + qDebug() << "RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + if((!m_tx) && (m_rxcodecq.size() > 7) ){ + for(int i = 0; i < 8; ++i){ + codec2[i] = m_rxcodecq.dequeue(); + } + m_c2->codec2_decode(pcm, codec2); + int s = get_mode() ? 160 : 320; + m_audio->write(pcm, s); + emit update_output_level(m_audio->level()); + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "M17 playback stopped"; + return; + } +} diff --git a/m17codec.h b/m17codec.h new file mode 100755 index 0000000..d5970b0 --- /dev/null +++ b/m17codec.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef M17CODEC_H +#define M17CODEC_H + +#include +#include "codec.h" +#include "codec2/codec2.h" + +class M17Codec : public Codec +{ + Q_OBJECT +public: + M17Codec(QString callsign, char module, QString hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout); + ~M17Codec(); + static void encode_callsign(uint8_t *); + static void decode_callsign(uint8_t *); + void decode_c2(int16_t *, uint8_t *); + void encode_c2(int16_t *, uint8_t *); + void set_mode(bool m){ m_c2->codec2_set_mode(m);} + bool get_mode(){ return m_c2->codec2_get_mode(); } + CCodec2 *m_c2; +private slots: + void process_udp(); + void send_ping(); + void send_disconnect(); + void toggle_tx(bool); + void start_tx(); + void transmit(); + void hostname_lookup(QHostInfo i); + void rate_changed(int r) { m_txrate = r; } + void process_rx_data(); +private: + int m_txrate; +}; + +#endif // M17CODEC_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..871b932 --- /dev/null +++ b/main.cpp @@ -0,0 +1,44 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include +#include +#include +#include +#include "droidstar.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QGuiApplication app(argc, argv); + QQuickStyle::setStyle("Fusion"); + app.setWindowIcon(QIcon(":/images/droidstar.png")); + qmlRegisterType("org.dudetronics.droidstar", 1, 0, "DroidStar"); + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QGuiApplication::quit); + return app.exec(); +} diff --git a/main.qml b/main.qml new file mode 100644 index 0000000..422fe34 --- /dev/null +++ b/main.qml @@ -0,0 +1,481 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +import QtQuick 2.10 +import QtQuick.Window 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Dialogs 1.3 +import org.dudetronics.droidstar 1.0 + +ApplicationWindow { + // @disable-check M16 + visible: true + // @disable-check M16 + width: 320 + // @disable-check M16 + height: 480 + // @disable-check M16 + // @disable-check M16 + title: qsTr("DroidStar") + + palette.window: "#252424" + palette.button: "#252424" + palette.buttonText: "white" + palette.base: "black" + palette.text: "white" + palette.windowText: "white" + palette.highlight: "steelblue" + + MessageDialog { + id: idcheckDialog + title: "Invalid credentials" + text: "A valid callsign and DMR ID are required to use Dudestar on any mode, and they must match. If you have entered a valid DMR ID that matches the entered callsign, and you are still seeing this message, then you either have to click update ID files button or wait until your DMR ID is added to the ID file and try again." + } + MessageDialog { + id: errorDialog + title: "Error" + } + MessageDialog { + id: updateDialog + title: "Updating..." + text: "Check log tab for details" + } + MessageDialog { + id: disclaimerDialog + title: "D-Star WARNING" + text: "DroidStar should only be used to monitor D-Star reflectors.\nDO NOT USE DROIDSTAR FOR GENERAL DSTAR TX.\nListening to D-Star reflectors is fine.\nD-Star vocoder quality is not very good, so transmitting should\nonly be done for experimentation and development purposes.\n\nYOU HAVE BEEN WARNED!" + } + + TabBar { + id: bar + width: parent.width + currentIndex: swiper.currentIndex + background: Rectangle { + color: "steelblue" + } + TabButton { + id: mainButton + padding: 10 + background: Rectangle { + color: bar.currentIndex === 0 ? "steelblue" : "#353535" + } + text: qsTr("Main") + } + TabButton { + id: settingsButton + padding: 10 + background: Rectangle { + color: bar.currentIndex === 1 ? "steelblue" : "#353535" + } + text: qsTr("Settings") + } + TabButton { + id: logButton + padding: 10 + background: Rectangle { + color: bar.currentIndex === 2 ? "steelblue" : "#353535" + } + text: qsTr("Log") + } + TabButton { + id: hostsButton + padding: 10 + background: Rectangle { + color: bar.currentIndex === 3 ? "steelblue" : "#353535" + } + text: qsTr("Hosts") + } + TabButton { + id: aboutButton + padding: 10 + background: Rectangle { + color: bar.currentIndex === 4 ? "steelblue" : "#353535" + } + text: qsTr("About") + } + } + SwipeView { + id: swiper + width: parent.width + height: parent.height - 50 + x: 0 + y: 50 + currentIndex: bar.currentIndex + interactive: false + + MainTab{ + id: mainTab + } + SettingsTab{ + id: settingsTab + } + LogTab{ + id: logTab + } + HostsTab{ + id: hostsTab + } + AboutTab{} + } + DroidStar { + id: droidstar + } + Connections { + target: Qt.application + function onStateChanged() { + //console.debug("applicationStateChanged: " + Qt.application.state) + } + } + Connections { + target: droidstar + Component.onCompleted: { + mainTab.comboMode.loaded = true; + droidstar.process_settings(); + settingsTab.comboVocoder.model = droidstar.get_vocoders(); + settingsTab.comboModem.model = droidstar.get_modems(); + settingsTab.comboPlayback.model = droidstar.get_playbacks(); + settingsTab.comboCapture.model = droidstar.get_captures(); + } + function onSwtx_state(s){ + mainTab.swtxBox.checked = s; + mainTab.swtxBox.enabled = !s; + } + function onSwrx_state(s){ + mainTab.swrxBox.checked = s; + mainTab.swrxBox.enabled = !s; + } + function onMycall_changed(s){ + settingsTab.mycallEdit.text = s; + } + function onUrcall_changed(s){ + settingsTab.urcallEdit.text = s; + } + function onRptr1_changed(s){ + settingsTab.rptr1Edit.text = s; + } + function onRptr2_changed(s){ + settingsTab.rptr2Edit.text = s; + } + + function onMode_changed() { + //console.log("onMode_changed ", mainTab.comboMode.find(droidstar.get_mode()), ":", droidstar.get_mode(), ":", droidstar.get_ref_host(), ":", droidstar.get_module()); + mainTab.label1.text = droidstar.get_label1(); + mainTab.label2.text = droidstar.get_label2(); + mainTab.label3.text = droidstar.get_label3(); + mainTab.label4.text = droidstar.get_label4(); + mainTab.label5.text = droidstar.get_label5(); + mainTab.label6.text = droidstar.get_label6(); + droidstar.set_modelchange(true); + mainTab.comboHost.model = droidstar.get_hosts(); + droidstar.set_modelchange(false); + mainTab.comboMode.currentIndex = mainTab.comboMode.find(droidstar.get_mode()); + if(droidstar.get_mode() === "REF"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ref_host()); + mainTab.comboModule.visible = true; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = true; + settingsTab.sliderMicGain.value = 0.0; + } + if(droidstar.get_mode() === "DCS"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dcs_host()); + mainTab.comboModule.visible = true; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = true; + settingsTab.sliderMicGain.value = 0.0; + } + if(droidstar.get_mode() === "XRF"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_xrf_host()); + mainTab.comboModule.visible = true; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = true; + settingsTab.sliderMicGain.value = 0.0; + } + if(droidstar.get_mode() === "YSF"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ysf_host()); + mainTab.comboModule.visible = false; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.2; + } + if(droidstar.get_mode() === "FCS"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_fcs_host()); + mainTab.comboModule.visible = false; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.2; + } + if(droidstar.get_mode() === "DMR"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dmr_host()); + mainTab.comboModule.visible = false; + mainTab.element3.visible = true; + mainTab.dmrtgidEdit.visible = true; + mainTab.privateBox.visible = true; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.3; + } + if(droidstar.get_mode() === "P25"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_p25_host()); + mainTab.comboModule.visible = false; + mainTab.element3.visible = true; + mainTab.dmrtgidEdit.visible = true; + mainTab.privateBox.visible = false; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.3; + } + if(droidstar.get_mode() === "NXDN"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_nxdn_host()); + mainTab.comboModule.visible = false; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.3; + } + if(droidstar.get_mode() === "M17"){ + mainTab.comboHost.visible = true; + mainTab.element1.text = "Host"; + mainTab.editIAXDTMF.visible = false; + mainTab.dtmfsendbutton.visible = false; + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_m17_host()); + mainTab.comboModule.currentIndex = mainTab.comboModule.find(droidstar.get_module()); + mainTab.comboModule.visible = true; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = true; + settingsTab.sliderMicGain.value = 0.5; + } + if(droidstar.get_mode() === "IAX"){ + mainTab.comboHost.visible = false; + mainTab.element1.text = "DTMF *"; + mainTab.editIAXDTMF.visible = true; + mainTab.dtmfsendbutton.visible = true; + mainTab.comboModule.visible = false; + mainTab.element3.visible = false; + mainTab.dmrtgidEdit.visible = false; + mainTab.privateBox.visible = false; + mainTab.element4.visible = false; + settingsTab.sliderMicGain.value = 0.5; + } + } + function onUpdate_data() { + mainTab.data1.text = droidstar.get_data1(); + mainTab.data2.text = droidstar.get_data2(); + mainTab.data3.text = droidstar.get_data3(); + mainTab.data4.text = droidstar.get_data4(); + mainTab.data5.text = droidstar.get_data5(); + mainTab.data6.text = droidstar.get_data6(); + mainTab.status.text = droidstar.get_statustxt(); + ++mainTab.uitimer.rxcnt; + } + function onUpdate_settings() { + //console.log("update_settings comboHost == ", mainTab.comboHost.find(droidstar.get_host())); + //console.log("update_settings comboModule == ", mainTab.comboModule.find(droidstar.get_module())); + settingsTab.ipv6.checked = droidstar.get_ipv6(); + settingsTab.xrf2ref.checked = droidstar.get_xrf2ref(); + settingsTab.toggleTX.checked = droidstar.get_toggletx(); + if(droidstar.get_mode() === "REF"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ref_host()); + } + if(droidstar.get_mode() === "DCS"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dcs_host()); + } + if(droidstar.get_mode() === "XRF"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_xrf_host()); + } + if(droidstar.get_mode() === "YSF"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_ysf_host()); + } + if(droidstar.get_mode() === "FCS"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_fcs_host()); + } + if(droidstar.get_mode() === "DMR"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_dmr_host()); + } + if(droidstar.get_mode() === "P25"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_p25_host()); + } + if(droidstar.get_mode() === "NXDN"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_nxdn_host()); + } + if(droidstar.get_mode() === "M17"){ + mainTab.comboHost.currentIndex = mainTab.comboHost.find(droidstar.get_m17_host()); + } + mainTab.comboModule.currentIndex = mainTab.comboModule.find(droidstar.get_module()); + settingsTab.callsignEdit.text = droidstar.get_callsign(); + settingsTab.dmridEdit.text = droidstar.get_dmrid(); + settingsTab.comboEssid.currentIndex = settingsTab.comboEssid.find(droidstar.get_essid()); + settingsTab.bmpwEdit.text = droidstar.get_bm_password(); + settingsTab.tgifpwEdit.text = droidstar.get_tgif_password(); + settingsTab.latEdit.text = droidstar.get_latitude(); + settingsTab.lonEdit.text = droidstar.get_longitude(); + settingsTab.locEdit.text = droidstar.get_location(); + settingsTab.descEdit.text = droidstar.get_description(); + settingsTab.urlEdit.text = droidstar.get_url(); + settingsTab.swidEdit.text = droidstar.get_swid(); + settingsTab.pkgidEdit.text = droidstar.get_pkgid(); + settingsTab.dmroptsEdit.text = droidstar.get_dmr_options(); + mainTab.dmrtgidEdit.text = droidstar.get_dmrtgid(); + settingsTab.iaxuserEdit.text = droidstar.get_iax_user(); + settingsTab.iaxpassEdit.text = droidstar.get_iax_pass(); + settingsTab.iaxnodeEdit.text = droidstar.get_iax_node(); + settingsTab.iaxhostEdit.text = droidstar.get_iax_host(); + settingsTab.iaxportEdit.text = droidstar.get_iax_port(); + settingsTab.mycallEdit.text = droidstar.get_mycall(); + settingsTab.urcallEdit.text = droidstar.get_urcall(); + settingsTab.rptr1Edit.text = droidstar.get_rptr1(); + settingsTab.rptr2Edit.text = droidstar.get_rptr2(); + settingsTab.txtimerEdit.text = droidstar.get_txtimeout(); + + settingsTab.modemRXFreqEdit.text = droidstar.get_modemRxFreq(); + settingsTab.modemTXFreqEdit.text = droidstar.get_modemTxFreq(); + settingsTab.modemRXOffsetEdit.text = droidstar.get_modemRxOffset(); + settingsTab.modemTXOffsetEdit.text = droidstar.get_modemTxOffset(); + settingsTab.modemRXDCOffsetEdit.text = droidstar.get_modemRxDCOffset(); + settingsTab.modemTXDCOffsetEdit.text = droidstar.get_modemTxDCOffset(); + settingsTab.modemRXLevelEdit.text = droidstar.get_modemRxLevel(); + settingsTab.modemTXLevelEdit.text = droidstar.get_modemTxLevel(); + settingsTab.modemRFLevelEdit.text = droidstar.get_modemRFLevel(); + settingsTab.modemTXDelayEdit.text = droidstar.get_modemTxDelay(); + settingsTab.modemCWIdTXLevelEdit.text = droidstar.get_modemCWIdTxLevel(); + settingsTab.modemDStarTXLevelEdit.text = droidstar.get_modemDstarTxLevel(); + settingsTab.modemDMRTXLevelEdit.text = droidstar.get_modemDMRTxLevel(); + settingsTab.modemYSFTXLevelEdit.text = droidstar.get_modemYSFTxLevel(); + settingsTab.modemP25TXLevelEdit.text = droidstar.get_modemP25TxLevel() + settingsTab.modemNXDNTXLevelEdit.text = droidstar.get_modemNXDNTxLevel(); + + hostsTab.hostsTextEdit.text = droidstar.get_local_hosts(); + } + function onUpdate_log(s) { + logTab.logText.append(s); + } + function onConnect_status_changed(c) { + if(c === 0){ + if(mainTab.buttonTX.tx){ + mainTab.buttonTX.tx = false; + droidstar.tx_clicked(false); + mainTab.txtimer.running = false; + mainTab.btntxt.color = "black"; + mainTab.btntxt.text = "TX"; + } + mainTab.connectbutton.text = "Connect"; + mainTab.comboMode.enabled = true; + mainTab.comboHost.enabled = true; + mainTab.comboModule.enabled = true; + mainTab.buttonTX.enabled = false; + mainTab.btntxt.color = "steelblue"; + mainTab.data1.text = ""; + mainTab.data2.text = ""; + mainTab.data3.text = ""; + mainTab.data4.text = ""; + mainTab.data5.text = ""; + mainTab.data6.text = ""; + mainTab.status.text = "Not connected"; + } + if(c === 1){ + mainTab.connectbutton.text = "Connecting"; + mainTab.comboMode.enabled = false; + mainTab.comboHost.enabled = false; + if(mainTab.comboMode.currentText != "REF"){ + mainTab.comboModule.enabled = false; + } + } + if(c === 2){ + mainTab.connectbutton.text = "Disconnect"; + mainTab.comboMode.enabled = false; + mainTab.comboHost.enabled = false; + + if(mainTab.comboMode.currentText != "REF"){ + mainTab.comboModule.enabled = false; + } + if( (mainTab.comboMode.currentText == "REF") || + (mainTab.comboMode.currentText == "DCS") || + (mainTab.comboMode.currentText == "XRF")) + { + disclaimerDialog.open(); + } + if(mainTab.comboMode.currentText === "YSF"){ + settingsTab.m171600.checked = true; + } + if(mainTab.comboMode.currentText === "FCS"){ + settingsTab.m171600.checked = true; + } + if(mainTab.comboMode.currentText === "M17"){ + settingsTab.m173200.checked = true; + } + + mainTab.buttonTX.enabled = true; + mainTab.btntxt.color = "black"; + mainTab.agcBox.checked = true; + } + if(c === 3){ + connectDialog.open(); + } + if(c === 4){ + idcheckDialog.open(); + onConnect_status_changed(0); + } + if(c === 5){ + errorDialog.text = droidstar.get_error_text(); + errorDialog.open(); + droidstar.onConnect_status_changed(0); + } + } + } +} diff --git a/micpermission.h b/micpermission.h new file mode 100755 index 0000000..ca02713 --- /dev/null +++ b/micpermission.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef __MicPermission_h_ +#define __MicPermission_h_ + +#include +#include + +class MicPermission : public QObject +{ + Q_OBJECT +public: + static int check_permission(); +}; + +#endif diff --git a/micpermission.mm b/micpermission.mm new file mode 100755 index 0000000..6f7099a --- /dev/null +++ b/micpermission.mm @@ -0,0 +1,31 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "micpermission.h" +//#import +//#import +#import +int MicPermission::check_permission() +{ + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + if(status != AVAuthorizationStatusAuthorized){ + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { + + }]; + } + return status; +} diff --git a/nxdncodec.cpp b/nxdncodec.cpp new file mode 100755 index 0000000..f2e6277 --- /dev/null +++ b/nxdncodec.cpp @@ -0,0 +1,714 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "nxdncodec.h" +#include + +#define DEBUG + +const int dvsi_interleave[49] = { + 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 41, 43, 45, 47, + 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 42, 44, 46, 48, + 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38 +}; + +const uint8_t NXDN_LICH_RFCT_RDCH = 2U; +const uint8_t NXDN_LICH_USC_SACCH_NS = 0U; +const uint8_t NXDN_LICH_USC_SACCH_SS = 2U; +const uint8_t NXDN_LICH_STEAL_FACCH = 0U; +const uint8_t NXDN_LICH_STEAL_NONE = 3U; +const uint8_t NXDN_LICH_DIRECTION_INBOUND = 0U; +const uint8_t NXDN_MESSAGE_TYPE_VCALL = 1U; +const uint8_t NXDN_MESSAGE_TYPE_TX_REL = 8U; + +const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +NXDNCodec::NXDNCodec(QString callsign, uint16_t nxdnid, uint32_t gwid, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, 0, NULL, host, port, ipv6, vocoder, modem, audioin, audioout), + m_nxdnid(nxdnid) +{ + m_txcnt = 0; + m_txtimerint = 19; + m_modeinfo.gwid = gwid; +} + +NXDNCodec::~NXDNCodec() +{ +} + +void NXDNCodec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + uint8_t ambe[7]; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if(buf.size() == 17){ + if(m_modeinfo.status == CONNECTING){ + m_modeinfo.status = CONNECTED_RW; + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + //m_mbeenc->set_gain_adjust(2.5); + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("NXDN"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + else{ + m_hwrx = false; + m_hwtx = false; + } + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + m_ping_timer->start(1000); + } + if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + m_modeinfo.count++; + } + if(buf.size() == 43){ + m_modeinfo.srcid = (uint16_t)((buf.data()[5] << 8) & 0xff00) | (buf.data()[6] & 0xff); + m_modeinfo.dstid = (uint16_t)((buf.data()[7] << 8) & 0xff00) | (buf.data()[8] & 0xff); + if(get_lich_fct(buf.data()[10U]) == NXDN_LICH_USC_SACCH_NS){ + if((buf.data()[9U] & 0x08) == 0x08){ + qDebug() << "Received EOT"; + m_modeinfo.frame_number = 0; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + m_modeinfo.streamid = 0; + } + else{ + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "New NXDN stream from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid; + } + } + else if(!m_tx && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_IDLE) )){ + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "New NXDN stream in progress from " << m_modeinfo.srcid << " to " << m_modeinfo.dstid; + } + else{ + m_modeinfo.stream_state = STREAMING; + m_modeinfo.frame_number++; + } + m_rxwatchdog = 0; + + memcpy(ambe, buf.data() + 15, 7); + if(m_hwrx){ + interleave(ambe); + } + for(int i = 0; i < 7; ++i){ + m_rxcodecq.append(ambe[i]); + } + + char t[7]; + char *d = &(buf.data()[21]); + for(int i = 0; i < 6; ++i){ + t[i] = d[i] << 1; + t[i] |= (1 & (d[i+1] >> 7)); + } + t[6] = d[6] << 1; + + memcpy(ambe, t, 7); + if(m_hwrx){ + interleave(ambe); + } + for(int i = 0; i < 7; ++i){ + m_rxcodecq.append(ambe[i]); + } + + memcpy(ambe, buf.data() + 29, 7); + if(m_hwrx){ + interleave(ambe); + } + for(int i = 0; i < 7; ++i){ + m_rxcodecq.append(ambe[i]); + } + + d = &(buf.data()[35]); + for(int i = 0; i < 6; ++i){ + t[i] = d[i] << 1; + t[i] |= (1 & (d[i+1] >> 7)); + } + t[6] = d[6] << 1; + + memcpy(ambe, t, 7); + if(m_hwrx){ + interleave(ambe); + } + for(int i = 0; i < 7; ++i){ + m_rxcodecq.append(ambe[i]); + } + } + emit update(m_modeinfo); +} + +void NXDNCodec::interleave(uint8_t *ambe) +{ + char ambe_data[49]; + char dvsi_data[7]; + memset(dvsi_data, 0, 7); + + for(int i = 0; i < 6; ++i){ + for(int j = 0; j < 8; j++){ + ambe_data[j+(8*i)] = (1 & (ambe[i] >> (7 - j))); + } + } + ambe_data[48] = (1 & (ambe[6] >> 7)); + for(int i = 0, j; i < 49; ++i){ + j = dvsi_interleave[i]; + dvsi_data[j/8] += (ambe_data[i])<<(7-(j%8)); + } + memcpy(ambe, dvsi_data, 7); +} + +void NXDNCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.append('N'); + out.append('X'); + out.append('D'); + out.append('N'); + out.append('P'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + out.append((m_modeinfo.gwid >> 8) & 0xff); + out.append((m_modeinfo.gwid >> 0) & 0xff); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void NXDNCodec::send_ping() +{ + QByteArray out; + out.append('N'); + out.append('X'); + out.append('D'); + out.append('N'); + out.append('P'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + out.append((m_modeinfo.gwid >> 8) & 0xff); + out.append((m_modeinfo.gwid >> 0) & 0xff); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void NXDNCodec::send_disconnect() +{ + QByteArray out; + out.append('N'); + out.append('X'); + out.append('D'); + out.append('N'); + out.append('U'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + out.append((m_modeinfo.gwid >> 8) & 0xff); + out.append((m_modeinfo.gwid >> 0) & 0xff); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void NXDNCodec::transmit() +{ + uint8_t ambe_frame[49]; + uint8_t ambe[7]; + int16_t pcm[160]; + + memset(ambe, 0, 7); + +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + + if(m_hwtx){ + m_ambedev->encode(pcm); + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2450(pcm, ambe_frame); + } + for(int i = 0; i < 7; ++i){ + for(int j = 0; j < 8; ++j){ + ambe[i] |= (ambe_frame[(i*8)+j] << (7-j)); + } + ambe[6] &= 0x80; + m_txcodecq.append(ambe[i]); + } + } + + if(m_tx && (m_txcodecq.size() >= 28)){ + for(int i = 0; i < 28; ++i){ + m_ambe[i] = m_txcodecq.dequeue(); + } + send_frame(); + } + else if(m_tx == false){ + send_frame(); + } +} + +void NXDNCodec::send_frame() +{ + QByteArray txdata; + unsigned char *temp_nxdn; + if(m_tx){ + m_modeinfo.stream_state = TRANSMITTING; + temp_nxdn = get_frame(); + txdata.append((char *)temp_nxdn, 43); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } + else{ + fprintf(stderr, "NXDN TX stopped\n"); + m_txtimer->stop(); + temp_nxdn = get_eot(); + m_ttscnt = 0; + txdata.append((char *)temp_nxdn, 43); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + m_modeinfo.stream_state = STREAM_IDLE; + } + m_modeinfo.srcid = m_nxdnid; + m_modeinfo.frame_number = m_txcnt; + m_modeinfo.dstid = m_modeinfo.gwid; + emit update_output_level(m_audio->level()); + emit update(m_modeinfo); +} + +uint8_t * NXDNCodec::get_frame() +{ + memcpy(m_nxdnframe, "NXDND", 5); + m_nxdnframe[5U] = (m_nxdnid >> 8) & 0xFFU; + m_nxdnframe[6U] = (m_nxdnid >> 0) & 0xFFU; + m_nxdnframe[7U] = (m_modeinfo.gwid >> 8) & 0xFFU; + m_nxdnframe[8U] = (m_modeinfo.gwid >> 0) & 0xFFU; + m_nxdnframe[9U] = 0x01U; + + if(!m_txcnt || m_eot){ + encode_header(); + } + else{ + encode_data(); + } + if (m_nxdnframe[10U] == 0x81U || m_nxdnframe[10U] == 0x83U) { + m_nxdnframe[9U] |= m_nxdnframe[15U] == 0x01U ? 0x04U : 0x00U; + m_nxdnframe[9U] |= m_nxdnframe[15U] == 0x08U ? 0x08U : 0x00U; + } + else if ((m_nxdnframe[10U] & 0xF0U) == 0x90U) { + m_nxdnframe[9U] |= 0x02U; + if (m_nxdnframe[10U] == 0x90U || m_nxdnframe[10U] == 0x92U || m_nxdnframe[10U] == 0x9CU || m_nxdnframe[10U] == 0x9EU) { + m_nxdnframe[9U] |= m_nxdnframe[12U] == 0x09U ? 0x04U : 0x00U; + m_nxdnframe[9U] |= m_nxdnframe[12U] == 0x08U ? 0x08U : 0x00U; + } + } + if(m_eot){ + m_txcnt = 0; + m_eot = false; + } + else{ + ++m_txcnt; + } + return m_nxdnframe; +} + +void NXDNCodec::encode_header() +{ + const uint8_t idle[3U] = {0x10, 0x00, 0x00}; + m_lich = 0; + memset(m_sacch, 0, 5U); + memset(m_layer3, 0, 22U); + set_lich_rfct(NXDN_LICH_RFCT_RDCH); + set_lich_fct(NXDN_LICH_USC_SACCH_NS); + set_lich_option(NXDN_LICH_STEAL_FACCH); + set_lich_dir(NXDN_LICH_DIRECTION_INBOUND); + m_nxdnframe[10U] = get_lich(); + + set_sacch_ran(0x01); + set_sacch_struct(0); //Single + set_sacch_data(idle); + get_sacch(&m_nxdnframe[11U]); + if(m_eot){ + set_layer3_msgtype(NXDN_MESSAGE_TYPE_TX_REL); + } + else{ + set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL); + } + set_layer3_srcid(m_nxdnid); + set_layer3_dstid(m_modeinfo.gwid); + set_layer3_grp(true); + set_layer3_blks(0U); + memcpy(&m_nxdnframe[15U], m_layer3, 14U); + memcpy(&m_nxdnframe[29U], m_layer3, 14U); +} + +void NXDNCodec::encode_data() +{ + uint8_t msg[3U]; + m_lich = 0; + memset(m_sacch, 0, 5U); + memset(m_layer3, 0, 22U); + set_lich_rfct(NXDN_LICH_RFCT_RDCH); + set_lich_fct(NXDN_LICH_USC_SACCH_SS); + set_lich_option(NXDN_LICH_STEAL_NONE); + set_lich_dir(NXDN_LICH_DIRECTION_INBOUND); + m_nxdnframe[10U] = get_lich(); + + set_sacch_ran(0x01); + + set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL); + set_layer3_srcid(m_nxdnid); + set_layer3_dstid(m_modeinfo.gwid); + set_layer3_grp(true); + set_layer3_blks(0U); + + switch(m_txcnt % 4){ + case 0: + set_sacch_struct(3); + layer3_encode(msg, 18U, 0U); + set_sacch_data(msg); + break; + case 1: + set_sacch_struct(2); + layer3_encode(msg, 18U, 18U); + set_sacch_data(msg); + break; + case 2: + set_sacch_struct(1); + layer3_encode(msg, 18U, 36U); + set_sacch_data(msg); + break; + case 3: + set_sacch_struct(0); + layer3_encode(msg, 18U, 54U); + set_sacch_data(msg); + break; + } + get_sacch(&m_nxdnframe[11U]); + + if(m_hwtx){ + for(int i = 0; i < 4; ++i){ + deinterleave_ambe(&m_ambe[7*i]); + } + } + + memcpy(&m_nxdnframe[15], m_ambe, 7); + for(int i = 0; i < 7; ++i){ + m_nxdnframe[21+i] |= (m_ambe[7+i] >> 1); + m_nxdnframe[22+i] = (m_ambe[7+i] & 1) << 7; + } + m_nxdnframe[28] |= (m_ambe[13] >> 2); + + memcpy(&m_nxdnframe[29], &m_ambe[14], 7); + for(int i = 0; i < 7; ++i){ + m_nxdnframe[35+i] |= (m_ambe[21+i] >> 1); + m_nxdnframe[36+i] = (m_ambe[21+i] & 1) << 7; + } + m_nxdnframe[41] |= (m_ambe[27] >> 2); +} + +void NXDNCodec::deinterleave_ambe(uint8_t *d) +{ + uint8_t dvsi_data[49]; + uint8_t ambe_data[7]; + memset(ambe_data, 0, 7); + + for(int i = 0; i < 6; ++i){ + for(int j = 0; j < 8; j++){ + dvsi_data[j+(8*i)] = (1 & (d[i] >> (7 - j))); + } + } + dvsi_data[48] = (1 & (d[6] >> 7)); + + for(int i = 0, j; i < 49; ++i){ + j = dvsi_interleave[i]; + ambe_data[i/8] += (dvsi_data[j])<<(7-(i%8)); + //j = dvsi_deinterleave[i]; + //ambe_data[j/8] += (dvsi_data[i])<<(7-(j%8)); + } + memcpy(d, ambe_data, 7); +} + +unsigned char NXDNCodec::get_lich_fct(uint8_t lich) +{ + return (lich >> 4) & 0x03U; +} + +void NXDNCodec::set_lich_rfct(uint8_t rfct) +{ + m_lich &= 0x3FU; + m_lich |= (rfct << 6) & 0xC0U; +} + +void NXDNCodec::set_lich_fct(uint8_t fct) +{ + m_lich &= 0xCFU; + m_lich |= (fct << 4) & 0x30U; +} + +void NXDNCodec::set_lich_option(uint8_t o) +{ + m_lich &= 0xF3U; + m_lich |= (o << 2) & 0x0CU; +} + +void NXDNCodec::set_lich_dir(uint8_t d) +{ + m_lich &= 0xFDU; + m_lich |= (d << 1) & 0x02U; +} + +uint8_t NXDNCodec::get_lich() +{ + bool parity; + switch (m_lich & 0xF0U) { + case 0x80U: + case 0xB0U: + parity = true; + break; + default: + parity = false; + } + if (parity) + m_lich |= 0x01U; + else + m_lich &= 0xFEU; + + return m_lich; +} + + +void NXDNCodec::set_sacch_ran(uint8_t ran) +{ + m_sacch[0] &= 0xC0U; + m_sacch[0] |= ran; +} + +void NXDNCodec::set_sacch_struct(uint8_t s) +{ + m_sacch[0] &= 0x3FU; + m_sacch[0] |= (s << 6) & 0xC0U;; +} + +void NXDNCodec::set_sacch_data(const uint8_t *d) +{ + uint8_t offset = 8U; + for (uint8_t i = 0U; i < 18U; i++, offset++) { + bool b = READ_BIT1(d, i); + WRITE_BIT1(m_sacch, offset, b); + } +} + +void NXDNCodec::get_sacch(uint8_t *d) +{ + memcpy(d, m_sacch, 4U); + encode_crc6(d, 26); +} + +void NXDNCodec::set_layer3_msgtype(uint8_t t) +{ + m_layer3[0] &= 0xC0U; + m_layer3[0] |= t & 0x3FU; +} + +void NXDNCodec::set_layer3_srcid(uint16_t src) +{ + m_layer3[3U] = (src >> 8) & 0xFF; + m_layer3[4U] = (src >> 0) & 0xFF ; +} + +void NXDNCodec::set_layer3_dstid(uint16_t dst) +{ + m_layer3[5U] = (dst >> 8) & 0xFF; + m_layer3[6U] = (dst >> 0) & 0xFF ; +} + +void NXDNCodec::set_layer3_grp(bool grp) +{ + m_layer3[2U] |= grp ? 0x20U : 0x20U; +} + +void NXDNCodec::set_layer3_blks(uint8_t b) +{ + m_layer3[8U] &= 0xF0U; + m_layer3[8U] |= b & 0x0FU; +} + +void NXDNCodec::layer3_encode(uint8_t* d, uint8_t len, uint8_t offset) +{ + for (uint32_t i = 0U; i < len; i++, offset++) { + bool b = READ_BIT1(m_layer3, offset); + WRITE_BIT1(d, i, b); + } +} + +void NXDNCodec::encode_crc6(uint8_t *d, uint8_t len) +{ + uint8_t crc = 0x3FU; + + for (unsigned int i = 0U; i < len; i++) { + bool bit1 = READ_BIT1(d, i) != 0x00U; + bool bit2 = (crc & 0x20U) == 0x20U; + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x27U; + } + crc &= 0x3FU; + uint8_t n = len; + for (uint8_t i = 2U; i < 8U; i++, n++) { + bool b = READ_BIT1((&crc), i); + WRITE_BIT1(d, n, b); + } +} + +void NXDNCodec::get_ambe() +{ + uint8_t ambe[7]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 7; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void NXDNCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[7]; + + if(m_rxwatchdog++ > 25){ + qDebug() << "NXDN RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_rxcodecq.clear(); + } + + if((!m_tx) && (m_rxcodecq.size() > 6) ){ + for(int i = 0; i < 7; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2450(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "YSF playback stopped"; + return; + } +} diff --git a/nxdncodec.h b/nxdncodec.h new file mode 100755 index 0000000..6373cc8 --- /dev/null +++ b/nxdncodec.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef NXDNCODEC_H +#define NXDNCODEC_H + +//#include +#include "codec.h" + +class NXDNCodec : public Codec +{ + Q_OBJECT +public: + NXDNCodec(QString callsign, uint16_t nxdnid, uint32_t gwid, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~NXDNCodec(); + unsigned char * get_frame(); + unsigned char * get_eot(){m_eot = true; return get_frame();} + void set_hwtx(bool hw){m_hwtx = hw;} +private slots: + void process_udp(); + void process_rx_data(); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void hostname_lookup(QHostInfo i); + void send_frame(); +private: + uint16_t m_nxdnid; + bool m_eot; + uint8_t m_nxdnframe[55]; + uint8_t m_lich; + uint8_t m_sacch[5]; + uint8_t m_layer3[22]; + uint8_t m_ambe[36]; + uint8_t packet_size; + + void encode_header(); + void encode_data(); + uint8_t get_lich_fct(uint8_t); + void set_lich_rfct(uint8_t); + void set_lich_fct(uint8_t); + void set_lich_option(uint8_t); + void set_lich_dir(uint8_t); + void set_sacch_ran(uint8_t); + void set_sacch_struct(uint8_t); + void set_sacch_data(const uint8_t *); + void set_layer3_msgtype(uint8_t); + void set_layer3_srcid(uint16_t); + void set_layer3_dstid(uint16_t); + void set_layer3_grp(bool); + void set_layer3_blks(uint8_t); + void layer3_encode(uint8_t*, uint8_t, uint8_t); + + uint8_t get_lich(); + void get_sacch(uint8_t *); + void encode_crc6(uint8_t *, uint8_t); + void deinterleave_ambe(uint8_t *); + void interleave(uint8_t *ambe); +}; + +#endif // NXDNCODEC_H diff --git a/p25codec.cpp b/p25codec.cpp new file mode 100755 index 0000000..4c73cf1 --- /dev/null +++ b/p25codec.cpp @@ -0,0 +1,448 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include "p25codec.h" + +#define DEBUG + +const unsigned char REC62[] = {0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; +const unsigned char REC63[] = {0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC64[] = {0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC65[] = {0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC66[] = {0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC67[] = {0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC68[] = {0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC69[] = {0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC6A[] = {0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; +const unsigned char REC6B[] = {0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; +const unsigned char REC6C[] = {0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC6D[] = {0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC6E[] = {0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC6F[] = {0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC70[] = {0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC71[] = {0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC72[] = {0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; +const unsigned char REC73[] = {0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; +const unsigned char REC80[] = {0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; + + +#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +P25Codec::P25Codec(QString callsign, int dmrid, int hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout) : + Codec(callsign, 0, NULL, host, port, ipv6, NULL, modem, audioin, audioout), + m_hostname(hostname), + m_dmrid(dmrid) +{ + m_p25cnt = 0; + m_txtimerint = 19; +} + +P25Codec::~P25Codec() +{ +} + +void P25Codec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RCCV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if(buf.size() == 11){ + if(m_modeinfo.status == CONNECTING){ + m_modeinfo.status = CONNECTED_RW; + m_modeinfo.status = CONNECTED_RW; + m_txtimer = new QTimer(); + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(5000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + } + if((m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + m_modeinfo.count++; + emit update(m_modeinfo); + } + if(buf.size() > 11){ + if( (m_modeinfo.stream_state == STREAM_END) || + (m_modeinfo.stream_state == STREAM_LOST) || + (m_modeinfo.stream_state == STREAM_IDLE)) + { + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + if(!m_tx && !m_rxtimer->isActive() ){ + m_rxcodecq.clear(); + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + qDebug() << "New P25 stream"; + } + else{ + m_modeinfo.stream_state = STREAMING; + } + m_rxwatchdog = 0; + int offset = 0; + m_modeinfo.frame_number = buf.data()[0U]; + switch ((uint8_t)buf.data()[0U]) { + case 0x62U: + offset = 10U; + break; + case 0x63U: + offset = 1U; + break; + case 0x64U: + offset = 5U; + break; + case 0x65U: + m_modeinfo.dstid = (uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff)); + offset = 5U; + break; + case 0x66U: + m_modeinfo.srcid = (uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff)); + //ui->rptr1->setText(QString::number((uint32_t)((buf.data()[1] << 16) | ((buf.data()[2] << 8) & 0xff00) | (buf.data()[3] & 0xff)))); + offset = 5U; + break; + case 0x67U: + case 0x68U: + case 0x69U: + offset = 5U; + break; + case 0x6AU: + offset = 4U; + break; + case 0x6BU: + offset = 10U; + break; + case 0x6CU: + offset = 1U; + break; + case 0x6DU: + case 0x6EU: + case 0x6FU: + case 0x70U: + case 0x71U: + case 0x72U: + offset = 5U; + break; + case 0x73U: + offset = 4U; + break; + case 0x80U: + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "P25 stream ended"; + default: + break; + } + //for(int i = 0; i < 11; ++i){ + //m_codecq.enqueue(buf.data()[i + offset]); + //} + for (int i = 0; i < 11; ++i){ + m_rxcodecq.append(buf.data()[offset+i]); + } + emit update(m_modeinfo); + } +} + +void P25Codec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.append(0xf0); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void P25Codec::send_ping() +{ + QByteArray out; + out.append(0xf0); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void P25Codec::send_disconnect() +{ + QByteArray out; + out.append(0xf1); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void P25Codec::transmit() +{ + QByteArray txdata; + uint8_t imbe[11]; + int16_t pcm[160]; + uint8_t buffer[22]; + static uint8_t p25step = 0; +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + //audiotx_cnt = 0; + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + encode_4400(pcm, imbe); + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + vocoder.encode_4400(pcm, imbe); + } + else{ + return; + } + } + + if(m_tx){ + switch (p25step) { + case 0x00U: + ::memcpy(buffer, REC62, 22U); + ::memcpy(buffer + 10U, imbe, 11U); + txdata.append((char *)buffer, 22U); + ++p25step; + break; + case 0x01U: + ::memcpy(buffer, REC63, 14U); + ::memcpy(buffer + 1U, imbe, 11U); + txdata.append((char *)buffer, 14U); + ++p25step; + break; + case 0x02U: + ::memcpy(buffer, REC64, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + buffer[1U] = 0x00U; + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x03U: + ::memcpy(buffer, REC65, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + buffer[1U] = (m_hostname >> 16) & 0xFFU; + buffer[2U] = (m_hostname >> 8) & 0xFFU; + buffer[3U] = (m_hostname >> 0) & 0xFFU; + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x04U: + ::memcpy(buffer, REC66, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + buffer[1U] = (m_dmrid >> 16) & 0xFFU; + buffer[2U] = (m_dmrid >> 8) & 0xFFU; + buffer[3U] = (m_dmrid >> 0) & 0xFFU; + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x05U: + ::memcpy(buffer, REC67, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x06U: + ::memcpy(buffer, REC68, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x07U: + ::memcpy(buffer, REC69, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x08U: + ::memcpy(buffer, REC6A, 16U); + ::memcpy(buffer + 4U, imbe, 11U); + txdata.append((char *)buffer, 16U); + ++p25step; + break; + case 0x09U: + ::memcpy(buffer, REC6B, 22U); + ::memcpy(buffer + 10U, imbe, 11U); + txdata.append((char *)buffer, 22U); + ++p25step; + break; + case 0x0AU: + ::memcpy(buffer, REC6C, 14U); + ::memcpy(buffer + 1U, imbe, 11U); + txdata.append((char *)buffer, 14U); + ++p25step; + break; + case 0x0BU: + ::memcpy(buffer, REC6D, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x0CU: + ::memcpy(buffer, REC6E, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x0DU: + ::memcpy(buffer, REC6F, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x0EU: + ::memcpy(buffer, REC70, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + buffer[1U] = 0x80U; + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x0FU: + ::memcpy(buffer, REC71, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x10U: + ::memcpy(buffer, REC72, 17U); + ::memcpy(buffer + 5U, imbe, 11U); + txdata.append((char *)buffer, 17U); + ++p25step; + break; + case 0x11U: + ::memcpy(buffer, REC73, 16U); + ::memcpy(buffer + 4U, imbe, 11U); + txdata.append((char *)buffer, 16U); + p25step = 0; + break; + } + m_modeinfo.stream_state = TRANSMITTING; + m_modeinfo.srcid = m_dmrid; + m_modeinfo.dstid = m_hostname; + m_modeinfo.frame_number = p25step; + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + } + else{ + txdata.append((char *)REC80, 17U); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + fprintf(stderr, "P25 TX stopped\n"); + m_txtimer->stop(); + if(m_ttsid == 0){ + m_audio->stop_capture(); + } + p25step = 0; + m_modeinfo.stream_state = STREAM_IDLE; + m_modeinfo.srcid = 0; + m_modeinfo.dstid = 0; + m_modeinfo.frame_number = 0; + m_txcodecq.clear(); + } + emit update_output_level(m_audio->level()); + emit update(m_modeinfo); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void P25Codec::process_rx_data() +{ + if(m_rxwatchdog++ > 50){ + qDebug() << "P25 RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + uint8_t imbe[11]; + int16_t pcm[160]; + + if(m_rxcodecq.size() > 10){ + for(int i = 0; i < 11; ++i){ + imbe[i] = m_rxcodecq.dequeue(); + } + + vocoder.decode_4400(pcm, imbe); + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "P25 playback stopped"; + } +} diff --git a/p25codec.h b/p25codec.h new file mode 100755 index 0000000..54a8923 --- /dev/null +++ b/p25codec.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef P25CODEC_H +#define P25CODEC_H + +#include "codec.h" + +class P25Codec : public Codec +{ + Q_OBJECT +public: + P25Codec(QString callsign, int dmrid, int hostname, QString host, int port, bool ipv6, QString modem, QString audioin, QString audioout); + ~P25Codec(); + unsigned char * get_frame(unsigned char *ambe); +private: + int m_p25cnt; + unsigned char imbe[11U]; + int m_hostname; + uint32_t m_dmrid; + uint32_t m_txdstid; +private slots: + void process_udp(); + void process_rx_data(); + void send_ping(); + void send_disconnect(); + void transmit(); + void hostname_lookup(QHostInfo i); + void dmr_tgid_changed(unsigned int id) { m_txdstid = id; } + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } +}; + +#endif // P25CODEC_H diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..531ecd1 --- /dev/null +++ b/qml.qrc @@ -0,0 +1,11 @@ + + + main.qml + images/droidstar.png + AboutTab.qml + HostsTab.qml + LogTab.qml + SettingsTab.qml + MainTab.qml + + diff --git a/refcodec.cpp b/refcodec.cpp new file mode 100755 index 0000000..b641e8b --- /dev/null +++ b/refcodec.cpp @@ -0,0 +1,713 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include "refcodec.h" +#include "CRCenc.h" + +#define DEBUG + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +REFCodec::REFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, module, hostname, host, port, ipv6, vocoder, modem, audioin, audioout) +{ +} + +REFCodec::~REFCodec() +{ +} + +void REFCodec::process_udp() +{ + QByteArray buf; + QByteArray out; + QHostAddress sender; + quint16 senderPort; + static bool sd_sync = 0; + static int sd_seq = 0; + static char user_data[21]; + const unsigned char header[5] = {0x80,0x44,0x53,0x56,0x54}; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if ((buf.size() == 5) && (buf.data()[0] == 5)){ + int x = (::rand() % (999999 - 7245 + 1)) + 7245; + QString serial = "HS" + QString("%1").arg(x, 6, 10, QChar('0')); + out.append(0x1c); + out.append(0xc0); + out.append(0x04); + out.append('\x00'); + out.append(m_modeinfo.callsign.toUpper().toLocal8Bit().data(), 6); + out.append(10,'\x00'); + out.append(serial.toUtf8()); + m_udp->writeDatagram(out, m_address, 20001); + } + if(buf.size() == 3){ //2 way keep alive ping + m_modeinfo.count++; + if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + emit update(m_modeinfo); + } +#ifdef DEBUG + if(out.size()){ + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); + } +#endif + if((m_modeinfo.status == CONNECTING) && (buf.size() == 0x08)){ + if((memcmp(&buf.data()[4], "OKRW", 4) == 0) || (memcmp(&buf.data()[4], "OKRO", 4) == 0) || (memcmp(&buf.data()[4], "BUSY", 4) == 0)){ + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("REF"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + else{ + m_hwrx = false; + m_hwtx = false; + } + if(m_modemport != ""){ + m_modem = new SerialModem("REF"); + m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex); + m_modem->set_modem_params(m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel); + m_modem->connect_to_serial(m_modemport); + connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray))); + } + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(1000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + + if(buf.data()[7] == 0x57){ //OKRW + m_modeinfo.status = CONNECTED_RW; + //memset(m_rptr2, ' ', 8); + //memcpy(rptr2, hostname.toLocal8Bit(), hostname.size()); + //rptr2[7] = module; + //rptr2[8] = 0; + //m_ping_timer->start(1000); + } + else if(buf.data()[7] == 0x4f){ //OKRO -- Go get registered! + m_modeinfo.status = CONNECTED_RW; + } + } + else if((buf.data()[4] == 0x46) && (buf.data()[5] == 0x41) && (buf.data()[6] == 0x49) && (buf.data()[7] == 0x4c)){ // FAIL response + m_modeinfo.status = DISCONNECTED; + } + else{ //Unknown response + m_modeinfo.status = DISCONNECTED; + } + emit update(m_modeinfo); + } + if(m_modeinfo.status != CONNECTED_RW) return; + + if((buf.size() == 0x3a) && (!memcmp(buf.data()+1, header, 5)) ){ + char temp[9]; + memcpy(temp, buf.data() + 20, 8); temp[8] = '\0'; + QString rptr2 = QString(temp); + memcpy(temp, buf.data() + 28, 8); temp[8] = '\0'; + QString rptr1 = QString(temp); + memcpy(temp, buf.data() + 36, 8); temp[8] = '\0'; + QString urcall = QString(temp); + memcpy(temp, buf.data() + 44, 8); temp[8] = '\0'; + QString mycall = QString(temp); + QString h = m_hostname + " " + m_module; + qDebug() << "h:r1:r2 == " << h.simplified() << ":" << rptr1.simplified() << ":" << rptr2.simplified(); + if( (rptr2.simplified() == h.simplified()) || (rptr1.simplified() == h.simplified()) ){ + m_rxwatchdog = 0; + const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff); + m_modeinfo.src = mycall; + m_modeinfo.dst = urcall; + m_modeinfo.gw = rptr1; + m_modeinfo.gw2 = rptr2; + + if(!m_tx && !m_rxtimer->isActive() && (m_modeinfo.streamid == 0)){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + m_rxcodecq.clear(); + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.streamid = streamid; + + if(m_modem){ + uint8_t out[44]; + out[0] = 0xe0; + out[1] = 44; + out[2] = MMDVM_DSTAR_HEADER; + out[3] = 0x40; + out[4] = 0; + out[5] = 0; + memcpy(out + 6, rptr2.toLocal8Bit().data(), 8); + memcpy(out + 14, rptr1.toLocal8Bit().data(), 8); + memcpy(out + 22, urcall.toLocal8Bit().data(), 8); + memcpy(out + 30, mycall.toLocal8Bit().data(), 8); + memcpy(out + 38, buf.data() + 52, 4); + CCRC::addCCITT161((uint8_t *)out + 3, 41); + for(int i = 0; i < 44; ++i){ + m_rxmodemq.append(out[i]); + } + //m_modem->write(out); + } + + qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16); + emit update(m_modeinfo); + } + } + else{ + //streamid = 0; + } + } + if((buf.size() == 0x1d) && (!memcmp(buf.data()+1, header, 5)) ){ //29 + const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff); + //qDebug() << "streamid:s == " << m_streamid << ":" << s; + if(streamid != m_modeinfo.streamid){ + return; + } + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAMING; + m_modeinfo.frame_number = buf.data()[16]; + + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(15); + m_rxmodemq.append(MMDVM_DSTAR_DATA); + for(int i = 0; i < 12; ++i){ + m_rxmodemq.append(buf.data()[17+i]); + } + } + + if((buf.data()[16] == 0) && (buf.data()[26] == 0x55) && (buf.data()[27] == 0x2d) && (buf.data()[28] == 0x16)){ + sd_sync = 1; + sd_seq = 1; + } + if(sd_sync && (sd_seq == 1) && (buf.data()[16] == 1) && (buf.data()[26] == 0x30)){ + user_data[0] = buf.data()[27] ^ 0x4f; + user_data[1] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 2) && (buf.data()[16] == 2)){ + user_data[2] = buf.data()[26] ^ 0x70; + user_data[3] = buf.data()[27] ^ 0x4f; + user_data[4] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 3) && (buf.data()[16] == 3) && (buf.data()[26] == 0x31)){ + user_data[5] = buf.data()[27] ^ 0x4f; + user_data[6] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 4) && (buf.data()[16] == 4)){ + user_data[7] = buf.data()[26] ^ 0x70; + user_data[8] = buf.data()[27] ^ 0x4f; + user_data[9] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 5) && (buf.data()[16] == 5) && (buf.data()[26] == 0x32)){ + user_data[10] = buf.data()[27] ^ 0x4f; + user_data[11] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 6) && (buf.data()[16] == 6)){ + user_data[12] = buf.data()[26] ^ 0x70; + user_data[13] = buf.data()[27] ^ 0x4f; + user_data[14] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 7) && (buf.data()[16] == 7) && (buf.data()[26] == 0x33)){ + user_data[15] = buf.data()[27] ^ 0x4f; + user_data[16] = buf.data()[28] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 8) && (buf.data()[16] == 8)){ + user_data[17] = buf.data()[26] ^ 0x70; + user_data[18] = buf.data()[27] ^ 0x4f; + user_data[19] = buf.data()[28] ^ 0x93; + user_data[20] = '\0'; + sd_sync = 0; + sd_seq = 0; + m_modeinfo.usertxt = QString(user_data); + //ui->usertxt->setText(QString::fromUtf8(user_data.data())); + } + for(int i = 0; i < 9; ++i){ + m_rxcodecq.append(buf.data()[17+i]); + } + emit update(m_modeinfo); + } + if(buf.size() == 0x20){ //32 + const uint16_t streamid = (buf.data()[14] << 8) | (buf.data()[15] & 0xff); + if(streamid == m_modeinfo.streamid){ + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(3); + m_rxmodemq.append(MMDVM_DSTAR_EOT); + } + m_modeinfo.usertxt.clear(); + qDebug() << "REF RX stream ended "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + } + //emit update(m_modeinfo); +} + +void REFCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.append(0x05); + out.append('\x00'); + out.append(0x18); + out.append('\x00'); + out.append(0x01); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void REFCodec::send_ping() +{ + QByteArray out; + out.append(0x03); + out.append(0x60); + out.append('\x00'); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void REFCodec::send_disconnect() +{ + QByteArray out; + out.append(0x05); + out.append('\x00'); + out.append(0x18); + out.append('\x00'); + out.append('\x00'); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void REFCodec::format_callsign(QString &s) +{ + QStringList l = s.simplified().split(' '); + + if(l.size() > 1){ + s = l.at(0).simplified(); + while(s.size() < 7){ + s.append(' '); + } + s += l.at(1).simplified(); + } + else{ + while(s.size() < 8){ + s.append(' '); + } + } +} + +void REFCodec::process_modem_data(QByteArray d) +{ + QByteArray txdata; + char cs[9]; + uint8_t ambe[9]; + + uint8_t *p_frame = (uint8_t *)(d.data()); + if(p_frame[2] == MMDVM_DSTAR_HEADER){ + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + cs[8] = 0; + memcpy(cs, p_frame + 22, 8); + m_txurcall = QString(cs); + memcpy(cs, p_frame + 30, 8); + m_txmycall = QString(cs); + m_modeinfo.stream_state = TRANSMITTING_MODEM; + m_tx = true; + } + else if( (p_frame[2] == MMDVM_DSTAR_EOT) || (p_frame[2] == MMDVM_DSTAR_LOST) ){ + m_tx = false; + } + else if(p_frame[2] == MMDVM_DSTAR_DATA){ + memcpy(ambe, p_frame + 3, 9); + } + send_frame(ambe); +} + +void REFCodec::toggle_tx(bool tx) +{ + tx ? start_tx() : stop_tx(); +} + +void REFCodec::start_tx() +{ + format_callsign(m_txmycall); + format_callsign(m_txurcall); + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + Codec::start_tx(); +} + +void REFCodec::transmit() +{ + unsigned char ambe[9]; + uint8_t ambe_frame[72]; + int16_t pcm[160]; + memset(ambe_frame, 0, 72); + memset(ambe, 0, 9); +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + //audiotx_cnt = 0; + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + + if(m_hwtx){ + m_ambedev->encode(pcm); + if(m_tx && (m_txcodecq.size() >= 9)){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_txcodecq.dequeue(); + } + send_frame(ambe); + } + else if(!m_tx){ + send_frame(ambe); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2400x1200(pcm, ambe); + } + send_frame(ambe); + } +} + +void REFCodec::send_frame(uint8_t *ambe) +{ + QByteArray txdata; + static uint16_t txstreamid = 0; + static bool sendheader = 1; + + if(txstreamid == 0){ + txstreamid = static_cast((::rand() & 0xFFFF)); + //std::cerr << "txstreamid == " << txstreamid << std::endl; + } + if(sendheader){ + sendheader = 0; + txdata.resize(58); + txdata[0] = 0x3a; + txdata[1] = 0x80; + txdata[2] = 0x44; + txdata[3] = 0x53; + txdata[4] = 0x56; + txdata[5] = 0x54; + txdata[6] = 0x10; + txdata[7] = 0x00; + txdata[8] = 0x00; + txdata[9] = 0x00; + txdata[10] = 0x20; + txdata[11] = 0x00; + txdata[12] = 0x02; + txdata[13] = 0x01; + txdata[14] = txstreamid & 0xff; + txdata[15] = (txstreamid >> 8) & 0xff; + txdata[16] = 0x80; + txdata[17] = 0x00; + txdata[18] = 0x00; + txdata[19] = 0x00; + txdata.replace(20, 8, m_txrptr2.toLocal8Bit().data()); + txdata.replace(28, 8, m_txrptr1.toLocal8Bit().data()); + txdata.replace(36, 8, m_txurcall.toLocal8Bit().data()); + txdata.replace(44, 8, m_txmycall.toLocal8Bit().data()); + txdata.replace(52, 4, "AMBE"); + CCRC::addCCITT161((uint8_t *)txdata.data() + 17, 41); + + m_modeinfo.src = m_txmycall; + m_modeinfo.dst = m_txurcall; + m_modeinfo.gw = m_txrptr1; + m_modeinfo.gw2 = m_txrptr2; + m_modeinfo.streamid = txstreamid; + m_modeinfo.frame_number = m_txcnt; + + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + } + else { + txdata.resize(29); + txdata[0] = 0x1d; + txdata[1] = 0x80; + txdata[2] = 0x44; + txdata[3] = 0x53; + txdata[4] = 0x56; + txdata[5] = 0x54; + txdata[6] = 0x20; + txdata[7] = 0x00; + txdata[8] = 0x00; + txdata[9] = 0x00; + txdata[10] = 0x20; + txdata[11] = 0x00; + txdata[12] = 0x02; + txdata[13] = 0x01; + txdata[14] = txstreamid & 0xff; + txdata[15] = (txstreamid >> 8) & 0xff; + txdata[16] = m_txcnt % 21; + memcpy(txdata.data() + 17, ambe, 9); + + //for(int i = 0; i < 9; ++i){ + //txdata[17 + i] = ad8dp[(tx_cnt * 9) + i]; + //if(ambeq.size()){ + // txdata[17 + i] = ambeq.dequeue(); + //} + //else{ + // txdata[17 + i] = 0; + //} + //} + + //memset(txdata.data() + 17, 0x00, 9); + switch(txdata.data()[16]){ + case 0: + txdata[26] = 0x55; + txdata[27] = 0x2d; + txdata[28] = 0x16; + break; + case 1: + txdata[26] = 0x40 ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[0] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[1] ^ 0x93; + break; + case 2: + txdata[26] = m_txusrtxt.toLocal8Bit().data()[2] ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[3] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[4] ^ 0x93; + break; + case 3: + txdata[26] = 0x41 ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[5] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[6] ^ 0x93; + break; + case 4: + txdata[26] = m_txusrtxt.toLocal8Bit().data()[7] ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[8] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[9] ^ 0x93; + break; + case 5: + txdata[26] = 0x42 ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[10] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[11] ^ 0x93; + break; + case 6: + txdata[26] = m_txusrtxt.toLocal8Bit().data()[12] ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[13] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[14] ^ 0x93; + break; + case 7: + txdata[26] = 0x43 ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[15] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[16] ^ 0x93; + break; + case 8: + txdata[26] = m_txusrtxt.toLocal8Bit().data()[17] ^ 0x70; + txdata[27] = m_txusrtxt.toLocal8Bit().data()[18] ^ 0x4f; + txdata[28] = m_txusrtxt.toLocal8Bit().data()[19] ^ 0x93; + break; + default: + txdata[26] = 0x16; + txdata[27] = 0x29; + txdata[28] = 0xf5; + break; + } + //if((tx_cnt * 9) >= sizeof(ad8dp)){ + // tx_cnt = 0; + //} + if((m_txcnt % 21) == 0){ + sendheader = 1; + } + + if(m_tx){ + m_txcnt++; + } + else{ + qDebug() << "TX stopped"; + //txdata[0] = 0x20; + //txdata[6] = 0x20; + //txdata[16] = m_txcnt % 21; + memset(txdata.data() + 17, 0, 9); + txdata[26] = 0x55; + txdata[27] = 0x55; + txdata[28] = 0x55; + txdata.append(0x55); + txdata.append(0xc8); + txdata.append(0x7a); + m_txcnt = 0; + txstreamid = 0; + m_modeinfo.streamid = 0; + sendheader = 1; + m_txtimer->stop(); + + if((m_ttsid == 0) && (m_modeinfo.stream_state == TRANSMITTING) ){ + m_audio->stop_capture(); + } + m_ttscnt = 0; + m_modeinfo.stream_state = STREAM_IDLE; + } + } + + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + emit update_output_level(m_audio->level()); + update(m_modeinfo); +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void REFCodec::get_ambe() +{ + uint8_t ambe[9]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 9; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void REFCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[9]; + + if(m_rxwatchdog++ > 50){ + qDebug() << "REF RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + if(m_rxmodemq.size() > 2){ + QByteArray out; + int s = m_rxmodemq[1]; + if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){ + for(int i = 0; i < s; ++i){ + out.append(m_rxmodemq.dequeue()); + } + m_modem->write(out); + } + } + + if((!m_tx) && (m_rxcodecq.size() > 8) ){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2400x1200(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "REF playback stopped"; + return; + } +} diff --git a/refcodec.h b/refcodec.h new file mode 100755 index 0000000..0a54777 --- /dev/null +++ b/refcodec.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef REFCODEC_H +#define REFCODEC_H + +#include "codec.h" + +class REFCodec : public Codec +{ + Q_OBJECT +public: + REFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~REFCodec(); + unsigned char * get_frame(unsigned char *ambe); +private: + QString m_txusrtxt; + uint8_t packet_size; +private slots: + void toggle_tx(bool); + void start_tx(); + void process_udp(); + void process_modem_data(QByteArray); + void process_rx_data(); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void format_callsign(QString &s); + void hostname_lookup(QHostInfo i); + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } + void module_changed(int m) { m_module = 0x41 + m; m_modeinfo.streamid = 0; } + void usrtxt_changed(QString t) { m_txusrtxt = t; } + void send_frame(uint8_t *); +}; + +#endif // REFCODEC_H diff --git a/serialambe.cpp b/serialambe.cpp new file mode 100755 index 0000000..8042f48 --- /dev/null +++ b/serialambe.cpp @@ -0,0 +1,425 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include +#include +#ifndef Q_OS_ANDROID +#include +#endif +#include +#include "serialambe.h" + +#define ENDLINE "\n" + +//#define DEBUG + +const uint8_t AMBEP251_4400_2800[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x05U, 0x58U, 0x08U, 0x6BU, 0x10U, 0x30U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x90U}; //DVSI P25 USB Dongle FEC +//const uint8_t AMBEP251_4400_0000[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x05U, 0x58U, 0x08U, 0x6BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x58U}; //DVSI P25 USB Dongle No-FEC +//const uint8_t AMBE1000_4400_2800[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x00U, 0x58U, 0x08U, 0x87U, 0x30U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x44U, 0x90U}; +//const uint8_t AMBE2000_4400_2800[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x02U, 0x58U, 0x07U, 0x65U, 0x00U, 0x09U, 0x1eU, 0x0cU, 0x41U, 0x27U, 0x73U, 0x90U}; +//const uint8_t AMBE3000_4400_2800[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x04U, 0x58U, 0x09U, 0x86U, 0x80U, 0x20U, 0x00U, 0x00U, 0x00U, 0x00U, 0x73U, 0x90U}; +const uint8_t AMBE2000_2400_1200[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x01U, 0x30U, 0x07U, 0x63U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x48U}; +const uint8_t AMBE3000_2450_1150[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x04U, 0x31U, 0x07U, 0x54U, 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x6fU, 0x48U}; +const uint8_t AMBE3000_2450_0000[17] = {0x61, 0x00, 0x0d, 0x00, 0x0a, 0x04U, 0x31U, 0x07U, 0x54U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x70U, 0x31U}; +const uint8_t AMBE3000_PARITY_DISABLE[8] = {0x61, 0x00, 0x04, 0x00, 0x3f, 0x00, 0x2f, 0x14}; + +//const uint8_t AMBE2020[48] = {0x13, 0xec, 0x00, 0x00, 0x10, 0x30, 0x00, 0x01, 0x00, 0x00, 0x42, 0x30, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +//const uint8_t AMBE2020[4] = {0x04, 0x20, 0x01, 0x00}; +const uint8_t AMBE2020[5] = {0x05, 0x00, 0x18, 0x00, 0x01}; +SerialAMBE::SerialAMBE(QString protocol) : + m_protocol(protocol), + m_decode_gain(1.0) +{ +} + +SerialAMBE::~SerialAMBE() +{ + //m_serial->close(); +} + +QMap SerialAMBE::discover_devices() +{ + QMap devlist; + const QString blankString = "N/A"; + QString out; +#ifndef Q_OS_ANDROID + const auto serialPortInfos = QSerialPortInfo::availablePorts(); + + if(serialPortInfos.count()){ + for(const QSerialPortInfo &serialPortInfo : serialPortInfos) { + out = "Port: " + serialPortInfo.portName() + ENDLINE + + "Location: " + serialPortInfo.systemLocation() + ENDLINE + + "Description: " + (!serialPortInfo.description().isEmpty() ? serialPortInfo.description() : blankString) + ENDLINE + + "Manufacturer: " + (!serialPortInfo.manufacturer().isEmpty() ? serialPortInfo.manufacturer() : blankString) + ENDLINE + + "Serial number: " + (!serialPortInfo.serialNumber().isEmpty() ? serialPortInfo.serialNumber() : blankString) + ENDLINE + + "Vendor Identifier: " + (serialPortInfo.hasVendorIdentifier() ? QByteArray::number(serialPortInfo.vendorIdentifier(), 16) : blankString) + ENDLINE + + "Product Identifier: " + (serialPortInfo.hasProductIdentifier() ? QByteArray::number(serialPortInfo.productIdentifier(), 16) : blankString) + ENDLINE + + "Busy: " + (serialPortInfo.isBusy() ? "Yes" : "No") + ENDLINE; + fprintf(stderr, "%s", out.toStdString().c_str());fflush(stderr); + if((!serialPortInfo.description().isEmpty()) && (!serialPortInfo.isBusy())){ + //devlist[serialPortInfo.systemLocation()] = serialPortInfo.portName() + " - " + serialPortInfo.manufacturer() + " " + serialPortInfo.description() + " - " + serialPortInfo.serialNumber(); + devlist[serialPortInfo.systemLocation()] = serialPortInfo.systemLocation() + ":" + serialPortInfo.description(); + } + } + } +#else + QStringList list = AndroidSerialPort::GetInstance().discover_devices(); + for ( const auto& i : list ){ + devlist[i] = i + ":" + i; + } +#endif +return devlist; +} + +void SerialAMBE::connect_to_serial(QString p) +{ + const QString blankString = "N/A"; + int br = 460800; + + if((m_protocol != "P25") && (m_protocol != "M17") && (p != "")){ +#ifndef Q_OS_ANDROID + m_serial = new QSerialPort; + QSerialPortInfo info(*m_serial); + QString out = "Port: " + info.portName() + ENDLINE + + "Location: " + info.systemLocation() + ENDLINE + + "Description: " + (!info.description().isEmpty() ? info.description() : blankString) + ENDLINE + + "Manufacturer: " + (!info.manufacturer().isEmpty() ? info.manufacturer() : blankString) + ENDLINE + + "Serial number: " + (!info.serialNumber().isEmpty() ? info.serialNumber() : blankString) + ENDLINE + + "Vendor Identifier: " + (info.hasVendorIdentifier() ? QByteArray::number(info.vendorIdentifier(), 16) : blankString) + ENDLINE + + "Product Identifier: " + (info.hasProductIdentifier() ? QByteArray::number(info.productIdentifier(), 16) : blankString) + ENDLINE + + "Busy: " + (info.isBusy() ? "Yes" : "No") + ENDLINE; + fprintf(stderr, "%s", out.toStdString().c_str());fflush(stderr); + m_description = info.description(); + + if(m_description == "DV Dongle"){ + br = 230400; + } + +#else + m_serial = &AndroidSerialPort::GetInstance(); +#endif + m_serial->setPortName(p); + m_serial->setBaudRate(br); + m_serial->setDataBits(QSerialPort::Data8); + m_serial->setStopBits(QSerialPort::OneStop); + m_serial->setParity(QSerialPort::NoParity); + //out << "Baud rate == " << serial->baudRate() << endl; + if (m_serial->open(QIODevice::ReadWrite)) { +#ifndef Q_OS_ANDROID + connect(m_serial, &QSerialPort::readyRead, this, &SerialAMBE::process_serial); +#else + //connect(m_serial, &AndroidSerialPort::readyRead, this, &SerialAMBE::process_serial); + connect(m_serial, SIGNAL(data_received(QByteArray)), this, SLOT(receive_serial(QByteArray))); +#endif + QByteArray a; + a.clear(); + if(m_description != "DV Dongle"){ + m_serial->setFlowControl(QSerialPort::HardwareControl); + m_serial->setRequestToSend(true); + a.append(reinterpret_cast(AMBE3000_PARITY_DISABLE), sizeof(AMBE3000_PARITY_DISABLE)); + m_serial->write(a); + QThread::msleep(100); + a.clear(); + } + + if(m_protocol == "DMR"){ + a.append(reinterpret_cast(AMBE3000_2450_1150), sizeof(AMBE3000_2450_1150)); + packet_size = 9; + } + else if( (m_protocol == "YSF") || (m_protocol == "NXDN") ){ + a.append(reinterpret_cast(AMBE3000_2450_0000), sizeof(AMBE3000_2450_0000)); + packet_size = 7; + } + else if(m_protocol == "P25"){ + a.append(reinterpret_cast(AMBEP251_4400_2800), sizeof(AMBEP251_4400_2800)); + } + else if(m_description != "DV Dongle"){ //D-Star with AMBE3000 + a.append(reinterpret_cast(AMBE2000_2400_1200), sizeof(AMBE2000_2400_1200)); + packet_size = 9; + } + else{ + a.append(reinterpret_cast(AMBE2020), sizeof(AMBE2020)); + packet_size = 9; + } + m_serial->write(a); +#ifdef DEBUG + fprintf(stderr, "SENDHW %d:%d:", a.size(), m_serialdata.size()); + for(int i = 0; i < a.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)a.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + //hw_ambe_present = true; + } + else{ + //hw_ambe_present = false; + //std::cerr << "Error: Failed to open device." << std::endl; + } + } +} + +void SerialAMBE::receive_serial(QByteArray d) +{ + for(int i = 0; i < d.size(); i++){ + m_serialdata.append(d[i]); + } + + if(m_description == "DV Dongle"){ + process_serial_2020(); + } + else{ + process_serial_3000(); + } +} + +void SerialAMBE::process_serial() +{ + QByteArray d = m_serial->readAll(); + for(int i = 0; i < d.size(); i++){ + m_serialdata.append(d[i]); + } +#ifdef DEBUG + qDebug() << "RECV: " << d.size() << " : " << m_serialdata.size() << " : " << d.toHex(); + fprintf(stderr, "AMBEHW %d:%d:", d.size(), m_serialdata.size()); + for(int i = 0; i < d.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)d.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + + if(m_description == "DV Dongle"){ + process_serial_2020(); + } + else{ + process_serial_3000(); + } +} + +void SerialAMBE::decode(uint8_t *ambe) +{ + if(m_description == "DV Dongle"){ + decode_2020(ambe); + } + else{ + decode_3000(ambe); + } +} + +void SerialAMBE::encode(int16_t *audio) +{ + uint8_t packet[327] = {0x61, 0x01, 0x43, 0x02, 0x40, 0x00, 0xa0}; + for(int i = 0; i < 160; ++i){ + packet [(i*2)+7] = (audio[i] >> 8) & 0xff; + packet [(i*2)+8] = audio[i] & 0xff; + } + int r = m_serial->write((char *)packet, 327); +#ifdef DEBUG + fprintf(stderr, "SENDHW:%d: ", r); + for(int i = 0; i < 326; ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", packet[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void SerialAMBE::decode_2020(uint8_t *ambe) +{ + uint8_t packet[50] = {0x32, 0xa0, 0xec, 0x13, 0x00, 0x00, 0x30, 0x10, 0x01, 0x00, 0x00, 0x00, 0x30, 0x42, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t pcm[322]; + memset(pcm, 0, 322); + pcm[0] = 0x42; + pcm[1] = 0x81; + memcpy(packet+24, ambe, packet_size); + m_serial->write((char *)packet, 50); + m_serial->write((char *)pcm, 322); +} + +void SerialAMBE::decode_3000(uint8_t *ambe) +{ + uint8_t packet[15] = {0x61, 0x00, 0x0b, 0x01, 0x01, 0x48}; + if( packet_size == 7 ){ + packet[2] = 0x09; + packet[5] = 0x31; + } + memcpy(packet+6, ambe, packet_size); + + m_serial->write((char *)packet, (6 + packet_size)); +} + +void SerialAMBE::process_serial_2020() +{ + if( (m_serialdata.size() > 321) && + ((uint8_t)m_serialdata[0] == 0x42) && + ((uint8_t)m_serialdata[1] == 0x81) + ) + { + emit data_ready(); + } + if( (m_serialdata.size() > 49) && + ((uint8_t)m_serialdata[0] == 0x32) && + ((uint8_t)m_serialdata[1] == 0xa0) && + ((uint8_t)m_serialdata[0] == 0xec) && + ((uint8_t)m_serialdata[1] == 0x13) + ) + { + emit data_ready(); + } +} + +void SerialAMBE::process_serial_3000() +{ + if( (m_serialdata.size() > 3) && + (m_serialdata[0] == 0x61) && + (m_serialdata[3] == 0x00) + ) + { + do { + m_serialdata.dequeue(); + } + while(m_serialdata.size() && m_serialdata[0] != 0x61); + } + if( (m_serialdata.size() >= (6 + packet_size)) && + (m_serialdata[0] == 0x61) && + (m_serialdata[3] == 0x01) + ) + { + emit data_ready(); + } +} + +bool SerialAMBE::get_ambe(uint8_t *ambe) +{ + bool r = false; + if(m_serialdata.isEmpty()){ + return r; + } + + if( (m_serialdata.size() > 3) && + (m_serialdata[0] == 0x61) && + (m_serialdata[3] != 0x01) + ) + { + do { + m_serialdata.dequeue(); + } + while(m_serialdata.size() && m_serialdata[0] != 0x61); + } + + if( (m_serialdata.size() >= (6 + packet_size)) && + (m_serialdata[0] == 0x61) && + (m_serialdata[3] == 0x01) + ) + { + for(int i = 0; i < 6; ++i){ + m_serialdata.dequeue(); + } + for(int i = 0; i < packet_size; i++){ + ambe[i] = m_serialdata.dequeue(); + } + r = true; + } + return r; +} + +bool SerialAMBE::get_audio(int16_t *audio) +{ + bool r = false; + if(m_serialdata.isEmpty()){ + return r; + } + uint8_t header[] = {0x61, 0x01, 0x42, 0x02, 0x00, 0xA0}; +/* + if( (m_serialdata.size() > 3) && + (m_serialdata[0] == 0x61) && + (m_serialdata[3] != 0x02) + ) + { + do { + m_serialdata.dequeue(); + } + while(m_serialdata.size() && m_serialdata[0] != 0x61); + } + + //qDebug() << "get_audio() m_serialdata.size() == " << m_serialdata.size(); + + fprintf(stderr, "AMBEHW %d:%d:", m_serialdata.size(), m_serialdata.size()); + for(int i = 0; i < m_serialdata.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)m_serialdata[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +*/ + if(m_serialdata.size() >= 326){ + if ( ((uint8_t)m_serialdata[0] == header[0]) && + ((uint8_t)m_serialdata[1] == header[1]) && + ((uint8_t)m_serialdata[2] == header[2]) && + ((uint8_t)m_serialdata[3] == header[3]) && + ((uint8_t)m_serialdata[4] == header[4]) && + ((uint8_t)m_serialdata[5] == header[5])){ + //m_serialdata.erase(m_serialdata.begin(), m_serialdata.begin() + 6); + m_serialdata.dequeue(); + m_serialdata.dequeue(); + m_serialdata.dequeue(); + m_serialdata.dequeue(); + m_serialdata.dequeue(); + m_serialdata.dequeue(); + //qDebug() << "Found header, deleting....................................................................."; + for(int i = 0; i < 160; i++){ + //Byte swap BE to LE + audio[i] = ((m_serialdata.dequeue() << 8) & 0xff00) | (m_serialdata.dequeue() & 0xff); + audio[i] = (qreal)audio[i] * m_decode_gain; + } + r = true; + } + else{ + qDebug() << "Header not found..............................................................................." ; + while( (m_serialdata.size() > 5) && ( + ((uint8_t)m_serialdata[0] != header[0]) || + ((uint8_t)m_serialdata[1] != header[1]) || + ((uint8_t)m_serialdata[2] != header[2]) || + ((uint8_t)m_serialdata[3] != header[3]) || + ((uint8_t)m_serialdata[4] != header[4]) || + ((uint8_t)m_serialdata[5] != header[5]))){ + m_serialdata.dequeue(); + //qDebug() << "Finding start of audio frame"; + } + } + } + return r; +} + +void SerialAMBE::clear_queue() +{ + m_serialdata.clear(); +} diff --git a/serialambe.h b/serialambe.h new file mode 100755 index 0000000..3d86451 --- /dev/null +++ b/serialambe.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef SERIALAMBE_H +#define SERIALAMBE_H + +#include +#include +#ifdef Q_OS_ANDROID +#include "androidserialport.h" +#endif +#include + +class SerialAMBE : public QObject +{ + Q_OBJECT +public: + SerialAMBE(QString); + ~SerialAMBE(); + static QMap discover_devices(); + void connect_to_serial(QString); + bool get_audio(int16_t *); + bool get_ambe(uint8_t *ambe); + void decode(uint8_t *); + void encode(int16_t *); + void clear_queue();//{ m_serialdata.clear(); } + void set_decode_gain(qreal g){ m_decode_gain = g; } +private slots: + void process_serial(); + void receive_serial(QByteArray); +private: +#ifndef Q_OS_ANDROID + QSerialPort *m_serial; +#else + AndroidSerialPort *m_serial; +#endif + QString m_description; + QString m_manufacturer; + QString m_serialnum; + QString m_protocol; + uint8_t packet_size; + qreal m_decode_gain; + QQueue m_serialdata; + void decode_2020(uint8_t *); + void encode_2020(int16_t *); + void decode_3000(uint8_t *); + void encode_3000(int16_t *); + void process_serial_2020(); + void process_serial_3000(); +signals: + void data_ready(); +}; + +#endif // SERIALAMBE_H diff --git a/serialmodem.cpp b/serialmodem.cpp new file mode 100644 index 0000000..b19cb3d --- /dev/null +++ b/serialmodem.cpp @@ -0,0 +1,434 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include +#include "serialmodem.h" + +//#define DEBUGHW +#define ENDLINE "\n" + +const unsigned char MODE_IDLE = 0U; +const unsigned char MODE_DSTAR = 1U; +const unsigned char MODE_DMR = 2U; +const unsigned char MODE_YSF = 3U; +const unsigned char MODE_P25 = 4U; +const unsigned char MODE_NXDN = 5U; +const unsigned char MODE_CW = 98U; +const unsigned char MODE_LOCKOUT = 99U; +const unsigned char MODE_ERROR = 100U; + +const unsigned char TAG_HEADER = 0x00U; +const unsigned char TAG_DATA = 0x01U; +const unsigned char TAG_LOST = 0x02U; +const unsigned char TAG_EOT = 0x03U; +const unsigned char TAG_NODATA = 0x04U; + +enum HW_TYPE { + HWT_MMDVM, + HWT_DVMEGA, + HWT_MMDVM_ZUMSPOT, + HWT_MMDVM_HS_HAT, + HWT_NANO_HOTSPOT, + HWT_MMDVM_HS, + HWT_UNKNOWN +}; + +enum RPT_RF_STATE { + RS_RF_LISTENING, + RS_RF_LATE_ENTRY, + RS_RF_AUDIO, + RS_RF_DATA, + RS_RF_REJECTED, + RS_RF_INVALID +}; + +enum RPT_NET_STATE { + RS_NET_IDLE, + RS_NET_AUDIO, + RS_NET_DATA +}; + +enum B_STATUS { + BS_NO_DATA, + BS_DATA, + BS_MISSING +}; + +const unsigned char MMDVM_FRAME_START = 0xE0U; + +const unsigned char MMDVM_GET_VERSION = 0x00U; +const unsigned char MMDVM_GET_STATUS = 0x01U; +const unsigned char MMDVM_SET_CONFIG = 0x02U; +const unsigned char MMDVM_SET_MODE = 0x03U; +const unsigned char MMDVM_SET_FREQ = 0x04U; + +const unsigned char MMDVM_SEND_CWID = 0x0AU; + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +const unsigned char MMDVM_DMR_DATA1 = 0x18U; +const unsigned char MMDVM_DMR_LOST1 = 0x19U; +const unsigned char MMDVM_DMR_DATA2 = 0x1AU; +const unsigned char MMDVM_DMR_LOST2 = 0x1BU; +const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; +const unsigned char MMDVM_DMR_START = 0x1DU; +const unsigned char MMDVM_DMR_ABORT = 0x1EU; + +const unsigned char MMDVM_YSF_DATA = 0x20U; +const unsigned char MMDVM_YSF_LOST = 0x21U; + +const unsigned char MMDVM_P25_HDR = 0x30U; +const unsigned char MMDVM_P25_LDU = 0x31U; +const unsigned char MMDVM_P25_LOST = 0x32U; + +const unsigned char MMDVM_NXDN_DATA = 0x40U; +const unsigned char MMDVM_NXDN_LOST = 0x41U; + +const unsigned char MMDVM_POCSAG_DATA = 0x50U; + +const unsigned char MMDVM_FM_PARAMS1 = 0x60U; +const unsigned char MMDVM_FM_PARAMS2 = 0x61U; +const unsigned char MMDVM_FM_PARAMS3 = 0x62U; + +const unsigned char MMDVM_ACK = 0x70U; +const unsigned char MMDVM_NAK = 0x7FU; + +const unsigned char MMDVM_SERIAL = 0x80U; + +const unsigned char MMDVM_TRANSPARENT = 0x90U; +const unsigned char MMDVM_QSO_INFO = 0x91U; + +const unsigned char MMDVM_DEBUG1 = 0xF1U; +const unsigned char MMDVM_DEBUG2 = 0xF2U; +const unsigned char MMDVM_DEBUG3 = 0xF3U; +const unsigned char MMDVM_DEBUG4 = 0xF4U; +const unsigned char MMDVM_DEBUG5 = 0xF5U; + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 2000U; + +SerialModem::SerialModem(QString protocol) +{ + set_mode(protocol); + m_dmrDelay = 0; + m_debug = false; + m_dmrColorCode = 1; +} + +SerialModem::~SerialModem() +{ + m_serial->close(); +} + +void SerialModem::set_mode(QString m) +{ + m_dstarEnabled = 0; + m_dmrEnabled = 0; + m_ysfEnabled = 0; + m_p25Enabled = 0; + m_nxdnEnabled = 0; + m_pocsagEnabled = 0; + + if((m == "REF") || (m == "DCS") || (m == "XRF")){ + m_dstarEnabled = 1; + } + else if(m == "DMR"){ + m_dmrEnabled = 1; + } + else if((m == "YSF") || (m == "FCS")){ + m_ysfEnabled = 1; + } + else if(m == "P25"){ + m_p25Enabled = 1; + } + else if(m == "NXDN"){ + m_nxdnEnabled = 1; + } +} + +void SerialModem::set_modem_flags(bool rxInvert, bool txInvert, bool pttInvert, bool useCOSAsLockout, bool duplex) +{ + m_rxInvert = rxInvert; + m_txInvert = txInvert; + m_pttInvert = pttInvert; + m_useCOSAsLockout = useCOSAsLockout; + m_duplex = duplex; + m_ysfLoDev = 0; +} + +void SerialModem::set_modem_params(uint32_t rxfreq, uint32_t txfreq, uint32_t txDelay, float rxLevel, float rfLevel, uint32_t ysfTXHang, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float pocsagTXLevel) +{ + m_rxfreq = rxfreq; + m_txfreq = txfreq; + m_txDelay = txDelay; + m_rxLevel = rxLevel; + m_rfLevel = rfLevel; + m_cwIdTXLevel = cwIdTXLevel; + m_dstarTXLevel = dstarTXLevel; + m_dmrTXLevel = dmrTXLevel; + m_ysfTXLevel = ysfTXLevel; + m_p25TXLevel = p25TXLevel; + m_nxdnTXLevel = nxdnTXLevel; + m_pocsagTXLevel = pocsagTXLevel; + m_ysfTXHang = ysfTXHang; +} + +void SerialModem::connect_to_serial(QString p) +{ +#ifndef Q_OS_ANDROID + m_serial = new QSerialPort; +#else + m_serial = &AndroidSerialPort::GetInstance(); +#endif + m_serial->setPortName(p); + m_serial->setBaudRate(115200); + m_serial->setDataBits(QSerialPort::Data8); + m_serial->setStopBits(QSerialPort::OneStop); + m_serial->setParity(QSerialPort::NoParity); + //out << "Baud rate == " << serial->baudRate() << endl; + if (m_serial->open(QIODevice::ReadWrite)) { + m_modemtimer = new QTimer(); + connect(m_modemtimer, SIGNAL(timeout()), this, SLOT(process_modem())); + m_modemtimer->start(19); +#ifndef Q_OS_ANDROID + connect(m_serial, &QSerialPort::readyRead, this, &SerialModem::process_serial); +#else + //connect(m_serial, &AndroidSerialPort::readyRead, this, &SerialModem::process_serial); + connect(m_serial, SIGNAL(data_received(QByteArray)), this, SLOT(receive_serial(QByteArray))); +#endif + m_serial->setFlowControl(QSerialPort::HardwareControl); + m_serial->setRequestToSend(true); + QByteArray a; + a.clear(); + a.append(MMDVM_FRAME_START); + a.append(3); + a.append(MMDVM_GET_VERSION); + m_serial->write(a); +#ifdef DEBUGHW + fprintf(stderr, "MODEMTX %d:%d:", a.size(), m_serialdata.size()); + for(int i = 0; i < a.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)a.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void SerialModem::receive_serial(QByteArray d) +{ + for(int i = 0; i < d.size(); i++){ + m_serialdata.enqueue(d[i]); + } + //qDebug() << "SerialModem::process_serial()" << d.toHex(); +} + +void SerialModem::process_serial() +{ + QByteArray d = m_serial->readAll(); + + for(int i = 0; i < d.size(); i++){ + m_serialdata.enqueue(d[i]); + } + //qDebug() << "SerialModem::process_serial()" << d.toHex(); +#ifdef DEBUGHW + fprintf(stderr, "MODEMRX %d:%d:", d.size(), m_serialdata.size()); + for(int i = 0; i < d.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)d.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void SerialModem::process_modem() +{ + QByteArray out; + if(m_serialdata.size() < 3){ + return; + } + //qDebug() << "process_modem() " << (uint8_t)m_serialdata[0] << ":" << (uint8_t)m_serialdata[1] << ":" << m_serialdata.size(); + if(((uint8_t)m_serialdata[0] == MMDVM_FRAME_START) && (m_serialdata.size() >= m_serialdata[1])){ + const uint8_t r = (uint8_t)m_serialdata[2]; + const uint8_t s = (uint8_t)m_serialdata[1]; + + if(m_serialdata.size() >= m_serialdata[1]){ + for(int i = 0; i < s; ++i){ + out.append(m_serialdata.dequeue()); + } + emit modem_data_ready(out); + } + + if(r == MMDVM_GET_VERSION){ + QThread::msleep(100); + set_freq(); + QThread::msleep(100); + set_config(); + } + } +} + +void SerialModem::set_freq() +{ + QByteArray out; + uint32_t pfreq = 433000000U; + qDebug() << "set_freq() rx:tx == " << m_rxfreq << ":" << m_txfreq; + out.clear(); + out.append(MMDVM_FRAME_START); + out.append(17); + out.append(MMDVM_SET_FREQ); + out.append('\00'); + out.append((m_rxfreq >> 0) & 0xFFU); + out.append((m_rxfreq >> 8) & 0xFFU); + out.append((m_rxfreq >> 16) & 0xFFU); + out.append((m_rxfreq >> 24) & 0xFFU); + out.append((m_txfreq >> 0) & 0xFFU); + out.append((m_txfreq >> 8) & 0xFFU); + out.append((m_txfreq >> 16) & 0xFFU); + out.append((m_txfreq >> 24) & 0xFFU); + out.append((unsigned char)(m_rfLevel * 2.55F + 0.5F)); + out.append((pfreq >> 0) & 0xFFU); + out.append((pfreq >> 8) & 0xFFU); + out.append((pfreq >> 16) & 0xFFU); + out.append((pfreq >> 24) & 0xFFU); + m_serial->write(out); +#ifdef DEBUGHW + fprintf(stderr, "MODEMTX %d:%d:", out.size(), m_serialdata.size()); + for(int i = 0; i < out.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void SerialModem::set_config() +{ + QByteArray out; + uint8_t c; + + out.clear(); + + out.append(MMDVM_FRAME_START); + out.append(24U); + out.append(MMDVM_SET_CONFIG); + + c = 0x00U; + if (m_rxInvert) + c |= 0x01U; + if (m_txInvert) + c |= 0x02U; + if (m_pttInvert) + c |= 0x04U; + if (m_ysfLoDev) + c |= 0x08U; + if (m_debug) + c |= 0x10U; + if (m_useCOSAsLockout) + c |= 0x20U; + if (!m_duplex) + c |= 0x80U; + + out.append(c); + + c = 0x00U; + if (m_dstarEnabled) + c |= 0x01U; + if (m_dmrEnabled) + c |= 0x02U; + if (m_ysfEnabled) + c |= 0x04U; + if (m_p25Enabled) + c |= 0x08U; + if (m_nxdnEnabled) + c |= 0x10U; + if (m_pocsagEnabled) + c |= 0x20U; + + out.append(c); + + out.append(m_txDelay / 10U); // In 10ms units + out.append(MODE_IDLE); + out.append((unsigned char)(m_rxLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_cwIdTXLevel * 2.55F + 0.5F)); + out.append(m_dmrColorCode); + out.append(m_dmrDelay); + out.append(128U); // Was OscOffset + out.append((unsigned char)(m_dstarTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_dmrTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_ysfTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_p25TXLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_txDCOffset + 128)); + out.append((unsigned char)(m_rxDCOffset + 128)); + out.append((unsigned char)(m_nxdnTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)m_ysfTXHang); + out.append((unsigned char)(m_pocsagTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)(m_fmTXLevel * 2.55F + 0.5F)); + out.append((unsigned char)m_p25TXHang); + out.append((unsigned char)m_nxdnTXHang); + m_serial->write(out); +#ifdef DEBUGHW + fprintf(stderr, "MODEMTX %d:%d:", out.size(), m_serialdata.size()); + for(int i = 0; i < out.size(); ++i){ + //if((d.data()[i] == 0x61) && (data.data()[i+1] == 0x01) && (data.data()[i+2] == 0x42) && (data.data()[i+3] == 0x02)){ + // i+= 6; + //} + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void SerialModem::set_mode(uint8_t m) +{ + QByteArray out; + out.clear(); + out.append(MMDVM_FRAME_START); + out.append(4); + out.append(MMDVM_SET_MODE); + out.append(m); + m_serial->write(out); +} + +void SerialModem::write(QByteArray b) +{ + m_serial->write(b); +#ifdef DEBUGHW + fprintf(stderr, "MODEMTX %d:%d:", b.size(), m_serialdata.size()); + for(int i = 0; i < b.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)b.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} diff --git a/serialmodem.h b/serialmodem.h new file mode 100644 index 0000000..2e42f6e --- /dev/null +++ b/serialmodem.h @@ -0,0 +1,96 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef SERIALMODEM_H +#define SERIALMODEM_H + +#include +#include +#ifdef Q_OS_ANDROID +#include "androidserialport.h" +#endif +#include +#include + +class SerialModem : public QObject +{ + Q_OBJECT +public: + SerialModem(QString); + ~SerialModem(); + void set_mode(QString); + void set_modem_flags(bool, bool, bool, bool, bool); + void set_modem_params(uint32_t, uint32_t, uint32_t, float, float, uint32_t, float, float, float, float, float, float, float); + static QMap discover_devices(); + void connect_to_serial(QString); + void write(QByteArray); +private slots: + void process_serial(); + void receive_serial(QByteArray); + void process_modem(); + void set_freq(); + void set_config(); + void set_mode(uint8_t); +private: +#ifndef Q_OS_ANDROID + QSerialPort *m_serial; +#else + AndroidSerialPort *m_serial; +#endif + QTimer *m_modemtimer; + uint8_t packet_size; + QQueue m_serialdata; + uint32_t m_rxfreq; + uint32_t m_txfreq; + uint32_t m_dmrColorCode; + bool m_ysfLoDev; + uint32_t m_ysfTXHang; + uint32_t m_p25TXHang; + uint32_t m_nxdnTXHang; + bool m_duplex; + bool m_rxInvert; + bool m_txInvert; + bool m_pttInvert; + uint32_t m_txDelay; + uint32_t m_dmrDelay; + float m_rxLevel; + float m_rfLevel; + float m_cwIdTXLevel; + float m_dstarTXLevel; + float m_dmrTXLevel; + float m_ysfTXLevel; + float m_p25TXLevel; + float m_nxdnTXLevel; + float m_pocsagTXLevel; + float m_fmTXLevel; + bool m_debug; + bool m_useCOSAsLockout; + bool m_dstarEnabled; + bool m_dmrEnabled; + bool m_ysfEnabled; + bool m_p25Enabled; + bool m_nxdnEnabled; + bool m_pocsagEnabled; + bool m_fmEnabled; + int m_rxDCOffset; + int m_txDCOffset; +signals: + void data_ready(); + void modem_data_ready(QByteArray); +}; + +#endif // SERIALMODEM_H diff --git a/vocoder_plugin.h b/vocoder_plugin.h new file mode 100644 index 0000000..d5b550b --- /dev/null +++ b/vocoder_plugin.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef VOCODER_PLUGIN_H +#define VOCODER_PLUGIN_H + +#include + +class Vocoder +{ +public: + Vocoder() {} + virtual ~Vocoder() {} + virtual void decode_2400x1200(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 72 bits as codec, returns 160 S_16_BE PCM frames as pcm + virtual void decode_2450x1150(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 72 bits as codec, returns 160 S_16_BE PCM frames as pcm + virtual void decode_2450(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 49 bits as codec, returns 160 S_16_BE PCM frames as pcm + virtual void encode_2400x1200(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 160 S_16_BE PCM frames as pcm, returns pointer to 72 bits as codec + virtual void encode_2450x1150(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 160 S_16_BE PCM frames as pcm, returns pointer to 72 bits as codec + virtual void encode_2450(int16_t *pcm, uint8_t *codec) = 0; // Provide pointer to 160 S_16_BE PCM frames as pcm, returns pointer to 49 bits as codec +}; + +typedef Vocoder* create_t(); +typedef void destry_t(Vocoder *); + +#endif // VOCODER_PLUGIN_H diff --git a/xrfcodec.cpp b/xrfcodec.cpp new file mode 100755 index 0000000..65a54e3 --- /dev/null +++ b/xrfcodec.cpp @@ -0,0 +1,635 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include +#include +#include "xrfcodec.h" +#include "CRCenc.h" + +#define DEBUG + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +XRFCodec::XRFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, module, hostname, host, port, ipv6, vocoder, modem, audioin, audioout) +{ +} + +XRFCodec::~XRFCodec() +{ +} + +void XRFCodec::process_udp() +{ + QByteArray buf; + QHostAddress sender; + quint16 senderPort; + static bool sd_sync = 0; + static int sd_seq = 0; + static char user_data[21]; + + buf.resize(m_udp->pendingDatagramSize()); + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if(buf.size() == 9){ + m_modeinfo.count++; + if( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + } + + if( (m_modeinfo.status == CONNECTING) && (buf.size() == 14) && (!memcmp(buf.data()+10, "ACK", 3)) ){ + m_modeinfo.status = CONNECTED_RW; + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("XRF"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + if(m_modemport != ""){ + m_modem = new SerialModem("XRF"); + m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex); + m_modem->set_modem_params(m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel); + m_modem->connect_to_serial(m_modemport); + connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray))); + } + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + m_ping_timer->start(3000); + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + } + + if((buf.size() == 56) && (!memcmp(buf.data(), "DSVT", 4)) ){ + uint16_t streamid = (buf.data()[12] << 8) | (buf.data()[13] & 0xff); + if( (m_modeinfo.streamid != 0) && (streamid != m_modeinfo.streamid) ){ + qDebug() << "New header received before timeout"; + m_modeinfo.streamid = 0; + m_audio->stop_playback(); + } + if(!m_tx && (m_modeinfo.streamid == 0)){ + char temp[9]; + memcpy(temp, buf.data() + 18, 8); temp[8] = '\0'; + m_modeinfo.gw2 = QString(temp); + memcpy(temp, buf.data() + 26, 8); temp[8] = '\0'; + m_modeinfo.gw = QString(temp); + memcpy(temp, buf.data() + 34, 8); temp[8] = '\0'; + m_modeinfo.dst = QString(temp); + memcpy(temp, buf.data() + 42, 8); temp[8] = '\0'; + m_modeinfo.src = QString(temp); + QString h = m_hostname + " " + m_module; + m_modeinfo.streamid = streamid; + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + + if(m_modem){ + uint8_t out[44]; + out[0] = 0xe0; + out[1] = 44; + out[2] = MMDVM_DSTAR_HEADER; + out[3] = 0x40; + out[4] = 0; + out[5] = 0; + memcpy(out + 6, m_modeinfo.gw2.toLocal8Bit().data(), 8); + memcpy(out + 14, m_modeinfo.gw.toLocal8Bit().data(), 8); + memcpy(out + 22, m_modeinfo.dst.toLocal8Bit().data(), 8); + memcpy(out + 30, m_modeinfo.src.toLocal8Bit().data(), 8); + memcpy(out + 38, buf.data() + 50, 4); + CCRC::addCCITT161((uint8_t *)out + 3, 41); + for(int i = 0; i < 44; ++i){ + m_rxmodemq.append(out[i]); + } + //m_modem->write(out); + } + + qDebug() << "New stream from " << m_modeinfo.src << " to " << m_modeinfo.dst << " id == " << QString::number(m_modeinfo.streamid, 16); + emit update(m_modeinfo); + } + m_rxwatchdog = 0; + } + + if((buf.size() == 27) && (!memcmp(buf.data(), "DSVT", 4))) { + m_rxwatchdog = 0; + uint16_t streamid = (buf.data()[12] << 8) | (buf.data()[13] & 0xff); + if( (streamid != m_modeinfo.streamid) ){ + qDebug() << "New data packet received before timeout"; + m_modeinfo.streamid = streamid; + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + } + if(!m_tx && ( (m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_IDLE) )){ + if(!m_rxtimer->isActive()){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + m_modeinfo.stream_state = STREAM_NEW; + } + else{ + m_modeinfo.stream_state = STREAMING; + } + m_modeinfo.streamid = streamid; + m_modeinfo.frame_number = buf.data()[14]; + + if(m_modeinfo.frame_number & 0x40){ + qDebug() << "XRF RX stream ended "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(3); + m_rxmodemq.append(MMDVM_DSTAR_EOT); + } + } + else if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(15); + m_rxmodemq.append(MMDVM_DSTAR_DATA); + for(int i = 0; i < 12; ++i){ + m_rxmodemq.append(buf.data()[15+i]); + } + } + + if((buf.data()[14] == 0) && (buf.data()[24] == 0x55) && (buf.data()[25] == 0x2d) && (buf.data()[26] == 0x16)){ + sd_sync = 1; + sd_seq = 1; + } + if(sd_sync && (sd_seq == 1) && (buf.data()[14] == 1) && (buf.data()[24] == 0x30)){ + user_data[0] = buf.data()[25] ^ 0x4f; + user_data[1] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 2) && (buf.data()[14] == 2)){ + user_data[2] = buf.data()[24] ^ 0x70; + user_data[3] = buf.data()[25] ^ 0x4f; + user_data[4] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 3) && (buf.data()[14] == 3) && (buf.data()[24] == 0x31)){ + user_data[5] = buf.data()[25] ^ 0x4f; + user_data[6] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 4) && (buf.data()[14] == 4)){ + user_data[7] = buf.data()[24] ^ 0x70; + user_data[8] = buf.data()[25] ^ 0x4f; + user_data[9] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 5) && (buf.data()[14] == 5) && (buf.data()[24] == 0x32)){ + user_data[10] = buf.data()[25] ^ 0x4f; + user_data[11] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 6) && (buf.data()[14] == 6)){ + user_data[12] = buf.data()[24] ^ 0x70; + user_data[13] = buf.data()[25] ^ 0x4f; + user_data[14] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 7) && (buf.data()[14] == 7) && (buf.data()[24] == 0x33)){ + user_data[15] = buf.data()[25] ^ 0x4f; + user_data[16] = buf.data()[26] ^ 0x93; + ++sd_seq; + } + if(sd_sync && (sd_seq == 8) && (buf.data()[14] == 8)){ + user_data[17] = buf.data()[24] ^ 0x70; + user_data[18] = buf.data()[25] ^ 0x4f; + user_data[19] = buf.data()[26] ^ 0x93; + user_data[20] = '\0'; + sd_sync = 0; + sd_seq = 0; + m_modeinfo.usertxt = QString(user_data); + } + for(int i = 0; i < 9; ++i){ + m_rxcodecq.append(buf.data()[15+i]); + } + } + emit update(m_modeinfo); +} + +void XRFCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + out.append(m_modeinfo.callsign.toUtf8()); + out.append(8 - m_modeinfo.callsign.size(), ' '); + out.append(m_module); + out.append(m_module); + out.append(11); + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void XRFCodec::send_ping() +{ + QByteArray out; + out.append(m_modeinfo.callsign.toUtf8()); + out.append(8 - m_modeinfo.callsign.size(), ' '); + out.append('\x00'); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void XRFCodec::send_disconnect() +{ + QByteArray out; + out.append(m_modeinfo.callsign.toUtf8()); + out.append(8 - m_modeinfo.callsign.size(), ' '); + out.append(m_module); + out.append(' '); + out.append('\x00'); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void XRFCodec::format_callsign(QString &s) +{ + QStringList l = s.simplified().split(' '); + + if(l.size() > 1){ + s = l.at(0).simplified(); + while(s.size() < 7){ + s.append(' '); + } + s += l.at(1).simplified(); + } + else{ + while(s.size() < 8){ + s.append(' '); + } + } +} + +void XRFCodec::process_modem_data(QByteArray d) +{ + QByteArray txdata; + char cs[9]; + uint8_t ambe[9]; + + uint8_t *p_frame = (uint8_t *)(d.data()); + if(p_frame[2] == MMDVM_DSTAR_HEADER){ + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + cs[8] = 0; + memcpy(cs, p_frame + 22, 8); + m_txurcall = QString(cs); + memcpy(cs, p_frame + 30, 8); + m_txmycall = QString(cs); + m_modeinfo.stream_state = TRANSMITTING_MODEM; + m_tx = true; + } + else if( (p_frame[2] == MMDVM_DSTAR_EOT) || (p_frame[2] == MMDVM_DSTAR_LOST) ){ + m_tx = false; + } + else if(p_frame[2] == MMDVM_DSTAR_DATA){ + memcpy(ambe, p_frame + 3, 9); + } + send_frame(ambe); +} + +void XRFCodec::toggle_tx(bool tx) +{ + tx ? start_tx() : stop_tx(); +} + +void XRFCodec::start_tx() +{ + format_callsign(m_txmycall); + format_callsign(m_txurcall); + format_callsign(m_txrptr1); + format_callsign(m_txrptr2); + Codec::start_tx(); +} + +void XRFCodec::transmit() +{ + unsigned char ambe[9]; + uint8_t ambe_frame[72]; + int16_t pcm[160]; + memset(ambe_frame, 0, 72); + memset(ambe, 0, 9); + +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + if(m_hwtx){ + m_ambedev->encode(pcm); + if(m_tx && (m_txcodecq.size() >= 9)){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_txcodecq.dequeue(); + } + send_frame(ambe); + } + else if(!m_tx){ + send_frame(ambe); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2400x1200(pcm, ambe); + } + send_frame(ambe); + } +} + +void XRFCodec::send_frame(uint8_t *ambe) +{ + QByteArray txdata; + static uint16_t txstreamid = 0; + static bool sendheader = 1; + + if(txstreamid == 0){ + txstreamid = static_cast((::rand() & 0xFFFF)); + } + + if(sendheader){ + sendheader = 0; + txdata.resize(56); + txdata[0] = 0x44; + txdata[1] = 0x53; + txdata[2] = 0x56; + txdata[3] = 0x54; + txdata[4] = 0x10; + txdata[5] = 0x00; + txdata[6] = 0x00; + txdata[7] = 0x00; + txdata[8] = 0x20; + txdata[9] = 0x00; + txdata[10] = 0x01; + txdata[11] = 0x02; + txdata[12] = txstreamid & 0xff; + txdata[13] = (txstreamid >> 8) & 0xff; + txdata[14] = 0x80; + txdata[15] = 0x00; + txdata[16] = 0x00; + txdata[17] = 0x00; + txdata.replace(18, 8, m_txrptr2.toLocal8Bit().data()); + txdata.replace(26, 8, m_txrptr1.toLocal8Bit().data()); + txdata.replace(34, 8, m_txurcall.toLocal8Bit().data()); + txdata.replace(42, 8, m_txmycall.toLocal8Bit().data()); + txdata.replace(50, 4, "AMBE"); + CCRC::addCCITT161((uint8_t *)txdata.data() + 15, 41); + + m_modeinfo.src = m_txmycall; + m_modeinfo.dst = m_txurcall; + m_modeinfo.gw = m_txrptr1; + m_modeinfo.gw2 = m_txrptr2; + m_modeinfo.streamid = txstreamid; + m_modeinfo.frame_number = m_txcnt; + } + else{ + txdata.resize(27); + txdata[0] = 0x44; + txdata[1] = 0x53; + txdata[2] = 0x56; + txdata[3] = 0x54; + txdata[4] = 0x20; + txdata[5] = 0x00; + txdata[6] = 0x00; + txdata[7] = 0x00; + txdata[8] = 0x20; + txdata[9] = 0x00; + txdata[10] = 0x01; + txdata[11] = 0x02; + txdata[12] = txstreamid & 0xff; + txdata[13] = (txstreamid >> 8) & 0xff; + txdata[14] = m_txcnt % 21; + memcpy(txdata.data() + 15, ambe, 9); + + switch(txdata.data()[14]){ + case 0: + txdata[24] = 0x55; + txdata[25] = 0x2d; + txdata[26] = 0x16; + break; + case 1: + txdata[24] = 0x40 ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[0] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[1] ^ 0x93; + break; + case 2: + txdata[24] = m_txusrtxt.toLocal8Bit().data()[2] ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[3] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[4] ^ 0x93; + break; + case 3: + txdata[24] = 0x41 ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[5] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[6] ^ 0x93; + break; + case 4: + txdata[24] = m_txusrtxt.toLocal8Bit().data()[7] ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[8] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[9] ^ 0x93; + break; + case 5: + txdata[24] = 0x42 ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[10] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[11] ^ 0x93; + break; + case 6: + txdata[24] = m_txusrtxt.toLocal8Bit().data()[12] ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[13] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[14] ^ 0x93; + break; + case 7: + txdata[24] = 0x43 ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[15] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[16] ^ 0x93; + break; + case 8: + txdata[24] = m_txusrtxt.toLocal8Bit().data()[17] ^ 0x70; + txdata[25] = m_txusrtxt.toLocal8Bit().data()[18] ^ 0x4f; + txdata[26] = m_txusrtxt.toLocal8Bit().data()[19] ^ 0x93; + break; + default: + txdata[24] = 0x16; + txdata[25] = 0x29; + txdata[26] = 0xf5; + break; + } + } + + if(m_tx){ + m_txcnt++; + } + else{ + txdata[14] = (++m_txcnt % 21) | 0x40; + m_txcnt = 0; + txstreamid = 0; + m_modeinfo.streamid = 0; + sendheader = 1; + m_txtimer->stop(); + + if((m_ttsid == 0) && (m_modeinfo.stream_state == TRANSMITTING) ){ + m_audio->stop_capture(); + } + + m_ttscnt = 0; + m_modeinfo.stream_state = STREAM_IDLE; + } + + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + emit update_output_level(m_audio->level()); + update(m_modeinfo); +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (unsigned char)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void XRFCodec::get_ambe() +{ + uint8_t ambe[9]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 9; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void XRFCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[9]; + + if(m_rxwatchdog++ > 100){ + qDebug() << "XRF RX stream timeout "; + m_rxwatchdog = 0; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + m_modeinfo.streamid = 0; + } + + if(m_rxmodemq.size() > 2){ + QByteArray out; + int s = m_rxmodemq[1]; + if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){ + for(int i = 0; i < s; ++i){ + out.append(m_rxmodemq.dequeue()); + } + m_modem->write(out); + } + } + + if((!m_tx) && (m_rxcodecq.size() > 8) ){ + for(int i = 0; i < 9; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2400x1200(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "XRF playback stopped"; + return; + } +} diff --git a/xrfcodec.h b/xrfcodec.h new file mode 100755 index 0000000..d34826f --- /dev/null +++ b/xrfcodec.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef XRFCODEC_H +#define XRFCODEC_H + +#include "codec.h" + +class XRFCodec : public Codec +{ + Q_OBJECT +public: + XRFCodec(QString callsign, QString hostname, char module, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~XRFCodec(); + unsigned char * get_frame(unsigned char *ambe); +private: + QString m_txusrtxt; + uint8_t packet_size; +private slots: + void toggle_tx(bool); + void start_tx(); + void format_callsign(QString &); + void process_udp(); + void process_rx_data(); + void process_modem_data(QByteArray d); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void hostname_lookup(QHostInfo i); + void input_src_changed(int id, QString t) { m_ttsid = id; m_ttstext = t; } + void usrtxt_changed(QString t) { m_txusrtxt = t; } + void send_frame(uint8_t *); +}; + +#endif // XRFCODEC_H diff --git a/ysfcodec.cpp b/ysfcodec.cpp new file mode 100755 index 0000000..8a0a493 --- /dev/null +++ b/ysfcodec.cpp @@ -0,0 +1,1350 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#include "ysfcodec.h" +#include "YSFConvolution.h" +#include "CRCenc.h" +#include "Golay24128.h" +#include "chamming.h" +#include +#include + +//#define DEBUG + +const unsigned int IMBE_INTERLEAVE[] = { + 0, 7, 12, 19, 24, 31, 36, 43, 48, 55, 60, 67, 72, 79, 84, 91, 96, 103, 108, 115, 120, 127, 132, 139, + 1, 6, 13, 18, 25, 30, 37, 42, 49, 54, 61, 66, 73, 78, 85, 90, 97, 102, 109, 114, 121, 126, 133, 138, + 2, 9, 14, 21, 26, 33, 38, 45, 50, 57, 62, 69, 74, 81, 86, 93, 98, 105, 110, 117, 122, 129, 134, 141, + 3, 8, 15, 20, 27, 32, 39, 44, 51, 56, 63, 68, 75, 80, 87, 92, 99, 104, 111, 116, 123, 128, 135, 140, + 4, 11, 16, 23, 28, 35, 40, 47, 52, 59, 64, 71, 76, 83, 88, 95, 100, 107, 112, 119, 124, 131, 136, 143, + 5, 10, 17, 22, 29, 34, 41, 46, 53, 58, 65, 70, 77, 82, 89, 94, 101, 106, 113, 118, 125, 130, 137, 142 +}; + +const int dvsi_interleave[49] = { + 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 41, 43, 45, 47, + 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 42, 44, 46, 48, + 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38 +}; + +const unsigned int INTERLEAVE_TABLE_5_20[] = { + 0U, 40U, 80U, 120U, 160U, + 2U, 42U, 82U, 122U, 162U, + 4U, 44U, 84U, 124U, 164U, + 6U, 46U, 86U, 126U, 166U, + 8U, 48U, 88U, 128U, 168U, + 10U, 50U, 90U, 130U, 170U, + 12U, 52U, 92U, 132U, 172U, + 14U, 54U, 94U, 134U, 174U, + 16U, 56U, 96U, 136U, 176U, + 18U, 58U, 98U, 138U, 178U, + 20U, 60U, 100U, 140U, 180U, + 22U, 62U, 102U, 142U, 182U, + 24U, 64U, 104U, 144U, 184U, + 26U, 66U, 106U, 146U, 186U, + 28U, 68U, 108U, 148U, 188U, + 30U, 70U, 110U, 150U, 190U, + 32U, 72U, 112U, 152U, 192U, + 34U, 74U, 114U, 154U, 194U, + 36U, 76U, 116U, 156U, 196U, + 38U, 78U, 118U, 158U, 198U}; + +const unsigned int INTERLEAVE_TABLE_9_20[] = { + 0U, 40U, 80U, 120U, 160U, 200U, 240U, 280U, 320U, + 2U, 42U, 82U, 122U, 162U, 202U, 242U, 282U, 322U, + 4U, 44U, 84U, 124U, 164U, 204U, 244U, 284U, 324U, + 6U, 46U, 86U, 126U, 166U, 206U, 246U, 286U, 326U, + 8U, 48U, 88U, 128U, 168U, 208U, 248U, 288U, 328U, + 10U, 50U, 90U, 130U, 170U, 210U, 250U, 290U, 330U, + 12U, 52U, 92U, 132U, 172U, 212U, 252U, 292U, 332U, + 14U, 54U, 94U, 134U, 174U, 214U, 254U, 294U, 334U, + 16U, 56U, 96U, 136U, 176U, 216U, 256U, 296U, 336U, + 18U, 58U, 98U, 138U, 178U, 218U, 258U, 298U, 338U, + 20U, 60U, 100U, 140U, 180U, 220U, 260U, 300U, 340U, + 22U, 62U, 102U, 142U, 182U, 222U, 262U, 302U, 342U, + 24U, 64U, 104U, 144U, 184U, 224U, 264U, 304U, 344U, + 26U, 66U, 106U, 146U, 186U, 226U, 266U, 306U, 346U, + 28U, 68U, 108U, 148U, 188U, 228U, 268U, 308U, 348U, + 30U, 70U, 110U, 150U, 190U, 230U, 270U, 310U, 350U, + 32U, 72U, 112U, 152U, 192U, 232U, 272U, 312U, 352U, + 34U, 74U, 114U, 154U, 194U, 234U, 274U, 314U, 354U, + 36U, 76U, 116U, 156U, 196U, 236U, 276U, 316U, 356U, + 38U, 78U, 118U, 158U, 198U, 238U, 278U, 318U, 358U}; + +const unsigned int INTERLEAVE_TABLE_26_4[] = { + 0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U, 32U, 36U, 40U, 44U, 48U, 52U, 56U, 60U, 64U, 68U, 72U, 76U, 80U, 84U, 88U, 92U, 96U, 100U, + 1U, 5U, 9U, 13U, 17U, 21U, 25U, 29U, 33U, 37U, 41U, 45U, 49U, 53U, 57U, 61U, 65U, 69U, 73U, 77U, 81U, 85U, 89U, 93U, 97U, 101U, + 2U, 6U, 10U, 14U, 18U, 22U, 26U, 30U, 34U, 38U, 42U, 46U, 50U, 54U, 58U, 62U, 66U, 70U, 74U, 78U, 82U, 86U, 90U, 94U, 98U, 102U, + 3U, 7U, 11U, 15U, 19U, 23U, 27U, 31U, 35U, 39U, 43U, 47U, 51U, 55U, 59U, 63U, 67U, 71U, 75U, 79U, 83U, 87U, 91U, 95U, 99U, 103U}; + +const unsigned char WHITENING_DATA[] = {0x93U, 0xD7U, 0x51U, 0x21U, 0x9CU, 0x2FU, 0x6CU, 0xD0U, 0xEFU, 0x0FU, + 0xF8U, 0x3DU, 0xF1U, 0x73U, 0x20U, 0x94U, 0xEDU, 0x1EU, 0x7CU, 0xD8U}; + +const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +YSFCodec::YSFCodec(QString callsign, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout) : + Codec(callsign, 0, hostname, host, port, ipv6, vocoder, modem, audioin, audioout), + + m_fcs(false), + m_txfullrate(false) +{ +} + +YSFCodec::~YSFCodec() +{ +} + +void YSFCodec::process_udp() +{ + QByteArray buf; + QByteArray out; + QHostAddress sender; + quint16 senderPort; + char ysftag[11]; + buf.resize(m_udp->pendingDatagramSize()); + int p = 5000; + m_udp->readDatagram(buf.data(), buf.size(), &sender, &senderPort); +#ifdef DEBUG + fprintf(stderr, "RECV: "); + for(int i = 0; i < buf.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)buf.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + if(((buf.size() == 14) && (m_hostname.left(3) != "FCS")) || ((buf.size() == 7) && (m_hostname.left(3) == "FCS"))){ + if(m_modeinfo.status == CONNECTING){ + m_modeinfo.status = CONNECTED_RW; + m_txtimer = new QTimer(); + connect(m_txtimer, SIGNAL(timeout()), this, SLOT(transmit())); + m_ping_timer = new QTimer(); + connect(m_ping_timer, SIGNAL(timeout()), this, SLOT(send_ping())); + set_fcs_mode(false); + //m_mbeenc->set_gain_adjust(2.5); + m_modeinfo.vocoder_loaded = load_vocoder_plugin(); + m_rxtimer = new QTimer(); + connect(m_rxtimer, SIGNAL(timeout()), this, SLOT(process_rx_data())); + + if(m_vocoder != ""){ + m_hwrx = true; + m_hwtx = true; + m_ambedev = new SerialAMBE("YSF"); + m_ambedev->connect_to_serial(m_vocoder); + connect(m_ambedev, SIGNAL(data_ready()), this, SLOT(get_ambe())); + } + else{ + m_hwrx = false; + m_hwtx = false; + } + + if(m_modemport != ""){ + m_modem = new SerialModem("YSF"); + m_modem->set_modem_flags(m_rxInvert, m_txInvert, m_pttInvert, m_useCOSAsLockout, m_duplex); + m_modem->set_modem_params(m_rxfreq, m_txfreq, m_txDelay, m_rxLevel, m_rfLevel, m_ysfTXHang, m_cwIdTXLevel, m_dstarTXLevel, m_dmrTXLevel, m_ysfTXLevel, m_p25TXLevel, m_nxdnTXLevel, m_pocsagTXLevel); + m_modem->connect_to_serial(m_modemport); + connect(m_modem, SIGNAL(modem_data_ready(QByteArray)), this, SLOT(process_modem_data(QByteArray))); + } + + m_audio = new AudioEngine(m_audioin, m_audioout); + m_audio->init(); + + if(m_hostname.left(3) == "FCS"){ + char info[100U]; + ::sprintf(info, "%9u%9u%-6.6s%-12.12s%7u", 438000000, 438000000, "AA00AA", "MMDVM", 1234567); + ::memset(info + 43U, ' ', 57U); + out.append(info, 100); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); + p = 800; + set_fcs_mode(true, m_hostname.left(8).toStdString()); + } + m_ping_timer->start(p); + } + if((m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + m_modeinfo.count++; + } + if((buf.size() == 10) && (::memcmp(buf.data(), "ONLINE", 6U) == 0)){ + m_modeinfo.count++; + if((m_modeinfo.stream_state == STREAM_LOST) || (m_modeinfo.stream_state == STREAM_END) ){ + m_modeinfo.stream_state = STREAM_IDLE; + } + } + uint8_t *p_data = nullptr; + if((buf.size() == 155) && (::memcmp(buf.data(), "YSFD", 4U) == 0)){ + memcpy(ysftag, buf.data() + 4, 10);ysftag[10] = '\0'; + m_modeinfo.gw = QString(ysftag); + p_data = (uint8_t *)buf.data() + 35; + if(m_modem){ + m_rxmodemq.append(0xe0); + m_rxmodemq.append(124); + m_rxmodemq.append(0x20); + m_rxmodemq.append('\x00'); + + for(int i = 0; i < 120; ++i){ + m_rxmodemq.append(buf.data()[35+i]); + } + //m_rxmodemq.append(buf.mid(35)); + } + } + else if(buf.size() == 130){ + memcpy(ysftag, buf.data() + 0x79, 8);ysftag[8] = '\0'; + m_modeinfo.gw = QString(ysftag); + p_data = (uint8_t *)buf.data(); + } + + if(p_data != nullptr){ + m_rxwatchdog = 0; + CYSFFICH fich; + if(fich.decode(p_data)){ + m_fi = fich.getFI(); + m_modeinfo.frame_number = fich.getFN(); + m_modeinfo.frame_total = fich.getFT(); + m_modeinfo.path = fich.getVoIP(); + m_modeinfo.type = fich.getDT(); + + if(m_fi == YSF_FI_HEADER){ + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + if(!m_tx && !m_rxtimer->isActive() ){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + qDebug() << "New YSF stream from gw" << m_modeinfo.gw; + } + else if(m_fi == YSF_FI_TERMINATOR){ + m_modeinfo.stream_state = STREAM_END; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + qDebug() << "YSF stream ended" << m_modeinfo.gw; + } + else if(YSF_FI_COMMUNICATIONS){ + if( (m_modeinfo.stream_state == STREAM_END) || + (m_modeinfo.stream_state == STREAM_LOST) || + (m_modeinfo.stream_state == STREAM_IDLE)) + { + m_modeinfo.stream_state = STREAM_NEW; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + if(!m_tx && !m_rxtimer->isActive() ){ + m_audio->start_playback(); + m_rxtimer->start(m_rxtimerint); + } + qDebug() << "New YSF stream in progress from gw" << m_modeinfo.gw; + + } + else{ + m_modeinfo.stream_state = STREAMING; + } + } + } + if(m_modeinfo.type == 3){ + decode_vw(p_data); + } + else{ + decode_dn(p_data); + } + } + emit update(m_modeinfo); +} + +void YSFCodec::hostname_lookup(QHostInfo i) +{ + if (!i.addresses().isEmpty()) { + QByteArray out; + if(m_hostname.left(3) == "FCS"){ + out.append('P'); + out.append('I'); + out.append('N'); + out.append('G'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(6 - m_modeinfo.callsign.size(), ' '); + out.append(m_hostname.toUtf8()); + out.append(7, '\x00'); + } + else{ + out.append('Y'); + out.append('S'); + out.append('F'); + out.append('P'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + } + m_address = i.addresses().first(); + m_udp = new QUdpSocket(this); + connect(m_udp, SIGNAL(readyRead()), this, SLOT(process_udp())); + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "CONN: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } +} + +void YSFCodec::send_ping() +{ + QByteArray out; + if(m_hostname.left(3) == "FCS"){ + out.append('P'); + out.append('I'); + out.append('N'); + out.append('G'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(6 - m_modeinfo.callsign.size(), ' '); + out.append(m_hostname.toUtf8()); + out.append(7, '\x00'); + } + else{ + out.append('Y'); + out.append('S'); + out.append('F'); + out.append('P'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + } + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "PING: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void YSFCodec::send_disconnect() +{ + QByteArray out; + if(m_hostname.left(3) == "FCS"){ + out.append('C'); + out.append('L'); + out.append('O'); + out.append('S'); + out.append('E'); + out.append(6, ' '); + } + else{ + out.append('Y'); + out.append('S'); + out.append('F'); + out.append('U'); + out.append(m_modeinfo.callsign.toUtf8()); + out.append(10 - m_modeinfo.callsign.size(), ' '); + } + m_udp->writeDatagram(out, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND: "); + for(int i = 0; i < out.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)out.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void YSFCodec::decode_vw(uint8_t* data) +{ + uint8_t vch[18U]; + uint8_t imbe[11U]; + bool bit[144U]; + + data += YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES; + + unsigned int offset = 0U; + + // We have a total of 5 VCH sections, iterate through each + for (unsigned int j = 0U; j < 5U; j++, offset += 18U) { + ::memcpy(vch, data + offset, 18U); + + for (unsigned int i = 0U; i < 144U; i++) { + unsigned int n = IMBE_INTERLEAVE[i]; + bit[i] = READ_BIT(vch, n); + } + unsigned int c0data = 0U; + for (unsigned int i = 0U; i < 12U; i++) + c0data = (c0data << 1) | (bit[i] ? 0x01U : 0x00U); + + bool prn[114U]; + + // Create the whitening vector and save it for future use + unsigned int p = 16U * c0data; + for (unsigned int i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // De-whiten some bits + for (unsigned int i = 0U; i < 114U; i++) + bit[i + 23U] ^= prn[i]; + + unsigned int offset = 0U; + for (unsigned int i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 0U]); + for (unsigned int i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 23U]); + for (unsigned int i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 46U]); + for (unsigned int i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 69U]); + for (unsigned int i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 92U]); + for (unsigned int i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 107U]); + for (unsigned int i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 122U]); + for (unsigned int i = 0U; i < 7U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 137U]); + + for(int i = 0; i < 11; ++i){ + m_rxcodecq.append(imbe[i]); + } + } +} + +void YSFCodec::decode_vd1(uint8_t* data, uint8_t *dt) +{ + unsigned char dch[45U]; + + const unsigned char* p1 = data; + unsigned char* p2 = dch; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 9U); + p1 += 18U; p2 += 9U; + } + + CYSFConvolution conv; + conv.start(); + + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT(dch, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT(dch, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output[23U]; + conv.chainback(output, 176U); + + bool ret = CCRC::checkCCITT162(output, 22U); + if (ret) { + for (unsigned int i = 0U; i < 20U; i++){ + output[i] ^= WHITENING_DATA[i]; + } + ::memcpy(dt, output, 20U); + } +} + +void YSFCodec::decode_vd2(uint8_t* data, uint8_t *dt) +{ + unsigned char dch[25U]; + + const unsigned char* p1 = data; + unsigned char* p2 = dch; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 5U); + p1 += 18U; p2 += 5U; + } + + CYSFConvolution conv; + conv.start(); + + for (unsigned int i = 0U; i < 100U; i++) { + unsigned int n = INTERLEAVE_TABLE_5_20[i]; + uint8_t s0 = READ_BIT(dch, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT(dch, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output[13U]; + conv.chainback(output, 96U); + + bool ret = CCRC::checkCCITT162(output, 12U); + if (ret) { + for (unsigned int i = 0U; i < 10U; i++){ + output[i] ^= WHITENING_DATA[i]; + } + ::memcpy(dt, output, YSF_CALLSIGN_LENGTH); + } +} + +void YSFCodec::decode_dn(uint8_t* data) +{ + uint8_t v_tmp[7U]; + uint8_t dt[20]; + ::memset(v_tmp, 0, 7U); + + data += YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES; + switch(m_modeinfo.type){ + case 0: + decode_vd1(data, dt); + break; + case 2: + decode_vd2(data, dt); + dt[10] = 0x00; + break; + } + + switch (m_modeinfo.frame_number) { + case 0: + if(m_fi == YSF_FI_COMMUNICATIONS){ + m_modeinfo.dst = QString((char *)dt); + } + break; + case 1: + m_modeinfo.src = QString((char *)dt); + break; + } + + uint32_t offset = 40U; // DCH(0) + + // We have a total of 5 VCH sections, iterate through each + for (uint32_t j = 0U; j < 5U; j++, offset += 144U) { + + uint8_t vch[13U]; + uint32_t dat_a = 0U; + uint32_t dat_b = 0U; + uint32_t dat_c = 0U; + + // Deinterleave + for (uint32_t i = 0U; i < 104U; i++) { + uint32_t n = INTERLEAVE_TABLE_26_4[i]; + bool s = READ_BIT(data, offset + n); + WRITE_BIT(vch, i, s); + } + + // "Un-whiten" (descramble) + for (uint32_t i = 0U; i < 13U; i++) + vch[i] ^= WHITENING_DATA[i]; + + for (uint32_t i = 0U; i < 12U; i++) { + dat_a <<= 1U; + if (READ_BIT(vch, 3U*i + 1U)) + dat_a |= 0x01U;; + } + + for (uint32_t i = 0U; i < 12U; i++) { + dat_b <<= 1U; + if (READ_BIT(vch, 3U*(i + 12U) + 1U)) + dat_b |= 0x01U;; + } + + for (uint32_t i = 0U; i < 3U; i++) { + dat_c <<= 1U; + if (READ_BIT(vch, 3U*(i + 24U) + 1U)) + dat_c |= 0x01U;; + } + + for (uint32_t i = 0U; i < 22U; i++) { + dat_c <<= 1U; + if (READ_BIT(vch, i + 81U)) + dat_c |= 0x01U;; + } + + for (uint32_t i = 0U; i < 12U; i++) { + bool s1 = (dat_a << (i + 20U)) & 0x80000000; + bool s2 = (dat_b << (i + 20U)) & 0x80000000; + WRITE_BIT(v_tmp, i, s1); + WRITE_BIT(v_tmp, i + 12U, s2); + } + + for (uint32_t i = 0U; i < 25U; i++) { + bool s = (dat_c << (i + 7U)) & 0x80000000; + WRITE_BIT(v_tmp, i + 24U, s); + } + if(m_hwrx){ + interleave(v_tmp); + } + for(int i = 0; i < 7; ++i){ + m_rxcodecq.append(v_tmp[i]); + } + } +} + +void YSFCodec::interleave(uint8_t *ambe) +{ + char ambe_data[49]; + char dvsi_data[7]; + memset(dvsi_data, 0, 7); + + for(int i = 0; i < 6; ++i){ + for(int j = 0; j < 8; j++){ + ambe_data[j+(8*i)] = (1 & (ambe[i] >> (7 - j))); + } + } + ambe_data[48] = (1 & (ambe[6] >> 7)); + for(int i = 0, j; i < 49; ++i){ + j = dvsi_interleave[i]; + dvsi_data[j/8] += (ambe_data[i])<<(7-(j%8)); + } + memcpy(ambe, dvsi_data, 7); +} + +void YSFCodec::process_modem_data(QByteArray d) +{ + QByteArray txdata; + uint8_t *p_frame = (uint8_t *)(d.data()); + if(m_fcs){ + ::memset(p_frame + 120U, 0, 10U); + ::memcpy(p_frame + 121U, m_fcsname.c_str(), 8); + } + else{ + ::memcpy(m_ysfFrame + 0U, "YSFD", 4U); + ::memcpy(m_ysfFrame + 4U, m_modeinfo.callsign.toStdString().c_str(), YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 14U, m_modeinfo.callsign.toStdString().c_str(), YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 24U, "ALL ", YSF_CALLSIGN_LENGTH); + m_ysfFrame[34U] = (m_txcnt & 0x7f) << 1; + ::memcpy(m_ysfFrame + 35U, p_frame + 4U, 120); + } + ++m_txcnt; + txdata.append((char *)m_ysfFrame, 155); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + qDebug() << "Sending modem to network....................................................."; +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif +} + +void YSFCodec::transmit() +{ + uint8_t ambe_frame[88]; + uint8_t ambe[7]; + int16_t pcm[160]; + uint8_t s = 7; + + memset(ambe, 0, 7); +#ifdef USE_FLITE + if(m_ttsid > 0){ + for(int i = 0; i < 160; ++i){ + if(m_ttscnt >= tts_audio->num_samples/2){ + pcm[i] = 0; + } + else{ + pcm[i] = tts_audio->samples[m_ttscnt*2] / 2; + m_ttscnt++; + } + } + } +#endif + if(m_ttsid == 0){ + if(m_audio->read(pcm, 160)){ + } + else{ + return; + } + } + if(m_hwtx && !m_txfullrate){ + m_ambedev->encode(pcm); + } + else{ + if(m_txfullrate){ + s = 11; + vocoder.encode_4400(pcm, ambe_frame); + } + else{ + s = 7; + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->encode_2450(pcm, ambe); + } + } + + for(int i = 0; i < s; ++i){ + if(!m_txfullrate){ + m_txcodecq.append(ambe[i]); + } + else{ + m_txcodecq.append(ambe_frame[i]); + } + } + } + if(m_tx && (m_txcodecq.size() >= (s*5))){ + for(int i = 0; i < (s*5); ++i){ + m_ambe[i] = m_txcodecq.dequeue(); + } + send_frame(); + } + else if(m_tx == false){ + send_frame(); + } +} + +void YSFCodec::send_frame() +{ + QByteArray txdata; + int frame_size; + + if(m_tx){ + m_modeinfo.stream_state = TRANSMITTING; + if(!m_txcnt){ + encode_header(); + } + else{ + m_txfullrate ? encode_vw() : encode_dv2(); + } + ++m_txcnt; + frame_size = ::memcmp(m_ysfFrame, "YSFD", 4) ? 130 : 155; + txdata.append((char *)m_ysfFrame, frame_size); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); +#ifdef DEBUG + fprintf(stderr, "SEND:%d: ", txdata.size()); + for(int i = 0; i < txdata.size(); ++i){ + fprintf(stderr, "%02x ", (uint8_t)txdata.data()[i]); + } + fprintf(stderr, "\n"); + fflush(stderr); +#endif + } + else{ + fprintf(stderr, "YSF TX stopped\n"); + m_txtimer->stop(); + if(m_ttsid == 0){ + m_audio->stop_capture(); + } + encode_header(1); + m_txcnt = 0; + m_ttscnt = 0; + frame_size = ::memcmp(m_ysfFrame, "YSFD", 4) ? 130 : 155; + txdata.append((char *)m_ysfFrame, frame_size); + m_udp->writeDatagram(txdata, m_address, m_modeinfo.port); + m_modeinfo.stream_state = STREAM_IDLE; + } + emit update_output_level(m_audio->level()); + emit update(m_modeinfo); +} + +void YSFCodec::encode_header(bool eot) +{ + unsigned char callsign[12]; + ::memcpy(callsign, " ", 10); + ::memcpy(callsign, m_modeinfo.callsign.toStdString().c_str(), ::strlen(m_modeinfo.callsign.toStdString().c_str())); + + uint8_t *p_frame = m_ysfFrame; + if(m_fcs){ + ::memset(p_frame + 120U, 0, 10U); + ::memcpy(p_frame + 121U, m_fcsname.c_str(), 8); + } + else{ + ::memcpy(p_frame + 0U, "YSFD", 4U); + ::memcpy(p_frame + 4U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(p_frame + 14U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(p_frame + 24U, "ALL ", YSF_CALLSIGN_LENGTH); + + if(eot){ + p_frame[34U] = ((m_txcnt & 0x7f) << 1U) | 1U; + } + else{ + p_frame[34U] = 0U; + } + p_frame = m_ysfFrame + 35U; + } + ::memcpy(p_frame, YSF_SYNC_BYTES, 5); + + fich.setFI(eot ? YSF_FI_TERMINATOR : YSF_FI_HEADER); + fich.setCS(2U); + fich.setCM(0U); + fich.setBN(0U); + fich.setBT(0U); + fich.setFN(0U); + fich.setFT(6U); + fich.setDev(0U); + fich.setMR(0U); + fich.setVoIP(false); + fich.setDT(m_txfullrate ? YSF_DT_VOICE_FR_MODE : YSF_DT_VD_MODE2); + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(p_frame); + + uint8_t csd1[20U], csd2[20U]; + memset(csd1, '*', YSF_CALLSIGN_LENGTH); + //memset(csd1, '*', YSF_CALLSIGN_LENGTH/2); + //memcpy(csd1 + YSF_CALLSIGN_LENGTH/2, ysf_radioid, YSF_CALLSIGN_LENGTH/2); + memcpy(csd1 + YSF_CALLSIGN_LENGTH, callsign, YSF_CALLSIGN_LENGTH); + memcpy(csd2, callsign, YSF_CALLSIGN_LENGTH); + memcpy(csd2 + YSF_CALLSIGN_LENGTH, callsign, YSF_CALLSIGN_LENGTH); + //memset(csd2, ' ', YSF_CALLSIGN_LENGTH + YSF_CALLSIGN_LENGTH); + + writeDataFRModeData1(csd1, p_frame); + writeDataFRModeData2(csd2, p_frame); +} + +void YSFCodec::encode_vw() +{ + unsigned char callsign[12]; + ::memcpy(callsign, " ", 10); + ::memcpy(callsign, m_modeinfo.callsign.toStdString().c_str(), ::strlen(m_modeinfo.callsign.toStdString().c_str())); + uint8_t *p_frame = m_ysfFrame; + if(m_fcs){ + ::memset(p_frame + 120U, 0, 10U); + ::memcpy(p_frame + 121U, m_fcsname.c_str(), 8); + } + else{ + ::memcpy(m_ysfFrame + 0U, "YSFD", 4U); + ::memcpy(m_ysfFrame + 4U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 14U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 24U, "ALL ", YSF_CALLSIGN_LENGTH); + m_ysfFrame[34U] = (m_txcnt & 0x7f) << 1; + p_frame = m_ysfFrame + 35U; + } + ::memcpy(p_frame, YSF_SYNC_BYTES, 5); + uint32_t fn = (m_txcnt - 1U) % 7U; + + fich.setFI(YSF_FI_COMMUNICATIONS); + fich.setCS(2U); + fich.setCM(0U); + fich.setBN(0U); + fich.setBT(0U); + fich.setFN(fn); + fich.setFT(6U); + fich.setDev(0U); + fich.setMR(0U); + fich.setVoIP(false); + fich.setDT(YSF_DT_VOICE_FR_MODE); + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(p_frame); + + m_modeinfo.gw = m_modeinfo.callsign; + m_modeinfo.src = m_modeinfo.callsign; + m_modeinfo.dst = "ALL "; + m_modeinfo.type = YSF_DT_VOICE_FR_MODE; + m_modeinfo.path = false; + m_modeinfo.frame_number = fn; + m_modeinfo.frame_total = 6; + + p_frame += YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES; + for(int i = 0; i < 5; ++i){ + //uint8_t imbe4400[11]; + uint8_t imbe7200[18]; + + //for(int j = 0; j < 11; ++j){ + // imbe4400[j] = m_txcodecq.dequeue(); + //} + encode_imbe(imbe7200, m_ambe + (11*i)); + memcpy(p_frame + (18*i), imbe7200, 18); + } +} + +void YSFCodec::encode_imbe(unsigned char* data, const unsigned char* imbe) +{ + bool bTemp[144U]; + bool* bit = bTemp; + + // c0 + unsigned int c0 = 0U; + for (unsigned int i = 0U; i < 12U; i++) { + bool b = READ_BIT(imbe, i); + c0 = (c0 << 1) | (b ? 0x01U : 0x00U); + } + unsigned int g2 = CGolay24128::encode23127(c0); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c1 + unsigned int c1 = 0U; + for (unsigned int i = 12U; i < 24U; i++) { + bool b = READ_BIT(imbe, i); + c1 = (c1 << 1) | (b ? 0x01U : 0x00U); + } + g2 = CGolay24128::encode23127(c1); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c2 + unsigned int c2 = 0; + for (unsigned int i = 24U; i < 36U; i++) { + bool b = READ_BIT(imbe, i); + c2 = (c2 << 1) | (b ? 0x01U : 0x00U); + } + g2 = CGolay24128::encode23127(c2); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c3 + unsigned int c3 = 0U; + for (unsigned int i = 36U; i < 48U; i++) { + bool b = READ_BIT(imbe, i); + c3 = (c3 << 1) | (b ? 0x01U : 0x00U); + } + g2 = CGolay24128::encode23127(c3); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c4 + for (unsigned int i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 48U); + CHamming::encode15113_1(bit); + bit += 15U; + + // c5 + for (unsigned int i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 59U); + CHamming::encode15113_1(bit); + bit += 15U; + + // c6 + for (unsigned int i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 70U); + CHamming::encode15113_1(bit); + bit += 15U; + + // c7 + for (unsigned int i = 0U; i < 7U; i++) + bit[i] = READ_BIT(imbe, i + 81U); + + bool prn[114U]; + + // Create the whitening vector and save it for future use + unsigned int p = 16U * c0; + for (unsigned int i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // Whiten some bits + for (unsigned int i = 0U; i < 114U; i++) + bTemp[i + 23U] ^= prn[i]; + + // Interleave + for (unsigned int i = 0U; i < 144U; i++) { + unsigned int n = IMBE_INTERLEAVE[i]; + WRITE_BIT(data, n, bTemp[i]); + } +} + +void YSFCodec::encode_dv2() +{ + unsigned char callsign[12]; + ::memcpy(callsign, " ", 10); + ::memcpy(callsign, m_modeinfo.callsign.toStdString().c_str(), ::strlen(m_modeinfo.callsign.toStdString().c_str())); + uint8_t *p_frame = m_ysfFrame; + if(m_fcs){ + ::memset(p_frame + 120U, 0, 10U); + ::memcpy(p_frame + 121U, m_fcsname.c_str(), 8); + } + else{ + ::memcpy(m_ysfFrame + 0U, "YSFD", 4U); + ::memcpy(m_ysfFrame + 4U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 14U, callsign, YSF_CALLSIGN_LENGTH); + ::memcpy(m_ysfFrame + 24U, "ALL ", YSF_CALLSIGN_LENGTH); + m_ysfFrame[34U] = (m_txcnt & 0x7f) << 1; + p_frame = m_ysfFrame + 35U; + } + ::memcpy(p_frame, YSF_SYNC_BYTES, 5); + uint32_t fn = (m_txcnt - 1U) % 7U; + + fich.setFI(YSF_FI_COMMUNICATIONS); + fich.setCS(2U); + fich.setCM(0U); + fich.setBN(0U); + fich.setBT(0U); + fich.setFN(fn); + fich.setFT(6U); + fich.setDev(0U); + fich.setMR(0U); + fich.setVoIP(false); + fich.setDT(YSF_DT_VD_MODE2); + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(p_frame); + + m_modeinfo.gw = m_modeinfo.callsign; + m_modeinfo.src = m_modeinfo.callsign; + m_modeinfo.dst = "ALL "; + m_modeinfo.type = YSF_DT_VD_MODE2; + m_modeinfo.path = false; + m_modeinfo.frame_number = fn; + m_modeinfo.frame_total = 6; + + const uint8_t ft70d1[10] = {0x01, 0x22, 0x61, 0x5f, 0x2b, 0x03, 0x11, 0x00, 0x00, 0x00}; + //const uint8_t dt1_temp[] = {0x31, 0x22, 0x62, 0x5F, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00}; + const uint8_t dt2_temp[10] = {0x00, 0x00, 0x00, 0x00, 0x6C, 0x20, 0x1C, 0x20, 0x03, 0x08}; + + switch (fn) { + case 0: + //memset(dch, '*', YSF_CALLSIGN_LENGTH/2); + //memcpy(dch + YSF_CALLSIGN_LENGTH/2, ysf_radioid, YSF_CALLSIGN_LENGTH/2); + //writeVDMode2Data(m_ysfFrame + 35U, dch); //Dest + writeVDMode2Data(p_frame, (const uint8_t*)"**********"); + break; + case 1: + writeVDMode2Data(p_frame, (const uint8_t*)callsign); //Src + break; + case 2: + writeVDMode2Data(p_frame, (const uint8_t*)callsign); //D/L + break; + case 3: + writeVDMode2Data(p_frame, (const uint8_t*)callsign); //U/L + break; + case 4: + writeVDMode2Data(p_frame, (const uint8_t*)" "); //Rem1/2 + break; + case 5: + writeVDMode2Data(p_frame, (const uint8_t*)" "); //Rem3/4 + //memset(dch, ' ', YSF_CALLSIGN_LENGTH/2); + //memcpy(dch + YSF_CALLSIGN_LENGTH/2, ysf_radioid, YSF_CALLSIGN_LENGTH/2); + //writeVDMode2Data(frame, dch); // Rem3/4 + break; + case 6: + writeVDMode2Data(p_frame, ft70d1); + break; + case 7: + writeVDMode2Data(p_frame, dt2_temp); + break; + default: + writeVDMode2Data(p_frame, (const uint8_t*)" "); + } +} + +void YSFCodec::writeDataFRModeData1(const uint8_t* dt, uint8_t* data) +{ + data += YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES; + + uint8_t output[25U]; + for (uint32_t i = 0U; i < 20U; i++) + output[i] = dt[i] ^ WHITENING_DATA[i]; + + CCRC::addCCITT162(output, 22U); + output[22U] = 0x00U; + + uint8_t convolved[45U]; + + CYSFConvolution conv; + conv.encode(output, convolved, 180U); + + uint8_t bytes[45U]; + uint32_t j = 0U; + for (uint32_t i = 0U; i < 180U; i++) { + uint32_t n = INTERLEAVE_TABLE_9_20[i]; + + bool s0 = READ_BIT(convolved, j) != 0U; + j++; + + bool s1 = READ_BIT(convolved, j) != 0U; + j++; + + WRITE_BIT(bytes, n, s0); + + n++; + WRITE_BIT(bytes, n, s1); + } + + uint8_t* p1 = data; + uint8_t* p2 = bytes; + for (uint32_t i = 0U; i < 5U; i++) { + ::memcpy(p1, p2, 9U); + p1 += 18U; p2 += 9U; + } +} + +void YSFCodec::writeDataFRModeData2(const uint8_t* dt, uint8_t* data) +{ + data += YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES; + + uint8_t output[25U]; + for (uint32_t i = 0U; i < 20U; i++) + output[i] = dt[i] ^ WHITENING_DATA[i]; + + CCRC::addCCITT162(output, 22U); + output[22U] = 0x00U; + + uint8_t convolved[45U]; + + CYSFConvolution conv; + conv.encode(output, convolved, 180U); + + uint8_t bytes[45U]; + uint32_t j = 0U; + for (uint32_t i = 0U; i < 180U; i++) { + uint32_t n = INTERLEAVE_TABLE_9_20[i]; + + bool s0 = READ_BIT(convolved, j) != 0U; + j++; + + bool s1 = READ_BIT(convolved, j) != 0U; + j++; + + WRITE_BIT(bytes, n, s0); + + n++; + WRITE_BIT(bytes, n, s1); + } + + uint8_t* p1 = data + 9U; + uint8_t* p2 = bytes; + for (uint32_t i = 0U; i < 5U; i++) { + ::memcpy(p1, p2, 9U); + p1 += 18U; p2 += 9U; + } +} + +void YSFCodec::ysf_scramble(uint8_t *buf, const int len) +{ // buffer is (de)scrambled in place + static const uint8_t scramble_code[180] = { + 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, + 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, + 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, + 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, + 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, + 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + for (int i=0; i> (7-j))); + //a[((8*i)+j)+1] = (1 & (data[5-i] >> j)); + } + } +*/ + for (int i=0; i<27; i++) { + buf[0+i*3] = a[i]; + buf[1+i*3] = a[i]; + buf[2+i*3] = a[i]; + } + memcpy(buf+81, a+27, 22); + buf[103] = 0; + ysf_scramble(buf, 104); + + //uint8_t bit_result[104]; + int x=4; + int y=26; + for (int i=0; i> (7 - j))); + //ambe_bits[j+(8*ii)] = (1 & (d[ii] >> j)); + } + } + for(int k = 0; k < 49; ++k){ + di_bits[k] = ambe_bits[dvsi_interleave[k]]; + } + generate_vch_vd2(di_bits); + } + else{ + uint8_t a[56]; + uint8_t *d = &m_ambe[7*i]; + for(int k = 0; k < 7; ++k){ + for(int j = 0; j < 8; ++j){ + a[(8*k)+j] = (1 & (d[k] >> (7-j))); + //a[((8*i)+j)+1] = (1 & (data[5-i] >> j)); + } + } + generate_vch_vd2(a); + } + ::memcpy(p1+5, m_vch, 13); + p1 += 18U; p2 += 5U; + } +} + +void YSFCodec::get_ambe() +{ + uint8_t ambe[7]; + + if(m_ambedev->get_ambe(ambe)){ + for(int i = 0; i < 7; ++i){ + m_txcodecq.append(ambe[i]); + } + } +} + +void YSFCodec::process_rx_data() +{ + int16_t pcm[160]; + uint8_t ambe[7]; + uint8_t imbe[11]; + + if(m_rxwatchdog++ > 20){ + qDebug() << "YSF RX stream timeout "; + m_modeinfo.stream_state = STREAM_LOST; + m_modeinfo.ts = QDateTime::currentMSecsSinceEpoch(); + emit update(m_modeinfo); + } + + if(m_rxmodemq.size() > 2){ + QByteArray out; + int s = m_rxmodemq[1]; + if((m_rxmodemq[0] == 0xe0) && (m_rxmodemq.size() >= s)){ + for(int i = 0; i < s; ++i){ + out.append(m_rxmodemq.dequeue()); + } + m_modem->write(out); + } + } + + if(m_modeinfo.type == 3){ + if(m_rxcodecq.size() > 10){ + for(int i = 0; i < 11; ++i){ + imbe[i] = m_rxcodecq.dequeue(); + } + vocoder.decode_4400(pcm, imbe); + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + qDebug() << "YSF FR playback stopped"; + } + } + else{ + if((!m_tx) && (m_rxcodecq.size() > 6) ){ + for(int i = 0; i < 7; ++i){ + ambe[i] = m_rxcodecq.dequeue(); + } + if(m_hwrx){ + m_ambedev->decode(ambe); + + if(m_ambedev->get_audio(pcm)){ + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else{ + if(m_modeinfo.vocoder_loaded){ + m_mbevocoder->decode_2450(pcm, ambe); + } + else{ + memset(pcm, 0, 160 * sizeof(int16_t)); + } + m_audio->write(pcm, 160); + emit update_output_level(m_audio->level()); + } + } + else if ( (m_modeinfo.stream_state == STREAM_END) || (m_modeinfo.stream_state == STREAM_LOST) ){ + m_rxtimer->stop(); + m_audio->stop_playback(); + m_rxwatchdog = 0; + m_modeinfo.streamid = 0; + m_rxcodecq.clear(); + //m_ambedev->clear_queue(); + qDebug() << "YSF VD playback stopped"; + return; + } + } +} diff --git a/ysfcodec.h b/ysfcodec.h new file mode 100755 index 0000000..b6550c7 --- /dev/null +++ b/ysfcodec.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 2019-2021 Doug McLain + + 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 3 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, see . +*/ + +#ifndef YSFCODEC_H +#define YSFCODEC_H + +const unsigned int YSF_FRAME_LENGTH_BYTES = 120U; + +const unsigned char YSF_SYNC_BYTES[] = {0xD4U, 0x71U, 0xC9U, 0x63U, 0x4DU}; +const unsigned int YSF_SYNC_LENGTH_BYTES = 5U; + +const unsigned int YSF_FICH_LENGTH_BYTES = 25U; + +const unsigned char YSF_SYNC_OK = 0x01U; + +const unsigned int YSF_CALLSIGN_LENGTH = 10U; + +const unsigned char YSF_FI_HEADER = 0x00U; +const unsigned char YSF_FI_COMMUNICATIONS = 0x01U; +const unsigned char YSF_FI_TERMINATOR = 0x02U; +const unsigned char YSF_FI_TEST = 0x03U; + +const unsigned char YSF_DT_VD_MODE1 = 0x00U; +const unsigned char YSF_DT_DATA_FR_MODE = 0x01U; +const unsigned char YSF_DT_VD_MODE2 = 0x02U; +const unsigned char YSF_DT_VOICE_FR_MODE = 0x03U; + +const unsigned char YSF_CM_GROUP1 = 0x00U; +const unsigned char YSF_CM_GROUP2 = 0x01U; +const unsigned char YSF_CM_INDIVIDUAL = 0x03U; + +const unsigned char YSF_MR_DIRECT = 0x00U; +const unsigned char YSF_MR_NOT_BUSY = 0x01U; +const unsigned char YSF_MR_BUSY = 0x02U; + +#include +#include "codec.h" +#include "YSFFICH.h" + +class YSFCodec : public Codec +{ + Q_OBJECT +public: + YSFCodec(QString callsign, QString hostname, QString host, int port, bool ipv6, QString vocoder, QString modem, QString audioin, QString audioout); + ~YSFCodec(); + void set_fcs_mode(bool y, std::string f = " "){ m_fcs = y; m_fcsname = f; } +private slots: + void process_udp(); + void process_rx_data(); + void get_ambe(); + void send_ping(); + void send_disconnect(); + void transmit(); + void hostname_lookup(QHostInfo i); + void send_frame(); + void rate_changed(int r) { m_txfullrate = r;} + void process_modem_data(QByteArray); +private: + void decode_dn(uint8_t* data); + void decode_vw(uint8_t* data); + void encode_header(bool eot = 0); + void encode_vw(); + void encode_imbe(unsigned char* data, const unsigned char* imbe); + void encode_dv2(); + void decode_vd2(uint8_t* data, uint8_t *dt); + void decode_vd1(uint8_t* data, uint8_t *dt); + void generate_vch_vd2(const unsigned char*); + void ysf_scramble(unsigned char *buf, const int len); + void writeDataFRModeData1(const unsigned char* dt, unsigned char* data); + void writeDataFRModeData2(const unsigned char* dt, unsigned char* data); + void writeVDMode2Data(unsigned char* data, const unsigned char* dt); + void interleave(uint8_t *ambe); + + uint8_t m_fi; + uint8_t packet_size; + unsigned char gateway[12]; + unsigned char m_ysfFrame[200]; + unsigned char m_vch[13U]; + unsigned char m_ambe[55]; + //unsigned char m_imbe[55]; + CYSFFICH fich; + unsigned char ambe_fr[4][24]; + unsigned int ambe_a; + unsigned int ambe_b; + unsigned int ambe_c; + bool m_fcs; + std::string m_fcsname; + bool m_txfullrate; +}; + +#endif