├── .gitignore ├── Arduino └── RepeaterControl │ └── RepeaterControl.ino ├── ArduinoController.cpp ├── ArduinoController.h ├── AudioCallback.h ├── AudioDelay.cpp ├── AudioDelay.h ├── CHANGES ├── COPYING ├── CWKeyer.cpp ├── CWKeyer.h ├── Config.cpp ├── Config.h ├── DummyController.cpp ├── DummyController.h ├── ExternalController.cpp ├── ExternalController.h ├── FIRFilter.cpp ├── FIRFilter.h ├── FMDefines.h ├── FMRepeater.cpp ├── FMRepeater.h ├── FMRepeater.ini ├── FMRepeater.sln ├── FMRepeater.vcxproj ├── FMRepeater.vcxproj.filters ├── FMRepeaterThread.cpp ├── FMRepeaterThread.h ├── Goertzel.cpp ├── Goertzel.h ├── HardwareController.cpp ├── HardwareController.h ├── Makefile ├── NCO.cpp ├── NCO.h ├── PTTDelay.cpp ├── PTTDelay.h ├── README.md ├── RingBuffer.h ├── SerialDataController.cpp ├── SerialDataController.h ├── SoundCardReaderWriter.cpp ├── SoundCardReaderWriter.h ├── StopWatch.cpp ├── StopWatch.h ├── Thread.cpp ├── Thread.h ├── TimeoutTones.cpp ├── TimeoutTones.h ├── Timer.cpp ├── Timer.h ├── UDRCController.cpp ├── UDRCController.h ├── Utils.h └── Version.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.bak 3 | *~ 4 | Debug 5 | Release 6 | x64 7 | FMRepeater 8 | *.vcxproj.user 9 | .vscode 10 | wiringPi.h 11 | -------------------------------------------------------------------------------- /Arduino/RepeaterControl/RepeaterControl.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | 20 | // 0 and 1 are used for serial 21 | const int RFTRANSMIT = 2; 22 | const int EXTTRANSMIT = 3; 23 | const int HEARTBEAT1 = 4; 24 | const int HEARTBEAT2 = 13; 25 | const int ACTIVE = 5; 26 | const int OUTPUT1 = 6; 27 | const int OUTPUT2 = 7; 28 | const int OUTPUT3 = 8; 29 | const int OUTPUT4 = 9; 30 | 31 | 32 | const int RFSQUELCH1 = A0; 33 | const int RFSQUELCH2 = A1; 34 | const int EXTSQUELCH = A2; 35 | const int BATTERY = A3; 36 | const int DISABLE = A4; 37 | 38 | 39 | void setup() { 40 | // Set up the outut pins with pull ups 41 | pinMode(RFSQUELCH1, INPUT); 42 | pinMode(RFSQUELCH2, INPUT); 43 | pinMode(EXTSQUELCH, INPUT); 44 | pinMode(BATTERY, INPUT); 45 | pinMode(DISABLE, INPUT); 46 | 47 | digitalWrite(RFSQUELCH1, HIGH); 48 | digitalWrite(RFSQUELCH2, HIGH); 49 | digitalWrite(EXTSQUELCH, HIGH); 50 | digitalWrite(BATTERY, HIGH); 51 | digitalWrite(DISABLE, HIGH); 52 | 53 | // Set up the output pins and set low 54 | pinMode(RFTRANSMIT, OUTPUT); 55 | pinMode(EXTTRANSMIT, OUTPUT); 56 | pinMode(HEARTBEAT1, OUTPUT); 57 | pinMode(HEARTBEAT2, OUTPUT); 58 | pinMode(ACTIVE, OUTPUT); 59 | pinMode(OUTPUT1, OUTPUT); 60 | pinMode(OUTPUT2, OUTPUT); 61 | pinMode(OUTPUT3, OUTPUT); 62 | pinMode(OUTPUT4, OUTPUT); 63 | 64 | digitalWrite(RFTRANSMIT, LOW); 65 | digitalWrite(EXTTRANSMIT, LOW); 66 | digitalWrite(HEARTBEAT1, LOW); 67 | digitalWrite(HEARTBEAT2, HIGH); 68 | digitalWrite(ACTIVE, LOW); 69 | digitalWrite(OUTPUT1, LOW); 70 | digitalWrite(OUTPUT2, LOW); 71 | digitalWrite(OUTPUT3, LOW); 72 | digitalWrite(OUTPUT4, LOW); 73 | 74 | // Set up the serial port 75 | Serial.begin(19200); 76 | } 77 | 78 | void loop() { 79 | if (Serial.available() > 0) { 80 | int out = Serial.read(); 81 | 82 | digitalWrite(RFTRANSMIT, (out & 0x01) == 0x01 ? HIGH : LOW); 83 | digitalWrite(EXTTRANSMIT, (out & 0x02) == 0x02 ? HIGH : LOW); 84 | digitalWrite(HEARTBEAT1, (out & 0x04) == 0x04 ? HIGH : LOW); 85 | digitalWrite(HEARTBEAT2, (out & 0x04) == 0x04 ? HIGH : LOW); 86 | digitalWrite(ACTIVE, (out & 0x08) == 0x08 ? HIGH : LOW); 87 | digitalWrite(OUTPUT1, (out & 0x10) == 0x10 ? HIGH : LOW); 88 | digitalWrite(OUTPUT2, (out & 0x20) == 0x20 ? HIGH : LOW); 89 | digitalWrite(OUTPUT3, (out & 0x40) == 0x40 ? HIGH : LOW); 90 | digitalWrite(OUTPUT4, (out & 0x80) == 0x80 ? HIGH : LOW); 91 | } 92 | 93 | byte val = 0x00; 94 | val |= (digitalRead(RFSQUELCH1) == LOW) ? 0x01 : 0x00; 95 | val |= (digitalRead(RFSQUELCH2) == LOW) ? 0x02 : 0x00; 96 | val |= (digitalRead(EXTSQUELCH) == LOW) ? 0x04 : 0x00; 97 | val |= (digitalRead(BATTERY) == LOW) ? 0x08 : 0x00; 98 | val |= (digitalRead(DISABLE) == LOW) ? 0x10 : 0x00; 99 | 100 | Serial.write(val); 101 | 102 | delay(20); // 20ms 103 | } 104 | 105 | -------------------------------------------------------------------------------- /ArduinoController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "ArduinoController.h" 15 | 16 | const char IN_PORT1 = 0x01U; 17 | const char IN_PORT2 = 0x02U; 18 | const char IN_PORT3 = 0x04U; 19 | const char IN_PORT4 = 0x08U; 20 | const char IN_PORT5 = 0x10U; 21 | 22 | const char OUT_PORT1 = 0x01U; 23 | const char OUT_PORT2 = 0x02U; 24 | const char OUT_PORT3 = 0x04U; 25 | const char OUT_PORT4 = 0x08U; 26 | const char OUT_PORT5 = 0x10U; 27 | const char OUT_PORT6 = 0x20U; 28 | const char OUT_PORT7 = 0x40U; 29 | const char OUT_PORT8 = 0x80U; 30 | 31 | 32 | CArduinoController::CArduinoController(const std::string& port) : 33 | CThread(), 34 | m_serial(port, SERIAL_19200), 35 | m_squelch(false), 36 | m_disable(false), 37 | m_transmit(false), 38 | m_heartbeat(false), 39 | m_active(false), 40 | m_kill(false) 41 | { 42 | } 43 | 44 | CArduinoController::~CArduinoController() 45 | { 46 | } 47 | 48 | bool CArduinoController::open() 49 | { 50 | bool ret = m_serial.open(); 51 | if (!ret) 52 | return false; 53 | 54 | unsigned char buffer; 55 | while (m_serial.read(&buffer, 1U) == 1) 56 | ; 57 | 58 | run(); 59 | 60 | return true; 61 | } 62 | 63 | void CArduinoController::entry() 64 | { 65 | while (!m_kill) { 66 | unsigned char in; 67 | int ret = m_serial.read(&in, 1U); 68 | if (ret == 1) { 69 | m_squelch = (in & IN_PORT1) == IN_PORT1; 70 | m_disable = (in & IN_PORT2) == IN_PORT2; 71 | } 72 | 73 | sleep(10U); 74 | } 75 | 76 | m_serial.close(); 77 | } 78 | 79 | bool CArduinoController::getSquelch() 80 | { 81 | return m_squelch; 82 | } 83 | 84 | bool CArduinoController::getDisable() 85 | { 86 | return m_disable; 87 | } 88 | 89 | void CArduinoController::setTransmit(bool value) 90 | { 91 | m_transmit = value; 92 | 93 | setOutputs(); 94 | } 95 | 96 | void CArduinoController::setHeartbeat(bool value) 97 | { 98 | m_heartbeat = value; 99 | 100 | setOutputs(); 101 | } 102 | 103 | void CArduinoController::setActive(bool value) 104 | { 105 | m_active = value; 106 | 107 | setOutputs(); 108 | } 109 | 110 | void CArduinoController::setOutputs() 111 | { 112 | unsigned char out = 0x00U; 113 | if (m_transmit) 114 | out |= OUT_PORT1; 115 | if (m_heartbeat) 116 | out |= OUT_PORT2; 117 | if (m_active) 118 | out |= OUT_PORT3; 119 | 120 | m_serial.write(&out, 1U); 121 | } 122 | 123 | void CArduinoController::close() 124 | { 125 | m_kill = true; 126 | 127 | wait(); 128 | } 129 | -------------------------------------------------------------------------------- /ArduinoController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef ArduinoController_H 15 | #define ArduinoController_H 16 | 17 | #include "SerialDataController.h" 18 | #include "HardwareController.h" 19 | #include "Thread.h" 20 | 21 | #include 22 | 23 | class CArduinoController : public IHardwareController, public CThread { 24 | public: 25 | CArduinoController(const std::string& port); 26 | virtual ~CArduinoController(); 27 | 28 | virtual bool open(); 29 | 30 | virtual void entry(); 31 | 32 | virtual bool getSquelch(); 33 | virtual bool getDisable(); 34 | 35 | virtual void setTransmit(bool value); 36 | virtual void setHeartbeat(bool value); 37 | virtual void setActive(bool value); 38 | 39 | virtual void close(); 40 | 41 | private: 42 | CSerialDataController m_serial; 43 | bool m_squelch; 44 | bool m_disable; 45 | bool m_transmit; 46 | bool m_heartbeat; 47 | bool m_active; 48 | bool m_kill; 49 | 50 | void setOutputs(); 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /AudioCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2015,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef AudioCallback_H 15 | #define AudioCallback_H 16 | 17 | class IAudioCallback { 18 | public: 19 | virtual void readCallback(const float* input, unsigned int nSamples) = 0; 20 | virtual void writeCallback(float* output, int& nSamples) = 0; 21 | 22 | private: 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /AudioDelay.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "AudioDelay.h" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | CAudioDelay::CAudioDelay(unsigned int delay) : 21 | m_delay(2U * delay), 22 | m_in(delay), 23 | m_out(0U), 24 | m_bufList(NULL) 25 | { 26 | assert(delay > 0U); 27 | 28 | m_bufList = new float[m_delay]; 29 | ::memset(m_bufList, 0x00, m_delay * sizeof(float)); 30 | } 31 | 32 | CAudioDelay::~CAudioDelay() 33 | { 34 | delete[] m_bufList; 35 | } 36 | 37 | void CAudioDelay::delay(float* audio, unsigned int length) 38 | { 39 | assert(audio != NULL); 40 | assert(length > 0U); 41 | 42 | for (unsigned int i = 0U; i < length; i++) { 43 | m_bufList[m_in++] = audio[i]; 44 | audio[i] = m_bufList[m_out++]; 45 | 46 | if (m_in >= m_delay) 47 | m_in = 0U; 48 | if (m_out >= m_delay) 49 | m_out = 0U; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AudioDelay.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2013,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef AudioDelay_H 20 | #define AudioDelay_H 21 | 22 | class CAudioDelay { 23 | public: 24 | CAudioDelay(unsigned int delay); 25 | ~CAudioDelay(); 26 | 27 | void delay(float* audio, unsigned int length); 28 | 29 | private: 30 | unsigned int m_delay; 31 | unsigned int m_in; 32 | unsigned int m_out; 33 | float* m_bufList; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | FM Repeater - 201804XX 2 | ====================== 3 | 4 | 20130702 5 | -------- 6 | 7 | Added daemon info to the logs. 8 | Split from the main Repeater package. 9 | Removed the URI USB support. 10 | 11 | 20130704 12 | -------- 13 | 14 | Added basic AX.25 receiving. 15 | 16 | 20140303 17 | -------- 18 | 19 | Added optional delay before sending the opening callsign. 20 | Add exception catching. 21 | Added APRS transmission with the beacon. 22 | Fixed bug in WAV file reading under Linux. 23 | 24 | 20150208 25 | -------- 26 | 27 | Use ALSA on Linux instead of PortAudio. 28 | Dynamic serial port selection on Linux. 29 | GPIO control potentially available on more platforms. 30 | 31 | 20150210 32 | -------- 33 | 34 | Updated the dynamic serial port selection for /dev/ttyACM* 35 | Updated the ALSA handing. 36 | 37 | 20150213 38 | -------- 39 | 40 | Updated the ALSA handling. 41 | Fixed a compile bug when defining GPIO. 42 | 43 | 20150308 44 | -------- 45 | 46 | More changes to the ALSA interface. 47 | Added a .mk for the Pi2. 48 | 49 | 20150404 50 | -------- 51 | 52 | Allow enumeration of the sound devices including the running one. 53 | 54 | 201804XX 55 | -------- 56 | 57 | Move the code over to use STL instead of wxWidgets. 58 | Remove the external input. 59 | Remove pre- and de-emphasis. 60 | Simplify the code. 61 | Remove the GUI and add new configuration system. 62 | Convert the RPi GPIO code to match the UDRC. 63 | 64 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /CWKeyer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2012,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "CWKeyer.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | const struct SSymbols { 22 | const char c; 23 | const char* symbol; 24 | } cwTable[] = { 25 | {'A', ".-"}, 26 | {'B', "-..."}, 27 | {'C', "-.-."}, 28 | {'D', "-.."}, 29 | {'E', "."}, 30 | {'F', "..-."}, 31 | {'G', "--."}, 32 | {'H', "...."}, 33 | {'I', ".."}, 34 | {'J', ".---"}, 35 | {'K', "-.-"}, 36 | {'L', ".-.."}, 37 | {'M', "--"}, 38 | {'N', "-."}, 39 | {'O', "---"}, 40 | {'P', ".--."}, 41 | {'Q', "--.-"}, 42 | {'R', ".-."}, 43 | {'S', "..."}, 44 | {'T', "-"}, 45 | {'U', "..-"}, 46 | {'V', "...-"}, 47 | {'W', ".--"}, 48 | {'X', "-..-"}, 49 | {'Y', "-.--"}, 50 | {'Z', "--.."}, 51 | {'0', "-----"}, 52 | {'1', ".----"}, 53 | {'2', "..---"}, 54 | {'3', "...--"}, 55 | {'4', "....-"}, 56 | {'5', "....."}, 57 | {'6', "-...."}, 58 | {'7', "--..."}, 59 | {'8', "---.."}, 60 | {'9', "----."}, 61 | {'/', "-..-."}, 62 | {'=', "-...-"}, 63 | {',', "--..--"}, 64 | {'?', "..--.."}, 65 | {' ', " "}, 66 | {'$', ""} 67 | }; 68 | 69 | const unsigned int RC_LEN = 5U; // The length of the raised cosine in ms 70 | 71 | CCWKeyer::CCWKeyer(const std::string& text, unsigned int speed, unsigned int freq, unsigned int sampleRate) : 72 | m_data(NULL), 73 | m_length(0U), 74 | m_current(0U) 75 | { 76 | assert(speed > 0U); 77 | assert(freq > 0U); 78 | assert(sampleRate > 0U); 79 | 80 | if (text.empty()) 81 | return; 82 | 83 | std::string cw = " "; 84 | for (unsigned int i = 0U; i < text.size(); i++) { 85 | cw += charToCW(text.at(i)); 86 | cw += " "; 87 | } 88 | cw += " "; 89 | 90 | for (unsigned int i = 0U; i < cw.size(); i++) { 91 | char c = cw.at(i); 92 | 93 | switch (c) { 94 | case ' ': 95 | m_length += 2U; 96 | break; 97 | case '-': 98 | m_length += 4U; 99 | break; 100 | case '.': 101 | m_length += 2U; 102 | break; 103 | } 104 | } 105 | 106 | float dotLenS = 1.2F / float(speed); 107 | unsigned int dotLenSamples = (unsigned int)(float(sampleRate) * dotLenS + 0.5F); 108 | 109 | unsigned int dotLen = dotLenSamples * 1U; 110 | unsigned int dashLen = dotLenSamples * 3U; 111 | 112 | m_length *= dotLenSamples; 113 | 114 | m_data = new float[m_length]; 115 | 116 | unsigned int sqLen = (sampleRate / freq) / 2U; 117 | 118 | float* dot = new float[dotLen]; 119 | float* dash = new float[dashLen]; 120 | 121 | bool high = false; 122 | for (unsigned int i = 0U; i < dashLen; i++) { 123 | if ((i % sqLen) == 0U) 124 | high = !high; 125 | 126 | dash[i] = high ? 1.0F : -1.0F; 127 | if (i < dotLen) 128 | dot[i] = high ? 1.0F : -1.0F; 129 | } 130 | 131 | // Calculate the length of the raised cosine shaping section 132 | unsigned int rcLen = RC_LEN * sampleRate / 1000U; 133 | 134 | // Shape the start and end of the tones 135 | for (unsigned int i = 0U; i < rcLen; i++) { 136 | float ampl = 0.5F * (1.0F - float(::cos(M_PI * (float(i) / float(rcLen))))); 137 | 138 | dot[i] *= ampl; 139 | dash[i] *= ampl; 140 | 141 | dot[dotLen - 1U - i] *= ampl; 142 | dash[dashLen - 1U - i] *= ampl; 143 | } 144 | 145 | // Clear the output buffer to ensure silence between the symbols 146 | ::memset(m_data, 0x00, m_length * sizeof(float)); 147 | 148 | unsigned int pos = 0U; 149 | for (unsigned int i = 0U; i < cw.size(); i++) { 150 | char c = cw.at(i); 151 | 152 | switch (c) { 153 | case ' ': 154 | pos += dotLenSamples * 2U; 155 | break; 156 | case '.': 157 | ::memcpy(m_data + pos, dot, dotLen * sizeof(float)); 158 | pos += dotLenSamples * 2U; 159 | break; 160 | case '-': 161 | ::memcpy(m_data + pos, dash, dashLen * sizeof(float)); 162 | pos += dotLenSamples * 4U; 163 | break; 164 | } 165 | } 166 | 167 | delete[] dot; 168 | delete[] dash; 169 | } 170 | 171 | CCWKeyer::~CCWKeyer() 172 | { 173 | delete[] m_data; 174 | } 175 | 176 | bool CCWKeyer::isEmpty() const 177 | { 178 | return m_length == 0U; 179 | } 180 | 181 | unsigned int CCWKeyer::getAudio(float* audio, unsigned int length, float amplitude) 182 | { 183 | assert(audio != NULL); 184 | 185 | if (m_length == 0U) 186 | return 0U; 187 | 188 | unsigned int n = 0U; 189 | while (n < length && m_current < m_length) 190 | audio[n++] += m_data[m_current++] * amplitude; 191 | 192 | return n; 193 | } 194 | 195 | void CCWKeyer::reset() 196 | { 197 | m_current = 0U; 198 | } 199 | 200 | std::string CCWKeyer::charToCW(char c) const 201 | { 202 | for (const SSymbols* s = cwTable; s->c != '$'; s++) { 203 | if (s->c == c) 204 | return s->symbol; 205 | } 206 | 207 | return ""; 208 | } 209 | -------------------------------------------------------------------------------- /CWKeyer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef CWKeyer_H 15 | #define CWKeyer_H 16 | 17 | #include 18 | 19 | class CCWKeyer { 20 | public: 21 | CCWKeyer(const std::string& text, unsigned int speed, unsigned int freq, unsigned int sampleRate); 22 | ~CCWKeyer(); 23 | 24 | unsigned int getAudio(float* audio, unsigned int length, float amplitude); 25 | 26 | bool isEmpty() const; 27 | 28 | void reset(); 29 | 30 | private: 31 | float* m_data; 32 | unsigned int m_length; 33 | unsigned int m_current; 34 | 35 | std::string charToCW(char c) const; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Config.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Config.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | enum SECTION { 26 | SECTION_NONE, 27 | SECTION_GENERAL, 28 | SECTION_ID, 29 | SECTION_ACK, 30 | SECTION_TIMES, 31 | SECTION_CTCSS, 32 | SECTION_TIMEOUT, 33 | SECTION_AUDIO, 34 | SECTION_INTERFACE 35 | }; 36 | 37 | CConfig::CConfig(const std::string& filename) : 38 | m_fileName(filename), 39 | 40 | m_daemon(false), 41 | 42 | m_idCallsign("G4KLX"), 43 | m_idBeacon("G4KLX IO90TT"), 44 | m_idSpeed(16U), 45 | m_idFreq(1000U), 46 | m_idLevelHi(0.5F), 47 | m_idLevelLo(0.2F), 48 | m_idAtStart(ACS_NONE), 49 | m_idStartDelay(0U), 50 | m_idAtEnd(true), 51 | m_idTime(10U), 52 | m_idHoldoff(6U), 53 | 54 | m_ackStyle("K"), 55 | m_ackSpeed(16U), 56 | m_ackFreq(1750U), 57 | m_ackLevel(0.5F), 58 | m_ackDelay(1000U), 59 | m_ackMin(4U), 60 | 61 | m_timeHang(6U), 62 | m_timeKerchunk(3U), 63 | 64 | m_ctcssFreq(71.9F), 65 | m_ctcssThresh(200.0F), 66 | m_ctcssLevel(0.02F), 67 | m_ctcssHangTime(100U), 68 | m_ctcssTXMode(ACO_ALWAYS), 69 | 70 | m_timeoutTime(120U), 71 | m_timeoutStyle(ATT_UK), 72 | m_timeoutLockout(1U), 73 | 74 | m_audioReadDevice(), 75 | m_audioWriteDevice(), 76 | m_audioDelay(120U), 77 | 78 | //m_lockoutTime(DEFAULT_LOCKOUTTIME), 79 | m_interfaceType("None"), 80 | m_interfacePort(), 81 | m_pttDelay(200U), 82 | m_squelchDelay(100U), 83 | m_pttInvert(false), 84 | m_squelchInvert(false) 85 | { 86 | assert(!filename.empty()); 87 | 88 | FILE* fp = ::fopen(filename.c_str(), "rt"); 89 | if (fp == NULL) { 90 | ::fprintf(stderr, "Cannot open the config file - %s\n", m_fileName.c_str()); 91 | return; 92 | } 93 | 94 | SECTION section = SECTION_NONE; 95 | 96 | char buffer[200U]; 97 | while (::fgets(buffer, 200, fp) != NULL) { 98 | if (buffer[0U] == '#') 99 | continue; 100 | 101 | if (buffer[0U] == '[') { 102 | if (::strncmp(buffer, "[General]", 9U) == 0) 103 | section = SECTION_GENERAL; 104 | else if (::strncmp(buffer, "[Id]", 4U) == 0) 105 | section = SECTION_ID; 106 | else if (::strncmp(buffer, "[Ack]", 5U) == 0) 107 | section = SECTION_ACK; 108 | else if (::strncmp(buffer, "[Times]", 7U) == 0) 109 | section = SECTION_TIMES; 110 | else if (::strncmp(buffer, "[CTCSS]", 7U) == 0) 111 | section = SECTION_CTCSS; 112 | else if (::strncmp(buffer, "[Timeout]", 9U) == 0) 113 | section = SECTION_TIMEOUT; 114 | else if (::strncmp(buffer, "[Audio]", 7U) == 0) 115 | section = SECTION_AUDIO; 116 | else if (::strncmp(buffer, "[Interface]", 11U) == 0) 117 | section = SECTION_INTERFACE; 118 | else 119 | section = SECTION_NONE; 120 | 121 | continue; 122 | } 123 | 124 | char* key = ::strtok(buffer, " \t=\r\n"); 125 | if (key == NULL) 126 | continue; 127 | 128 | char* val = ::strtok(NULL, "\r\n"); 129 | if (val == NULL) 130 | continue; 131 | 132 | if (section == SECTION_GENERAL) { 133 | if (::strcmp(key, "daemon") == 0) { 134 | m_daemon = ::atoi(val) == 1; 135 | } 136 | } else if (section == SECTION_ID) { 137 | if (::strcmp(key, "callsign") == 0) { 138 | m_idCallsign = val; 139 | } else if (::strcmp(key, "beacon") == 0) { 140 | m_idBeacon = val; 141 | } else if (::strcmp(key, "speed") == 0) { 142 | m_idSpeed = (unsigned int)::atoi(val); 143 | } else if (::strcmp(key, "freq") == 0) { 144 | m_idFreq = (unsigned int)::atoi(val); 145 | } else if (::strcmp(key, "levelHi") == 0) { 146 | m_idLevelHi = float(::atof(val)); 147 | } else if (::strcmp(key, "levelLo") == 0) { 148 | m_idLevelLo = float(::atof(val)); 149 | } else if (::strcmp(key, "atStart") == 0) { 150 | m_idAtStart = ANALOGUE_CALLSIGN_START(::atoi(val)); 151 | } else if (::strcmp(key, "startDelay") == 0) { 152 | m_idStartDelay = (unsigned int)::atoi(val); 153 | } else if (::strcmp(key, "atEnd") == 0) { 154 | m_idAtEnd = ::atoi(val) == 1; 155 | } else if (::strcmp(key, "time") == 0) { 156 | m_idTime = (unsigned int)::atoi(val); 157 | } else if (::strcmp(key, "holdoff") == 0) { 158 | m_idHoldoff = (unsigned int)::atoi(val); 159 | } 160 | } else if (section == SECTION_ACK) { 161 | if (::strcmp(key, "style") == 0) { 162 | m_ackStyle = val; 163 | } else if (::strcmp(key, "speed") == 0) { 164 | m_ackSpeed = (unsigned int)::atoi(val); 165 | } else if (::strcmp(key, "freq") == 0) { 166 | m_ackFreq = (unsigned int)::atoi(val); 167 | } else if (::strcmp(key, "level") == 0) { 168 | m_ackLevel = float(::atof(val)); 169 | } else if (::strcmp(key, "dekay") == 0) { 170 | m_ackDelay = (unsigned int)::atoi(val); 171 | } else if (::strcmp(key, "min") == 0) { 172 | m_ackMin = (unsigned int)::atoi(val); 173 | } 174 | } else if (section == SECTION_TIMES) { 175 | if (::strcmp(key, "hang") == 0) { 176 | m_timeHang = (unsigned int)::atoi(val); 177 | } else if (::strcmp(key, "kerchunk") == 0) { 178 | m_timeKerchunk = (unsigned int)::atoi(val); 179 | } 180 | } else if (section == SECTION_CTCSS) { 181 | if (::strcmp(key, "freq") == 0) { 182 | m_ctcssFreq = float(::atof(val)); 183 | } else if (::strcmp(key, "threshold") == 0) { 184 | m_ctcssThresh = float(::atof(val)); 185 | } else if (::strcmp(key, "level") == 0) { 186 | m_ctcssLevel = float(::atof(val)); 187 | } else if (::strcmp(key, "hang") == 0) { 188 | m_ctcssHangTime = (unsigned int)::atoi(val); 189 | } else if (::strcmp(key, "txMode") == 0) { 190 | m_ctcssTXMode = ANALOGUE_CTCSS_OUTPUT(::atoi(val)); 191 | } 192 | } else if (section == SECTION_TIMEOUT) { 193 | if (::strcmp(key, "time") == 0) { 194 | m_timeoutTime = (unsigned int)::atoi(val); 195 | } else if (::strcmp(key, "style") == 0) { 196 | m_timeoutStyle = ANALOGUE_TIMEOUT_TYPE(::atoi(val)); 197 | } else if (::strcmp(key, "lockout") == 0) { 198 | m_timeoutLockout = (unsigned int)::atoi(val); 199 | } 200 | } else if (section == SECTION_AUDIO) { 201 | if (::strcmp(key, "rxDevice") == 0) { 202 | m_audioReadDevice = val; 203 | } else if (::strcmp(key, "txDevice") == 0) { 204 | m_audioWriteDevice = val; 205 | } else if (::strcmp(key, "delay") == 0) { 206 | m_audioDelay = (unsigned int)::atoi(val); 207 | } 208 | } else if (section == SECTION_INTERFACE) { 209 | // if (::strcmp(key, KEY_LOCKOUTTIME) == 0) { 210 | // m_lockoutTime = (unsigned int)::atoi(val); 211 | if (::strcmp(key, "type") == 0) { 212 | m_interfaceType = val; 213 | } else if (::strcmp(key, "port") == 0) { 214 | m_interfacePort = val; 215 | } else if (::strcmp(key, "pttDelay") == 0) { 216 | m_pttDelay = (unsigned int)::atoi(val); 217 | } else if (::strcmp(key, "sqDelay") == 0) { 218 | m_squelchDelay = (unsigned int)::atoi(val); 219 | } else if (::strcmp(key, "pttInvert") == 0) { 220 | m_pttInvert = ::atoi(val) == 1; 221 | } else if (::strcmp(key, "sqInvert") == 0) { 222 | m_squelchInvert = ::atoi(val) == 1; 223 | } 224 | } 225 | } 226 | 227 | ::fclose(fp); 228 | } 229 | 230 | CConfig::~CConfig() 231 | { 232 | } 233 | 234 | bool CConfig::isDaemon() const 235 | { 236 | return m_daemon; 237 | } 238 | 239 | void CConfig::getId(std::string& callsign, std::string& beacon, unsigned int& speed, unsigned int& freq, float& levelHi, float& levelLo) const 240 | { 241 | callsign = m_idCallsign; 242 | beacon = m_idBeacon; 243 | speed = m_idSpeed; 244 | freq = m_idFreq; 245 | levelHi = m_idLevelHi; 246 | levelLo = m_idLevelLo; 247 | } 248 | 249 | void CConfig::getAck(std::string& style, unsigned int& speed, unsigned int& freq, float& level, unsigned int& ack, unsigned int& minimum) const 250 | { 251 | style = m_ackStyle; 252 | speed = m_ackSpeed; 253 | freq = m_ackFreq; 254 | level = m_ackLevel; 255 | ack = m_ackDelay; 256 | minimum = m_ackMin; 257 | } 258 | 259 | void CConfig::getTimes(unsigned int& callsignTime, unsigned int& timeout, unsigned int& lockout, unsigned int& hangTime, unsigned int& kerchunkTime) const 260 | { 261 | callsignTime = m_idTime; 262 | timeout = m_timeoutTime; 263 | lockout = m_timeoutLockout; 264 | hangTime = m_timeHang; 265 | kerchunkTime = m_timeKerchunk; 266 | } 267 | 268 | void CConfig::getTones(float& ctcssFreq, float& ctcssThresh, float& ctcssLevel, unsigned int& ctcssHangTime, ANALOGUE_CTCSS_OUTPUT& ctcssTXMode) const 269 | { 270 | ctcssFreq = m_ctcssFreq; 271 | ctcssThresh = m_ctcssThresh; 272 | ctcssLevel = m_ctcssLevel; 273 | ctcssHangTime = m_ctcssHangTime; 274 | ctcssTXMode = m_ctcssTXMode; 275 | } 276 | 277 | void CConfig::getFeel(ANALOGUE_CALLSIGN_START& callsignAtStart, unsigned int& callsignStartDelay, bool& callsignAtEnd, ANALOGUE_TIMEOUT_TYPE& timeoutStyle, unsigned int& callsignHoldoff) const 278 | { 279 | callsignAtStart = m_idAtStart; 280 | callsignStartDelay = m_idStartDelay; 281 | callsignAtEnd = m_idAtEnd; 282 | timeoutStyle = m_timeoutStyle; 283 | callsignHoldoff = m_idHoldoff; 284 | } 285 | 286 | void CConfig::getRadio(std::string& readDevice, std::string& writeDevice, unsigned int& delay) const 287 | { 288 | readDevice = m_audioReadDevice; 289 | writeDevice = m_audioWriteDevice; 290 | delay = m_audioDelay; 291 | } 292 | 293 | void CConfig::getController(std::string& type, std::string& port, unsigned int& pttDelay, unsigned int& squelchDelay, bool& pttInvert, bool& squelchInvert) const 294 | { 295 | type = m_interfaceType; 296 | port = m_interfacePort; 297 | pttDelay = m_pttDelay; 298 | squelchDelay = m_squelchDelay; 299 | pttInvert = m_pttInvert; 300 | squelchInvert = m_squelchInvert; 301 | } 302 | 303 | -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef Config_H 20 | #define Config_H 21 | 22 | #include "TimeoutTones.h" 23 | #include "FMDefines.h" 24 | 25 | #include 26 | 27 | class CConfig { 28 | public: 29 | CConfig(const std::string& filename); 30 | ~CConfig(); 31 | 32 | bool isDaemon() const; 33 | 34 | void getId(std::string& callsign, std::string& beacon, unsigned int& speed, unsigned int& freq, float& levelHi, float& levelLo) const; 35 | 36 | void getAck(std::string& style, unsigned int& speed, unsigned int& freq, float& level, unsigned int& ack, unsigned int& minimum) const; 37 | 38 | void getTimes(unsigned int& callsignTime, unsigned int& timeout, unsigned int& lockout, unsigned int& hangTime, unsigned int& kerchunkTime) const; 39 | 40 | void getTones(float& ctcssFreq, float& ctcssThresh, float& ctcssLevel, unsigned int& ctcssHangTime, ANALOGUE_CTCSS_OUTPUT& ctcssTXMode) const; 41 | 42 | void getFeel(ANALOGUE_CALLSIGN_START& callsignAtStart, unsigned int& callsignStartDelay, bool& callsignAtEnd, ANALOGUE_TIMEOUT_TYPE& timeoutStyle, unsigned int& callsignHoldoff) const; 43 | 44 | void getRadio(std::string& readDevice, std::string& writeDevice, unsigned int& delay) const; 45 | 46 | void getController(std::string& type, std::string& port, unsigned int& pttDelay, unsigned int& squelchDelay, bool& pttInvert, bool& squelchInvert) const; 47 | 48 | private: 49 | std::string m_fileName; 50 | 51 | bool m_daemon; 52 | 53 | std::string m_idCallsign; 54 | std::string m_idBeacon; 55 | unsigned int m_idSpeed; 56 | unsigned int m_idFreq; 57 | float m_idLevelHi; 58 | float m_idLevelLo; 59 | ANALOGUE_CALLSIGN_START m_idAtStart; 60 | unsigned int m_idStartDelay; 61 | bool m_idAtEnd; 62 | unsigned int m_idTime; 63 | unsigned int m_idHoldoff; 64 | 65 | std::string m_ackStyle; 66 | unsigned int m_ackSpeed; 67 | unsigned int m_ackFreq; 68 | float m_ackLevel; 69 | unsigned int m_ackDelay; 70 | unsigned int m_ackMin; 71 | 72 | unsigned int m_timeHang; 73 | unsigned int m_timeKerchunk; 74 | 75 | float m_ctcssFreq; 76 | float m_ctcssThresh; 77 | float m_ctcssLevel; 78 | unsigned int m_ctcssHangTime; 79 | ANALOGUE_CTCSS_OUTPUT m_ctcssTXMode; 80 | 81 | unsigned int m_timeoutTime; 82 | ANALOGUE_TIMEOUT_TYPE m_timeoutStyle; 83 | unsigned int m_timeoutLockout; 84 | 85 | std::string m_audioReadDevice; 86 | std::string m_audioWriteDevice; 87 | unsigned int m_audioDelay; 88 | 89 | // unsigned int m_lockoutTime; 90 | std::string m_interfaceType; 91 | std::string m_interfacePort; 92 | unsigned int m_pttDelay; 93 | unsigned int m_squelchDelay; 94 | bool m_pttInvert; 95 | bool m_squelchInvert; 96 | }; 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /DummyController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "DummyController.h" 15 | 16 | CDummyController::CDummyController() 17 | { 18 | } 19 | 20 | CDummyController::~CDummyController() 21 | { 22 | } 23 | 24 | bool CDummyController::open() 25 | { 26 | return true; 27 | } 28 | 29 | bool CDummyController::getSquelch() 30 | { 31 | return false; 32 | } 33 | 34 | bool CDummyController::getDisable() 35 | { 36 | return false; 37 | } 38 | 39 | void CDummyController::setTransmit(bool) 40 | { 41 | } 42 | 43 | void CDummyController::setHeartbeat(bool) 44 | { 45 | } 46 | 47 | void CDummyController::setActive(bool) 48 | { 49 | } 50 | 51 | void CDummyController::close() 52 | { 53 | } 54 | -------------------------------------------------------------------------------- /DummyController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef DummyController_H 15 | #define DummyController_H 16 | 17 | #include "HardwareController.h" 18 | 19 | class CDummyController : public IHardwareController { 20 | public: 21 | CDummyController(); 22 | virtual ~CDummyController(); 23 | 24 | virtual bool open(); 25 | 26 | virtual bool getSquelch(); 27 | virtual bool getDisable(); 28 | 29 | virtual void setTransmit(bool value); 30 | virtual void setHeartbeat(bool value); 31 | virtual void setActive(bool value); 32 | 33 | virtual void close(); 34 | 35 | private: 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /ExternalController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "ExternalController.h" 15 | 16 | #include "FMDefines.h" 17 | 18 | #include 19 | #include 20 | 21 | CExternalController::CExternalController(IHardwareController* controller, bool pttInvert, bool squelchInvert) : 22 | m_controller(controller), 23 | m_pttInvert(pttInvert), 24 | m_squelchInvert(squelchInvert), 25 | m_heartbeat(false) 26 | { 27 | assert(controller != NULL); 28 | } 29 | 30 | CExternalController::~CExternalController() 31 | { 32 | } 33 | 34 | bool CExternalController::open() 35 | { 36 | bool ret = m_controller->open(); 37 | if (!ret) 38 | return false; 39 | 40 | setTransmit(false); 41 | setActive(false); 42 | 43 | return true; 44 | } 45 | 46 | bool CExternalController::getSquelch() 47 | { 48 | assert(m_controller != NULL); 49 | 50 | bool squelch = m_controller->getSquelch(); 51 | 52 | if (m_squelchInvert) 53 | return !squelch; 54 | else 55 | return squelch; 56 | } 57 | 58 | bool CExternalController::getDisable() 59 | { 60 | assert(m_controller != NULL); 61 | 62 | return m_controller->getDisable(); 63 | } 64 | 65 | void CExternalController::setTransmit(bool value) 66 | { 67 | assert(m_controller != NULL); 68 | 69 | if (m_pttInvert) 70 | value = !value; 71 | 72 | m_controller->setTransmit(value); 73 | } 74 | 75 | void CExternalController::setHeartbeat() 76 | { 77 | assert(m_controller != NULL); 78 | 79 | m_controller->setHeartbeat(m_heartbeat); 80 | 81 | m_heartbeat = !m_heartbeat; 82 | } 83 | 84 | void CExternalController::setActive(bool value) 85 | { 86 | assert(m_controller != NULL); 87 | 88 | m_controller->setActive(value); 89 | } 90 | 91 | void CExternalController::close() 92 | { 93 | assert(m_controller != NULL); 94 | 95 | setTransmit(false); 96 | setActive(false); 97 | 98 | m_controller->close(); 99 | 100 | delete m_controller; 101 | m_controller = NULL; 102 | } 103 | -------------------------------------------------------------------------------- /ExternalController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef ExternalController_H 15 | #define ExternalController_H 16 | 17 | #include "HardwareController.h" 18 | 19 | class CExternalController { 20 | public: 21 | CExternalController(IHardwareController* controller, bool pttInvert = false, bool squelchInvert = false); 22 | ~CExternalController(); 23 | 24 | bool open(); 25 | 26 | bool getSquelch(); 27 | bool getDisable(); 28 | 29 | void setTransmit(bool value); 30 | void setHeartbeat(); 31 | void setActive(bool value); 32 | 33 | void close(); 34 | 35 | private: 36 | IHardwareController* m_controller; 37 | bool m_pttInvert; 38 | bool m_squelchInvert; 39 | bool m_heartbeat; 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /FIRFilter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2001, 2002, 2003 by Tomi Manninen, OH2BNS 3 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; version 2 of the License. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | */ 14 | 15 | #include "FIRFilter.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | CFIRFilter::CFIRFilter(const float* taps, unsigned int length) : 22 | m_taps(NULL), 23 | m_length(length), 24 | m_buffer(NULL), 25 | m_bufLen(20U * m_length), 26 | m_pointer(length) 27 | { 28 | assert(taps != NULL); 29 | assert(length > 0U); 30 | 31 | m_taps = new float[m_length]; 32 | m_buffer = new float[m_bufLen]; 33 | 34 | ::memcpy(m_taps, taps, m_length * sizeof(float)); 35 | ::memset(m_buffer, 0x00, m_bufLen * sizeof(float)); 36 | } 37 | 38 | CFIRFilter::CFIRFilter() : 39 | m_taps(NULL), 40 | m_length(0U), 41 | m_buffer(NULL), 42 | m_bufLen(0U), 43 | m_pointer(0U) 44 | { 45 | } 46 | 47 | CFIRFilter::~CFIRFilter() 48 | { 49 | delete[] m_taps; 50 | delete[] m_buffer; 51 | } 52 | 53 | void CFIRFilter::setTaps(const float* taps, unsigned int length) 54 | { 55 | assert(taps != NULL); 56 | assert(length > 0U); 57 | 58 | delete[] m_taps; 59 | delete[] m_buffer; 60 | 61 | m_length = length; 62 | m_pointer = length; 63 | m_bufLen = 20U * m_length; 64 | 65 | m_taps = new float[m_length]; 66 | m_buffer = new float[m_bufLen]; 67 | 68 | ::memcpy(m_taps, taps, m_length * sizeof(float)); 69 | ::memset(m_buffer, 0x00, m_bufLen * sizeof(float)); 70 | } 71 | 72 | float CFIRFilter::process(float val) 73 | { 74 | float* ptr = m_buffer + m_pointer++; 75 | 76 | *ptr = val; 77 | 78 | float* a = ptr - m_length; 79 | float* b = m_taps; 80 | 81 | float out = 0.0F; 82 | for (unsigned int i = 0U; i < m_length; i++) 83 | out += (*a++) * (*b++); 84 | 85 | if (m_pointer == m_bufLen) { 86 | ::memcpy(m_buffer, m_buffer + m_bufLen - m_length, m_length * sizeof(float)); 87 | m_pointer = m_length; 88 | } 89 | 90 | return out; 91 | } 92 | 93 | void CFIRFilter::process(float* inOut, unsigned int length) 94 | { 95 | assert(inOut != NULL); 96 | 97 | for (unsigned int i = 0U; i < length; i++) 98 | inOut[i] = process(inOut[i]); 99 | } 100 | 101 | void CFIRFilter::process(const float* in, float* out, unsigned int length) 102 | { 103 | assert(in != NULL); 104 | assert(out != NULL); 105 | 106 | for (unsigned int i = 0U; i < length; i++) 107 | out[i] = process(in[i]); 108 | } 109 | 110 | void CFIRFilter::reset() 111 | { 112 | ::memset(m_buffer, 0x00, m_bufLen * sizeof(float));; 113 | } 114 | -------------------------------------------------------------------------------- /FIRFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef FIRFilter_H 15 | #define FIRFilter_H 16 | 17 | class CFIRFilter { 18 | public: 19 | CFIRFilter(const float* taps, unsigned int length); 20 | CFIRFilter(); 21 | ~CFIRFilter(); 22 | 23 | void setTaps(const float* taps, unsigned int length); 24 | 25 | float process(float val); 26 | void process(float* inOut, unsigned int length); 27 | void process(const float* in, float* out, unsigned int length); 28 | 29 | void reset(); 30 | 31 | private: 32 | float* m_taps; 33 | unsigned int m_length; 34 | float* m_buffer; 35 | unsigned int m_bufLen; 36 | unsigned int m_pointer; 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /FMDefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2011,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef AnalogueDefines_H 15 | #define AnalogueDefines_H 16 | 17 | const unsigned int ANALOGUE_RADIO_SAMPLE_RATE = 48000U; 18 | const unsigned int ANALOGUE_RADIO_BLOCK_SIZE = 960U; 19 | 20 | const float MAX_VOGAD_GAIN = 6.0F; 21 | const float MAX_AUDIO_LEVEL = 0.5F; 22 | 23 | const unsigned int ANALOGUE_FRAME_TIME_MS = 20U; 24 | 25 | const unsigned int ANALOGUE_TICKS_PER_SEC = ANALOGUE_RADIO_SAMPLE_RATE / ANALOGUE_RADIO_BLOCK_SIZE; 26 | 27 | enum ANALOGUE_RPT_STATE { 28 | ARS_SHUTDOWN, 29 | ARS_LISTENING, 30 | ARS_RELAYING, 31 | ARS_WAITING, 32 | ARS_TIMEOUT, 33 | ARS_LOCKOUT 34 | }; 35 | 36 | enum ANALOGUE_SQUELCH { 37 | AS_CLOSED, 38 | AS_CARRIER, 39 | AS_CTCSS, 40 | AS_EXTERNAL 41 | }; 42 | 43 | enum ANALOGUE_CALLSIGN_START { 44 | ACS_NONE, 45 | ACS_OPEN, 46 | ACS_LATCH 47 | }; 48 | 49 | enum ANALOGUE_CTCSS_OUTPUT { 50 | ACO_NONE, 51 | ACO_WHEN_OPEN, 52 | ACO_ON_AUDIO, 53 | ACO_ALWAYS 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /FMRepeater.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "ExternalController.h" 20 | #include "ArduinoController.h" 21 | #include "FMRepeater.h" 22 | #include "DummyController.h" 23 | #include "UDRCController.h" 24 | #include "CWKeyer.h" 25 | #include "Version.h" 26 | #include "Config.h" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #if defined(_WIN32) || defined(_WIN64) 33 | #include 34 | #else 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #endif 42 | 43 | #if defined(_WIN32) || defined(_WIN64) 44 | const char* DEFAULT_INI_FILE = "FMRepeater.ini"; 45 | #else 46 | const char* DEFAULT_INI_FILE = "/etc/FMRepeater.ini"; 47 | #endif 48 | 49 | int main(int argc, char** argv) 50 | { 51 | const char* iniFile = DEFAULT_INI_FILE; 52 | if (argc > 1) { 53 | for (int currentArg = 1; currentArg < argc; ++currentArg) { 54 | std::string arg = argv[currentArg]; 55 | if ((arg == "-v") || (arg == "--version")) { 56 | ::printf("FMRepeater version %s\n", VERSION); 57 | return 0; 58 | } else if (arg.substr(0, 1) == "-") { 59 | ::fprintf(stderr, "Usage: FMRepeater [-v|--version] [filename]\n"); 60 | return 1; 61 | } else { 62 | iniFile = argv[currentArg]; 63 | } 64 | } 65 | } 66 | 67 | CFMRepeater* repeater = new CFMRepeater(std::string(iniFile)); 68 | repeater->run(); 69 | delete repeater; 70 | 71 | return 0; 72 | } 73 | 74 | CFMRepeater::CFMRepeater(const std::string& filename) : 75 | m_filename(filename), 76 | m_thread(NULL) 77 | { 78 | } 79 | 80 | CFMRepeater::~CFMRepeater() 81 | { 82 | } 83 | 84 | bool CFMRepeater::run() 85 | { 86 | ::printf("Starting the FMRepeater - %s\n", VERSION); 87 | 88 | CConfig config(m_filename); 89 | 90 | m_thread = new CFMRepeaterThread; 91 | 92 | std::string callsign, beacon; 93 | unsigned int speed, freq; 94 | float levelHi, levelLo; 95 | config.getId(callsign, beacon, speed, freq, levelHi, levelLo); 96 | ::printf("Callsign value: \"%s\", beacon value: \"%s\", speed: %u WPM, freq: %u Hz, level hi: %.3f, level lo: %.3f\n", callsign.c_str(), beacon.c_str(), speed, freq, levelHi, levelLo); 97 | 98 | CCWKeyer* callsignAudio = new CCWKeyer(callsign, speed, freq, ANALOGUE_RADIO_SAMPLE_RATE); 99 | CCWKeyer* beaconAudio = new CCWKeyer(beacon, speed, freq, ANALOGUE_RADIO_SAMPLE_RATE); 100 | 101 | m_thread->setCallsign(callsignAudio, beaconAudio, levelHi, levelLo); 102 | 103 | std::string style; 104 | unsigned int ack, minimum; 105 | float level; 106 | config.getAck(style, speed, freq, level, ack, minimum); 107 | ::printf("Ack style: \"%s\", speed: %u WPM, freq: %u Hz, level: %.3f, ack: %u ms, minimum: %u ms\n", style.c_str(), speed, freq, level, ack, minimum); 108 | 109 | CCWKeyer* ackAudio = new CCWKeyer(style, speed, freq, ANALOGUE_RADIO_SAMPLE_RATE); 110 | 111 | m_thread->setAck(ackAudio, level, ack, minimum); 112 | 113 | unsigned int callsignTime, timeout, lockoutTime, hangTime, kerchunkTime; 114 | config.getTimes(callsignTime, timeout, lockoutTime, hangTime, kerchunkTime); 115 | m_thread->setTimes(callsignTime, timeout, lockoutTime, hangTime, kerchunkTime); 116 | ::printf("Times set to: callsign time: %u mins, timeout: %u secs, lockout time: %u secs, hang time: %u secs, kerchunk time: %u secs\n", callsignTime, timeout, lockoutTime, hangTime, kerchunkTime); 117 | 118 | float ctcssFreq, ctcssThresh, ctcssLevel; 119 | unsigned int ctcssHangTime; 120 | ANALOGUE_CTCSS_OUTPUT ctcssOutput; 121 | config.getTones(ctcssFreq, ctcssThresh, ctcssLevel, ctcssHangTime, ctcssOutput); 122 | m_thread->setTones(ctcssFreq, ctcssThresh, ctcssLevel, ctcssHangTime, ctcssOutput); 123 | ::printf("Tones set to: CTCSS freq: %.1f Hz, threshold: %.3f, level: %.3f, hang time: %u ms, output: %d\n", ctcssFreq, ctcssThresh, ctcssLevel, ctcssHangTime * 20U, ctcssOutput); 124 | 125 | ANALOGUE_CALLSIGN_START callsignAtStart; 126 | unsigned int callsignStartDelay, callsignHoldoff; 127 | bool callsignAtEnd; 128 | ANALOGUE_TIMEOUT_TYPE timeoutType; 129 | config.getFeel(callsignAtStart, callsignStartDelay, callsignAtEnd, timeoutType, callsignHoldoff); 130 | m_thread->setFeel(callsignAtStart, callsignStartDelay, callsignAtEnd, timeoutType, callsignHoldoff); 131 | ::printf("Feel set to: callsignAtStart: %d, callsignStartDelay: %u s, callsignAtEnd: %u, timeoutType: %d, callsignHoldoff: %u mins\n", callsignAtStart, callsignStartDelay, callsignAtEnd, timeoutType, callsignHoldoff); 132 | 133 | std::string readDevice, writeDevice; 134 | unsigned int audioDelay; 135 | config.getRadio(readDevice, writeDevice, audioDelay); 136 | ::printf("Soundcard set to %s:%s, delay: %u ms\n", readDevice.c_str(), writeDevice.c_str(), audioDelay * 20U); 137 | 138 | if (!readDevice.empty() && !writeDevice.empty()) { 139 | CSoundCardReaderWriter* soundcard = new CSoundCardReaderWriter(readDevice, writeDevice, ANALOGUE_RADIO_SAMPLE_RATE, ANALOGUE_RADIO_BLOCK_SIZE); 140 | soundcard->setCallback(m_thread); 141 | 142 | bool res = soundcard->open(); 143 | if (!res) { 144 | ::fprintf(stderr, "Cannot open the radio sound card\n"); 145 | return false; 146 | } 147 | 148 | m_thread->setRadio(soundcard, audioDelay); 149 | } 150 | 151 | std::string type, port; 152 | unsigned int pttDelay, squelchDelay; 153 | bool pttInvert, squelchInvert; 154 | config.getController(type, port, pttDelay, squelchDelay, pttInvert, squelchInvert); 155 | ::printf("Controller set to %s, port: %s, ptt delay: %u ms, squelch delay: %u ms, ptt invert: %d, squelch invert: %d\n", type.c_str(), port.c_str(), pttDelay * 20U, squelchInvert * 20U, pttInvert, squelchInvert); 156 | 157 | CExternalController* controller = NULL; 158 | 159 | if (type == "Arduino") { 160 | controller = new CExternalController(new CArduinoController(port), pttInvert, squelchInvert); 161 | } else if (type == "UDRC") { 162 | controller = new CExternalController(new CUDRCController, pttInvert, squelchInvert); 163 | } else { 164 | controller = new CExternalController(new CDummyController, pttInvert, squelchInvert); 165 | } 166 | 167 | bool res = controller->open(); 168 | if (!res) { 169 | ::fprintf(stderr, "Cannot open the hardware interface - %s\n", type.c_str()); 170 | return false; 171 | } 172 | 173 | m_thread->setController(controller, pttDelay, squelchDelay); 174 | 175 | #if !defined(_WIN32) && !defined(_WIN64) 176 | bool daemon = config.isDaemon(); 177 | if (daemon) { 178 | // Create new process 179 | pid_t pid = ::fork(); 180 | if (pid == -1) { 181 | ::fprintf(stderr, "Couldn't fork() , exiting\n"); 182 | return false; 183 | } else if (pid != 0) 184 | exit(EXIT_SUCCESS); 185 | 186 | // Create new session and process group 187 | if (::setsid() == -1) { 188 | ::fprintf(stderr, "Couldn't setsid(), exiting\n"); 189 | return false; 190 | } 191 | 192 | // Set the working directory to the root directory 193 | if (::chdir("/") == -1) { 194 | ::fprintf(stderr, "Couldn't cd /, exiting\n"); 195 | return false; 196 | } 197 | 198 | ::close(STDIN_FILENO); 199 | ::close(STDOUT_FILENO); 200 | ::close(STDERR_FILENO); 201 | 202 | //If we are currently root... 203 | if (getuid() == 0) { 204 | struct passwd* user = ::getpwnam("mmdvm"); 205 | if (user == NULL) { 206 | ::fprintf(stderr, "Could not get the mmdvm user, exiting\n"); 207 | return false; 208 | } 209 | 210 | uid_t mmdvm_uid = user->pw_uid; 211 | gid_t mmdvm_gid = user->pw_gid; 212 | 213 | //Set user and group ID's to mmdvm:mmdvm 214 | if (setgid(mmdvm_gid) != 0) { 215 | ::fprintf(stderr, "Could not set mmdvm GID, exiting\n"); 216 | return false; 217 | } 218 | 219 | if (setuid(mmdvm_uid) != 0) { 220 | ::fprintf(stderr, "Could not set mmdvm UID, exiting\n"); 221 | return false; 222 | } 223 | 224 | //Double check it worked (AKA Paranoia) 225 | if (setuid(0) != -1) { 226 | ::fprintf(stderr, "It's possible to regain root - something is wrong!, exiting\n"); 227 | return false; 228 | } 229 | } 230 | } 231 | #endif 232 | 233 | m_thread->run(); 234 | 235 | return true; 236 | } 237 | 238 | -------------------------------------------------------------------------------- /FMRepeater.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2011,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef FMRepeater_H 20 | #define FMRepeater_H 21 | 22 | #include "FMRepeaterThread.h" 23 | #include "FMDefines.h" 24 | 25 | #include 26 | 27 | class CFMRepeater { 28 | public: 29 | CFMRepeater(const std::string& filename); 30 | ~CFMRepeater(); 31 | 32 | bool run(); 33 | 34 | private: 35 | std::string m_filename; 36 | CFMRepeaterThread* m_thread; 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /FMRepeater.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | daemon=0 3 | 4 | [Id] 5 | callsign=GB3IN 6 | beacon=GB3IN IO93GD 7 | speed=14 8 | freq=1000 9 | levelHi=0.5 10 | levelLo=0.2 11 | # Callsign at start, 0=none, 1=on open, 2=on latch 12 | atStart=0 13 | startDelay=0 14 | # Callsign at end, 0 or 1 15 | atEnd=1 16 | # Callsign beacon time in minutes 17 | time=10 18 | # Time to inhibit callsign being sent, in minutes 19 | holdoff=6 20 | 21 | [Ack] 22 | # Typical styles are K, E, and T 23 | style=K 24 | speed=16 25 | freq=1750 26 | level=0.5 27 | # Delay in ms between incoming transmission ending and sending the ack 28 | delay=1000 29 | # The minimum length of a transmission before sending the ack, in seconds 30 | min=4 31 | 32 | [Times] 33 | # The time in seconds to hold the transmitter on after the last incoming transmission 34 | hang=6 35 | # The minimum transmission length in seconds before the repeater 'catches', 0 disables 36 | kerchunk=3 37 | 38 | [CTCSS] 39 | freq=71.9 40 | threshold=200 41 | level=0.02 42 | hang=100 43 | # 0=None, 1=When being used, 2=Audio only, 3=Always 44 | txMode=3 45 | 46 | [Timeout] 47 | # Timeout in seconds, 0 disables 48 | time=120 49 | # Telephone busy tones, 1=DL 2=UK 3=US 50 | style=2 51 | # How long to run tones for before TX drops, in seconds 52 | lockout=30 53 | 54 | [Audio] 55 | rxDevice=hw:CARD=udrc,DEV=0 56 | txDevice=hw:CARD=udrc,DEV=0 57 | # Audio delay in ms 58 | delay=120 59 | 60 | [Interface] 61 | # Values are Arduino and UDRC 62 | type=UDRC 63 | # Used for Arduino 64 | port=/dev/ttyS1 65 | # PTT signal delay in ms 66 | pttDelay=200 67 | # Squelch signal delay in ms 68 | sqDelay=100 69 | pttInvert=0 70 | sqInvert=0 71 | 72 | -------------------------------------------------------------------------------- /FMRepeater.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FMRepeater", "FMRepeater.vcxproj", "{83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Debug|x64.ActiveCfg = Debug|x64 17 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Debug|x64.Build.0 = Debug|x64 18 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Debug|x86.ActiveCfg = Debug|Win32 19 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Debug|x86.Build.0 = Debug|Win32 20 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Release|x64.ActiveCfg = Release|x64 21 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Release|x64.Build.0 = Release|x64 22 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Release|x86.ActiveCfg = Release|Win32 23 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0799CFAB-658B-446B-B910-65E2224A9FF2} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /FMRepeater.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {83B3AD93-3391-4EF5-8359-9DAFC87EF6D0} 24 | Win32Proj 25 | 26 | 27 | 28 | Application 29 | true 30 | v141 31 | 32 | 33 | Application 34 | false 35 | v141 36 | 37 | 38 | Application 39 | true 40 | v141 41 | 42 | 43 | Application 44 | false 45 | v141 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | true 67 | ..\portaudio\include;$(IncludePath) 68 | ..\portaudio\build\msvc\Win32\Debug;$(LibraryPath) 69 | 70 | 71 | true 72 | ..\portaudio\include;$(IncludePath) 73 | ..\portaudio\build\msvc\Win32\Release;$(LibraryPath) 74 | 75 | 76 | ..\portaudio\include;$(IncludePath) 77 | ..\portaudio\build\msvc\x64\Debug;$(LibraryPath) 78 | 79 | 80 | ..\portaudio\include;$(IncludePath) 81 | ..\portaudio\build\msvc\x64\Release;$(LibraryPath) 82 | 83 | 84 | 85 | _CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;%(PreprocessorDefinitions) 86 | MultiThreadedDebugDLL 87 | Level3 88 | ProgramDatabase 89 | Disabled 90 | 91 | 92 | MachineX86 93 | true 94 | Console 95 | portaudio_x86.lib;%(AdditionalDependencies) 96 | 97 | 98 | 99 | 100 | _CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;%(PreprocessorDefinitions) 101 | MultiThreadedDLL 102 | Level3 103 | ProgramDatabase 104 | 105 | 106 | MachineX86 107 | true 108 | Console 109 | true 110 | true 111 | portaudio_x86.lib;%(AdditionalDependencies) 112 | 113 | 114 | 115 | 116 | _CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;%(PreprocessorDefinitions) 117 | 118 | 119 | portaudio_x64.lib;%(AdditionalDependencies) 120 | 121 | 122 | 123 | 124 | _CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;%(PreprocessorDefinitions) 125 | 126 | 127 | portaudio_x64.lib;%(AdditionalDependencies) 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /FMRepeater.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | 14 | 15 | Source Files 16 | 17 | 18 | Source Files 19 | 20 | 21 | Source Files 22 | 23 | 24 | Source Files 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | 76 | 77 | Header Files 78 | 79 | 80 | Header Files 81 | 82 | 83 | Header Files 84 | 85 | 86 | Header Files 87 | 88 | 89 | Header Files 90 | 91 | 92 | Header Files 93 | 94 | 95 | Header Files 96 | 97 | 98 | Header Files 99 | 100 | 101 | Header Files 102 | 103 | 104 | Header Files 105 | 106 | 107 | Header Files 108 | 109 | 110 | Header Files 111 | 112 | 113 | Header Files 114 | 115 | 116 | Header Files 117 | 118 | 119 | Header Files 120 | 121 | 122 | Header Files 123 | 124 | 125 | Header Files 126 | 127 | 128 | Header Files 129 | 130 | 131 | Header Files 132 | 133 | 134 | Header Files 135 | 136 | 137 | Header Files 138 | 139 | 140 | Header Files 141 | 142 | 143 | Header Files 144 | 145 | 146 | Header Files 147 | 148 | 149 | Header Files 150 | 151 | 152 | -------------------------------------------------------------------------------- /FMRepeaterThread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "FMRepeaterThread.h" 20 | #include "StopWatch.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // A band pass filter created with 27 | // b = fir1(200, [ 300 / 24000 3000 / 24000 ]); 28 | 29 | const unsigned int FIRLP_LEN = 201U; 30 | const float FIRLP_TAPS[] = { 31 | 4.348089722683099e-004F, 4.135042495540001e-004F, 3.565734316721585e-004F, 32 | 2.697620683049211e-004F, 1.630208019098757e-004F, 4.963421717875377e-005F, 33 | -5.511664728362165e-005F, -1.357146691842085e-004F, -1.784720386611927e-004F, 34 | -1.736941634835989e-004F, -1.176523500371192e-004F, -1.402211853398311e-005F, 35 | 1.255561999118871e-004F, 2.820687925363604e-004F, 4.310038699806798e-004F, 36 | 5.454413649542267e-004F, 6.000529950196235e-004F, 5.754846676938578e-004F, 37 | 4.624257460107961e-004F, 2.646027935871826e-004F, -8.998878026856994e-019F, 38 | -3.001857568578321e-004F, -5.950169554549807e-004F, -8.394648920034268e-004F, 39 | -9.915596586982927e-004F, -1.019998307501092e-003F, -9.109416298854615e-004F, 40 | -6.727015058714779e-004F, -3.372223785104403e-004F, 4.231523099903672e-005F, 41 | 3.978432840308519e-004F, 6.560960944864116e-004F, 7.503714920446452e-004F, 42 | 6.325803609781686e-004F, 2.835429460363310e-004F, -2.804399678111389e-004F, 43 | -1.006291058992636e-003F, -1.808947908432476e-003F, -2.582095951203979e-003F, 44 | -3.213662476370489e-003F, -3.603952054699695e-003F, -3.683632046000646e-003F, 45 | -3.428514864134490e-003F, -2.868316566104280e-003F, -2.087306999884778e-003F, 46 | -1.215927593116136e-003F, -4.138814968899417e-004F, 1.533200345187720e-004F, 47 | 3.410866837681884e-004F, 5.162936712129394e-005F, -7.453774810594479e-004F, 48 | -2.001260159459314e-003F, -3.588693388062809e-003F, -5.314977400685459e-003F, 49 | -6.947255754887167e-003F, -8.246635510914082e-003F, -9.006443163121124e-003F, 50 | -9.088753480449461e-003F, -8.453095966632742e-003F, -7.171983753681457e-003F, 51 | -5.429581110071774e-003F, -3.502232559034098e-003F, -1.722388887843640e-003F, 52 | -4.302625426330871e-004F, 8.012281697951944e-005F, -3.876344919835156e-004F, 53 | -1.891805835793981e-003F, -4.330331749140149e-003F, -7.442040563048283e-003F, 54 | -1.083314186121884e-002F, -1.402704694735273e-002F, -1.653176933584757e-002F, 55 | -1.791595309144118e-002F, -1.788250347606054e-002F, -1.632825233260431e-002F, 56 | -1.337927103416548e-002F, -9.394287349331992e-003F, -4.932853682002187e-003F, 57 | -6.899140995522271e-004F, 2.596466785959089e-003F, 4.252970468674810e-003F, 58 | 3.780101653662344e-003F, 9.468283669403368e-004F, -4.143948225037351e-003F, 59 | -1.103074294656373e-002F, -1.891637020172199e-002F, -2.673890106758282e-002F, 60 | -3.328462589955223e-002F, -3.733082798472451e-002F, -3.780140989137119e-002F, 61 | -3.391573795286361e-002F, -2.531094064719834e-002F, -1.212043181384624e-002F, 62 | 5.003619980645199e-003F, 2.492938215129132e-002F, 4.614378276854996e-002F, 63 | 6.690036788706782e-002F, 8.540010514415053e-002F, 9.998405763108859e-002F, 64 | 1.093149075489733e-001F, 1.125253099317730e-001F, 1.093149075489733e-001F, 65 | 9.998405763108859e-002F, 8.540010514415053e-002F, 6.690036788706782e-002F, 66 | 4.614378276854996e-002F, 2.492938215129132e-002F, 5.003619980645199e-003F, 67 | -1.212043181384624e-002F, -2.531094064719834e-002F, -3.391573795286361e-002F, 68 | -3.780140989137119e-002F, -3.733082798472451e-002F, -3.328462589955223e-002F, 69 | -2.673890106758282e-002F, -1.891637020172199e-002F, -1.103074294656373e-002F, 70 | -4.143948225037351e-003F, 9.468283669403368e-004F, 3.780101653662344e-003F, 71 | 4.252970468674810e-003F, 2.596466785959089e-003F, -6.899140995522271e-004F, 72 | -4.932853682002187e-003F, -9.394287349331992e-003F, -1.337927103416548e-002F, 73 | -1.632825233260431e-002F, -1.788250347606054e-002F, -1.791595309144118e-002F, 74 | -1.653176933584757e-002F, -1.402704694735273e-002F, -1.083314186121884e-002F, 75 | -7.442040563048283e-003F, -4.330331749140149e-003F, -1.891805835793981e-003F, 76 | -3.876344919835156e-004F, 8.012281697951944e-005F, -4.302625426330871e-004F, 77 | -1.722388887843640e-003F, -3.502232559034098e-003F, -5.429581110071774e-003F, 78 | -7.171983753681457e-003F, -8.453095966632742e-003F, -9.088753480449461e-003F, 79 | -9.006443163121124e-003F, -8.246635510914082e-003F, -6.947255754887167e-003F, 80 | -5.314977400685459e-003F, -3.588693388062809e-003F, -2.001260159459314e-003F, 81 | -7.453774810594479e-004F, 5.162936712129394e-005F, 3.410866837681884e-004F, 82 | 1.533200345187720e-004F, -4.138814968899417e-004F, -1.215927593116136e-003F, 83 | -2.087306999884778e-003F, -2.868316566104280e-003F, -3.428514864134490e-003F, 84 | -3.683632046000646e-003F, -3.603952054699695e-003F, -3.213662476370489e-003F, 85 | -2.582095951203979e-003F, -1.808947908432476e-003F, -1.006291058992636e-003F, 86 | -2.804399678111389e-004F, 2.835429460363310e-004F, 6.325803609781686e-004F, 87 | 7.503714920446452e-004F, 6.560960944864116e-004F, 3.978432840308519e-004F, 88 | 4.231523099903672e-005F, -3.372223785104403e-004F, -6.727015058714779e-004F, 89 | -9.109416298854615e-004F, -1.019998307501092e-003F, -9.915596586982927e-004F, 90 | -8.394648920034268e-004F, -5.950169554549807e-004F, -3.001857568578321e-004F, 91 | -8.998878026856994e-019F, 2.646027935871826e-004F, 4.624257460107961e-004F, 92 | 5.754846676938578e-004F, 6.000529950196235e-004F, 5.454413649542267e-004F, 93 | 4.310038699806798e-004F, 2.820687925363604e-004F, 1.255561999118871e-004F, 94 | -1.402211853398311e-005F, -1.176523500371192e-004F, -1.736941634835989e-004F, 95 | -1.784720386611927e-004F, -1.357146691842085e-004F, -5.511664728362165e-005F, 96 | 4.963421717875377e-005F, 1.630208019098757e-004F, 2.697620683049211e-004F, 97 | 3.565734316721585e-004F, 4.135042495540001e-004F, 4.348089722683099e-004F}; 98 | 99 | 100 | CFMRepeaterThread::CFMRepeaterThread() : 101 | m_soundcard(NULL), 102 | m_controller(NULL), 103 | m_pttDelay(NULL), 104 | m_squelchDelay(NULL), 105 | m_stopped(true), 106 | m_state(ARS_LISTENING), 107 | m_radioInBuffer(ANALOGUE_RADIO_BLOCK_SIZE * 5U), 108 | m_radioOutBuffer(ANALOGUE_RADIO_BLOCK_SIZE * 5U), 109 | m_ctcssIn(NULL), 110 | m_ctcssOut(NULL), 111 | m_ctcssOutput(ACO_WHEN_OPEN), 112 | m_timeoutTones(NULL), 113 | m_callsignAudio(NULL), 114 | m_beaconAudio(NULL), 115 | m_ackAudio(NULL), 116 | m_timeoutTimer(1000U), 117 | m_lockoutTimer(1000U), 118 | m_callsignTimer(1000U), 119 | m_callsignStartTimer(1000U), 120 | m_hangTimer(1000U), 121 | m_kerchunkTimer(1000U), 122 | m_minimumTimer(1000U), 123 | m_ackTimer(1000U), 124 | m_idLevelHi(0.0F), 125 | m_idLevelLo(0.0F), 126 | m_ackLevel(0.0F), 127 | m_ctcssLevel(0.0F), 128 | m_radioAudioDelay(NULL), 129 | m_audioFilter(FIRLP_TAPS, FIRLP_LEN), 130 | m_callsignAtStart(ACS_NONE), 131 | m_callsignAtEnd(false), 132 | m_callsignHoldoff(6U), 133 | m_squelch(AS_CLOSED), 134 | m_radioTransmit(false), 135 | m_firstTime(false), 136 | m_sendOpen(false), 137 | m_sendClose(false), 138 | m_sendCallsign(false), 139 | m_sendBeacon(false), 140 | m_sendAck(ACK_NONE), 141 | m_ctcssHangTimer(1000U), 142 | m_shutdown(false) 143 | { 144 | } 145 | 146 | CFMRepeaterThread::~CFMRepeaterThread() 147 | { 148 | } 149 | 150 | void CFMRepeaterThread::run() 151 | { 152 | m_stopped = false; 153 | 154 | m_callsignTimer.start(); 155 | m_controller->setTransmit(false); 156 | m_controller->setActive(false); 157 | 158 | m_radioInBuffer.clear(); 159 | 160 | unsigned int counter = 0U; 161 | 162 | CStopWatch timer; 163 | 164 | try { 165 | for (;;) { 166 | timer.start(); 167 | 168 | float radioAudio[ANALOGUE_RADIO_BLOCK_SIZE]; 169 | unsigned int nRadio = 0U; 170 | getAudio(radioAudio, nRadio); 171 | 172 | // Set the watchdog port every one second 173 | counter++; 174 | if (counter == 50U) { 175 | m_controller->setHeartbeat(); 176 | counter = 0U; 177 | } 178 | 179 | // Check the shutdown state 180 | bool disable = m_controller->getDisable(); 181 | if (disable || m_shutdown) { 182 | if (m_state != ARS_SHUTDOWN) 183 | setState(ARS_SHUTDOWN); 184 | } else { 185 | if (m_state == ARS_SHUTDOWN) 186 | setState(ARS_LISTENING); 187 | } 188 | 189 | // If we're shutdown then don't go any further 190 | if (m_state == ARS_SHUTDOWN) 191 | continue; 192 | 193 | // Set the output state 194 | if (m_state != ARS_LISTENING || m_sendCallsign || m_sendOpen || m_sendClose || m_sendBeacon || m_callsignStartTimer.isRunning()) { 195 | m_controller->setActive(true); 196 | } else { 197 | m_controller->setActive(false); 198 | } 199 | 200 | // Detect a suitable access signal, 1750Hz, CTCSS, or carrier 201 | m_squelch = checkRadioSquelch(radioAudio, nRadio, m_squelch); 202 | 203 | stateMachine(); 204 | 205 | m_radioTransmit = m_state == ARS_RELAYING || 206 | m_state == ARS_WAITING || 207 | m_state == ARS_TIMEOUT || 208 | m_sendCallsign || m_sendBeacon || m_sendOpen || m_sendClose || m_callsignStartTimer.isRunning(); 209 | 210 | // The audio is chosen depending on the squelch and state 211 | if (m_state != ARS_RELAYING || m_squelch == AS_CLOSED) { 212 | // No open squelch, silence 213 | nRadio = ANALOGUE_RADIO_BLOCK_SIZE; 214 | ::memset(radioAudio, 0x00, ANALOGUE_RADIO_BLOCK_SIZE * sizeof(float)); 215 | } 216 | 217 | // From here onwards, the audio is destined for the radio only 218 | 219 | // Add tones 220 | sendTones(radioAudio, nRadio); 221 | 222 | // Filter the audio 223 | filterAudio(radioAudio, nRadio); 224 | 225 | // Insert CTCSS here because of the role off in the pre-emphasis filter 226 | insertCTCSS(radioAudio, nRadio); 227 | 228 | // Set the transmitter, and provide some audio 229 | feedRadio(radioAudio, nRadio); 230 | 231 | unsigned int ms = timer.elapsed(); 232 | clock(ms); 233 | } 234 | } 235 | catch (std::exception& e) { 236 | ::fprintf(stderr, "Exception raised - \"%s\"\n", e.what()); 237 | } 238 | catch (...) { 239 | ::fprintf(stderr, "Unknown exception raised\n"); 240 | } 241 | 242 | m_soundcard->close(); 243 | 244 | m_controller->setTransmit(false); 245 | m_controller->setActive(false); 246 | m_controller->close(); 247 | 248 | delete m_timeoutTones; 249 | delete m_soundcard; 250 | delete m_controller; 251 | delete m_radioAudioDelay; 252 | delete m_pttDelay; 253 | delete m_squelchDelay; 254 | delete m_ctcssIn; 255 | delete m_ctcssOut; 256 | delete m_callsignAudio; 257 | delete m_beaconAudio; 258 | delete m_ackAudio; 259 | } 260 | 261 | void CFMRepeaterThread::setCallsign(CCWKeyer* callsignAudio, CCWKeyer* beaconAudio, float levelHi, float levelLo) 262 | { 263 | m_callsignAudio = callsignAudio; 264 | m_beaconAudio = beaconAudio; 265 | 266 | m_idLevelHi = levelHi * MAX_AUDIO_LEVEL / 1.0F; // Max of 100% of full deviation 267 | m_idLevelLo = levelLo * MAX_AUDIO_LEVEL / 2.0F; // Max of 50% of full deviation 268 | } 269 | 270 | void CFMRepeaterThread::setAck(CCWKeyer* ackAudio, float level, unsigned int ack, unsigned int minimum) 271 | { 272 | m_ackAudio = ackAudio; 273 | 274 | // Add the ack delay to the minimum time, if it's set 275 | minimum *= 1000U; 276 | if (minimum > 0U) 277 | minimum += ack; 278 | 279 | m_ackTimer.setTimeout(0U, ack); 280 | m_minimumTimer.setTimeout(0U, minimum); 281 | m_ackLevel = level * MAX_AUDIO_LEVEL / 1.0F; // Max of 100% of full deviation 282 | } 283 | 284 | void CFMRepeaterThread::setTimes(unsigned int callsignTime, unsigned int timeout, unsigned int lockoutTime, unsigned int hangTime, unsigned int& kerchunkTime) 285 | { 286 | m_callsignTimer.setTimeout(callsignTime * 60U); 287 | m_timeoutTimer.setTimeout(timeout); 288 | m_lockoutTimer.setTimeout(lockoutTime); 289 | m_hangTimer.setTimeout(hangTime); 290 | m_kerchunkTimer.setTimeout(kerchunkTime); 291 | } 292 | 293 | void CFMRepeaterThread::setTones(float ctcssFreq, float ctcssThresh, float ctcssLevel, unsigned int ctcssHangTime, ANALOGUE_CTCSS_OUTPUT ctcssOutput) 294 | { 295 | ctcssThresh *= 500000.0F; // Based on testing by Jon, G4TSN 296 | 297 | m_ctcssOutput = ctcssOutput; 298 | 299 | if (ctcssFreq != 0.0F) 300 | m_ctcssIn = new CGoertzel(ANALOGUE_RADIO_SAMPLE_RATE, ctcssFreq, ANALOGUE_RADIO_SAMPLE_RATE / 5U, ctcssThresh); // 5 Hz B/W 301 | 302 | if (ctcssFreq != 0.0F && ctcssOutput != ACO_NONE) 303 | m_ctcssOut = new CNCO(ANALOGUE_RADIO_SAMPLE_RATE, ctcssFreq); 304 | 305 | // CTCSS deviation in the UK for 5 kHz total deviation is 500 Hz +-200 Hz 306 | m_ctcssLevel = ctcssLevel * MAX_AUDIO_LEVEL / 5.0F; // Max of 20% of full deviation 307 | 308 | m_ctcssHangTimer.setTimeout(0U, ctcssHangTime * 20U); 309 | } 310 | 311 | void CFMRepeaterThread::setFeel(ANALOGUE_CALLSIGN_START callsignAtStart, unsigned int callsignStartDelay, bool callsignAtEnd, ANALOGUE_TIMEOUT_TYPE timeoutStyle, unsigned int callsignHoldoff) 312 | { 313 | m_callsignAtStart = callsignAtStart; 314 | m_callsignAtEnd = callsignAtEnd; 315 | m_callsignHoldoff = callsignHoldoff; 316 | 317 | m_callsignStartTimer.setTimeout(callsignStartDelay); 318 | 319 | m_timeoutTones = new CTimeoutTones(ANALOGUE_RADIO_SAMPLE_RATE, timeoutStyle); 320 | } 321 | 322 | void CFMRepeaterThread::setRadio(CSoundCardReaderWriter* soundcard, unsigned int audioDelay) 323 | { 324 | assert(soundcard != NULL); 325 | 326 | m_soundcard = soundcard; 327 | 328 | if (audioDelay > 0U) 329 | m_radioAudioDelay = new CAudioDelay(audioDelay * ANALOGUE_FRAME_TIME_MS * ANALOGUE_RADIO_SAMPLE_RATE / 1000U); 330 | } 331 | 332 | void CFMRepeaterThread::setController(CExternalController* controller, unsigned int pttDelay, unsigned int squelchDelay) 333 | { 334 | assert(controller != NULL); 335 | 336 | m_controller = controller; 337 | 338 | if (pttDelay > 0U) 339 | m_pttDelay = new CPTTDelay(pttDelay); 340 | 341 | if (squelchDelay > 0U) 342 | m_squelchDelay = new CPTTDelay(squelchDelay); 343 | } 344 | 345 | void CFMRepeaterThread::readCallback(const float* input, unsigned int nSamples) 346 | { 347 | if (m_stopped) 348 | return; 349 | 350 | m_radioInBuffer.addData(input, nSamples); 351 | } 352 | 353 | void CFMRepeaterThread::writeCallback(float* output, int& nSamples) 354 | { 355 | if (nSamples == 0) 356 | return; 357 | 358 | ::memset(output, 0x00, nSamples * sizeof(float)); 359 | 360 | if (m_stopped) 361 | return; 362 | 363 | nSamples = m_radioOutBuffer.getData(output, nSamples); 364 | } 365 | 366 | void CFMRepeaterThread::getAudio(float* radioAudio, unsigned int& nRadio) 367 | { 368 | assert(radioAudio != NULL); 369 | 370 | nRadio = 0U; 371 | 372 | ::memset(radioAudio, 0x00, ANALOGUE_RADIO_BLOCK_SIZE * sizeof(float)); 373 | 374 | // Get radio audio 375 | for (unsigned int n = 0U; nRadio < ANALOGUE_RADIO_BLOCK_SIZE && n < 20U; n++) { 376 | nRadio += m_radioInBuffer.getData(radioAudio + nRadio, ANALOGUE_RADIO_BLOCK_SIZE - nRadio); 377 | 378 | if (nRadio < ANALOGUE_RADIO_BLOCK_SIZE) 379 | CThread::sleep(ANALOGUE_FRAME_TIME_MS / 4UL); 380 | } 381 | 382 | if (m_radioAudioDelay != NULL && nRadio > 0U) 383 | m_radioAudioDelay->delay(radioAudio, nRadio); 384 | 385 | if (nRadio < ANALOGUE_RADIO_BLOCK_SIZE) 386 | ::fprintf(stderr, "No radio audio is being received\n"); 387 | } 388 | 389 | ANALOGUE_SQUELCH CFMRepeaterThread::checkRadioSquelch(const float* audio, unsigned int length, ANALOGUE_SQUELCH squelch) 390 | { 391 | assert(audio != NULL); 392 | 393 | bool squelchIn = m_controller->getSquelch(); 394 | 395 | if (m_squelchDelay != NULL) 396 | squelchIn = m_squelchDelay->delay(squelchIn); 397 | 398 | if (!squelchIn) 399 | return AS_CLOSED; 400 | 401 | if (m_state == ARS_LISTENING) { 402 | // 1750Hz access 403 | } else { 404 | // Hardware squelch open 405 | if (squelchIn) 406 | return AS_CARRIER; 407 | } 408 | 409 | if (m_ctcssIn != NULL) { 410 | m_ctcssIn->process(audio, length); 411 | TRISTATE state = m_ctcssIn->getDetected(); 412 | 413 | if (squelchIn && state == STATE_TRUE) { 414 | m_ctcssHangTimer.start(); 415 | return AS_CTCSS; 416 | } 417 | 418 | if (squelchIn && state == STATE_UNKNOWN && squelch == AS_CTCSS) { 419 | m_ctcssHangTimer.start(); 420 | return AS_CTCSS; 421 | } 422 | 423 | if (squelchIn && m_ctcssHangTimer.isRunning() && !m_ctcssHangTimer.hasExpired()) 424 | return AS_CTCSS; 425 | 426 | if (m_ctcssHangTimer.isRunning() && m_ctcssHangTimer.hasExpired()) 427 | m_ctcssHangTimer.stop(); 428 | } 429 | 430 | return AS_CLOSED; 431 | } 432 | 433 | void CFMRepeaterThread::sendTones(float* audio, unsigned int length) 434 | { 435 | assert(audio != NULL); 436 | 437 | if (length == 0U) 438 | return; 439 | 440 | // Transmitting over the ack, abort it 441 | if (m_sendAck != ACK_NONE && m_squelch != AS_CLOSED) { 442 | m_sendAck = ACK_NONE; 443 | 444 | if (m_ackAudio != NULL) 445 | m_ackAudio->reset(); 446 | 447 | return; 448 | } 449 | 450 | // Do callsign here so that it pre-empts the ack, unless ack is already sending 451 | if (m_sendCallsign && m_sendAck != ACK_RADIO_SENDING) { 452 | unsigned int len; 453 | if (m_state == ARS_LISTENING || m_state == ARS_TIMEOUT || m_state == ARS_LOCKOUT) 454 | len = m_callsignAudio->getAudio(audio, length, m_idLevelHi); 455 | else 456 | len = m_callsignAudio->getAudio(audio, length, m_idLevelLo); 457 | 458 | if (len < length) { 459 | m_sendCallsign = false; 460 | m_callsignAudio->reset(); 461 | m_callsignTimer.start(); 462 | } 463 | 464 | return; 465 | } 466 | 467 | if (m_sendOpen && m_sendAck != ACK_RADIO_SENDING) { 468 | unsigned int len; 469 | if (m_state == ARS_LISTENING || m_state == ARS_TIMEOUT || m_state == ARS_LOCKOUT) 470 | len = m_callsignAudio->getAudio(audio, length, m_idLevelHi); 471 | else 472 | len = m_callsignAudio->getAudio(audio, length, m_idLevelLo); 473 | 474 | if (len < length) { 475 | m_sendOpen = false; 476 | m_callsignAudio->reset(); 477 | m_callsignTimer.start(); 478 | } 479 | 480 | return; 481 | } 482 | 483 | if (m_sendClose && m_sendAck != ACK_RADIO_SENDING) { 484 | unsigned int len; 485 | if (m_state == ARS_LISTENING || m_state == ARS_TIMEOUT || m_state == ARS_LOCKOUT) 486 | len = m_callsignAudio->getAudio(audio, length, m_idLevelHi); 487 | else 488 | len = m_callsignAudio->getAudio(audio, length, m_idLevelLo); 489 | 490 | if (len < length) { 491 | m_sendClose = false; 492 | m_callsignAudio->reset(); 493 | m_callsignTimer.start(); 494 | } 495 | 496 | return; 497 | } 498 | 499 | if (m_sendBeacon) { 500 | unsigned int len; 501 | if (m_state == ARS_LISTENING) 502 | len = m_beaconAudio->getAudio(audio, length, m_idLevelHi); 503 | else 504 | len = m_beaconAudio->getAudio(audio, length, m_idLevelLo); 505 | 506 | if (len < length) { 507 | m_beaconAudio->reset(); 508 | m_sendBeacon = false; 509 | m_callsignTimer.start(); 510 | return; 511 | } 512 | } 513 | 514 | if (m_sendAck == ACK_RADIO_WAITING || m_sendAck == ACK_RADIO_SENDING) { 515 | if (m_ackAudio == NULL) { 516 | m_sendAck = ACK_NONE; 517 | return; 518 | } 519 | 520 | unsigned int len = m_ackAudio->getAudio(audio, length, m_ackLevel); 521 | 522 | if (len < length) { 523 | m_sendAck = ACK_NONE; 524 | m_ackAudio->reset(); 525 | return; 526 | } 527 | 528 | m_sendAck = ACK_RADIO_SENDING; 529 | return; 530 | } 531 | 532 | // The timeout tone, here so that a callsign can pre-empt it 533 | if (m_state == ARS_TIMEOUT) 534 | m_timeoutTones->getAudio(audio, length, m_idLevelHi); 535 | } 536 | 537 | void CFMRepeaterThread::clock(unsigned int ms) 538 | { 539 | m_callsignTimer.clock(ms); 540 | if (m_callsignTimer.hasExpired()) { 541 | if (m_state == ARS_LISTENING) { 542 | if (!m_beaconAudio->isEmpty() && !m_sendBeacon) 543 | m_sendBeacon = true; 544 | } else { 545 | if (!m_callsignAudio->isEmpty() && !m_sendCallsign) 546 | m_sendCallsign = true; 547 | } 548 | } 549 | 550 | m_callsignStartTimer.clock(ms); 551 | if (m_callsignStartTimer.isRunning() && m_callsignStartTimer.hasExpired()) { 552 | m_sendOpen = true; 553 | m_callsignStartTimer.stop(); 554 | } 555 | 556 | m_hangTimer.clock(ms); 557 | m_ackTimer.clock(ms); 558 | m_ctcssHangTimer.clock(ms); 559 | 560 | if (m_squelch != AS_CLOSED) { 561 | m_kerchunkTimer.clock(ms); 562 | m_minimumTimer.clock(ms); 563 | m_timeoutTimer.clock(ms); 564 | m_lockoutTimer.clock(ms); 565 | } 566 | } 567 | 568 | void CFMRepeaterThread::stateMachine() 569 | { 570 | switch (m_state) { 571 | case ARS_SHUTDOWN: 572 | break; 573 | 574 | case ARS_LISTENING: 575 | if (m_squelch != AS_CLOSED) { 576 | // Squelch open 577 | if (m_callsignAtStart == ACS_OPEN) 578 | sendOpen(); 579 | 580 | setState(ARS_RELAYING); 581 | } 582 | break; 583 | 584 | case ARS_RELAYING: 585 | if (m_squelch != AS_CLOSED) { 586 | // Squelch open 587 | if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { 588 | // The input has timed out 589 | setState(ARS_TIMEOUT); 590 | } 591 | } else { 592 | // Squelch closed 593 | if (m_firstTime && m_kerchunkTimer.isRunning() && !m_kerchunkTimer.hasExpired()) { 594 | // Transmission not long enough, closedown 595 | setState(ARS_LISTENING); 596 | } else { 597 | // If first time and the latch was running, send a callsign if needed 598 | if (m_firstTime && m_kerchunkTimer.isRunning() && m_kerchunkTimer.hasExpired()) { 599 | if (m_callsignAtStart == ACS_LATCH) 600 | sendOpen(); 601 | } 602 | 603 | // Signal long enough, or not first time 604 | setState(ARS_WAITING); 605 | } 606 | } 607 | break; 608 | 609 | case ARS_WAITING: 610 | if (m_squelch != AS_CLOSED) { 611 | // Squelch open 612 | setState(ARS_RELAYING); 613 | } else { 614 | // Squelch closed, is it time for the ack? 615 | if (m_ackTimer.hasExpired()) { 616 | // Only send the audible ack after the minimum time 617 | if (!m_minimumTimer.isRunning() || (m_minimumTimer.isRunning() && m_minimumTimer.hasExpired())) 618 | m_sendAck = ACK_RADIO_WAITING; 619 | 620 | m_ackTimer.stop(); 621 | m_timeoutTimer.stop(); 622 | m_lockoutTimer.stop(); 623 | m_hangTimer.start(); 624 | m_minimumTimer.stop(); 625 | } 626 | if (m_hangTimer.hasExpired()) { 627 | // Shutdown 628 | if (m_callsignAtEnd) 629 | sendClose(); 630 | setState(ARS_LISTENING); 631 | } 632 | } 633 | break; 634 | 635 | case ARS_TIMEOUT: 636 | if (m_squelch == AS_CLOSED) { 637 | setState(ARS_WAITING); 638 | } else { 639 | if (m_lockoutTimer.hasExpired()) { 640 | sendCallsign(); 641 | setState(ARS_LOCKOUT); 642 | } 643 | } 644 | break; 645 | 646 | case ARS_LOCKOUT: 647 | if (m_squelch == AS_CLOSED) 648 | setState(ARS_WAITING); 649 | break; 650 | } 651 | } 652 | 653 | void CFMRepeaterThread::setState(ANALOGUE_RPT_STATE state) 654 | { 655 | // The 'from' state 656 | switch (m_state) { 657 | case ARS_SHUTDOWN: 658 | m_ackTimer.stop(); 659 | m_hangTimer.stop(); 660 | m_kerchunkTimer.stop(); 661 | m_minimumTimer.stop(); 662 | m_timeoutTimer.stop(); 663 | m_lockoutTimer.stop(); 664 | m_callsignTimer.start(); 665 | m_callsignStartTimer.stop(); 666 | m_radioTransmit = false; 667 | m_sendCallsign = false; 668 | m_sendBeacon = false; 669 | m_sendOpen = false; 670 | m_sendClose = false; 671 | m_sendAck = ACK_NONE; 672 | break; 673 | 674 | case ARS_LISTENING: 675 | m_kerchunkTimer.start(); 676 | m_firstTime = true; 677 | break; 678 | 679 | case ARS_TIMEOUT: 680 | m_timeoutTimer.stop(); 681 | m_lockoutTimer.stop(); 682 | break; 683 | 684 | case ARS_LOCKOUT: 685 | m_lockoutTimer.stop(); 686 | break; 687 | 688 | default: 689 | break; 690 | } 691 | 692 | // The 'to' state 693 | switch (state) { 694 | case ARS_SHUTDOWN: 695 | m_ackTimer.stop(); 696 | m_hangTimer.stop(); 697 | m_kerchunkTimer.stop(); 698 | m_minimumTimer.stop(); 699 | m_timeoutTimer.stop(); 700 | m_lockoutTimer.stop(); 701 | m_callsignTimer.stop(); 702 | m_callsignStartTimer.stop(); 703 | m_radioTransmit = false; 704 | m_sendCallsign = false; 705 | m_sendBeacon = false; 706 | m_sendOpen = false; 707 | m_sendClose = false; 708 | m_sendAck = ACK_NONE; 709 | m_state = ARS_SHUTDOWN; 710 | m_controller->setTransmit(false); 711 | m_controller->setActive(false); 712 | break; 713 | 714 | case ARS_LISTENING: 715 | m_ackTimer.stop(); 716 | m_kerchunkTimer.stop(); 717 | m_kerchunkTimer.stop(); 718 | m_minimumTimer.stop(); 719 | m_timeoutTimer.stop(); 720 | m_lockoutTimer.stop(); 721 | m_state = ARS_LISTENING; 722 | break; 723 | 724 | case ARS_RELAYING: 725 | m_ackTimer.stop(); 726 | m_hangTimer.stop(); 727 | if (!m_minimumTimer.isRunning()) 728 | m_minimumTimer.start(); 729 | if (!m_timeoutTimer.isRunning()) 730 | m_timeoutTimer.start(); 731 | m_state = state; 732 | break; 733 | 734 | case ARS_WAITING: 735 | m_ackTimer.start(); 736 | m_kerchunkTimer.stop(); 737 | m_firstTime = false; 738 | m_state = state; 739 | break; 740 | 741 | case ARS_TIMEOUT: 742 | m_lockoutTimer.start(); 743 | m_state = state; 744 | break; 745 | 746 | case ARS_LOCKOUT: 747 | m_lockoutTimer.stop(); 748 | m_state = state; 749 | break; 750 | } 751 | } 752 | 753 | void CFMRepeaterThread::feedRadio(const float* audio, unsigned int length) 754 | { 755 | assert(audio != NULL); 756 | 757 | if (m_radioTransmit) 758 | m_radioOutBuffer.addData(audio, length); 759 | 760 | if (m_pttDelay != NULL) 761 | m_controller->setTransmit(m_pttDelay->delay(m_radioTransmit)); 762 | else 763 | m_controller->setTransmit(m_radioTransmit); 764 | } 765 | 766 | void CFMRepeaterThread::insertCTCSS(float* audio, unsigned int length) 767 | { 768 | assert(audio != NULL); 769 | 770 | // Add the CTCSS to the output audio only when relaying user audio, not beacons or locked out 771 | 772 | if (length == 0U || m_ctcssOut == NULL || m_ctcssLevel == 0.0F) 773 | return; 774 | 775 | switch (m_ctcssOutput) { 776 | case ACO_WHEN_OPEN: 777 | switch (m_state) { 778 | // Always put CTCSS on if relaying audio 779 | case ARS_RELAYING: 780 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 781 | return; 782 | 783 | case ARS_TIMEOUT: 784 | // If the callsign at the end is disabled.... put the CTCSS on until the tx closedown 785 | if (!m_callsignAtEnd) { 786 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 787 | return; 788 | } 789 | 790 | // If the callsign at the end is enabled then shut off the CTCSS a second before it's sent 791 | if (!m_lockoutTimer.isRunning() || m_lockoutTimer.getRemaining() > 0U) 792 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 793 | 794 | return; 795 | 796 | case ARS_WAITING: 797 | // If the callsign at the end is disabled.... put the CTCSS on until the tx closedown 798 | if (!m_callsignAtEnd) { 799 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 800 | return; 801 | } 802 | 803 | // If the callsign at the end is enabled then shut off the CTCSS a second before it's sent 804 | if (!m_hangTimer.isRunning() || m_hangTimer.getRemaining() > 0U) 805 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 806 | 807 | return; 808 | 809 | default: 810 | // No CTCSS otherwise 811 | return; 812 | } 813 | break; 814 | 815 | case ACO_ON_AUDIO: 816 | if (m_state == ARS_RELAYING) 817 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 818 | break; 819 | 820 | case ACO_ALWAYS: 821 | if (m_radioTransmit) 822 | m_ctcssOut->getAudio(audio, length, m_ctcssLevel); 823 | break; 824 | 825 | default: 826 | break; 827 | } 828 | } 829 | 830 | void CFMRepeaterThread::filterAudio(float* audio, unsigned int length) 831 | { 832 | assert(audio != NULL); 833 | 834 | if (length == 0U) 835 | return; 836 | 837 | if (m_radioTransmit) 838 | m_audioFilter.process(audio, length); 839 | else 840 | m_audioFilter.reset(); 841 | } 842 | 843 | void CFMRepeaterThread::sendOpen() 844 | { 845 | // Already sending, or waiting to send, ignore request 846 | if (m_sendCallsign || m_sendBeacon || m_sendOpen || m_sendClose || m_callsignStartTimer.isRunning()) 847 | return; 848 | 849 | if (m_callsignHoldoff == 0U) { 850 | if (m_callsignStartTimer.getTimeout() > 0U) 851 | m_callsignStartTimer.start(); 852 | else 853 | m_sendOpen = true; 854 | return; 855 | } 856 | 857 | unsigned int t = m_callsignTimer.getTimer(); 858 | if (t >= m_callsignHoldoff) { 859 | if (m_callsignStartTimer.getTimeout() > 0U) 860 | m_callsignStartTimer.start(); 861 | else 862 | m_sendOpen = true; 863 | } 864 | } 865 | 866 | void CFMRepeaterThread::sendClose() 867 | { 868 | // Already sending, or waiting to send, ignore request 869 | if (m_sendCallsign || m_sendBeacon || m_sendOpen || m_sendClose || m_callsignStartTimer.isRunning()) 870 | return; 871 | 872 | if (m_callsignHoldoff == 0U) { 873 | m_sendClose = true; 874 | return; 875 | } 876 | 877 | unsigned int t = m_callsignTimer.getTimer(); 878 | if (t >= m_callsignHoldoff) 879 | m_sendClose = true; 880 | } 881 | 882 | void CFMRepeaterThread::sendCallsign() 883 | { 884 | // Already sending, or waiting to send, ignore request 885 | if (m_sendCallsign || m_sendBeacon || m_sendOpen || m_sendClose || m_callsignStartTimer.isRunning()) 886 | return; 887 | 888 | if (m_callsignHoldoff == 0U) { 889 | if (!m_callsignAudio->isEmpty()) 890 | m_sendCallsign = true; 891 | return; 892 | } 893 | 894 | unsigned int t = m_callsignTimer.getTimer(); 895 | if (t >= m_callsignHoldoff && !m_callsignAudio->isEmpty()) 896 | m_sendCallsign = true; 897 | } 898 | 899 | -------------------------------------------------------------------------------- /FMRepeaterThread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef FMRepeaterThread_H 20 | #define FMRepeaterThread_H 21 | 22 | #include "SoundCardReaderWriter.h" 23 | #include "ExternalController.h" 24 | #include "FMDefines.h" 25 | #include "AudioCallback.h" 26 | #include "TimeoutTones.h" 27 | #include "RingBuffer.h" 28 | #include "AudioDelay.h" 29 | #include "FIRFilter.h" 30 | #include "Goertzel.h" 31 | #include "PTTDelay.h" 32 | #include "CWKeyer.h" 33 | #include "Timer.h" 34 | #include "NCO.h" 35 | 36 | #include 37 | 38 | enum ACK_TYPE { 39 | ACK_NONE, 40 | ACK_RADIO_WAITING, 41 | ACK_RADIO_SENDING, 42 | ACK_BATTERY_WAITING, 43 | ACK_BATTERY_SENDING 44 | }; 45 | 46 | class CFMRepeaterThread : public IAudioCallback { 47 | public: 48 | CFMRepeaterThread(); 49 | virtual ~CFMRepeaterThread(); 50 | 51 | virtual void readCallback(const float* input, unsigned int nSamples); 52 | virtual void writeCallback(float* output, int& nSamples); 53 | 54 | virtual void setCallsign(CCWKeyer* callsign, CCWKeyer* beacon, float levelHi, float levelLo); 55 | virtual void setAck(CCWKeyer* ackAudio, float level, unsigned int ack, unsigned int minimum); 56 | virtual void setTimes(unsigned int callsignTime, unsigned int timeout, unsigned int lockoutTime, unsigned int hangTime, unsigned int& kerchunkTime); 57 | virtual void setTones(float ctcssFreq, float ctcssThresh, float ctcssLevel, unsigned int ctcssHangTime, ANALOGUE_CTCSS_OUTPUT ctcssOutput); 58 | virtual void setFeel(ANALOGUE_CALLSIGN_START callsignAtStart, unsigned int callsignStartDelay, bool callsignAtEnd, ANALOGUE_TIMEOUT_TYPE timeoutType, unsigned int callsignHoldoff); 59 | virtual void setRadio(CSoundCardReaderWriter* soundcard, unsigned int audioDelay); 60 | virtual void setController(CExternalController* controller, unsigned int pttDelay, unsigned int squelchDelay); 61 | 62 | virtual void run(); 63 | 64 | private: 65 | CSoundCardReaderWriter* m_soundcard; 66 | CExternalController* m_controller; 67 | CPTTDelay* m_pttDelay; 68 | CPTTDelay* m_squelchDelay; 69 | bool m_stopped; 70 | ANALOGUE_RPT_STATE m_state; 71 | CRingBuffer m_radioInBuffer; 72 | CRingBuffer m_radioOutBuffer; 73 | CGoertzel* m_ctcssIn; 74 | CNCO* m_ctcssOut; 75 | ANALOGUE_CTCSS_OUTPUT m_ctcssOutput; 76 | CTimeoutTones* m_timeoutTones; 77 | CCWKeyer* m_callsignAudio; 78 | CCWKeyer* m_beaconAudio; 79 | CCWKeyer* m_ackAudio; 80 | CTimer m_timeoutTimer; 81 | CTimer m_lockoutTimer; 82 | CTimer m_callsignTimer; 83 | CTimer m_callsignStartTimer; 84 | CTimer m_hangTimer; 85 | CTimer m_kerchunkTimer; 86 | CTimer m_minimumTimer; 87 | CTimer m_ackTimer; 88 | float m_idLevelHi; 89 | float m_idLevelLo; 90 | float m_ackLevel; 91 | float m_ctcssLevel; 92 | CAudioDelay* m_radioAudioDelay; 93 | CFIRFilter m_audioFilter; 94 | ANALOGUE_CALLSIGN_START m_callsignAtStart; 95 | bool m_callsignAtEnd; 96 | unsigned int m_callsignHoldoff; 97 | ANALOGUE_SQUELCH m_squelch; 98 | bool m_radioTransmit; 99 | bool m_firstTime; 100 | bool m_sendOpen; 101 | bool m_sendClose; 102 | bool m_sendCallsign; 103 | bool m_sendBeacon; 104 | ACK_TYPE m_sendAck; 105 | CTimer m_ctcssHangTimer; 106 | bool m_shutdown; 107 | 108 | void getAudio(float* audio, unsigned int& n); 109 | ANALOGUE_SQUELCH checkRadioSquelch(const float* audio, unsigned int length, ANALOGUE_SQUELCH squelch); 110 | void sendTones(float* audio, unsigned int length); 111 | void setState(ANALOGUE_RPT_STATE state); 112 | void stateMachine(); 113 | void feedRadio(const float* audio, unsigned int length); 114 | void insertCTCSS(float* audio, unsigned int length); 115 | void filterAudio(float* audio, unsigned int length); 116 | void sendOpen(); 117 | void sendClose(); 118 | void sendCallsign(); 119 | void clock(unsigned int ms); 120 | }; 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /Goertzel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "Goertzel.h" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | CGoertzel::CGoertzel(unsigned int sampleRate, float freq, unsigned int n, float threshold) : 21 | m_freq(freq), 22 | m_n(n), 23 | m_threshold(threshold), 24 | m_coeff(0.0F), 25 | m_q1(0.0F), 26 | m_q2(0.0F), 27 | m_value(0.0F), 28 | m_count(0U), 29 | m_result(false) 30 | { 31 | assert(threshold >= 0.0F); 32 | assert(sampleRate > 0U); 33 | assert(freq > 0.0F); 34 | assert(n > 0U); 35 | 36 | m_coeff = 2.0F * ::cos(2.0F * M_PI * freq / float(sampleRate)); 37 | } 38 | 39 | CGoertzel::~CGoertzel() 40 | { 41 | } 42 | 43 | void CGoertzel::setThreshold(float threshold) 44 | { 45 | assert(threshold >= 0.0F); 46 | 47 | m_threshold = threshold; 48 | } 49 | 50 | void CGoertzel::process(const float *data, unsigned int length) 51 | { 52 | assert(data != NULL); 53 | 54 | for (unsigned int i = 0U; i < length; i++) { 55 | float q0 = m_coeff * m_q1 - m_q2 + data[i]; 56 | m_q2 = m_q1; 57 | m_q1 = q0; 58 | 59 | m_count++; 60 | if (m_count == m_n) { 61 | float value = m_q1 * m_q1 + m_q2 * m_q2 - m_q1 * m_q2 * m_coeff; 62 | if (!m_result) 63 | m_value = value; 64 | if (m_result && value > m_value) 65 | m_value = value; 66 | 67 | // wxLogMessage(wxT("Tone detector: freq=%f Hz threshold=%f value=%f"), m_freq, m_threshold, value); 68 | 69 | m_result = true; 70 | m_count = 0U; 71 | m_q1 = 0.0F; 72 | m_q2 = 0.0F; 73 | } 74 | } 75 | } 76 | 77 | float CGoertzel::getResult() const 78 | { 79 | return m_value; 80 | } 81 | 82 | TRISTATE CGoertzel::getDetected() 83 | { 84 | if (!m_result) 85 | return STATE_UNKNOWN; 86 | 87 | if (m_value >= m_threshold) { 88 | // wxLogMessage(wxT("Goertzel: result = %f thresh = %f TRUE"), m_value, m_threshold); 89 | m_result = false; 90 | return STATE_TRUE; 91 | } else { 92 | // wxLogMessage(wxT("Goertzel: result = %f thresh = %f FALSE"), m_value, m_threshold); 93 | m_result = false; 94 | return STATE_FALSE; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Goertzel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2011,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef Goertzel_H 15 | #define Goertzel_H 16 | 17 | #include "Utils.h" 18 | 19 | class CGoertzel { 20 | public: 21 | CGoertzel(unsigned int sampleRate, float freq, unsigned int n, float threshold); 22 | ~CGoertzel(); 23 | 24 | void setThreshold(float threshold); 25 | 26 | float getResult() const; 27 | 28 | void process(const float* data, unsigned int length); 29 | 30 | TRISTATE getDetected(); 31 | 32 | private: 33 | float m_freq; // XXX 34 | unsigned int m_n; 35 | float m_threshold; 36 | float m_coeff; 37 | float m_q1; 38 | float m_q2; 39 | float m_value; 40 | unsigned int m_count; 41 | bool m_result; 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /HardwareController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "HardwareController.h" 20 | 21 | IHardwareController::~IHardwareController() 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /HardwareController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef HardwareController_H 15 | #define HardwareController_H 16 | 17 | class IHardwareController { 18 | public: 19 | virtual ~IHardwareController() = 0; 20 | 21 | virtual bool open() = 0; 22 | 23 | virtual bool getSquelch() = 0; 24 | virtual bool getDisable() = 0; 25 | 26 | virtual void setTransmit(bool value) = 0; 27 | virtual void setHeartbeat(bool value) = 0; 28 | virtual void setActive(bool value) = 0; 29 | 30 | virtual void close() = 0; 31 | 32 | private: 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DUDRC 3 | LIBS = -lpthread -lasound -lwiringPi 4 | LDFLAGS = -g 5 | 6 | OBJECTS = ArduinoController.o AudioDelay.o Config.o CWKeyer.o DummyController.o ExternalController.o FIRFilter.o \ 7 | FMRepeater.o FMRepeaterThread.o Goertzel.o HardwareController.o NCO.o PTTDelay.o SerialDataController.o \ 8 | SoundCardReaderWriter.o StopWatch.o Thread.o TimeoutTones.o Timer.o UDRCController.o 9 | 10 | all: FMRepeater 11 | 12 | FMRepeater: $(OBJECTS) 13 | $(CXX) $(OBJECTS) $(LDFLAGS) $(LIBS) -o FMRepeater 14 | 15 | %.o: %.cpp 16 | $(CXX) $(CFLAGS) -c -o $@ $< 17 | 18 | clean: 19 | $(RM) FMRepeater *.o *.d *.bak *~ 20 | 21 | -------------------------------------------------------------------------------- /NCO.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "NCO.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | CNCO::CNCO(unsigned int sampleRate, float freq) : 26 | m_audio(NULL), 27 | m_length(0U), 28 | m_position(0U) 29 | { 30 | assert(sampleRate > 0U); 31 | assert(freq > 0.0F); 32 | 33 | // Fill a buffer with 10 seconds of audio 34 | m_length = sampleRate * 10U; 35 | m_audio = new float[m_length]; 36 | 37 | float incr = 2.0F * float(M_PI) * freq / float(sampleRate); 38 | for (unsigned int i = 0U; i < m_length; i++) { 39 | float phase = incr * float(i); 40 | m_audio[i] = ::sin(phase); 41 | } 42 | } 43 | 44 | CNCO::~CNCO() 45 | { 46 | delete[] m_audio; 47 | } 48 | 49 | void CNCO::getAudio(float* audio, unsigned int length, float amplitude) 50 | { 51 | assert(audio != NULL); 52 | 53 | for (unsigned int i = 0U; i < length; i++) { 54 | audio[i] += m_audio[m_position++] * amplitude; 55 | 56 | if (m_position == m_length) 57 | m_position = 0U; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /NCO.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef NCO_H 20 | #define NCO_H 21 | 22 | class CNCO { 23 | public: 24 | CNCO(unsigned int sampleRate, float freq); 25 | ~CNCO(); 26 | 27 | void getAudio(float* audio, unsigned int length, float amplitude = 1.0F); 28 | 29 | private: 30 | float* m_audio; 31 | unsigned int m_length; 32 | unsigned int m_position; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /PTTDelay.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "PTTDelay.h" 15 | 16 | #include 17 | #include 18 | 19 | CPTTDelay::CPTTDelay(unsigned int delay) : 20 | m_delay(2U * delay), 21 | m_in(delay), 22 | m_out(0U), 23 | m_buffer(NULL) 24 | { 25 | assert(delay > 0U); 26 | 27 | m_buffer = new bool[m_delay]; 28 | 29 | for (unsigned int i = 0U; i < m_delay; i++) 30 | m_buffer[i] = false; 31 | } 32 | 33 | CPTTDelay::~CPTTDelay() 34 | { 35 | delete[] m_buffer; 36 | } 37 | 38 | bool CPTTDelay::delay(bool in) 39 | { 40 | bool out = m_buffer[m_out]; 41 | m_buffer[m_in] = in; 42 | 43 | m_in++; 44 | m_out++; 45 | 46 | if (m_in >= m_delay) 47 | m_in = 0U; 48 | if (m_out >= m_delay) 49 | m_out = 0U; 50 | 51 | return out; 52 | } 53 | -------------------------------------------------------------------------------- /PTTDelay.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef PTTDelay_H 20 | #define PTTDelay_H 21 | 22 | class CPTTDelay { 23 | public: 24 | CPTTDelay(unsigned int delay); 25 | ~CPTTDelay(); 26 | 27 | bool delay(bool in); 28 | 29 | private: 30 | unsigned int m_delay; 31 | unsigned int m_in; 32 | unsigned int m_out; 33 | bool* m_buffer; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | These are the source files for building the FMRepeater. This is a simplified version of the old Analogue Repeater that has existed on the OpenDV GitHub repository for many years. It has the following features: 3 | 4 | * Seperate id and beacon texts. 5 | * Timeout can be one of three styles, or no timeout. 6 | * Optional callsign at repeater open or close, or both. 7 | * Id may be suppressed if too close in time to a previous id. 8 | * Optional kerchunk timer. 9 | * Selectable acknowledgement style. 10 | * All ids and acknowledgements have selectable frequency and speed. 11 | * Minimum talk time before an acknowledgement is sent. 12 | * Built-in string audio bandpass filter. 13 | * Repeater control via an Arduino or a UDRC. 14 | * Audio I/O via any supported sound system, including the UDRC. 15 | 16 | It builds on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64. 17 | 18 | This software is licenced under the GPL v2 and is intended for amateur and educational use only. Use of this software for commercial purposes is strictly forbidden. 19 | 20 | -------------------------------------------------------------------------------- /RingBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006-2009,2012,2013,2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef RingBuffer_H 20 | #define RingBuffer_H 21 | 22 | #include 23 | 24 | enum RBSTATE { 25 | RBSTATE_EMPTY, 26 | RBSTATE_FULL, 27 | RBSTATE_DATA 28 | }; 29 | 30 | template class CRingBuffer { 31 | public: 32 | CRingBuffer(unsigned int length) : 33 | m_length(length), 34 | m_buffer(NULL), 35 | m_iPtr(0U), 36 | m_oPtr(0U), 37 | m_state(RBSTATE_EMPTY) 38 | { 39 | assert(length > 0U); 40 | 41 | m_buffer = new T[length]; 42 | 43 | ::memset(m_buffer, 0x00, length * sizeof(T)); 44 | } 45 | 46 | ~CRingBuffer() 47 | { 48 | delete[] m_buffer; 49 | } 50 | 51 | unsigned int addData(const T* buffer, unsigned int nSamples) 52 | { 53 | unsigned int space = freeSpace(); 54 | 55 | if (nSamples > space) 56 | return 0U; 57 | 58 | m_state = (nSamples == space) ? RBSTATE_FULL : RBSTATE_DATA; 59 | 60 | for (unsigned int i = 0U; i < nSamples; i++) { 61 | m_buffer[m_iPtr++] = buffer[i]; 62 | 63 | if (m_iPtr == m_length) 64 | m_iPtr = 0U; 65 | } 66 | 67 | return nSamples; 68 | } 69 | 70 | unsigned int getData(T* buffer, unsigned int nSamples) 71 | { 72 | unsigned int space = dataSpace(); 73 | 74 | if (space < nSamples) 75 | return 0U; 76 | 77 | m_state = (nSamples == space) ? RBSTATE_EMPTY : RBSTATE_DATA; 78 | 79 | for (unsigned int i = 0U; i < nSamples; i++) { 80 | buffer[i] = m_buffer[m_oPtr++]; 81 | 82 | if (m_oPtr == m_length) 83 | m_oPtr = 0U; 84 | } 85 | 86 | return nSamples; 87 | } 88 | 89 | unsigned int peek(T* buffer, unsigned int nSamples) 90 | { 91 | unsigned int space = dataSpace(); 92 | 93 | if (space < nSamples) 94 | return 0U; 95 | 96 | unsigned int ptr = m_oPtr; 97 | for (unsigned int i = 0U; i < nSamples; i++) { 98 | buffer[i] = m_buffer[ptr++]; 99 | 100 | if (ptr == m_length) 101 | ptr = 0U; 102 | } 103 | 104 | return nSamples; 105 | } 106 | 107 | void clear() 108 | { 109 | m_iPtr = 0U; 110 | m_oPtr = 0U; 111 | m_state = RBSTATE_EMPTY; 112 | 113 | ::memset(m_buffer, 0x00, m_length * sizeof(T)); 114 | } 115 | 116 | unsigned int freeSpace() 117 | { 118 | if (isEmpty()) 119 | return m_length; 120 | 121 | if (isFull()) 122 | return 0U; 123 | 124 | if (m_oPtr > m_iPtr) 125 | return m_oPtr - m_iPtr; 126 | 127 | return m_length - (m_iPtr - m_oPtr); 128 | } 129 | 130 | unsigned int dataSpace() 131 | { 132 | if (isEmpty()) 133 | return 0U; 134 | 135 | if (isFull()) 136 | return m_length; 137 | 138 | if (m_iPtr >= m_oPtr) 139 | return m_iPtr - m_oPtr; 140 | 141 | return m_length - (m_oPtr - m_iPtr); 142 | } 143 | 144 | bool isEmpty() 145 | { 146 | return m_state == RBSTATE_EMPTY; 147 | } 148 | 149 | bool isFull() 150 | { 151 | return m_state == RBSTATE_FULL; 152 | } 153 | 154 | private: 155 | unsigned int m_length; 156 | T* m_buffer; 157 | volatile unsigned int m_iPtr; 158 | volatile unsigned int m_oPtr; 159 | volatile RBSTATE m_state; 160 | }; 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /SerialDataController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2002-2004,2007-2011,2013,2015,2018 by Jonathan Naylor G4KLX 3 | * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include "SerialDataController.h" 21 | 22 | #include 23 | 24 | #if defined(_WIN32) || defined(_WIN64) 25 | #include 26 | #include 27 | #else 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #endif 35 | 36 | #include 37 | #include 38 | 39 | #if defined(_WIN32) || defined(_WIN64) 40 | 41 | const unsigned int BUFFER_LENGTH = 1000U; 42 | 43 | CSerialDataController::CSerialDataController(const std::string& device, SERIAL_SPEED speed) : 44 | m_device(device), 45 | m_speed(speed), 46 | m_handle(INVALID_HANDLE_VALUE), 47 | m_readOverlapped(), 48 | m_writeOverlapped(), 49 | m_readBuffer(NULL), 50 | m_readLength(0U), 51 | m_readPending(false) 52 | { 53 | assert(!device.empty()); 54 | 55 | m_readBuffer = new unsigned char[BUFFER_LENGTH]; 56 | } 57 | 58 | CSerialDataController::~CSerialDataController() 59 | { 60 | delete[] m_readBuffer; 61 | } 62 | 63 | bool CSerialDataController::open() 64 | { 65 | assert(m_handle == INVALID_HANDLE_VALUE); 66 | 67 | DWORD errCode; 68 | 69 | std::string baseName = m_device.substr(4U); // Convert "\\.\COM10" to "COM10" 70 | 71 | m_handle = ::CreateFile(m_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); 72 | if (m_handle == INVALID_HANDLE_VALUE) { 73 | ::fprintf(stderr, "Cannot open device - %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 74 | return false; 75 | } 76 | 77 | if (!::SetupComm(m_handle, 32768UL, 32768UL)) { 78 | ::fprintf(stderr, "Cannot set the communications parameters for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 79 | ::ClearCommError(m_handle, &errCode, NULL); 80 | ::CloseHandle(m_handle); 81 | return false; 82 | } 83 | 84 | DWORD size = sizeof(COMMCONFIG); 85 | 86 | COMMCONFIG config; 87 | ::memset(&config, 0x00U, sizeof(COMMCONFIG)); 88 | config.dwSize = size; 89 | 90 | if (!::GetDefaultCommConfig(baseName.c_str(), &config, &size)) { 91 | ::fprintf(stderr, "Cannot get the default comm config for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 92 | ::ClearCommError(m_handle, &errCode, NULL); 93 | ::CloseHandle(m_handle); 94 | return false; 95 | } 96 | 97 | if (!::SetCommConfig(m_handle, &config, size)) { 98 | ::fprintf(stderr, "Cannot set the comm config for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 99 | ::ClearCommError(m_handle, &errCode, NULL); 100 | ::CloseHandle(m_handle); 101 | return false; 102 | } 103 | 104 | DCB dcb; 105 | if (::GetCommState(m_handle, &dcb) == 0) { 106 | ::fprintf(stderr, "Cannot get the attributes for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 107 | ::ClearCommError(m_handle, &errCode, NULL); 108 | ::CloseHandle(m_handle); 109 | return false; 110 | } 111 | 112 | dcb.BaudRate = DWORD(m_speed); 113 | dcb.ByteSize = 8; 114 | dcb.Parity = NOPARITY; 115 | dcb.fParity = FALSE; 116 | dcb.StopBits = ONESTOPBIT; 117 | dcb.fInX = FALSE; 118 | dcb.fOutX = FALSE; 119 | dcb.fOutxCtsFlow = FALSE; 120 | dcb.fOutxDsrFlow = FALSE; 121 | dcb.fDtrControl = DTR_CONTROL_DISABLE; 122 | dcb.fRtsControl = RTS_CONTROL_DISABLE; 123 | 124 | if (::SetCommState(m_handle, &dcb) == 0) { 125 | ::fprintf(stderr, "Cannot set the attributes for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 126 | ::ClearCommError(m_handle, &errCode, NULL); 127 | ::CloseHandle(m_handle); 128 | return false; 129 | } 130 | 131 | COMMTIMEOUTS timeouts; 132 | if (!::GetCommTimeouts(m_handle, &timeouts)) { 133 | ::fprintf(stderr, "Cannot get the timeouts for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 134 | ::ClearCommError(m_handle, &errCode, NULL); 135 | ::CloseHandle(m_handle); 136 | return false; 137 | } 138 | 139 | timeouts.ReadIntervalTimeout = MAXDWORD; 140 | timeouts.ReadTotalTimeoutMultiplier = 0UL; 141 | timeouts.ReadTotalTimeoutConstant = 0UL; 142 | 143 | if (!::SetCommTimeouts(m_handle, &timeouts)) { 144 | ::fprintf(stderr, "Cannot set the timeouts for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 145 | ::ClearCommError(m_handle, &errCode, NULL); 146 | ::CloseHandle(m_handle); 147 | return false; 148 | } 149 | 150 | if (::EscapeCommFunction(m_handle, CLRDTR) == 0) { 151 | ::fprintf(stderr, "Cannot clear DTR for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 152 | ::ClearCommError(m_handle, &errCode, NULL); 153 | ::CloseHandle(m_handle); 154 | return false; 155 | } 156 | 157 | if (::EscapeCommFunction(m_handle, CLRRTS) == 0) { 158 | ::fprintf(stderr, "Cannot clear RTS for %s, err=%04lx\n", m_device.c_str(), ::GetLastError()); 159 | ::ClearCommError(m_handle, &errCode, NULL); 160 | ::CloseHandle(m_handle); 161 | return false; 162 | } 163 | 164 | ::ClearCommError(m_handle, &errCode, NULL); 165 | 166 | ::memset(&m_readOverlapped, 0x00U, sizeof(OVERLAPPED)); 167 | ::memset(&m_writeOverlapped, 0x00U, sizeof(OVERLAPPED)); 168 | 169 | m_readOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); 170 | m_writeOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); 171 | 172 | m_readLength = 0U; 173 | m_readPending = false; 174 | ::memset(m_readBuffer, 0x00U, BUFFER_LENGTH); 175 | 176 | return true; 177 | } 178 | 179 | int CSerialDataController::read(unsigned char* buffer, unsigned int length) 180 | { 181 | assert(m_handle != INVALID_HANDLE_VALUE); 182 | assert(buffer != NULL); 183 | 184 | unsigned int ptr = 0U; 185 | 186 | while (ptr < length) { 187 | int ret = readNonblock(buffer + ptr, length - ptr); 188 | if (ret < 0) { 189 | return ret; 190 | } else if (ret == 0) { 191 | if (ptr == 0U) 192 | return 0; 193 | } else { 194 | ptr += ret; 195 | } 196 | } 197 | 198 | return int(length); 199 | } 200 | 201 | int CSerialDataController::readNonblock(unsigned char* buffer, unsigned int length) 202 | { 203 | assert(m_handle != INVALID_HANDLE_VALUE); 204 | assert(buffer != NULL); 205 | 206 | if (length > BUFFER_LENGTH) 207 | length = BUFFER_LENGTH; 208 | 209 | if (m_readPending && length != m_readLength) { 210 | ::CancelIo(m_handle); 211 | m_readPending = false; 212 | } 213 | 214 | m_readLength = length; 215 | 216 | if (length == 0U) 217 | return 0; 218 | 219 | if (!m_readPending) { 220 | DWORD bytes = 0UL; 221 | BOOL res = ::ReadFile(m_handle, m_readBuffer, m_readLength, &bytes, &m_readOverlapped); 222 | if (res) { 223 | ::memcpy(buffer, m_readBuffer, bytes); 224 | return int(bytes); 225 | } 226 | 227 | DWORD error = ::GetLastError(); 228 | if (error != ERROR_IO_PENDING) { 229 | ::fprintf(stderr, "Error from ReadFile: %04lx\n", error); 230 | return -1; 231 | } 232 | 233 | m_readPending = true; 234 | } 235 | 236 | BOOL res = HasOverlappedIoCompleted(&m_readOverlapped); 237 | if (!res) 238 | return 0; 239 | 240 | DWORD bytes = 0UL; 241 | res = ::GetOverlappedResult(m_handle, &m_readOverlapped, &bytes, TRUE); 242 | if (!res) { 243 | ::fprintf(stderr, "Error from GetOverlappedResult (ReadFile): %04lx\n", ::GetLastError()); 244 | return -1; 245 | } 246 | 247 | ::memcpy(buffer, m_readBuffer, bytes); 248 | m_readPending = false; 249 | 250 | return int(bytes); 251 | } 252 | 253 | int CSerialDataController::write(const unsigned char* buffer, unsigned int length) 254 | { 255 | assert(m_handle != INVALID_HANDLE_VALUE); 256 | assert(buffer != NULL); 257 | 258 | if (length == 0U) 259 | return 0; 260 | 261 | unsigned int ptr = 0U; 262 | 263 | while (ptr < length) { 264 | DWORD bytes = 0UL; 265 | BOOL res = ::WriteFile(m_handle, buffer + ptr, length - ptr, &bytes, &m_writeOverlapped); 266 | if (!res) { 267 | DWORD error = ::GetLastError(); 268 | if (error != ERROR_IO_PENDING) { 269 | ::fprintf(stderr, "Error from WriteFile: %04lx\n", error); 270 | return -1; 271 | } 272 | 273 | res = ::GetOverlappedResult(m_handle, &m_writeOverlapped, &bytes, TRUE); 274 | if (!res) { 275 | ::fprintf(stderr, "Error from GetOverlappedResult (WriteFile): %04lx\n", ::GetLastError()); 276 | return -1; 277 | } 278 | } 279 | 280 | ptr += bytes; 281 | } 282 | 283 | return int(length); 284 | } 285 | 286 | void CSerialDataController::close() 287 | { 288 | assert(m_handle != INVALID_HANDLE_VALUE); 289 | 290 | ::CloseHandle(m_handle); 291 | m_handle = INVALID_HANDLE_VALUE; 292 | 293 | ::CloseHandle(m_readOverlapped.hEvent); 294 | ::CloseHandle(m_writeOverlapped.hEvent); 295 | } 296 | 297 | #else 298 | 299 | CSerialDataController::CSerialDataController(const std::string& device, SERIAL_SPEED speed) : 300 | m_device(device), 301 | m_speed(speed), 302 | m_fd(-1) 303 | { 304 | assert(!device.empty()); 305 | } 306 | 307 | CSerialDataController::~CSerialDataController() 308 | { 309 | } 310 | 311 | bool CSerialDataController::open() 312 | { 313 | assert(m_fd == -1); 314 | 315 | m_fd = ::open(m_device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); 316 | if (m_fd < 0) { 317 | ::fprintf(stderr, "Cannot open device - %s\n", m_device.c_str()); 318 | return false; 319 | } 320 | 321 | if (::isatty(m_fd) == 0) { 322 | ::fprintf(stderr, "%s is not a TTY device\n", m_device.c_str()); 323 | ::close(m_fd); 324 | return false; 325 | } 326 | 327 | termios termios; 328 | if (::tcgetattr(m_fd, &termios) < 0) { 329 | ::fprintf(stderr, "Cannot get the attributes for %s\n", m_device.c_str()); 330 | ::close(m_fd); 331 | return false; 332 | } 333 | 334 | termios.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); 335 | termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); 336 | termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); 337 | termios.c_cflag |= CS8; 338 | termios.c_oflag &= ~(OPOST); 339 | termios.c_cc[VMIN] = 0; 340 | termios.c_cc[VTIME] = 10; 341 | 342 | switch (m_speed) { 343 | case SERIAL_1200: 344 | ::cfsetospeed(&termios, B1200); 345 | ::cfsetispeed(&termios, B1200); 346 | break; 347 | case SERIAL_2400: 348 | ::cfsetospeed(&termios, B2400); 349 | ::cfsetispeed(&termios, B2400); 350 | break; 351 | case SERIAL_4800: 352 | ::cfsetospeed(&termios, B4800); 353 | ::cfsetispeed(&termios, B4800); 354 | break; 355 | case SERIAL_9600: 356 | ::cfsetospeed(&termios, B9600); 357 | ::cfsetispeed(&termios, B9600); 358 | break; 359 | case SERIAL_19200: 360 | ::cfsetospeed(&termios, B19200); 361 | ::cfsetispeed(&termios, B19200); 362 | break; 363 | case SERIAL_38400: 364 | ::cfsetospeed(&termios, B38400); 365 | ::cfsetispeed(&termios, B38400); 366 | break; 367 | case SERIAL_115200: 368 | ::cfsetospeed(&termios, B115200); 369 | ::cfsetispeed(&termios, B115200); 370 | break; 371 | case SERIAL_230400: 372 | ::cfsetospeed(&termios, B230400); 373 | ::cfsetispeed(&termios, B230400); 374 | break; 375 | default: 376 | ::fprintf(stderr, "Unsupported serial port speed - %d\n", int(m_speed)); 377 | ::close(m_fd); 378 | return false; 379 | } 380 | 381 | if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { 382 | ::fprintf(stderr, "Cannot set the attributes for %s\n", m_device.c_str()); 383 | ::close(m_fd); 384 | return false; 385 | } 386 | 387 | return true; 388 | } 389 | 390 | int CSerialDataController::read(unsigned char* buffer, unsigned int length) 391 | { 392 | assert(buffer != NULL); 393 | assert(m_fd != -1); 394 | 395 | if (length == 0U) 396 | return 0; 397 | 398 | unsigned int offset = 0U; 399 | 400 | while (offset < length) { 401 | fd_set fds; 402 | FD_ZERO(&fds); 403 | FD_SET(m_fd, &fds); 404 | 405 | int n; 406 | if (offset == 0U) { 407 | struct timeval tv; 408 | tv.tv_sec = 0; 409 | tv.tv_usec = 0; 410 | 411 | n = ::select(m_fd + 1, &fds, NULL, NULL, &tv); 412 | if (n == 0) 413 | return 0; 414 | } else { 415 | n = ::select(m_fd + 1, &fds, NULL, NULL, NULL); 416 | } 417 | 418 | if (n < 0) { 419 | ::fprintf(stderr, "Error from select(), errno=%d\n", errno); 420 | return -1; 421 | } 422 | 423 | if (n > 0) { 424 | ssize_t len = ::read(m_fd, buffer + offset, length - offset); 425 | if (len < 0) { 426 | if (errno != EAGAIN) { 427 | ::fprintf(stderr, "Error from read(), errno=%d\n", errno); 428 | return -1; 429 | } 430 | } 431 | 432 | if (len > 0) 433 | offset += len; 434 | } 435 | } 436 | 437 | return length; 438 | } 439 | 440 | int CSerialDataController::write(const unsigned char* buffer, unsigned int length) 441 | { 442 | assert(buffer != NULL); 443 | assert(m_fd != -1); 444 | 445 | if (length == 0U) 446 | return 0; 447 | 448 | unsigned int ptr = 0U; 449 | 450 | while (ptr < length) { 451 | ssize_t n = ::write(m_fd, buffer + ptr, length - ptr); 452 | if (n < 0) { 453 | if (errno != EAGAIN) { 454 | ::fprintf(stderr, "Error returned from write(), errno=%d\n", errno); 455 | return -1; 456 | } 457 | } 458 | 459 | if (n > 0) 460 | ptr += n; 461 | } 462 | 463 | return length; 464 | } 465 | 466 | void CSerialDataController::close() 467 | { 468 | assert(m_fd != -1); 469 | 470 | ::close(m_fd); 471 | m_fd = -1; 472 | } 473 | 474 | #endif 475 | -------------------------------------------------------------------------------- /SerialDataController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2002-2004,2007-2009,2011-2013,2015,2018 by Jonathan Naylor G4KLX 3 | * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #ifndef SerialDataController_H 21 | #define SerialDataController_H 22 | 23 | #include 24 | 25 | #if defined(_WIN32) || defined(_WIN64) 26 | #include 27 | #endif 28 | 29 | enum SERIAL_SPEED { 30 | SERIAL_1200 = 1200, 31 | SERIAL_2400 = 2400, 32 | SERIAL_4800 = 4800, 33 | SERIAL_9600 = 9600, 34 | SERIAL_19200 = 19200, 35 | SERIAL_38400 = 38400, 36 | SERIAL_76800 = 76800, 37 | SERIAL_115200 = 115200, 38 | SERIAL_230400 = 230400 39 | }; 40 | 41 | class CSerialDataController { 42 | public: 43 | CSerialDataController(const std::string& device, SERIAL_SPEED speed); 44 | ~CSerialDataController(); 45 | 46 | bool open(); 47 | 48 | int read(unsigned char* buffer, unsigned int length); 49 | int write(const unsigned char* buffer, unsigned int length); 50 | 51 | void close(); 52 | 53 | private: 54 | std::string m_device; 55 | SERIAL_SPEED m_speed; 56 | #if defined(_WIN32) || defined(_WIN64) 57 | HANDLE m_handle; 58 | OVERLAPPED m_readOverlapped; 59 | OVERLAPPED m_writeOverlapped; 60 | unsigned char* m_readBuffer; 61 | unsigned int m_readLength; 62 | bool m_readPending; 63 | #else 64 | int m_fd; 65 | #endif 66 | 67 | #if defined(_WIN32) || defined(_WIN64) 68 | int readNonblock(unsigned char* buffer, unsigned int length); 69 | #endif 70 | }; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /SoundCardReaderWriter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006-2010,2015,2018 by Jonathan Naylor G4KLX 3 | * Copyright (C) 2014 by John Wiseman, G8BPQ 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 | */ 19 | 20 | #include "SoundCardReaderWriter.h" 21 | 22 | #include 23 | #include 24 | 25 | #if (defined(__APPLE__) && defined(__MACH__)) || defined(_WIN32) || defined(_WIN64) 26 | 27 | static int scrwCallback(const void* input, void* output, unsigned long nSamples, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) 28 | { 29 | assert(userData != NULL); 30 | 31 | CSoundCardReaderWriter* object = reinterpret_cast(userData); 32 | 33 | object->callback(static_cast(input), static_cast(output), nSamples); 34 | 35 | return paContinue; 36 | } 37 | 38 | CSoundCardReaderWriter::CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize) : 39 | m_readDevice(readDevice), 40 | m_writeDevice(writeDevice), 41 | m_sampleRate(sampleRate), 42 | m_blockSize(blockSize), 43 | m_callback(NULL), 44 | m_stream(NULL) 45 | { 46 | } 47 | 48 | CSoundCardReaderWriter::~CSoundCardReaderWriter() 49 | { 50 | } 51 | 52 | void CSoundCardReaderWriter::setCallback(IAudioCallback* callback) 53 | { 54 | assert(callback != NULL); 55 | 56 | m_callback = callback; 57 | } 58 | 59 | bool CSoundCardReaderWriter::open() 60 | { 61 | PaError error = ::Pa_Initialize(); 62 | if (error != paNoError) { 63 | ::fprintf(stderr, "Cannot initialise PortAudio\n"); 64 | return false; 65 | } 66 | 67 | PaStreamParameters* pParamsIn = NULL; 68 | PaStreamParameters* pParamsOut = NULL; 69 | 70 | PaStreamParameters paramsIn; 71 | PaStreamParameters paramsOut; 72 | 73 | PaDeviceIndex inDev, outDev; 74 | bool res = convertNameToDevices(inDev, outDev); 75 | if (!res) { 76 | ::fprintf(stderr, "Cannot convert name to device\n"); 77 | return false; 78 | } 79 | 80 | if (inDev != -1) { 81 | const PaDeviceInfo* inInfo = ::Pa_GetDeviceInfo(inDev); 82 | if (inInfo == NULL) { 83 | ::fprintf(stderr, "Cannot get device information for the input device\n"); 84 | return false; 85 | } 86 | 87 | paramsIn.device = inDev; 88 | paramsIn.channelCount = 1; 89 | paramsIn.sampleFormat = paFloat32; 90 | paramsIn.hostApiSpecificStreamInfo = NULL; 91 | paramsIn.suggestedLatency = inInfo->defaultLowInputLatency; 92 | 93 | pParamsIn = ¶msIn; 94 | } 95 | 96 | if (outDev != -1) { 97 | const PaDeviceInfo* outInfo = ::Pa_GetDeviceInfo(outDev); 98 | if (outInfo == NULL) { 99 | ::fprintf(stderr, "Cannot get device information for the output device\n"); 100 | return false; 101 | } 102 | 103 | paramsOut.device = outDev; 104 | paramsOut.channelCount = 1; 105 | paramsOut.sampleFormat = paFloat32; 106 | paramsOut.hostApiSpecificStreamInfo = NULL; 107 | paramsOut.suggestedLatency = outInfo->defaultLowOutputLatency; 108 | 109 | pParamsOut = ¶msOut; 110 | } 111 | 112 | error = ::Pa_OpenStream(&m_stream, pParamsIn, pParamsOut, double(m_sampleRate), m_blockSize, paNoFlag, &scrwCallback, this); 113 | if (error != paNoError) { 114 | ::fprintf(stderr, "Cannot open the audios stream(s)\n"); 115 | ::Pa_Terminate(); 116 | return false; 117 | } 118 | 119 | error = ::Pa_StartStream(m_stream); 120 | if (error != paNoError) { 121 | ::fprintf(stderr, "Cannot start the audio stream(s)\n"); 122 | ::Pa_CloseStream(m_stream); 123 | m_stream = NULL; 124 | 125 | ::Pa_Terminate(); 126 | return false; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | void CSoundCardReaderWriter::close() 133 | { 134 | assert(m_stream != NULL); 135 | 136 | ::Pa_AbortStream(m_stream); 137 | 138 | ::Pa_CloseStream(m_stream); 139 | 140 | ::Pa_Terminate(); 141 | } 142 | 143 | void CSoundCardReaderWriter::callback(const float* input, float* output, unsigned int nSamples) 144 | { 145 | if (m_callback != NULL) { 146 | m_callback->readCallback(input, nSamples); 147 | m_callback->writeCallback(output, nSamples); 148 | } 149 | } 150 | 151 | bool CSoundCardReaderWriter::convertNameToDevices(PaDeviceIndex& inDev, PaDeviceIndex& outDev) 152 | { 153 | inDev = outDev = -1; 154 | 155 | #if defined(_WIN32) || defined(_WIN64) 156 | PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paDirectSound); 157 | #elif defined(__APPLE__) && defined(__MACH__) 158 | PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paCoreAudio); 159 | #else 160 | PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paALSA); 161 | #endif 162 | if (apiIndex == paHostApiNotFound) 163 | return false; 164 | 165 | PaDeviceIndex n = ::Pa_GetDeviceCount(); 166 | if (n <= 0) 167 | return false; 168 | 169 | for (PaDeviceIndex i = 0; i < n; i++) { 170 | const PaDeviceInfo* device = ::Pa_GetDeviceInfo(i); 171 | 172 | if (device->hostApi != apiIndex) 173 | continue; 174 | 175 | std::string name(device->name); 176 | 177 | if (!m_readDevice.empty() && m_readDevice == name && device->maxInputChannels > 0) 178 | inDev = i; 179 | 180 | if (!m_writeDevice.empty() && m_writeDevice == name && device->maxOutputChannels > 0) 181 | outDev = i; 182 | } 183 | 184 | if (inDev == -1 && outDev == -1) 185 | return false; 186 | 187 | return true; 188 | } 189 | 190 | #else 191 | 192 | CSoundCardReaderWriter::CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize) : 193 | m_readDevice(readDevice), 194 | m_writeDevice(writeDevice), 195 | m_sampleRate(sampleRate), 196 | m_blockSize(blockSize), 197 | m_callback(NULL), 198 | m_reader(NULL), 199 | m_writer(NULL) 200 | { 201 | assert(sampleRate > 0U); 202 | assert(blockSize > 0U); 203 | } 204 | 205 | CSoundCardReaderWriter::~CSoundCardReaderWriter() 206 | { 207 | } 208 | 209 | void CSoundCardReaderWriter::setCallback(IAudioCallback* callback) 210 | { 211 | assert(callback != NULL); 212 | 213 | m_callback = callback; 214 | } 215 | 216 | bool CSoundCardReaderWriter::open() 217 | { 218 | int err = 0; 219 | 220 | char buf1[100]; 221 | char buf2[100]; 222 | char* ptr; 223 | 224 | ::strcpy(buf1, (const char*)m_writeDevice.c_str()); 225 | ::strcpy(buf2, (const char*)m_readDevice.c_str()); 226 | 227 | ptr = ::strchr(buf1, ' '); 228 | if (ptr) *ptr = 0; // Get Device part of name 229 | 230 | ptr = ::strchr(buf2, ' '); 231 | if (ptr) *ptr = 0; // Get Device part of name 232 | 233 | std::string writeDevice(buf1); 234 | std::string readDevice(buf2); 235 | 236 | snd_pcm_t* playHandle = NULL; 237 | if ((err = ::snd_pcm_open(&playHandle, buf1, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { 238 | ::fprintf(stderr, "Cannot open playback audio device %s (%s)\n", writeDevice.c_str(), ::snd_strerror(err)); 239 | return false; 240 | } 241 | 242 | snd_pcm_hw_params_t* hw_params; 243 | if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) { 244 | ::fprintf(stderr, "Cannot allocate hardware parameter structure (%s)\n", ::snd_strerror(err)); 245 | return false; 246 | } 247 | 248 | if ((err = ::snd_pcm_hw_params_any(playHandle, hw_params)) < 0) { 249 | ::fprintf(stderr, "Cannot initialize hardware parameter structure (%s)\n", ::snd_strerror(err)); 250 | return false; 251 | } 252 | 253 | if ((err = ::snd_pcm_hw_params_set_access(playHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { 254 | ::fprintf(stderr, "Cannot set access type (%s)\n", ::snd_strerror(err)); 255 | return false; 256 | } 257 | 258 | if ((err = ::snd_pcm_hw_params_set_format(playHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { 259 | ::fprintf(stderr, "Cannot set sample format (%s)\n", ::snd_strerror(err)); 260 | return false; 261 | } 262 | 263 | if ((err = ::snd_pcm_hw_params_set_rate(playHandle, hw_params, m_sampleRate, 0)) < 0) { 264 | ::fprintf(stderr, "Cannot set sample rate (%s)\n", ::snd_strerror(err)); 265 | return false; 266 | } 267 | 268 | unsigned int playChannels = 1U; 269 | 270 | if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 1)) < 0) { 271 | playChannels = 2U; 272 | 273 | if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 2)) < 0) { 274 | ::fprintf(stderr, "Cannot play set channel count (%s)\n", ::snd_strerror(err)); 275 | return false; 276 | } 277 | } 278 | 279 | if ((err = ::snd_pcm_hw_params(playHandle, hw_params)) < 0) { 280 | ::fprintf(stderr, "Cannot set parameters (%s)\n", ::snd_strerror(err)); 281 | return false; 282 | } 283 | 284 | ::snd_pcm_hw_params_free(hw_params); 285 | 286 | if ((err = ::snd_pcm_prepare(playHandle)) < 0) { 287 | ::fprintf(stderr, "Cannot prepare audio interface for use (%s)\n", ::snd_strerror(err)); 288 | return false; 289 | } 290 | 291 | // Open Capture 292 | snd_pcm_t* recHandle = NULL; 293 | if ((err = ::snd_pcm_open(&recHandle, buf2, SND_PCM_STREAM_CAPTURE, 0)) < 0) { 294 | ::fprintf(stderr, "Cannot open capture audio device %s (%s)\n", readDevice.c_str(), ::snd_strerror(err)); 295 | return false; 296 | } 297 | 298 | if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) { 299 | ::fprintf(stderr, "Cannot allocate hardware parameter structure (%s)\n", ::snd_strerror(err)); 300 | return false; 301 | } 302 | 303 | if ((err = ::snd_pcm_hw_params_any(recHandle, hw_params)) < 0) { 304 | ::fprintf(stderr, "Cannot initialize hardware parameter structure (%s)\n", ::snd_strerror(err)); 305 | return false; 306 | } 307 | 308 | if ((err = ::snd_pcm_hw_params_set_access(recHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { 309 | ::fprintf(stderr, "Cannot set access type (%s)\n", ::snd_strerror(err)); 310 | return false; 311 | } 312 | 313 | if ((err = ::snd_pcm_hw_params_set_format(recHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { 314 | ::fprintf(stderr, "Cannot set sample format (%s)\n", ::snd_strerror(err)); 315 | return false; 316 | } 317 | 318 | if ((err = ::snd_pcm_hw_params_set_rate(recHandle, hw_params, m_sampleRate, 0)) < 0) { 319 | ::fprintf(stderr, "Cannot set sample rate (%s)\n", ::snd_strerror(err)); 320 | return false; 321 | } 322 | 323 | unsigned int recChannels = 1U; 324 | 325 | if ((err = ::snd_pcm_hw_params_set_channels(recHandle, hw_params, 1)) < 0) { 326 | recChannels = 2U; 327 | 328 | if ((err = ::snd_pcm_hw_params_set_channels (recHandle, hw_params, 2)) < 0) { 329 | ::fprintf(stderr, "Cannot rec set channel count (%s)\n", ::snd_strerror(err)); 330 | return false; 331 | } 332 | } 333 | 334 | if ((err = ::snd_pcm_hw_params(recHandle, hw_params)) < 0) { 335 | ::fprintf(stderr, "Cannot set parameters (%s)\n", ::snd_strerror(err)); 336 | return false; 337 | } 338 | 339 | ::snd_pcm_hw_params_free(hw_params); 340 | 341 | if ((err = ::snd_pcm_prepare(recHandle)) < 0) { 342 | ::fprintf(stderr, "Cannot prepare audio interface for use (%s)\n", ::snd_strerror(err)); 343 | return false; 344 | } 345 | 346 | short samples[256]; 347 | for (unsigned int i = 0U; i < 10U; ++i) 348 | ::snd_pcm_readi(recHandle, samples, 128); 349 | 350 | ::printf("Opened %s %s Rate %u\n", writeDevice.c_str(), readDevice.c_str(), m_sampleRate); 351 | 352 | m_reader = new CSoundCardReader(recHandle, m_blockSize, recChannels, m_callback); 353 | m_writer = new CSoundCardWriter(playHandle, m_blockSize, playChannels, m_callback); 354 | 355 | m_reader->run(); 356 | m_writer->run(); 357 | 358 | return true; 359 | } 360 | 361 | void CSoundCardReaderWriter::close() 362 | { 363 | m_reader->kill(); 364 | m_writer->kill(); 365 | 366 | m_reader->wait(); 367 | m_writer->wait(); 368 | } 369 | 370 | bool CSoundCardReaderWriter::isWriterBusy() const 371 | { 372 | return m_writer->isBusy(); 373 | } 374 | 375 | CSoundCardReader::CSoundCardReader(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback) : 376 | CThread(), 377 | m_handle(handle), 378 | m_blockSize(blockSize), 379 | m_channels(channels), 380 | m_callback(callback), 381 | m_killed(false), 382 | m_buffer(NULL), 383 | m_samples(NULL) 384 | { 385 | assert(handle != NULL); 386 | assert(blockSize > 0U); 387 | assert(channels == 1U || channels == 2U); 388 | assert(callback != NULL); 389 | 390 | m_buffer = new float[blockSize]; 391 | m_samples = new short[2U * blockSize]; 392 | } 393 | 394 | CSoundCardReader::~CSoundCardReader() 395 | { 396 | delete[] m_buffer; 397 | delete[] m_samples; 398 | } 399 | 400 | void CSoundCardReader::entry() 401 | { 402 | while (!m_killed) { 403 | snd_pcm_sframes_t ret; 404 | while ((ret = ::snd_pcm_readi(m_handle, m_samples, m_blockSize)) < 0) { 405 | if (ret != -EPIPE) 406 | ::fprintf(stderr, "snd_pcm_readi returned %ld (%s)\n", ret, ::snd_strerror(ret)); 407 | 408 | ::snd_pcm_recover(m_handle, ret, 1); 409 | } 410 | 411 | if (m_channels == 1U) { 412 | for (int n = 0; n < ret; n++) 413 | m_buffer[n] = float(m_samples[n]) / 32768.0F; 414 | } else { 415 | int i = 0; 416 | for (int n = 0; n < (ret * 2); n += 2) 417 | m_buffer[i++] = float(m_samples[n + 1]) / 32768.0F; 418 | } 419 | 420 | m_callback->readCallback(m_buffer, (unsigned int)ret); 421 | } 422 | 423 | ::snd_pcm_close(m_handle); 424 | } 425 | 426 | void CSoundCardReader::kill() 427 | { 428 | m_killed = true; 429 | } 430 | 431 | CSoundCardWriter::CSoundCardWriter(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback) : 432 | CThread(), 433 | m_handle(handle), 434 | m_blockSize(blockSize), 435 | m_channels(channels), 436 | m_callback(callback), 437 | m_killed(false), 438 | m_buffer(NULL), 439 | m_samples(NULL) 440 | { 441 | assert(handle != NULL); 442 | assert(blockSize > 0U); 443 | assert(channels == 1U || channels == 2U); 444 | assert(callback != NULL); 445 | 446 | m_buffer = new float[2U * blockSize]; 447 | m_samples = new short[4U * blockSize]; 448 | } 449 | 450 | CSoundCardWriter::~CSoundCardWriter() 451 | { 452 | delete[] m_buffer; 453 | delete[] m_samples; 454 | } 455 | 456 | void CSoundCardWriter::entry() 457 | { 458 | while (!m_killed) { 459 | int nSamples = 2U * m_blockSize; 460 | m_callback->writeCallback(m_buffer, nSamples); 461 | 462 | if (nSamples == 0U) { 463 | sleep(5UL); 464 | } else { 465 | if (m_channels == 1U) { 466 | for (int n = 0U; n < nSamples; n++) 467 | m_samples[n] = short(m_buffer[n] * 32767.0F); 468 | } else { 469 | int i = 0U; 470 | for (int n = 0U; n < nSamples; n++) { 471 | short sample = short(m_buffer[n] * 32767.0F); 472 | m_samples[i++] = sample; 473 | m_samples[i++] = sample; // Same value to both channels 474 | } 475 | } 476 | 477 | int offset = 0U; 478 | snd_pcm_sframes_t ret; 479 | while ((ret = ::snd_pcm_writei(m_handle, m_samples + offset, nSamples - offset)) != (nSamples - offset)) { 480 | if (ret < 0) { 481 | if (ret != -EPIPE) 482 | ::fprintf(stderr, "snd_pcm_writei returned %ld (%s)\n", ret, ::snd_strerror(ret)); 483 | 484 | ::snd_pcm_recover(m_handle, ret, 1); 485 | } else { 486 | offset += ret; 487 | } 488 | } 489 | } 490 | } 491 | 492 | ::snd_pcm_close(m_handle); 493 | } 494 | 495 | void CSoundCardWriter::kill() 496 | { 497 | m_killed = true; 498 | } 499 | 500 | bool CSoundCardWriter::isBusy() const 501 | { 502 | snd_pcm_state_t state = ::snd_pcm_state(m_handle); 503 | 504 | return state == SND_PCM_STATE_RUNNING || state == SND_PCM_STATE_DRAINING; 505 | } 506 | 507 | #endif 508 | -------------------------------------------------------------------------------- /SoundCardReaderWriter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2015,2018 by Jonathan Naylor, G4KLX 3 | * Copyright (C) 2014 by John Wiseman, G8BPQ 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; version 2 of the License. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | */ 14 | 15 | #ifndef SoundCardReaderWriter_H 16 | #define SoundCardReaderWriter_H 17 | 18 | #include "AudioCallback.h" 19 | #include "Thread.h" 20 | 21 | #include 22 | 23 | #if (defined(__APPLE__) && defined(__MACH__)) || defined(_WIN32) || defined(_WIN64) 24 | 25 | #include "portaudio.h" 26 | 27 | class CSoundCardReaderWriter { 28 | public: 29 | CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize); 30 | ~CSoundCardReaderWriter(); 31 | 32 | void setCallback(IAudioCallback* callback); 33 | bool open(); 34 | void close(); 35 | 36 | void callback(const float* input, float* output, unsigned int nSamples); 37 | 38 | private: 39 | std::string m_readDevice; 40 | std::string m_writeDevice; 41 | unsigned int m_sampleRate; 42 | unsigned int m_blockSize; 43 | IAudioCallback* m_callback; 44 | PaStream* m_stream; 45 | 46 | bool convertNameToDevices(PaDeviceIndex& inDev, PaDeviceIndex& outDev); 47 | }; 48 | 49 | #else 50 | 51 | #include 52 | 53 | class CSoundCardReader : public CThread { 54 | public: 55 | CSoundCardReader(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback); 56 | virtual ~CSoundCardReader(); 57 | 58 | virtual void entry(); 59 | 60 | virtual void kill(); 61 | 62 | private: 63 | snd_pcm_t* m_handle; 64 | unsigned int m_blockSize; 65 | unsigned int m_channels; 66 | IAudioCallback* m_callback; 67 | bool m_killed; 68 | float* m_buffer; 69 | short* m_samples; 70 | }; 71 | 72 | class CSoundCardWriter : public CThread { 73 | public: 74 | CSoundCardWriter(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback); 75 | virtual ~CSoundCardWriter(); 76 | 77 | virtual void entry(); 78 | 79 | virtual void kill(); 80 | 81 | virtual bool isBusy() const; 82 | 83 | private: 84 | snd_pcm_t* m_handle; 85 | unsigned int m_blockSize; 86 | unsigned int m_channels; 87 | IAudioCallback* m_callback; 88 | bool m_killed; 89 | float* m_buffer; 90 | short* m_samples; 91 | }; 92 | 93 | class CSoundCardReaderWriter { 94 | public: 95 | CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize); 96 | ~CSoundCardReaderWriter(); 97 | 98 | void setCallback(IAudioCallback* callback); 99 | bool open(); 100 | void close(); 101 | 102 | bool isWriterBusy() const; 103 | 104 | private: 105 | std::string m_readDevice; 106 | std::string m_writeDevice; 107 | unsigned int m_sampleRate; 108 | unsigned int m_blockSize; 109 | IAudioCallback* m_callback; 110 | CSoundCardReader* m_reader; 111 | CSoundCardWriter* m_writer; 112 | }; 113 | 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /StopWatch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "StopWatch.h" 20 | 21 | #if defined(_WIN32) || defined(_WIN64) 22 | 23 | CStopWatch::CStopWatch() : 24 | m_frequencyS(), 25 | m_frequencyMS(), 26 | m_start() 27 | { 28 | ::QueryPerformanceFrequency(&m_frequencyS); 29 | 30 | m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL; 31 | } 32 | 33 | CStopWatch::~CStopWatch() 34 | { 35 | } 36 | 37 | unsigned long long CStopWatch::time() const 38 | { 39 | LARGE_INTEGER now; 40 | ::QueryPerformanceCounter(&now); 41 | 42 | return (unsigned long long)(now.QuadPart / m_frequencyMS.QuadPart); 43 | } 44 | 45 | unsigned long long CStopWatch::start() 46 | { 47 | ::QueryPerformanceCounter(&m_start); 48 | 49 | return (unsigned long long)(m_start.QuadPart / m_frequencyS.QuadPart); 50 | } 51 | 52 | unsigned int CStopWatch::elapsed() 53 | { 54 | LARGE_INTEGER now; 55 | ::QueryPerformanceCounter(&now); 56 | 57 | LARGE_INTEGER temp; 58 | temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000; 59 | 60 | return (unsigned int)(temp.QuadPart / m_frequencyS.QuadPart); 61 | } 62 | 63 | #else 64 | 65 | #include 66 | #include 67 | 68 | CStopWatch::CStopWatch() : 69 | m_startMS(0ULL) 70 | { 71 | } 72 | 73 | CStopWatch::~CStopWatch() 74 | { 75 | } 76 | 77 | unsigned long long CStopWatch::time() const 78 | { 79 | struct timeval now; 80 | ::gettimeofday(&now, NULL); 81 | 82 | return now.tv_sec * 1000ULL + now.tv_usec / 1000ULL; 83 | } 84 | 85 | unsigned long long CStopWatch::start() 86 | { 87 | struct timespec now; 88 | ::clock_gettime(CLOCK_MONOTONIC, &now); 89 | 90 | m_startMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; 91 | 92 | return m_startMS; 93 | } 94 | 95 | unsigned int CStopWatch::elapsed() 96 | { 97 | struct timespec now; 98 | ::clock_gettime(CLOCK_MONOTONIC, &now); 99 | 100 | unsigned long long nowMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; 101 | 102 | return nowMS - m_startMS; 103 | } 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /StopWatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(STOPWATCH_H) 20 | #define STOPWATCH_H 21 | 22 | #if defined(_WIN32) || defined(_WIN64) 23 | #include 24 | #else 25 | #include 26 | #endif 27 | 28 | class CStopWatch 29 | { 30 | public: 31 | CStopWatch(); 32 | ~CStopWatch(); 33 | 34 | unsigned long long time() const; 35 | 36 | unsigned long long start(); 37 | unsigned int elapsed(); 38 | 39 | private: 40 | #if defined(_WIN32) || defined(_WIN64) 41 | LARGE_INTEGER m_frequencyS; 42 | LARGE_INTEGER m_frequencyMS; 43 | LARGE_INTEGER m_start; 44 | #else 45 | unsigned long long m_startMS; 46 | #endif 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /Thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Thread.h" 20 | 21 | #if defined(_WIN32) || defined(_WIN64) 22 | 23 | CThread::CThread() : 24 | m_handle() 25 | { 26 | } 27 | 28 | CThread::~CThread() 29 | { 30 | } 31 | 32 | bool CThread::run() 33 | { 34 | m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL); 35 | 36 | return m_handle != NULL; 37 | } 38 | 39 | 40 | void CThread::wait() 41 | { 42 | ::WaitForSingleObject(m_handle, INFINITE); 43 | 44 | ::CloseHandle(m_handle); 45 | } 46 | 47 | 48 | DWORD CThread::helper(LPVOID arg) 49 | { 50 | CThread* p = (CThread*)arg; 51 | 52 | p->entry(); 53 | 54 | return 0UL; 55 | } 56 | 57 | void CThread::sleep(unsigned int ms) 58 | { 59 | ::Sleep(ms); 60 | } 61 | 62 | #else 63 | 64 | #include 65 | 66 | CThread::CThread() : 67 | m_thread() 68 | { 69 | } 70 | 71 | CThread::~CThread() 72 | { 73 | } 74 | 75 | bool CThread::run() 76 | { 77 | return ::pthread_create(&m_thread, NULL, helper, this) == 0; 78 | } 79 | 80 | 81 | void CThread::wait() 82 | { 83 | ::pthread_join(m_thread, NULL); 84 | } 85 | 86 | 87 | void* CThread::helper(void* arg) 88 | { 89 | CThread* p = (CThread*)arg; 90 | 91 | p->entry(); 92 | 93 | return NULL; 94 | } 95 | 96 | void CThread::sleep(unsigned int ms) 97 | { 98 | ::usleep(ms * 1000); 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /Thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(THREAD_H) 20 | #define THREAD_H 21 | 22 | #if defined(_WIN32) || defined(_WIN64) 23 | #include 24 | #else 25 | #include 26 | #endif 27 | 28 | class CThread 29 | { 30 | public: 31 | CThread(); 32 | virtual ~CThread(); 33 | 34 | virtual bool run(); 35 | 36 | virtual void entry() = 0; 37 | 38 | virtual void wait(); 39 | 40 | static void sleep(unsigned int ms); 41 | 42 | private: 43 | #if defined(_WIN32) || defined(_WIN64) 44 | HANDLE m_handle; 45 | #else 46 | pthread_t m_thread; 47 | #endif 48 | 49 | #if defined(_WIN32) || defined(_WIN64) 50 | static DWORD __stdcall helper(LPVOID arg); 51 | #else 52 | static void* helper(void* arg); 53 | #endif 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /TimeoutTones.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "TimeoutTones.h" 15 | #include "NCO.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | const float DL_FREQ = 425.0F; // Frequency of German busy tone 23 | const float DL_LEN = 0.5F; // Length of German busy tone 24 | 25 | const float UK_FREQ = 400.0F; // Frequency of UK busy tone 26 | const float UK_LEN = 0.4F; // Length of UK busy tone 27 | 28 | const float US_FREQ = 620.0F; // Frequency of US busy tone 29 | const float US_LEN = 0.5F; // Length of US busy tone 30 | 31 | const unsigned int RC_LEN = 5U; // The length of the raised cosine in ms 32 | 33 | CTimeoutTones::CTimeoutTones(unsigned int sampleRate, ANALOGUE_TIMEOUT_TYPE type) : 34 | m_data(NULL), 35 | m_length(0U), 36 | m_current(0U) 37 | { 38 | assert(sampleRate > 0U); 39 | 40 | float len = DL_LEN; 41 | float freq = DL_FREQ; 42 | 43 | switch (type) { 44 | case ATT_DL: 45 | len = DL_LEN; 46 | freq = DL_FREQ; 47 | break; 48 | case ATT_UK: 49 | len = UK_LEN; 50 | freq = UK_FREQ; 51 | break; 52 | case ATT_US: 53 | len = US_LEN; 54 | freq = US_FREQ; 55 | break; 56 | } 57 | 58 | unsigned int length = (unsigned int)(len * float(sampleRate) + 0.5F); 59 | 60 | m_length = 2U * length; 61 | m_data = new float[m_length]; 62 | 63 | ::memset(m_data, 0x00, m_length * sizeof(float)); 64 | 65 | CNCO tone(sampleRate, freq); 66 | tone.getAudio(m_data, length); 67 | 68 | // Calculate the length of the raised cosine shaping section 69 | unsigned int rcLen = RC_LEN * sampleRate / 1000U; 70 | 71 | // Shape the start and end of the tone 72 | for (unsigned int i = 0U; i < rcLen; i++) { 73 | float ampl = 0.5F * (1.0F - float(::cos(M_PI * (float(i) / float(rcLen))))); 74 | 75 | m_data[i] *= ampl; 76 | m_data[length - 1U - i] *= ampl; 77 | } 78 | } 79 | 80 | CTimeoutTones::~CTimeoutTones() 81 | { 82 | delete[] m_data; 83 | } 84 | 85 | void CTimeoutTones::getAudio(float* audio, unsigned int length, float amplitude) 86 | { 87 | assert(audio != NULL); 88 | 89 | for (unsigned int i = 0U; i < length; i++) { 90 | audio[i] = m_data[m_current++] * amplitude; 91 | 92 | if (m_current >= m_length) 93 | m_current = 0U; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /TimeoutTones.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef TimeoutTones_H 15 | #define TimeoutTones_H 16 | 17 | enum ANALOGUE_TIMEOUT_TYPE { 18 | ATT_DL, 19 | ATT_UK, 20 | ATT_US 21 | }; 22 | 23 | class CTimeoutTones { 24 | public: 25 | CTimeoutTones(unsigned int sampleRate, ANALOGUE_TIMEOUT_TYPE type); 26 | ~CTimeoutTones(); 27 | 28 | void getAudio(float* audio, unsigned int length, float amplitude = 1.0F); 29 | 30 | private: 31 | float* m_data; 32 | unsigned int m_length; 33 | unsigned int m_current; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Timer.h" 20 | 21 | #include 22 | #include 23 | 24 | CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) : 25 | m_ticksPerSec(ticksPerSec), 26 | m_timeout(0U), 27 | m_timer(0U) 28 | { 29 | assert(ticksPerSec > 0U); 30 | 31 | if (secs > 0U || msecs > 0U) { 32 | // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; 33 | unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; 34 | m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); 35 | } 36 | } 37 | 38 | CTimer::~CTimer() 39 | { 40 | } 41 | 42 | void CTimer::setTimeout(unsigned int secs, unsigned int msecs) 43 | { 44 | if (secs > 0U || msecs > 0U) { 45 | // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; 46 | unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; 47 | m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); 48 | } else { 49 | m_timeout = 0U; 50 | m_timer = 0U; 51 | } 52 | } 53 | 54 | unsigned int CTimer::getTimeout() const 55 | { 56 | if (m_timeout == 0U) 57 | return 0U; 58 | 59 | return (m_timeout - 1U) / m_ticksPerSec; 60 | } 61 | 62 | unsigned int CTimer::getTimer() const 63 | { 64 | if (m_timer == 0U) 65 | return 0U; 66 | 67 | return (m_timer - 1U) / m_ticksPerSec; 68 | } 69 | -------------------------------------------------------------------------------- /Timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2011,2013,2014 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef Timer_H 20 | #define Timer_H 21 | 22 | class CTimer { 23 | public: 24 | CTimer(unsigned int ticksPerSec, unsigned int secs = 0U, unsigned int msecs = 0U); 25 | ~CTimer(); 26 | 27 | void setTimeout(unsigned int secs, unsigned int msecs = 0U); 28 | 29 | unsigned int getTimeout() const; 30 | unsigned int getTimer() const; 31 | 32 | unsigned int getRemaining() 33 | { 34 | if (m_timeout == 0U || m_timer == 0U) 35 | return 0U; 36 | 37 | if (m_timer >= m_timeout) 38 | return 0U; 39 | 40 | return (m_timeout - m_timer) / m_ticksPerSec; 41 | } 42 | 43 | bool isRunning() 44 | { 45 | return m_timer > 0U; 46 | } 47 | 48 | void start() 49 | { 50 | if (m_timeout > 0U) 51 | m_timer = 1U; 52 | } 53 | 54 | void stop() 55 | { 56 | m_timer = 0U; 57 | } 58 | 59 | bool hasExpired() 60 | { 61 | if (m_timeout == 0U || m_timer == 0U) 62 | return false; 63 | 64 | if (m_timer >= m_timeout) 65 | return true; 66 | 67 | return false; 68 | } 69 | 70 | void clock(unsigned int ticks = 1U) 71 | { 72 | if (m_timer > 0U && m_timeout > 0U) 73 | m_timer += ticks; 74 | } 75 | 76 | private: 77 | unsigned int m_ticksPerSec; 78 | unsigned int m_timeout; 79 | unsigned int m_timer; 80 | }; 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /UDRCController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012,2013,2015,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "UDRCController.h" 15 | 16 | #if defined(UDRC) 17 | 18 | #include 19 | 20 | #include 21 | 22 | const int BASE_PIN = 6; 23 | const int PTT_PIN = 12; 24 | const int PKSQL_PIN = 5; 25 | const int SQL_PIN = 25; 26 | 27 | CUDRCController::CUDRCController() 28 | { 29 | } 30 | 31 | CUDRCController::~CUDRCController() 32 | { 33 | } 34 | 35 | bool CUDRCController::open() 36 | { 37 | bool ret = ::wiringPiSetup() != -1; 38 | if (!ret) { 39 | ::fprintf(stderr, "Unable to initialise wiringPi\n"); 40 | return false; 41 | } 42 | 43 | ::pinMode(SQL_PIN, INPUT); 44 | ::pinMode(PKSQL_PIN, INPUT); 45 | 46 | // Set pull ups on the input pins 47 | ::pullUpDnControl(SQL_PIN, PUD_UP); 48 | ::pullUpDnControl(PKSQL_PIN, PUD_UP); 49 | 50 | ::pinMode(PTT_PIN, OUTPUT); 51 | ::pinMode(BASE_PIN, OUTPUT); 52 | 53 | return true; 54 | } 55 | 56 | bool CUDRCController::getSquelch() 57 | { 58 | return ::digitalRead(SQL_PIN) == LOW; 59 | } 60 | 61 | bool CUDRCController::getDisable() 62 | { 63 | return ::digitalRead(PKSQL_PIN) == LOW; 64 | } 65 | 66 | void CUDRCController::setTransmit(bool value) 67 | { 68 | ::digitalWrite(PTT_PIN, value ? LOW : HIGH); 69 | } 70 | 71 | void CUDRCController::setActive(bool value) 72 | { 73 | ::digitalWrite(BASE_PIN, value ? LOW : HIGH); 74 | } 75 | 76 | void CUDRCController::setHeartbeat(bool) 77 | { 78 | } 79 | 80 | void CUDRCController::close() 81 | { 82 | } 83 | 84 | #else 85 | 86 | CUDRCController::CUDRCController() 87 | { 88 | } 89 | 90 | CUDRCController::~CUDRCController() 91 | { 92 | } 93 | 94 | bool CUDRCController::open() 95 | { 96 | return true; 97 | } 98 | 99 | bool CUDRCController::getSquelch() 100 | { 101 | return false; 102 | } 103 | 104 | bool CUDRCController::getDisable() 105 | { 106 | return false; 107 | } 108 | 109 | void CUDRCController::setTransmit(bool value) 110 | { 111 | } 112 | 113 | void CUDRCController::setHeartbeat(bool value) 114 | { 115 | } 116 | 117 | void CUDRCController::setActive(bool value) 118 | { 119 | } 120 | 121 | void CUDRCController::close() 122 | { 123 | } 124 | 125 | #endif 126 | 127 | -------------------------------------------------------------------------------- /UDRCController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012,2015,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef UDRCController_H 15 | #define UDRCController_H 16 | 17 | #include "HardwareController.h" 18 | 19 | class CUDRCController : public IHardwareController { 20 | public: 21 | CUDRCController(); 22 | virtual ~CUDRCController(); 23 | 24 | virtual bool open(); 25 | 26 | virtual bool getSquelch(); 27 | virtual bool getDisable(); 28 | 29 | virtual void setTransmit(bool value); 30 | virtual void setHeartbeat(bool value); 31 | virtual void setActive(bool value); 32 | 33 | virtual void close(); 34 | 35 | private: 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2013,2018 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef Utils_H 15 | #define Utils_H 16 | 17 | enum TRISTATE { 18 | STATE_FALSE, 19 | STATE_TRUE, 20 | STATE_UNKNOWN 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef Version_H 20 | #define Version_H 21 | 22 | const char* VERSION = "20180418"; 23 | 24 | #endif 25 | --------------------------------------------------------------------------------