diff -Nru quisk-3.6.18/CHANGELOG.txt quisk-3.7.6/CHANGELOG.txt --- quisk-3.6.18/CHANGELOG.txt 2014-06-22 15:01:38.000000000 +0000 +++ quisk-3.7.6/CHANGELOG.txt 2015-09-15 14:40:18.000000000 +0000 @@ -1,3 +1,182 @@ +Quisk Version 3.7.6 September 2015 +=================================== +The PulseAudio code was completely rewritten by Eric Thornton, KM4DSJ. It now uses +the asynchronous interface. This will: +1)Improve stability/performance. We can now manage latency events how we want to and prevent +ever increasing latency due to CPU load or network delays. latency_millisecs in the quick_conf.py +file will established the target buffer size for playback. Realtime latency for pulse audio +streams is available on the config/status tab. + +2)Allow connection to two different PulseAudio servers. The default connection is to the local +machine for normal usage. A separate machine can be specified to pass IQ audio via the network. +This is useful to utilize a Softrock or Peaberry via a raspberry pi or other remote computer. +Specify IQ_Server_IP = "your.ip.here" in your quisk_conf.py to utilize this option. Be sure to +set latency_millisecs to something reasonable (300 works for me over wifi). NOTE: To keep network +congestion down, all modes except CW will "cork" the idle up/down IQ stream. +3)Quisk will attempt to utilize native 16 bit LE format if a PulseAudio sink is configured this way. +Default fallback is Float 32. This reduces network overhead for remote IQ audio streaming while not +losing resolution for any local streams that are configured as floating point. + +Additional changes: +-Remote control of Softrock receivers via usbsoftrock. Add usbsr_ip_address = "your.ip.here", +usbsr_port=port#, and import hardware_net.py in quick_conf.py to communicate with a usbsoftrock +daemon. Start usbsoftrock on the remote machine with the -D option. +-Added a macports target to the makefile to utilize dependency libraries from macports. +This option now allows use of PulseAudio on OSX. + +Eric +KM4DSJ + + +Quisk Version 3.7.5 September 2015 +=================================== +I changed the code for full-screen mode. You can now enter window sizes directly. See +window_width in the file quisk_conf_defaults.py. + +I added two more bits to tx_control for the FPGA protocol. I updated the PyUSB +package that ships with Quisk to version 1.0.0b2. + +The received characters from FreeDV now appear in the upper left of the graph. I added +the new FreeDV 700 and 700B modes. Right-click the FDV button to select the mode. + +To make it clear when buttons have added functions, I added a miniature slider to buttons +that can be right-clicked, and a circular arrow to buttons that can be clicked repeatedly. +This can be turned off in your config file. + +Quisk Version 3.7.4 August 2015 +================================ +The Spot button transmits a variable level carrier in all modes, not just SSB. The new config +file option "spot_button_keys_tx = True" will cause the Spot button to key the transmitter. The +default is False. The up button for the Favorites screen used to set the frequency in Hertz, +but the frequencies must be in Megahertz. The ctcss was also broken. This has been fixed. + +I added a new Hermes-Lite model configuration file hermes/quisk_conf2.py. This is for +use by people who can program in Python and want to add features to Quisk. It is set up +to send the Spot button status on the J16 connector. Quisk now operates in CW mode using +the new Hermes-Lite key input. For other modes, use the PTT button. On the Hermes, +use the PTT button with Spot, even for CW. The CW key always transmits full power CW even +if the mode is not CW. + +There is now an option to receive microphone samples from a UDP device. This is used for +SDR hardware that has a built-in codec. See quisk_conf_defaults.py. + +The FreeDV code was completely rewritten. It is now simpler, and it is easier to change to a +newer libcodec2. See the file freedvpkg/README.txt. If anyone is running on Apple OSX, and +has problems, email me, and look at freedv.c. + +The config file option graph_width=0.8 sets the width of the graph. If this is set to exactly 1.0, +Quisk will run in full-screen mode. This is meant for built-in screens, tablets etc. that lack +window management. + +Quisk Version 3.7.2 July 2015 +============================== +I made some changes for Hermes-Lite. You must add rx_udp_clock to your config file. You can adjust +rx_udp_clock slightly to correct the frequency display. See hermes/quisk_conf.py. +I added Hermes_BandDict to the config file. It controls the bits on the J16 connector of the Hermes-Lite. + +Quisk will now keep the same filter for a given mode when changing bands. I added RepeaterOffset() to +the softrock hardware file so that repeater offsets work. I added mode DGT-FM for digital FM modes. + + +Quisk Version 3.7.1 July 2015 +============================== +This is a bug fix release to correct an incompatibility between my FPGA firmware and that of +Stephen, DL2STG. He was using the byte in the control record that I used to send the sidetone volume. +I now send the sidetone volume as byte 17. + +I added the config file parameter use_sidetone to add/remove the Sto sidetone volume control. It +used to be necessary to use the hardware file for this. The default is no sidetone. To add the +control, add "use_sidetone=1" to your config file. + +I fixed a bug in the Audio Plackback button and Hermes-Lite. + +Quisk Version 3.7.0 July 2015 +============================== +Quisk now supports a new radio, the HermesLite. If you have a HermesLite, copy hermes/quisk_conf.py +to your config file and modify the copy. You should only need to change the soundcard names. +If you use Quisk on multiple radios, remember that you can have multiple config files. +Use "python quisk.py -c my_config_file.py" or its equivalent to choose among config files. This +code is currently under test and may have problems. + +The favorites screen now has two more columns for repeater offset and CTCSS tone. See the file +quisk_conf_defaults.py. You must set do_repeater_offset=True, and your hardware must be capable +of performing the shift. + +The FDV feature is now in "final" form. Please see freedvpkg/README.txt. Note that FDV is +a moving target. + +There is now an option to send radio sound to a UDP device. This is used to send radio sound to +SDR hardware that has a built-in codec. See quisk_conf_defaults.py. + +The SoftRock hardware file now supports the key line as a PTT button for voice modes. Use this if you +have a hardware mic button that you can connect to the key line. + +I added the sidetone volume to the HiQSDR control data to accommodate new hardware. I softened the +volume controls Vol and Sto for a smoother response. I added the new config file option cw_delay. + +Quisk Version 3.6.22 April 2015 +=============================== +FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice +in 1200 Hz bandwidth suitable for HF transmission. You can use FreeDV by downloading the FreeDv +program from freedv.org, and connecting it to Quisk using the usual digital mode DGT-U. But now +there is a simpler way. Quisk has a new mode button FDV. Just push the FDV mode and talk. See +http://www.rowetel.com/blog/?page_id=452. + +Quisk will add the FDV mode button unless your config file contains the line + add_freedv_button = 0 +If there is a problem with the freedv module, the button will be grayed out. See README.txt in the +freedvpkg subdirectory for more information. The "Split" button currently fails with FDV. + +With the mouse in the frequency display, roll the mouse wheel to change the digit. + +The new config option hamlib_ip specifies the Hamlib IP address; default "localhost". + +The IMD button now has a right-click level control like the Spot button. This is more +convenient than having a few fixed levels. + +The Spot button can now transmit a carrier at zero amplitude. This is useful for testing +the output noise. If you wrote your own OnSpot() method, level -1 is now Spot button off, +and the level is now 0 to 1000. + +Quisk Version 3.6.21 March 2015 +=============================== +Quisk can now transmit a message from a WAV file. Record your message at +a high level (near clipping) at 48 ksps, 16-bit, one channel (monophonic). +Then enter the file name on the Config/Config screen. Press the "File play" +button to transmit. Quisk will press the PTT button for you, and release it +during pauses. To interrupt playback, press PTT or release FilePlay so you can answer. + +The "Split" button has been replaced with a "Splt" button and a "Rev" button. The "Splt" +button splits Rx and Tx; and if you click it with the right mouse button instead of the +left, it also locks the Tx frequency so tuning changes the Rx frequency. The "Rev" +button reverses the Tx and Rx frequencies. These features were suggested by Mario, DH5YM. + +I added a new parameter mic_agc_level to the config file to control the mic AGC. Input levels +below mic_agc_level are ignored. The default is 0.1. Increase this (up to 1.0) to reduce the +AGC range, and reduce it to increase the AGC mic gain boost. + +Philip Lee contributed a patch to the PulseAudio code to set the play buffer size. + +Quisk Version 3.6.20 December 2014 +================================== +Thanks to Graeme, ZL2APV, quisk now changes both channels of a control when using mixer_settings[]. +He reports that setting boolean values does not work, but it works on my machine. More testing +is needed. + +The new config file dictionary bandTransverterOffset[] gives the offset in Hertz for bands that are +used with a transverter; for example 144000000 - 28000000 for a two meter transverter. This +currently works for HiQSDR, SoftRock and SdrIQ. I also changed the graph X axis code so gigahertz +frequency labels are not too wide. + +Quisk Version 3.6.19 October 2014 +================================= +Mario, DL3LSM, contributed changes for MacOS support. Thanks! + +I added device names to PulseAudio. The PulseAudio name "pulse" still refers to the default +device. Otherwise, enter a PulseAudio name such as "pulse:alsa_input.pci-0000_00_1b.0.analog-stereo". +See quisk_conf_defaults.py or docs.html. PulseAudio support enables you to connect to recent +versions of wsjt-x. To turn this off, set show_pulse_audio_devices = False in your config file. + Quisk Version 3.6.18 June 2014 ============================== The Windows installer now works with the new WxPython 3.0. Previously it required 2.x. diff -Nru quisk-3.6.18/debian/changelog quisk-3.7.6/debian/changelog --- quisk-3.6.18/debian/changelog 2014-10-09 14:20:03.000000000 +0000 +++ quisk-3.7.6/debian/changelog 2015-09-20 00:38:58.000000000 +0000 @@ -1,3 +1,16 @@ +quisk (3.7.6-1) unstable; urgency=medium + + * New upstream release + + -- A. Maitland Bottoms Sat, 19 Sep 2015 20:38:41 -0400 + +quisk (3.7.4-1) unstable; urgency=low + + * New upstream release + * freedv mode uses libcodec2-0.4 + + -- A. Maitland Bottoms Tue, 01 Sep 2015 23:40:34 -0400 + quisk (3.6.18-2) unstable; urgency=low * Update runtime dependency to python-wxgtk3.0 (Closes: #758956) diff -Nru quisk-3.6.18/debian/control quisk-3.7.6/debian/control --- quisk-3.6.18/debian/control 2014-10-09 14:18:30.000000000 +0000 +++ quisk-3.7.6/debian/control 2015-09-08 01:21:50.000000000 +0000 @@ -2,14 +2,23 @@ Section: hamradio Priority: optional Maintainer: A. Maitland Bottoms -Build-Depends: debhelper (>= 9.0.0~), python, portaudio19-dev, libpulse-dev, - libusb-dev, libfftw3-dev, python-all-dev, libasound2-dev, python-wxgtk3.0, hardening-wrapper -Standards-Version: 3.9.5 +Build-Depends: debhelper (>= 9.0.0~), + dh-python, + libasound2-dev, + libcodec2-dev (>= 0.4-2), + libfftw3-dev, + libpulse-dev, + libusb-dev, + portaudio19-dev, + python, + python-all-dev, + python-wxgtk3.0 +Standards-Version: 3.9.6 Homepage: http://james.ahlstrom.name/quisk/ Package: quisk Architecture: any -Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}, python-wxgtk3.0 +Depends: python-wxgtk3.0, ${misc:Depends}, ${python:Depends}, ${shlibs:Depends} Recommends: alsa-utils, python-serial, python-usb, udev Description: Software Defined Radio (SDR) Quisk uses ALSA sound drivers or PortAudio and offers these capabilities: diff -Nru quisk-3.6.18/debian/copyright quisk-3.7.6/debian/copyright --- quisk-3.6.18/debian/copyright 2013-05-21 01:05:26.000000000 +0000 +++ quisk-3.7.6/debian/copyright 2015-09-08 01:28:12.000000000 +0000 @@ -1,58 +1,76 @@ -GPL licensed source code downloaded via source package links from -http://james.ahlstrom.name/quisk/quisk-3.6.10.tar.gz -and -http://amrad.org/projects/dsp/charleston-1.0.tar.gz - -The Icon image file: -http://wa5znu.org/2009/04/quisk-lppan-k3/quisk-icon.png -Copyright (C) 2009 Leigh L. Klotz Jr. WA5ZNU - -Debianized by A. Maitland Bottoms -on 12 January 2012. - -Debian packaging: -Copyright (C) 2012 A. Maitland Bottoms, AA4HS - -This software is Copyright (C) 2007-2011 by James C. Ahlstrom, and is -licensed for use under the GNU General Public License (GPL). -See http://www.opensource.org. -Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!! - -The GNU General Public License (GPL) -Version 2, June 1991 - -A full copy of the license is in the file: -/usr/share/common-licenses/GPL-2 - -Files in the charleston directory are: -Copyright (C) 2010 Terry Fox -also licensed GPL-2. - -Files in the usb directory are from the PyUSB project and are: - -Copyright (C) 2009-2010 Wander Lairson Costa. All Rights Reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: quisk +Upstream-Contact: http://james.ahlstrom.name/quisk/ +Source: http://james.ahlstrom.name/quisk/quisk-3.7.4.tar.gz + http://amrad.org/projects/dsp/charleston-1.0.tar.gz +Copyright: 2007-2011 by James C. Ahlstrom +License: GPL-2+ +Comment: Debianized by A. Maitland Bottoms + on 12 January 2012. + +Files: debian/quisk-icon.png +Comment: The Icon image file: + http://wa5znu.org/2009/04/quisk-lppan-k3/quisk-icon.png +Copyright: 2009 Leigh L. Klotz Jr. WA5ZNU +License: GPL-2+ + +Files: charleston/* +Copyright: 2010 Terry Fox +License: GPL-2+ + +Files: debian/* +Comment: Debian packaging: +Copyright: 2012-2015 A. Maitland Bottoms, AA4HS +License: GPL-2+ + +Files: usb/* +Comment: from the PyUSB project +Copyright: 2009-2010 Wander Lairson Costa. All Rights Reserved. +License: WLCBSD + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright + . + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. The name of the author may not be used to endorse or promote products + . + 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + . + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + +Files: * +Copyright: 2007-2011 by James C. Ahlstrom +License: GPL-2+ + +License: GPL-2+ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at + your option) any later version. + . + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, + USA. + . + A full copy of the license is in the file: + /usr/share/common-licenses/GPL-2 diff -Nru quisk-3.6.18/debian/patches/debian-setup quisk-3.7.6/debian/patches/debian-setup --- quisk-3.6.18/debian/patches/debian-setup 2014-05-02 22:56:26.000000000 +0000 +++ quisk-3.7.6/debian/patches/debian-setup 2015-09-07 18:44:20.000000000 +0000 @@ -15,16 +15,16 @@ modulew1 = Extension ('quisk._quisk', include_dirs = ['../fftw3', 'C:/Program Files (x86)/Microsoft DirectX SDK (February 2010)/Include', 'C:/Program Files/Microsoft DirectX SDK (February 2010)/Include',], -@@ -50,7 +58,7 @@ - if sys.platform == "win32": - Modules = [modulew1, modulew2] +@@ -70,7 +78,7 @@ + elif sys.platform == "darwin": + Modules = [modulem1, modulem2] else: - Modules = [module1, module2] + Modules = [module1, module2, module3] setup (name = 'quisk', version = Version, -@@ -73,9 +81,9 @@ +@@ -93,9 +101,9 @@ author_email = 'jahlstr@gmail.com', url = 'http://james.ahlstrom.name/quisk/', download_url = 'http://james.ahlstrom.name/quisk/', diff -Nru quisk-3.6.18/debian/rules quisk-3.7.6/debian/rules --- quisk-3.6.18/debian/rules 2014-05-02 18:37:30.000000000 +0000 +++ quisk-3.7.6/debian/rules 2015-09-20 00:59:25.000000000 +0000 @@ -13,3 +13,17 @@ find . -name \*\.pyd -exec rm \{\} \; find . -name \*\.so -exec rm \{\} \; dh_auto_clean + +get-orig-source: + wget http://james.ahlstrom.name/quisk/quisk-3.7.6.tar.gz + tar zxvf quisk-3.7.6.tar.gz + rm -rf quisk-3.7.6/_quisk.so + rm -rf quisk-3.7.6/_quisk.pyd + rm -rf quisk-3.7.6/sdriqpkg/sdriq.so + rm -rf quisk-3.7.6/sdriqpkg/sdriq.pyd + rm -rf quisk-3.7.6/freedvpkg/libcodec2* + rm -rf quisk-3.7.6/usb/*.pyc + rm -rf quisk-3.7.6/usb/backend/*.pyc + tar cvf quisk-3.7.6.tar quisk-3.7.6 + xz quisk-3.7.6.tar + rm quisk-3.7.6.tar.gz diff -Nru quisk-3.6.18/debian/watch quisk-3.7.6/debian/watch --- quisk-3.6.18/debian/watch 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/debian/watch 2015-09-08 00:54:30.000000000 +0000 @@ -0,0 +1,2 @@ +version=3 + http://james.ahlstrom.name/quisk/ quisk-(.+)\.tar\.gz diff -Nru quisk-3.6.18/defaults.html quisk-3.7.6/defaults.html --- quisk-3.6.18/defaults.html 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/defaults.html 2015-04-30 19:09:49.000000000 +0000 @@ -0,0 +1,33 @@ + + + +QUISK Configuration Defaults: quisk_conf_defaults.py + + + + + + + + +
+ +
+ + diff -Nru quisk-3.6.18/docs.html quisk-3.7.6/docs.html --- quisk-3.6.18/docs.html 2014-05-31 14:51:58.000000000 +0000 +++ quisk-3.7.6/docs.html 2015-04-02 13:31:23.000000000 +0000 @@ -5,6 +5,7 @@ + Documentation for Quisk @@ -663,8 +664,41 @@

-Thanks to Philip G. Lee, Quisk now has native support for PulseAudio. Just -use the name "pulse". +Thanks to Philip G. Lee, Quisk now has native support for PulseAudio. +For PulseAudio devices, use the name "pulse:name" and connect the streams +to your hardware devices using a PulseAudio control program. The name "pulse" +alone refers to the "default" device. The PulseAudio names are quite long; +for example "alsa_output.pci-0000_00_1b.0.analog-stereo". Look on the screen +Config/Sound to see the device names. There is a description, a PulseAudio name, +and for ALSA devices, the ALSA name. +Instead of the long PulseAudio name, you can enter a substring of any of these three strings. +An example is: + +
+# As seen on the Config/Sound screen:
+  CM106 Like Sound Device Analog Stereo
+     alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-stereo
+     USB Sound Device USB Audio (hw:1,0)
+
+# In your config file:
+# Use the default pulse device for radio sound:
+name_of_sound_play = "pulse"
+# Use a PulseAudio name for radio sound:
+name_of_sound_play = "pulse:alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-stereo"
+# Abbreviate the PulseAudio name:
+name_of_sound_play = "pulse:alsa_output.usb"
+# Another abbreviation:
+name_of_sound_play = "pulse:CM106"
+
+ +The PulseAudio code should not cause problems, but I am not sure what happens if PulseAudio is not +installed, or if you replace it with JACK. This config file option will turn off all but directly +entered "pulse:" names: +
+show_pulse_audio_devices = False
+
+ +

diff -Nru quisk-3.6.18/filter.c quisk-3.7.6/filter.c --- quisk-3.6.18/filter.c 2014-05-30 17:15:03.000000000 +0000 +++ quisk-3.7.6/filter.c 2015-09-01 17:45:40.000000000 +0000 @@ -14,7 +14,7 @@ memset(filter->cSamples, 0, taps * sizeof(complex double)); filter->ptcSamp = filter->cSamples; filter->nTaps = taps; - filter->counter = 0; + filter->decim_index = 0; filter->cBuf = NULL; filter->nBuf = 0; } @@ -27,7 +27,7 @@ memset(filter->dSamples, 0, taps * sizeof(double)); filter->ptdSamp = filter->dSamples; filter->nTaps = taps; - filter->counter = 0; + filter->decim_index = 0; filter->dBuf = NULL; filter->nBuf = 0; } @@ -181,8 +181,8 @@ nOut = 0; for (i = 0; i < count; i++) { *filter->ptcSamp = cSamples[i]; - if (++filter->counter >= decim) { - filter->counter = 0; // output a sample + if (++filter->decim_index >= decim) { + filter->decim_index = 0; // output a sample csample = 0; ptSample = filter->ptcSamp; ptCoef = filter->dCoefs; @@ -209,8 +209,8 @@ nOut = 0; for (i = 0; i < count; i++) { *filter->ptdSamp = dSamples[i]; - if (++filter->counter >= decim) { - filter->counter = 0; // output a sample + if (++filter->decim_index >= decim) { + filter->decim_index = 0; // output a sample dsample = 0; ptSample = filter->ptdSamp; ptCoef = filter->dCoefs; @@ -226,6 +226,44 @@ } return nOut; } + +int quisk_cInterpDecim(complex double * cSamples, int count, struct quisk_cFilter * filter, int interp, int decim) +{ // Interpolate by interp, and then decimate by decim. + // This uses the double coefficients of filter (not the complex). Samples are complex. + int i, k, nOut; + double * ptCoef; + complex double * ptSample; + complex double csample; + + if (count > filter->nBuf) { // increase size of sample buffer + filter->nBuf = count * 2; + if (filter->cBuf) + free(filter->cBuf); + filter->cBuf = (complex double *)malloc(filter->nBuf * sizeof(complex double)); + } + memcpy(filter->cBuf, cSamples, count * sizeof(complex double)); + nOut = 0; + for (i = 0; i < count; i++) { + // Put samples into buffer left to right. Use samples right to left. + *filter->ptcSamp = filter->cBuf[i]; + while (filter->decim_index < interp) { + ptSample = filter->ptcSamp; + ptCoef = filter->dCoefs + filter->decim_index; + csample = 0; + for (k = 0; k < filter->nTaps / interp; k++, ptCoef += interp) { + csample += *ptSample * *ptCoef; + if (--ptSample < filter->cSamples) + ptSample = filter->cSamples + filter->nTaps - 1; + } + cSamples[nOut++] = csample * interp; + filter->decim_index += decim; + } + if (++filter->ptcSamp >= filter->cSamples + filter->nTaps) + filter->ptcSamp = filter->cSamples; + filter->decim_index = filter->decim_index - interp; + } + return nOut; +} double quisk_dD_out(double samp, struct quisk_dFilter * filter) { // Filter double samples. diff -Nru quisk-3.6.18/filter.h quisk-3.7.6/filter.h --- quisk-3.6.18/filter.h 2014-05-30 17:09:41.000000000 +0000 +++ quisk-3.7.6/filter.h 2015-09-01 17:42:14.000000000 +0000 @@ -1,23 +1,23 @@ struct quisk_cFilter { double * dCoefs; // filter coefficients complex double * cpxCoefs; // make the complex coefficients from dCoefs - int nBuf; // dimension of cBuf - int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs - int counter; // used to count samples for decimation + int nBuf; // dimension of cBuf + int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs + int decim_index; // used to count samples for decimation complex double * cSamples; // storage for old samples complex double * ptcSamp; // next available position in cSamples complex double * cBuf; // auxillary buffer for interpolation } ; struct quisk_dFilter { - double * dCoefs; // filter coefficients + double * dCoefs; // filter coefficients complex double * cpxCoefs; // make the complex coefficients from dCoefs - int nBuf; // dimension of dBuf - int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs - int counter; // used to count samples for decimation - double * dSamples; // storage for old samples - double * ptdSamp; // next available position in dSamples - double * dBuf; // auxillary buffer for interpolation + int nBuf; // dimension of dBuf + int nTaps; // dimension of dSamples, cSamples, dCoefs and cpxCoefs + int decim_index; // used to count samples for decimation + double * dSamples; // storage for old samples + double * ptdSamp; // next available position in dSamples + double * dBuf; // auxillary buffer for interpolation } ; struct quisk_cHB45Filter { // Complex half band decimate by 2 filter with 45 coefficients @@ -45,6 +45,7 @@ int quisk_dInterpolate(double *, int, struct quisk_dFilter *, int); int quisk_cDecimate(complex double *, int, struct quisk_cFilter *, int); int quisk_dDecimate(double *, int, struct quisk_dFilter *, int); +int quisk_cInterpDecim(complex double *, int, struct quisk_cFilter *, int, int); int quisk_cDecim2HB45(complex double *, int, struct quisk_cHB45Filter *); int quisk_dInterp2HB45(double *, int, struct quisk_dHB45Filter *); int quisk_cInterp2HB45(complex double *, int, struct quisk_cHB45Filter *); @@ -56,19 +57,21 @@ extern double quiskMicFilt8Coefs[93]; extern double quiskLpFilt48Coefs[186]; extern double quiskFilt12_19Coefs[64]; -extern double quiskFilt185D3Coefs[188]; +extern double quiskFilt185D3Coefs[189]; extern double quiskFilt133D2Coefs[136]; -extern double quiskFilt167D3Coefs[173]; +extern double quiskFilt167D3Coefs[174]; extern double quiskFilt111D2Coefs[114]; extern double quiskFilt53D1Coefs[55]; -extern double quiskFilt144D3Coefs[194]; -extern double quiskFilt240D5Coefs[114]; -extern double quiskFilt240D5CoefsSharp[246]; +extern double quiskFilt144D3Coefs[195]; +extern double quiskFilt240D5Coefs[115]; +extern double quiskFilt240D5CoefsSharp[245]; extern double quiskFilt48dec24Coefs[98]; extern double quiskAudio24p6Coefs[36]; extern double quiskAudio48p6Coefs[71]; extern double quiskAudio96Coefs[11]; -extern double quiskAudio24p4Coefs[47]; +extern double quiskAudio24p4Coefs[50]; extern double quiskAudioFmHpCoefs[309]; -extern double quiskAudio24p3Coefs[93]; +extern double quiskAudio24p3Coefs[100]; extern double quiskFiltTx8kAudioB[168]; +extern double quiskFilt16dec8Coefs[62]; +extern double quiskFilt120s03[480]; diff -Nru quisk-3.6.18/filters.h quisk-3.7.6/filters.h --- quisk-3.6.18/filters.h 2014-05-27 19:23:28.000000000 +0000 +++ quisk-3.7.6/filters.h 2015-08-24 12:49:09.000000000 +0000 @@ -144,44 +144,44 @@ 0.000043810351462319} ; // Sample 185185 Hz, pass 20000, stop 24000, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Stop 0.12960. -double quiskFilt185D3Coefs[188] = { 0.000008153800001840, -0.000001771417630395, -0.000037253333929034, -0.000119009795238060, --0.000255018503860098, -0.000431457630502916, -0.000606779374823655, -0.000718366697089728, -0.000703620301380014, --0.000529751805487936, -0.000219462957628643, 0.000142545317473537, 0.000431747689511389, 0.000531458261578183, - 0.000388343045150861, 0.000049369127393032, -0.000342917144392365, -0.000603313895145129, -0.000587692675369399, --0.000270442421243832, 0.000225386999468089, 0.000669074815263746, 0.000825032169620294, 0.000573830722863008, --0.000009077307935200, -0.000664918089414559, -0.001061106586632969, -0.000955502500127749, -0.000336438190374833, - 0.000539349074607305, 0.001245645977048461, 0.001389173752996503, 0.000822043470106957, -0.000247598052622219, --0.001319476735963911, -0.001828796700280690, -0.001437569856610004, -0.000242431773066792, 0.001222656916942065, - 0.002216144517102359, 0.002157389501918925, 0.000954312268698896, -0.000889647952809809, -0.002472890164473277, --0.002928202444412127, -0.001887404641539060, 0.000266028947341680, 0.002515528194768854, 0.003680340277064590, - 0.003024505869869945, 0.000697099931502566, -0.002249597199494156, -0.004320335692842188, -0.004323014468691384, --0.002034738390180464, 0.001575270318365026, 0.004732326142178709, 0.005712473175796522, 0.003763483203528208, --0.000391719810437692, -0.004781111888508037, -0.007096880559266867, -0.005884712017661407, -0.001407862533738378, - 0.004306693842081116, 0.008352211939490001, 0.008388529075785354, 0.003946736249321500, -0.003112792599933821, --0.009323923519118172, -0.011269282117577429, -0.007398426385575320, 0.000928531365753789, 0.009809484942446507, - 0.014553037120437585, 0.012062615692744548, 0.002683413619215453, -0.009516487432546697, -0.018375440190605826, --0.018584023004946444, -0.008607549657362717, 0.007911844367040830, 0.023198314076417474, 0.028659214411537771, - 0.019167047651360933, -0.003633593297082508, -0.030707268487267729, -0.048352925046987114, -0.043463395046782413, --0.009280832167164070, 0.050801866891763532, 0.123196454160356600, 0.188154012695948810, 0.226499569996940320, - 0.226499569996940320, 0.188154012695948810, 0.123196454160356600, 0.050801866891763532, -0.009280832167164070, --0.043463395046782413, -0.048352925046987114, -0.030707268487267729, -0.003633593297082508, 0.019167047651360933, - 0.028659214411537771, 0.023198314076417474, 0.007911844367040830, -0.008607549657362717, -0.018584023004946444, --0.018375440190605826, -0.009516487432546697, 0.002683413619215453, 0.012062615692744548, 0.014553037120437585, - 0.009809484942446507, 0.000928531365753789, -0.007398426385575320, -0.011269282117577429, -0.009323923519118172, --0.003112792599933821, 0.003946736249321500, 0.008388529075785354, 0.008352211939490001, 0.004306693842081116, --0.001407862533738378, -0.005884712017661407, -0.007096880559266867, -0.004781111888508037, -0.000391719810437692, - 0.003763483203528208, 0.005712473175796522, 0.004732326142178709, 0.001575270318365026, -0.002034738390180464, --0.004323014468691384, -0.004320335692842188, -0.002249597199494156, 0.000697099931502566, 0.003024505869869945, - 0.003680340277064590, 0.002515528194768854, 0.000266028947341680, -0.001887404641539060, -0.002928202444412127, --0.002472890164473277, -0.000889647952809809, 0.000954312268698896, 0.002157389501918925, 0.002216144517102359, - 0.001222656916942065, -0.000242431773066792, -0.001437569856610004, -0.001828796700280690, -0.001319476735963911, --0.000247598052622219, 0.000822043470106957, 0.001389173752996503, 0.001245645977048461, 0.000539349074607305, --0.000336438190374833, -0.000955502500127749, -0.001061106586632969, -0.000664918089414559, -0.000009077307935200, - 0.000573830722863008, 0.000825032169620294, 0.000669074815263746, 0.000225386999468089, -0.000270442421243832, --0.000587692675369399, -0.000603313895145129, -0.000342917144392365, 0.000049369127393032, 0.000388343045150861, - 0.000531458261578183, 0.000431747689511389, 0.000142545317473537, -0.000219462957628643, -0.000529751805487936, --0.000703620301380014, -0.000718366697089728, -0.000606779374823655, -0.000431457630502916, -0.000255018503860098, --0.000119009795238060, -0.000037253333929034, -0.000001771417630395, 0.000008153800001840 }; +double quiskFilt185D3Coefs[189] = { 0.000012775222963944, 0.000016983455496025, 0.000005695459401072, -0.000043370392322925, +-0.000147289810253534, -0.000306853192747712, -0.000495824111042255, -0.000659912449094600, -0.000730624820807555, +-0.000652527190094691, -0.000414108542407609, -0.000067101230547892, 0.000279651977919746, 0.000496266897694305, + 0.000487147757737141, 0.000242333355062937, -0.000142550724009829, -0.000494575786766818, -0.000635523916496215, +-0.000469056286907829, -0.000040928295181758, 0.000463663137093341, 0.000794834769348999, 0.000757740301889996, + 0.000321720777800376, -0.000340193393344642, -0.000912913559618510, -0.001084751128773843, -0.000711940589185640, + 0.000082299572584560, 0.000936277365105903, 0.001408757213795883, 0.001201831453599291, 0.000337664985017852, +-0.000811409064230295, -0.001675111110230421, -0.001762493246689817, -0.000932484695478804, 0.000487612811028567, + 0.001818232361534366, 0.002345552396132496, 0.001696550268334318, 0.000077777468070599, -0.001766041325684469, +-0.002884900730916659, -0.002604595228251389, -0.000916585198089093, 0.001441898300187086, 0.003295669370079313, + 0.003607178283399026, 0.002043134080945332, -0.000770834928466311, -0.003477680777975640, -0.004630423337101654, +-0.003451243412347569, -0.000316897327995463, 0.003317404994207552, 0.005575542061462892, 0.005112123782225341, + 0.001883844314927795, -0.002688463188924373, -0.006318001179294125, -0.006973801349612467, -0.003986753493372796, + 0.001448254061321833, 0.006704745658002290, 0.008962905591378809, 0.006685267872330669, 0.000575241526335665, +-0.006544512593532594, -0.010988781408054678, -0.010065358566522303, -0.003619687890287124, 0.005579382505096557, + 0.012949729773207512, 0.014296503321222344, 0.008083093875296470, -0.003403672893464083, -0.014738780224596465, +-0.019777086918256612, -0.014784814209033215, -0.000774387161930971, 0.016257202240672342, 0.027605140528859175, + 0.025878721397717495, 0.009050451134125328, -0.017412070606886515, -0.041603859087594751, -0.049367067487597505, +-0.030106928037341840, 0.018134551394911973, 0.086605309563255489, 0.157907419743824910, 0.211651319263293920, + 0.231619508265925700, 0.211651319263293920, 0.157907419743824910, 0.086605309563255489, 0.018134551394911973, +-0.030106928037341840, -0.049367067487597505, -0.041603859087594751, -0.017412070606886515, 0.009050451134125328, + 0.025878721397717495, 0.027605140528859175, 0.016257202240672342, -0.000774387161930971, -0.014784814209033215, +-0.019777086918256612, -0.014738780224596465, -0.003403672893464083, 0.008083093875296470, 0.014296503321222344, + 0.012949729773207512, 0.005579382505096557, -0.003619687890287124, -0.010065358566522303, -0.010988781408054678, +-0.006544512593532594, 0.000575241526335665, 0.006685267872330669, 0.008962905591378809, 0.006704745658002290, + 0.001448254061321833, -0.003986753493372796, -0.006973801349612467, -0.006318001179294125, -0.002688463188924373, + 0.001883844314927795, 0.005112123782225341, 0.005575542061462892, 0.003317404994207552, -0.000316897327995463, +-0.003451243412347569, -0.004630423337101654, -0.003477680777975640, -0.000770834928466311, 0.002043134080945332, + 0.003607178283399026, 0.003295669370079313, 0.001441898300187086, -0.000916585198089093, -0.002604595228251389, +-0.002884900730916659, -0.001766041325684469, 0.000077777468070599, 0.001696550268334318, 0.002345552396132496, + 0.001818232361534366, 0.000487612811028567, -0.000932484695478804, -0.001762493246689817, -0.001675111110230421, +-0.000811409064230295, 0.000337664985017852, 0.001201831453599291, 0.001408757213795883, 0.000936277365105903, + 0.000082299572584560, -0.000711940589185640, -0.001084751128773843, -0.000912913559618510, -0.000340193393344642, + 0.000321720777800376, 0.000757740301889996, 0.000794834769348999, 0.000463663137093341, -0.000040928295181758, +-0.000469056286907829, -0.000635523916496215, -0.000494575786766818, -0.000142550724009829, 0.000242333355062937, + 0.000487147757737141, 0.000496266897694305, 0.000279651977919746, -0.000067101230547892, -0.000414108542407609, +-0.000652527190094691, -0.000730624820807555, -0.000659912449094600, -0.000495824111042255, -0.000306853192747712, +-0.000147289810253534, -0.000043370392322925, 0.000005695459401072, 0.000016983455496025, 0.000012775222963944} ; // Sample 185185 Hz, pass 15000, stop 23900, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Stop 0.129060. double quiskFilt185D3XCoefs[88] = { -0.000026016801458962, -0.000079343977811083, -0.000167897725115826, -0.000272158478205181, @@ -449,78 +449,82 @@ // Sample 240 kHz, pass 15, stop 23.9, ripple 0. 1dB, atten 100 dB, taps 114. For 240 to 48 decimation by 5. Stop 0.09958. -double quiskFilt240D5Coefs[114] = { -0.000017444714121896, -0.000043389176259008, -0.000086666475464372, -0.000143622066705595, --0.000204429258992282, -0.000250894192563019, -0.000258038648420713, -0.000198760816315186, -0.000051592624047688, 0.000190007232237523, - 0.000509003255256297, 0.000859996990289808, 0.001171187148400100, 0.001354817951508860, 0.001325999614958582, 0.001026888404781159, - 0.000450896776475450, -0.000340058091672613, -0.001212260716607277, -0.001975712012888962, -0.002419081295027340, -0.002359587962889753, --0.001697757353388466, -0.000463309281242825, 0.001162369385377015, 0.002858574184542211, 0.004216991598475732, 0.004827550436585937, - 0.004383011824547803, 0.002778680650352727, 0.000180287057286554, -0.002963889630832239, -0.005980581348043338, -0.008097241865875090, --0.008616599305826346, -0.007104942880323794, -0.003549433861261754, 0.001560075425221182, 0.007257901257043677, 0.012249987278338943, - 0.015166545447995922, 0.014877026236042860, 0.010803636353332058, 0.003161070172512297, -0.006942541244647841, -0.017585167076596235, --0.026297182632904086, -0.030480128886681127, -0.027906762548949221, -0.017206725660431417, 0.001763368880847165, 0.027750612266565198, - 0.058186952307218993, 0.089522326728125629, 0.117760091355900630, 0.139101943345962650, 0.150584021572851110, 0.150584021572851110, - 0.139101943345962650, 0.117760091355900630, 0.089522326728125629, 0.058186952307218993, 0.027750612266565198, 0.001763368880847165, --0.017206725660431417, -0.027906762548949221, -0.030480128886681127, -0.026297182632904086, -0.017585167076596235, -0.006942541244647841, - 0.003161070172512297, 0.010803636353332058, 0.014877026236042860, 0.015166545447995922, 0.012249987278338943, 0.007257901257043677, - 0.001560075425221182, -0.003549433861261754, -0.007104942880323794, -0.008616599305826346, -0.008097241865875090, -0.005980581348043338, --0.002963889630832239, 0.000180287057286554, 0.002778680650352727, 0.004383011824547803, 0.004827550436585937, 0.004216991598475732, - 0.002858574184542211, 0.001162369385377015, -0.000463309281242825, -0.001697757353388466, -0.002359587962889753, -0.002419081295027340, --0.001975712012888962, -0.001212260716607277, -0.000340058091672613, 0.000450896776475450, 0.001026888404781159, 0.001325999614958582, - 0.001354817951508860, 0.001171187148400100, 0.000859996990289808, 0.000509003255256297, 0.000190007232237523, -0.000051592624047688, --0.000198760816315186, -0.000258038648420713, -0.000250894192563019, -0.000204429258992282, -0.000143622066705595, -0.000086666475464372, --0.000043389176259008, -0.000017444714121896} ; +double quiskFilt240D5Coefs[115] = { -0.000016728727123248, -0.000044087357718719, -0.000091924762762279, -0.000159511110578360, +-0.000239892740115764, -0.000317037848665795, -0.000366082222804719, -0.000356867696341249, -0.000260799073810572, +-0.000060362275313048, 0.000240882695147526, 0.000610784476390585, 0.000986747708265456, 0.001282232501802643, + 0.001402382039117427, 0.001267163059859552, 0.000837554837630180, 0.000138176060202843, -0.000731120724040292, +-0.001601062409512967, -0.002259171778682862, -0.002494407062419913, -0.002152575146141896, -0.001190005644388707, + 0.000289463534037753, 0.002027990436998251, 0.003646223170245011, 0.004714602533497222, 0.004852837355866500, + 0.003837221343174296, 0.001689352142256304, -0.001280461340842228, -0.004495043698374529, -0.007204192122448914, +-0.008639958370167617, -0.008205272191961563, -0.005654509302706373, -0.001219595475489302, 0.004358910000877759, + 0.009919482807219961, 0.014084647220259008, 0.015553522670126217, 0.013426967081734104, 0.007494973982180793, +-0.001584790943077927, -0.012272742073297286, -0.022322599151574144, -0.029132041059785355, -0.030214716745057827, +-0.023707788648535934, -0.008813567526928788, 0.013920929398671823, 0.042556246756071010, 0.073971248970453499, + 0.104307827587028490, 0.129576402798269570, 0.146313667429180170, 0.152169870450184460, 0.146313667429180170, + 0.129576402798269570, 0.104307827587028490, 0.073971248970453499, 0.042556246756071010, 0.013920929398671823, +-0.008813567526928788, -0.023707788648535934, -0.030214716745057827, -0.029132041059785355, -0.022322599151574144, +-0.012272742073297286, -0.001584790943077927, 0.007494973982180793, 0.013426967081734104, 0.015553522670126217, + 0.014084647220259008, 0.009919482807219961, 0.004358910000877759, -0.001219595475489302, -0.005654509302706373, +-0.008205272191961563, -0.008639958370167617, -0.007204192122448914, -0.004495043698374529, -0.001280461340842228, + 0.001689352142256304, 0.003837221343174296, 0.004852837355866500, 0.004714602533497222, 0.003646223170245011, + 0.002027990436998251, 0.000289463534037753, -0.001190005644388707, -0.002152575146141896, -0.002494407062419913, +-0.002259171778682862, -0.001601062409512967, -0.000731120724040292, 0.000138176060202843, 0.000837554837630180, + 0.001267163059859552, 0.001402382039117427, 0.001282232501802643, 0.000986747708265456, 0.000610784476390585, + 0.000240882695147526, -0.000060362275313048, -0.000260799073810572, -0.000356867696341249, -0.000366082222804719, +-0.000317037848665795, -0.000239892740115764, -0.000159511110578360, -0.000091924762762279, -0.000044087357718719, +-0.000016728727123248} ; // Sample 240 kHz, pass 20, stop 24, ripple 0. 1dB, atten 100 dB, taps 114. For 240 to 48 decimation by 5. Stop 0.1000. -double quiskFilt240D5CoefsSharp[246] = { 0.000011951828200011, 0.000019798433756403, 0.000028547330106604, 0.000030165284908057, - 0.000016460532579755, -0.000020835366942643, -0.000087256395370183, -0.000182447830402188, -0.000297836387286273, --0.000416082361724969, -0.000513090543707066, -0.000562800094080439, -0.000543922161049598, -0.000447038893351438, --0.000279964105369211, -0.000069269573936326, 0.000143339868281261, 0.000309776397278667, 0.000387771231401110, - 0.000353469185042608, 0.000210686954436390, -0.000006554284588476, -0.000240063519947365, -0.000421527417296624, --0.000491443283168306, -0.000417820101680067, -0.000208868052760534, 0.000085048027839757, 0.000382180878613336, - 0.000591123735768924, 0.000638042870263572, 0.000491558598565883, 0.000177179393286866, -0.000224481053594759, --0.000597014064504330, -0.000820930704466417, -0.000810918218693337, -0.000546326732285124, -0.000084459306793206, - 0.000449422013818846, 0.000894687281371102, 0.001102541594699144, 0.000984325470442889, 0.000545624937191001, --0.000106146183328305, -0.000785797405770491, -0.001281535037100789, -0.001419612701203994, -0.001123137180667268, --0.000445673862380164, 0.000433514280597531, 0.001254730807221768, 0.001752968220115270, 0.001741675133186359, - 0.001179202567033726, 0.000195102369455356, -0.000935783344044036, -0.001867876483977602, -0.002288593398642863, --0.002020698232986274, -0.001090456401574151, 0.000263178814654847, 0.001646402890369986, 0.002622726888175473, - 0.002848310252497599, 0.002189220340667481, 0.000781654013623377, -0.000988901141783250, -0.002589700588821625, --0.003498176390167329, -0.003369141152473258, -0.002159241772770695, -0.000165464996932323, 0.002041618007644499, - 0.003777187265991297, 0.004450866229775656, 0.003762315917274342, 0.001820394865830094, -0.000858234059046015, --0.003480363951041699, -0.005206132491721142, -0.005412440056656052, -0.003908911556192801, -0.001033963211572532, - 0.002409267811432101, 0.005369858454077349, 0.006862619655562839, 0.006286791984567192, 0.003649433286047820, --0.000385090824056697, -0.004649684536230074, -0.007801516508809942, -0.008732884415488085, -0.006943637236744675, --0.002754601930921507, 0.002718968772510816, 0.007846810206617289, 0.010951016745475875, 0.010832279872067337, - 0.007196213900006544, 0.000837720738654166, -0.006496855603590420, -0.012558347528192997, -0.015246550948573622, --0.013285617706403107, -0.006719315573973105, 0.002942907242266441, 0.013010190699738699, 0.020286552390091313, - 0.021982424443790855, 0.016621666248368951, 0.004683273039499340, -0.011231582005110220, -0.026802267461528154, --0.036907110368292514, -0.036858745295310878, -0.023657596515413496, 0.003060851507965577, 0.040649713296411087, - 0.083777373113400611, 0.125416065458074430, 0.158282028171104980, 0.176396323031566690, 0.176396323031566690, - 0.158282028171104980, 0.125416065458074430, 0.083777373113400611, 0.040649713296411087, 0.003060851507965577, --0.023657596515413496, -0.036858745295310878, -0.036907110368292514, -0.026802267461528154, -0.011231582005110220, - 0.004683273039499340, 0.016621666248368951, 0.021982424443790855, 0.020286552390091313, 0.013010190699738699, - 0.002942907242266441, -0.006719315573973105, -0.013285617706403107, -0.015246550948573622, -0.012558347528192997, --0.006496855603590420, 0.000837720738654166, 0.007196213900006544, 0.010832279872067337, 0.010951016745475875, - 0.007846810206617289, 0.002718968772510816, -0.002754601930921507, -0.006943637236744675, -0.008732884415488085, --0.007801516508809942, -0.004649684536230074, -0.000385090824056697, 0.003649433286047820, 0.006286791984567192, - 0.006862619655562839, 0.005369858454077349, 0.002409267811432101, -0.001033963211572532, -0.003908911556192801, --0.005412440056656052, -0.005206132491721142, -0.003480363951041699, -0.000858234059046015, 0.001820394865830094, - 0.003762315917274342, 0.004450866229775656, 0.003777187265991297, 0.002041618007644499, -0.000165464996932323, --0.002159241772770695, -0.003369141152473258, -0.003498176390167329, -0.002589700588821625, -0.000988901141783250, - 0.000781654013623377, 0.002189220340667481, 0.002848310252497599, 0.002622726888175473, 0.001646402890369986, - 0.000263178814654847, -0.001090456401574151, -0.002020698232986274, -0.002288593398642863, -0.001867876483977602, --0.000935783344044036, 0.000195102369455356, 0.001179202567033726, 0.001741675133186359, 0.001752968220115270, - 0.001254730807221768, 0.000433514280597531, -0.000445673862380164, -0.001123137180667268, -0.001419612701203994, --0.001281535037100789, -0.000785797405770491, -0.000106146183328305, 0.000545624937191001, 0.000984325470442889, - 0.001102541594699144, 0.000894687281371102, 0.000449422013818846, -0.000084459306793206, -0.000546326732285124, --0.000810918218693337, -0.000820930704466417, -0.000597014064504330, -0.000224481053594759, 0.000177179393286866, - 0.000491558598565883, 0.000638042870263572, 0.000591123735768924, 0.000382180878613336, 0.000085048027839757, --0.000208868052760534, -0.000417820101680067, -0.000491443283168306, -0.000421527417296624, -0.000240063519947365, --0.000006554284588476, 0.000210686954436390, 0.000353469185042608, 0.000387771231401110, 0.000309776397278667, - 0.000143339868281261, -0.000069269573936326, -0.000279964105369211, -0.000447038893351438, -0.000543922161049598, --0.000562800094080439, -0.000513090543707066, -0.000416082361724969, -0.000297836387286273, -0.000182447830402188, --0.000087256395370183, -0.000020835366942643, 0.000016460532579755, 0.000030165284908057, 0.000028547330106604, - 0.000019798433756403, 0.000011951828200011 }; +double quiskFilt240D5CoefsSharp[245] = {0.000010509517951006, 0.000013600436904199, 0.000014114646487651, + 0.000003107336183074, -0.000027062229851180, -0.000082599263842674, -0.000165408687939920, -0.000270459677107609, +-0.000384429386564009, -0.000486513762577162, -0.000551912068282870, -0.000557575961050952, -0.000489097193751572, +-0.000346866350142718, -0.000149400346186253, 0.000067944169519721, 0.000259321418469537, 0.000379109945031964, + 0.000394500403136929, 0.000296443107959613, 0.000105514360701591, -0.000130126194004534, -0.000345130836201596, +-0.000473961146691502, -0.000470132844463270, -0.000322205344194264, -0.000061120176458703, 0.000244508351024915, + 0.000505224922624527, 0.000635967434817550, 0.000582869672082906, 0.000343410640475203, -0.000027211329865173, +-0.000427051494167815, -0.000734325622577685, -0.000843497343068510, -0.000700190684320865, -0.000323704342054218, + 0.000191371674406576, 0.000697200136082736, 0.001033942501059566, 0.001079068090563369, 0.000789896649978617, + 0.000225095253699184, -0.000463869809966486, -0.001071638023123453, -0.001398840064879135, -0.001315934285574502, +-0.000810990907316290, -0.000004610004754826, 0.000875943963655002, 0.001559026923347157, 0.001810835543451637, + 0.001513186414012800, 0.000711563768288702, -0.000384610343377712, -0.001453941368175342, -0.002155359985238479, +-0.002234880299660999, -0.001614005062810786, -0.000429922430657965, 0.000989560561609744, 0.002214005025736412, + 0.002838581123665033, 0.002615068817313840, 0.001544038090357682, -0.000104494434130949, -0.001854539656237390, +-0.003158534255089867, -0.003565698051769338, -0.002873486424864839, -0.001212438364790582, 0.000968078677012544, + 0.003016832609770853, 0.004271507641806298, 0.004268577543475138, 0.002906991659042924, 0.000509839095017221, +-0.002243915738203439, -0.004506439605309381, -0.005516963680092404, -0.004850900074620331, -0.002582471594385039, + 0.000697817320287101, 0.004028242154893376, 0.006350748353554076, 0.006839211177606702, 0.005181509596969096, + 0.001722314463339247, -0.002590160933654113, -0.006453095775103189, -0.008591647653124229, -0.008165982840134313, +-0.005076656227616793, -0.000063471506879233, 0.005451413373916889, 0.009749704157369357, 0.011332344600275311, + 0.009414890577675568, 0.004246680386224628, -0.002862043391342491, -0.009841507474776875, -0.014431368776294761, +-0.014868975905553507, -0.010498438296855372, -0.002106968919934075, 0.008141920985575500, 0.017201630945497853, + 0.021962093122837603, 0.020191237691200150, 0.011339390591466089, -0.003037076058820319, -0.019391273155317083, +-0.032862864929577094, -0.038387061811562206, -0.031979063119985378, -0.011872162453893439, 0.020795139196288157, + 0.061948624205024894, 0.105252254728618600, 0.143357336865708950, 0.169447992824642290, 0.178721289066174270, + 0.169447992824642290, 0.143357336865708950, 0.105252254728618600, 0.061948624205024894, 0.020795139196288157, +-0.011872162453893439, -0.031979063119985378, -0.038387061811562206, -0.032862864929577094, -0.019391273155317083, +-0.003037076058820319, 0.011339390591466089, 0.020191237691200150, 0.021962093122837603, 0.017201630945497853, + 0.008141920985575500, -0.002106968919934075, -0.010498438296855372, -0.014868975905553507, -0.014431368776294761, +-0.009841507474776875, -0.002862043391342491, 0.004246680386224628, 0.009414890577675568, 0.011332344600275311, + 0.009749704157369357, 0.005451413373916889, -0.000063471506879233, -0.005076656227616793, -0.008165982840134313, +-0.008591647653124229, -0.006453095775103189, -0.002590160933654113, 0.001722314463339247, 0.005181509596969096, + 0.006839211177606702, 0.006350748353554076, 0.004028242154893376, 0.000697817320287101, -0.002582471594385039, +-0.004850900074620331, -0.005516963680092404, -0.004506439605309381, -0.002243915738203439, 0.000509839095017221, + 0.002906991659042924, 0.004268577543475138, 0.004271507641806298, 0.003016832609770853, 0.000968078677012544, +-0.001212438364790582, -0.002873486424864839, -0.003565698051769338, -0.003158534255089867, -0.001854539656237390, +-0.000104494434130949, 0.001544038090357682, 0.002615068817313840, 0.002838581123665033, 0.002214005025736412, + 0.000989560561609744, -0.000429922430657965, -0.001614005062810786, -0.002234880299660999, -0.002155359985238479, +-0.001453941368175342, -0.000384610343377712, 0.000711563768288702, 0.001513186414012800, 0.001810835543451637, + 0.001559026923347157, 0.000875943963655002, -0.000004610004754826, -0.000810990907316290, -0.001315934285574502, +-0.001398840064879135, -0.001071638023123453, -0.000463869809966486, 0.000225095253699184, 0.000789896649978617, + 0.001079068090563369, 0.001033942501059566, 0.000697200136082736, 0.000191371674406576, -0.000323704342054218, +-0.000700190684320865, -0.000843497343068510, -0.000734325622577685, -0.000427051494167815, -0.000027211329865173, + 0.000343410640475203, 0.000582869672082906, 0.000635967434817550, 0.000505224922624527, 0.000244508351024915, +-0.000061120176458703, -0.000322205344194264, -0.000470132844463270, -0.000473961146691502, -0.000345130836201596, +-0.000130126194004534, 0.000105514360701591, 0.000296443107959613, 0.000394500403136929, 0.000379109945031964, + 0.000259321418469537, 0.000067944169519721, -0.000149400346186253, -0.000346866350142718, -0.000489097193751572, +-0.000557575961050952, -0.000551912068282870, -0.000486513762577162, -0.000384429386564009, -0.000270459677107609, +-0.000165408687939920, -0.000082599263842674, -0.000027062229851180, 0.000003107336183074, 0.000014114646487651, + 0.000013600436904199, 0.000010509517951006} ; // Sample 48 kHz, pass 10, stop 12, ripple 0.1 dB, atten 100 dB. Stop 0.25000. double quiskFilt48dec24Coefs[98] = { 0.000036864882767612, 0.000009858596392836, -0.000330770380800406, -0.001009174072411182, @@ -544,16 +548,34 @@ 0.000947009978368078, 0.000523955998497470, -0.000770236458542885, -0.001404963853116591, -0.001009174072411182, -0.000330770380800406, 0.000009858596392836, 0.000036864882767612} ; -// Sample 24 kHz, pass 4, stop 6, ripple 0.2 dB, atten 100 dB. Stop 0.25000. -double quiskAudio24p4Coefs[47] = { -0.000088032928341076, -0.000644655891856698, -0.001950684938386873, -0.003218845681314026, --0.002450782287864210, 0.001217506567762789, 0.004686926326285676, 0.002765800843238593, -0.004566450766965229, -0.008584510412060854, --0.000971556018221359, 0.012083832500170146, 0.012188815169382777, -0.007290936633701373, -0.024348624812992169, -0.010068583077594291, - 0.027777796699414107, 0.039152990662982279, -0.009401736137370085, -0.072784723467554810, -0.051588082257896843, 0.099826186027899166, - 0.298074745160505860, 0.389807370973666690, 0.298074745160505860, 0.099826186027899166, -0.051588082257896843, -0.072784723467554810, --0.009401736137370085, 0.039152990662982279, 0.027777796699414107, -0.010068583077594291, -0.024348624812992169, -0.007290936633701373, - 0.012188815169382777, 0.012083832500170146, -0.000971556018221359, -0.008584510412060854, -0.004566450766965229, 0.002765800843238593, - 0.004686926326285676, 0.001217506567762789, -0.002450782287864210, -0.003218845681314026, -0.001950684938386873, -0.000644655891856698, --0.000088032928341076} ; +// Sample 16 kHz, pass 3, stop 4, ripple 0.2 dB, atten 100 dB. Stop 0.25000. +double quiskFilt16dec8Coefs[62] = { -0.000124177474244557, -0.000430523605276357, -0.000493631990211104, 0.000494123472239155, + 0.002450538023220896, 0.003434046886116560, 0.001430964941031747, -0.002160470594532627, -0.002743908756760310, + 0.001447692179523467, 0.005035286516949985, 0.001559830515735925, -0.005832171392166522, -0.005961983009450190, + 0.004090355806650406, 0.010727470109490524, 0.001295745938037953, -0.013610977881243281, -0.010206097747183476, + 0.011968191835149432, 0.021092443344418406, -0.003254546341513048, -0.030789280172024487, -0.014459667926688211, + 0.034543627845998542, 0.042879451356168191, -0.025057567311424923, -0.087496290237351254, -0.017166635170977933, + 0.194607349925601910, 0.387989214201396540, 0.387989214201396540, 0.194607349925601910, -0.017166635170977933, +-0.087496290237351254, -0.025057567311424923, 0.042879451356168191, 0.034543627845998542, -0.014459667926688211, +-0.030789280172024487, -0.003254546341513048, 0.021092443344418406, 0.011968191835149432, -0.010206097747183476, +-0.013610977881243281, 0.001295745938037953, 0.010727470109490524, 0.004090355806650406, -0.005961983009450190, +-0.005832171392166522, 0.001559830515735925, 0.005035286516949985, 0.001447692179523467, -0.002743908756760310, +-0.002160470594532627, 0.001430964941031747, 0.003434046886116560, 0.002450538023220896, 0.000494123472239155, +-0.000493631990211104, -0.000430523605276357, -0.000124177474244557} ; + + +// Sample 24 kHz, pass 4, stop 6, ripple 0.1 dB, atten 100 dB. Stop 0.25000. +double quiskAudio24p4Coefs[50] = { 0.000157528548309112, 0.000724067233656635, 0.001494063979696902, 0.001415398667382404, +-0.000379600362177000, -0.002568955219195720, -0.001955131144563022, 0.002290648015950155, 0.005318866809894723, + 0.001269605748864858, -0.007123779005412860, -0.008160775426007658, 0.003744714029953210, 0.015299947817479908, + 0.007159589230094381, -0.016608567214549036, -0.024058672575662729, 0.004961464480378132, 0.040021693556365548, + 0.025370838603802327, -0.042357473426880975, -0.080147218603820458, 0.005640775946851126, 0.201560113392096980, + 0.369146036302117400, 0.369146036302117400, 0.201560113392096980, 0.005640775946851126, -0.080147218603820458, +-0.042357473426880975, 0.025370838603802327, 0.040021693556365548, 0.004961464480378132, -0.024058672575662729, +-0.016608567214549036, 0.007159589230094381, 0.015299947817479908, 0.003744714029953210, -0.008160775426007658, +-0.007123779005412860, 0.001269605748864858, 0.005318866809894723, 0.002290648015950155, -0.001955131144563022, +-0.002568955219195720, -0.000379600362177000, 0.001415398667382404, 0.001494063979696902, 0.000724067233656635, + 0.000157528548309112} ; // Sample 24 kHz, pass 6, stop 8, ripple 0.5 dB, atten 80 dB. Stop 0.33333. double quiskAudio24p6Coefs[36] = { 0.001199140008010727, 0.005953815908571521, 0.008621055763448699, -0.000319602525571569, @@ -641,60 +663,65 @@ -0.000365311474947399, -0.000434052387522716, -0.000504808183463150, -0.000579117971409598, -0.000658731927598885, -0.000745515693376396, -0.000841696354699768, -0.000949749961336003, -0.001072277374239886, -0.001211970386842814, 0.004847574947800705} ; -// Sample 24 kHz, pass 3, stop 4, ripple 0.2 dB, atten 100 dB. Stop 0.166667. -double quiskAudio24p3Coefs[93] = { -0.000037783993624828, -0.000115216534351507, -0.000197004848051633, -0.000166882497461099, - 0.000128538373562514, 0.000763065896965353, 0.001593251408880778, 0.002221476977470587, 0.002159957821475971, 0.001165685783386774, --0.000455884791825150, -0.001852148041710624, -0.002061857791611326, -0.000704774303788570, 0.001548186623458437, 0.003181880133780748, - 0.002773578777222453, 0.000127448157901667, -0.003249774740083473, -0.004900026839230316, -0.003119142778828723, 0.001505846985194979, - 0.006005835857891277, 0.006816532154902235, 0.002465206003840461, -0.004779621260530268, -0.009853981311881564, -0.008278389476095381, - 0.000168292207858724, 0.010329120063920900, 0.014549834598444522, 0.008161510823584768, -0.006176039595703008, -0.018913340959814302, --0.019528202468339487, -0.004537713172735999, 0.018084136858548469, 0.032234903031086101, 0.023988321525860046, -0.007153715631741163, --0.043920635751648937, -0.058267538333419230, -0.027093916052665576, 0.052156072898706766, 0.155951479742186460, 0.243837576013442580, - 0.278208881979582880, 0.243837576013442580, 0.155951479742186460, 0.052156072898706766, -0.027093916052665576, -0.058267538333419230, --0.043920635751648937, -0.007153715631741163, 0.023988321525860046, 0.032234903031086101, 0.018084136858548469, -0.004537713172735999, --0.019528202468339487, -0.018913340959814302, -0.006176039595703008, 0.008161510823584768, 0.014549834598444522, 0.010329120063920900, - 0.000168292207858724, -0.008278389476095381, -0.009853981311881564, -0.004779621260530268, 0.002465206003840461, 0.006816532154902235, - 0.006005835857891277, 0.001505846985194979, -0.003119142778828723, -0.004900026839230316, -0.003249774740083473, 0.000127448157901667, - 0.002773578777222453, 0.003181880133780748, 0.001548186623458437, -0.000704774303788570, -0.002061857791611326, -0.001852148041710624, --0.000455884791825150, 0.001165685783386774, 0.002159957821475971, 0.002221476977470587, 0.001593251408880778, 0.000763065896965353, - 0.000128538373562514, -0.000166882497461099, -0.000197004848051633, -0.000115216534351507, -0.000037783993624828} ; +// Sample 24 kHz, pass 3, stop 4, ripple 0.1 dB, atten 100 dB. Stop 0.166667. +double quiskAudio24p3Coefs[100] = { 0.000040101217607380, 0.000144142945632828, 0.000313272623844654, 0.000472482653992188, + 0.000478145332060253, 0.000196098255702949, -0.000370577202968848, -0.000996638165520275, -0.001297816111521122, +-0.000964536466628102, -0.000042557313824444, 0.000962997869327471, 0.001323534221298773, 0.000594056697947640, +-0.000938434034616708, -0.002262533962755747, -0.002248428515927852, -0.000532704303549466, 0.001971754193404763, + 0.003472380925898466, 0.002523293296727912, -0.000709634306372636, -0.004178686614322947, -0.005154383468615377, +-0.002273829954161664, 0.003035630406357807, 0.007068054286328254, 0.006315276617742645, 0.000339547080891264, +-0.007198952625282303, -0.010594445062611715, -0.006237154402398545, 0.004041031055408734, 0.013268415977346956, + 0.013808366838176838, 0.003317264370573068, -0.012230819216356600, -0.021457570785737779, -0.015397758077551751, + 0.004833972586296557, 0.026490252025494231, 0.032271888015886901, 0.012604612531251926, -0.024801184242399594, +-0.055520053090148611, -0.050680798756218122, 0.005272625250748675, 0.102090078311359670, 0.205131072151264500, + 0.271331067474994470, 0.271331067474994470, 0.205131072151264500, 0.102090078311359670, 0.005272625250748675, +-0.050680798756218122, -0.055520053090148611, -0.024801184242399594, 0.012604612531251926, 0.032271888015886901, + 0.026490252025494231, 0.004833972586296557, -0.015397758077551751, -0.021457570785737779, -0.012230819216356600, + 0.003317264370573068, 0.013808366838176838, 0.013268415977346956, 0.004041031055408734, -0.006237154402398545, +-0.010594445062611715, -0.007198952625282303, 0.000339547080891264, 0.006315276617742645, 0.007068054286328254, + 0.003035630406357807, -0.002273829954161664, -0.005154383468615377, -0.004178686614322947, -0.000709634306372636, + 0.002523293296727912, 0.003472380925898466, 0.001971754193404763, -0.000532704303549466, -0.002248428515927852, +-0.002262533962755747, -0.000938434034616708, 0.000594056697947640, 0.001323534221298773, 0.000962997869327471, +-0.000042557313824444, -0.000964536466628102, -0.001297816111521122, -0.000996638165520275, -0.000370577202968848, + 0.000196098255702949, 0.000478145332060253, 0.000472482653992188, 0.000313272623844654, 0.000144142945632828, + 0.000040101217607380} ; // Sample 166.667 kHz, pass 20.000, stop 23.900, ripple 0.1dB, atten 100 dB. Stop 0.143400. -double quiskFilt167D3Coefs[173] = { 0.000027361057967466, 0.000079022927580003, 0.000153315280214681, 0.000217036303406228, - 0.000216051711988195, 0.000097396424148557, -0.000154926195574306, -0.000490420382204739, -0.000792207008833313, --0.000919092591781850, -0.000778111000786101, -0.000391274475742801, 0.000087636155699997, 0.000431036292372644, - 0.000453513042034128, 0.000127278368774421, -0.000375305195602114, -0.000754032727213185, -0.000745162366934028, --0.000291077295308464, 0.000391691804410550, 0.000907469994025008, 0.000906322210882447, 0.000315586781248832, --0.000570606431536495, -0.001224850102488048, -0.001191706572954991, -0.000392843729362266, 0.000766771909476392, - 0.001591024783572854, 0.001502286627641521, 0.000433072083786191, -0.001058714434132909, -0.002065657351825392, --0.001872596976495124, -0.000447193062325162, 0.001448204360826397, 0.002647575301181126, 0.002286703308575930, - 0.000403725700513730, -0.001971005587443560, -0.003359964169317242, -0.002743264224337261, -0.000278054902152837, - 0.002659789549535132, 0.004222291522979791, 0.003233725726619094, 0.000035039391335235, -0.003557779274022707, --0.005259805099663439, -0.003746914316541065, 0.000371441180756436, 0.004722776316977453, 0.006507912944025527, - 0.004270009795668968, -0.001002952818151785, -0.006235533234742016, -0.008019243636496016, -0.004787772358159753, - 0.001948724018108411, 0.008220101302576532, 0.009881282059334088, 0.005283717292111678, -0.003348783872543343, --0.010884795691042899, -0.012252153446073137, -0.005739902334679721, 0.005449960457274001, 0.014621846680862713, - 0.015450997622704835, 0.006140561144775123, -0.008746269854437872, -0.020270474363958398, -0.020205028153266937, --0.006470785151112811, 0.014438262597161191, 0.030022509645911820, 0.028563858550479816, 0.006716896161104373, --0.026463233531944890, -0.051946702805221123, -0.049198922096865508, -0.006868026781416496, 0.069760519092899292, - 0.158783283295639180, 0.229837831554147300, 0.256919080611963280, 0.229837831554147300, 0.158783283295639180, - 0.069760519092899292, -0.006868026781416496, -0.049198922096865508, -0.051946702805221123, -0.026463233531944890, - 0.006716896161104373, 0.028563858550479816, 0.030022509645911820, 0.014438262597161191, -0.006470785151112811, --0.020205028153266937, -0.020270474363958398, -0.008746269854437872, 0.006140561144775123, 0.015450997622704835, - 0.014621846680862713, 0.005449960457274001, -0.005739902334679721, -0.012252153446073137, -0.010884795691042899, --0.003348783872543343, 0.005283717292111678, 0.009881282059334088, 0.008220101302576532, 0.001948724018108411, --0.004787772358159753, -0.008019243636496016, -0.006235533234742016, -0.001002952818151785, 0.004270009795668968, - 0.006507912944025527, 0.004722776316977453, 0.000371441180756436, -0.003746914316541065, -0.005259805099663439, --0.003557779274022707, 0.000035039391335235, 0.003233725726619094, 0.004222291522979791, 0.002659789549535132, --0.000278054902152837, -0.002743264224337261, -0.003359964169317242, -0.001971005587443560, 0.000403725700513730, - 0.002286703308575930, 0.002647575301181126, 0.001448204360826397, -0.000447193062325162, -0.001872596976495124, --0.002065657351825392, -0.001058714434132909, 0.000433072083786191, 0.001502286627641521, 0.001591024783572854, - 0.000766771909476392, -0.000392843729362266, -0.001191706572954991, -0.001224850102488048, -0.000570606431536495, - 0.000315586781248832, 0.000906322210882447, 0.000907469994025008, 0.000391691804410550, -0.000291077295308464, --0.000745162366934028, -0.000754032727213185, -0.000375305195602114, 0.000127278368774421, 0.000453513042034128, - 0.000431036292372644, 0.000087636155699997, -0.000391274475742801, -0.000778111000786101, -0.000919092591781850, --0.000792207008833313, -0.000490420382204739, -0.000154926195574306, 0.000097396424148557, 0.000216051711988195, - 0.000217036303406228, 0.000153315280214681, 0.000079022927580003, 0.000027361057967466 } ; +double quiskFilt167D3Coefs[174] = { 0.000030576791453527, 0.000100024508790516, 0.000219490573577180, 0.000367267217464130, + 0.000487951714515836, 0.000506290951844816, 0.000363119613743102, 0.000058217319683104, -0.000325800746668442, +-0.000641682936184189, -0.000741147106205377, -0.000558069498091234, -0.000164677374303788, 0.000241795422764969, + 0.000430784306851279, 0.000269110113911442, -0.000183008351848305, -0.000678061334270815, -0.000905480029500586, +-0.000676768412048412, -0.000064217209958982, 0.000604041196303946, 0.000913992713709960, 0.000618300484733981, +-0.000178595029186343, -0.001034200731311528, -0.001402993970581089, -0.000974211666713609, 0.000092170056602371, + 0.001200780364176702, 0.001644807535570492, 0.001049188500933230, -0.000339340877146445, -0.001726815143299963, +-0.002213609343829470, -0.001365082350867275, 0.000444074067558948, 0.002167326762557576, 0.002679800165208473, + 0.001499801473272823, -0.000816400722026263, -0.002906147604258461, -0.003386440545728839, -0.001750807523066417, + 0.001194559262667584, 0.003696427504621467, 0.004080958623548870, 0.001851416220932293, -0.001849699778520349, +-0.004790658557602842, -0.004973805523919799, -0.001961292686306448, 0.002659261470241232, 0.006072015731008707, + 0.005920856423841668, 0.001893698122810163, -0.003838202172739613, -0.007743253601342105, -0.007066127284542626, +-0.001708831992923160, 0.005393163352848182, 0.009818853048919281, 0.008356595075109337, 0.001237190414144524, +-0.007594080720116375, -0.012578375307378011, -0.009950345325599911, -0.000416149899767642, 0.010713667739836119, + 0.016347877720485654, 0.011969545448953062, -0.001060994469278557, -0.015508256818753343, -0.022033387392024387, +-0.014893466276995419, 0.003733297630878139, 0.023713405546777432, 0.031919145545518959, 0.019968942416937793, +-0.009436963490412478, -0.041522182058827371, -0.055167482103195595, -0.033140893435705220, 0.028140747437596390, + 0.114536627613187850, 0.198460066797957520, 0.250031620638361150, 0.250031620638361150, 0.198460066797957520, + 0.114536627613187850, 0.028140747437596390, -0.033140893435705220, -0.055167482103195595, -0.041522182058827371, +-0.009436963490412478, 0.019968942416937793, 0.031919145545518959, 0.023713405546777432, 0.003733297630878139, +-0.014893466276995419, -0.022033387392024387, -0.015508256818753343, -0.001060994469278557, 0.011969545448953062, + 0.016347877720485654, 0.010713667739836119, -0.000416149899767642, -0.009950345325599911, -0.012578375307378011, +-0.007594080720116375, 0.001237190414144524, 0.008356595075109337, 0.009818853048919281, 0.005393163352848182, +-0.001708831992923160, -0.007066127284542626, -0.007743253601342105, -0.003838202172739613, 0.001893698122810163, + 0.005920856423841668, 0.006072015731008707, 0.002659261470241232, -0.001961292686306448, -0.004973805523919799, +-0.004790658557602842, -0.001849699778520349, 0.001851416220932293, 0.004080958623548870, 0.003696427504621467, + 0.001194559262667584, -0.001750807523066417, -0.003386440545728839, -0.002906147604258461, -0.000816400722026263, + 0.001499801473272823, 0.002679800165208473, 0.002167326762557576, 0.000444074067558948, -0.001365082350867275, +-0.002213609343829470, -0.001726815143299963, -0.000339340877146445, 0.001049188500933230, 0.001644807535570492, + 0.001200780364176702, 0.000092170056602371, -0.000974211666713609, -0.001402993970581089, -0.001034200731311528, +-0.000178595029186343, 0.000618300484733981, 0.000913992713709960, 0.000604041196303946, -0.000064217209958982, +-0.000676768412048412, -0.000905480029500586, -0.000678061334270815, -0.000183008351848305, 0.000269110113911442, + 0.000430784306851279, 0.000241795422764969, -0.000164677374303788, -0.000558069498091234, -0.000741147106205377, +-0.000641682936184189, -0.000325800746668442, 0.000058217319683104, 0.000363119613743102, 0.000506290951844816, + 0.000487951714515836, 0.000367267217464130, 0.000219490573577180, 0.000100024508790516, 0.000030576791453527} ; // Sample 8000 Hz, stop 0 to 120, pass 300 to 2700, stop 2900 to 4000, ripple 0.1dB, atten 100 dB. double quiskFiltTx8kAudioB[168] = { 0.000382863369135811, 0.001125903700328483, 0.000925837447276999, -0.000374734941094989, @@ -820,43 +847,142 @@ -0.000287398500897765, -0.000192494530734137, -0.000119178480453066, -0.000087601196745517} ; // Sample 144 kHz, pass 22.5, stop 25.5, ripple 0.1dB, atten 100 dB. Stop 0.177083. -double quiskFilt144D3Coefs[194] = { -0.000017362646744960, -0.000110996454883008, -0.000305662442281008, --0.000581062092898679, -0.000813592880911599, -0.000835736178417635, -0.000552107526238211, -0.000054292188328318, - 0.000387773639773515, 0.000494027000545870, 0.000199352366062691, -0.000255881206127522, -0.000485740874895228, --0.000275452022064688, 0.000208833549611114, 0.000531744141964012, 0.000367581824060558, -0.000175453824284145, --0.000607318273395854, -0.000487816655783889, 0.000132693079697097, 0.000699066404450690, 0.000638095603939989, --0.000070303980749569, -0.000799106348889160, -0.000818491755176836, -0.000017387631184785, 0.000902749872050692, - 0.001029667300547813, 0.000134959379552175, -0.001006723075110756, -0.001273440263814288, -0.000287982640712631, - 0.001106868403908265, 0.001551023787987609, 0.000481887570810472, -0.001198782421759553, -0.001863328908272039, --0.000722025795734941, 0.001278004560152671, 0.002211344243623479, 0.001013908066665751, -0.001340209298573909, --0.002596738345596567, -0.001363939022079807, 0.001380885370638605, 0.003022505100981749, 0.001781163375917694, --0.001392819236778710, -0.003490161397622442, -0.002274598703178404, 0.001368261851295833, 0.004001016216719358, - 0.002852992881535207, -0.001301401881125085, -0.004561084830193090, -0.003531401220247879, 0.001181711086882310, - 0.005175897180115789, 0.004328107542098295, -0.000995201957981096, -0.005850285719973323, -0.005262280747149780, - 0.000730535564202526, 0.006599333845579120, 0.006368532227413207, -0.000364276829190096, -0.007436459975443203, --0.007687502662736216, -0.000128323325945401, 0.008389171878714446, 0.009284846717615725, 0.000787714738678533, --0.009496704044210443, -0.011258777810115510, -0.001673923806614419, 0.010823740890780302, 0.013768325691894498, - 0.002883606625761274, -0.012483069290180011, -0.017091073428179691, -0.004588813972380848, 0.014681862153683193, - 0.021751575898596408, 0.007124268344056597, -0.017848070732915621, -0.028877484772780435, -0.011247057630458057, - 0.023018317798969463, 0.041405495608225998, 0.019112216134543804, -0.033496644504759192, -0.070110267351511241, --0.040261998601017132, 0.068464073221763413, 0.211997420853570540, 0.313289733368657400, 0.313289733368657400, - 0.211997420853570540, 0.068464073221763413, -0.040261998601017132, -0.070110267351511241, -0.033496644504759192, - 0.019112216134543804, 0.041405495608225998, 0.023018317798969463, -0.011247057630458057, -0.028877484772780435, --0.017848070732915621, 0.007124268344056597, 0.021751575898596408, 0.014681862153683193, -0.004588813972380848, --0.017091073428179691, -0.012483069290180011, 0.002883606625761274, 0.013768325691894498, 0.010823740890780302, --0.001673923806614419, -0.011258777810115510, -0.009496704044210443, 0.000787714738678533, 0.009284846717615725, - 0.008389171878714446, -0.000128323325945401, -0.007687502662736216, -0.007436459975443203, -0.000364276829190096, - 0.006368532227413207, 0.006599333845579120, 0.000730535564202526, -0.005262280747149780, -0.005850285719973323, --0.000995201957981096, 0.004328107542098295, 0.005175897180115789, 0.001181711086882310, -0.003531401220247879, --0.004561084830193090, -0.001301401881125085, 0.002852992881535207, 0.004001016216719358, 0.001368261851295833, --0.002274598703178404, -0.003490161397622442, -0.001392819236778710, 0.001781163375917694, 0.003022505100981749, - 0.001380885370638605, -0.001363939022079807, -0.002596738345596567, -0.001340209298573909, 0.001013908066665751, - 0.002211344243623479, 0.001278004560152671, -0.000722025795734941, -0.001863328908272039, -0.001198782421759553, - 0.000481887570810472, 0.001551023787987609, 0.001106868403908265, -0.000287982640712631, -0.001273440263814288, --0.001006723075110756, 0.000134959379552175, 0.001029667300547813, 0.000902749872050692, -0.000017387631184785, --0.000818491755176836, -0.000799106348889160, -0.000070303980749569, 0.000638095603939989, 0.000699066404450690, - 0.000132693079697097, -0.000487816655783889, -0.000607318273395854, -0.000175453824284145, 0.000367581824060558, - 0.000531744141964012, 0.000208833549611114, -0.000275452022064688, -0.000485740874895228, -0.000255881206127522, - 0.000199352366062691, 0.000494027000545870, 0.000387773639773515, -0.000054292188328318, -0.000552107526238211, --0.000835736178417635, -0.000813592880911599, -0.000581062092898679, -0.000305662442281008, -0.000110996454883008, --0.000017362646744960 }; +double quiskFilt144D3Coefs[195] = { 0.000000929800301509, -0.000047455270447012, -0.000185557217763084, -0.000429493244511602, +-0.000706119906393579, -0.000858892671327589, -0.000736298367084556, -0.000324524827723275, 0.000185900608975433, + 0.000490568680803351, 0.000389714702472048, -0.000028408824974806, -0.000417096760720408, -0.000434697067626504, +-0.000045435020309505, 0.000415838805132423, 0.000514895088228092, 0.000118996752819866, -0.000439074514820927, +-0.000627861598115024, -0.000214739908222163, 0.000464742161905899, 0.000765936881836607, 0.000338342855679689, +-0.000483649671488582, -0.000925263389046922, -0.000492778428030410, 0.000491291211056113, 0.001106115649241533, + 0.000684738287761995, -0.000478561277211712, -0.001302519093830372, -0.000913327003967223, 0.000444430379514246, + 0.001516346439022341, 0.001184835917280570, -0.000381660390358622, -0.001744510443920924, -0.001501874568572245, + 0.000285813002924867, 0.001986533572558733, 0.001869946797066696, -0.000149436934937348, -0.002239418986499540, +-0.002292895246658123, -0.000033774141410974, 0.002501522068919682, 0.002776421936382509, 0.000271889211515328, +-0.002770584313185423, -0.003327169931282938, -0.000574595905443091, 0.003043521692525203, 0.003952281151373279, + 0.000952226224687421, -0.003318326482338467, -0.004662201441300811, -0.001418796409742763, 0.003591707082723813, + 0.005469328509978473, 0.001990936240034607, -0.003861051870726370, -0.006391207213311409, -0.002691164241606223, + 0.004122818888690941, 0.007451048654820467, 0.003548671245409008, -0.004374400318436638, -0.008683144444647864, +-0.004605239765264122, 0.004612724565641136, 0.010137841708727604, 0.005921730149932176, -0.004834856891886717, +-0.011892738561842359, -0.007591491516671162, 0.005038182912840544, 0.014073683244849233, 0.009766782177576589, +-0.005220034436270117, -0.016896974121906973, -0.012713712016459091, 0.005378157936635823, 0.020767187386954995, + 0.016944725648890040, -0.005510332370442290, -0.026533471301589350, -0.023587866224551134, 0.005615065508435361, + 0.036324676555211169, 0.035708313387290108, -0.005690931520283190, -0.057386859399577647, -0.065572279298325861, + 0.005736846079812847, 0.140463750721693400, 0.272668549191353910, 0.327581110872857630, 0.272668549191353910, + 0.140463750721693400, 0.005736846079812847, -0.065572279298325861, -0.057386859399577647, -0.005690931520283190, + 0.035708313387290108, 0.036324676555211169, 0.005615065508435361, -0.023587866224551134, -0.026533471301589350, +-0.005510332370442290, 0.016944725648890040, 0.020767187386954995, 0.005378157936635823, -0.012713712016459091, +-0.016896974121906973, -0.005220034436270117, 0.009766782177576589, 0.014073683244849233, 0.005038182912840544, +-0.007591491516671162, -0.011892738561842359, -0.004834856891886717, 0.005921730149932176, 0.010137841708727604, + 0.004612724565641136, -0.004605239765264122, -0.008683144444647864, -0.004374400318436638, 0.003548671245409008, + 0.007451048654820467, 0.004122818888690941, -0.002691164241606223, -0.006391207213311409, -0.003861051870726370, + 0.001990936240034607, 0.005469328509978473, 0.003591707082723813, -0.001418796409742763, -0.004662201441300811, +-0.003318326482338467, 0.000952226224687421, 0.003952281151373279, 0.003043521692525203, -0.000574595905443091, +-0.003327169931282938, -0.002770584313185423, 0.000271889211515328, 0.002776421936382509, 0.002501522068919682, +-0.000033774141410974, -0.002292895246658123, -0.002239418986499540, -0.000149436934937348, 0.001869946797066696, + 0.001986533572558733, 0.000285813002924867, -0.001501874568572245, -0.001744510443920924, -0.000381660390358622, + 0.001184835917280570, 0.001516346439022341, 0.000444430379514246, -0.000913327003967223, -0.001302519093830372, +-0.000478561277211712, 0.000684738287761995, 0.001106115649241533, 0.000491291211056113, -0.000492778428030410, +-0.000925263389046922, -0.000483649671488582, 0.000338342855679689, 0.000765936881836607, 0.000464742161905899, +-0.000214739908222163, -0.000627861598115024, -0.000439074514820927, 0.000118996752819866, 0.000514895088228092, + 0.000415838805132423, -0.000045435020309505, -0.000434697067626504, -0.000417096760720408, -0.000028408824974806, + 0.000389714702472048, 0.000490568680803351, 0.000185900608975433, -0.000324524827723275, -0.000736298367084556, +-0.000858892671327589, -0.000706119906393579, -0.000429493244511602, -0.000185557217763084, -0.000047455270447012, + 0.000000929800301509} ; + +// Sample 120000 Hz, pass 2700, stop 3730, ripple 0.1dB, atten 100 dB. Stop 0.03108. +double quiskFilt120s03[480] = { -0.000005050567303837, -0.000000267011791999, 0.000000197734700398, 0.000001038946634000, + 0.000002322193058869, 0.000004115682735322, 0.000006499942123311, 0.000009551098482930, 0.000013350669444763, + 0.000017966192635412, 0.000023463361155584, 0.000029885221425020, 0.000037271082107518, 0.000045630720487935, + 0.000054970017069384, 0.000065233162392019, 0.000076360900545177, 0.000088271373315159, 0.000100818605854714, + 0.000113853476544409, 0.000127174196746337, 0.000140558396336177, 0.000153744508371709, 0.000166450784469067, + 0.000178368313347299, 0.000189176709991702, 0.000198541881389953, 0.000206128795372885, 0.000211604878787747, + 0.000214655997661182, 0.000214994859281552, 0.000212358734245594, 0.000206539880117977, 0.000197379393194548, + 0.000184780318878738, 0.000168719942655099, 0.000149250512353807, 0.000126511346757621, 0.000100726393185629, + 0.000072210925236429, 0.000041365841965015, 0.000008680571408025, -0.000025277165852799, -0.000059865389594949, +-0.000094384355854646, -0.000128080670195777, -0.000160170174848483, -0.000189854272533545, -0.000216333899003825, +-0.000238836419299503, -0.000256632149501508, -0.000269058714331757, -0.000275541485292432, -0.000275614059005332, +-0.000268937472718753, -0.000255317038867589, -0.000234717772155001, -0.000207273956099563, -0.000173297342436372, +-0.000133280012107173, -0.000087895370243821, -0.000037986085678081, 0.000015440388211825, 0.000071232572821451, + 0.000128114399130489, 0.000184710477990398, 0.000239577162514028, 0.000291234779803098, 0.000338204791740229, + 0.000379047713684221, 0.000412403761615261, 0.000437031818051652, 0.000451848709179591, 0.000455966225408344, + 0.000448726371643413, 0.000429729020814434, 0.000398857326863837, 0.000356297600912998, 0.000302547334727027, + 0.000238422248479072, 0.000165048886226905, 0.000083853091464077, -0.000003462782744354, -0.000094949813106744, +-0.000188451833293202, -0.000281651282503015, -0.000372121907291206, -0.000457387566635848, -0.000534985542936898, +-0.000602532044011899, -0.000657788245032425, -0.000698728981427767, -0.000723604675185869, -0.000731002305621048, +-0.000719899536922384, -0.000689709694056092, -0.000640319946685634, -0.000572115873292030, -0.000485996080304965, +-0.000383371840261246, -0.000266155252511831, -0.000136731311264191, 0.000002082667095075, 0.000147092077716480, + 0.000294790953130229, 0.000441441918072383, 0.000583164190168290, 0.000716029226064227, 0.000836164238172957, + 0.000939856052624227, 0.001023657909064450, 0.001084492755093968, 0.001119751426837743, 0.001127383039339373, + 0.001105974243787613, 0.001054815583369999, 0.000973950761085690, 0.000864209315714227, 0.000727219011746881, + 0.000565398080608305, 0.000381924396468366, 0.000180685902835315, -0.000033793183292569, -0.000256444114966522, +-0.000481764526566339, -0.000703946352348464, -0.000917016099829735, -0.001114986581270253, -0.001292014799874503, +-0.001442563411804926, -0.001561559957317790, -0.001644551048567398, -0.001687846581475964, -0.001688649703502788, +-0.001645167889846890, -0.001556702802350076, -0.001423714708648073, -0.001247857669697092, -0.001031986722557201, +-0.000780131048444402, -0.000497436825078657, -0.000190077210351809, 0.000134868279325909, 0.000469563533327739, + 0.000805591531546815, 0.001134152328775355, 0.001446279849797673, 0.001733071409562941, 0.001985924997799762, + 0.002196778054604388, 0.002358342626407065, 0.002464328098407475, 0.002509648218888532, 0.002490604086803692, + 0.002405037734357425, 0.002252452724297770, 0.002034094661603120, 0.001752990365583534, 0.001413941154886139, + 0.001023470495638453, 0.000589723521647734, 0.000122320866350319, -0.000367832138027160, -0.000868777013398284, +-0.001367771151677059, -0.001851587344265625, -0.002306838088978190, -0.002720317947026380, -0.003079353614002113, +-0.003372155891804708, -0.003588162376578369, -0.003718362558663737, -0.003755596511143005, -0.003694818131674599, +-0.003533315298404129, -0.003270878754553819, -0.002909914962857412, -0.002455496391464944, -0.001915346645364514, +-0.001299757227227888, -0.000621437066532776, 0.000104706515738248, 0.000861849931067767, 0.001631595707499856, + 0.002394368911341672, 0.003129858565588139, 0.003817496679992245, 0.004436963307209760, 0.004968707287606522, + 0.005394469536085115, 0.005697797543539088, 0.005864537618023589, 0.005883292537600076, 0.005745832319314692, + 0.005447447099071761, 0.004987231255534477, 0.004368289529377007, 0.003597859022418248, 0.002687338851256991, + 0.001652226293162047, 0.000511956075882180, -0.000710356149138656, -0.001988263330091648, -0.003292424566049982, +-0.004591123342747130, -0.005850857852106148, -0.007036991266043732, -0.008114450164977267, -0.009048456200082230, +-0.009805276478965942, -0.010352975302354198, -0.010662152577592631, -0.010706650669328861, -0.010464214075017983, +-0.009917087295446811, -0.009052534679222271, -0.007863270920348924, -0.006347789704693751, -0.004510582323649121, +-0.002362238055733795, 0.000080576968834213, 0.002795265196543707, 0.005753566158586979, 0.008921944932552510, + 0.012262093950265378, 0.015731539846483594, 0.019284344624007944, 0.022871886384520687, 0.026443706729191677, + 0.029948406200633094, 0.033334570666910354, 0.036551709955124537, 0.039551189200810140, 0.042287133974308874, + 0.044717290029466283, 0.046803820535016104, 0.048514022996355009, 0.049820951883635139, 0.050703932928426454, + 0.051148959210315710, 0.051148959210315710, 0.050703932928426454, 0.049820951883635139, 0.048514022996355009, + 0.046803820535016104, 0.044717290029466283, 0.042287133974308874, 0.039551189200810140, 0.036551709955124537, + 0.033334570666910354, 0.029948406200633094, 0.026443706729191677, 0.022871886384520687, 0.019284344624007944, + 0.015731539846483594, 0.012262093950265378, 0.008921944932552510, 0.005753566158586979, 0.002795265196543707, + 0.000080576968834213, -0.002362238055733795, -0.004510582323649121, -0.006347789704693751, -0.007863270920348924, +-0.009052534679222271, -0.009917087295446811, -0.010464214075017983, -0.010706650669328861, -0.010662152577592631, +-0.010352975302354198, -0.009805276478965942, -0.009048456200082230, -0.008114450164977267, -0.007036991266043732, +-0.005850857852106148, -0.004591123342747130, -0.003292424566049982, -0.001988263330091648, -0.000710356149138656, + 0.000511956075882180, 0.001652226293162047, 0.002687338851256991, 0.003597859022418248, 0.004368289529377007, + 0.004987231255534477, 0.005447447099071761, 0.005745832319314692, 0.005883292537600076, 0.005864537618023589, + 0.005697797543539088, 0.005394469536085115, 0.004968707287606522, 0.004436963307209760, 0.003817496679992245, + 0.003129858565588139, 0.002394368911341672, 0.001631595707499856, 0.000861849931067767, 0.000104706515738248, +-0.000621437066532776, -0.001299757227227888, -0.001915346645364514, -0.002455496391464944, -0.002909914962857412, +-0.003270878754553819, -0.003533315298404129, -0.003694818131674599, -0.003755596511143005, -0.003718362558663737, +-0.003588162376578369, -0.003372155891804708, -0.003079353614002113, -0.002720317947026380, -0.002306838088978190, +-0.001851587344265625, -0.001367771151677059, -0.000868777013398284, -0.000367832138027160, 0.000122320866350319, + 0.000589723521647734, 0.001023470495638453, 0.001413941154886139, 0.001752990365583534, 0.002034094661603120, + 0.002252452724297770, 0.002405037734357425, 0.002490604086803692, 0.002509648218888532, 0.002464328098407475, + 0.002358342626407065, 0.002196778054604388, 0.001985924997799762, 0.001733071409562941, 0.001446279849797673, + 0.001134152328775355, 0.000805591531546815, 0.000469563533327739, 0.000134868279325909, -0.000190077210351809, +-0.000497436825078657, -0.000780131048444402, -0.001031986722557201, -0.001247857669697092, -0.001423714708648073, +-0.001556702802350076, -0.001645167889846890, -0.001688649703502788, -0.001687846581475964, -0.001644551048567398, +-0.001561559957317790, -0.001442563411804926, -0.001292014799874503, -0.001114986581270253, -0.000917016099829735, +-0.000703946352348464, -0.000481764526566339, -0.000256444114966522, -0.000033793183292569, 0.000180685902835315, + 0.000381924396468366, 0.000565398080608305, 0.000727219011746881, 0.000864209315714227, 0.000973950761085690, + 0.001054815583369999, 0.001105974243787613, 0.001127383039339373, 0.001119751426837743, 0.001084492755093968, + 0.001023657909064450, 0.000939856052624227, 0.000836164238172957, 0.000716029226064227, 0.000583164190168290, + 0.000441441918072383, 0.000294790953130229, 0.000147092077716480, 0.000002082667095075, -0.000136731311264191, +-0.000266155252511831, -0.000383371840261246, -0.000485996080304965, -0.000572115873292030, -0.000640319946685634, +-0.000689709694056092, -0.000719899536922384, -0.000731002305621048, -0.000723604675185869, -0.000698728981427767, +-0.000657788245032425, -0.000602532044011899, -0.000534985542936898, -0.000457387566635848, -0.000372121907291206, +-0.000281651282503015, -0.000188451833293202, -0.000094949813106744, -0.000003462782744354, 0.000083853091464077, + 0.000165048886226905, 0.000238422248479072, 0.000302547334727027, 0.000356297600912998, 0.000398857326863837, + 0.000429729020814434, 0.000448726371643413, 0.000455966225408344, 0.000451848709179591, 0.000437031818051652, + 0.000412403761615261, 0.000379047713684221, 0.000338204791740229, 0.000291234779803098, 0.000239577162514028, + 0.000184710477990398, 0.000128114399130489, 0.000071232572821451, 0.000015440388211825, -0.000037986085678081, +-0.000087895370243821, -0.000133280012107173, -0.000173297342436372, -0.000207273956099563, -0.000234717772155001, +-0.000255317038867589, -0.000268937472718753, -0.000275614059005332, -0.000275541485292432, -0.000269058714331757, +-0.000256632149501508, -0.000238836419299503, -0.000216333899003825, -0.000189854272533545, -0.000160170174848483, +-0.000128080670195777, -0.000094384355854646, -0.000059865389594949, -0.000025277165852799, 0.000008680571408025, + 0.000041365841965015, 0.000072210925236429, 0.000100726393185629, 0.000126511346757621, 0.000149250512353807, + 0.000168719942655099, 0.000184780318878738, 0.000197379393194548, 0.000206539880117977, 0.000212358734245594, + 0.000214994859281552, 0.000214655997661182, 0.000211604878787747, 0.000206128795372885, 0.000198541881389953, + 0.000189176709991702, 0.000178368313347299, 0.000166450784469067, 0.000153744508371709, 0.000140558396336177, + 0.000127174196746337, 0.000113853476544409, 0.000100818605854714, 0.000088271373315159, 0.000076360900545177, + 0.000065233162392019, 0.000054970017069384, 0.000045630720487935, 0.000037271082107518, 0.000029885221425020, + 0.000023463361155584, 0.000017966192635412, 0.000013350669444763, 0.000009551098482930, 0.000006499942123311, + 0.000004115682735322, 0.000002322193058869, 0.000001038946634000, 0.000000197734700398, -0.000000267011791999, +-0.000005050567303837 }; diff -Nru quisk-3.6.18/filters.py quisk-3.7.6/filters.py --- quisk-3.6.18/filters.py 2012-05-08 19:58:45.000000000 +0000 +++ quisk-3.7.6/filters.py 2015-04-16 00:24:40.000000000 +0000 @@ -1,4 +1,7 @@ # Rate 24000 sps, ripple 0.2 dB, atten 100 dB, shape 1.2 +# Filters key is the bandwidth at 24000 sps. +# key = bw * 24000 / rate / 2 +# bw = key * 2 * rate / 24000 Filters = { 8500: # 55 taps @@ -172,6 +175,42 @@ -0.000491406291157732, -0.000025129491557388, 0.000324946604356638, 0.000506924615738917, 0.000529323030925432, 0.000441565642149420, 0.000306338738553062, 0.000175970492512707, 0.000080053245121681, 0.000025917992771962], +2250: # 204 taps +[ 0.000021235929725912, 0.000058222413723091, 0.000120890002831670, 0.000203732920433964, 0.000289842861102114, 0.000349648687783014, + 0.000345987806871638, 0.000244741113281256, 0.000028683359304148, -0.000289767552139220, -0.000662562723513035, -0.001011139906426526, +-0.001244168321321761, -0.001284537444429324, -0.001097853884402359, -0.000712241533727792, -0.000220016931252946, 0.000242881192519691, + 0.000536533139642942, 0.000564406769439812, 0.000312158569581760, -0.000137882856713157, -0.000625674547956830, -0.000963565458358823, +-0.001002878434444682, -0.000693839038801721, -0.000115120712337792, 0.000542271936437241, 0.001036221937634474, 0.001161519435408656, + 0.000833656052982527, 0.000135090190320360, -0.000698564960603252, -0.001354001229403848, -0.001554644716587147, -0.001173297796072647, +-0.000298257519547670, 0.000778476775888895, 0.001654851153636767, 0.001966028879000262, 0.001532563461927731, 0.000450688659966846, +-0.000917352451094544, -0.002059631204322221, -0.002502298531213019, -0.002001396043186186, -0.000662678470838726, 0.001065622033389926, + 0.002537010973466811, 0.003144134768937308, 0.002563969753555681, 0.000913777569993959, -0.001253108006318142, -0.003125545369287028, +-0.003932636886255475, -0.003254230145379617, -0.001221262259301159, 0.001484407116176422, 0.003849603664148359, 0.004902048797404353, + 0.004101215157545026, 0.001593866974392046, -0.001780147556263722, -0.004757480195317273, -0.006114639178429671, -0.005158848135225734, +-0.002053747031888304, 0.002164989106816366, 0.005919348898632932, 0.007665501534134797, 0.006511589945553144, 0.002635680468215082, +-0.002680807848145089, -0.007454309167215744, -0.009718217353005822, -0.008306681703332177, -0.003400058938312392, 0.003406734158392871, + 0.009587959427131761, 0.012589212182082522, 0.010837597719690596, 0.004476080310267183, -0.004494123792760167, -0.012783165957938696, +-0.016952789434463451, -0.014752125292065949, -0.006159094027769298, 0.006304669250227038, 0.018192572127496920, 0.024568955795679859, + 0.021832946865353555, 0.009314262398551900, -0.009958208720588032, -0.029683337395096002, -0.041898689176258344, -0.039357322455930641, +-0.017957925042069021, 0.021558306071190737, 0.073246545073647112, 0.127141924217462950, 0.171715605172332860, 0.196918159377366730, + 0.196918159377366730, 0.171715605172332860, 0.127141924217462950, 0.073246545073647112, 0.021558306071190737, -0.017957925042069021, +-0.039357322455930641, -0.041898689176258344, -0.029683337395096002, -0.009958208720588032, 0.009314262398551900, 0.021832946865353555, + 0.024568955795679859, 0.018192572127496920, 0.006304669250227038, -0.006159094027769298, -0.014752125292065949, -0.016952789434463451, +-0.012783165957938696, -0.004494123792760167, 0.004476080310267183, 0.010837597719690596, 0.012589212182082522, 0.009587959427131761, + 0.003406734158392871, -0.003400058938312392, -0.008306681703332177, -0.009718217353005822, -0.007454309167215744, -0.002680807848145089, + 0.002635680468215082, 0.006511589945553144, 0.007665501534134797, 0.005919348898632932, 0.002164989106816366, -0.002053747031888304, +-0.005158848135225734, -0.006114639178429671, -0.004757480195317273, -0.001780147556263722, 0.001593866974392046, 0.004101215157545026, + 0.004902048797404353, 0.003849603664148359, 0.001484407116176422, -0.001221262259301159, -0.003254230145379617, -0.003932636886255475, +-0.003125545369287028, -0.001253108006318142, 0.000913777569993959, 0.002563969753555681, 0.003144134768937308, 0.002537010973466811, + 0.001065622033389926, -0.000662678470838726, -0.002001396043186186, -0.002502298531213019, -0.002059631204322221, -0.000917352451094544, + 0.000450688659966846, 0.001532563461927731, 0.001966028879000262, 0.001654851153636767, 0.000778476775888895, -0.000298257519547670, +-0.001173297796072647, -0.001554644716587147, -0.001354001229403848, -0.000698564960603252, 0.000135090190320360, 0.000833656052982527, + 0.001161519435408656, 0.001036221937634474, 0.000542271936437241, -0.000115120712337792, -0.000693839038801721, -0.001002878434444682, +-0.000963565458358823, -0.000625674547956830, -0.000137882856713157, 0.000312158569581760, 0.000564406769439812, 0.000536533139642942, + 0.000242881192519691, -0.000220016931252946, -0.000712241533727792, -0.001097853884402359, -0.001284537444429324, -0.001244168321321761, +-0.001011139906426526, -0.000662562723513035, -0.000289767552139220, 0.000028683359304148, 0.000244741113281256, 0.000345987806871638, + 0.000349648687783014, 0.000289842861102114, 0.000203732920433964, 0.000120890002831670, 0.000058222413723091, 0.000021235929725912], + 2200: #208 taps [ 0.000019773973500698, 0.000050300702719923, 0.000099108356371034, 0.000158194404233062, 0.000210525538441445, 0.000229815943545636, 0.000185844232898202, 0.000053925802103604, -0.000173777821813056, -0.000478713240844808, -0.000812707500835325, -0.001104087723445511, @@ -250,6 +289,81 @@ 0.000422974737428721, 0.000404952972379638, 0.000340023431940833, 0.000253967146823514, 0.000168379817473922, 0.000097406241243338, 0.000047149978647285, 0.000018263332334824], +1050: # 436 taps +[ 0.000009448479205375, 0.000012696465277652, 0.000020370172970180, 0.000030184991625035, 0.000042106693955817, 0.000055852072599971, + 0.000070887005271560, 0.000086420158465092, 0.000101355825900618, 0.000114363268643203, 0.000123887834821102, 0.000128274519298202, + 0.000125832586450277, 0.000114986590640600, 0.000094403728281619, 0.000063154754823051, 0.000020845988244699, -0.000032265535133044, +-0.000095180544123547, -0.000166116860144892, -0.000242513979646880, -0.000321114777409093, -0.000398098262223401, -0.000469277292459888, +-0.000530343005106164, -0.000577150816605647, -0.000606030552867816, -0.000614088176959215, -0.000599488135042163, -0.000561676165437471, +-0.000501537574188219, -0.000421453273437500, -0.000325256102322617, -0.000218069429252057, -0.000106038676866598, 0.000004038733321948, + 0.000105163201801783, 0.000190629395147399, 0.000254537701320533, 0.000292276183024851, 0.000300940504739341, 0.000279647367467038, + 0.000229712182001491, 0.000154664133559244, 0.000060087287885032, -0.000046708916188273, -0.000157178278901246, -0.000262150604318790, +-0.000352532643004546, -0.000420038843447295, -0.000457900791044766, -0.000461490220027360, -0.000428802562294159, -0.000360749682491985, +-0.000261227783830262, -0.000136942941254748, 0.000003006863095541, 0.000147769782466689, 0.000285560076794725, 0.000404597750694492, + 0.000494097819748234, 0.000545227040891837, 0.000551946387992179, 0.000511661803626807, 0.000425615907406962, 0.000298975923041507, + 0.000140591237099924, -0.000037572806623586, -0.000221297040657601, -0.000395204977572457, -0.000544014582709044, -0.000653844664412656, +-0.000713468032359235, -0.000715401015700932, -0.000656726220245329, -0.000539565358551840, -0.000371142263662183, -0.000163412087825843, + 0.000067734912716773, 0.000303646478822936, 0.000524376688359045, 0.000710337247559984, 0.000843997687273033, 0.000911491282666008, + 0.000903981565989860, 0.000818661581901103, 0.000659279571163340, 0.000436124965687766, 0.000165448090041410, -0.000131662114271814, +-0.000430871860061819, -0.000706551570190628, -0.000933927661174878, -0.001091260873927019, -0.001161862605544963, -0.001135768011733947, +-0.001010903443574116, -0.000793624567958766, -0.000498548786522930, -0.000147664383619282, 0.000231239373696082, 0.000606716500508849, + 0.000946221796915500, 0.001218879442233109, 0.001398230278516649, 0.001464719587961910, 0.001407696084540016, 0.001226727670795367, + 0.000932088534687613, 0.000544338945220889, 0.000092993997809168, -0.000385643796196990, -0.000851335887551482, -0.001263225987480693, +-0.001583356294077108, -0.001780085545791869, -0.001831106846925676, -0.001725785932705194, -0.001466586997062531, -0.001069422391909016, +-0.000562849829295198, 0.000013863768127086, 0.000613691289313864, 0.001185595579630164, 0.001678788016865224, 0.002047148219684201, + 0.002253427125685210, 0.002272859461156746, 0.002095846355480220, 0.001729435227991120, 0.001197417642361670, 0.000538977181181343, +-0.000194056307018250, -0.000941167802354134, -0.001638040755694804, -0.002221979108638823, -0.002637419881293469, -0.002841061961880444, +-0.002806150254550888, -0.002525507242008391, -0.002012996612957378, -0.001303224500472842, -0.000449429868884502, 0.000480331461610398, + 0.001408451066145862, 0.002254087235765462, 0.002940037601874124, 0.003399594478686270, 0.003582789834066899, 0.003461463426614760, + 0.003032665933951381, 0.002320030370388926, 0.001372905661370613, 0.000263228943665216, -0.000919692494408471, -0.002076132077760251, +-0.003104105713267222, -0.003908095577429846, -0.004407611415610481, -0.004544845892385366, -0.004290726778249251, -0.003648773776410054, +-0.002656330572825240, -0.001382946510100638, 0.000074084090594797, 0.001596775046947397, 0.003055612326596284, 0.004320145989574181, + 0.005270175821128589, 0.005806607129742208, 0.005861039774711523, 0.005403216045538973, 0.004445593651759434, 0.003044516829901583, + 0.001297719390662469, -0.000661812043684862, -0.002675281726881974, -0.004570210544203085, -0.006174367519919625, -0.007330437981090785, +-0.007910248018541601, -0.007827342889142773, -0.007046794045290511, -0.005591280073358511, -0.003542743024648185, -0.001039245490340182, + 0.001732978782646685, 0.004551902468552846, 0.007175761837551073, 0.009361715988035382, 0.010885774625277341, 0.011562497118639900, + 0.011262913292836945, 0.009929181902797219, 0.007584685668611571, 0.004338552786979482, 0.000383973096667143, -0.004009880384869925, +-0.008512042746918275, -0.012749310695913854, -0.016329981037081505, -0.018870242377519306, -0.020021437259417553, -0.019496284710171480, +-0.017092162548070899, -0.012709698776261012, -0.006365201717689276, 0.001804145292512615, 0.011542917413936767, 0.022487965990827675, + 0.034186326021837028, 0.046119492420907078, 0.057732581500000407, 0.068466529917194638, 0.077791244144850280, 0.085237520364008251, + 0.090425612489785534, 0.093088535620340640, 0.093088535620340640, 0.090425612489785534, 0.085237520364008251, 0.077791244144850280, + 0.068466529917194638, 0.057732581500000407, 0.046119492420907078, 0.034186326021837028, 0.022487965990827675, 0.011542917413936767, + 0.001804145292512615, -0.006365201717689276, -0.012709698776261012, -0.017092162548070899, -0.019496284710171480, -0.020021437259417553, +-0.018870242377519306, -0.016329981037081505, -0.012749310695913854, -0.008512042746918275, -0.004009880384869925, 0.000383973096667143, + 0.004338552786979482, 0.007584685668611571, 0.009929181902797219, 0.011262913292836945, 0.011562497118639900, 0.010885774625277341, + 0.009361715988035382, 0.007175761837551073, 0.004551902468552846, 0.001732978782646685, -0.001039245490340182, -0.003542743024648185, +-0.005591280073358511, -0.007046794045290511, -0.007827342889142773, -0.007910248018541601, -0.007330437981090785, -0.006174367519919625, +-0.004570210544203085, -0.002675281726881974, -0.000661812043684862, 0.001297719390662469, 0.003044516829901583, 0.004445593651759434, + 0.005403216045538973, 0.005861039774711523, 0.005806607129742208, 0.005270175821128589, 0.004320145989574181, 0.003055612326596284, + 0.001596775046947397, 0.000074084090594797, -0.001382946510100638, -0.002656330572825240, -0.003648773776410054, -0.004290726778249251, +-0.004544845892385366, -0.004407611415610481, -0.003908095577429846, -0.003104105713267222, -0.002076132077760251, -0.000919692494408471, + 0.000263228943665216, 0.001372905661370613, 0.002320030370388926, 0.003032665933951381, 0.003461463426614760, 0.003582789834066899, + 0.003399594478686270, 0.002940037601874124, 0.002254087235765462, 0.001408451066145862, 0.000480331461610398, -0.000449429868884502, +-0.001303224500472842, -0.002012996612957378, -0.002525507242008391, -0.002806150254550888, -0.002841061961880444, -0.002637419881293469, +-0.002221979108638823, -0.001638040755694804, -0.000941167802354134, -0.000194056307018250, 0.000538977181181343, 0.001197417642361670, + 0.001729435227991120, 0.002095846355480220, 0.002272859461156746, 0.002253427125685210, 0.002047148219684201, 0.001678788016865224, + 0.001185595579630164, 0.000613691289313864, 0.000013863768127086, -0.000562849829295198, -0.001069422391909016, -0.001466586997062531, +-0.001725785932705194, -0.001831106846925676, -0.001780085545791869, -0.001583356294077108, -0.001263225987480693, -0.000851335887551482, +-0.000385643796196990, 0.000092993997809168, 0.000544338945220889, 0.000932088534687613, 0.001226727670795367, 0.001407696084540016, + 0.001464719587961910, 0.001398230278516649, 0.001218879442233109, 0.000946221796915500, 0.000606716500508849, 0.000231239373696082, +-0.000147664383619282, -0.000498548786522930, -0.000793624567958766, -0.001010903443574116, -0.001135768011733947, -0.001161862605544963, +-0.001091260873927019, -0.000933927661174878, -0.000706551570190628, -0.000430871860061819, -0.000131662114271814, 0.000165448090041410, + 0.000436124965687766, 0.000659279571163340, 0.000818661581901103, 0.000903981565989860, 0.000911491282666008, 0.000843997687273033, + 0.000710337247559984, 0.000524376688359045, 0.000303646478822936, 0.000067734912716773, -0.000163412087825843, -0.000371142263662183, +-0.000539565358551840, -0.000656726220245329, -0.000715401015700932, -0.000713468032359235, -0.000653844664412656, -0.000544014582709044, +-0.000395204977572457, -0.000221297040657601, -0.000037572806623586, 0.000140591237099924, 0.000298975923041507, 0.000425615907406962, + 0.000511661803626807, 0.000551946387992179, 0.000545227040891837, 0.000494097819748234, 0.000404597750694492, 0.000285560076794725, + 0.000147769782466689, 0.000003006863095541, -0.000136942941254748, -0.000261227783830262, -0.000360749682491985, -0.000428802562294159, +-0.000461490220027360, -0.000457900791044766, -0.000420038843447295, -0.000352532643004546, -0.000262150604318790, -0.000157178278901246, +-0.000046708916188273, 0.000060087287885032, 0.000154664133559244, 0.000229712182001491, 0.000279647367467038, 0.000300940504739341, + 0.000292276183024851, 0.000254537701320533, 0.000190629395147399, 0.000105163201801783, 0.000004038733321948, -0.000106038676866598, +-0.000218069429252057, -0.000325256102322617, -0.000421453273437500, -0.000501537574188219, -0.000561676165437471, -0.000599488135042163, +-0.000614088176959215, -0.000606030552867816, -0.000577150816605647, -0.000530343005106164, -0.000469277292459888, -0.000398098262223401, +-0.000321114777409093, -0.000242513979646880, -0.000166116860144892, -0.000095180544123547, -0.000032265535133044, 0.000020845988244699, + 0.000063154754823051, 0.000094403728281619, 0.000114986590640600, 0.000125832586450277, 0.000128274519298202, 0.000123887834821102, + 0.000114363268643203, 0.000101355825900618, 0.000086420158465092, 0.000070887005271560, 0.000055852072599971, 0.000042106693955817, + 0.000030184991625035, 0.000020370172970180, 0.000012696465277652, 0.000009448479205375 ], + 1200: # 390 taps [ -0.000004331430434813, 0.000000895178754483, 0.000004819206934844, 0.000012387135981227, 0.000024702874344864, 0.000042904801446151, 0.000067985064859609, 0.000100627030615644, 0.000141025873402660, 0.000188738905083740, 0.000242545741476270, 0.000300399529662751, @@ -318,6 +432,93 @@ 0.000042904801446151, 0.000024702874344864, 0.000012387135981227, 0.000004819206934844, 0.000000895178754483, -0.000004331430434813], +900: # 507 taps +[ 0.000008584643858565, 0.000009418988876053, 0.000014080571079703, 0.000019768330546629, 0.000026407938202815, 0.000033835554436160, + 0.000041775447258916, 0.000049840688763229, 0.000057525397104827, 0.000064222904381994, 0.000069229653331724, 0.000071774979399342, + 0.000071044828173283, 0.000066239730112684, 0.000056611414853593, 0.000041517869163367, 0.000020462723568921, -0.000006825518135279, +-0.000040362137084292, -0.000079866391614059, -0.000124740944604127, -0.000174014477804677, -0.000226421731946560, -0.000280359514663211, +-0.000333959777524947, -0.000385165398367582, -0.000431779497772114, -0.000471602831913601, -0.000502508714800933, -0.000522587601943708, +-0.000530244985591953, -0.000524326246532992, -0.000504199216087390, -0.000469843487184555, -0.000421891912076977, -0.000361653837423190, +-0.000291090671787090, -0.000212772068148448, -0.000129775708505696, -0.000045562167590561, 0.000036181038541454, 0.000111715248667983, + 0.000177457791675865, 0.000230168155042476, 0.000267145694421862, 0.000286408115238973, 0.000286822428035563, 0.000268225243572190, + 0.000231462231911356, 0.000178399161339807, 0.000111858949442223, 0.000035506782941005, -0.000046322038661538, -0.000128840476454010, +-0.000207064338646769, -0.000276095967153748, -0.000331410885812673, -0.000369138410006413, -0.000386317254135060, -0.000381111227069818, +-0.000352960919521696, -0.000302673831926362, -0.000232432591548855, -0.000145719497401035, -0.000047160366530124, 0.000057705644340284, + 0.000162731306278391, 0.000261502601122459, 0.000347728839468377, 0.000415628506871784, 0.000460307676423158, 0.000478101121808084, + 0.000466848645480576, 0.000426095215060785, 0.000357188814988566, 0.000263277817392097, 0.000149189135268233, 0.000021201177134175, +-0.000113285445089856, -0.000246159846025947, -0.000369078758947477, -0.000473974817394057, -0.000553573238187010, -0.000601879769614216, +-0.000614609034335983, -0.000589524295786869, -0.000526664670957786, -0.000428434330673012, -0.000299553787970989, -0.000146857332295172, + 0.000021049474537857, 0.000194254912976452, 0.000362113995846136, 0.000513885517380605, 0.000639407293886249, 0.000729757323783321, + 0.000777868837348859, 0.000779050717176191, 0.000731379294218185, 0.000635932028877494, 0.000496840283261992, 0.000321150767788200, + 0.000118496954373743, -0.000099406083911376, -0.000319420735859412, -0.000527759843251443, -0.000710834560643461, -0.000856122360814998, +-0.000953000983271033, -0.000993496322190293, -0.000972892522850171, -0.000890155314802122, -0.000748144004291994, -0.000553578640569728, +-0.000316765531771492, -0.000051080037307638, 0.000227762682493944, 0.000502615360611366, 0.000755932794801656, 0.000970869777260266, + 0.001132378198325256, 0.001228230831826114, 0.001249905234811852, 0.001193265605753869, 0.001058991562763383, 0.000852715731034719, + 0.000584852833903113, 0.000270116979072993, -0.000073253806782210, -0.000424524625176341, -0.000761689644626737, -0.001062825395605405, +-0.001307487536115730, -0.001478067477842081, -0.001561017661066179, -0.001547863924535660, -0.001435933022484648, -0.001228735487931008, +-0.000935971757611233, -0.000573140492907433, -0.000160766721791647, 0.000276722077340107, 0.000712407276705890, 0.001118502589392694, + 0.001468091403060663, 0.001736873763731978, 0.001904808530758137, 0.001957546402581353, 0.001887551700267456, 0.001694830920708386, + 0.001387207697842587, 0.000980110478290227, 0.000495866308669078, -0.000037468525946086, -0.000587687238352686, -0.001120332197278825, +-0.001600807439724522, -0.001996577848597569, -0.002279316251738701, -0.002426862344229953, -0.002424859679572050, -0.002267956637237035, +-0.001960478287612480, -0.001516506229070831, -0.000959342574003313, -0.000320365808444990, 0.000362667830279848, 0.001047791955533175, + 0.001691344168593488, 0.002250683068847439, 0.002686938772729870, 0.002967628123966539, 0.003068960407414939, 0.002977678084347548, + 0.002692297230438709, 0.002223648489886749, 0.001594655540313720, 0.000839340735107497, 0.000001086828787463, -0.000869758241867305, +-0.001718811255366889, -0.002490949831033119, -0.003133783611782112, -0.003601086428852005, -0.003855971828191669, -0.003873602941247202, +-0.003643247691842578, -0.003169524021369342, -0.002472727031941658, -0.001588180215901802, -0.000564613998354494, 0.000538365561072565, + 0.001653594591084052, 0.002710281216575659, 0.003638323695477434, 0.004372765950231630, 0.004858115780200945, 0.005052252604909589, + 0.004929664405729861, 0.004483785715272022, 0.003728252839970840, 0.002696955568587015, 0.001442828675796044, 0.000035407607162536, +-0.001442758217956277, -0.002900658243916532, -0.004244107302544382, -0.005381470691504941, -0.006229500241120763, -0.006718928923729580, +-0.006799475746570288, -0.006443928763042613, -0.005651020748503490, -0.004446866818108242, -0.002884813426831026, -0.001043632901391050, + 0.000975905251500855, 0.003055961712387741, 0.005067831037893753, 0.006879122081429573, 0.008361541203410547, 0.009398858161408168, + 0.009894604643011696, 0.009779054490283380, 0.009015056125786116, 0.007602336538990519, 0.005579966604992522, 0.003026770185465134, + 0.000059562008821989, -0.003170779553680069, -0.006485285707743206, -0.009684675209522759, -0.012558594763991068, -0.014895884693277550, +-0.016495367870764655, -0.017176610952606329, -0.016790098400516502, -0.015226268406232360, -0.012422909599680294, -0.008370486626783004, +-0.003115063791803408, 0.003241392133040441, 0.010543411361223271, 0.018587073767192364, 0.027127521275133942, 0.035888613808298719, + 0.044574291632219959, 0.052881118780651611, 0.060511410978741485, 0.067186317405564938, 0.072658219055817971, 0.076721836801003668, + 0.079223499565701364, 0.080068115845938093, 0.079223499565701364, 0.076721836801003668, 0.072658219055817971, 0.067186317405564938, + 0.060511410978741485, 0.052881118780651611, 0.044574291632219959, 0.035888613808298719, 0.027127521275133942, 0.018587073767192364, + 0.010543411361223271, 0.003241392133040441, -0.003115063791803408, -0.008370486626783004, -0.012422909599680294, -0.015226268406232360, +-0.016790098400516502, -0.017176610952606329, -0.016495367870764655, -0.014895884693277550, -0.012558594763991068, -0.009684675209522759, +-0.006485285707743206, -0.003170779553680069, 0.000059562008821989, 0.003026770185465134, 0.005579966604992522, 0.007602336538990519, + 0.009015056125786116, 0.009779054490283380, 0.009894604643011696, 0.009398858161408168, 0.008361541203410547, 0.006879122081429573, + 0.005067831037893753, 0.003055961712387741, 0.000975905251500855, -0.001043632901391050, -0.002884813426831026, -0.004446866818108242, +-0.005651020748503490, -0.006443928763042613, -0.006799475746570288, -0.006718928923729580, -0.006229500241120763, -0.005381470691504941, +-0.004244107302544382, -0.002900658243916532, -0.001442758217956277, 0.000035407607162536, 0.001442828675796044, 0.002696955568587015, + 0.003728252839970840, 0.004483785715272022, 0.004929664405729861, 0.005052252604909589, 0.004858115780200945, 0.004372765950231630, + 0.003638323695477434, 0.002710281216575659, 0.001653594591084052, 0.000538365561072565, -0.000564613998354494, -0.001588180215901802, +-0.002472727031941658, -0.003169524021369342, -0.003643247691842578, -0.003873602941247202, -0.003855971828191669, -0.003601086428852005, +-0.003133783611782112, -0.002490949831033119, -0.001718811255366889, -0.000869758241867305, 0.000001086828787463, 0.000839340735107497, + 0.001594655540313720, 0.002223648489886749, 0.002692297230438709, 0.002977678084347548, 0.003068960407414939, 0.002967628123966539, + 0.002686938772729870, 0.002250683068847439, 0.001691344168593488, 0.001047791955533175, 0.000362667830279848, -0.000320365808444990, +-0.000959342574003313, -0.001516506229070831, -0.001960478287612480, -0.002267956637237035, -0.002424859679572050, -0.002426862344229953, +-0.002279316251738701, -0.001996577848597569, -0.001600807439724522, -0.001120332197278825, -0.000587687238352686, -0.000037468525946086, + 0.000495866308669078, 0.000980110478290227, 0.001387207697842587, 0.001694830920708386, 0.001887551700267456, 0.001957546402581353, + 0.001904808530758137, 0.001736873763731978, 0.001468091403060663, 0.001118502589392694, 0.000712407276705890, 0.000276722077340107, +-0.000160766721791647, -0.000573140492907433, -0.000935971757611233, -0.001228735487931008, -0.001435933022484648, -0.001547863924535660, +-0.001561017661066179, -0.001478067477842081, -0.001307487536115730, -0.001062825395605405, -0.000761689644626737, -0.000424524625176341, +-0.000073253806782210, 0.000270116979072993, 0.000584852833903113, 0.000852715731034719, 0.001058991562763383, 0.001193265605753869, + 0.001249905234811852, 0.001228230831826114, 0.001132378198325256, 0.000970869777260266, 0.000755932794801656, 0.000502615360611366, + 0.000227762682493944, -0.000051080037307638, -0.000316765531771492, -0.000553578640569728, -0.000748144004291994, -0.000890155314802122, +-0.000972892522850171, -0.000993496322190293, -0.000953000983271033, -0.000856122360814998, -0.000710834560643461, -0.000527759843251443, +-0.000319420735859412, -0.000099406083911376, 0.000118496954373743, 0.000321150767788200, 0.000496840283261992, 0.000635932028877494, + 0.000731379294218185, 0.000779050717176191, 0.000777868837348859, 0.000729757323783321, 0.000639407293886249, 0.000513885517380605, + 0.000362113995846136, 0.000194254912976452, 0.000021049474537857, -0.000146857332295172, -0.000299553787970989, -0.000428434330673012, +-0.000526664670957786, -0.000589524295786869, -0.000614609034335983, -0.000601879769614216, -0.000553573238187010, -0.000473974817394057, +-0.000369078758947477, -0.000246159846025947, -0.000113285445089856, 0.000021201177134175, 0.000149189135268233, 0.000263277817392097, + 0.000357188814988566, 0.000426095215060785, 0.000466848645480576, 0.000478101121808084, 0.000460307676423158, 0.000415628506871784, + 0.000347728839468377, 0.000261502601122459, 0.000162731306278391, 0.000057705644340284, -0.000047160366530124, -0.000145719497401035, +-0.000232432591548855, -0.000302673831926362, -0.000352960919521696, -0.000381111227069818, -0.000386317254135060, -0.000369138410006413, +-0.000331410885812673, -0.000276095967153748, -0.000207064338646769, -0.000128840476454010, -0.000046322038661538, 0.000035506782941005, + 0.000111858949442223, 0.000178399161339807, 0.000231462231911356, 0.000268225243572190, 0.000286822428035563, 0.000286408115238973, + 0.000267145694421862, 0.000230168155042476, 0.000177457791675865, 0.000111715248667983, 0.000036181038541454, -0.000045562167590561, +-0.000129775708505696, -0.000212772068148448, -0.000291090671787090, -0.000361653837423190, -0.000421891912076977, -0.000469843487184555, +-0.000504199216087390, -0.000524326246532992, -0.000530244985591953, -0.000522587601943708, -0.000502508714800933, -0.000471602831913601, +-0.000431779497772114, -0.000385165398367582, -0.000333959777524947, -0.000280359514663211, -0.000226421731946560, -0.000174014477804677, +-0.000124740944604127, -0.000079866391614059, -0.000040362137084292, -0.000006825518135279, 0.000020462723568921, 0.000041517869163367, + 0.000056611414853593, 0.000066239730112684, 0.000071044828173283, 0.000071774979399342, 0.000069229653331724, 0.000064222904381994, + 0.000057525397104827, 0.000049840688763229, 0.000041775447258916, 0.000033835554436160, 0.000026407938202815, 0.000019768330546629, + 0.000014080571079703, 0.000009418988876053, 0.000008584643858565 ], + 800: # 576 taps [ 0.000007812421315026, 0.000008939491599758, 0.000013730623424634, 0.000019922294330328, 0.000027648080357536, 0.000037033583075960, 0.000048143861498476, 0.000060975611534136, 0.000075478577997365, 0.000091486545323856, 0.000108759338791293, 0.000126959389238696, diff -Nru quisk-3.6.18/freedv.c quisk-3.7.6/freedv.c --- quisk-3.6.18/freedv.c 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/freedv.c 2015-09-11 11:49:36.000000000 +0000 @@ -0,0 +1,492 @@ +#include +#include +#include +#include // Use native C99 complex type for fftw3 +#include + +#include "quisk.h" + +int DEBUG; + +#define MAX_RECEIVERS 2 + +typedef struct { // from comp.h + float real; + float imag; +} COMP; + +struct freedv; // from freedv_api.h +typedef void (*freedv_callback_rx)(void *, char); +typedef char (*freedv_callback_tx)(void *); + +#ifdef MS_WINDOWS +#include +HMODULE WINAPI hLib; +#define GET_HANDLE1 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll") +#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll") +#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll") +#define GET_HANDLE4 hLib = LoadLibrary("libcodec2.dll") +#define GET_ADDR(name) (void *)GetProcAddress(hLib, name) +#define CLOSE_LIB FreeLibrary(hLib) +#else +#include +void * hLib; +#define GET_HANDLE1 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY) +#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY) +#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY) +#define GET_HANDLE4 hLib = dlopen("libcodec2.so", RTLD_LAZY) +#define GET_ADDR(name) dlsym(hLib, name) +#define CLOSE_LIB dlclose(hLib) +#endif + +static int requested_mode = -1; // requested mode +static int current_mode = -1; // the current running mode +static int quisk_freedv_squelch; +static int freedv_version = -1; + +#define SPEECH_BUF_SIZE 3000 // speech buffer size +static struct _rx_channel{ + struct freedv * hFreedv; + COMP * demod_in; + int rxdata_index; + short speech_out[SPEECH_BUF_SIZE]; // output buffer + int speech_available; // number of samples in output buffer + int playing; // are we currently returning speech samples? +} rx_channel[MAX_RECEIVERS] ; + +// freedv_version is the library version number, or +// -1 no library was found +// -2 a library was found, but freedv_get_version is missing + +// FreeDV API functions: +// open, close +struct freedv * (*freedv_open)(int mode); +void (*freedv_close)(struct freedv *freedv); +// Transmit +void (*freedv_tx)(struct freedv *freedv, short *, short *); +void (*freedv_comptx)(struct freedv *freedv, COMP *, short *); +// Receive +int (*freedv_nin)(struct freedv *freedv); +int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]); +int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]); +int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]); +// Set parameters +void (*freedv_set_callback_txt)(struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state); +void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames); +void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols); +void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en); +void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh); +// Get parameters +int (*freedv_get_version)(void); +void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est); +int (*freedv_get_test_frames) (struct freedv *freedv); +int (*freedv_get_n_speech_samples) (struct freedv *freedv); +int (*freedv_get_n_max_modem_samples) (struct freedv *freedv); +int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv); +int (*freedv_get_total_bits) (struct freedv *freedv); +int (*freedv_get_total_bit_errors) (struct freedv *freedv); +// Below this line, version 11 and up is required. +int (*freedv_get_sync) (struct freedv *freedv); + +static void GetAddrs(void) +{ + if (DEBUG) printf("Try handle 1\n"); + GET_HANDLE1; + if (hLib) { // check the first library name + freedv_version = -2; + freedv_get_version = GET_ADDR("freedv_get_version"); + if (freedv_get_version != NULL) + freedv_version = freedv_get_version(); + } + if (freedv_version < 10) { // try the next library + if (hLib) + CLOSE_LIB; + if (DEBUG) printf("Try handle 2\n"); + GET_HANDLE2; + if (hLib) { + freedv_version = -2; + freedv_get_version = GET_ADDR("freedv_get_version"); + if (freedv_get_version != NULL) + freedv_version = freedv_get_version(); + } + } + if (freedv_version < 10) { // try the next library + if (hLib) + CLOSE_LIB; + if (DEBUG) printf("Try handle 3\n"); + GET_HANDLE3; + if (hLib) { + freedv_version = -2; + freedv_get_version = GET_ADDR("freedv_get_version"); + if (freedv_get_version != NULL) + freedv_version = freedv_get_version(); + } + } + if (freedv_version < 10) { // try the next library + if (hLib) + CLOSE_LIB; + if (DEBUG) printf("Try handle 4\n"); + GET_HANDLE4; + if (hLib) { + freedv_version = -2; + freedv_get_version = GET_ADDR("freedv_get_version"); + if (freedv_get_version != NULL) + freedv_version = freedv_get_version(); + } + } + if (DEBUG) printf("freedv_version is %d\n", freedv_version); + if (freedv_version < 10) { + if (hLib) + CLOSE_LIB; + return; + } + +// open, close + freedv_open = GET_ADDR("freedv_open"); + freedv_close = GET_ADDR("freedv_close"); +// Transmit + freedv_tx = GET_ADDR("freedv_tx"); + freedv_comptx = GET_ADDR("freedv_comptx"); +// Receive + freedv_nin = GET_ADDR("freedv_nin"); + freedv_rx = GET_ADDR("freedv_rx"); + freedv_floatrx = GET_ADDR("freedv_floatrx"); + freedv_comprx = GET_ADDR("freedv_comprx"); +// Set parameters + freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt"); + freedv_set_test_frames = GET_ADDR("freedv_set_test_frames"); + freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols"); + freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en"); + freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh"); +// Get parameters + freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats"); + freedv_get_test_frames = GET_ADDR("freedv_get_test_frames"); + freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples"); + freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples"); + freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples"); + freedv_get_total_bits = GET_ADDR("freedv_get_total_bits"); + freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors"); + freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11 + return; +} + +static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int count, int bank) // Called from the sound thread. +{ // Input digital modulation is cSamples; decoded voice is dsamples. Each "bank" is a stream of audio. + int i, nout, need, have, sync; + int n_speech_samples; + complex double cx; + double scale = (double)CLIP32 / CLIP16; // convert 32 bits to 16 bits + struct freedv * hF; + struct _rx_channel * pCh; + + if (cSamples == NULL) { // shutdown + for (i = 0; i < MAX_RECEIVERS; i++) { + if (rx_channel[i].demod_in) { + free(rx_channel[i].demod_in); + rx_channel[i].demod_in = NULL; + } + } + return 0; + } + + if (bank < 0 || bank >= MAX_RECEIVERS) + return 0; + hF = rx_channel[bank].hFreedv; + if ( ! hF) + return 0; + pCh = rx_channel + bank; + n_speech_samples = freedv_get_n_speech_samples(hF); + nout = 0; + need = freedv_nin(hF); + for (i = 0; i < count; i++) { + cx = cRxFilterOut(cSamples[i], bank); + if (rxMode == 12) // lower sideband + cx = conj(cx); +#if 0 + pCh->demod_in[pCh->rxdata_index].real = creal(cx) / scale; + pCh->demod_in[pCh->rxdata_index].imag = cimag(cx) / scale; +#else + pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx)) / scale; + pCh->demod_in[pCh->rxdata_index].imag = 0; +#endif + pCh->rxdata_index++; + if (pCh->rxdata_index >= need) { + if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space + have = freedv_comprx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in); + if (freedv_version > 10) + sync = freedv_get_sync(hF); + else + freedv_get_modem_stats(hF, &sync, NULL); + if (current_mode == 0) { // mode 1600 + if (sync) // throw away speech if not in sync + pCh->speech_available += have; + } + else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) { + pCh->speech_available += have; // keep speech if there is space + } + else { + if (DEBUG) printf("Close to maximum in speech output buffer\n"); + } + } + else { // no space in buffer + if (DEBUG) printf("Overflow in speech output buffer\n"); + } + pCh->rxdata_index = 0; + need = freedv_nin(hF); + } + } + if ( ! pCh->playing) { + if (pCh->speech_available >= 2 * n_speech_samples) { + pCh->playing = 1; + } + else { // return zero samples + for (i = 0; i < count; i++) + dsamples[i] = 0; + //if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available); + return count; + } + } + for (nout = 0; nout < pCh->speech_available && nout < count; nout++) + dsamples[nout] = pCh->speech_out[nout] * scale * 0.7; + if (nout) { + pCh->speech_available -= nout; + memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short)); + } + if ( ! pCh->speech_available) { + pCh->playing = 0; + while (nout < count) + dsamples[nout++] = 0; + } + //if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available); + return nout; +} + +static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count) // Called from the sound thread. +{ // Input voice samples are dsamples; output digital modulation is cSamples. + int i, nout; + int n_speech_samples; + int n_nom_modem_samples; + static COMP * mod_out = NULL; + static short * speech_in = NULL; + static int speech_index=0, mod_index=0; + + if (dsamples == NULL) { // shutdown + if (mod_out) + free(mod_out); + mod_out = NULL; + if (speech_in) + free(speech_in); + speech_in = NULL; + return 0; + } + if ( ! rx_channel[0].hFreedv) + return 0; + n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv); + n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv); + if (mod_out == NULL) { // initialize + mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples); + memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples); + speech_in = (short*)malloc(sizeof(short) * n_speech_samples); + speech_index=0; + mod_index=0; + } + nout = 0; + for (i = 0; i < count; i++) { + speech_in[speech_index++] = (short)dsamples[i]; + if (speech_index >= n_speech_samples) { + // Calculate a new block, but first write out the rest of the old block + for ( ; mod_index < n_nom_modem_samples; mod_index++) + cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag; + freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in); + mod_index = 0; + speech_index = 0; + } + else { // write out samples slowly + if (mod_index < n_nom_modem_samples) { + cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag; + mod_index++; + } + } + } + if (rxMode == 12) + for (i = 0; i < nout; i++) + cSamples[i] = conj(cSamples[i]); + return nout; +} + +#define TX_MSG_SIZE 80 +static char quisk_tx_msg[TX_MSG_SIZE]; + +static char get_next_tx_char(void * callback_state) +{ + char c; + static int index = 0; + + c = quisk_tx_msg[index++]; + if (index >= TX_MSG_SIZE) + index = 0; + if ( ! c) { + index = 0; + c = quisk_tx_msg[index++]; + } + return c; +} + +#define RX_MSG_SIZE 80 +static char quisk_rx_msg[RX_MSG_SIZE + 1]; + +static void put_next_rx_char(void * callback_state, char ch) +{ + if (ch == '\n' || ch == '\r') + ch = ' '; + if (ch < 32 || ch > 126) // printable characters + return; + if (strlen(quisk_rx_msg) < RX_MSG_SIZE) + strncat(quisk_rx_msg, &ch, 1); +} + +PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread. +{ + PyObject * txt; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + txt = PyString_FromString(quisk_rx_msg); + quisk_rx_msg[0] = 0; + return txt; +} + +static void CloseFreedv(void) // Called from the GUI thread or sound thread +{ + int i; + + for (i = 0; i < MAX_RECEIVERS; i++) { + if (rx_channel[i].hFreedv) { + freedv_close(rx_channel[i].hFreedv); + rx_channel[i].hFreedv = NULL; + } + if (rx_channel[i].demod_in) { + free(rx_channel[i].demod_in); + rx_channel[i].demod_in = NULL; + } + } + quisk_freedv_rx(NULL, NULL, 0, 0); + quisk_freedv_tx(NULL, NULL, 0); + current_mode = -1; +} + +static int OpenFreedv(void) // Called from the GUI thread or sound thread +{ + int i, n_max_modem_samples; + struct freedv * hF; + + if ( ! hLib) + GetAddrs(); // Get the entry points for funtions in the codec2 library + if (DEBUG) printf("freedv_open: version %d\n", freedv_version); + if (freedv_version < 10 || (hF = freedv_open(requested_mode)) == NULL) { + CloseFreedv(); + requested_mode = -1; + return 0; // failure + } + rx_channel[0].hFreedv = hF; + quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx); + if (quisk_tx_msg[0]) + freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL); + else + freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL); + freedv_set_squelch_en(hF, quisk_freedv_squelch); + n_max_modem_samples = freedv_get_n_max_modem_samples(hF); + for (i = 0; i < MAX_RECEIVERS; i++) { + rx_channel[i].rxdata_index = 0; + rx_channel[i].speech_available = 0; + rx_channel[i].playing = 0; + if (rx_channel[i].demod_in) + free(rx_channel[i].demod_in); + rx_channel[i].demod_in = (COMP *)malloc(sizeof(COMP) * n_max_modem_samples); + if (i > 0) { + rx_channel[i].hFreedv = freedv_open(requested_mode); + if (rx_channel[i].hFreedv) + freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch); + } + } + if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv)); + if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv)); + if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples); + current_mode = requested_mode; + return 1; // success +} + +void quisk_check_freedv_mode(void) +{ // see if we need to change the mode + if (requested_mode == current_mode) + return; + if (DEBUG) printf("Change in mode to %d\n", requested_mode); + CloseFreedv(); + if (requested_mode >= 0) + OpenFreedv(); + else + requested_mode = -1; +} + +PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open +{ + if (!PyArg_ParseTuple (args, "")) + return NULL; + return PyInt_FromLong(OpenFreedv()); +} + +PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread. +{ + if (!PyArg_ParseTuple (args, "")) + return NULL; + requested_mode = -1; // request close + Py_INCREF (Py_None); + return Py_None; +} + +PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread. +{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode. + int mode; // Call again to change the mode. + char * ptMsg=NULL; + static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", NULL} ; + struct freedv * hFreedv; + + if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch)) + return NULL; + if (ptMsg) + strncpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE); + if (current_mode < 0) // not started + requested_mode = mode; + else if (freedv_version == 10 && mode == 0) + requested_mode = mode; + else if (freedv_version == 11 && mode <= 2) + requested_mode = mode; + else { + hFreedv = freedv_open(mode); // test new mode + if (hFreedv != NULL) { + freedv_close(hFreedv); + requested_mode = mode; + } + } + return PyInt_FromLong(requested_mode); // Return the mode +} + +PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread. +{ + float snr_est = 0.0; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + if (rx_channel[0].hFreedv) + freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est); + return PyFloat_FromDouble(snr_est); +} + +PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread. +{ + if (!PyArg_ParseTuple (args, "")) + return NULL; + if ( ! hLib) + GetAddrs(); // Get the entry points for funtions in the codec2 library + return PyInt_FromLong(freedv_version); +} diff -Nru quisk-3.6.18/freedvpkg/README.txt quisk-3.7.6/freedvpkg/README.txt --- quisk-3.6.18/freedvpkg/README.txt 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/freedvpkg/README.txt 2015-08-06 18:50:44.000000000 +0000 @@ -0,0 +1,90 @@ + +Note: The directory freedvpkg no longer contains source files. It only contains copies of + the codec2 libraries for use by Quisk. If any other files are present, delete them. + +FreeDV and Directory freedvpkg +============================== + +FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice +in 1200 Hz bandwidth suitable for HF transmission. Quisk has native (built-in) support for +FreeDV. Just push the FDV mode and talk. This freedvpkg directory contains copies of the +codec2 libraries for use by Quisk. + +You can also use the separate FreeDV program available at freedv.org, and the Quisk DGT-U mode +which attaches to external digital programs. The setup is identical to fldigi and other external +digital programs. + +Quisk will add the FDV mode button unless your config file contains the line + add_freedv_button = 0 +If there is a problem with the freedv module, the button will be grayed out. + +The freedv module requires the codec2 library. This library is included for Windows and for +Ubuntu 14.04 LTS 32-bit and 64-bit. For other systems (such as ARM) you will need to build another +codec2. Just try the FDV mode and see if it works. It should always work on Windows, and may work +on Linux. If the FDV button is grayed out, you need a different codec2 than the one included. Make +a new codec2 library, and copy it to freedvpkg/libcodec2.so. + +Search Order +============ + +Quisk will search for a valid codec2 library in this order on Windows: +1. freedvpkg/libcodec2.dll. Not included. Copy the codec2 you want to use to this name. +2. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk. +3. The system codec2 library installed outside of Quisk by another program. + +Quisk will search for a valid codec2 library in this order on Linux: +1. freedvpkg/libcodec2.so. Not included. Copy the codec2 you want to use to this name. +2. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk. +3. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk. +4. The system codec2 library installed outside of Quisk by another program. + +How to Build a New codec2 +========================= + +The source for codec2 is in SourceForge in the freetel project. Or google for other sources or perhaps +a pre-built library. If you need to compile codec2 from source, first change to a suitable directory +(not the Quisk directory) and download the source with svn: + + svn co https://svn.code.sf.net/p/freetel/code/codec2-dev codec2-dev + +Note that we are using codec2-dev to get the most recent source. Then build codec2 using the directions +found in README.cmake. The directions given below are current as of August 2015, but check for changes. +Then copy the codec2 library to the freedvpkg directory under Quisk. Nothing else is required. + +Build a New codec2 on Linux +=========================== +The Speex "-dev" packages are not needed by codec2, but are required for the Unit Test modules. +Create the codec2 shared library. Note the "../". + + cd codec2-dev + mkdir build_linux + cd build_linux + cmake -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../ + make codec2 + cd src + cp libcodec2* my-quisk-directory/freedvpkg + +Build a New codec2 on Windows +============================= +For Windows you need to install MinGW, MSYS, and g++. Use the MSYS bash shell. The Speex libraries +are not needed by codec2, but are required for the Unit Test modules. To build the Unit Test modules, +you need to install Speex and add -DSPEEXDSP_INCLUDE_DIR=../speex/include/speex -DSPEEXDSP_LIBRARY=../speex/bin. + + cd codec2-dev + mkdir build_win32 + cd build_win32 + cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../ + make codec2 + cd src + cp libcodec2.dll my-quisk-directory/freedvpkg + +Testing +======= +You can just start Quisk and see if the FDV button is not grayed out, and FDV works. Or you can +test the import of freedv and look for error messages. + + cd my-quisk-directory + c:/python27/python.exe # (or just "python" on Linux) + import _quisk + _quisk.freedv_get_version() # This should return 10 or higher for a recent codec2 + diff -Nru quisk-3.6.18/help.html quisk-3.7.6/help.html --- quisk-3.6.18/help.html 2014-05-25 21:15:15.000000000 +0000 +++ quisk-3.7.6/help.html 2015-09-03 13:06:33.000000000 +0000 @@ -6,14 +6,18 @@ +

-QUISK Help (November 2013) +QUISK Help (September 2015)

The The documentation is here.
+
+The The default configuration is here. +

This is the Help file for Quisk, a Software Defined Radio (SDR). @@ -161,6 +165,14 @@

+Some buttons, like the Graph button, can be pressed repeatedly to select different +settings. These buttons have a circular arrow on the right. Other button can be +right-clicked. For example, if you right-click the AGC button, you can set the +level. These buttons are marked with a control symbol. +
+

+ +

Next press WFall to see the Waterfall screen, then Help to return. You can resize the Quisk window to get see more waterfall history. You will need to adjust "Ys" (Y scale) and "Yz" (Y zero) to get a @@ -233,7 +245,8 @@

You can left-click the digits in the frequency display to increase (click the top) or decrease (click the bottom) the digit and round the -frequency.  Try it. +frequency. You can also roll the mouse wheel up and down to change +the digit. Try it.

@@ -256,9 +269,9 @@ frequency unless Split is used (see below).  The frequency display window will turn red to indicate sound capture (input ADC) overrun (clipping). -

+

The RIT (receiver incremental tuning) button and slider can add a small offset to the receive frequency.   Leave RIT off for SSB unless @@ -273,13 +286,20 @@ by clicking exactly on the signal, and everything will work. The value of cwTone can be changed in your configuration file. -

If the Split button is pressed, a second green tuning line is shown +

+

+If the split button "Splt" is pressed, a second green tuning line is shown to indicate the receive frequency, and the receive frequency can be -independently adjusted.  The mouse controls the closest tuning -line.  To receive on the transmit frequency again, just turn Split -off.  The Split feature is used to work a DX station operating +independently adjusted. The mouse controls the closest tuning +line. To receive on the transmit frequency again, just turn split +off. The Split feature is used to work a DX station operating split, or it can be used to easily switch between two arbitrary receive -frequencies.

+frequencies. There are options in quisk_conf_defaults.py to control split. +If you click the split button with the right mouse button, the Tx frequency +is locked, and all tuning operations change the Rx frequency. If you click the "Rev" +button, the Tx and Rx frequencies are reversed. +

+

Top Row

@@ -432,7 +452,8 @@ amplitude of signals and any QSB while still protecting your ears.  I set the AGC On setting to a high value, and the AGC Off setting to a lower value that allows band noise to be faintly heard.

-

When you switch the mode to FM, the AGC button changes to a squelch button.  Right click it to adjust the squelch level.

+

When you switch the mode to FM, the AGC button changes to a squelch button.  +Right click it to adjust the squelch level.

When the Spot button is pressed, a carrier replaces the SSB signal.  This is used for tuning.  Right click the Spot button to adjust the level. @@ -444,7 +465,10 @@

-The first row of buttons on the right selects the receive mode. +The first row of buttons on the right selects the mode. The FDV mode +is for FreeDV digital voice. The IMD mode +transmits a two-tone test signal which is used to test amplifiers. Right +click the IMD button to adjust the level. The next row selects the filter to use. The filter bandwidths can be set in your config file.  The right-most filter button bandwidth is adjustable; right-click it to adjust.  The third row selects @@ -511,7 +535,10 @@ because Quisk will clip the audio anyway when it processes it.  It may be difficult to figure out how to adjust the mic level.  For Linux, figure out the correct control number, and use mixer_settings[]; -or use one of the Linux mixer apps.  For Windows, use the level +or use one of the Linux mixer apps. +To figure out the control number, use "amixer -c 1 contents" to get a list +of mixer controls and their numid's for card 1 (or "-c 0" for card 0). +For Windows, use the level control on the audio control screen in Control Panel, but be careful that another application does not change it after you set it for Quisk.  Quisk will attempt to adjust the audio level with its AGC, @@ -525,7 +552,7 @@ your voice.  Zero is no boost, and 1.0 is 6 dB per octave.  Use the record and playback buttons to test for the best control settings.  Notice that your voice will become louder with more -clipping.  Note that SSB, AM and FM each have their own settings, +clipping.  Note that SSB, AM, FM and FDV each have their own settings, so change to the correct mode before you start.  Audio processing is most useful for SSB, so if you are a DX enthusiast, use aggressive settings.  I only use AM for rag chewing, so I use zero @@ -537,7 +564,7 @@

These modes are for digital signals, and require an  external -program such as Fldigi to decode the signals.  Press the "DGT-" +program such as Fldigi or FreeDV to decode the signals.  Press the "DGT-" button repeatedly to select a digital mode.  To use Fldigi, first start Quisk; then start Fldigi and change "Rig Control" to "Use XML-RPC".  Now changing the frequency in either program should @@ -560,7 +587,7 @@ localhost:4575, then change the port Quisk uses by adding "hamlib_port = 4532" to your config file.  See the documentation for more information. -

Sound Recording

+

Temporary Sound Recording

There is a Record and Playback button in the third row.  Push Record to start recording radio sound.  The maximum time to record @@ -585,10 +612,22 @@ of the recording.
+

Sound Playback from a File

+ +Quisk can transmit a message from a WAV file. Record your message at +a high level (almost clipping) at 48 ksps, 16-bit, one channel (monophonic). +Do not leave silence at the beginning or end of the message. This feature is +meant for CQ messages, for example "CQ contest, CQ contest, this is N2ADR". +Then enter the file name on the Config/Config screen. Set the repeat time, +or set the repeat time to zero for one-time play. Then press the "File play" +button to transmit. Quisk will press the PTT button for you, and release it +during pauses. To interrupt playback, press PTT or release FilePlay so you can answer. +
+

Frequency Measurement

Quisk can measure and display the frequency of a continuous tone RF -signal.  To use this feature, right click the S-meter window, and +signal.  To use this feature, press the S-meter button, and select one of the Frequency items.  The numbers are the averaging times in seconds.  Then find a signal of interest and put the tuning line exactly on it.  Quisk will search 500 Hertz up and diff -Nru quisk-3.6.18/hermes/__init__.py quisk-3.7.6/hermes/__init__.py --- quisk-3.6.18/hermes/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/hermes/__init__.py 2015-06-22 16:17:58.000000000 +0000 @@ -0,0 +1 @@ +# diff -Nru quisk-3.6.18/hermes/quisk_conf2.py quisk-3.7.6/hermes/quisk_conf2.py --- quisk-3.6.18/hermes/quisk_conf2.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/hermes/quisk_conf2.py 2015-08-09 14:07:51.000000000 +0000 @@ -0,0 +1,64 @@ +# This is a sample config file for the Hermes-Lite. Do not modify this file. +# This example file uses the lower bits of J16 for the band, and the highest bit for a Spot indicator. + +from hermes.quisk_hardware import Hardware as BaseHardware # We have our own hardware file defined below +from hermes import quisk_widgets + +use_rx_udp = 10 # Use this for Hermes-Lite +rx_udp_clock = 73728000 # The clock is 73.728 or 61.440 megahertz. Adjust slightly for actual frequency. +rx_udp_ip = "" # Sample source IP address "" for DHCP +rx_udp_port = 1024 # Sample source UDP port; must be 1024 +data_poll_usec = 10000 # poll time in microseconds + +use_sidetone = 1 # Use the Quisk sidetone for CW + +sample_rate = 96000 +name_of_sound_capt = "" # We do not capture from the soundcard + +name_of_sound_play = 'hw:1' # Play radio sound here. Linux. +microphone_name = 'hw:1' # The microphone is here. + +#name_of_sound_play = 'Primary' # Play radio sound here. Windows. +#microphone_name = 'Primary' # The microphone is here. + +graph_y_scale = 100 +graph_y_zero = 100 +playback_rate = 48000 +add_imd_button = 1 +add_fdx_button = 1 +tx_level = {None:255, '60':110} # Adjust your power for each band + +# Control the J16 connector according to the band. J16 is C0 index 0, C2[7:1]. If the band is not here, the default is 0x00. +# This value is written to bits C2[7:1]. That is, it is left shifted by one bit and written to byte C2. +Hermes_BandDict = {'160':0b0000001, '80':0b0000010, '60':0b0000011, '40':0b0000100, '30':0b0000101, '20':0b0000110, '15':0b0000111} + + +# Define the Hardware class in this config file instead of a separate file. + +class Hardware(BaseHardware): + def __init__(self, app, conf): + BaseHardware.__init__(self, app, conf) + self.usingSpot = False # Use bit C2[7] as the Spot indicator + def ChangeBand(self, band): + # band is a string: "60", "40", "WWV", etc. + # The call to BaseHardware will set C2 according to the Hermes_BandDict{} + ret = BaseHardware.ChangeBand(self, band) + if self.usingSpot: + byte = self.GetControlByte(0, 2) # C0 index == 0, C2: user output + byte |= 0b10000000 + self.SetControlByte(0, 2, byte) + return ret + def OnSpot(self, level): + # level is -1 for Spot button Off; else the Spot level 0 to 1000. + ret = BaseHardware.OnSpot(self, level) + if level >= 0 and not self.usingSpot: # Spot was turned on + byte = self.GetControlByte(0, 2) + byte |= 0b10000000 + self.SetControlByte(0, 2, byte) + self.usingSpot = True + elif level < 0 and self.usingSpot: # Spot was turned off + byte = self.GetControlByte(0, 2) + byte &= 0b01111111 + self.SetControlByte(0, 2, byte) + self.usingSpot = False + return ret diff -Nru quisk-3.6.18/hermes/quisk_conf.py quisk-3.7.6/hermes/quisk_conf.py --- quisk-3.6.18/hermes/quisk_conf.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/hermes/quisk_conf.py 2015-08-19 15:16:14.000000000 +0000 @@ -0,0 +1,32 @@ +# This is a sample config file for the Hermes-Lite. Do not modify this file. + +from hermes import quisk_hardware +from hermes import quisk_widgets + +use_rx_udp = 10 # Use this for Hermes-Lite +rx_udp_clock = 73728000 # The clock is 73.728 or 61.440 megahertz. Adjust slightly for actual frequency. +rx_udp_ip = "" # Sample source IP address "" for DHCP +rx_udp_port = 1024 # Sample source UDP port; must be 1024 +data_poll_usec = 10000 # poll time in microseconds + +use_sidetone = 1 # Use the Quisk sidetone for CW + +sample_rate = 96000 +name_of_sound_capt = "" # We do not capture from the soundcard + +name_of_sound_play = 'hw:1' # Play radio sound here. Linux. +microphone_name = 'hw:1' # The microphone is here. + +#name_of_sound_play = 'Primary' # Play radio sound here. Windows. +#microphone_name = 'Primary' # The microphone is here. + +graph_y_scale = 100 +graph_y_zero = 100 +playback_rate = 48000 +add_imd_button = 1 +add_fdx_button = 1 +tx_level = {None:255, '60':255} # Adjust your power for each band + +# Control the J16 connector according to the band. J16 is C0 index 0, C2[7:1]. If the band is not here, the default is 0x00. +# This value is written to bits C2[7:1]. That is, it is left shifted by one bit and written to byte C2. +Hermes_BandDict = {'160':0b0000001, '80':0b0000010, '60':0b0000100, '40':0b0001000, '30':0b0010000, '20':0b0100000, '15':0b1000000} diff -Nru quisk-3.6.18/hermes/quisk_hardware.py quisk-3.7.6/hermes/quisk_hardware.py --- quisk-3.6.18/hermes/quisk_hardware.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/hermes/quisk_hardware.py 2015-08-09 15:14:32.000000000 +0000 @@ -0,0 +1,247 @@ +# This is a sample hardware file for UDP control using the Hermes-Metis protocol. Use this for +# the HermesLite project. It can also be used for the HPSDR, but since I don't have one, I +# can't test it. + +from __future__ import print_function + +import socket, traceback, time, math +import _quisk as QS + +from quisk_hardware_model import Hardware as BaseHardware + +DEBUG = 0 + +class Hardware(BaseHardware): + var_rates = ['48', '96', '192', '384'] + def __init__(self, app, conf): + BaseHardware.__init__(self, app, conf) + self.var_index = 0 + self.hermes_ip = "" + self.hermes_board_id = -1 + self.mode = None + self.band = None + self.vfo_frequency = 0 + self.tx_frequency = 0 + self.repeater_freq = None # original repeater output frequency + try: + self.repeater_delay = conf.repeater_delay # delay for changing repeater frequency in seconds + except: + self.repeater_delay = 0.25 + self.repeater_time0 = 0 # time of repeater change in frequency + # Create the proper broadcast address for rx_udp_ip. + if False and self.conf.rx_udp_ip: + nm = self.conf.rx_udp_ip_netmask.split('.') + ip = self.conf.rx_udp_ip.split('.') + nm = map(int, nm) + ip = map(int, ip) + bc = '' + for i in range(4): + x = (ip[i] | ~ nm[i]) & 0xFF + bc = bc + str(x) + '.' + self.broadcast_addr = bc[:-1] + else: + self.broadcast_addr = '255.255.255.255' + # This socket is used for the Metis Discover protocol + self.discover_request = chr(0xEF) + chr(0xFE) + chr(0x02) + chr(0) * 60 + self.socket_discover = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket_discover.setblocking(0) + self.socket_discover.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + # This is the control data to send to the Hermes using the Metis protocol + self.pc2hermes = bytearray(17 * 4) # Python initializes this to zero + self.pc2hermes[3] = 0x04 # C0 index == 0, C4[5:3]: number of receivers 0b000 -> one receiver; C4[2] duplex on + QS.pc_to_hermes(self.pc2hermes) + def pre_open(self): + st = "No capture device found." + port = self.conf.rx_udp_port + for i in range(5): + if DEBUG: print ('Send discover') + try: + self.socket_discover.sendto(self.discover_request, (self.broadcast_addr, port)) + time.sleep(0.05) + data, addr = self.socket_discover.recvfrom(1500) + except: + if DEBUG > 1: traceback.print_exc() + else: + if len(data) > 32 and data[0] == chr(0xEF) and data[1] == chr(0xFE): + data = map(ord, data) + ver = self.conf.hermes_code_version + bid = self.conf.hermes_board_id + if ver >= 0 and data[9] != ver: + pass + elif bid >= 0 and data[10] != bid: + pass + else: + st = 'Capture from Hermes device: Mac %2x:%2x:%2x:%2x:%2x:%2x, Version %d, ID %d' % tuple(data[3:11]) + self.hermes_ip = addr[0] + self.hermes_board_id = data[10] + if DEBUG: print (st) + adr = self.conf.rx_udp_ip + if adr and adr != addr[0]: # Specified IP address + if DEBUG: print("Change IP address from %s to %s" % (addr[0], adr)) + ip = adr.split('.') + ip = map(int, ip) + cmd = (chr(0xEF) + chr(0xFE) + chr(0x03) + + chr(data[3]) + chr(data[4]) + chr(data[5]) + chr(data[6]) + chr(data[7]) + chr(data[8]) + + chr(ip[0]) + chr(ip[1]) + chr(ip[2]) + chr(ip[3]) + chr(0) * 60) + self.socket_discover.sendto(cmd, (self.broadcast_addr, port)) + time.sleep(0.1) + self.socket_discover.sendto(cmd, (self.broadcast_addr, port)) + # Note: There is no response, contrary to the documentation + self.hermes_ip = adr + if False: + try: + data, addr = self.socket_discover.recvfrom(1500) + except: + if DEBUG: traceback.print_exc() + else: + print(repr(data), addr) + ##self.hermes_ip = adr + time.sleep(1.0) + st += ', IP %s' % self.hermes_ip + # Open a socket for communication with the hardware + msg = QS.open_rx_udp(self.hermes_ip, port) + if msg[0:8] != "Capture ": + st = msg # Error + break + time.sleep(0.05) + self.socket_discover.close() + self.config_text = st + def open(self): + return self.config_text + def GetControlByte(self, C0_index, byte_index): + # Get the control byte at C0 index and byte index. The bytes are C0, C1, C2, C3, C4. + # The C0 index is 0 to 16 inclusive. The byte index is 1 to 4. The byte index of C2 is 2. + return self.pc2hermes[C0_index * 4 + byte_index - 1] + def SetControlByte(self, C0_index, byte_index, value): # Set the control byte. + self.pc2hermes[C0_index * 4 + byte_index - 1] = value + QS.pc_to_hermes(self.pc2hermes) + def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None): + if tx_freq and tx_freq > 0: + self.tx_frequency = tx_freq + tx = int(tx_freq - self.transverter_offset) + self.pc2hermes[ 4] = tx >> 24 & 0xff # C0 index == 1, C1, C2, C3, C4: Tx freq, MSB in C1 + self.pc2hermes[ 5] = tx >> 16 & 0xff + self.pc2hermes[ 6] = tx >> 8 & 0xff + self.pc2hermes[ 7] = tx & 0xff + if self.vfo_frequency != vfo_freq: + self.vfo_frequency = vfo_freq + vfo = int(vfo_freq - self.transverter_offset) + self.pc2hermes[ 8] = vfo >> 24 & 0xff # C0 index == 2, C1, C2, C3, C4: Rx freq, MSB in C1 + self.pc2hermes[ 9] = vfo >> 16 & 0xff + self.pc2hermes[10] = vfo >> 8 & 0xff + self.pc2hermes[11] = vfo & 0xff + if DEBUG > 1: print("Change freq Tx", tx_freq, "Rx", vfo_freq) + QS.pc_to_hermes(self.pc2hermes) + return tx_freq, vfo_freq + def ReturnVfoFloat(self): # Return the accurate VFO as a float + # This code attempts to duplicate the calculation of phase increment in the FPGA code. + clock = ((int(self.conf.rx_udp_clock) + 24000) / 48000) * 48000 # this assumes the nominal clock is a multiple of 48kHz + M2 = 2 ** 57 / clock + M3 = 2 ** 24 + freqcomp = int(self.vfo_frequency - self.transverter_offset) * M2 + M3 + rx_phase = (freqcomp / 2 ** 25) & 0xFFFFFFFF + freq = float(rx_phase) * self.conf.rx_udp_clock / 2.0**32 + return freq + def ReturnFrequency(self): # Return the current tuning and VFO frequency + return None, None # frequencies have not changed + def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx + if offset is None: # Return True if frequency change is complete + if time.time() > self.repeater_time0 + self.repeater_delay: + return True + elif offset == 0: # Change back to the original frequency + if self.repeater_freq is not None: + self.repeater_time0 = time.time() + self.ChangeFrequency(self.repeater_freq, self.vfo_frequency, 'repeater') + self.repeater_freq = None + else: # Shift to repeater input frequency + self.repeater_freq = self.tx_frequency + offset = int(offset * 1000) # Convert kHz to Hz + self.repeater_time0 = time.time() + self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency, 'repeater') + return False + def ChangeBand(self, band): + # band is a string: "60", "40", "WWV", etc. + BaseHardware.ChangeBand(self, band) + self.band = band + J16 = self.conf.Hermes_BandDict.get(band, 0) + self.SetControlByte(0, 2, J16 << 1) # C0 index == 0, C2[7:1]: user output + self.SetTxLevel() + def ChangeMode(self, mode): + # mode is a string: "USB", "AM", etc. + BaseHardware.ChangeMode(self, mode) + self.mode = mode + self.SetTxLevel() + def OnButtonPTT(self, event): + btn = event.GetEventObject() + if btn.GetValue(): + QS.set_PTT(1) + else: + QS.set_PTT(0) + def OnSpot(self, level): + # level is -1 for Spot button Off; else the Spot level 0 to 1000. + pass + def VarDecimGetChoices(self): # return text labels for the control + return self.var_rates + def VarDecimGetLabel(self): # return a text label for the control + return "Sample rate ksps" + def VarDecimGetIndex(self): # return the current index + return self.var_index + def VarDecimSet(self, index=None): # set decimation, return sample rate + if index is None: # initial call to set rate before the call to open() + rate = self.application.vardecim_set # May be None or from different hardware + else: + rate = int(self.var_rates[index]) * 1000 + if rate == 48000: + self.var_index = 0 + elif rate == 96000: + self.var_index = 1 + elif rate == 192000: + self.var_index = 2 + elif rate == 384000: + self.var_index = 3 + else: + self.var_index = 0 + rate = 48000 + self.pc2hermes[0] = self.var_index # C0 index == 0, C1[1:0]: rate + QS.pc_to_hermes(self.pc2hermes) + if DEBUG: print ("Change sample rate to", rate) + return rate + def VarDecimRange(self): + return (48000, 384000) + def ChangeAGC(self, value): + if value: + self.pc2hermes[2] |= 0x10 # C0 index == 0, C3[4]: AGC enable + else: + self.pc2hermes[2] &= ~0x10 + QS.pc_to_hermes(self.pc2hermes) + if DEBUG: print ("Change AGC to", value) + def ChangeLNA(self, value): + # value is -12 to +48 + if value < 20: + self.pc2hermes[2] |= 0x08 # C0 index == 0, C3[3]: LNA +32 dB disable == 1 + value = 19 - value + else: + self.pc2hermes[2] &= ~0x08 # C0 index == 0, C3[3]: LNA +32 dB enable == 0 + value = 51 - value + self.pc2hermes[4 * 10 + 3] = value # C0 index == 0x1010, C4[4:0] LNA 0-32 dB gain + QS.pc_to_hermes(self.pc2hermes) + if DEBUG: print ("Change LNA to", value) + def SetTxLevel(self): + try: + tx_level = self.conf.tx_level[self.band] + except KeyError: + tx_level = self.conf.tx_level[None] # The default + if self.mode[0:3] in ('DGT', 'FDV'): # Digital modes; change power by a percentage + reduc = self.application.digital_tx_level + else: + reduc = self.application.tx_level + level = 1.0 + tx_level * 0.0326 + level *= math.sqrt(reduc / 100.0) # Convert from a power to an amplitude + tx_level = int((level - 1.0) / 0.0326 + 0.5) + if tx_level < 0: + tx_level = 0 + elif tx_level > 255: + tx_level = 255 + self.pc2hermes[4 * 9] = tx_level # C0 index == 0x1001, C1[7:0] Tx level + QS.pc_to_hermes(self.pc2hermes) + if DEBUG: print("Change tx_level to", tx_level) diff -Nru quisk-3.6.18/hermes/quisk_widgets.py quisk-3.7.6/hermes/quisk_widgets.py --- quisk-3.6.18/hermes/quisk_widgets.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/hermes/quisk_widgets.py 2015-07-11 18:36:33.000000000 +0000 @@ -0,0 +1,36 @@ +# Please do not change this widgets module for Quisk. Instead copy +# it to your own quisk_widgets.py and make changes there. +# +# This module is used to add extra widgets to the QUISK screen. + +from __future__ import print_function + +import wx + +class BottomWidgets: # Add extra widgets to the bottom of the screen + def __init__(self, app, hardware, conf, frame, gbs, vertBox): + self.config = conf + self.hardware = hardware + self.application = app + if hardware.hermes_board_id == 0x06: # Hermes-Lite + self.Widgets_0x06(app, hardware, conf, frame, gbs, vertBox) + else: + self.Widgets_dflt(app, hardware, conf, frame, gbs, vertBox) + def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox): + row = 4 # The next available row + b = app.QuiskCheckbutton(frame, self.OnAGC, 'RfAgc') + gbs.Add(b, (row, 0), (1, 2), flag=wx.EXPAND) + init = 10 + sl = app.SliderBoxHH(frame, 'RfLna %d dB', init, -12, 48, self.OnLNA, True) + hardware.ChangeLNA(init) + gbs.Add(sl, (row, 2), (1, 8), flag=wx.EXPAND) + def Widgets_dflt(self, app, hardware, conf, frame, gbs, vertBox): + pass + def OnAGC(self, event): + btn = event.GetEventObject() + value = btn.GetValue() + self.hardware.ChangeAGC(value) + def OnLNA(self, event): + sl = event.GetEventObject() + value = sl.GetValue() + self.hardware.ChangeLNA(value) diff -Nru quisk-3.6.18/hiqsdr/quisk_hardware.py quisk-3.7.6/hiqsdr/quisk_hardware.py --- quisk-3.6.18/hiqsdr/quisk_hardware.py 2014-06-22 13:41:03.000000000 +0000 +++ quisk-3.7.6/hiqsdr/quisk_hardware.py 2015-08-13 12:31:22.000000000 +0000 @@ -14,7 +14,6 @@ class Hardware(BaseHardware): def __init__(self, app, conf): BaseHardware.__init__(self, app, conf) - self.use_sidetone = 1 self.got_udp_status = '' # status from UDP receiver # want_udp_status is a 14-byte string with numbers in little-endian order: # [0:2] 'St' @@ -26,6 +25,8 @@ # 0x02 Enable all other transmit # 0x04 Use the HiQSDR extended IO pins not present in the 2010 QEX ver 1.0 # 0x08 The key is down (software key) + # 0x40 odyssey: Spot button is in use + # 0x80 odyssey: Mic Boost 20dB # [12] Rx control bits # Second stage decimation less one, 1-39, six bits # [13] zero or firmware version number @@ -37,9 +38,12 @@ # [17:22] The remaining five bytes are sent as zero. # Version 1.2 uses the same format as 1.1, but adds the "Qs" command (see below). # Version 1.3 adds features needed by the new quisk_vna.py program: - # [17] This one byte must be zero + # [17] The sidetone volume 0 to 255 # [18:20] This is vna_count, the number of VNA data points; or zero for normal operation - # [20:22] These two bytes mmust be zero + # [20] The CW delay as specified in the config file + # [21] Control bits: + # 0x01 Switch on tx mirror on rx for adaptive predistortion + # [22:24] Noise blanker level # The "Qs" command is a two-byte UDP packet sent to the control port. It returns the hardware status # as the above string, except that the string starts with "Qs" instead of "St". Do not send the "Qs" command @@ -55,13 +59,22 @@ self.tx_control = 0 self.rx_control = 0 self.vna_count = 0 # VNA scan count; MUST be zero for non-VNA operation + self.cw_delay = conf.cw_delay self.index = 0 self.mode = None + self.usingSpot = False self.band = None self.rf_gain = 0 + self.sidetone_volume = 0 # sidetone volume 0 to 255 + self.repeater_freq = None # original repeater output frequency self.HiQSDR_Connector_X1 = 0 self.HiQSDR_Attenuator = 0 self.HiQSDR_Bits = 0 + try: + if conf.radio_sound_mic_boost: + self.tx_control = 0x80 + except: + pass if conf.use_rx_udp == 2: # Set to 2 for the HiQSDR self.rf_gain_labels = ('RF 0 dB', 'RF +10', 'RF -10', 'RF -20', 'RF -30') self.antenna_labels = ('Ant 1', 'Ant 2') @@ -108,30 +121,44 @@ def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None): if vfo_freq != self.vfo_frequency: self.vfo_frequency = vfo_freq - self.rx_phase = int(float(vfo_freq) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF + self.rx_phase = int(float(vfo_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF if tx_freq and tx_freq > 0: self.tx_frequency = tx_freq - tx = tx_freq - self.tx_phase = int(float(tx) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF + self.tx_phase = int(float(tx_freq - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF self.NewUdpStatus() return tx_freq, vfo_freq + def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx + if offset is None: # Return True if frequency change is complete + self.HeartBeat() + return self.want_udp_status == self.got_udp_status + if offset == 0: # Change back to the original frequency + if self.repeater_freq is None: # Frequency was already reset + return self.want_udp_status == self.got_udp_status + self.tx_frequency = self.repeater_freq + self.repeater_freq = None + else: # Shift to repeater input frequency + self.repeater_freq = self.tx_frequency + offset = int(offset * 1000) # Convert kHz to Hz + self.tx_frequency += offset + self.tx_phase = int(float(self.tx_frequency - self.transverter_offset) / self.conf.rx_udp_clock * 2.0**32 + 0.5) & 0xFFFFFFFF + self.NewUdpStatus(True) + return False def ChangeMode(self, mode): # mode is a string: "USB", "AM", etc. self.mode = mode self.tx_control &= ~0x03 # Erase last two bits if self.vna_count: pass + elif self.usingSpot: + self.tx_control |= 0x02 elif mode in ("CWL", "CWU"): self.tx_control |= 0x01 - elif mode in ("USB", "LSB", "AM", "FM"): - self.tx_control |= 0x02 - elif mode[0:4] == 'DGT-': - self.tx_control |= 0x02 - elif mode[0:3] == 'IMD': + else: self.tx_control |= 0x02 self.SetTxLevel() def ChangeBand(self, band): # band is a string: "60", "40", "WWV", etc. + BaseHardware.ChangeBand(self, band) self.band = band self.HiQSDR_Connector_X1 &= ~0x0F # Mask in the last four bits self.HiQSDR_Connector_X1 |= self.conf.HiQSDR_BandDict.get(band, 0) & 0x0F @@ -143,17 +170,18 @@ try: self.tx_level = self.conf.tx_level[self.band] except KeyError: - self.tx_level = self.conf.tx_level[None] # The default - if self.mode[0:4] == 'DGT-': + self.tx_level = self.conf.tx_level[None] # The default + if self.mode[0:3] in ('DGT', 'FDV'): # Digital modes; change power by a percentage reduc = self.application.digital_tx_level else: reduc = self.application.tx_level - if reduc < 100: # reduce power by a percentage - level = 1.0 + self.tx_level * 0.0326 - level *= math.sqrt(reduc / 100.0) # Convert from a power to an amplitude - self.tx_level = int((level - 1.0) / 0.0326 + 0.5) - if self.tx_level < 0: - self.tx_level = 0 + level = 1.0 + self.tx_level * 0.0326 + level *= math.sqrt(reduc / 100.0) # Convert from a power to an amplitude + self.tx_level = int((level - 1.0) / 0.0326 + 0.5) + if self.tx_level < 0: + self.tx_level = 0 + elif self.tx_level > 255: + self.tx_level = 255 self.NewUdpStatus() def OnButtonRfGain(self, event): # The HiQSDR attenuator is five bits: 2, 4, 8, 10, 20 dB @@ -198,6 +226,9 @@ else: self.HiQSDR_Bits &= ~0x01 self.NewUdpStatus() + def ChangeSidetone(self, value): # The sidetone volume changed + self.sidetone_volume = int(value * 255.1) # Change 0.0-1.0 to 0-255 + self.NewUdpStatus() def HeartBeat(self): if self.sndp_active: # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address try: @@ -259,7 +290,16 @@ def GetFirmwareVersion(self): return self.firmware_version def OnSpot(self, level): - pass + # level is -1 for Spot button Off; else the Spot level 0 to 1000. + # The Spot button sets the mode to SSB-equivalent for CW so that the Spot level works. + if level >= 0 and not self.usingSpot: # Spot was turned on + self.usingSpot = True + self.tx_control |= 0x40 + self.ChangeMode(self.mode) + elif level < 0 and self.usingSpot: # Spot was turned off + self.usingSpot = False + self.tx_control &= ~0x40 + self.ChangeMode(self.mode) def OnBtnFDX(self, is_fdx): # Status of FDX button, 0 or 1 if is_fdx: self.HiQSDR_Connector_X1 |= 0x20 # Mask in the FDX bit @@ -307,11 +347,12 @@ s = s + chr(self.HiQSDR_Connector_X1) s = s + chr(self.HiQSDR_Attenuator) s = s + chr(self.HiQSDR_Bits) - s = s + chr(0) else: - s = s + chr(0) * 4 + s = s + chr(0) * 3 + s = s + chr(self.sidetone_volume) s = s + struct.pack("= wavEnd) { - fseek (wavFp, wavStart, SEEK_SET); - pos = wavStart; - } - } - } -} -#endif - #if USE_GET_SIN static void get_sin(complex double * cSamples, int count) { // replace mic samples with a sin wave @@ -313,15 +259,15 @@ return 0; } // check size of dsamples[] and csamples[] buffer - if (count > samples_size) { - samples_size = count * 2; - if (dsamples) - free(dsamples); - if (csamples) - free(csamples); - dsamples = (double *)malloc(samples_size * sizeof(double)); - csamples = (complex double *)malloc(samples_size * sizeof(complex double)); - } + if (count > samples_size) { + samples_size = count * 2; + if (dsamples) + free(dsamples); + if (csamples) + free(csamples); + dsamples = (double *)malloc(samples_size * sizeof(double)); + csamples = (complex double *)malloc(samples_size * sizeof(complex double)); + } // copy to dsamples[], normalize to +/- 1.0 for (i = 0; i < count; i++) dsamples[i] = creal(filtered[i]) / CLIP16; @@ -360,10 +306,10 @@ magn = cabs(csample); if (magn > inMax) inMax = inMax * (1 - time_short) + time_short * magn; - else if(magn > mic_min_level) + else if(magn > mic_agc_level) inMax = inMax * (1 - time_long) + time_long * magn; else - inMax = inMax * (1 - time_long) + time_long * mic_min_level; + inMax = inMax * (1 - time_long) + time_long * mic_agc_level; csample /= inMax; magn /= inMax; #if DEBUG_IO @@ -385,10 +331,10 @@ magn = fabs(dsample); if (magn > inMax) inMax = inMax * (1 - time_short) + time_short * magn; - else if(magn > mic_min_level) + else if(magn > mic_agc_level) inMax = inMax * (1 - time_long) + time_long * magn; else - inMax = inMax * (1 - time_long) + time_long * mic_min_level; + inMax = inMax * (1 - time_long) + time_long * mic_agc_level; dsample /= inMax; magn /= inMax; #if DEBUG_IO @@ -411,7 +357,7 @@ if (is_ssb) { // SSB // FIR bandpass filter; separate into I and Q for (i = 0; i < count; i++) { - csamples[i] = quisk_dC_out(creal(dsamples[i]), &filter2); + csamples[i] = quisk_dC_out(dsamples[i], &filter2); #if DEBUG_IO magn = cabs(csamples[i]); if (magn > Level3) @@ -513,7 +459,7 @@ #endif if (!filtered) { // initialization quisk_filt_dInit(&filter1, quiskMic5Filt48Coefs, sizeof(quiskMic5Filt48Coefs)/sizeof(double)); - quisk_filt_tune(&filter1, 2650.0 / 48000, rxMode != 2); + quisk_filt_tune(&filter1, 2650.0 / 48000, rxMode != 8); return 0; } #if DEBUG_IO @@ -553,6 +499,114 @@ return count; } +static int tx_filter_freedv(complex double * filtered, int count, int encode) +{ // Input samples are creal(filtered), output is filtered. + // This filter is used for digital voice. + int i; + int sample_rate = 8000; + double dtmp, magn, dsample; + static int samples_size = 0; + static double aaa, bbb, ccc, Xmin, Xmax, Ymax; + static double time_long, time_short; + static double x_1=0; + static double inMax=0.3; + static double * dsamples = NULL; + static struct quisk_dFilter filter2, filtDecim; + static struct quisk_cFilter cfiltInterp; +#if DEBUG_IO + double x; + static double peakIn = 0, peakOut2 = 0; // input/output level +#endif + + if (!filtered) { // initialization + quisk_filt_dInit(&filter2, quiskMicFilt8Coefs, sizeof(quiskMicFilt8Coefs)/sizeof(double)); + quisk_filt_tune(&filter2, 1650.0 / sample_rate, rxMode != 12); + quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); + quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); + dtmp = 1.0 / sample_rate; // sample time + time_long = 1.0 - exp(- dtmp / 3.000); + time_short = 1.0 - exp(- dtmp / 0.005); + Ymax = pow(10.0, - 1 / 20.0); // maximum y + Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax + Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression + aaa = 1.0 / (2.0 * (Xmin - Xmax)); // quadratic + bbb = -2.0 * aaa * Xmax; + ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax; + return 0; + } + // check size of dsamples[] buffer + if (count > samples_size) { + samples_size = count * 2; + if (dsamples) + free(dsamples); + dsamples = (double *)malloc(samples_size * sizeof(double)); + } + // copy to dsamples[] + for (i = 0; i < count; i++) + dsamples[i] = creal(filtered[i]) / CLIP16; + // Decimate to 8000 Hz + if (quisk_sound_state.mic_sample_rate != sample_rate) + count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / sample_rate); + // high pass filter for preemphasis: See Radcom, January 2010, page 76. + // quisk_mic_preemphasis == 1 was measured as 6 dB / octave. + // gain at 800 Hz was measured as 0.104672. + for (i = 0; i < count; i++) { + dtmp = dsamples[i]; + dsamples[i] = dtmp - quisk_mic_preemphasis * x_1; + x_1 = dtmp; // delayed sample + dsamples[i] *= 2; // compensate for loss + } + // Measure average peak input audio level and normalize + for (i = 0; i < count; i++) { + dsample = dsamples[i]; + magn = fabs(dsample); + if (magn > inMax) + inMax = inMax * (1 - time_short) + time_short * magn; + else if(magn > mic_agc_level) + inMax = inMax * (1 - time_long) + time_long * magn; + else + inMax = inMax * (1 - time_long) + time_long * mic_agc_level; + dsample /= inMax; + magn /= inMax; + // Audio compression. + dsample *= quisk_mic_clip; + magn *= quisk_mic_clip; + if (magn < Xmin) + dsamples[i] = dsample; + else if (magn > Xmax) + dsamples[i] = copysign(Ymax, dsample); + else + dsamples[i] = copysign(aaa * magn * magn + bbb * magn + ccc, dsample); +#if DEBUG_IO + x = fabs(dsamples[i]); + if (x > peakIn) + peakIn = x; +#endif + dsamples[i] = dsamples[i] * CLIP16; + } + if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation + count = (* pt_quisk_freedv_tx)(filtered, dsamples, count); + // Interpolate up to 48000 + if (MIC_OUT_RATE != sample_rate) + count = quisk_cInterpolate(filtered, count, &cfiltInterp, MIC_OUT_RATE / sample_rate); +#if DEBUG_IO + for (i = 0; i < count; i++) { + magn = cabs(filtered[i]) / CLIP16; + if (magn > peakOut2) + peakOut2 = magn; + } + if (debug_timer == 0) { + printf ("peakIn %10.6lf peakOut2 %10.6lf", peakIn, peakOut2); + if (peakOut2 > 1.0) + printf (" CLIP\n"); + else + printf ("\n"); + peakIn = peakOut2 = 0; + } +#endif + return count; +} + PyObject * quisk_get_tx_filter(PyObject * self, PyObject * args) { // return the TX filter response to display on the graph // This is for debugging. Change quisk.py to call QS.get_tx_filter() instead @@ -644,11 +698,159 @@ return tuple2; } +// Send samples using the Metis-Hermes protocol. A frame is 8 bytes: L/R audio and I/Q mic samples. +// All samples are 2 bytes. The 1032 byte UDP packet contains 63*2 radio sound samples, and 63*2 mic I/Q samples. +// Samples are sent synchronously with the input samples. + +static void quisk_hermes_add_zeros(void) +{ // Add zeros to the buffer + int i, tx_count; + + tx_count = HERMES_TX_BUF_SAMPLES / 2; + hermes_num_samples += tx_count; + for (i = 0; i < tx_count; i++) { + hermes_buf[hermes_write_index++] = 0; + hermes_buf[hermes_write_index++] = 0; + if (hermes_write_index >= HERMES_TX_BUF_SHORTS) + hermes_write_index = 0; + } +} + +void quisk_hermes_tx_add(complex double * cSamples, int tx_count) +{ // Add samples to the Tx buffer + int i; + + if (hermes_started == 0) + return; // do not add samples until Rx starts + if (hermes_num_samples + tx_count >= HERMES_TX_BUF_SAMPLES) { // no more space; throw away half the samples + quisk_udp_mic_error("Tx hermes buffer overflow"); + //printf("Tx hermes buffer overflow\n"); + i = HERMES_TX_BUF_SAMPLES / 2; + hermes_num_samples -= i; + hermes_write_index -= i * 2; + if (hermes_write_index < 0) + hermes_write_index += HERMES_TX_BUF_SHORTS; + } + hermes_num_samples += tx_count; + if (cSamples) { + for (i = 0; i < tx_count; i++) { // Put transmit mic samples into the buffer + hermes_buf[hermes_write_index++] = (short)cimag(cSamples[i]); + hermes_buf[hermes_write_index++] = (short)creal(cSamples[i]); + if (hermes_write_index >= HERMES_TX_BUF_SHORTS) + hermes_write_index = 0; + } + } + else { + for (i = 0; i < tx_count; i++) { // Put zero mic samples into the buffer + hermes_buf[hermes_write_index++] = 0; + hermes_buf[hermes_write_index++] = 0; + if (hermes_write_index >= HERMES_TX_BUF_SHORTS) + hermes_write_index = 0; + } + } +} + +void quisk_hermes_tx_send(void) +{ // Send samples using the Metis-Hermes protocol. This quisk_hermes_tx_send() is called for each block received. + int i, offset, key_down, sent, ratio; + short s; + unsigned char sendbuf[1032]; + unsigned char * pt_buf; + static unsigned int seq = 0; + static unsigned char C0_index = 0; + static int num_blocks = 0; + + if (hermes_started == 0) { // we received some Rx samples + hermes_started = 1; + quisk_hermes_add_zeros(); // fill buffer half full + } + //printf ("Buffer usage %.1f %%\n", 100.0 * hermes_num_samples / HERMES_TX_BUF_SAMPLES); + // Transmit samples as rx samples are received; receive at sample_rate, transmit at 48000 + ratio = quisk_sound_state.sample_rate / 48000; + if (++num_blocks >= ratio) { + num_blocks = 0; + if (hermes_num_samples < 63 * 2) { // Not enough samples to send + //printf("Tx hermes buffer underflow\n"); + quisk_udp_mic_error("Tx hermes buffer underflow"); + quisk_hermes_add_zeros(); + } + hermes_num_samples -= 63 * 2; + sendbuf[0] = 0xEF; + sendbuf[1] = 0xFE; + sendbuf[2] = 0x01; + sendbuf[3] = 0x02; + sendbuf[4] = seq >> 24 & 0xFF; + sendbuf[5] = seq >> 16 & 0xFF; + sendbuf[6] = seq >> 8 & 0xFF; + sendbuf[7] = seq & 0xFF; + seq++; + sendbuf[8] = 0x7F; + sendbuf[9] = 0x7F; + sendbuf[10] = 0x7F; + offset = C0_index * 4; // offset into quisk_pc_to_hermes is C0[7:1] * 4 + if (quisk_is_key_down()) + key_down = 1; + else + key_down = 0; + sendbuf[11] = C0_index << 1 | key_down; // C0 + sendbuf[12] = quisk_pc_to_hermes[offset++]; // C1 + sendbuf[13] = quisk_pc_to_hermes[offset++]; // C2 + sendbuf[14] = quisk_pc_to_hermes[offset++]; // C3 + sendbuf[15] = quisk_pc_to_hermes[offset++]; // C4 + if (++C0_index > 16) + C0_index = 0; + pt_buf = sendbuf + 16; + for (i = 0; i < 63; i++) { // add 63 samples + *pt_buf++ = 0x00; // Left/Right audio sample + *pt_buf++ = 0x00; + *pt_buf++ = 0x00; + *pt_buf++ = 0x00; + s = hermes_buf[hermes_read_index++]; + *pt_buf++ = (s >> 8) & 0xFF; // Two bytes of I + *pt_buf++ = s & 0xFF; + s = hermes_buf[hermes_read_index++]; + *pt_buf++ = (s >> 8) & 0xFF; // Two bytes of Q + *pt_buf++ = s & 0xFF; + if (hermes_read_index >= HERMES_TX_BUF_SHORTS) + hermes_read_index = 0; + } + sendbuf[520] = 0x7F; + sendbuf[521] = 0x7F; + sendbuf[522] = 0x7F; + offset = C0_index * 4; // offset into quisk_pc_to_hermes is C0[7:1] * 4 + sendbuf[523] = C0_index << 1 | key_down; // C0 + sendbuf[524] = quisk_pc_to_hermes[offset++]; // C1 + sendbuf[525] = quisk_pc_to_hermes[offset++]; // C2 + sendbuf[526] = quisk_pc_to_hermes[offset++]; // C3 + sendbuf[527] = quisk_pc_to_hermes[offset++]; // C4 + if (++C0_index > 16) + C0_index = 0; + pt_buf = sendbuf + 528; + for (i = 0; i < 63; i++) { // add 63 samples + *pt_buf++ = 0x00; // Left/Right audio sample + *pt_buf++ = 0x00; + *pt_buf++ = 0x00; + *pt_buf++ = 0x00; + s = hermes_buf[hermes_read_index++]; + *pt_buf++ = (s >> 8) & 0xFF; // Two bytes of I + *pt_buf++ = s & 0xFF; + s = hermes_buf[hermes_read_index++]; + *pt_buf++ = (s >> 8) & 0xFF; // Two bytes of Q + *pt_buf++ = s & 0xFF; + if (hermes_read_index >= HERMES_TX_BUF_SHORTS) + hermes_read_index = 0; + } + sent = send(mic_socket, (char *)sendbuf, 1032, 0); + if (sent != 1032) + quisk_udp_mic_error("Tx UDP socket error in Hermes"); + } +} + // udp_iq has an initial zero followed by the I/Q samples. // The initial zero is sent iff align4 == 1. static void transmit_udp(complex double * cSamples, int count) -{ // Send count samples. Each sample is sent as two shorts (4 bytes) of I/Q data. +{ // Send count samples using the HiQSDR protocol. Each sample is sent as two shorts (4 bytes) of I/Q data. // Transmission is delayed until a whole block of data is available. int i, sent; static short udp_iq[TX_BLOCK_SHORTS + 1] = {0}; @@ -661,6 +863,10 @@ udp_iq[0] = 0; // should not be necessary return; } + if (doTxCorrect) { + for (i = 0; i < count; i++) + cSamples[i] = cSamples[i] * TxCorrectLevel + TxCorrectDc; + } for (i = 0; i < count; i++) { // transmit samples udp_iq[udp_size++] = (short)creal(cSamples[i]); udp_iq[udp_size++] = (short)cimag(cSamples[i]); @@ -682,7 +888,6 @@ for (i = 0; i < count; i++) // transmit a carrier equal to the number of samples cSamples[i] = level * CLIP16; - transmit_udp(cSamples, count); } static void transmit_mic_imd(complex double * cSamples, int count, double level) @@ -705,14 +910,14 @@ v = level * (vector1 + vector2); cSamples[i] = v; } - transmit_udp(cSamples, count); } int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int count) { - int i, sample, maximum, interp, mic_interp; - double d; + int i, sample, maximum, interp, mic_interp, key_down; + double d, ctcss_delta; static struct quisk_cFilter filtInterp={NULL}; + static double ctcss_angle=0; // Microphone sample are input at mic_sample_rate. But after processing, // the output rate is MIC_OUT_RATE. @@ -748,9 +953,6 @@ debug_timer = 0; #endif -#ifdef TEST_TX_WAV_FILE - get_wav(cSamples, count); // Replace mic samples with sound from a WAV file -#endif #if USE_GET_SIN get_sin(cSamples, count); // Replace mic samples with a sin wave #endif @@ -765,7 +967,7 @@ if (sample > maximum) maximum = sample; } - if (maximum > vox_level) { + if (maximum > vox_level) { is_vox = mic_sample_rate / 1000 * timeVOX; // reset timer to maximum } else if(is_vox) { @@ -782,69 +984,96 @@ mic_level = 1; } - if (quisk_is_key_down()) { + // quiskTxHoldState is a state machine to implement a pause for a repeater frequency shift for FM + key_down = quisk_is_key_down(); + if (rxMode == 5 || rxMode == 13) { + switch (quiskTxHoldState) { + case 0: // Never implement any hold + break; + case 1: // Start hold when key goes down + if (key_down) + quiskTxHoldState = 2; + break; + case 2: // Key down hold is in progress; wait until state changes to 3 + break; + case 3: // Hold is released; when key goes up, hold starts again + if ( ! key_down) + quiskTxHoldState = 4; + break; + case 4: // Key up hold is in progress; wait until state changes to 1 + break; + } + } + if (quiskTxHoldState == 2 || quiskTxHoldState == 4) { // don't transmit until the hold is cleared + key_down = 0; + for (i = 0; i < count; i++) + cSamples[i] = 0; + } + if (key_down) { // create transmit I/Q samples #if USE_GET_SIN == 2 transmit_udp(cSamples, count * interp); #else - switch (rxMode) { + if (quiskSpotLevel >= 0) { // Spot is in use + count *= interp; + transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0); + } + else switch (rxMode) { case 2: // LSB case 3: // USB - if (quisk_record_state == PLAYBACK) { + if (quisk_record_state == PLAYBACK) count = tx_filter_digital(cSamples, count, 0.9); // filter samples, minimal processing - transmit_udp(cSamples, count); - } - else if (quiskSpotLevel == 0) { + else count = tx_filter(cSamples, count); // filter samples - transmit_udp(cSamples, count); - } - else { - count *= interp; - transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0); - } break; case 4: // AM if (quisk_record_state != PLAYBACK) // no audio processing for recorded sound count = tx_filter(cSamples, count); for (i = 0; i < count; i++) // transmit (0.5 + ampl/2, 0) cSamples[i] = (creal(cSamples[i]) + CLIP16) * 0.5; - transmit_udp(cSamples, count); break; case 5: // FM if (quisk_record_state != PLAYBACK) // no audio processing for recorded sound count = tx_filter(cSamples, count); + if (quisk_ctcss_freq) { + ctcss_delta = 2.0 * M_PI / MIC_OUT_RATE * quisk_ctcss_freq; + for (i = 0; i < count; i++) { + cSamples[i] = 0.85 * cSamples[i] + 0.15 * CLIP16 * sin (ctcss_angle); + ctcss_angle += ctcss_delta; + if (ctcss_angle >= 2.0 * M_PI) + ctcss_angle -= 2.0 * M_PI; + } + } for (i = 0; i < count; i++) { // this is phase modulation == FM and 6 dB /octave preemphasis cSamples[i] = CLIP16 * cexp(I * creal(cSamples[i]) / CLIP16 * modulation_index); } - transmit_udp(cSamples, count); - break; - case 7: // digital modes + break; + case 7: // external digital modes case 8: case 9: - if (quiskSpotLevel == 0) { - count = tx_filter_digital(cSamples, count, 1.0); // filter samples, minimal processing - transmit_udp(cSamples, count); - } - else { - count *= interp; - transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0); - } - break; - case 10: // transmit IMD 2-tone test - count *= interp; - transmit_mic_imd(cSamples, count, 1.0); + case 13: + count = tx_filter_digital(cSamples, count, 1.0); // filter samples, minimal processing break; - case 11: + case 10: // transmit IMD 2-tone test count *= interp; - transmit_mic_imd(cSamples, count, 1.0 / sqrt(2.0)); + transmit_mic_imd(cSamples, count, quiskImdLevel / 1000.0); break; + case 11: // FDV case 12: - count *= interp; - transmit_mic_imd(cSamples, count, 0.5); + count = tx_filter_freedv(cSamples, count, 1); break; } #endif } - else if (quisk_record_state == RECORD_MIC) { + if (quisk_use_rx_udp == 10) { // Send mic samples when key is up or down + if (key_down) + quisk_hermes_tx_add(cSamples, count); + else + quisk_hermes_tx_add(NULL, count); + } + else if (quisk_use_rx_udp && key_down) { // Send mic samples to UDP when key is down + transmit_udp(cSamples, count); + } + if (quisk_record_state == RECORD_MIC) { switch (rxMode) { case 2: // LSB case 3: // USB @@ -855,7 +1084,11 @@ break; case 5: // FM count = tx_filter(cSamples, count); - break; + break; + case 11: // FDV + case 12: + count = tx_filter_freedv(cSamples, count, 0); + break; default: for (i = 0; i < count; i++) cSamples[i] = 0; @@ -896,6 +1129,30 @@ return Py_None; } +PyObject * quisk_set_udp_tx_correct(PyObject * self, PyObject * args) // Called from GUI thread +{ + double DcI, DcQ, level; + + if (!PyArg_ParseTuple (args, "ddd", &DcI, &DcQ, &level)) + return NULL; + if (DcI == 0 && DcQ == 0 && level == 1.0){ + doTxCorrect = 0; + } + else { + doTxCorrect = 1; + TxCorrectDc = (DcI + I * DcQ) * CLIP16; + DcI = fabs(DcI); + DcQ = fabs(DcQ); + if (DcI > DcQ) + TxCorrectLevel = 1.0 - DcI; + else + TxCorrectLevel = 1.0 - DcQ; + TxCorrectLevel *= level; + } + Py_INCREF (Py_None); + return Py_None; +} + PyObject * quisk_is_vox(PyObject * self, PyObject * args) { /* return the VOX state */ if (!PyArg_ParseTuple (args, "")) @@ -934,6 +1191,7 @@ #endif modulation_index = QuiskGetConfigDouble("modulation_index", 1.6); + mic_agc_level = QuiskGetConfigDouble("mic_agc_level", 0.1); if (quisk_sound_state.tx_audio_port == 0x553B) align4 = 0; // Using old port: data starts at byte 42. else @@ -976,9 +1234,6 @@ { tx_filter(NULL, 0); tx_filter_digital(NULL, 0, 0.0); + tx_filter_freedv(NULL, 0, 0); transmit_udp(NULL, 0); -#ifdef TEST_TX_WAV_FILE - if (!wavFp) // convenient place to open file - open_wav(); -#endif } diff -Nru quisk-3.6.18/n2adr/conf1.py quisk-3.7.6/n2adr/conf1.py --- quisk-3.6.18/n2adr/conf1.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/n2adr/conf1.py 2015-08-10 16:54:28.000000000 +0000 @@ -0,0 +1,11 @@ +from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path + +name_of_sound_play = n2adr_sound_usb_play +microphone_name = n2adr_sound_usb_mic +digital_input_name = "" +digital_output_name = "" + +use_rx_udp = 1 # Get ADC samples from UDP +rx_udp_ip = "192.168.1.196" # Sample source IP address +rx_udp_port = 0xBC77 # Sample source UDP port +graph_width = 0.80 diff -Nru quisk-3.6.18/n2adr/conf2.py quisk-3.7.6/n2adr/conf2.py --- quisk-3.6.18/n2adr/conf2.py 2013-10-15 17:14:30.000000000 +0000 +++ quisk-3.7.6/n2adr/conf2.py 2015-08-04 11:13:40.000000000 +0000 @@ -1,18 +1,18 @@ -# This is a second config file that I use to test various hardware configurations. - -from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic -from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path - -name_of_sound_play = n2adr_sound_usb_play -name_of_sound_capt = n2adr_sound_pc_capt - -sdriq_name = "/dev/ttyUSB1" # Name of the SDR-IQ device to open - -default_screen = 'WFall' -waterfall_y_scale = 80 -waterfall_y_zero = 40 -waterfall_graph_y_scale = 40 -waterfall_graph_y_zero = 90 -waterfall_graph_size = 160 -display_fraction = 1.00 # The edges of the full bandwidth are not valid - +# This is a second config file that I use to test various hardware configurations. + +from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic +from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path + +name_of_sound_play = n2adr_sound_usb_play +name_of_sound_capt = n2adr_sound_pc_capt + +sdriq_name = "/dev/ttyUSB1" # Name of the SDR-IQ device to open + +default_screen = 'WFall' +waterfall_y_scale = 80 +waterfall_y_zero = 40 +waterfall_graph_y_scale = 40 +waterfall_graph_y_zero = 90 +waterfall_graph_size = 160 +display_fraction = 1.00 # The edges of the full bandwidth are not valid + diff -Nru quisk-3.6.18/n2adr/conf3A.py quisk-3.7.6/n2adr/conf3A.py --- quisk-3.6.18/n2adr/conf3A.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/n2adr/conf3A.py 2015-08-25 17:41:57.000000000 +0000 @@ -0,0 +1,3 @@ +from n2adr import conf3 + +microphone_name = "" diff -Nru quisk-3.6.18/n2adr/conf3.py quisk-3.7.6/n2adr/conf3.py --- quisk-3.6.18/n2adr/conf3.py 2013-07-06 18:37:04.000000000 +0000 +++ quisk-3.7.6/n2adr/conf3.py 2015-08-16 16:04:26.000000000 +0000 @@ -1 +1,11 @@ -# Not used +from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path + +name_of_sound_play = n2adr_sound_usb_play +microphone_name = n2adr_sound_usb_mic + +rx_udp_clock = 73728000 - 102 +rx_udp_ip = "192.168.1.213" # Sample source IP address "" for DHCP + +do_repeater_offset = True +#bandTransverterOffset = {'10' : 300000} +spot_button_keys_tx = True diff -Nru quisk-3.6.18/n2adr/conf4.py quisk-3.7.6/n2adr/conf4.py --- quisk-3.6.18/n2adr/conf4.py 2013-10-15 17:15:02.000000000 +0000 +++ quisk-3.7.6/n2adr/conf4.py 2015-08-04 11:14:13.000000000 +0000 @@ -1,39 +1,39 @@ -# This is a config file to test the microphone by sending microphone Tx playback to the audio out. -# Set the frequency to zero, and press FDX and PTT. -# Set these values for DEBUG_MIC in sound.c: -# 0: Normal FFT. -# 1: Send filtered Tx audio to the FFT. -# 2: Send mic playback to the FFT. -# 3: Send unfiltered mono mic audio to the FFT. - -import sys -from quisk_hardware_model import Hardware as BaseHardware -import _quisk as QS - -from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic -from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path -from n2adr.quisk_conf import mixer_settings - -name_of_sound_capt = n2adr_sound_pc_capt -name_of_sound_play = '' -microphone_name = n2adr_sound_usb_mic -name_of_mic_play = n2adr_sound_usb_play - -graph_y_scale = 160 - -mic_sample_rate = 48000 - -sample_rate = 48000 -mic_playback_rate = 48000 -mic_out_volume = 0.6 -add_fdx_button = 1 - -class Hardware(BaseHardware): - def __init__(self, app, conf): - BaseHardware.__init__(self, app, conf) - self.use_sidetone = 1 - def OnButtonPTT(self, event): - if event.GetEventObject().GetValue(): - QS.set_key_down(1) - else: - QS.set_key_down(0) +# This is a config file to test the microphone by sending microphone Tx playback to the audio out. +# Set the frequency to zero, and press FDX and PTT. +# Set these values for DEBUG_MIC in sound.c: +# 0: Normal FFT. +# 1: Send filtered Tx audio to the FFT. +# 2: Send mic playback to the FFT. +# 3: Send unfiltered mono mic audio to the FFT. + +import sys +from quisk_hardware_model import Hardware as BaseHardware +import _quisk as QS + +from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic +from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path +from n2adr.quisk_conf import mixer_settings + +name_of_sound_capt = n2adr_sound_pc_capt +name_of_sound_play = '' +microphone_name = n2adr_sound_usb_mic +name_of_mic_play = n2adr_sound_usb_play + +graph_y_scale = 160 + +mic_sample_rate = 48000 + +sample_rate = 48000 +mic_playback_rate = 48000 +mic_out_volume = 0.6 +add_fdx_button = 1 + +class Hardware(BaseHardware): + def __init__(self, app, conf): + BaseHardware.__init__(self, app, conf) + self.use_sidetone = 1 + def OnButtonPTT(self, event): + if event.GetEventObject().GetValue(): + QS.set_key_down(1) + else: + QS.set_key_down(0) diff -Nru quisk-3.6.18/n2adr/conf5.py quisk-3.7.6/n2adr/conf5.py --- quisk-3.6.18/n2adr/conf5.py 2013-01-16 21:31:09.000000000 +0000 +++ quisk-3.7.6/n2adr/conf5.py 2015-08-04 11:14:27.000000000 +0000 @@ -1,8 +1,13 @@ -import sys - -#hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too. -hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port. -if sys.platform != "win32": - digital_input_name = 'hw:Loopback,0' - digital_output_name = digital_input_name - +import sys + +#hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too. +hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port. +if sys.platform == "win32": + pass +elif 0: + digital_input_name = 'pulse' + digital_output_name ='' +else: + digital_input_name = 'hw:Loopback,0' + digital_output_name = digital_input_name + diff -Nru quisk-3.6.18/n2adr/conf6.py quisk-3.7.6/n2adr/conf6.py --- quisk-3.6.18/n2adr/conf6.py 2014-02-18 17:34:02.000000000 +0000 +++ quisk-3.7.6/n2adr/conf6.py 2015-08-06 16:39:41.000000000 +0000 @@ -1,26 +1,29 @@ -# This is a second config file to test the softrock radios. - -from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic -from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path -from n2adr.quisk_conf import mixer_settings - -name_of_sound_capt = n2adr_sound_pc_capt -name_of_sound_play = n2adr_sound_usb_play - -default_screen = 'WFall' -waterfall_y_scale = 80 -waterfall_y_zero = 40 -waterfall_graph_y_scale = 40 -waterfall_graph_y_zero = 90 -waterfall_graph_size = 160 -display_fraction = 1.00 - -sample_rate = 96000 -playback_rate = 48000 -key_poll_msec = 5 - -# Microphone capture and playback: -microphone_name = n2adr_sound_usb_mic -name_of_mic_play = n2adr_sound_pc_play -mic_playback_rate = sample_rate -mic_out_volume = 0.6 +# This is a second config file to test the softrock radios. + +from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic +from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path +from n2adr.quisk_conf import mixer_settings + +name_of_sound_capt = n2adr_sound_pc_capt +name_of_sound_play = n2adr_sound_usb_play + +default_screen = 'WFall' +waterfall_y_scale = 80 +waterfall_y_zero = 40 +waterfall_graph_y_scale = 40 +waterfall_graph_y_zero = 90 +waterfall_graph_size = 160 +display_fraction = 1.00 + +sample_rate = 48000 +playback_rate = 48000 +key_poll_msec = 5 + +do_repeater_offset = True +#bandTransverterOffset = {'40' : 300000} + +# Microphone capture and playback: +microphone_name = n2adr_sound_usb_mic +name_of_mic_play = n2adr_sound_pc_play +mic_playback_rate = sample_rate +mic_out_volume = 0.6 diff -Nru quisk-3.6.18/n2adr/conf7.py quisk-3.7.6/n2adr/conf7.py --- quisk-3.6.18/n2adr/conf7.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/n2adr/conf7.py 2015-08-04 11:14:52.000000000 +0000 @@ -0,0 +1,12 @@ +import sys + +if sys.platform == "win32": + digital_output_name = 'CABLE-A Input' +elif 0: + digital_input_name = 'pulse' + digital_output_name ='' +else: + digital_output_name = 'hw:Loopback,0' + +name_of_sound_play = '' +microphone_name = '' diff -Nru quisk-3.6.18/n2adr/quisk_conf.py quisk-3.7.6/n2adr/quisk_conf.py --- quisk-3.6.18/n2adr/quisk_conf.py 2014-05-26 18:26:40.000000000 +0000 +++ quisk-3.7.6/n2adr/quisk_conf.py 2015-09-15 20:30:05.000000000 +0000 @@ -7,11 +7,17 @@ from n2adr import quisk_hardware from n2adr import quisk_widgets +try: + index = sys.argv.index('--local') + local_option = sys.argv[index + 1] +except: + local_option = '' + if sys.platform == "win32": n2adr_sound_pc_capt = 'Line In (Realtek High Definition Audio)' n2adr_sound_pc_play = 'Speakers (Realtek High Definition Audio)' n2adr_sound_usb_play = 'Primary' - n2adr_sound_usb_mic = 'Line In (USB Multi-Channel' + n2adr_sound_usb_mic = 'Primary' latency_millisecs = 150 data_poll_usec = 20000 favorites_file_path = "C:/pub/quisk_favorites.txt" @@ -21,6 +27,14 @@ latency_millisecs = 150 data_poll_usec = 5000 favorites_file_path = "/home/jim/pub/quisk_favorites.txt" +elif 0: # pulseaudio devices + n2adr_sound_pc_capt = 'pulse:Built-in' + n2adr_sound_pc_play = 'pulse:Built-in' + n2adr_sound_usb_play = 'pulse:USB Sound Device' + n2adr_sound_usb_mic = 'pulse:USB Sound Device' + latency_millisecs = 150 + data_poll_usec = 5000 + favorites_file_path = "/home/jim/pub/quisk_favorites.txt" else: # alsa devices n2adr_sound_pc_capt = 'alsa:ALC888-VD' n2adr_sound_pc_play = 'alsa:ALC888-VD' @@ -31,13 +45,24 @@ favorites_file_path = "/home/jim/pub/quisk_favorites.txt" name_of_sound_capt = "" -name_of_sound_play = n2adr_sound_usb_play microphone_name = n2adr_sound_usb_mic +if local_option == "Q2H": + name_of_sound_play = "" +else: + name_of_sound_play = n2adr_sound_usb_play + +#name_of_sound_play = "pulse:alsa_output.usb" +#microphone_name = "" mic_sample_rate = 48000 playback_rate = 48000 agc_off_gain = 80 tx_level = {None:100, '60':100, '40':110, '30':100, '20':90, '15':150, '12':170, '10':130} +digital_tx_level = 300 +do_repeater_offset = True +#bandTransverterOffset = {'10' : 300000} +#spot_button_keys_tx = True +#graph_width = 1.0 default_screen = 'WFall' waterfall_y_scale = 80 @@ -46,6 +71,11 @@ waterfall_graph_y_zero = 90 waterfall_graph_size = 160 +#radio_sound_ip = "192.168.1.196" # IP address of play device +#radio_sound_port = 12345 # port number for audio +#radio_sound_nsamples = 360 # number of samples for each block; maximum 367 +#name_of_sound_play = '' # do not send audio to a soundcard (optional) + station_display_lines = 1 # DX cluster telent login data, thanks to DJ4CM. dxClHost = '' @@ -55,6 +85,8 @@ add_imd_button = 1 add_fdx_button = 1 +use_sidetone = 1 +split_rxtx = 1 use_rx_udp = 1 # Get ADC samples from UDP rx_udp_ip = "192.168.1.196" # Sample source IP address @@ -67,13 +99,13 @@ tx_ip = "192.168.1.196" tx_audio_port = 0xBC79 mic_out_volume = 0.8 +freedv_tx_msg = "Jim N2ADR in NJ, USA. " -mixer_settings = [ - #(microphone_name, 2, 0.80), # numid of microphone volume control, volume 0.0 to 1.0; - #(microphone_name, 1, 1.0) # numid of capture on/off control, turn on with 1.0; - #(microphone_name, 8, 1.00), # numid of microphone volume control, volume 0.0 to 1.0; - #(microphone_name, 7, 1.0) # numid of capture on/off control, turn on with 1.0; - (microphone_name, 14, 1), # capture from line - (microphone_name, 9, 1), # line capture ON - (microphone_name, 10, 0.65), # line capture level +mixer_settings = [ # These are for CM106 like sound device + (microphone_name, 16, 1), # PCM capture from line + (microphone_name, 14, 0), # PCM capture switch + (microphone_name, 11, 1), # line capture switch + (microphone_name, 12, 0.70), # line capture volume + (microphone_name, 3, 0), # mic playback switch + (microphone_name, 9, 0), # mic capture switch ] diff -Nru quisk-3.6.18/n2adr/quisk_hardware.py quisk-3.7.6/n2adr/quisk_hardware.py --- quisk-3.6.18/n2adr/quisk_hardware.py 2013-11-15 19:26:09.000000000 +0000 +++ quisk-3.7.6/n2adr/quisk_hardware.py 2015-08-16 16:18:19.000000000 +0000 @@ -8,7 +8,6 @@ class Hardware(BaseHw): def __init__(self, app, conf): BaseHw.__init__(self, app, conf) - self.use_sidetone = 1 self.vfo_frequency = 0 # current vfo frequency self.rf_gain_labels = ('RF 0 dB', 'RF +16', 'RF -20', 'RF -10') self.rf_gain = 0 # Preamp or attenuation in dB; changed via app.Hardware @@ -16,11 +15,13 @@ self.anttuner = station_hardware.AntennaTuner(app, conf) # Control the antenna tuner self.lpfilter = station_hardware.LowPassFilter(app, conf) # Control LP filter box self.hpfilter = station_hardware.HighPassFilter(app, conf) # Control HP filter box + self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box def open(self): self.anttuner.open() return BaseHw.open(self) def close(self): self.anttuner.close() + self.controlbox.close() return BaseHw.close(self) def OnAntTuner(self, text): # One of the tuner buttons was pressed self.anttuner.OnAntTuner(text) @@ -31,7 +32,7 @@ self.lpfilter.SetTxFreq(tx_freq) self.hpfilter.SetTxFreq(tx_freq) def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None): - if source == 'MouseBtn1' and self.application.mode in ('LSB', 'USB', 'AM', 'FM'): + if source == 'MouseBtn1' and self.application.mode in ('LSB', 'USB', 'AM', 'FM', 'FDV-U', 'FDV-L'): tx_freq = (tx_freq + 500) // 1000 * 1000 self.ChangeFilterFrequency(tx_freq) return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event) @@ -47,8 +48,10 @@ self.anttuner.HeartBeat() self.lpfilter.HeartBeat() self.hpfilter.HeartBeat() + self.controlbox.HeartBeat() return BaseHw.HeartBeat(self) def OnSpot(self, level): + # level is -1 for Spot button Off; else the Spot level 0 to 1000. self.anttuner.OnSpot(level) return BaseHw.OnSpot(self, level) def OnButtonRfGain(self, event): @@ -61,3 +64,6 @@ self.correct_smeter = 20.5 self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant + def OnButtonPTT(self, event): + self.controlbox.OnButtonPTT(event) + return BaseHw.OnButtonPTT(self, event) diff -Nru quisk-3.6.18/n2adr/quisk_widgets.py quisk-3.7.6/n2adr/quisk_widgets.py --- quisk-3.6.18/n2adr/quisk_widgets.py 2013-04-14 20:03:34.000000000 +0000 +++ quisk-3.7.6/n2adr/quisk_widgets.py 2015-08-25 20:42:10.000000000 +0000 @@ -13,7 +13,7 @@ self.config = conf self.hardware = hardware self.application = app - row = 4 # The next available row + row = app.widget_row # The next available row b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1')) bw, bh = b.GetMinSize() gbs.Add(b, (row, 0), (1, 2), flag=wx.EXPAND) diff -Nru quisk-3.6.18/n2adr/startup.py quisk-3.7.6/n2adr/startup.py --- quisk-3.6.18/n2adr/startup.py 2013-07-27 13:11:23.000000000 +0000 +++ quisk-3.7.6/n2adr/startup.py 2015-08-25 17:43:27.000000000 +0000 @@ -1,83 +1,92 @@ -#! /usr/bin/python - -# All QUISK software is Copyright (C) 2006-2011 by James C. Ahlstrom. -# This free software is licensed for use under the GNU General Public -# License (GPL), see http://www.opensource.org. -# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!! - -"Select the desired hardware, and start Quisk" - -import sys, wx, subprocess, os - -Choices = [ -(' My Transceiver', 'n2adr/quisk_conf.py', ''), -(' Softrock Rx Ensemble', 'softrock/conf_rx_ensemble2.py', 'n2adr/conf2.py'), -(' Softrock Rx/Tx Ensemble', 'softrock/conf_rx_tx_ensemble.py', 'n2adr/conf6.py'), -(' Plain Sound Card, Rx only', 'n2adr/conf2.py', ''), -(' Test microphone sound', 'n2adr/conf4.py', ''), -(' SDR-IQ, receive only, antenna to RF input', 'quisk_conf_sdriq.py', 'n2adr/conf2.py'), -(' AOR AR8600 with IF to my hardware', 'n2adr/quisk_conf_8600.py', ''), -(' AOR AR8600 with IF to SDR-IQ', 'quisk_conf_sdr8600.py', 'n2adr/conf2.py'), -(' Fldigi with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf5.py'), -] - -if sys.platform == 'win32': - os.chdir('C:\\pub\\quisk') - exe = "C:\\python27\\pythonw.exe" -else: - os.chdir('/home/jim/pub/quisk') - exe = "/usr/bin/python" - -class ListBoxFrame(wx.Frame): - def __init__(self): - wx.Frame.__init__(self, None, -1, 'Select Hardware') - font = wx.Font(14, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL) - self.SetFont(font) - charx = self.GetCharWidth() - chary = self.GetCharHeight() - width = 0 - height = chary * 2 - tlist = [] - for txt, conf1, conf2 in Choices: - text = "%s, %s" % (txt, conf1) - if conf2: - text = "%s, %s" % (text, conf2) - tlist.append(text) - w, h = self.GetTextExtent(text) - width = max(width, w) - height += h - width += 3 * chary - lb = wx.ListBox(self, -1, (0, 0), (width, height), tlist, wx.LB_SINGLE) - lb.SetSelection(0) - lb.SetFont(font) - lb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDClick, lb) - lb.Bind(wx.EVT_KEY_DOWN, self.OnChar) - self.SetClientSize((width, height)) - def OnDClick(self, event): - lb = event.GetEventObject() - index = lb.GetSelection() - text, conf1, conf2 = Choices[index] - cmd = [exe, 'quisk.py', '-c', conf1] - if conf2: - cmd = cmd + ['--config2', conf2] - subprocess.Popen(cmd) - self.Destroy() - def OnChar(self, event): - if event.GetKeyCode() == 13: - self.OnDClick(event) - else: - event.Skip() - -class App(wx.App): - def __init__(self): - if sys.stdout.isatty(): - wx.App.__init__(self, redirect=False) - else: - wx.App.__init__(self, redirect=True) - def OnInit(self): - frame = ListBoxFrame() - frame.Show() - return True - -app = App() -app.MainLoop() +#! /usr/bin/python + +# All QUISK software is Copyright (C) 2006-2011 by James C. Ahlstrom. +# This free software is licensed for use under the GNU General Public +# License (GPL), see http://www.opensource.org. +# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!! + +"Select the desired hardware, and start Quisk" + +import sys, wx, subprocess, os + +Choices = [ +(' My Transceiver', 'n2adr/quisk_conf.py', ''), +(' VHF/UHF Receiver', 'n2adr/uhfrx_conf.py', ''), +(' Softrock Rx Ensemble', 'softrock/conf_rx_ensemble2.py', 'n2adr/conf2.py'), +(' Softrock Rx/Tx Ensemble', 'softrock/conf_rx_tx_ensemble.py', 'n2adr/conf6.py'), +(' Plain Sound Card, Rx only', 'n2adr/conf2.py', ''), +(' Test microphone sound', 'n2adr/conf4.py', ''), +(' SDR-IQ, receive only, antenna to RF input', 'quisk_conf_sdriq.py', 'n2adr/conf2.py'), +(' AOR AR8600 with IF to my hardware', 'n2adr/quisk_conf_8600.py', ''), +(' AOR AR8600 with IF to SDR-IQ', 'quisk_conf_sdr8600.py', 'n2adr/conf2.py'), +(' Fldigi with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf5.py'), +(' Freedv.org Rx with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf7.py'), +(' Hermes-Lite', 'hermes/quisk_conf.py', 'n2adr/conf3.py'), +(' Odyssey', 'odyssey/quisk_conf.py', 'n2adr/conf1.py'), +(' My Transceiver to Hermes-Lite', 'Quisk2Hermes', ''), +] + +if sys.platform == 'win32': + os.chdir('C:\\pub\\quisk') + exe = "C:\\python27\\pythonw.exe" +else: + os.chdir('/home/jim/pub/quisk') + exe = "/usr/bin/python" + +class ListBoxFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Select Hardware') + font = wx.Font(14, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL) + self.SetFont(font) + charx = self.GetCharWidth() + chary = self.GetCharHeight() + width = 0 + height = chary * 2 + tlist = [] + for txt, conf1, conf2 in Choices: + text = "%s, %s" % (txt, conf1) + if conf2: + text = "%s, %s" % (text, conf2) + tlist.append(text) + w, h = self.GetTextExtent(text) + width = max(width, w) + height += h + width += 3 * chary + lb = wx.ListBox(self, -1, (0, 0), (width, height), tlist, wx.LB_SINGLE) + lb.SetSelection(0) + lb.SetFont(font) + lb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDClick, lb) + lb.Bind(wx.EVT_KEY_DOWN, self.OnChar) + self.SetClientSize((width, height)) + def OnDClick(self, event): + lb = event.GetEventObject() + index = lb.GetSelection() + text, conf1, conf2 = Choices[index] + if conf1 == "Quisk2Hermes": + subprocess.Popen([exe, 'quisk.py', '-c', 'n2adr/quisk_conf.py', '--local', 'Q2H']) + subprocess.Popen([exe, 'quisk.py', '-c', 'hermes/quisk_conf.py', '--config2', 'n2adr/conf3A.py', '--local', 'Q2H']) + else: + cmd = [exe, 'quisk.py', '-c', conf1] + if conf2: + cmd = cmd + ['--config2', conf2] + subprocess.Popen(cmd) + self.Destroy() + def OnChar(self, event): + if event.GetKeyCode() == 13: + self.OnDClick(event) + else: + event.Skip() + +class App(wx.App): + def __init__(self): + if sys.stdout.isatty(): + wx.App.__init__(self, redirect=False) + else: + wx.App.__init__(self, redirect=True) + def OnInit(self): + frame = ListBoxFrame() + frame.Show() + return True + +app = App() +app.MainLoop() diff -Nru quisk-3.6.18/n2adr/station_hardware.py quisk-3.7.6/n2adr/station_hardware.py --- quisk-3.6.18/n2adr/station_hardware.py 2014-01-01 16:46:55.000000000 +0000 +++ quisk-3.7.6/n2adr/station_hardware.py 2015-08-16 16:23:04.000000000 +0000 @@ -13,6 +13,52 @@ gatewayTime = 0 # time of last gateway command gatewayLimit = 0.2 # minimum time between gateway commands +class ControlBox: # Control my station control box + address = ('192.168.1.194', 0x3A00 + 64) + def __init__(self, app, conf): + self.application = app # Application instance (to provide attributes) + self.conf = conf # Config file module + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setblocking(0) + self.socket.connect(self.address) + self.have_data = 'C\000' + self.want_data = self.have_data + self.timer = 0 + def close(self): + self.want_data = 'C\000' # raise key if down + if self.have_data != self.want_data: + self.socket.send(self.want_data) + time.sleep(0.1) + self.socket.send(self.want_data) + def OnButtonPTT(self, event): + btn = event.GetEventObject() + if btn.GetValue(): # Turn the software key bit on or off + self.want_data = 'C\001' + else: + self.want_data = 'C\000' + def HeartBeat(self): + global gatewayTime + if not self.socket: + return + try: # The control box echoes its commands + self.have_data = self.socket.recv(50) + except socket.error: + pass + except socket.timeout: + pass + if self.have_data != self.want_data and time.time() - gatewayTime > gatewayLimit: + gatewayTime = time.time() + if self.timer <= 10: + self.timer += 1 + if self.timer == 10: + print ('Control box error') + try: + self.socket.send(self.want_data) + except socket.error: + pass + except socket.timeout: + pass + class LowPassFilter: # Control my low pass filter box address = ('192.168.1.194', 0x3A00 + 39) # Filters are numbered 1 thru 8 for bands: 80, 15, 60, 40, 30, 20, 17, short @@ -298,6 +344,7 @@ def close(self): pass def OnSpot(self, level): + # level is -1 for Spot button Off; else the Spot level 0 to 1000. pass def ChangeBand(self, band): pass @@ -777,9 +824,9 @@ def ChangeBand(self, band): pass ##self.ReqSetFreq(self.tx_freq) def OnSpot(self, level): - # level 0 == OFF, else the level 10 to 1000 + # level is -1 for Spot button Off; else the Spot level 0 to 1000. if self.serial: - if level == 0: + if level < 0: self.live_update = 0 elif not self.live_update: self.live_update = 1 diff -Nru quisk-3.6.18/n2adr/uhfrx_conf.py quisk-3.7.6/n2adr/uhfrx_conf.py --- quisk-3.6.18/n2adr/uhfrx_conf.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/n2adr/uhfrx_conf.py 2015-05-09 20:59:43.000000000 +0000 @@ -0,0 +1,516 @@ +# This is the config file for the VHF/UHF receiver and transmitter. + +from __future__ import print_function + +import sys, struct, socket, traceback + +from quisk_hardware_model import Hardware as BaseHardware +from n2adr import uhfrx_widgets as quisk_widgets +import _quisk as QS + +DEBUG = 0 +if sys.platform == "win32": + n2adr_sound_pc_capt = 'Line In (Realtek High Definition Audio)' + n2adr_sound_pc_play = 'Speakers (Realtek High Definition Audio)' + n2adr_sound_usb_play = 'Primary' + n2adr_sound_usb_mic = 'Primary' + latency_millisecs = 150 + data_poll_usec = 20000 + favorites_file_path = "C:/pub/quisk_favorites.txt" +elif 0: # portaudio devices + name_of_sound_play = 'portaudio:CODEC USB' + microphone_name = "portaudio:AK5370" + latency_millisecs = 150 + data_poll_usec = 5000 + favorites_file_path = "/home/jim/pub/quisk_favorites.txt" +else: # alsa devices + n2adr_sound_pc_capt = 'alsa:ALC888-VD' + n2adr_sound_pc_play = 'alsa:ALC888-VD' + n2adr_sound_usb_play = 'alsa:USB Sound Device' + n2adr_sound_usb_mic = 'alsa:USB Sound Device' + latency_millisecs = 150 + data_poll_usec = 5000 + favorites_file_path = "/home/jim/pub/quisk_favorites.txt" + +name_of_sound_capt = "" +name_of_sound_play = n2adr_sound_usb_play +microphone_name = n2adr_sound_usb_mic + +playback_rate = 48000 +agc_off_gain = 80 +do_repeater_offset = True + +station_display_lines = 1 +# DX cluster telent login data, thanks to DJ4CM. +dxClHost = '' +#dxClHost = 'dxc.w8wts.net' +dxClPort = 7373 +user_call_sign = 'n2adr' + +bandLabels = ['6', '2', '1.25', '70cm', '33cm', '23cm', 'WWV'] +bandState['WWV'] = (19990000, 10000, 'AM') +BandEdge['WWV'] = (19500000, 20500000) + +use_rx_udp = 17 # Get ADC samples from UDP +rx_udp_ip = "192.168.1.199" # Sample source IP address +rx_udp_port = 0xAA53 # Sample source UDP port +rx_clock38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal +rx_udp_clock = rx_clock38 * 32 / 2 / 9 # ADC sample rate in Hertz +rx_udp_clock_nominal = 69120000 # rate to display +sample_rate = 96000 # 96, 192, 384, 768, 1152 (for 69120/3/10) +display_fraction = 1.00 +fft_size_multiplier = 16 +tx_ip = "192.168.1.201" +tx_audio_port = 0xBC79 +tx_clock80 = 80000000 + 14 +add_imd_button = 1 +add_fdx_button = 1 +CorrectTxDc = { +'23cm':(1270.0, 0.167081, 0.150557), +'2':(146.0, 0.018772, 0.038658), +'33cm':(915.0, 0.140150, 0.051967), +'6':(52.0, 0.020590, 0.024557), +'70cm':(435.0, 0.004495, 0.096879), +'1.25':(223.5, 0.042958, 0.055212), +} + +class Adf4351: # class to hold adf4351 attributes + def __init__(self, receiver, clock, r_counter): + self.receiver = receiver + self.clock = clock + self.r_counter = r_counter + self.int_mode = 1 # integer one, fractional zero + self.band_sel_clock_div = 40 + self.aux_rf_out = 0b000 # enable 1/0, power 00 to 11 + self.frac_value = 0 + self.modulus = 23 + self.changed = 0 + +class Hardware(BaseHardware): + def __init__(self, app, conf): + BaseHardware.__init__(self, app, conf) + self.use_sidetone = 0 + self.vfo_frequency = 52000000 + self.vfo_sample_rate = conf.sample_rate + self.vfo_test = 0 # JIM + self.tx_frequency = 0 + self.firmware_version = None # firmware version is initially unknown + self.rx_udp_socket = None + self.tx_udp_socket = None + self.got_rx_udp_status = '' + self.got_tx_udp_status = '' + self.band = '' + self.rx_phase0 = self.rx_phase1 = 0 + self.tx_phase = 0 + self.button_PTT = 0 + self.mode_is_cw = 0 + self.scan_enable = 0 + self.scan_blocks = 0 + self.scan_samples = 1 + self.scan_phase = 0 + self.fft_scan_valid = 0.84 + self.Rx4351 = Adf4351(True, conf.rx_clock38, 8) + self.Tx4351 = Adf4351(False, 10700000, 2) + self.Tx4351.aux_rf_out = 0b000 # enable aux RF out 0b111 or turn off 0b000 + self.decim3 = 10 + self.SetDecim(192000) + self.var_rates = ['31X', '19X', '9X', '5X', '3X', '2X', '1728', '1152', '768', '384', '192', '96', '48'] # supported sample rates as strings + self.index = 0 + self.DcI, self.DcQ = (0.0, 0.0) + self.NewAdf4351(self.Rx4351, 146E6) + self.NewAdf4351(self.Tx4351, 146E6) + self.NewAd9951(52e6) + self.NewUdpStatus() + def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None): + self.tx_frequency = tx_freq + if not self.Rx4351.frequency - 3E6 < vfo_freq < self.Rx4351.frequency + 3E6: + self.NewAdf4351(self.Rx4351, vfo_freq) + self.vfo_frequency = -1 + self.NewAd9951(tx_freq) + if abs(self.ad9951_freq - 10.7e6) > 15000: + self.NewAdf4351(self.Tx4351, tx_freq) + self.NewAd9951(tx_freq) + self.NewAd9951(tx_freq) + if self.vfo_frequency != vfo_freq: + self.vfo_frequency = vfo_freq + self.scan_deltaf = int(1152E3 * self.fft_scan_valid + 0.5) + self.scan_phase = int(1152.E3 * self.fft_scan_valid / self.conf.rx_udp_clock * 2.0**32 + 0.5) + self.scan_vfo0 = vfo_freq + rx_phase1 = int((vfo_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5) + if self.scan_enable: + self.scan_vfo0 = self.scan_vfo0 - self.scan_deltaf * (self.scan_blocks - 1) / 2 + rx_phase1 = rx_phase1 - int(self.scan_phase * (self.scan_blocks - 1) / 2.0 + 0.5) + self.rx_phase1 = rx_phase1 & 0xFFFFFFFF + rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32 + QS.change_rates(96000, tx_freq, self.vfo_sample_rate, vfo_freq) + QS.change_scan(self.scan_blocks, 1152000, self.fft_scan_valid, self.scan_vfo0, self.scan_deltaf) + if DEBUG: + #print( "vfo", vfo_freq, "adf4351", self.Rx4351.frequency, "phase", rx_phase1, "rx_tune", self.Rx4351.frequency - vfo_freq, rx_tune_freq) + #print ("VFO", self.Rx4351.frequency + rx_tune_freq) + print ("Change to Tx %d Vfo %d; VFO %.0f = adf4351_freq %.0f + rx_tune_freq %.0f" % (tx_freq, vfo_freq, + self.Rx4351.frequency + rx_tune_freq, self.Rx4351.frequency, rx_tune_freq)) + #print ("scan_enable %d, scan_blocks %d, scan_vfo0 %d, scan_deltaf %d" % (self.scan_enable, self.scan_blocks, self.scan_vfo0, self.scan_deltaf)) + else: + QS.change_rates(96000, tx_freq, self.vfo_sample_rate, self.vfo_frequency) + rx_phase0 = int((tx_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5) + self.rx_phase0 = rx_phase0 & 0xFFFFFFFF + self.NewUdpStatus() + if self.application.bottom_widgets: + Rx1 = self.Rx4351.frequency * 1e-6 + Rx2 = (self.ReturnVfoFloat() - self.Rx4351.frequency) * 1e-6 + t = "Rx Div %d; ADF4351 %.6f + rx_tune %.6f = %.6f Tx Adf4351 %.6f AD9951 %.6f" % ( + 2**self.Rx4351.rf_divider, Rx1, Rx2, Rx1 + Rx2, self.Tx4351.frequency * 1e-6, self.ad9951_freq * 1e-6) + self.application.bottom_widgets.UpdateText(t) + return tx_freq, vfo_freq + def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx + if offset is None: # Return True if frequency change is complete + self.HeartBeat() + return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:] + if offset == 0: # Change back to the original frequency + if self.repeater_freq is None: # Frequency was already reset + return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:] + self.ChangeFrequency(self.repeater_freq, self.vfo_frequency) + self.repeater_freq = None + else: # Shift to repeater input frequency + self.repeater_freq = self.tx_frequency + offset = int(offset * 1000) # Convert kHz to Hz + self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency) + return False + def ReturnVfoFloat(self): # Return the accurate VFO as a float + rx_phase1 = int((self.vfo_frequency - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5) + rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32 + return self.Rx4351.frequency + rx_tune_freq + def open(self): + ##self.application.config_screen.config.tx_phase.Enable(1) + # Create the proper broadcast address for rx_udp_ip. + nm = self.conf.rx_udp_ip_netmask.split('.') + ip = self.conf.rx_udp_ip.split('.') + nm = map(int, nm) + ip = map(int, ip) + bc = '' + for i in range(4): + x = (ip[i] | ~ nm[i]) & 0xFF + bc = bc + str(x) + '.' + self.broadcast_addr = bc[:-1] + # This socket is used for the Simple Network Discovery Protocol by AE4JY + self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket_sndp.setblocking(0) + self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.sndp_request = chr(56) + chr(0) + chr(0x5A) + chr(0xA5) + chr(0) * 52 + self.sndp_rx_active = True + # conf.rx_udp_port is used for returning ADC samples + # conf.rx_udp_port + 1 is used for control + self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.rx_udp_socket.setblocking(0) + self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1)) + # conf.tx_audio_port + 1 is used for control + if self.conf.tx_ip: + self.sndp_tx_active = True + self.tx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.tx_udp_socket.setblocking(0) + self.tx_udp_socket.connect((self.conf.tx_ip, self.conf.tx_audio_port + 1)) + else: + self.sndp_tx_active = False + QS.change_rates(96000, 0, 96000, 0) + self.application.test1Button.Enable(0) + return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port) + def close(self): + if self.rx_udp_socket: + self.rx_udp_socket.close() + self.rx_udp_socket = None + if self.tx_udp_socket: + self.tx_udp_socket.close() + self.tx_udp_socket = None + def PrintStatus(self, msg, string): + print (msg, ' ', end=' ') + print (string[0:2], end=' ') + for c in string[2:]: + print ("%2X" % ord(c), end=' ') + print () + def GetFirmwareVersion(self): + return self.firmware_version + def ChangeMode(self, mode): + # mode is a string: "USB", "AM", etc. + if mode in ("CWL", "CWU"): + self.mode_is_cw = 1 + else: + self.mode_is_cw = 0 + self.NewUdpStatus() + def ChangeBand(self, band): + # band is a string: "60", "40", "WWV", etc. + self.band = band + try: + freq, DcI, DcQ = self.conf.CorrectTxDc[band] + except KeyError: + DcI, DcQ = (0.0, 0.0) + self.NewUdpCorrect(DcI, DcQ) + def NewUdpCorrect(self, DcI, DcQ): + self.DcI = DcI + self.DcQ = DcQ + QS.set_udp_tx_correct(DcI, DcQ, 0.828) + self.NewUdpStatus() + def PrintUdpCorrect(self): + for band, (freq, DcI, DcQ) in self.conf.CorrectTxDc.items(): + print ("'%s':(%.1f, %.6f, %.6f)," % (band, freq, DcI, DcQ)) + def OnButtonPTT(self, event): + btn = event.GetEventObject() + if btn.GetValue(): # Turn the software key bit on or off + self.button_PTT = 1 + else: + self.button_PTT = 0 + QS.set_key_down(self.button_PTT) + self.NewUdpStatus() + def OnSpot(self, level): + # level is -1 for Spot button Off; else the Spot level 0 to 1000. + pass + def Sndp(self): # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address + try: + self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321)) + except: + if DEBUG: + traceback.print_exc() + return + for i in range(5): + try: + data = self.socket_sndp.recv(1024) + except: + break + if len(data) != 56: + continue + if data[5:17] == 'QuiskUHFR-v1': + ip = self.conf.rx_udp_ip.split('.') + ip = map(int, ip) + ip = map(chr, ip) + if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]: + self.sndp_rx_active = False + if DEBUG: print("SNDP success for Rx") + else: + t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0] + + chr(0) * 12 + chr(self.conf.rx_udp_port & 0xFF) + chr(self.conf.rx_udp_port >> 8) + chr(0)) + self.socket_sndp.sendto(t, (self.broadcast_addr, 48321)) + elif data[5:17] == 'QuiskUHFT-v1': + if self.conf.tx_ip: + ip = self.conf.tx_ip.split('.') + ip = map(int, ip) + ip = map(chr, ip) + if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]: + self.sndp_tx_active = False + if DEBUG: print("SNDP success for Tx") + else: + t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0] + + chr(0) * 12 + chr(self.conf.tx_audio_port & 0xFF) + chr(self.conf.tx_audio_port >> 8) + chr(0)) + self.socket_sndp.sendto(t, (self.broadcast_addr, 48321)) + def HeartBeat(self): + if self.sndp_rx_active or self.sndp_tx_active: + self.Sndp() + return # SNDP is required + for i in range(10): + try: # receive the Rx status if any + data = self.rx_udp_socket.recv(1024) + if DEBUG > 1: + self.PrintStatus(' gotRx ', data) + except: + break + else: + if data[0:2] == 'Sx': + self.got_rx_udp_status = data + if self.tx_udp_socket: + for i in range(10): + try: # receive the Tx status if any + data = self.tx_udp_socket.recv(1024) + if DEBUG > 1: + self.PrintStatus(' gotTx ', data) + except: + break + else: + if data[0:2] == 'Sx': + self.got_tx_udp_status = data + if self.want_rx_udp_status[16:] == self.got_rx_udp_status[16:]: # The first part returns information from the hardware + self.firmware_version = ord(self.got_rx_udp_status[2]) # Firmware version is returned here + self.Rx4351.changed = 0 + else: + if DEBUG > 1: + self.PrintStatus('HaveRx', self.got_rx_udp_status[0:20]) + self.PrintStatus('sendRx', self.want_rx_udp_status[0:20]) + try: + self.rx_udp_socket.send(self.want_rx_udp_status) + except: + #traceback.print_exc() + pass + if not self.tx_udp_socket: + pass + elif self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]: # The first part returns information from the hardware + self.Tx4351.changed = 0 + self.Tx9951_changed = 0 + else: + if DEBUG > 1: + self.PrintStatus('HaveTx', self.got_rx_udp_status[0:20]) + self.PrintStatus('sendTx', self.want_rx_udp_status[0:20]) + try: + self.tx_udp_socket.send(self.want_rx_udp_status) + except: + #traceback.print_exc() + pass + if 0: + self.rx_udp_socket.send('Qs') + def VarDecimGetChoices(self): # return text labels for the control + return self.var_rates + def VarDecimGetLabel(self): # return a text label for the control + return "Sample rate ksps" + def VarDecimGetIndex(self): # return the current index + return self.index + def VarDecimRange(self): + return (48000, 1152000) + def VarDecimSet(self, index=None): # set decimation, return sample rate + if index is None: # initial call to set decimation before the call to open() + rate = self.application.vardecim_set # May be None or from different hardware + try: + rate /= 1000 + if rate > 1152: + rate = 1152 + index = self.var_rates.index(str(rate)) + except: + rate = 192 + index = self.var_rates.index(str(rate)) + self.index = index + rate = self.var_rates[index] + if rate[-1] == 'X': + self.scan_enable = 1 + self.scan_blocks = int(rate[0:-1]) + self.scan_samples = self.application.fft_size + self.decim1 = 2 + self.decim2 = 3 + rate = 1152000 * self.scan_blocks + else: + self.scan_enable = 0 + self.scan_blocks = 0 + rate = int(rate) + rate = rate * 1000 + self.SetDecim(rate) + vfo = self.vfo_frequency + self.vfo_frequency = -1 + self.vfo_sample_rate = rate + self.ChangeFrequency(self.tx_frequency, vfo) + self.NewUdpStatus() + return rate + def SetDecim(self, rate): + # self.decim1, decim2, decim3 are the first, second, third decimations in the hardware + if rate >= 1152000: + self.decim1 = 2 + elif rate >= 192000: + self.decim1 = 3 + elif rate == 96000: + self.decim1 = 6 + else: + self.decim1 = 12 + self.decim2 = self.conf.rx_udp_clock_nominal // rate // self.decim1 // self.decim3 + def NewUdpStatus(self): + # Start of 16 bytes sent to the hardware: + s = "Sx" # 0:2 Fixed string + s += chr(0) # 2 Version number is returned here + s += chr(0) # 3 + s += chr(0) * 12 # 4:16 + # Start of 80 bytes of data sent to the hardware: + s += chr( 6 - 1) # 0 Variable decimation less one channel 0 first + s += chr(12 - 1) # 1 Variable decimation less one channel 0 second + s += struct.pack("format = format; + hWav->nChan = nChan; + hWav->bytes_per_sample = bytes; + hWav->sample_rate = rate; + hWav->scale = scale; + hWav->samples = 0; // number of samples written + hWav->fp = fopen(file_name, "wb"); + if ( ! hWav->fp) + return 0; + if (format == 0) // RAW format - no header + return 1; + if (fwrite("RIFF", 1, 4, hWav->fp) != 4) { + fclose(hWav->fp); + hWav->fp = NULL; + return 0; + } + if (format == 1) // PCM + u = 36; + else + u = 50; + fwrite(&u, 4, 1, hWav->fp); + fwrite("WAVE", 1, 4, hWav->fp); + fwrite("fmt ", 1, 4, hWav->fp); + if (format == 1) // PCM + u = 16; + else + u = 18; + fwrite(&u, 4, 1, hWav->fp); + fwrite(&format, 2, 1, hWav->fp); // format + fwrite(&nChan, 2, 1, hWav->fp); // number of channels + fwrite(&rate, 4, 1, hWav->fp); // sample rate + u = rate * bytes * nChan; + fwrite(&u, 4, 1, hWav->fp); + s = bytes * nChan; + fwrite(&s, 2, 1, hWav->fp); + s = bytes * 8; + fwrite(&s, 2, 1, hWav->fp); + if (format != 1) { + s = 0; + fwrite(&s, 2, 1, hWav->fp); + fwrite("fact", 1, 4, hWav->fp); + u = 4; + fwrite(&u, 4, 1, hWav->fp); + u = 0; + fwrite(&u, 4, 1, hWav->fp); + } + fwrite("data", 1, 4, hWav->fp); + u = 0; + fwrite(&u, 4, 1, hWav->fp); + return 1; +} + +void QuiskWavWriteC(struct QuiskWav * hWav, complex double * cSamples, int nSamples) +{ // Record the samples to a WAV file, two float samples I/Q. Always use IEEE format 3. + int j; // TODO: add other formats + float samp; // must be 4 bytes + + if ( ! hWav->fp) + return; + // append the samples + hWav->samples += (unsigned int)nSamples; + fseek(hWav->fp, 0, SEEK_END); // seek to the end + for (j = 0; j < nSamples; j++) { + samp = creal(cSamples[j]) * hWav->scale; + fwrite(&samp, 4, 1, hWav->fp); + samp = cimag(cSamples[j]) * hWav->scale; + fwrite(&samp, 4, 1, hWav->fp); + } + // write the sizes to the header + QuiskWavWriteD(hWav, NULL, 0); +} + +void QuiskWavWriteD(struct QuiskWav * hWav, double * dSamples, int nSamples) +{ // Record the samples to a file, one channel. + int j; + float samp; // must be 4 bytes + unsigned int u; // must be 4 bytes + int i; // must be 4 bytes + char c; // must be 1 byte + short s; // must be 2 bytes + + if ( ! hWav->fp) + return; + // append the samples + hWav->samples += (unsigned int)nSamples; + fseek(hWav->fp, 0, SEEK_END); // seek to the end + if ( ! dSamples) { + ; // Only update the header + } + else if (hWav->format == 3) { // float + for (j = 0; j < nSamples; j++) { + samp = dSamples[j] * hWav->scale; + fwrite(&samp, 4, 1, hWav->fp); + } + } + else { // PCM integer + switch (hWav->bytes_per_sample) { + case 1: + for (j = 0; j < nSamples; j++) { + c = (char)(dSamples[j] * hWav->scale); + fwrite(&c, 1, 1, hWav->fp); + } + break; + case 2: + for (j = 0; j < nSamples; j++) { + s = (short)(dSamples[j] * hWav->scale); + fwrite(&s, 2, 1, hWav->fp); + } + break; + case 4: + for (j = 0; j < nSamples; j++) { + i = (int)(dSamples[j] * hWav->scale); + fwrite(&i, 4, 1, hWav->fp); + } + break; + } + } + // write the sizes to the header + if (hWav->format == 0) { // RAW format + ; + } + else if (hWav->format == 3) { // float + fseek(hWav->fp, 54, SEEK_SET); // seek from the beginning + u = hWav->bytes_per_sample * hWav->nChan * hWav->samples; + fwrite(&u, 4, 1, hWav->fp); + fseek(hWav->fp, 4, SEEK_SET); + u += 50 ; + fwrite(&u, 4, 1, hWav->fp); + fseek(hWav->fp, 46, SEEK_SET); + u = hWav->samples * hWav->nChan; + fwrite(&u, 4, 1, hWav->fp); + } + else { + fseek(hWav->fp, 40, SEEK_SET); + u = hWav->bytes_per_sample * hWav->nChan * hWav->samples; + fwrite(&u, 4, 1, hWav->fp); + fseek(hWav->fp, 4, SEEK_SET); + u += 36 ; + fwrite(&u, 4, 1, hWav->fp); + } + if (hWav->samples > 536870000) // 2**32 / 8 + QuiskWavClose(hWav); +} + +int QuiskWavReadOpen(struct QuiskWav * hWav, char * file_name, short format, short nChan, short bytes, int rate, double scale) +{ // TODO: Get parameters from the WAV file header. + char name[5]; + int size; + + hWav->format = format; + hWav->nChan = nChan; + hWav->bytes_per_sample = bytes; + hWav->sample_rate = rate; + hWav->scale = scale; + hWav->fp = fopen(file_name, "rb"); + if (!hWav->fp) + return 0; + if (hWav->format == 0) { // RAW format + fseek(hWav->fp, 0, SEEK_END); // seek to the end + hWav->fpEnd = ftell(hWav->fp); + hWav->fpStart = hWav->fpPos = 0; + return 1; + } + hWav->fpEnd = 0; + while (1) { // WAV format + if (fread (name, 4, 1, hWav->fp) != 1) + return 0; + if (fread (&size, 4, 1, hWav->fp) != 1) + return 0; + name[4] = 0; + //printf("name %s size %d\n", name, size); + if (!strncmp(name, "RIFF", 4)) + fseek (hWav->fp, 4, SEEK_CUR); // Skip "WAVE" + else if (!strncmp(name, "data", 4)) { // sound data starts here + hWav->fpStart = ftell(hWav->fp); + hWav->fpEnd = hWav->fpStart + size; + hWav->fpPos = hWav->fpStart; + break; + } + else // Skip other records + fseek (hWav->fp, size, SEEK_CUR); + } + //printf("start %d end %d\n", hWav->fpStart, hWav->fpEnd); + if (!hWav->fpEnd) { // Failure to find "data" record + fclose(hWav->fp); + hWav->fp = NULL; + return 0; + } + return 1; +} + +void QuiskWavReadC(struct QuiskWav * hWav, complex double * cSamples, int nSamples) +{ // Always uses format 3. TODO: add other formats. + int i; + float fi, fq; + double di, dq; + #if 0 -static int fFracDecim(double * dSamples, int nSamples, double fdecim) + double noise; + noise = 1.6E6; + for (i = 0; i < nSamples; i++) { + di = ((float)random() / RAND_MAX - 0.5) * noise; + dq = ((float)random() / RAND_MAX - 0.5) * noise; + cSamples[i] = di + I * dq; + } +#endif + if (hWav->fp && nSamples > 0) { + fseek (hWav->fp, hWav->fpPos, SEEK_SET); + for (i = 0; i < nSamples; i++) { + if (fread(&fi, 4, 1, hWav->fp) != 1) + break; + if (fread(&fq, 4, 1, hWav->fp) != 1) + break; + di = fi * hWav->scale; + dq = fq * hWav->scale; + cSamples[i] += di + I * dq; + hWav->fpPos += hWav->bytes_per_sample * hWav->nChan; + if (hWav->fpPos >= hWav->fpEnd) + hWav->fpPos = hWav->fpStart; + } + } +} + +void QuiskWavReadD(struct QuiskWav * hWav, double * dSamples, int nSamples) +{ + int j; + float samp; + int i; // must be 4 bytes + char c; // must be 1 byte + short s; // must be 2 bytes + + if (hWav->fp && nSamples > 0) { + fseek (hWav->fp, hWav->fpPos, SEEK_SET); + for (j = 0; j < nSamples; j++) { + if (hWav->format == 3) { // float + if (fread(&samp, 4, 1, hWav->fp) != 1) + return; + } + else { // PCM integer + switch (hWav->bytes_per_sample) { + case 1: + if (fread(&c, 1, 1, hWav->fp) != 1) + return; + samp = c; + break; + case 2: + if (fread(&s, 2, 1, hWav->fp) != 1) + return; + samp = s; + break; + case 4: + if (fread(&i, 4, 1, hWav->fp) != 1) + return; + samp = i; + break; + } + } + dSamples[j] = samp * hWav->scale; + hWav->fpPos += hWav->bytes_per_sample * hWav->nChan; + if (hWav->fpPos >= hWav->fpEnd) + hWav->fpPos = hWav->fpStart; + } + } +} + +void QuiskWavClose(struct QuiskWav * hWav) { + if (hWav->fp) { + fclose(hWav->fp); + hWav->fp = NULL; + } +} +#endif + +// These are used for digital voice codecs +ty_dvoice_codec_rx pt_quisk_freedv_rx; +ty_dvoice_codec_tx pt_quisk_freedv_tx; + +void quisk_dvoice_freedv(ty_dvoice_codec_rx rx, ty_dvoice_codec_tx tx) +{ + pt_quisk_freedv_rx = rx; + pt_quisk_freedv_tx = tx; +} +#if 0 +static int fFracDecim(double * dSamples, int nSamples, double fdecim) +{ // fractional decimation by fdecim > 1.0 int i, nout; double xm0, xm1, xm2, xm3; static double dindex = 1; @@ -182,7 +492,6 @@ return nout; } #endif - static int cFracDecim(complex double * cSamples, int nSamples, double fdecim) { // Fractional decimation of I/Q signals works poorly because it introduces aliases and birdies. @@ -227,7 +536,7 @@ //printf ("in %d out %d\n", in, out); return nout; } - + #define QUISK_NB_HWINDOW_SECS 500.E-6 // half-size of blanking window in seconds static void NoiseBlanker(complex double * cSamples, int nSamples) { @@ -630,7 +939,7 @@ return cx; } -static complex double cRxFilterOut(complex double sample, int bank) +complex double cRxFilterOut(complex double sample, int bank) { // Rx FIR filter; bank is the static storage index, and must be different for different data streams double accI, accQ; int j, k; @@ -691,6 +1000,7 @@ } break; case 5: // FM + case 13: //audioPhase = cexp(I * 2 * M_PI * quisk_sidetoneCtrl * 5 / sample_rate); audioPhase = cexp(I * 2.0 * M_PI * 1000 / quisk_sound_state.sample_rate); for (i = 0; i < nSamples; i++) { @@ -765,8 +1075,12 @@ case 3: // release play quisk_record_state = IDLE; break; + case 5: // press play file + wavPosSound = wavPosMic = wavStart; + quisk_record_state = PLAY_FILE; + break; } - return PyInt_FromLong(quisk_record_state != PLAYBACK); + return PyInt_FromLong(quisk_record_state != PLAYBACK && quisk_record_state != PLAY_FILE); } void quisk_tmp_record(complex double * cSamples, int nSamples, double scale) // save sound @@ -782,7 +1096,7 @@ } } -static void tmp_playback(complex double * cSamples, int nSamples, double volume) +void quisk_tmp_playback(complex double * cSamples, int nSamples, double volume) { // replace radio sound with saved sound int i; double d; @@ -816,8 +1130,136 @@ } } -static int quisk_process_decimate(complex double * cSamples, int nSamples, int bank) +static PyObject * open_file_play(PyObject * self, PyObject * args) +{ +// The WAV file must be recorded at 48000 Hertz in S16_LE format. + const char * fname; + char name[5]; + int size; + + if (!PyArg_ParseTuple (args, "s", &fname)) + return NULL; + if (wavFp) + fclose(wavFp); + wavFp = fopen(fname, "rb"); + if (!wavFp) { + printf("open_wav failed\n"); + return PyInt_FromLong(1); + } + wavEnd = 0; + while (1) { + if (fread (name, 4, 1, wavFp) != 1) + break; + if (fread (&size, 4, 1, wavFp) != 1) + break; + name[4] = 0; + //printf("name %s size %d ret %d\n", name, size, ret); + if (!strncmp(name, "RIFF", 4)) + fseek (wavFp, 4, SEEK_CUR); // Skip "WAVE" + else if (!strncmp(name, "data", 4)) { // sound data starts here + wavStart = ftell(wavFp); + wavEnd = wavStart + size; + break; + } + else // Skip other records + fseek (wavFp, size, SEEK_CUR); + } + //printf("start %d end %d\n", wavStart, wavEnd); + if (!wavEnd) { // Failure to find "data" record + fclose(wavFp); + wavFp = NULL; + return PyInt_FromLong(2); + } + return PyInt_FromLong(0); +} + +void quisk_file_playback(complex double * cSamples, int nSamples, double volume) +{ + // Replace radio sound by file samples. + // The sample rate must equal quisk_sound_state.mic_sample_rate. + int i; + short sh; + double d; + + if (wavFp && wavPosSound < wavEnd) { + fseek (wavFp, wavPosSound, SEEK_SET); + for (i = 0; i < nSamples; i++) { + if (fread(&sh, 2, 1, wavFp) != 1) + break; + d = sh * ((double)CLIP32 / CLIP16) * volume; + cSamples[i] = d + I * d; + wavPosSound += 2; + if (wavPosSound >= wavEnd) { + quisk_record_state = IDLE; + break; + } + } + } +} + +#define BUF2CHAN_SIZE 12000 +static int Buffer2Chan(double * samp1, int count1, double * samp2, int count2) +{ // return the minimum of count1 and count2, buffering as necessary + int nout; + static int nbuf1=0, nbuf2=0; + static double buf1[BUF2CHAN_SIZE], buf2[BUF2CHAN_SIZE]; + + if (samp1 == NULL) { // initialize + nbuf1 = nbuf2 = 0; + return 0; + } + if (nbuf1 == 0 && nbuf2 == 0 && count1 == count2) // nothing to do + return count1; + if (count1 + nbuf1 >= BUF2CHAN_SIZE || count2 + nbuf2 >= BUF2CHAN_SIZE) { // overflow + if (DEBUG || DEBUG_IO) + printf("Overflow in Buffer2Chan nbuf1 %d nbuf2 %d size %d\n", nbuf1, nbuf2, BUF2CHAN_SIZE); + nbuf1 = nbuf2 = 0; + } + memcpy(buf1 + nbuf1, samp1, count1 * sizeof(double)); // add samples to buffer + nbuf1 += count1; + memcpy(buf2 + nbuf2, samp2, count2 * sizeof(double)); + nbuf2 += count2; + if (nbuf1 <= nbuf2) + nout = nbuf1; // number of samples to output + else + nout = nbuf2; + //if (count1 + nbuf1 >= 2000 || count2 + nbuf2 >= 2000) + // printf("Buffer2Chan nbuf1 %d nbuf2 %d nout %d\n", nbuf1, nbuf2, nout); + memcpy(samp1, buf1, nout * sizeof(double)); // output samples + nbuf1 -= nout; + memmove(buf1, buf1 + nout, nbuf1 * sizeof(double)); + memcpy(samp2, buf2, nout * sizeof(double)); + nbuf2 -= nout; + memmove(buf2, buf2 + nout, nbuf2 * sizeof(double)); + return nout; +} + +void quisk_file_microphone(complex double * cSamples, int nSamples) { + // Replace mic samples by file samples. + // The sample rate must equal quisk_sound_state.mic_sample_rate. + int i; + short sh; + double d; + + if (wavFp && wavPosMic < wavEnd) { + fseek (wavFp, wavPosMic, SEEK_SET); + for (i = 0; i < nSamples; i++) { + if (fread(&sh, 2, 1, wavFp) != 1) + break; + d = sh * ((double)CLIP32 / CLIP16); + cSamples[i] = d + I * d; + wavPosMic += 2; + if (wavPosMic >= wavEnd) { + quisk_record_state = IDLE; + break; + } + } + } +} + +static int quisk_process_decimate(complex double * cSamples, int nSamples, int bank) +{ // Changes here will require changes to get_filter_rate(); int i, final_filter; static struct stStorage { struct quisk_cHB45Filter HalfBand1; @@ -965,17 +1407,18 @@ nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim3, 3); break; default: - printf ("Failure in quisk.c in integer decimation\n"); + printf ("Failure in quisk.c in integer decimation for rate %d\n", quisk_sound_state.sample_rate); break; } return nSamples; } static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank) -{ +{ // Changes here will require changes to get_filter_rate(); int i; complex double cx, cpx; double d, di, dd; + static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0}; static struct stStorage { complex double fm_1; // Sample delayed by one double dc_remove; // DC removal for AM @@ -989,7 +1432,9 @@ struct quisk_dFilter filtAudio12p2; struct quisk_dFilter filtAudio24p6; struct quisk_dFilter filtAudioFmHp; + struct quisk_cFilter filtDecim16to8; struct quisk_cFilter filtDecim48to24; + struct quisk_cFilter filtDecim48to16; } Storage[2] ; if ( ! cSamples) { // Initialize all filters @@ -1002,7 +1447,9 @@ quisk_filt_dInit(&Storage[i].filtAudio12p2, quiskAudio24p4Coefs, sizeof(quiskAudio24p4Coefs)/sizeof(double)); quisk_filt_dInit(&Storage[i].filtAudio24p6, quiskAudio24p6Coefs, sizeof(quiskAudio24p6Coefs)/sizeof(double)); quisk_filt_dInit(&Storage[i].filtAudioFmHp, quiskAudioFmHpCoefs, sizeof(quiskAudioFmHpCoefs)/sizeof(double)); + quisk_filt_cInit(&Storage[i].filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); quisk_filt_cInit(&Storage[i].filtDecim48to24, quiskFilt48dec24Coefs, sizeof(quiskFilt48dec24Coefs)/sizeof(double)); + quisk_filt_cInit(&Storage[i].filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); Storage[i].fm_1 = 10; Storage[i].FM_www = tan(M_PI * FM_FILTER_DEMPH / 24000); // filter for FM Storage[i].FM_nnn = 1.0 / (1.0 + Storage[i].FM_www); @@ -1108,6 +1555,7 @@ dAutoNotch(dsamples, nSamples, 0, quisk_filter_srate); break; case 5: // FM at 24 ksps + case 13: quisk_demod_srate /= 2; quisk_filter_srate = quisk_demod_srate; nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to24, 2); @@ -1156,9 +1604,40 @@ if (filter_bandwidth < 19000) { // No filtering for wide bandwidth for (i = 0; i < nSamples; i++) cSamples[i] = dRxFilterOut(cSamples[i], bank); - measure_audio_sum += cSamples[i] * conj(cSamples[i]); + measure_audio_sum = measure_audio_sum + cSamples[i] * conj(cSamples[i]); + measure_audio_count += 1; + } + break; + case 11: // digital voice at 8 ksps + case 12: + quisk_check_freedv_mode(); + quisk_demod_srate /= 3; + quisk_filter_srate = quisk_demod_srate; + nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3); + if (bank == 0) + process_agc(&Agc1, cSamples, nSamples, 1); + else + process_agc(&Agc2, cSamples, nSamples, 1); + if(bank == 0) + dAutoNotch(dsamples, nSamples, 0, quisk_filter_srate); + // Perhaps decimate by an additional fraction + if (quisk_decim_srate != 48000) { + dd = quisk_decim_srate / 48000.0; + nSamples = cFracDecim(cSamples, nSamples, dd); + quisk_demod_srate = (int)(quisk_demod_srate / dd + 0.5); + quisk_decim_srate = 48000; + } + quisk_demod_srate /= 2; + quisk_filter_srate /= 2; + nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2); + if (pt_quisk_freedv_rx) + nSamples = (* pt_quisk_freedv_rx)(cSamples, dsamples, nSamples, bank); + for (i = 0; i < nSamples; i++) { + measure_audio_sum += dsamples[i] * dsamples[i]; measure_audio_count += 1; } + nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3); + quisk_demod_srate *= 3; break; } if (measure_audio_count >= quisk_filter_srate * measure_audio_time) { @@ -1180,8 +1659,10 @@ #endif if ( ! dat->buf_size) { // initialize - dat->buf_size = quisk_sound_state.playback_rate * AGC_DELAY / 1000; // total delay in samples - //printf("play rate %d buf_size %d\n", quisk_sound_state.playback_rate, dat->buf_size); + if (dat->sample_rate == 0) + dat->sample_rate = quisk_sound_state.playback_rate; + dat->buf_size = dat->sample_rate * AGC_DELAY / 1000; // total delay in samples + //printf("play rate %d buf_size %d\n", dat->sample_rate, dat->buf_size); dat->index_read = 0; // Index to output; and then write a new sample here dat->index_start = 0; // Start index for measure of maximum sample dat->is_clipping = 0; // Are we decreasing gain to handle a clipping condition? @@ -1189,6 +1670,7 @@ dat->gain = 100; // Current output gain dat->delta = 0; // Amount to change dat->gain at each sample dat->target_gain = 100; // Move to this gain unless we clip + dat->time_release = 1.0 - exp( - 1.0 / dat->sample_rate / agc_release_time); // long time constant for AGC release dat->c_samp = (complex double *) malloc(dat->buf_size * sizeof(complex double)); // buffer for complex samples for (i = 0; i < dat->buf_size; i++) dat->c_samp[i] = 0; @@ -1220,8 +1702,8 @@ else buf_magn = fabs(creal(csample)); if (dat->is_clipping == 0) { - if (buf_magn * dat->gain > AGC_MAX_OUT) { - dat->target_gain = AGC_MAX_OUT / buf_magn; + if (buf_magn * dat->gain > dat->max_out * CLIP32) { + dat->target_gain = dat->max_out * CLIP32 / buf_magn; dat->delta = (dat->gain - dat->target_gain) / dat->buf_size; dat->is_clipping = 1; dat->themax = buf_magn; @@ -1230,28 +1712,28 @@ dat->gain -= dat->delta; } else if (dat->index_read == dat->index_start) { - clip_gain = AGC_MAX_OUT / dat->themax; // clip gain based on the maximum sample in the buffer - if (rxMode == 5) // mode is FM + clip_gain = dat->max_out * CLIP32 / dat->themax; // clip gain based on the maximum sample in the buffer + if (rxMode == 5 || rxMode == 13) // mode is FM dat->target_gain = clip_gain; else if (agcReleaseGain > clip_gain) dat->target_gain = clip_gain; else dat->target_gain = agcReleaseGain; dat->themax = buf_magn; - dat->gain = dat->gain * (1.0 - agcTimeRelease) + dat->target_gain * agcTimeRelease; + dat->gain = dat->gain * (1.0 - dat->time_release) + dat->target_gain * dat->time_release; // printf("New index %5d themax %7.5lf clip_gain %5.0lf agcReleaseGain %5.0lf\n", // dat->index_start, dat->themax / CLIP32, clip_gain, agcReleaseGain); } else { if (dat->themax < buf_magn) dat->themax = buf_magn; - dat->gain = dat->gain * (1.0 - agcTimeRelease) + dat->target_gain * agcTimeRelease; + dat->gain = dat->gain * (1.0 - dat->time_release) + dat->target_gain * dat->time_release; } } else { // dat->is_clipping == 1; we are handling a clip condition if (buf_magn > dat->themax) { dat->themax = buf_magn; - dat->target_gain = AGC_MAX_OUT / buf_magn; + dat->target_gain = dat->max_out * CLIP32 / buf_magn; dtmp = (dat->gain - dat->target_gain) / dat->buf_size; // new value of delta if (dtmp > dat->delta) { dat->delta = dtmp; @@ -1276,7 +1758,7 @@ if (++dat->index_read >= dat->buf_size) dat->index_read = 0; #if DEBUG - if (printit++ >= quisk_sound_state.playback_rate * 500 / 1000) { + if (printit++ >= dat->sample_rate * 500 / 1000) { printit = 0; dtmp = 20 * log10(maxout / CLIP32); if (dtmp >= 0) @@ -1304,6 +1786,7 @@ fft_data * ptFFT; static int size_dsamples = 0; // Current dimension of dsamples, dsamples2, orig_cSamples + static int old_split_rxtx = 0; // Prior value of split_rxtx static double * dsamples = NULL; static double * dsamples2 = NULL; static complex double * orig_cSamples = NULL; @@ -1319,7 +1802,7 @@ static struct quisk_cHB45Filter HalfBand7 = {NULL, 0, 0}; static struct quisk_cHB45Filter HalfBand8 = {NULL, 0, 0}; static struct quisk_cHB45Filter HalfBand9 = {NULL, 0, 0}; - static struct AgcState Agc1 = {0}, Agc2 = {0}; + static struct AgcState Agc1 = {0.7, 0, 0}, Agc2 = {0.7, 0, 0}; #if DEBUG static int printit; @@ -1348,10 +1831,21 @@ dsamples2 = (double *)malloc(size_dsamples * sizeof(double)); orig_cSamples = (complex double *)malloc(size_dsamples * sizeof(complex double)); } + +#if SAMPLES_FROM_FILE == 1 + QuiskWavWriteC(&hWav, cSamples, nSamples); +#elif SAMPLES_FROM_FILE == 2 + QuiskWavReadC(&hWav, cSamples, nSamples); +#endif + is_key_down = quisk_transmit_mode || quisk_is_key_down(); orig_nSamples = nSamples; - if (split_rxtx) + if (split_rxtx) { memcpy(orig_cSamples, cSamples, nSamples * sizeof(complex double)); + if ( ! old_split_rxtx) // start of new split mode + Buffer2Chan(NULL, 0, NULL, 0); + } + old_split_rxtx = split_rxtx; if (is_key_down && !isFDX) { // The key is down; replace this data block dOutCounter += (double)nSamples * quisk_sound_state.playback_rate / @@ -1458,9 +1952,6 @@ } } - // No need to tune and demodulate if we don't play sound - if (quisk_sound_state.dev_play_name[0] == 0) - return 0; // Tune the data to frequency if (multiple_sample_rates == 0) tune = rx_tune_freq; @@ -1528,7 +2019,7 @@ } n = quisk_process_decimate(orig_cSamples, orig_nSamples, 1); n = quisk_process_demodulate(orig_cSamples, dsamples2, n, 1); - // We assume that n == nSamples + nSamples = Buffer2Chan(dsamples, nSamples, dsamples2, n); // buffer dsamples and dsamples2 so the count is equal switch(split_rxtx) { default: case 1: // stereo, higher frequency is real @@ -1592,13 +2083,16 @@ interp /= 2; } if (interp != 1) - printf ("Failure in quisk.c in integer interpolation\n"); + printf ("Failure in quisk.c in integer interpolation %d %d\n", quisk_decim_srate, quisk_sound_state.playback_rate); // Find the peak signal amplitude start_agc: - if (rxMode == 6 || rxMode == 9) { // DGT-IQ stereo sound + if (rxMode == 6 || rxMode == 9) { // Ext and DGT-IQ stereo sound process_agc(&Agc1, cSamples, nSamples, 1); } + else if (rxMode == 11 || rxMode == 12) { // Agc already done + ; + } else if (split_rxtx) { // separate AGC for left and right channels for (i = 0; i < nSamples; i++) { orig_cSamples[i] = cimag(cSamples[i]); @@ -1650,8 +2144,6 @@ } if (quisk_record_state == RECORD_RADIO && ! is_squelch) quisk_tmp_record(cSamples, nSamples, 1.0); // save radio sound - if (quisk_record_state == PLAYBACK) - tmp_playback(cSamples, nSamples, 1.0); // replace radio sound return nSamples; } @@ -1699,15 +2191,65 @@ } static PyObject * get_filter_rate(PyObject * self, PyObject * args) -{ // return the filter sample rate as used by quisk_process_samples - complex double cSamples[2]; - double dsamples[2]; +{ // Return the filter sample rate as used by quisk_process_samples. + // Changes to quisk_process_decimate or quisk_process_demodulate will require changes here. + int rate, decim_srate, filter_srate; if (!PyArg_ParseTuple (args, "")) return NULL; - // run some fake data through the filters to calculate the rates - quisk_process_decimate(cSamples, 0, 0); - quisk_process_demodulate(cSamples, dsamples, 0, 0); - return PyInt_FromLong(quisk_filter_srate); + rate = quisk_sound_state.sample_rate; + switch((rate + 100) / 1000) { + case 53: // SDR-IQ + decim_srate = rate; + break; + case 111: // SDR-IQ + decim_srate = rate / 2; + break; + case 133: // SDR-IQ + decim_srate = rate / 2; + break; + case 185: // SDR-IQ + decim_srate = rate / 3; + break; + case 370: + decim_srate = rate / 6; + break; + case 740: + decim_srate = rate / 12; + break; + case 1333: + decim_srate = rate / 24; + break; + default: + decim_srate = 48000; + break; + } + switch(rxMode) { + case 0: // lower sideband CW at 6 ksps + case 1: // upper sideband CW at 6 ksps + filter_srate = decim_srate / 8; + break; + case 2: // lower sideband SSB at 12 ksps + case 3: // upper sideband SSB at 12 ksps + default: + filter_srate = decim_srate / 4; + break; + case 4: // AM at 24 ksps + case 5: // FM at 24 ksps + case 13: // digital FM at 24 ksps + filter_srate = decim_srate / 2; + break; + case 7: // digital modes DGT-* at 48 ksps + case 8: + case 9: + filter_srate = decim_srate; + break; + case 11: // digital voice at 8 ksps + case 12: + filter_srate = 8000; + break; + } + //printf("Filter rate %d\n", filter_srate); + return PyInt_FromLong(filter_srate); } static PyObject * get_smeter(PyObject * self, PyObject * args) @@ -1785,11 +2327,39 @@ #endif } +static void close_udp10(void) // Metis-Hermes protocol +{ + int i; + unsigned char buf[64]; + + quisk_using_udp = 0; + if (rx_udp_socket != INVALID_SOCKET) { + shutdown(rx_udp_socket, QUISK_SHUT_RD); + buf[0] = 0xEF; + buf[1] = 0xFE; + buf[2] = 0x04; + buf[3] = 0x00; + for (i = 4; i < 64; i++) + buf[i] = 0; + send(rx_udp_socket, (char *)buf, 64, 0); + QuiskSleepMicrosec(3000000); + close(rx_udp_socket); + rx_udp_socket = INVALID_SOCKET; + } + rx_udp_started = 0; +#ifdef MS_WINDOWS + if (cleanupWSA) { + cleanupWSA = 0; + WSACleanup(); + } +#endif +} + static PyObject * close_rx_udp(PyObject * self, PyObject * args) { // Not necessary to call from Python because close_udp() is called from sound.c if (!PyArg_ParseTuple (args, "")) return NULL; - close_udp(); + //close_udp(); Py_INCREF (Py_None); return Py_None; } @@ -1958,12 +2528,120 @@ return nSamples; } +static int read_rx_udp10(complex double * samp) // Read samples from UDP +{ // Size of complex sample array is SAMP_BUFFER_SIZE + ssize_t bytes; + unsigned char buf[1500]; // Maximum Ethernet is 1500 bytes. + unsigned int seq; + static unsigned int seq0; // must be 32 bits + static int key_state = 0; + int i, nSamples, xr, xi, index, start, want_samples, dindex, state; + struct timeval tm_wait; + fd_set fds; + + if ( ! rx_udp_started) { // we never received any data + // send our return address until we receive UDP blocks + buf[0] = 0xEF; + buf[1] = 0xFE; + buf[2] = 0x04; + buf[3] = 0x01; + for (i = 4; i < 64; i++) + buf[i] = 0; + send(rx_udp_socket, (char *)buf, 64, 0); + } + nSamples = 0; + want_samples = (int)(quisk_sound_state.data_poll_usec * 1e-6 * quisk_sound_state.sample_rate + 0.5); + while (nSamples < want_samples) { // read several UDP blocks + tm_wait.tv_sec = 0; + tm_wait.tv_usec = 100000; // Linux seems to have problems with very small time intervals + FD_ZERO (&fds); + FD_SET (rx_udp_socket, &fds); + i = select (rx_udp_socket + 1, &fds, NULL, NULL, &tm_wait); // blocking wait + if (i == 1) + ; + else if (i == 0) { +#if DEBUG_IO + printf("Udp socket timeout\n"); +#endif + return 0; + } + else { +#if DEBUG_IO + printf("Udp select error %d\n", i); +#endif + return 0; + } + bytes = recv(rx_udp_socket, (char *)buf, 1500, 0); // blocking read + if (bytes != 1032 || buf[0] != 0xEF || buf[1] != 0xFE || buf[2] != 0x01) { // Known size of sample block + quisk_sound_state.read_error++; +#if DEBUG_IO + printf("read_rx_udp10: Bad block size or header\n"); +#endif + continue; + } + if (buf[3] != 0x06) // End point 6: I/Q and mic samples + continue; + rx_udp_started = 1; + seq = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7]; // sequence number + if (seq != seq0) { +#if DEBUG_IO + printf("read_rx_udp10: Bad sequence want %d got %d\n", seq0, seq); +#endif + quisk_sound_state.read_error++; + } + seq0 = seq + 1; // Next expected sequence number + quisk_hermes_tx_send(); + for (start = 11; start < 1000; start += 512) { + // check the sync bytes + if (buf[start - 3] != 0x7F || buf[start - 2] != 0x7F || buf[start - 1] != 0x7F) { +#if DEBUG_IO + printf("read_rx_udp10: Bad sync byte\n"); +#endif + quisk_sound_state.read_error++; + break; + } + // read five bytes of control information. start is the index of C0. + dindex = buf[start] >> 3; + if (dindex >= 0 && dindex <= 4) { // Save the data returned by the hardware + quisk_hermes_to_pc[dindex * 4 ] = buf[start + 1]; // C1 to C4 + quisk_hermes_to_pc[dindex * 4 + 1] = buf[start + 2]; + quisk_hermes_to_pc[dindex * 4 + 2] = buf[start + 3]; + quisk_hermes_to_pc[dindex * 4 + 3] = buf[start + 4]; + } + if (dindex == 0) { // C0 is 0b00000xxx + if ((quisk_hermes_to_pc[0] & 0x01) != 0) // C1 + quisk_sound_state.overrange++; + if (rxMode == 0 || rxMode == 1) { + state = buf[start] & 0x01; // C0 bit zero is CW key state + state |= is_PTT_down; + } + else { + state = is_PTT_down; + } + if (state != key_state) { + key_state = state; + quisk_set_key_down(state); + } + } + // convert 24-bit samples to 32-bit samples; int must be 32 bits. + index = start + 5; + for (i = 0; i < 63; i++) { // read 63 records + xi = buf[index ] << 24 | buf[index + 1] << 16 | buf[index + 2] << 8; + xr = buf[index + 3] << 24 | buf[index + 4] << 16 | buf[index + 5] << 8; + samp[nSamples++] = xr + xi * I; + index += 8; + } + } + } + return nSamples; +} + static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP { // Size of complex sample array is SAMP_BUFFER_SIZE ssize_t bytes; unsigned char buf[1500]; // Maximum Ethernet is 1500 bytes. static unsigned char seq0; // must be 8 bits - int i, n, nSamples0, xr, xi, index, want_samples; + int i, n, nSamples0, xr, xi, index, want_samples, key_down; complex double sample; unsigned char * ptxr, * ptxi; struct timeval tm_wait; @@ -2007,6 +2685,7 @@ } nSamples0 = 0; want_samples = (int)(quisk_sound_state.data_poll_usec * 1e-6 * quisk_sound_state.sample_rate + 0.5); + key_down = quisk_is_key_down(); while (nSamples0 < want_samples) { // read several UDP blocks tm_wait.tv_sec = 0; tm_wait.tv_usec = 100000; // Linux seems to have problems with very small time intervals @@ -2047,7 +2726,7 @@ quisk_sound_state.read_error++; } seq0 = buf[0] + 1; // Next expected sequence number - quisk_set_key_down(buf[1] & 0x01); // bit zero is key state + //quisk_set_key_down(buf[1] & 0x01); // bit zero is key state if (buf[1] & 0x02) // bit one is ADC overrange quisk_sound_state.overrange++; index = 2; @@ -2103,7 +2782,7 @@ printf("Bad block_number %d\n", block_number); } ptFFT->samples[ptFFT->index] = sample; - if (++(ptFFT->index) >= fft_size) { // check sample count + if ((isFDX || ! key_down) && ++(ptFFT->index) >= fft_size) { // check sample count n = fft_data_index + 1; // next FFT data location if (n >= FFT_ARRAY_SIZE) n = 0; @@ -2126,7 +2805,7 @@ } } } - if (quisk_is_key_down()) { + if (key_down) { dc_key_delay = 0; dc_sum = 0; dc_count = 0; @@ -2187,6 +2866,9 @@ cleanupWSA = 1; } #endif +#if DEBUG_IO + printf("open_rx_udp to IP %s port 0x%X\n", ip, port); +#endif quisk_using_udp = 1; rx_udp_socket = socket(PF_INET, SOCK_DGRAM, 0); if (rx_udp_socket != INVALID_SOCKET) { @@ -2208,8 +2890,10 @@ } else { sprintf(buf, "Capture from UDP %s port 0x%X", ip, port); - if (use_rx_udp == 17) + if (quisk_use_rx_udp == 17) quisk_sample_source(NULL, close_udp, read_rx_udp17); + else if (quisk_use_rx_udp == 10) + quisk_sample_source(NULL, close_udp10, read_rx_udp10); else quisk_sample_source(NULL, close_udp, quisk_read_rx_udp); #if DEBUG_IO @@ -2230,7 +2914,6 @@ { int rate; char * capt, * play, * mname, * mip, * mpname; - double dtmp; if (!PyArg_ParseTuple (args, "ssiiissiiiidsi", &capt, &play, &rate, @@ -2246,6 +2929,12 @@ &quisk_sound_state.mic_playback_rate )) return NULL; + +#if SAMPLES_FROM_FILE == 1 + QuiskWavWriteOpen(&hWav, "band.wav", 3, 2, 4, 48000, 1E3 / CLIP32); +#elif SAMPLES_FROM_FILE == 2 + QuiskWavReadOpen(&hWav, "band.wav", 3, 2, 4, 48000, CLIP32 / 1E6); +#endif if (quisk_sound_state.mic_out_volume > 0.7) // maximum value must leave headroom for quisk_sound_state.mic_out_volume = 0.7; // the amplitude and phase adjustments quisk_sound_state.playback_rate = QuiskGetConfigInt("playback_rate", 48000); @@ -2253,16 +2942,16 @@ //if (quisk_mic_preemphasis < 0.0 || quisk_mic_preemphasis > 1.0) // quisk_mic_preemphasis = 1.0; quisk_mic_clip = QuiskGetConfigDouble("mic_clip", 3.0); + agc_release_time = QuiskGetConfigDouble("agc_release_time", 1.0); strncpy(quisk_sound_state.dev_capt_name, capt, QUISK_SC_SIZE); strncpy(quisk_sound_state.dev_play_name, play, QUISK_SC_SIZE); strncpy(quisk_sound_state.mic_dev_name, mname, QUISK_SC_SIZE); strncpy(quisk_sound_state.name_of_mic_play, mpname, QUISK_SC_SIZE); strncpy(quisk_sound_state.mic_ip, mip, IP_SIZE); + strncpy(quisk_sound_state.IQ_server, QuiskGetConfigString("IQ_Server_IP", ""), IP_SIZE); fft_error = 0; quisk_open_sound(); quisk_open_mic(); - dtmp = QuiskGetConfigDouble("agc_release_time", 1.0); - agcTimeRelease = 1.0 - exp( - 1.0 / quisk_sound_state.playback_rate / dtmp); // long time constant for AGC release return get_state(NULL, NULL); } @@ -2273,6 +2962,9 @@ quisk_close_mic(); quisk_close_sound(); quisk_close_key(); +#if SAMPLES_FROM_FILE + QuiskWavClose(&hWav); +#endif Py_INCREF (Py_None); return Py_None; } @@ -2360,6 +3052,32 @@ return PyString_FromString(err_msg); } +static PyObject * pc_to_hermes(PyObject * self, PyObject * args) +{ + PyObject * byteArray; + + if (!PyArg_ParseTuple (args, "O", &byteArray)) + return NULL; + if ( ! PyByteArray_Check(byteArray)) { + PyErr_SetString (QuiskError, "Object is not a bytearray."); + return NULL; + } + if (PyByteArray_Size(byteArray) != 17 * 4) { + PyErr_SetString (QuiskError, "Bytearray size must be 17 * 4."); + return NULL; + } + memmove(quisk_pc_to_hermes, PyByteArray_AsString(byteArray), 17 * 4); + Py_INCREF (Py_None); + return Py_None; +} + +static PyObject * hermes_to_pc(PyObject * self, PyObject * args) +{ + if (!PyArg_ParseTuple (args, "")) + return NULL; + return PyByteArray_FromStringAndSize((char *)quisk_hermes_to_pc, 5 * 4); +} + static PyObject * invert_spectrum(PyObject * self, PyObject * args) { if (!PyArg_ParseTuple (args, "i", &quisk_invert_spectrum)) @@ -2451,6 +3169,14 @@ return Py_None; } +static PyObject * set_imd_level(PyObject * self, PyObject * args) +{ + if (!PyArg_ParseTuple (args, "i", &quiskImdLevel)) + return NULL; + Py_INCREF (Py_None); + return Py_None; +} + static PyObject * set_mic_out_volume(PyObject * self, PyObject * args) { int level; @@ -2482,11 +3208,8 @@ { double delay; // play extra silence after key-up, in milliseconds - if (!PyArg_ParseTuple (args, "iid", &quisk_sidetoneCtrl, &rit_freq, &delay)) + if (!PyArg_ParseTuple (args, "idid", &quisk_sidetoneCtrl, &sidetoneVolume, &rit_freq, &delay)) return NULL; - //printf("Sidetone control times 5 = %d\n", quisk_sidetoneCtrl * 5); - // Simulate log taper pot - sidetoneVolume = (exp(quisk_sidetoneCtrl * 0.006908) - 1) / 1000.0; sidetonePhase = cexp((I * 2.0 * M_PI * abs(rit_freq)) / quisk_sound_state.playback_rate); keyupDelay = (int)(quisk_sound_state.playback_rate *1e-3 * delay + 0.5); if (rxMode == 0 || rxMode == 1) @@ -2511,6 +3234,17 @@ return Py_None; } +static PyObject * tx_hold_state(PyObject * self, PyObject * args) +{ // Query or set the transmit hold state + int i; + + if (!PyArg_ParseTuple (args, "i", &i)) + return NULL; + if (i >= 0) // arg < 0 is a Query for the current value + quiskTxHoldState = i; + return PyInt_FromLong(quiskTxHoldState); +} + static PyObject * set_transmit_mode(PyObject * self, PyObject * args) { /* Set the radio to transmit mode */ if (!PyArg_ParseTuple (args, "i", &quisk_transmit_mode)) @@ -2527,6 +3261,14 @@ return Py_None; } +static PyObject * set_ctcss(PyObject * self, PyObject * args) +{ + if (!PyArg_ParseTuple (args, "d", &quisk_ctcss_freq)) + return NULL; + Py_INCREF (Py_None); + return Py_None; +} + static PyObject * set_key_down(PyObject * self, PyObject * args) { int down; @@ -2538,6 +3280,14 @@ return Py_None; } +static PyObject * set_PTT(PyObject * self, PyObject * args) +{ + if (!PyArg_ParseTuple (args, "i", &is_PTT_down)) + return NULL; + Py_INCREF (Py_None); + return Py_None; +} + static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the GUI thread { int i, j, k, m, n, index, ffts, ii, mm, m0, deltam; @@ -2608,10 +3358,12 @@ break; case 2: // LSB: bandwidth is below tx frequency case 8: + case 12: i = (int)(d1 - d2 + 0.5); break; case 3: // USB: bandwidth is above tx frequency case 7: + case 11: i = (int)(d1 + 0.5); break; } @@ -2865,6 +3617,7 @@ c3 = 1.36 * (fft_average[ipeak+1] - fft_average[ipeak - 1]) / (fft_average[ipeak-1] + fft_average[ipeak] + fft_average[ipeak+1]); freq = srate * (2 * (ipeak + c3) - fft_size) / 2 / fft_size; freq += rx_tune_freq; + //printf("freq %.0f rx_tune_freq %d vfo_screen %d vfo_audio %d\n", freq, rx_tune_freq, vfo_screen, vfo_audio); // printf("\n%5d %.4lf %.2lf k=%d\n", ipeak, c3, freq, k); measured_frequency = freq; //for (i = ipeak - 10; i <= ipeak + 10 && i >= 0 && i < fft_size; i++) @@ -3011,7 +3764,7 @@ rx_udp_clock = QuiskGetConfigDouble("rx_udp_clock", 122.88e6); graph_refresh = QuiskGetConfigInt("graph_refresh", 7); - use_rx_udp = QuiskGetConfigInt("use_rx_udp", 0); + quisk_use_rx_udp = QuiskGetConfigInt("use_rx_udp", 0); quisk_sound_state.sample_rate = rate; fft_sample_rate = rate; is_little_endian = 1; // Test machine byte order @@ -3110,12 +3863,16 @@ {"get_overrange", get_overrange, METH_VARARGS, "Return the count of overrange (clip) for the ADC."}, {"get_smeter", get_smeter, METH_VARARGS, "Return the S meter reading."}, {"invert_spectrum", invert_spectrum, METH_VARARGS, "Invert the input RF spectrum"}, + {"pc_to_hermes", pc_to_hermes, METH_VARARGS, "Send this block of control data to the Hermes device"}, + {"hermes_to_pc", hermes_to_pc, METH_VARARGS, "Get the block of control data from the Hermes device"}, {"record_app", record_app, METH_VARARGS, "Save the App instance."}, {"record_graph", record_graph, METH_VARARGS, "Record graph parameters."}, {"set_ampl_phase", quisk_set_ampl_phase, METH_VARARGS, "Set the sound card amplitude and phase corrections."}, + {"set_udp_tx_correct", quisk_set_udp_tx_correct, METH_VARARGS, "Set the UDP transmit corrections."}, {"set_agc", set_agc, METH_VARARGS, "Set the AGC parameters."}, {"set_squelch", set_squelch, METH_VARARGS, "Set the squelch parameter."}, {"get_squelch", get_squelch, METH_VARARGS, "Get the squelch state, 0 or 1."}, + {"set_ctcss", set_ctcss, METH_VARARGS, "Set the frequency of the repeater access tone."}, {"set_file_record", quisk_set_file_record, METH_VARARGS, "Set the state and names of the recording files."}, {"set_filters", set_filters, METH_VARARGS, "Set the receive audio I and Q channel filters."}, {"set_auto_notch", set_auto_notch, METH_VARARGS, "Set the auto notch on or off."}, @@ -3124,7 +3881,8 @@ {"set_record_state", set_record_state, METH_VARARGS, "Set the temp buffer record and playback state."}, {"set_rx_mode", set_rx_mode, METH_VARARGS, "Set the receive mode: CWL, USB, AM, etc."}, {"set_mic_out_volume", set_mic_out_volume, METH_VARARGS, "Set the level of the mic output for SoftRock transmit"}, - {"set_spot_level", set_spot_level, METH_VARARGS, "Set the spot level, or zero for no spot"}, + {"set_spot_level", set_spot_level, METH_VARARGS, "Set the spot level, or -1 for no spot"}, + {"set_imd_level", set_imd_level, METH_VARARGS, "Set the imd level 0 to 1000."}, {"set_sidetone", set_sidetone, METH_VARARGS, "Set the sidetone volume and frequency."}, {"set_transmit_mode", set_transmit_mode, METH_VARARGS, "Change the radio to transmit mode independent of key_down."}, {"set_volume", set_volume, METH_VARARGS, "Set the audio output volume."}, @@ -3135,10 +3893,13 @@ {"test_1", test_1, METH_VARARGS, "Test 1 function."}, {"test_2", test_2, METH_VARARGS, "Test 2 function."}, {"test_3", test_3, METH_VARARGS, "Test 3 function."}, + {"tx_hold_state", tx_hold_state, METH_VARARGS, "Query or set the transmit hold state."}, {"set_fdx", set_fdx, METH_VARARGS, "Set full duplex mode; ignore the key status."}, {"sound_devices", quisk_sound_devices, METH_VARARGS, "Return a list of available sound device names."}, + {"pa_sound_devices", quisk_pa_sound_devices, METH_VARARGS, "Return a list of available PulseAudio sound device names."}, {"sound_errors", quisk_sound_errors, METH_VARARGS, "Return a list of text strings with sound devices and error counts"}, - {"open_sound", open_sound, METH_VARARGS, "Open the the soundcard device."}, + {"open_sound", open_sound, METH_VARARGS, "Open the soundcard device."}, + {"open_file_play", open_file_play, METH_VARARGS, "Open a WAV file to play instead of the microphone."}, {"close_sound", close_sound, METH_VARARGS, "Stop the soundcard and release resources."}, {"capt_channels", quisk_capt_channels, METH_VARARGS, "Set the I and Q capture channel numbers"}, {"play_channels", quisk_play_channels, METH_VARARGS, "Set the I and Q playback channel numbers"}, @@ -3152,7 +3913,14 @@ {"open_key", open_key, METH_VARARGS, "Open access to the state of the key (CW or PTT)."}, {"open_rx_udp", open_rx_udp, METH_VARARGS, "Open a UDP port for capture."}, {"close_rx_udp", close_rx_udp, METH_VARARGS, "Close the UDP port used for capture."}, - {"set_key_down", set_key_down, METH_VARARGS, "Change the key up/down state for method \"\""}, + {"set_key_down", set_key_down, METH_VARARGS, "Change the key up/down state."}, + {"set_PTT", set_PTT, METH_VARARGS, "Change the PTT button state."}, + {"freedv_open", quisk_freedv_open, METH_VARARGS, "Open FreeDV."}, + {"freedv_close", quisk_freedv_close, METH_VARARGS, "Close FreeDV."}, + {"freedv_get_snr", quisk_freedv_get_snr, METH_VARARGS, "Return the signal to noise ratio in dB."}, + {"freedv_get_version", quisk_freedv_get_version, METH_VARARGS, "Return the codec2 API version."}, + {"freedv_get_rx_char", quisk_freedv_get_rx_char, METH_VARARGS, "Get text characters received from freedv."}, + {"freedv_set_options", (PyCFunction)quisk_freedv_set_options, METH_VARARGS|METH_KEYWORDS, "Set the freedv parameters."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff -Nru quisk-3.6.18/quisk_conf_defaults.py quisk-3.7.6/quisk_conf_defaults.py --- quisk-3.6.18/quisk_conf_defaults.py 2014-06-17 18:06:58.000000000 +0000 +++ quisk-3.7.6/quisk_conf_defaults.py 2015-09-14 14:23:24.000000000 +0000 @@ -1,4 +1,6 @@ -# Please do not change the configuration file quisk_conf_defaults.py. +### This is the file quisk_conf_defaults.py that contains defaults for Quisk. +### +# Please do not change this configuration file quisk_conf_defaults.py. # Instead copy one of the other quisk_conf_*.py files to your own # .quisk_conf.py and make changes there. For a normal sound card # configuration, copy quisk_conf_model.py to your .quisk_conf.py. @@ -47,7 +49,17 @@ # of the space needed for all the buttons. graph_width = 0.8 -# The graph_width parameter controls the width of Quisk unless a larger width is forced. +# The use of graph_width provides an optimal size for PC screens. But when running +# full screen, for example, on a tablet screen or a dedicated display, greater control +# is required. These options exactly set the Quisk window geometry. When window_width +# is used, graph_width is ignored. You may need to reduce button_font_size. +window_width = -1 # The window width in pixels, or -1 to use graph_width +window_height = -1 # The window height in pixels, or -1 for a default height +# This sets the window position. The -1 means use the default. For full screen, you +# probably want (0,0); that is, the upper left corner. +window_posX = -1 # The X position of the window. +window_posY = -1 # The Y position of the window. + # If the Quisk screen is too wide or the buttons are too crowded, perhaps due to a low screen # resolution, you can reduce the font sizes. Thanks to Christof, DJ4CM. button_font_size = 10 @@ -57,6 +69,7 @@ status_font_size = 14 config_font_size = 14 graph_font_size = 10 +graph_msg_font_size = 14 favorites_font_size = 14 # This controls the speed of the graph peak hold. Lower numbers give a longer time constant. @@ -127,6 +140,7 @@ Ubtn_text_play = unichr(0x25BA) # Play button Ubtn_text_rec = unichr(0x25CF) # Record button, a filled dot Ubtn_text_file_rec = "File " + unichr(0x25CF) # Record to file +Ubtn_text_file_play = "File " + unichr(0x25BA) # Play from file Ubtn_text_fav_add = unichr(0x2605) + unichr(0x2191) # Add to favorites Ubtn_text_fav_recall = unichr(0x2605) + unichr(0x2193) # Jump to favorites screen Ubtn_text_mem_add = unichr(0x24C2) + unichr(0x2191) # Add to memory @@ -138,12 +152,18 @@ Tbtn_text_play = "Play" Tbtn_text_rec = "Rec" Tbtn_text_file_rec = "File Rec" +Tbtn_text_file_play = "File Play" Tbtn_text_fav_add = ">Fav" Tbtn_text_fav_recall = "Fav" Tbtn_text_mem_add = "Save" Tbtn_text_mem_next = "Next" Tbtn_text_mem_del = "Del" +# These options control the button decorations that mark cycle and adjust buttons. +decorate_buttons = True # Do you want button decorations? +btn_text_cycle = unichr(0x21B7) # Character to display on multi-push buttons +btn_text_cycle_small = unichr(0x2193) # Smaller version when there is little space + # Station info display configuration, thanks to DJ4CM. This displays a window of station names # below the graph frequency (X axis). station_display_lines = 1 # number of station info display lines below the graph X axis @@ -161,6 +181,10 @@ # This converts from dB to S-units for the S-meter (it is in S-units). correct_smeter = 15.5 +# If you want the Spot button to key the transmitter immediately when you press it, set this option. +# Your hardware must have a working PTT button for this to work. +spot_button_keys_tx = False + # This is the fraction of spectrum to display from zero to one. It is needed if # the passband edges are not valid. Use 0.85 for the SDR-IQ. display_fraction = 1.00 @@ -173,12 +197,17 @@ # If you want to transmit recorded sound, then mic_sample_rate must equal playback_rate and both must be 48000. max_record_minutes = 1.00 -# Quisk can save recorded sound and samples to files. There is a button on the Config/Config screen -# to set the file names. You can set the initial names with these variables: +# Quisk can save radio sound and samples to files, and can play recorded sound. There is a button on the +# Config/Config screen to set the file names. You can set the initial names with these variables: file_name_audio = "" #file_name_audio = "/home/jim/tmp/qaudio.wav" file_name_samples = "" #file_name_samples = "C:/tmp/qsamples.wav" +# The file for playback must be 48 ksps, 16-bit, one channel (monophonic); the same as the mic input. When +# you play a file, the PTT button (if any) is pushed. There is a control to repeat the playback. This +# feature is intended to transmit a "CQ CQ" message, for example, during a contest. +file_name_playback = "" +#file_name_playback = "/home/jim/sounds/cqcq_contest.wav" # Thanks to Steve Murphy, KB8RWQ for the patch adding additional color control. # Thanks to Christof, DJ4CM for the patch adding additional color control. @@ -207,6 +236,8 @@ color_txline = 'red' # vertical line color for tx in graph color_rxline = 'green' # vertical line color for rx in graph color_notebook_txt = 'black' # text of notebook labels +color_graph_msg_fg = 'black' # text messages on the graph screen +color_graph_msg_bg = 'lemonchiffon2' # background of text messages on the graph screen # This is a dark color scheme designed by Steve Murphy, KB8RWQ. #color_bg = '#111111' @@ -232,6 +263,7 @@ #color_txline = 'red' #color_rxline = 'green' #color_notebook_txt = 'white' +##### Colors are incomplete filter_display = 1 # Display the filter bandwidth on the graph screen; 0 or 1; thanks to WB4JFI @@ -265,7 +297,8 @@ (255, 255, 58, 0) ) -# Quisk can access your sound card through PortAudio or through ALSA drivers. +# On Linux, Quisk can access your sound card through ALSA, PortAudio or PulseAudio. +# On Windows, Quisk uses DirectX for sound card access. # In PortAudio, soundcards have an index number 0, 1, 2, ... and a name. # The name can be something like "HDA NVidia: AD198x Analog (hw:0,0)" or @@ -286,10 +319,36 @@ #name_of_sound_capt = "plughw:1" #name_of_sound_capt = "default" -# Pulseaudio support added by Philip G. Lee. Many thanks! -# For PulseAudio support, use the name "pulse" and connect the streams -# to your hardware devices using a program like pavucontrol -#name_of_sound_capt = "pulse" +# It is usually best to use ALSA names because they provide minimum latency. But +# you may need to use PulseAudio to connect to other programs such as wsjt-x. + +# Pulseaudio support was added by Philip G. Lee. Many thanks! +# For PulseAudio devices, use the name "pulse:name" and connect the streams +# to your hardware devices using a PulseAudio control program. The name "pulse" +# alone refers to the "default" device. The PulseAudio names are quite long; +# for example "alsa_output.pci-0000_00_1b.0.analog-stereo". Look on the screen +# Config/Sound to see the device names. There is a description, a PulseAudio name, +# and for ALSA devices, the ALSA name. An example is: +# +# CM106 Like Sound Device Analog Stereo +# alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-stereo +# USB Sound Device USB Audio (hw:1,0) +# +# Instead of the long PulseAudio name, you can enter a substring of any of +# these three strings. +# +# Use the default pulse device for radio sound: +#name_of_sound_play = "pulse" +# Use a PulseAudio name for radio sound: +#name_of_sound_play = "pulse:alsa_output.usb-0d8c_USB_Sound_Device-00-Device.analog-stereo" +# Abbreviate the PulseAudio name: +#name_of_sound_play = "pulse:alsa_output.usb" +# Another abbreviation: +#name_of_sound_play = "pulse:CM106" + +# This controls whether the PulseAudio devices are shown in the device list. +# If you don't have PulseAudio, you must set this to False. Thanks to Simon, S54MI. +show_pulse_audio_devices = True # Normally you would capture and play on the same soundcard to avoid problems with the # two clocks running at slightly different rates. But you can define name_of_sound_play @@ -343,6 +402,8 @@ # The HiQSDR_BandDict sets the preselect (4 bits) on the X1 connector. HiQSDR_BandDict = {'160':1, '80':2, '40':3, '30':4, '20':5, '15':6, '17':7, '12':8, '10':9, '6':10, '500k':11, '137k':12 } +cw_delay = 0 # the delay for CW +# # For the original N2ADR hardware set this: # use_rx_udp = 1 # For the newer HiQSDR hardware set this: @@ -355,6 +416,23 @@ rx_udp_clock = 122880000 # ADC sample rate in Hertz sndp_active = True # Enable setting the hardware IP to rx_udp_ip +# Quisk has support for the Hermes-Lite project. This support will be extended to the original Hermes. +# Use the file hermes/quisk_conf.py as a model config file. The Hermes can obtain its IP address from +# DHCP. Set rx_udp_ip to the null string in this case. Or use rx_udp_ip to specify an IP address, but +# be sure it is unique and not in use by a DHCP server. The tx_ip and tx_audio_port are not used. +# Note: Setting the IP fails for the Hermes-Lite. +# You can set these options: +# use_rx_udp = 10 # This must be 10 to specify the original Ethernet Hermes protocol +# rx_udp_clock = 73728000 # The clock is 73.728 or 61.440 megahertz. Adjust for actual frequency. +# rx_udp_ip = "" # Null string for DHCP, else specify an IP address +# rx_udp_port = 1024 # Sample source UDP port. Must be 1024. +# There can be multiple Hermes devices on a network, but Quisk can only use one of these. If you have multiple +# hermes devices, you can use these to specify a unique device. +hermes_code_version = -1 # -1 for any Code Version, else the Code Version of the Hermes. +hermes_board_id = -1 # -1 for any Board ID, else the Board ID of the Hermes. +# The Hermes_BandDict sets the 7 bits on the J16 connector. The default is 0x00. +Hermes_BandDict = {'160':0b0000001, '80':0b0000010, '60':0b0000100, '40':0b0001000, '30':0b0010000, '20':0b0100000, '15':0b1000000} + # Vendor and product ID's for the SoftRock usb_vendor_id = 0x16c0 usb_product_id = 0x05dc @@ -381,9 +459,9 @@ # If you use quisk_hardware_fixed.py, this is the fixed VFO frequency in Hertz fixed_vfo_freq = 7056000 -# Softrock hardware must be polled to get the key up/down state when using CW mode. -# This is the time between polls in milliseconds. Use zero to turn off the poll if you -# only use SSB, or if your SoftRock does not have a key jack and USB control. +# Softrock hardware must be polled to get the key up/down state. This is the time between +# polls in milliseconds. Use zero to turn off the poll if your SoftRock does not have a key +# jack and USB control. key_poll_msec = 0 #key_poll_msec = 5 # Softrock transmit hardware uses semi break-in for CW operation. This is the time in @@ -427,8 +505,32 @@ sample_playback_name = "" #sample_playback_name = "hw:Loopback,0" +# This option sends radio playback sound to a UDP device. Some SDR hardware devices have an +# audio codec that can play radio sound with less latency than a soundcard. The sample rate +# is the same as the soundcard sample rate, but probably you will want 48000 sps. The UDP +# data consists of two bytes of zero, followed by the specified number of samples. Each +# sample consists of two bytes (a short) of I data and two bytes of Q data in little-endian order. +# For radio_sound_nsamples = 360, the total number of UDP data bytes is 1442. +#radio_sound_ip = "192.168.1.201" # IP address of play device +#radio_sound_port = 0x1234 # port number for audio +#radio_sound_nsamples = 360 # number of samples for each block; maximum 367 +#name_of_sound_play = '' # do not send audio to a soundcard (optional) +#playback_rate = 48000 # set the rate as required by the hardware +# +# This option receives microphone samples from a UDP device. The UDP +# data consists of two bytes of zero, followed by the specified number of samples. Each +# sample consists of two bytes (a short) of monophonic microphone data in little-endian order. +# For radio_sound_mic_nsamples = 720, the total number of UDP data bytes is 1442. +#radio_sound_mic_ip = "192.168.1.201" # IP address of mic device +#radio_sound_mic_port = 0x1234 # port number for audio +#radio_sound_mic_nsamples = 720 # number of samples for each block; maximum 734 +#radio_sound_mic_boost = 0 # Mic boost control: 0 == no boost, else 1 for boost +#microphone_name = "" # Do not capture the mic from a soundcard +#mic_sample_rate = 48000 # Microphone capture sample rate in Hertz, must be 48000 + # You can control Quisk from Hamlib. Set the Hamlib rig to 2 and the device for rig 2 to # localhost:4575, or other hamlib_port as used by Quisk. +hamlib_ip = "localhost" # IP address for Hamlib hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too. #hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port. #hamlib_port = 0 # Turn off Hamlib control. @@ -481,14 +583,22 @@ # ("hw:1", 1, 1) # numid of capture on/off control, turn on with 1; # ] -# If you want Quisk to add a button to generate a 2-tone IMD test signal, -# set this to 1. This feature requires the microphone to work. -add_imd_button = 0 - # If you want Quisk to add a full duplex button (transmit and receive at the # same time), set this to 1. add_fdx_button = 0 +# These buttons add up to two additional mode buttons after CW, USB, etc. +# Set this to add the FDV mode button for digital voice: +add_freedv_button = 1 +# For freedv, this is the text message to send: +freedv_tx_msg = '' +#freedv_tx_msg = 'N2XXX Jim \n' +# This is the list of FreeDV modes and their index number. The starting mode is the first listed. +freedv_modes = (('Mode 1600', 0), ('Mode 700', 1), ('Mode 700B', 2), ('Future 3', 3), ('Future 4', 4)) + +# If you want Quisk to add a button to generate a 2-tone IMD test signal, +# set this to 1. This feature requires the microphone to work. +add_imd_button = 0 # If you want to write your own I/Q filter and demodulation module, set # this to the name of the button to add, and change extdemod.c. # add_extern_demod = "WFM" @@ -504,8 +614,9 @@ FilterBwAM = (4000, 5000, 6000, 8000, 10000, 9000) FilterBwFM = (8000, 10000, 12000, 15000, 17000, 20000) FilterBwIMD = FilterBwSSB -FilterBwDGT= (1600, 3200, 4800, 10000, 20000, 20000) +FilterBwDGT = (1600, 3200, 4800, 10000, 20000, 20000) FilterBwEXT = (8000, 10000, 12000, 15000, 17000, 20000) +FilterBwFDV = (1200, 1400, 1600, '', '', '') # This is the data used to draw colored lines on the frequency X axis to # indicate CW and Phone sub-bands. You can make it anything you want. @@ -748,6 +859,22 @@ bandLabels = ['Audio', '160', '80', ('60',) * 5, '40', '30', '20', '17', '15', '12', '10', ('Time',) * len(bandTime)] +# Quisk can implement the frequency shift needed for repeaters. If the repeater frequency +# is on the favorites screen, and you tune close (500 Hz) to that frequency, and there +# is an entry in the "offset" column, and the mode is FM, and this parameter is True, +# then Quisk will shift the Tx frequency by the offset when transmitting. Your hardware +# file must define the method RepeaterOffset(self, offset=None). +do_repeater_offset = False + +# If you use a transverter, you need to tune your hardware to a frequency lower than +# the frequency displayed by Quisk. For example, if you have a 2 meter transverter, +# you may need to tune your hardware from 28 to 30 MHz to receive 144 to 146 MHz. +# Enter the transverter offset in Hertz in this dictionary. For this to work, your +# hardware must support it. Currently, the HiQSDR, SDR-IQ and SoftRock are supported. +bandTransverterOffset = { +# '2': 144000000 - 28000000 +} + # If you get your I/Q samples from a sound card, you will need to correct the # amplitude and phase for inaccuracies in the analog hardware. The data is # entered using the controls from the "Rx Phase" button on the config screen. @@ -824,8 +951,10 @@ # hot_key_ptt2 = ord(' ') # hot_key_ptt2 = ord('A') -# If you want Quisk to generate a sidetone, include self.use_sidetone = 1 -# in the __init__ method of your hardware file. +# This controls whether Quisk will display a sidetone volume control "Sto", +# and whether Quisk will gererate a CW sidetone. +use_sidetone = 0 # No sidetone, and no Sto sidetone volume control +# use_sidetone = 1 # Add the Sto sidetone volume control, and generate a sidetone # # If you are using keying, key-down throws away the current capture buffer # and starts a sidetone with a rise time of 5 milliseconds. For @@ -862,3 +991,6 @@ # This is the AGC release time in seconds. It must be greater than zero. It is the time # constant for gain recovery after a strong signal disappears. agc_release_time = 1.0 + +#IP Adddress for remote pulseaudio IQ server (pulseaudio). +IQ_Server_IP = "" diff -Nru quisk-3.6.18/quisk.h quisk-3.7.6/quisk.h --- quisk-3.6.18/quisk.h 2014-06-02 13:44:43.000000000 +0000 +++ quisk-3.7.6/quisk.h 2015-09-14 15:49:25.000000000 +0000 @@ -17,6 +17,7 @@ #define IMD_TONE_2 1600 #define INTERP_FILTER_TAPS 85 // interpolation filter #define MIC_OUT_RATE 48000 // mic post-processing sample rate +#define PA_LIST_SIZE 16 // max number of pulseaudio devices // Test the audio: 0 == No test; normal operation; // 1 == Copy real data to the output; 2 == copy imaginary data to the output; @@ -75,6 +76,10 @@ complex double dc_remove; // filter to remove DC from samples double save_sample; // Used to delay the I or Q sample char msg1[QUISK_SC_SIZE]; // string for information message + int stream_dir_record; // 1 for recording, 0 for playback + char server[IP_SIZE]; // server string for remote pulseaudio + int stream_format; // format of pulseaudio device + volatile int cork_status; // 1 for corked, 0 for uncorked } ; struct sound_conf { @@ -108,15 +113,39 @@ int mic_channel_I; // channel number for microphone: 0, 1, ... int mic_channel_Q; double mic_out_volume; + char IQ_server[IP_SIZE]; //IP address of optional streaming IQ server (pulseaudio) } ; enum quisk_rec_state { IDLE, RECORD_RADIO, RECORD_MIC, - PLAYBACK } ; + PLAYBACK, + PLAY_FILE } ; extern enum quisk_rec_state quisk_record_state; +struct QuiskWav { // data to create a WAV or RAW audio file + double scale; + int sample_rate; + short format; // RAW is 0; PCM integer is 1; IEEE float is 3. + short nChan; + short bytes_per_sample; + FILE * fp; + unsigned int samples; + int fpStart; + int fpEnd; + int fpPos; +} ; + +void QuiskWavClose(struct QuiskWav *); +int QuiskWavWriteOpen(struct QuiskWav *, char *, short, short, short, int, double); +void QuiskWavWriteC(struct QuiskWav *, complex double *, int); +void QuiskWavWriteD(struct QuiskWav *, double *, int); +int QuiskWavReadOpen(struct QuiskWav *, char *, short, short, short, int, double); +void QuiskWavReadC(struct QuiskWav *, complex double *, int); +void QuiskWavReadD(struct QuiskWav *, double *, int); +void QuiskMeasureRate(const char *, int); + extern struct sound_conf quisk_sound_state, * pt_quisk_sound_state; extern int mic_max_display; // display value of maximum microphone signal level extern int quiskSpotLevel; // 0 for no spotting; else the level 10 to 1000 @@ -131,6 +160,12 @@ extern int quisk_noise_blanker; // Noise blanker level, 0 for off extern int quisk_sidetoneCtrl; // sidetone control value 0 to 1000 extern double quisk_audioVolume; // volume control for radio sound playback, 0.0 to 1.0 +extern int quiskImdLevel; // level for rxMode IMD +extern int quiskTxHoldState; // state machine for Tx wait for repeater frequency shift +extern double quisk_ctcss_freq; // frequency in Hertz +extern unsigned char quisk_pc_to_hermes[17 * 4]; // Data to send from the PC to the Hermes hardware +extern int quisk_use_rx_udp; // Method of access to UDP hardware +extern complex double cRxFilterOut(complex double, int); extern PyObject * quisk_set_spot_level(PyObject * , PyObject *); extern PyObject * quisk_get_tx_filter(PyObject * , PyObject *); @@ -140,10 +175,19 @@ extern PyObject * quisk_play_channels(PyObject * , PyObject *); extern PyObject * quisk_micplay_channels(PyObject * , PyObject *); extern PyObject * quisk_sound_devices(PyObject * , PyObject *); +extern PyObject * quisk_pa_sound_devices(PyObject * , PyObject *); extern PyObject * quisk_sound_errors(PyObject *, PyObject *); extern PyObject * quisk_set_file_record(PyObject *, PyObject *); extern PyObject * quisk_set_tx_audio(PyObject *, PyObject *, PyObject *); extern PyObject * quisk_is_vox(PyObject *, PyObject *); +extern PyObject * quisk_set_udp_tx_correct(PyObject *, PyObject *); + +extern PyObject * quisk_freedv_open(PyObject *, PyObject *); +extern PyObject * quisk_freedv_close(PyObject *, PyObject *); +extern PyObject * quisk_freedv_get_snr(PyObject *, PyObject *); +extern PyObject * quisk_freedv_get_version(PyObject *, PyObject *); +extern PyObject * quisk_freedv_get_rx_char(PyObject *, PyObject *); +extern PyObject * quisk_freedv_set_options(PyObject *, PyObject *, PyObject *); // These function pointers are the Start/Stop/Read interface for // the SDR-IQ and any other C-language extension modules that return @@ -173,6 +217,19 @@ int quisk_extern_demod(complex double *, int, double); void quisk_tmp_microphone(complex double *, int); void quisk_tmp_record(complex double * , int, double); +void quisk_file_microphone(complex double *, int); +void quisk_file_playback(complex double *, int, double); +void quisk_tmp_playback(complex double *, int, double); +void quisk_hermes_tx_add(complex double *, int); +void quisk_hermes_tx_send(void); +void quisk_udp_mic_error(char *); +void quisk_check_freedv_mode(void); + +// Functions supporting digital voice codecs +typedef int (* ty_dvoice_codec_rx)(complex double *, double *, int, int); +typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int); +extern ty_dvoice_codec_rx pt_quisk_freedv_rx; +extern ty_dvoice_codec_tx pt_quisk_freedv_tx; // Driver function definitions================================================= int quisk_read_alsa(struct sound_dev *, complex double *); @@ -188,7 +245,9 @@ int quisk_read_pulseaudio(struct sound_dev *, complex double *); void quisk_play_pulseaudio(struct sound_dev *, int, complex double *, int, double); void quisk_start_sound_pulseaudio(struct sound_dev **, struct sound_dev **); -void quisk_close_sound_pulseaudio(struct sound_dev **, struct sound_dev **); +void quisk_close_sound_pulseaudio(void); +void quisk_cork_pulseaudio(struct sound_dev *, int); +void quisk_flush_pulseaudio(struct sound_dev *); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ /* @@ -212,6 +271,7 @@ #define QuiskSleepMicrosec (*( void (*) (int) )Quisk_API[5]) #define QuiskPrintTime (*( void (*) (const char *, int) )Quisk_API[6]) #define quisk_sample_source (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read) )Quisk_API[7]) +#define quisk_dvoice_freedv (*( void (*) (ty_dvoice_codec_rx, ty_dvoice_codec_tx) )Quisk_API[8]) #else // Used to export symbols from _quisk in quisk.c @@ -223,10 +283,11 @@ void QuiskSleepMicrosec(int); void QuiskPrintTime(const char *, int); void quisk_sample_source(ty_sample_start, ty_sample_stop, ty_sample_read); +void quisk_dvoice_freedv(ty_dvoice_codec_rx, ty_dvoice_codec_tx); #define QUISK_API_INIT { \ &quisk_sound_state, &QuiskGetConfigInt, &QuiskGetConfigDouble, &QuiskGetConfigString, &QuiskTimeSec, \ - &QuiskSleepMicrosec, &QuiskPrintTime, &quisk_sample_source \ + &QuiskSleepMicrosec, &QuiskPrintTime, &quisk_sample_source, &quisk_dvoice_freedv \ } #endif diff -Nru quisk-3.6.18/quisk_hardware_model.py quisk-3.7.6/quisk_hardware_model.py --- quisk-3.6.18/quisk_hardware_model.py 2014-06-17 12:35:48.000000000 +0000 +++ quisk-3.7.6/quisk_hardware_model.py 2015-07-12 18:10:38.000000000 +0000 @@ -25,7 +25,8 @@ self.conf = conf # Config file module self.rf_gain_labels = () # Do not add the Rf Gain button self.correct_smeter = conf.correct_smeter # Default correction for S-meter - self.use_sidetone = 0 # Don't show the sidetone volume control + self.use_sidetone = conf.use_sidetone # Copy from the config file + self.transverter_offset = 0 # Calculate the transverter offset in Hertz for each band def open(self): # Called once to open the Hardware # Return an informative message for the config screen t = "Capture from sound card %s." % self.conf.name_of_sound_capt @@ -66,7 +67,10 @@ pass def ChangeBand(self, band): # band is a string: "60", "40", "WWV", etc. - pass + try: + self.transverter_offset = self.conf.bandTransverterOffset[band] + except: + self.transverter_offset = 0 def OnBtnFDX(self, is_fdx): # Status of FDX button, 0 or 1 pass def HeartBeat(self): # Called at about 10 Hz by the main diff -Nru quisk-3.6.18/quisk.py quisk-3.7.6/quisk.py --- quisk-3.6.18/quisk.py 2014-06-22 14:48:55.000000000 +0000 +++ quisk-3.7.6/quisk.py 2015-09-14 14:14:26.000000000 +0000 @@ -50,9 +50,12 @@ help='Specify the configuration file path') parser.add_option('', '--config2', dest='config_file_path2', default='', help='Specify a second configuration file to read after the first') +parser.add_option('', '--local', dest='local_option', default='', + help='Specify a custom option that you have programmed yourself') argv_options = parser.parse_args()[0] ConfigPath = argv_options.config_file_path # Get config file path ConfigPath2 = argv_options.config_file_path2 +LocalOption = argv_options.local_option if not ConfigPath: # Use default path if sys.platform == 'win32': path = os.getenv('HOMEDRIVE', '') + os.getenv('HOMEPATH', '') @@ -364,6 +367,8 @@ mode = 'CW' elif mode == 'CWL': # Is this what CWR means? mode = 'CWR' + elif mode == 'DGT-FM': + mode = 'FM' elif mode[0:4] == 'DGT-': mode = 'USB' self.Reply('Mode', mode, 'Passband', self.app.filter_bandwidth, 0) @@ -489,13 +494,14 @@ else: tab = self.notebook.GetSelection() -class ConfigStatus(wx.Panel): +class ConfigStatus(wx.ScrolledWindow): """Display the status screen.""" def __init__(self, parent, width, fft_size): - wx.Panel.__init__(self, parent) + wx.ScrolledWindow.__init__(self, parent) self.Bind(wx.EVT_PAINT, self.OnPaint) self.width = width self.fft_size = fft_size + self.scroll_height = None self.interupts = 0 self.read_error = -1 self.write_error = -1 @@ -545,7 +551,8 @@ # Make and blit variable data self.MakeBitmap() dc = wx.PaintDC(self) - dc.Blit(0, 0, self.mem_width, self.mem_height, self.mem_dc, 0, 0) + x, y = self.GetViewStart() + dc.Blit(0, 0, self.mem_width, self.mem_height, self.mem_dc, x, y) def MakeRow2(self, *args): for col in range(len(args)): t = args[col] @@ -621,6 +628,9 @@ self.MakeRow2("Capture radio samples", "UDP", application.sample_rate, self.latencyCapt, self.read_error) for use, name, rate, latency, errors in QS.sound_errors(): self.MakeRow2(use, name, rate, latency, errors) + if self.scroll_height is None: + self.scroll_height = self.mem_y + self.dy + self.SetScrollbars(1, 1, 100, self.scroll_height) def OnGraphData(self, data=None): if not self.tabstops2: # Must wait for sound to start self.MakeTabstops() @@ -629,13 +639,13 @@ self.read_error, self.write_error, self.underrun_error, self.latencyCapt, self.latencyPlay, self.interupts, self.fft_error, self.mic_max_display, self.data_poll_usec - ) = QS.get_state() + ) = QS.get_state() self.mic_max_display = 20.0 * math.log10((self.mic_max_display + 1) / 32767.0) self.RefreshRect(self.mem_rect) -class ConfigConfig(wx.Panel): +class ConfigConfig(wx.ScrolledWindow): def __init__(self, parent, width): - wx.Panel.__init__(self, parent) + wx.ScrolledWindow.__init__(self, parent) self.width = width self.SetBackgroundColour(conf.color_graph) self.SetForegroundColour(conf.color_graphlabels) @@ -646,7 +656,7 @@ self.chary = chary = self.GetCharHeight() self.dy = self.chary self.rx_phase = None - # Make controls + # Make controls FIRST column tab0 = charx * 4 # Receive phase rx = wx.StaticText(self, -1, "Adjust receive amplitude and phase") @@ -660,20 +670,11 @@ tab1 = tab0 + x1 + charx * 2 tab2 = tab1 + x2 tab3 = tab2 + charx * 8 - self.y = y2 + self.chary + self.y = self.yyy = y2 + self.chary self.dy = y2 * 12 // 10 self.offset = (y2 - y1) // 2 rx.SetPosition((tab0, self.y)) ctrl.SetPosition((tab1, self.y - self.offset)) - # File for recording speaker audio - b = wx.Button(self, -1, "File...", pos=(tab3, self.y - self.offset)) - self.Bind(wx.EVT_BUTTON, self.OnBtnFileAudio, b) - x3, y3 = b.GetSizeTuple() - tab4 = tab3 + x3 + charx - self.static_audio_text = "Record audio to WAV file " - self.static_audio_path = conf.file_name_audio - self.static_audio = wx.StaticText(self, -1, self.static_audio_text + self.static_audio_path, pos=(tab4, self.y)) - QS.set_file_record(0, self.static_audio_path) self.y += self.dy # Transmit phase self.tx_phase = ctrl = wx.Button(self, -1, "Tx Phase...") @@ -682,13 +683,6 @@ ctrl.Enable(0) tx.SetPosition((tab0, self.y)) ctrl.SetPosition((tab1, self.y - self.offset)) - # File for recording samples - b = wx.Button(self, -1, "File...", pos=(tab3, self.y - self.offset)) - self.Bind(wx.EVT_BUTTON, self.OnBtnFileSamples, b) - self.static_samples_text = "Record samples to WAV file " - self.static_samples_path = conf.file_name_samples - self.static_samples = wx.StaticText(self, -1, self.static_samples_text + self.static_samples_path, pos=(tab4, self.y)) - QS.set_file_record(1, self.static_samples_path) self.y += self.dy # Choice (combo) box for decimation lst = Hardware.VarDecimGetChoices() @@ -710,13 +704,48 @@ # Transmit level controls if hasattr(Hardware, "SetTxLevel"): SliderBoxH(self, "Tx level %d%% ", 100, 0, 100, self.OnTxLevel, True, (tab0, self.y), tab2-tab0) + self.y += self.dy level = conf.digital_tx_level - SliderBoxH(self, "Digital Tx level %d%% ", level, 0, level, self.OnDigitalTxLevel, True, (tab3, self.y), tab2-tab0) + SliderBoxH(self, "Digital Tx level %d%% ", level, 0, level, self.OnDigitalTxLevel, True, (tab0, self.y), tab2-tab0) self.y += self.dy # mic_out_volume - level = int(conf.mic_out_volume * 100.0 + 0.1) - SliderBoxH(self, "SftRock Tx level %d%% ", level, 0, 100, self.OnSrTxLevel, True, (tab0, self.y), tab2-tab0) + if conf.name_of_mic_play: + level = int(conf.mic_out_volume * 100.0 + 0.1) + SliderBoxH(self, "SftRock Tx level %d%% ", level, 0, 100, self.OnSrTxLevel, True, (tab0, self.y), tab2-tab0) + self.y += self.dy + self.scroll_height = self.y + # Make controls SECOND column + self.y = self.yyy + # File for recording speaker audio + b = wx.Button(self, -1, "File...", pos=(tab3, self.y - self.offset)) + self.Bind(wx.EVT_BUTTON, self.OnBtnFileAudioRec, b) + x3, y3 = b.GetSizeTuple() + tab4 = tab3 + x3 + charx + self.static_frec_text = "Record Rx audio to WAV file " + self.static_frec_path = conf.file_name_audio + self.static_frec = wx.StaticText(self, -1, self.static_frec_text + self.static_frec_path, pos=(tab4, self.y)) + QS.set_file_record(0, self.static_frec_path) + self.y += self.dy + # File for recording samples + b = wx.Button(self, -1, "File...", pos=(tab3, self.y - self.offset)) + self.Bind(wx.EVT_BUTTON, self.OnBtnFileSamples, b) + self.static_samples_text = "Record Rx samples to WAV file " + self.static_samples_path = conf.file_name_samples + self.static_samples = wx.StaticText(self, -1, self.static_samples_text + self.static_samples_path, pos=(tab4, self.y)) + QS.set_file_record(1, self.static_samples_path) + self.y += self.dy + # File for playing a file to the mic input + b = wx.Button(self, -1, "File...", pos=(tab3, self.y - self.offset)) + self.Bind(wx.EVT_BUTTON, self.OnBtnFileAudioPlay, b) + self.static_fplay_text = "Transmit sound from WAV file " + self.static_fplay_path = conf.file_name_playback + self.static_fplay = wx.StaticText(self, -1, self.static_fplay_text + self.static_fplay_path, pos=(tab4, self.y)) + self.y += self.dy + SliderBoxH(self, "Repeat secs %.1f ", 0, 0, 100, self.OnPlayFileRepeat, True, (tab4, self.y), tab2-tab0, 0.1) self.y += self.dy + if self.y > self.scroll_height: + self.scroll_height = self.y + self.SetScrollbars(1, 1, 100, self.scroll_height) def OnTxLevel(self, event): application.tx_level = event.GetEventObject().GetValue() Hardware.SetTxLevel() @@ -737,8 +766,8 @@ application.w_phase.Raise() else: application.w_phase = QAdjustPhase(self, self.width, rx_tx) - def OnBtnFileAudio(self, event): - dr, fn = os.path.split(self.static_audio_path) + def OnBtnFileAudioRec(self, event): + dr, fn = os.path.split(self.static_frec_path) dlg = wx.FileDialog(self, 'Choose WAV file', dr, fn, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT, wildcard="Wave files (*.wav)|*.wav") if dlg.ShowModal() == wx.ID_OK: @@ -747,8 +776,20 @@ path = path + '.wav' QS.set_file_record(0, path) application.btn_file_record.Enable() - self.static_audio.SetLabel(self.static_audio_text + path) - self.static_audio_path = path + self.static_frec.SetLabel(self.static_frec_text + path) + self.static_frec_path = path + dlg.Destroy() + def OnBtnFileAudioPlay(self, event): + dr, fn = os.path.split(self.static_fplay_path) + dlg = wx.FileDialog(self, 'Choose WAV file', dr, fn, style=wx.FD_OPEN, + wildcard="Wave files (*.wav)|*.wav") + if dlg.ShowModal() == wx.ID_OK: + path = dlg.GetPath() + if path[-4:].lower() != '.wav': + path = path + '.wav' + application.btnFilePlay.Enable(QS.open_file_play(path) == 0) + self.static_fplay.SetLabel(self.static_fplay_text + path) + self.static_fplay_path = path dlg.Destroy() def OnBtnFileSamples(self, event): dr, fn = os.path.split(self.static_samples_path) @@ -763,11 +804,13 @@ self.static_samples.SetLabel(self.static_samples_text + path) self.static_samples_path = path dlg.Destroy() + def OnPlayFileRepeat(self, event): + application.file_play_repeat = event.GetEventObject().GetValue() * 0.1 -class ConfigSound(wx.Panel): +class ConfigSound(wx.ScrolledWindow): """Display the available sound devices.""" def __init__(self, parent, width): - wx.Panel.__init__(self, parent) + wx.ScrolledWindow.__init__(self, parent) self.Bind(wx.EVT_PAINT, self.OnPaint) self.width = width self.dev_capt, self.dev_play = QS.sound_devices() @@ -778,8 +821,13 @@ self.charx = self.GetCharWidth() self.chary = self.GetCharHeight() self.dy = self.chary + height = self.chary * (3 + len(self.dev_capt) + len(self.dev_play)) + if sys.platform != 'win32' and conf.show_pulse_audio_devices: + height += self.chary * (3 + 3 * len(application.pa_dev_capt) + 3 * len(application.pa_dev_play)) + self.SetScrollbars(1, 1, 100, height) def OnPaint(self, event): dc = wx.PaintDC(self) + self.DoPrepareDC(dc) dc.SetFont(self.font) dc.SetTextForeground(conf.color_graphlabels) x0 = self.charx @@ -794,11 +842,33 @@ for name in self.dev_play: dc.DrawText(' ' + name, x0, self.y) self.y += self.dy + if sys.platform != 'win32' and conf.show_pulse_audio_devices: + dc.DrawText("Available PulseAudio devices for capture (sources):", x0, self.y) + self.y += self.dy + for n0, n1, n2 in application.pa_dev_capt: + dc.DrawText(' ' * 4 + n1, x0, self.y) + self.y += self.dy + dc.DrawText(' ' * 8 + n0, x0, self.y) + self.y += self.dy + if n2: + dc.DrawText(' ' * 8 + n2, x0, self.y) + self.y += self.dy + dc.DrawText("Available PulseAudio devices for playback (sinks):", x0, self.y) + self.y += self.dy + for n0, n1, n2 in application.pa_dev_play: + dc.DrawText(' ' * 4 + n1, x0, self.y) + self.y += self.dy + dc.DrawText(' ' * 8 + n0, x0, self.y) + self.y += self.dy + if n2: + dc.DrawText(' ' * 8 + n2, x0, self.y) + self.y += self.dy class ConfigFavorites(wx.grid.Grid): def __init__(self, parent, width): wx.grid.Grid.__init__(self, parent) self.changed = False + self.RepeaterDict = {} self.SetBackgroundColour(conf.color_graph) self.SetForegroundColour(conf.color_graphlabels) font = wx.Font(conf.favorites_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL, @@ -815,21 +885,29 @@ self.SetGridLineColour(conf.color_graphlabels) self.SetDefaultRowSize(self.GetCharHeight()+3) self.Bind(wx.grid.EVT_GRID_LABEL_RIGHT_CLICK, self.OnRightClickLabel) + self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLeftClickLabel) self.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnChange) self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.OnLeftDClick) - self.CreateGrid(0, 4) + self.CreateGrid(0, 6) self.EnableDragRowSize(False) w = self.GetTextExtent(' 999 ')[0] self.SetRowLabelSize(w) self.SetColLabelValue(0, 'Name') - self.SetColLabelValue(1, 'Frequency') - self.SetColLabelValue(2, 'Mode') + self.SetColLabelValue(1, 'Freq MHz') + self.SetColLabelValue(2, 'Mode') # This column has a choice editor self.SetColLabelValue(3, 'Description') + self.SetColLabelValue(4, 'Offset kHz') + self.SetColLabelValue(5, 'Tone Hz') w = self.GetTextExtent("xFrequencyx")[0] self.SetColSize(0, w * 3 // 2) self.SetColSize(1, w) - self.SetColSize(2, w // 2) - self.SetColSize(3, width - w * 3 - self.GetRowLabelSize() - 20) + self.SetColSize(4, w) + self.SetColSize(5, w) + self.SetColSize(2, w) + ww = width - w * 7 - self.GetRowLabelSize() - 20 + if ww < w: + ww = w + self.SetColSize(3, ww) if conf.favorites_file_path: self.init_path = conf.favorites_file_path else: @@ -856,6 +934,18 @@ # Make a timer self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnTimer) + def SetModeEditor(self, mode_names): + self.mode_names = mode_names + for row in range(self.GetNumberRows()): + self.SetCellEditor(row, 2, wx.grid.GridCellChoiceEditor(mode_names, True)) + def FormatFloat(self, freq): + freq = "%.6f" % freq + for i in range(3): + if freq[-1] == '0': + freq = freq[:-1] + else: + break + return freq def ReadIn(self): try: fp = open(self.init_path, 'rb') @@ -868,27 +958,46 @@ self.AppendRows() fields = lines[row].split('|') for col in range(len(fields)): - self.SetCellValue(row, col, fields[col].strip()) + if col == 1: # Correct old entries made in Hertz + freq = fields[1] + try: + freq = float(freq) + except: + pass + else: + if freq > 30000.0: # Must be in Hertz + freq *= 1E-6 + fields[1] = self.FormatFloat(freq) + if col <= 5: + self.SetCellValue(row, col, fields[col].strip()) + self.MakeRepeaterDict() def WriteOut(self): + ncols = self.GetNumberCols() + if ncols != 6: + print ("Bad logic in favorites WriteOut()") + return self.changed = False try: fp = open(self.init_path, 'wb') except: return for row in range(self.GetNumberRows()): - t = self.GetCellValue(row, 0) - for col in range(1, self.GetNumberCols()): + out = [] + for col in range(0, ncols): cell = self.GetCellValue(row, col) cell = cell.replace('|', ';') - t = "%s | %s" % (t, cell) - t = t + '\r\n' + out.append(cell) + t = "%20s | %10s | %10s | %30s | %10s | %10s\r\n" % tuple(out) fp.write(t) fp.close() def AddNewFavorite(self): self.InsertRows(0) self.SetCellValue(0, 0, 'New station'); - self.SetCellValue(0, 1, str(application.rxFreq + application.VFO)) + freq = (application.rxFreq + application.VFO) * 1E-6 # convert to megahertz + freq = self.FormatFloat(freq) + self.SetCellValue(0, 1, freq) self.SetCellValue(0, 2, application.mode); + self.SetCellEditor(0, 2, wx.grid.GridCellChoiceEditor(self.mode_names, True)) self.OnChange() def OnRightClickLabel(self, event): event.Skip() @@ -896,25 +1005,31 @@ if self.menurow >= 0: pos = event.GetPosition() self.PopupMenu(self.popupmenu, pos) + def OnLeftClickLabel(self, event): + pass def OnLeftDClick(self, event): # Thanks to Christof, DJ4CM self.menurow = event.GetRow() - self.OnPopupTuneto(event) + if self.menurow >= 0: + self.OnPopupTuneto(event) def OnPopupAppend(self, event): self.InsertRows(self.menurow + 1) + self.SetCellEditor(self.menurow + 1, 2, wx.grid.GridCellChoiceEditor(self.mode_names, True)) self.OnChange() def OnPopupInsert(self, event): self.InsertRows(self.menurow) + self.SetCellEditor(self.menurow, 2, wx.grid.GridCellChoiceEditor(self.mode_names, True)) self.OnChange() def OnPopupDelete(self, event): self.DeleteRows(self.menurow) if self.GetNumberRows() < 1: self.AppendRows() + self.SetCellEditor(0, 2, wx.grid.GridCellChoiceEditor(self.mode_names, True)) self.OnChange() def OnPopupMoveUp(self, event): row = self.menurow if row < 1: return - for i in range(4): + for i in range(self.GetNumberCols()): c = self.GetCellValue(row - 1, i) self.SetCellValue(row - 1, i, self.GetCellValue(row, i)) self.SetCellValue(row, i, c) @@ -922,7 +1037,7 @@ row = self.menurow if row == self.GetNumberRows() - 1: return - for i in range(4): + for i in range(self.GetNumberCols()): c = self.GetCellValue(row + 1, i) self.SetCellValue(row + 1, i, self.GetCellValue(row, i)) self.SetCellValue(row, i, c) @@ -944,7 +1059,30 @@ mode = mode.upper() application.OnBtnMode(None, mode) application.screenBtnGroup.SetLabel(conf.default_screen, do_cmd=True) + def MakeRepeaterDict(self): + self.RepeaterDict = {} + for row in range(self.GetNumberRows()): + offset = self.GetCellValue(row, 4) + offset = offset.strip() + if not offset: + continue + freq = self.GetCellValue(row, 1) + tone = self.GetCellValue(row, 5) + tone = tone.strip() + if not tone: + tone = '0' + try: + offset = float(offset) + freq = float(freq) + tone = float(tone) + except: + traceback.print_exc() + else: + freq = int(freq * 1E6 + 0.5) # frequency in Hertz + freq = (freq + 500) / 1000 # frequency in units of 1 kHz + self.RepeaterDict[freq * 1000] = (offset, tone) def OnChange(self, event=None): + self.MakeRepeaterDict() self.changed = True if self.timer.IsRunning(): self.timer.Stop() @@ -953,10 +1091,10 @@ if self.changed: self.WriteOut() -class ConfigTxAudio(wx.Panel): +class ConfigTxAudio(wx.ScrolledWindow): """Display controls for the transmit audio.""" def __init__(self, parent, width): - wx.Panel.__init__(self, parent) + wx.ScrolledWindow.__init__(self, parent) self.width = width self.SetBackgroundColour(conf.color_graph) self.SetForegroundColour(conf.color_graphlabels) @@ -1016,6 +1154,7 @@ t = "Tx audio preemphasis of high frequencies." wx.StaticText(self, -1, t, pos=(tab2, self.y)) self.y += self.dy + self.SetScrollbars(1, 1, 100, self.y) def OnGraphData(self, data=None): if conf.microphone_name: txt = "Peak microphone audio level %3.0f dB" % self.status.mic_max_display @@ -1075,7 +1214,7 @@ self.backgroundPen = wx.Pen(self.GetBackgroundColour(), 1) self.backgroundBrush = wx.Brush(self.GetBackgroundColour()) self.horizPen = wx.Pen(conf.color_gl, 1, wx.SOLID) - self.font = wx.Font(conf.graph_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL, + self.font = wx.Font(conf.graph_msg_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, face=conf.quisk_typeface) self.SetFont(self.font) if sys.platform == 'win32': @@ -1115,8 +1254,10 @@ dc.DrawLine(0, y, self.graph_width, y) # y line if self.display_text: dc.SetFont(self.font) - dc.SetTextForeground(conf.color_graphlabels) - dc.DrawText(self.display_text, self.chary, self.chary) + dc.SetTextBackground(conf.color_graph_msg_bg) + dc.SetTextForeground(conf.color_graph_msg_fg) + dc.SetBackgroundMode(wx.SOLID) + dc.DrawText(self.display_text, 0, 0) def SetHeight(self, height): self.height = height self.SetSize((self.graph_width, height)) @@ -1134,13 +1275,30 @@ self.line[x] = [x, y] x = x + 1 self.Refresh() - def UpdateFilterDisplay(self, size, tune): # WB4JFI ADD - Update filter display - self.fltr_disp_size = size - self.fltr_disp_tune = tune + def UpdateFilterDisplay(self, bandwidth, style, offset): # WB4JFI ADD - Update filter display self.fltr_disp_tx_dc.Clear() - self.fltr_disp_tx_dc.DrawLine(tune, 0, tune, self.max_height) self.fltr_disp_rx_dc.Clear() - self.fltr_disp_rx_dc.DrawLine(tune, 0, tune, self.max_height) + if bandwidth < 2: + bandwidth = 1 + self.fltr_disp_size = bandwidth + offset + if style == 'AM': + self.fltr_disp_tune = bandwidth // 2 + elif style == 'LSB': + self.fltr_disp_tune = self.fltr_disp_size - 1 + for dc in (self.fltr_disp_tx_dc, self.fltr_disp_rx_dc): + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(self.backgroundBrush) + dc.DrawRectangle(bandwidth, 0, offset, self.max_height) + else: + self.fltr_disp_tune = 0 + for dc in (self.fltr_disp_tx_dc, self.fltr_disp_rx_dc): + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(self.backgroundBrush) + dc.DrawRectangle(0, 0, offset, self.max_height) + self.fltr_disp_tx_dc.SetPen(self.tuningPenTx) + self.fltr_disp_tx_dc.DrawLine(self.fltr_disp_tune, 0, self.fltr_disp_tune, self.max_height) + self.fltr_disp_rx_dc.SetPen(self.tuningPenRx) + self.fltr_disp_rx_dc.DrawLine(self.fltr_disp_tune, 0, self.fltr_disp_tune, self.max_height) def SetTuningLine(self, tune_tx, tune_rx): dc = wx.ClientDC(self) dc.SetPen(self.backgroundPen) @@ -1212,6 +1370,7 @@ self.originY = 10 self.zeroDB = 10 # y location of zero dB; may be above the top of the graph self.scale = 10 + self.mouse_is_rx = False self.SetSize((self.width, self.height)) self.SetSizeHints(self.width, 1, self.width) self.SetBackgroundColour(conf.color_graph) @@ -1226,6 +1385,13 @@ def MakeDisplay(self): self.display = GraphDisplay(self, self.originX, 0, self.graph_width, 5, self.chary) self.display.zeroDB = self.zeroDB + def SetDisplayMsg(self, text=''): + self.display.display_text = text + self.display.Refresh() + def ScrollMsg(self, chars): # Add characters to a scrolling message + self.display.display_text = self.display.display_text + chars + self.display.display_text = self.display.display_text[-50:] + self.display.Refresh() def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetFont(self.font) @@ -1340,56 +1506,33 @@ c = color # check the width of the frequency label versus frequency span df = charx * sample_rate // self.data_width - if df < 2000: - tfreq = 2000 # tick frequency for labels - stick = 100 # stick: small tick in Hertz - mtick = 500 # mtick: medium tick - ltick = 1000 # ltick: large tick - elif df < 5000: - tfreq = 5000 # tick frequency for labels - stick = 500 - mtick = 1000 - ltick = 5000 - elif df < 10000: - tfreq = 10000 - stick = 1000 - mtick = 5000 - ltick = 10000 - elif df < 20000: - tfreq = 20000 - stick = 1000 - mtick = 5000 - ltick = 10000 - elif df < 50000: - tfreq = 50000 - stick = 5000 - mtick = 10000 - ltick = 50000 - elif df < 100000: - tfreq = 100000 - stick = 10000 - mtick = 50000 - ltick = 100000 - elif df < 200000: - tfreq = 200000 - stick = 10000 - mtick = 50000 - ltick = 100000 - elif df < 500000: - tfreq = 500000 - stick = 50000 - mtick = 100000 - ltick = 500000 - elif df < 1000000: - tfreq = 1000000 - stick = 100000 - mtick = 500000 - ltick = 1000000 - else: - tfreq = 2000000 - stick = 100000 - mtick = 500000 - ltick = 1000000 + if VFO >= 10E9: # Leave room for big labels + df *= 1.33 + elif VFO >= 1E9: + df *= 1.17 + # tfreq: tick frequency for labels in Hertz + # stick: small tick in Hertz + # mtick: medium tick + # ltick: large tick + s2 = 1000 + tfreq = None + while tfreq is None: + if df < s2: + tfreq = s2 + stick = s2 / 10 + mtick = s2 / 2 + ltick = tfreq + elif df < s2 * 2: + tfreq = s2 * 2 + stick = s2 / 10 + mtick = s2 / 2 + ltick = s2 + elif df < s2 * 5: + tfreq = s2 * 5 + stick = s2 / 2 + mtick = s2 + ltick = tfreq + s2 *= 10 # Draw the X axis ticks and frequency in kHz dc.SetPen(self.pen_tick) freq1 = VFO - sample_rate // 2 @@ -1399,13 +1542,13 @@ for f in range (freq1, freq2, stick): x = self.x0 + int(float(f - VFO) / sample_rate * self.data_width) if self.originX <= x <= x3: - if f % ltick is 0: # large tick + if f % ltick == 0: # large tick dc.DrawLine(x, originY, x, originY + tick2) - elif f % mtick is 0: # medium tick + elif f % mtick == 0: # medium tick dc.DrawLine(x, originY, x, originY + tick1) else: # small tick dc.DrawLine(x, originY, x, originY + tick0) - if f % tfreq is 0: # place frequency label + if f % tfreq == 0: # place frequency label t = str(f//1000) w, h = dc.GetTextExtent(t) dc.DrawText(t, x - w // 2, originY + tick2) @@ -1472,7 +1615,9 @@ mouse_x, mouse_y = self.GetMousePosition(event) self.mouse_x = mouse_x x = mouse_x - self.originX - if self.display.tune_rx and abs(x - self.display.tune_tx) > abs(x - self.display.tune_rx): + if application.split_locktx: + self.mouse_is_rx = True + elif self.display.tune_rx and abs(x - self.display.tune_tx) > abs(x - self.display.tune_rx): self.mouse_is_rx = True else: self.mouse_is_rx = False @@ -1523,7 +1668,13 @@ wm = self.WheelMod # Round frequency when using mouse wheel mouse_x, mouse_y = self.GetMousePosition(event) x = mouse_x - self.originX - if self.display.tune_rx and abs(x - self.display.tune_tx) > abs(x - self.display.tune_rx): + if application.split_locktx: + self.mouse_is_rx = True + elif self.display.tune_rx and abs(x - self.display.tune_tx) > abs(x - self.display.tune_rx): + self.mouse_is_rx = True + else: + self.mouse_is_rx = False + if self.mouse_is_rx: freq = application.rxFreq + self.VFO + wm * event.GetWheelRotation() // event.GetWheelDelta() if conf.freq_spacing: freq = self.FreqRound(freq, 0) @@ -1826,6 +1977,7 @@ l = min(l, 255) row = row + "%c%c%c" % (chr(self.red[l]), chr(self.green[l]), chr(self.blue[l])) #T('graph string') + # Perhaps use 4-byte pixels and BitmapFromBufferRGBA() bmp = wx.BitmapFromBuffer(len(row) // 3, 1, row) bmp.x_origin = int(float(self.VFO) / sample_rate * self.data_width + 0.5) self.bitmaps.insert(0, bmp) @@ -1862,6 +2014,10 @@ self.pane1 = GraphScreen(self, data_width, graph_width, 1) self.pane2 = WaterfallPane(self, data_width, graph_width) self.SplitHorizontally(self.pane1, self.pane2, conf.waterfall_graph_size) + def SetDisplayMsg(self, text=''): + self.pane1.SetDisplayMsg(text) + def ScrollMsg(self, char): # Add a character to a scrolling message + self.pane1.ScrollMsg(char) def OnIdle(self, event): self.pane1.OnIdle(event) self.pane2.OnIdle(event) @@ -2143,7 +2299,9 @@ fp = open('__init__.py') # Read in the title title = fp.readline().strip()[1:] fp.close() - wx.Frame.__init__(self, None, -1, title, wx.DefaultPosition, + x = conf.window_posX + y = conf.window_posY + wx.Frame.__init__(self, None, -1, title, (x, y), (width, height), wx.DEFAULT_FRAME_STYLE, 'MainFrame') self.SetBackgroundColour(conf.color_bg) self.SetForegroundColour(conf.color_bg_txt) @@ -2336,8 +2494,8 @@ 'bandState', 'bandAmplPhase', 'lastBand', 'VFO', 'txFreq', 'mode', 'vardecim_set', 'filterAdjBw1', 'levelAGC', 'levelOffAGC', 'volumeAudio', 'levelSpot', 'levelSquelch', 'levelVOX', 'timeVOX', - 'txAudioClipUsb', 'txAudioClipAm','txAudioClipFm', - 'txAudioPreemphUsb', 'txAudioPreemphAm', 'txAudioPreemphFm', + 'txAudioClipUsb', 'txAudioClipAm','txAudioClipFm', 'txAudioClipFdv', + 'txAudioPreemphUsb', 'txAudioPreemphAm', 'txAudioPreemphFm', 'txAudioPreemphFdv', 'wfallScaleZ'] def __init__(self): global application @@ -2361,6 +2519,8 @@ return QuiskCycleCheckbutton(*args, **kw) def RadioButtonGroup(self, *args, **kw): return RadioButtonGroup(*args, **kw) + def SliderBoxHH(self, *args, **kw): + return SliderBoxHH(*args, **kw) def OnInit(self): """Perform most initialization of the app here (called by wxPython on startup).""" wx.lib.colourdb.updateColourDB() # Add additional color names @@ -2384,7 +2544,8 @@ setattr(conf, 'config_file_exists', False) # Choose whether to use Unicode or text symbols for k in ('sym_stat_mem', 'sym_stat_fav', 'sym_stat_dx', - 'btn_text_range_dn', 'btn_text_range_up', 'btn_text_play', 'btn_text_rec', 'btn_text_file_rec', 'btn_text_fav_add', + 'btn_text_range_dn', 'btn_text_range_up', 'btn_text_play', 'btn_text_rec', 'btn_text_file_rec', + 'btn_text_file_play', 'btn_text_fav_add', 'btn_text_fav_recall', 'btn_text_mem_add', 'btn_text_mem_next', 'btn_text_mem_del'): if conf.use_unicode_symbols: setattr(conf, 'X' + k, getattr(conf, 'U' + k)) @@ -2397,6 +2558,16 @@ self.bandState.update(conf.bandState) self.memoryState = [] # a list of (freq, band, self.VFO, self.txFreq, self.mode) self.bandAmplPhase = conf.bandAmplPhase + self.modeFilter = { # the filter button index in use for each mode + 'CW' : 3, + 'SSB' : 3, + 'AM' : 3, + 'FM' : 3, + 'DGT' : 1, + 'FDV' : 2, + 'IMD' : 3, + conf.add_extern_demod : 3, + } # Open hardware file global Hardware if hasattr(conf, "Hardware"): # Hardware defined in config file @@ -2431,9 +2602,11 @@ self.txAudioClipUsb = 5 # Tx audio clip level in dB self.txAudioClipAm = 0 self.txAudioClipFm = 0 + self.txAudioClipFdv = 3 self.txAudioPreemphUsb = 70 # Tx audio preemphasis 0 to 100 self.txAudioPreemphAm = 0 self.txAudioPreemphFm = 0 + self.txAudioPreemphFdv = 30 self.levelSpot = 500 # Spot level control, 10 to 1000 self.volumeAudio = 300 # audio volume self.VFO = 0 # frequency of the VFO @@ -2444,12 +2617,14 @@ self.digital_tx_level = conf.digital_tx_level self.hot_key_ptt_on = False self.fft_size = 1 + if conf.do_repeater_offset and hasattr(Hardware, "RepeaterOffset"): + QS.tx_hold_state(1) # Quisk control by Hamlib through rig 2 self.hamlib_clients = [] # list of TCP connections to handle if conf.hamlib_port: try: self.hamlib_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.hamlib_socket.bind(('localhost', conf.hamlib_port)) + self.hamlib_socket.bind((conf.hamlib_ip, conf.hamlib_port)) self.hamlib_socket.settimeout(0.0) self.hamlib_socket.listen(0) # listen for TCP connections from multiple clients except: @@ -2469,7 +2644,8 @@ self.oldRxFreq = 0 # Last value of self.rxFreq self.screen = None self.audio_volume = 0.0 # Set output volume, 0.0 to 1.0 - self.sidetone_volume = 0 + self.sidetone_volume = 0 # sidetone control value 0 to 1000 + self.sidetone_0to1 = 0 # log taper sidetone volume 0.0 to 1.0 self.sound_error = 0 self.sound_thread = None self.mode = conf.default_mode @@ -2481,12 +2657,18 @@ self.filter_bandwidth = 1000 self.zoom_deltaf = 0 self.zooming = False - self.split_rxtx = False # Are we in split Rx/Tx mode? + self.split_rxtx = False # Are we in split Rx/Tx mode? + self.split_locktx = False # Split mode Tx frequency is fixed. self.savedState = {} self.pttButton = None self.tmp_playing = False + self.file_play_state = 0 # Not playing a file + self.file_play_repeat = 0 # Repeat time in seconds, or zero for no repeat + self.file_play_timer = 0 # get the screen size - thanks to Lucian Langa - x, y, self.screen_width, self.screen_height = wx.Display().GetGeometry() + x1, y1, x2, y2 = wx.Display().GetGeometry() + self.screen_width = x2 - x1 + self.screen_height = y2 - y1 self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_QUERY_END_SESSION, self.OnEndSession) # Restore persistent program state @@ -2533,21 +2715,68 @@ conf.playback_rate = 48000 else: conf.playback_rate = conf.sample_rate + # Check for PulseAudio names and substitute the actual device name for abbreviations + if sys.platform != 'win32' and conf.show_pulse_audio_devices: + self.pa_dev_capt, self.pa_dev_play = QS.pa_sound_devices() + for key in ("name_of_sound_play", "name_of_mic_play", "digital_output_name", "sample_playback_name"): + value = getattr(conf, key) # playback devices + if value[0:6] == "pulse:": + for n0, n1, n2 in self.pa_dev_play: + for n in (n0, n1, n2): + if value[6:] in n: + setattr(conf, key, "pulse:" + n0) + for key in ("name_of_sound_capt", "microphone_name", "digital_input_name"): + value = getattr(conf, key) # capture devices + if value[0:6] == "pulse:": + for n0, n1, n2 in self.pa_dev_capt: + for n in (n0, n1, n2): + if value[6:] in n: + setattr(conf, key, "pulse:" + n0) + # Create the main frame + if conf.window_width > 0: # fixed width of the main frame + self.width = conf.window_width + else: + self.width = self.screen_width * 8 // 10 + if conf.window_height > 0: # fixed height of the main frame + self.height = conf.window_height + else: + self.height = self.screen_height * 5 // 10 + self.main_frame = frame = QMainFrame(self.width, self.height) + self.SetTopWindow(frame) + #w, h = frame.GetSizeTuple() + #ww, hh = frame.GetClientSizeTuple() + #print ('Main frame: size', w, h, 'client', ww, hh) # Find the data width from a list of prefered sizes; it is the width of returned graph data. # The graph_width is the width of data_width that is displayed. - width = self.screen_width * conf.graph_width - percent = conf.display_fraction # display central fraction of total width - percent = int(percent * 100.0 + 0.4) - width = width * 100 // percent - for x in fftPreferedSizes: - if x > width: - self.data_width = x - break - else: - self.data_width = fftPreferedSizes[-1] - self.graph_width = self.data_width * percent // 100 - if self.graph_width % 2 == 1: # Both data_width and graph_width are even numbers - self.graph_width += 1 + if conf.window_width > 0: + wFrame, h = frame.GetClientSizeTuple() # client window width + self.graph = GraphScreen(frame, self.width/2, self.width/2) # make a GraphScreen to calculate borders + self.graph_width = wFrame - (self.graph.width - self.graph.graph_width) # less graph borders equals actual graph_width + del self.graph + if self.graph_width % 2 == 1: # Both data_width and graph_width are even numbers + self.graph_width -= 1 + width = int(self.graph_width / conf.display_fraction) # estimated data width + for x in fftPreferedSizes: + if x >= width: + self.data_width = x + break + else: + self.data_width = fftPreferedSizes[-1] + else: # use conf.graph_width to determine the width + width = self.screen_width * conf.graph_width # estimated graph width + percent = conf.display_fraction # display central fraction of total width + percent = int(percent * 100.0 + 0.4) + width = width * 100 // percent # estimated data width + for x in fftPreferedSizes: + if x > width: + self.data_width = x + break + else: + self.data_width = fftPreferedSizes[-1] + self.graph_width = self.data_width * percent // 100 + if self.graph_width % 2 == 1: # Both data_width and graph_width are even numbers + self.graph_width += 1 + #print('graph_width', self.graph_width, 'data_width', self.data_width) # The FFT size times the average_count controls the graph refresh rate factor = float(self.sample_rate) / conf.graph_refresh / self.data_width ifactor = int(factor + 0.5) # fft size multiplier * average count @@ -2582,10 +2811,6 @@ average_count = 1 self.fft_size = self.data_width * fft_mult # print 'data, graph,fft', self.data_width, self.graph_width, self.fft_size - self.width = self.screen_width * 8 // 10 - self.height = self.screen_height * 5 // 10 - self.main_frame = frame = QMainFrame(self.width, self.height) - self.SetTopWindow(frame) # Record the basic application parameters if sys.platform == 'win32': h = self.main_frame.GetHandle() @@ -2666,16 +2891,28 @@ button_width //= 13 # This is our final button size bw = button_width button_width, button_height = self.MakeButtons(frame, gbs, button_width, gap) - ww = self.graph.width - self.main_frame.SetSizeHints(ww, 100) + minw = width = self.graph.width if button_width > bw: # The button width was increased - ww += (button_width - bw) * 12 - self.main_frame.SetClientSizeWH(ww, self.screen_height * 5 // 10) + width = minw + (button_width - bw) * 12 + maxw = maxh = -1 + minh = 100 + if conf.window_width > 0: + minw = width = maxw = conf.window_width + if conf.window_height > 0: + minh = maxh = conf.window_height + self.main_frame.SetSizeHints(minw, minh, maxw, maxh) + self.main_frame.SetClientSizeWH(width, self.height) self.MakeTopRow(frame, gbs, button_width, button_height) self.button_width = button_width self.button_height = button_height + if hasattr(Hardware, 'pre_open'): # pre_open() is called before open() + Hardware.pre_open() if conf.quisk_widgets: self.bottom_widgets = conf.quisk_widgets.BottomWidgets(self, Hardware, conf, frame, gbs, vertBox) + # Open the hardware. This must be called before open_sound(). + self.config_text = Hardware.open() + if not self.config_text: + self.config_text = "Missing config_text" if QS.open_key(conf.key_method): print('open_key failed for name "%s"' % conf.key_method) if hasattr(conf, 'mixer_settings'): @@ -2683,11 +2920,11 @@ err_msg = QS.mixer_set(dev, numid, value) if err_msg: print("Mixer", err_msg) - # Open the hardware. This must be called before open_sound(). - self.config_text = Hardware.open() - if not self.config_text: - self.config_text = "Missing config_text" - if conf.use_rx_udp: + if conf.use_rx_udp == 10: # Hermes UDP protocol + self.add_version = False + conf.tx_ip = Hardware.hermes_ip + conf.tx_audio_port = conf.rx_udp_port + elif conf.use_rx_udp: self.add_version = True # Add firmware version to config text else: self.add_version = False @@ -2725,7 +2962,7 @@ self.sound_thread.start() if conf.dxClHost: # create DX Cluster and register listener for change notification - self.dxCluster = dxcluster.DxCluster() + self.dxCluster = dxcluster.DxCluster() self.dxCluster.setListener(self.OnDxClChange) self.dxCluster.start() return True @@ -2824,7 +3061,11 @@ frame.Bind(wx.EVT_TEXT_ENTER, self.FreqEntry, source=e) # S-meter self.smeter = QuiskText(frame, ' S9+23 -166.00 dB ', bh, wx.ALIGN_LEFT, True) - gbs.Add(self.smeter, (0, 23), (1, 4), flag=wx.EXPAND) + b = QuiskPushbutton(frame, self.OnSmeterRightDown, '..') + szr = wx.BoxSizer(wx.HORIZONTAL) + szr.Add(self.smeter, 1, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) + szr.Add(b, 0, flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + gbs.Add(szr, (0, 23), (1, 4), flag=wx.EXPAND) self.smeter.TextCtrl.Bind(wx.EVT_RIGHT_DOWN, self.OnSmeterRightDown) self.smeter.TextCtrl.SetBackgroundColour(conf.color_freq) self.smeter.TextCtrl.SetForegroundColour(conf.color_freq_txt) @@ -2845,8 +3086,12 @@ # Make a popup menu for the memory buttons self.memory_menu = wx.Menu() def OnSmeterRightDown(self, event): - pos = event.GetPosition() - self.smeter.TextCtrl.PopupMenu(self.smeter_menu, pos) + try: + pos = event.GetPosition() # works for right-click + self.smeter.TextCtrl.PopupMenu(self.smeter_menu, pos) + except: + btn = event.GetEventObject() # works for button + btn.PopupMenu(self.smeter_menu, (0,0)) def OnSmeterMeterA(self, event): self.smeter_avg_seconds = 1.0 self.smeter_usage = "smeter" @@ -2884,7 +3129,7 @@ left_row2 = [] b = QuiskCheckbutton(frame, self.OnBtnMute, text='Mute') left_row2.append(b) - self.BtnAGC = QuiskSliderButton(frame, self.OnBtnAGC, 'AGC') # AGC and Squelch + self.BtnAGC = QuiskSliderButton(frame, self.OnBtnAGC, 'AGC', display=True) # AGC and Squelch self.BtnAGC.SetDual(True) self.BtnAGC.SetSlider(self.levelSquelch, self.levelOffAGC, self.levelAGC) left_row2.append(self.BtnAGC) @@ -2921,20 +3166,23 @@ # Transmit button row: Spot left_row3=[] b = QuiskSpotButton(frame, self.OnBtnSpot, 'Spot', slider_value=self.levelSpot, - color=conf.color_test, slider_min=10, slider_max=1000) + color=conf.color_test, slider_min=0, slider_max=1000, display=True) if not hasattr(Hardware, 'OnSpot'): b.Enable(False) left_row3.append(b) - b = self.splitButton = QuiskCheckbutton(frame, self.OnBtnSplit, "Split") + b = self.splitButton = QuiskCheckbutton(frame, self.OnBtnSplit, "Splt", use_right=True) if conf.mouse_tune_method: # Mouse motion changes the VFO frequency b.Enable(False) left_row3.append(b) + self.btnSplitRev = QuiskPushbutton(frame, self.OnBtnSplitRev, 'Rev') + self.btnSplitRev.Enable(False) + left_row3.append(self.btnSplitRev) b = QuiskCheckbutton(frame, self.OnBtnFDX, 'FDX', color=conf.color_test) if not conf.add_fdx_button: b.Enable(False) left_row3.append(b) if hasattr(Hardware, 'OnButtonPTT'): - b = QuiskCheckbutton(frame, Hardware.OnButtonPTT, 'PTT', color='red') + b = QuiskCheckbutton(frame, self.OnButtonPTT, 'PTT', color='red') self.pttButton = b left_row3.append(b) b = QuiskCheckbutton(frame, self.OnButtonVOX, 'VOX') @@ -2956,20 +3204,58 @@ if not conf.file_name_audio and not conf.file_name_samples: self.btn_file_record.Enable(0) left_row3.append(self.btn_file_record) - b = QuiskCheckbutton(frame, None, text='') - left_row3.append(b) + self.btnFilePlay = QuiskCheckbutton(frame, self.OnBtnFilePlay, conf.Xbtn_text_file_play) + if conf.file_name_playback and QS.open_file_play(conf.file_name_playback) == 0: + pass + else: + self.btnFilePlay.Enable(0) + left_row3.append(self.btnFilePlay) ### Right bank of buttons - labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-IQ')] + mode_names = ['CWL', 'CWU', 'LSB', 'USB', 'AM', 'FM', 'DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ', 'FDV-U', 'FDV-L', 'IMD'] + labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ')] + count = 5 # There is room for seven buttons + if conf.add_freedv_button: + n_freedv = count + count += 1 + labels.append('FDV-U') if conf.add_imd_button: - labels.append(('IMD', 'IMD -3dB', 'IMD -6dB')) - elif conf.add_extern_demod: + n_imd = count + count += 1 + labels.append('IMD') + if count < 7 and conf.add_extern_demod: labels.append(conf.add_extern_demod) - else: + mode_names.append(conf.add_extern_demod) + while count < 7: + count += 1 labels.append('') + mode_names.sort() + self.config_screen.favorites.SetModeEditor(mode_names) self.modeButns = RadioButtonGroup(frame, self.OnBtnMode, labels, None) - right_row1 = self.modeButns.GetButtons()[:] + self.freedv_menu_items = {} + if conf.add_freedv_button: + self.freedv_menu = wx.Menu() + for mode, index in conf.freedv_modes: + item = self.freedv_menu.AppendRadioItem(-1, mode) + self.freedv_menu_items[index] = item + self.Bind(wx.EVT_MENU, self.OnFreedvMenu, item) + b = QuiskCycleMenuButton(frame, self.freedv_menu, self.OnBtnMode, ('FDV-U', 'FDV-L'), is_radio=True) + self.modeButns.ReplaceButton(n_freedv, b) + msg = conf.freedv_tx_msg + QS.freedv_set_options(mode=conf.freedv_modes[0][1], tx_msg=msg, DEBUG=0, squelch=1) + try: + ok = QS.freedv_open() + except: + traceback.print_exc() + ok = 0 + if not ok: + conf.add_freedv_button = False + self.modeButns.GetButtons()[n_freedv].Enable(0) if conf.add_imd_button: - right_row1[-1].color = conf.color_test + val = 500 + QS.set_imd_level(val) + b = QuiskImdButton(frame, text='IMD', color=conf.color_test, slider_value=val, display=True) + self.modeButns.ReplaceButton(n_imd, b) + right_row1 = self.modeButns.GetButtons()[:] labels = ('0', '0', '0', '0', '0', '0') self.filterButns = RadioButtonGroup(frame, self.OnBtnFilter, labels, None) b = QuiskFilterButton(frame, text=str(self.filterAdjBw1)) @@ -2984,24 +3270,31 @@ col += 2 col = 0 for b in left_row3: - if col in (6, 7, 8, 9): # single column + if col in (2, 3, 6, 7, 8, 9): # single column gbs.Add(b, (3, col), flag=flag) col += 1 - else: # double column + else: # double column gbs.Add(b, (3, col), (1, 2), flag=flag) col += 2 col = 15 + for b in right_row1: + if col in (19, 20): # single column + gbs.Add(b, (1, col), flag=flag) + col += 1 + else: # double column + gbs.Add(b, (1, col), (1, 2), flag=flag) + col += 2 + col = 15 for i in range(0, 6): - gbs.Add(right_row1[i], (1, col), (1, 2), flag=flag) gbs.Add(right_row2[i], (2, col), (1, 2), flag=flag) gbs.Add(right_row3[i], (3, col), (1, 2), flag=flag) col += 2 - if 1: - for i in range(14): - gbs.AddGrowableCol(i,1) - for i in range(15, 27): - gbs.AddGrowableCol(i,1) + for i in range(14): + gbs.AddGrowableCol(i,1) + for i in range(15, 27): + gbs.AddGrowableCol(i,1) w, height = right_row1[0].GetMinSize() + self.widget_row = 4 # Next available row for widgets return button_width, height def MeasureAudioVoltage(self): v = QS.measure_audio(-1) @@ -3012,8 +3305,19 @@ vfo = Hardware.ReturnVfoFloat() if vfo is None: vfo = self.VFO + vfo += Hardware.transverter_offset t = '%13.2f' % (QS.measure_frequency(-1) + vfo) - t = ' ' + t[0:4] + ' ' + t[4:7] + ' ' + t[7:] + ' Hz' + t = t[0:4] + ' ' + t[4:7] + ' ' + t[7:] + ' Hz' + self.smeter.SetLabel(t) + def NewDVmeter(self): + if conf.add_freedv_button: + snr = QS.freedv_get_snr() + txt = QS.freedv_get_rx_char() + self.graph.ScrollMsg(txt) + self.waterfall.ScrollMsg(txt) + else: + snr = 0.0 + t = " SNR %3.0f" % snr self.smeter.SetLabel(t) def NewSmeter(self): self.smeter_db_count += 1 # count for average @@ -3036,23 +3340,28 @@ s = (s - 9.0) * 6 t = " S9+%2.0f %7.2f dB" % (s, self.smeter_db) else: - t = " S%.0f %7.2f dB" % (s, self.smeter_db) + t = " S%.0f %7.2f dB" % (s, self.smeter_db) self.smeter.SetLabel(t) def MakeFilterButtons(self, args): # Change the filter selections depending on the mode: CW, SSB, etc. # Do not change the adjustable filter buttons. buttons = self.filterButns.GetButtons() for i in range(0, len(buttons) - 1): - buttons[i].SetLabel(str(args[i])) + label = str(args[i]) + buttons[i].SetLabel(label) buttons[i].Refresh() + if label: + buttons[i].Enable(1) + else: + buttons[i].Enable(0) def MakeFilterCoef(self, rate, N, bw, center): """Make an I/Q filter with rectangular passband.""" lowpass = bw * 24000 // rate // 2 if lowpass in Filters: filtD = Filters[lowpass] - #print "Custom filter rate %d bandwidth %d" % (rate, bw) + #print ("Custom filter key %d rate %d bandwidth %d size %d" % (lowpass, rate, bw, len(filtD))) else: - #print "Window filter rate %d bandwidth %d" % (rate, bw) + #print ("Window filter key %d rate %d bandwidth %d" % (lowpass, rate, bw)) if N is None: shape = 1.5 # Shape factor at 88 dB trans = (bw / 2.0 / rate) * (shape - 1.0) # 88 dB atten @@ -3099,49 +3408,78 @@ def UpdateFilterDisplay(self): # Note: Filter bandwidths are ripple bandwidths with a shape factor of 1.2. # Also, SSB filters start at 300 Hz. + # The FDV filter is centered at 1500 Hz + scale = 1.0 / self.zoom / self.sample_rate * self.data_width + bandwidth = int(self.filter_bandwidth * scale + 0.5) if not conf.filter_display: - size = 1 - tune = 0 - elif self.mode in ('AM', 'FM', 'CWL', 'CWU', 'DGT-IQ'): - size = int(self.filter_bandwidth / self.zoom / self.sample_rate * self.data_width + 0.5) - tune = size // 2 + style = 'AM' + bandwidth = 1 + offset = 0 + elif self.mode in ('AM', 'FM', 'CWL', 'CWU', 'DGT-IQ', 'DGT-FM'): + style = 'AM' + offset = 0 elif self.mode in ('LSB', 'DGT-L'): - size = int((self.filter_bandwidth + 300) / self.zoom / self.sample_rate * self.data_width + 0.5) - tune = size - 1 - else: - size = int((self.filter_bandwidth + 300) / self.zoom / self.sample_rate * self.data_width + 0.5) - tune = 0 - if size < 2: - size = 1 - tune = 0 - self.graph.display.UpdateFilterDisplay(size, tune) - self.waterfall.pane1.display.UpdateFilterDisplay(size, tune) + style = 'LSB' + offset = int(300 * scale + 0.5) + elif self.mode in ('USB', 'DGT-U', 'IMD'): + style = 'USB' + offset = int(300 * scale + 0.5) + elif self.mode == 'FDV-U': + style = 'USB' + offset = 1500 - self.filter_bandwidth // 2 + offset = int(offset * scale + 0.5) + elif self.mode == 'FDV-L': + style = 'LSB' + offset = 1500 - self.filter_bandwidth // 2 + offset = int(offset * scale + 0.5) + else: + style = 'AM' + bandwidth = 1 + offset = 0 + self.graph.display.UpdateFilterDisplay(bandwidth, style, offset) + self.waterfall.pane1.display.UpdateFilterDisplay(bandwidth, style, offset) + def SetFilterByMode(self, mode): + index = self.modeFilter[mode] + bw = int(self.filterButns.buttons[index].GetLabel()) + self.OnBtnFilter(None, bw) def OnBtnFilter(self, event, bw=None): if event is None: # called by application self.filterButns.SetLabel(str(bw)) else: # called by button btn = event.GetEventObject() bw = int(btn.GetLabel()) + index = self.filterButns.GetIndex() mode = self.mode if mode in ("CWL", "CWU"): + self.modeFilter['CW'] = index bw = min(bw, 2500) center = max(conf.cwTone, bw//2) elif mode in ('LSB', 'USB'): + self.modeFilter['SSB'] = index bw = min(bw, 5000) center = 300 + bw // 2 elif mode == 'AM': + self.modeFilter[mode] = index bw = min(bw, 21000) center = 0 elif mode == 'FM': + self.modeFilter[mode] = index bw = min(bw, 21000) center = 0 elif mode in ('DGT-U', 'DGT-L'): + self.modeFilter['DGT'] = index bw = min(bw, 21000) center = 300 + bw / 2 - elif mode == 'DGT-IQ': + elif mode in ('DGT-IQ', 'DGT-FM'): + self.modeFilter['DGT'] = index bw = min(bw, 21000) center = 0 + elif mode in ('FDV-U', 'FDV-L'): + self.modeFilter['FDV'] = index + bw = min(bw, 4000) + center = 1500 else: + self.modeFilter[mode] = index bw = min(bw, 5000) center = 300 + bw / 2 self.filter_bandwidth = bw @@ -3151,6 +3489,21 @@ QS.set_filters(filtI, filtQ, bw) if self.screen is self.filter_screen: self.screen.NewFilter() + def OnFreedvMenu(self, event): + idd = event.GetId() + text = self.freedv_menu.GetLabel(idd) + for mode, index in conf.freedv_modes: + if mode == text: + break + else: + print ("Failure in OnFreedvMenu") + return + mode = QS.freedv_set_options(mode=index) + if mode != index: # change to new mode failed + self.freedv_menu_items[mode].Check(1) + pos = (self.width/2, self.height/2) + dlg = wx.MessageDialog(self.main_frame, "No codec2 support for mode " + text, "FreeDV Modes", wx.OK, pos) + dlg.ShowModal() def OnBtnScreen(self, event, name=None): if event is not None: win = event.GetEventObject() @@ -3238,6 +3591,16 @@ QS.set_tx_audio(vox_level=20) if self.pttButton.GetValue(): self.pttButton.SetValue(0, True) + def OnButtonPTT(self, event): + if self.btnFilePlay.GetValue(): + self.btnFilePlay.SetValue(False, True) + Hardware.OnButtonPTT(event) + def SetPTT(self, value): + if self.pttButton: + self.pttButton.SetValue(value, False) + event = wx.PyEvent() + event.SetEventObject(self.pttButton) + Hardware.OnButtonPTT(event) def OnTxAudioClip(self, event): v = event.GetEventObject().GetValue() if self.mode in ('USB', 'LSB'): @@ -3246,6 +3609,8 @@ self.txAudioClipAm = v elif self.mode == 'FM': self.txAudioClipFm = v + elif self.mode in ('FDV-U', 'FDV-L'): + self.txAudioClipFdv = v else: return QS.set_tx_audio(mic_clip=v) @@ -3257,6 +3622,8 @@ self.txAudioPreemphAm = v elif self.mode == 'FM': self.txAudioPreemphFm = v + elif self.mode in ('FDV-U', 'FDV-L'): + self.txAudioPreemphFdv = v else: return QS.set_tx_audio(mic_preemphasis = v * 0.01) @@ -3270,6 +3637,9 @@ elif self.mode == 'FM': clp = self.txAudioClipFm pre = self.txAudioPreemphFm + elif self.mode in ('FDV-U', 'FDV-L'): + clp = self.txAudioClipFdv + pre = self.txAudioPreemphFdv else: return QS.set_tx_audio(mic_clip=clp, mic_preemphasis=pre * 0.01) @@ -3308,14 +3678,22 @@ value = self.sliderVol.GetValue() self.volumeAudio = 1000 - value # Simulate log taper pot - x = (10.0 ** (float(value) * 0.003000434077) - 1) / 1000.0 + B = 50.0 # This controls the gain at mid-volume + x = (B ** (value/1000.0) - 1.0) / (B - 1.0) # x is 0.0 to 1.0 + #print ("Vol %3d %10.6f" % (value, x)) self.audio_volume = x # audio_volume is 0 to 1.000 QS.set_volume(x) def ChangeSidetone(self, event=None): # Caution: event can be None value = self.sliderSto.GetValue() self.sidetone_volume = value - QS.set_sidetone(value, self.ritFreq, conf.keyupDelay) + # Simulate log taper pot + B = 50.0 # This controls the gain at mid-volume + x = (B ** (value/1000.0) - 1.0) / (B - 1.0) # x is 0.0 to 1.0 + self.sidetone_0to1 = x + QS.set_sidetone(value, x, self.ritFreq, conf.keyupDelay) + if hasattr(Hardware, 'ChangeSidetone'): + Hardware.ChangeSidetone(x) def OnRitScale(self, event=None): # Called when the RIT slider is moved # Caution: event can be None if self.ritButton.GetValue(): @@ -3323,10 +3701,16 @@ value = int(value) self.ritFreq = value QS.set_tune(self.rxFreq + self.ritFreq, self.txFreq) - QS.set_sidetone(self.sidetone_volume, self.ritFreq, conf.keyupDelay) + QS.set_sidetone(self.sidetone_volume, self.sidetone_0to1, self.ritFreq, conf.keyupDelay) def OnBtnSplit(self, event): # Called when the Split check button is pressed self.split_rxtx = self.splitButton.GetValue() + self.btnSplitRev.Enable(self.split_rxtx) if self.split_rxtx: + if self.splitButton.direction > 0: # Left button was used + self.split_locktx = False + else: # Right button was used + self.split_locktx = True + self.splitButton.SetLabel("LkSp") QS.set_split_rxtx(conf.split_rxtx) self.rxFreq = self.oldRxFreq d = self.sample_rate * 49 // 100 # Move rxFreq on-screen @@ -3335,11 +3719,17 @@ elif self.rxFreq > d: self.rxFreq = d else: + self.split_locktx = False + self.splitButton.SetLabel("Splt") QS.set_split_rxtx(0) self.oldRxFreq = self.rxFreq self.rxFreq = self.txFreq self.screen.SetTxFreq(self.txFreq, self.rxFreq) QS.set_tune(self.rxFreq + self.ritFreq, self.txFreq) + def OnBtnSplitRev(self, event): # Called when the Split Reverse button is pressed + rx = self.rxFreq + self.rxFreq = self.txFreq + self.ChangeHwFrequency(rx, self.VFO, 'FreqEntry') def OnBtnRit(self, event=None): # Called when the RIT check button is pressed # Caution: event can be None if self.ritButton.GetValue(): @@ -3347,7 +3737,7 @@ else: self.ritFreq = 0 QS.set_tune(self.rxFreq + self.ritFreq, self.txFreq) - QS.set_sidetone(self.sidetone_volume, self.ritFreq, conf.keyupDelay) + QS.set_sidetone(self.sidetone_volume, self.sidetone_0to1, self.ritFreq, conf.keyupDelay) def SetRit(self, freq): if freq: self.ritButton.SetValue(1) @@ -3371,9 +3761,11 @@ if btn.GetValue(): value = btn.slider_min + btn.slider_max - btn.slider_value # slider values are backwards in Wx else: - value = 0 + value = -1 QS.set_spot_level(value) Hardware.OnSpot(value) + if conf.spot_button_keys_tx and self.pttButton: + Hardware.OnButtonPTT(event) def OnBtnTmpRecord(self, event): btn = event.GetEventObject() if btn.GetValue(): @@ -3395,6 +3787,20 @@ self.btnTmpRecord.Enable(1) QS.set_record_state(3) self.tmp_playing = False + def OnBtnFilePlay(self, event): + btn = event.GetEventObject() + if btn.GetValue(): + self.file_play_state = 1 # Start playing a file + QS.set_record_state(5) + self.SetPTT(True) + else: + self.file_play_state = 0 # Not playing a file + QS.set_record_state(3) + self.SetPTT(False) + def TurnOffFilePlay(self): + self.btnFilePlay.SetValue(False, False) + self.file_play_state = 0 # Not playing a file + QS.set_record_state(3) def OnBtnTest1(self, event): btn = event.GetEventObject() if btn.GetValue(): @@ -3543,32 +3949,32 @@ QS.set_rx_mode(0) self.SetRit(conf.cwTone) self.MakeFilterButtons(conf.FilterBwCW) - self.OnBtnFilter(None, conf.FilterBwCW[3]) + self.SetFilterByMode('CW') elif mode == 'CWU': QS.set_rx_mode(1) self.SetRit(-conf.cwTone) self.MakeFilterButtons(conf.FilterBwCW) - self.OnBtnFilter(None, conf.FilterBwCW[3]) + self.SetFilterByMode('CW') elif mode == 'LSB': QS.set_rx_mode(2) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwSSB) - self.OnBtnFilter(None, conf.FilterBwSSB[3]) + self.SetFilterByMode('SSB') elif mode == 'USB': QS.set_rx_mode(3) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwSSB) - self.OnBtnFilter(None, conf.FilterBwSSB[3]) + self.SetFilterByMode('SSB') elif mode == 'AM': QS.set_rx_mode(4) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwAM) - self.OnBtnFilter(None, conf.FilterBwAM[3]) + self.SetFilterByMode(mode) elif mode == 'FM': QS.set_rx_mode(5) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwFM) - self.OnBtnFilter(None, conf.FilterBwFM[3]) + self.SetFilterByMode(mode) elif mode[0:4] == 'DGT-': if mode == 'DGT-U': QS.set_rx_mode(7) @@ -3576,21 +3982,33 @@ QS.set_rx_mode(8) elif mode == 'DGT-IQ': QS.set_rx_mode(9) + elif mode == 'DGT-FM': + QS.set_rx_mode(13) else: QS.set_rx_mode(7) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwDGT) - self.OnBtnFilter(None, conf.FilterBwDGT[1]) - elif mode[0:3] == 'IMD': - QS.set_rx_mode(10 + self.modeButns.GetSelectedButton().index) # 10, 11, 12 + self.SetFilterByMode('DGT') + elif mode == 'FDV-U': + QS.set_rx_mode(11) + self.SetRit(0) + self.MakeFilterButtons(conf.FilterBwFDV) + self.SetFilterByMode('FDV') + elif mode == 'FDV-L': + QS.set_rx_mode(12) + self.SetRit(0) + self.MakeFilterButtons(conf.FilterBwFDV) + self.SetFilterByMode('FDV') + elif mode == 'IMD': + QS.set_rx_mode(10) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwIMD) - self.OnBtnFilter(None, conf.FilterBwIMD[3]) + self.SetFilterByMode(mode) elif mode == conf.add_extern_demod: # External demodulation QS.set_rx_mode(6) self.SetRit(0) self.MakeFilterButtons(conf.FilterBwEXT) - self.OnBtnFilter(None, conf.FilterBwEXT[3]) + self.SetFilterByMode(conf.add_extern_demod) if mode == 'FM': self.BtnAGC.SetDual(False) self.BtnAGC.SetLabel('Sqlch') @@ -3599,6 +4017,9 @@ self.BtnAGC.SetDual(True) self.BtnAGC.SetLabel('AGC') self.BtnAGC.SetValue(self.use_AGC, True) + if mode not in ('FDV-L', 'FDV-U'): + self.graph.SetDisplayMsg() + self.waterfall.SetDisplayMsg() self.SetTxAudio() def MakeMemPopMenu(self): self.memory_menu.Destroy() @@ -3725,8 +4146,8 @@ vfo, tune, mode = conf.bandTime[btn.index] self.OnBtnMode(None, mode) self.txFreq = self.VFO = -1 # demand change - self.ChangeHwFrequency(tune, vfo, 'BtnBand', band=band) self.ChangeBand(band) + self.ChangeHwFrequency(tune, vfo, 'BtnBand', band=band) def BandFromFreq(self, frequency): # Change to a new band based on the frequency try: f1, f2 = conf.BandEdge[self.lastBand] @@ -3915,26 +4336,50 @@ # print 'Remove', client.address break def OnReadSound(self): # called at frequent intervals - if self.pttButton: # Manage the PTT button using VOX and hot keys + if conf.do_repeater_offset: + hold = QS.tx_hold_state(-1) + if hold == 2: # Tx is being held for an FM repeater TX frequency shift + rdict = self.config_screen.favorites.RepeaterDict + freq = self.txFreq + self.VFO + freq = ((freq + 500) / 1000) * 1000 + if rdict.has_key(freq): + offset, tone = rdict[freq] + QS.set_ctcss(tone) + Hardware.RepeaterOffset(offset) + for i in range(100): + time.sleep(0.010) + if Hardware.RepeaterOffset(): + break + QS.tx_hold_state(3) + elif hold == 4: # No delay necessary on key up + Hardware.RepeaterOffset(0) + QS.set_ctcss(0) + QS.tx_hold_state(1) + if self.pttButton: # Manage the PTT button using VOX, hot keys and WAV file play ptt = None if self.useVOX: - if QS.is_vox(): + if self.file_play_state == 0: + if QS.is_vox(): + ptt = True + else: + ptt = False + elif self.file_play_state == 2 and QS.is_vox(): # VOX tripped between file play repeats + self.TurnOffFilePlay() ptt = True - else: - ptt = False if conf.hot_key_ptt1: hot_key = wx.GetKeyState(conf.hot_key_ptt1) and (conf.hot_key_ptt2 is None or wx.GetKeyState(conf.hot_key_ptt2)) if hot_key: self.hot_key_ptt_on = True + self.TurnOffFilePlay() ptt = True elif self.hot_key_ptt_on: self.hot_key_ptt_on = False if not self.useVOX: ptt = False if ptt is True and not self.pttButton.GetValue(): - self.pttButton.SetValue(1, True) + self.SetPTT(True) elif ptt is False and self.pttButton.GetValue(): - self.pttButton.SetValue(0, True) + self.SetPTT(False) self.timer = time.time() if self.screen == self.scope: data = QS.get_graph(0, 1.0, 0) # get raw data @@ -3950,8 +4395,11 @@ data = QS.get_graph(1, self.zoom, float(self.zoom_deltaf)) # get FFT data if data: #T('') - if self.smeter_usage == "smeter": - self.NewSmeter() # update the S-meter + if self.smeter_usage == "smeter": # update the S-meter + if self.mode in ('FDV-U', 'FDV-L'): + self.NewDVmeter() + else: + self.NewSmeter() elif self.smeter_usage == "freq": self.MeasureFrequency() # display measured frequency else: @@ -3997,6 +4445,21 @@ self.SaveState() if self.tmp_playing and QS.set_record_state(-1): # poll to see if playback is finished self.btnTmpPlay.SetValue(False, True) + if self.file_play_state == 0: + pass + elif self.file_play_state == 1: + if QS.set_record_state(-1): # poll to see if playback is finished + if self.file_play_repeat: + self.file_play_state = 2 # Waiting for the timer to expire, and start another playback + self.file_play_timer = self.timer + self.file_play_repeat + self.SetPTT(False) + else: + self.btnFilePlay.SetValue(False, True) + elif self.file_play_state == 2: + if self.timer >= self.file_play_timer: + QS.set_record_state(5) # Start another playback + self.file_play_state = 1 + self.SetPTT(True) def main(): """If quisk is installed as a package, you can run it with quisk.main().""" Binary files /tmp/edonsy5nYK/quisk-3.6.18/_quisk.pyd and /tmp/ZwEcPOdYqm/quisk-3.7.6/_quisk.pyd differ Binary files /tmp/edonsy5nYK/quisk-3.6.18/_quisk.so and /tmp/ZwEcPOdYqm/quisk-3.7.6/_quisk.so differ diff -Nru quisk-3.6.18/quisk_widgets.py quisk-3.7.6/quisk_widgets.py --- quisk-3.6.18/quisk_widgets.py 2014-05-27 19:53:08.000000000 +0000 +++ quisk-3.7.6/quisk_widgets.py 2015-09-11 12:56:21.000000000 +0000 @@ -5,6 +5,7 @@ from types import * # The main script will alter quisk_conf_defaults to include the user's config file. import quisk_conf_defaults as conf +import _quisk as QS def FreqFormatter(freq): # Format the string or integer frequency by adding blanks freq = int(freq) @@ -47,9 +48,15 @@ self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) # Click on a digit changes the frequency self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) + if sys.platform == 'win32': + self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) self.timer = wx.Timer(self) # Holding a digit continuously changes the frequency self.Bind(wx.EVT_TIMER, self.OnTimer) self.repeat_time = 0 # Repeat function is inactive + def OnEnter(self, event): + if not application.w_phase: + self.SetFocus() # Set focus so we get mouse wheel events def Clip(self, clip): """Change color to indicate clipping.""" if clip: @@ -61,37 +68,43 @@ """Set the frequency to be displayed.""" txt = FreqFormatter(freq) self.SetLabel('%s Hz' % txt) - def OnLeftDown(self, event): # Click on a digit changes the frequency - if self.repeat_time: - self.timer.Stop() - self.repeat_time = 0 + def GetIndex(self, event): # Determine which digit is being changed mouse_x, mouse_y = event.GetPosition() width, height = self.GetClientSizeTuple() - if mouse_y < height / 2: - self.increase = True - else: - self.increase = False text = self.GetLabel() tw, th = self.GetTextExtent(text) edge = (width - tw) / 2 digit = self.GetTextExtent('0')[0] blank = self.GetTextExtent(' ')[0] if mouse_x < edge - digit: - return + return None x = width - edge - self.GetTextExtent(" Hz")[0] - mouse_x if x < 0: - return + return None #print ('size', width, height, 'mouse', mouse_x, mouse_y, 'digit', digit, 'blank', blank) shift = 0 while x > digit * 3: shift += 1 x -= digit * 3 + blank if x < 0: - return - self.index = x / digit + shift * 3 # index of digit being changed - self.ChangeFreq() - self.repeat_time = 300 # first button push - self.timer.Start(milliseconds=300, oneShot=True) + return None + return x / digit + shift * 3 # index of digit being changed + def OnLeftDown(self, event): # Click on a digit changes the frequency + if self.repeat_time: + self.timer.Stop() + self.repeat_time = 0 + index = self.GetIndex(event) + if index is not None: + self.index = index + mouse_x, mouse_y = event.GetPosition() + width, height = self.GetClientSizeTuple() + if mouse_y < height / 2: + self.increase = True + else: + self.increase = False + self.ChangeFreq() + self.repeat_time = 300 # first button push + self.timer.Start(milliseconds=300, oneShot=True) def OnLeftUp(self, event): self.timer.Stop() self.repeat_time = 0 @@ -114,7 +127,15 @@ self.repeat_time -= 5 self.ChangeFreq() self.timer.Start(milliseconds=self.repeat_time, oneShot=True) - + def OnWheel(self, event): + index = self.GetIndex(event) + if index is not None: + self.index = index + if event.GetWheelRotation() > 0: + self.increase = True + else: + self.increase = False + self.ChangeFreq() class SliderBoxH: """A horizontal control with a slider and text with a value. The text must have a %d or %f if display is True.""" @@ -309,6 +330,7 @@ dc.SetFont(self.GetFont()) label = self.GetLabel() tw, th = dc.GetTextExtent(label) + self.label_width = tw dx = dy = self.labelDelta slabel = re.split('('+unichr(0x25CF)+')', label) # unicode symbol for record: a filled dot for part in slabel: # This code makes the symbol red. Thanks to Christof, DJ4CM. @@ -325,6 +347,37 @@ pass def OnKeyUp(self, event): pass + def DrawGlyphAdj(self, dc, width, height): # Add an adjustment indicator to the label + if not conf.decorate_buttons: + return + wd, ht = dc.GetTextExtent('X') + wd = wd * 12 / 10 + wd = wd + wd % 2 + b = self.bezelWidth + 1 + dc.SetPen(wx.Pen(conf.color_enable, 1, wx.SOLID)) + if self.GetValue(): + dc.SetBrush(wx.Brush(self.color, wx.SOLID)) + else: + dc.SetBrush(wx.Brush(conf.color_btn, wx.SOLID)) + x1 = width - wd * 2 + dc.DrawRoundedRectangle(x1, b, wd, height - b * 2, 6) + x1 = x1 + 2 + y1 = height // 2 + x2 = x1 + wd - 4 + dc.DrawLine( x1, y1, x2, y1) + dc.DrawLine(x1+2, y1-1, x2-2, y1-1) + dc.DrawLine(x1+2, y1+1, x2-2, y1+1) + def DrawGlyphCycle(self, dc, width, height): # Add a cycle indicator to the label + if not conf.decorate_buttons: + return + uch = conf.btn_text_cycle + wd, ht = dc.GetTextExtent(uch) + if wd * 2 + self.label_width > width: # not enough space + uch = conf.btn_text_cycle_small + wd, ht = dc.GetTextExtent(uch) + dc.DrawText(uch, width - wd, (height - ht) // 2) + else: + dc.DrawText(uch, width - wd * 15 // 10, (height - ht) // 2) class QuiskPushbutton(QuiskButtons, wx.lib.buttons.GenButton): """A plain push button widget.""" @@ -514,6 +567,9 @@ self.adjust = None else: self.adjust = QFilterButtonWindow(self) + def DrawLabel(self, dc, width, height, dx=0, dy=0): + QuiskButtons.DrawLabel(self, dc, width, height, dx, dy) + self.DrawGlyphAdj(dc, width, height) class QSliderButtonWindow(wx.Frame): """Create a window with controls for the button""" @@ -545,11 +601,12 @@ class QuiskSliderButton(QuiskCheckbutton): """An adjustable check button; right-click to adjust.""" def __init__(self, parent, command=None, text='', color=None, slider_value=0, - slider_min=0, slider_max=1000): + slider_min=0, slider_max=1000, display=False): if color is None: color = conf.color_adjust_btn self.text = text self.dual = False # separate values for button up or down + self.display = display # Display the value at the top self.slider_value = slider_value # value for not dual self.slider_value_off = slider_value # value for dual and button up self.slider_value_on = slider_value # value for dual and button down @@ -575,9 +632,15 @@ else: value = self.slider_value_off self.adjust = QSliderButtonWindow(self, value) + if self.display: + self.DisplaySliderValue(value) def SetLabel(self, text=None): if text is not None: QuiskCheckbutton.SetLabel(self, text) + def DisplaySliderValue(self, value): + value = self.slider_min + self.slider_max - value # values are backwards + value = float(value) / self.slider_max + self.adjust.SetTitle("%6.3f" % value) def SetSlider(self, value=None, value_off=None, value_on=None): if value is not None: self.slider_value = value @@ -594,12 +657,17 @@ else: self.slider_value_off = value self.SetValue(self.GetValue(), True) + if self.display and self.adjust: + self.DisplaySliderValue(value) def OnButton(self, event): if self.adjust: self.adjust.Destroy() self.adjust = None QuiskCheckbutton.OnButton(self, event) - + def DrawLabel(self, dc, width, height, dx=0, dy=0): + QuiskButtons.DrawLabel(self, dc, width, height, dx, dy) + self.DrawGlyphAdj(dc, width, height) + class QuiskSpotButton(QuiskSliderButton): def SetLabel(self, text=None): if text is None: @@ -611,6 +679,15 @@ QuiskCheckbutton.SetLabel(self, text) self.Refresh() +class QuiskImdButton(QuiskSliderButton): + def OnSlider(self, event): + value = event.GetEventObject().GetValue() + self.slider_value = value + if self.display and self.adjust: + self.DisplaySliderValue(value) + value = self.slider_min + self.slider_max - self.slider_value + QS.set_imd_level(value) + class QuiskCycleCheckbutton(QuiskCheckbutton): """A button that cycles through its labels with each push. @@ -671,6 +748,21 @@ self.direction = 1 if self.command: self.command(event) + def DrawLabel(self, dc, width, height, dx=0, dy=0): + QuiskButtons.DrawLabel(self, dc, width, height, dx, dy) + self.DrawGlyphCycle(dc, width, height) + +class QuiskCycleMenuButton(QuiskCycleCheckbutton): + """A cycle check button that can be right-clicked to configure.""" + def __init__(self, parent, menu, command, labels, color=None, is_radio=False): + QuiskCycleCheckbutton.__init__(self, parent, command, labels, color, is_radio) + self.menu = menu + def OnRightDown(self, event): + pos = event.GetPosition() + self.PopupMenu(self.menu, pos) + def DrawLabel(self, dc, width, height, dx=0, dy=0): + QuiskButtons.DrawLabel(self, dc, width, height, dx, dy) + self.DrawGlyphAdj(dc, width, height) class RadioButtonGroup: """This class encapsulates a group of radio buttons. This class is not a button! @@ -743,6 +835,8 @@ return self.button.GetLabel() def GetSelectedButton(self): # return the selected button return self.button + def GetIndex(self): # Careful. Some buttons are complex. + return self.buttons.index(self.button) class FreqSetter(wx.TextCtrl): def __init__(self, parent, x, y, label, fmin, fmax, freq, command): diff -Nru quisk-3.6.18/sdriqpkg/quisk_hardware.py quisk-3.7.6/sdriqpkg/quisk_hardware.py --- quisk-3.6.18/sdriqpkg/quisk_hardware.py 2013-03-26 17:33:35.000000000 +0000 +++ quisk-3.7.6/sdriqpkg/quisk_hardware.py 2015-07-12 18:06:11.000000000 +0000 @@ -12,7 +12,6 @@ decimations = [1250, 600, 500, 360] def __init__(self, app, conf): BaseHardware.__init__(self, app, conf) - self.use_sidetone = 1 self.clock = conf.sdriq_clock self.rf_gain_labels = ('RF +30', 'RF +20', 'RF +10', 'RF 0 dB') if conf.fft_size_multiplier == 0: @@ -44,10 +43,11 @@ print ('Unknown RfGain') def ChangeFrequency(self, tune, vfo, source='', band='', event=None): if vfo: - sdriq.freq_sdriq(vfo) + sdriq.freq_sdriq(vfo - self.transverter_offset) return tune, vfo def ChangeBand(self, band): # band is a string: "60", "40", "WWV", etc. + BaseHardware.ChangeBand(self, band) btn = self.application.BtnRfGain if btn: if band in ('160', '80', '60', '40'): diff -Nru quisk-3.6.18/sdriqpkg/sdriq.c quisk-3.7.6/sdriqpkg/sdriq.c --- quisk-3.6.18/sdriqpkg/sdriq.c 2014-05-30 17:21:33.000000000 +0000 +++ quisk-3.7.6/sdriqpkg/sdriq.c 2014-07-21 13:31:19.000000000 +0000 @@ -3,6 +3,9 @@ #ifdef MS_WINDOWS #include #include "ftd2xx.h" +#elif defined(__MACH__) +// Changes for MacOS support (__MACH__) thanks to Mario, DL3LSM. +#include "ftd2xx.h" #else #include #include @@ -67,7 +70,8 @@ static int cur_freq, cur_gstate, cur_gain; // current value of frequency and gain static int cur_decimation; // current value of decimation -#ifdef MS_WINDOWS +// Changes for MacOS support (__MACH__) thanks to Mario, DL3LSM. +#if defined(MS_WINDOWS) || defined(__MACH__) static FT_HANDLE quisk_sdriq_fd = INVALID_HANDLE_VALUE; static int Read(void * buf, int bufsize) @@ -571,7 +575,7 @@ } -#ifdef MS_WINDOWS +#if defined(MS_WINDOWS) || defined(__MACH__) static void quisk_open_sdriq_dev(const char * name, char * buf, int bufsize) { #if DEBUG @@ -794,7 +798,7 @@ return NULL; if (quisk_sdriq_fd != INVALID_HANDLE_VALUE) { sdr_idle = -1; // unknown state -#ifdef MS_WINDOWS +#if defined(MS_WINDOWS) || defined(__MACH__) FT_Close(quisk_sdriq_fd); #else close(quisk_sdriq_fd); Binary files /tmp/edonsy5nYK/quisk-3.6.18/sdriqpkg/sdriq.pyd and /tmp/ZwEcPOdYqm/quisk-3.7.6/sdriqpkg/sdriq.pyd differ Binary files /tmp/edonsy5nYK/quisk-3.6.18/sdriqpkg/sdriq.so and /tmp/ZwEcPOdYqm/quisk-3.7.6/sdriqpkg/sdriq.so differ diff -Nru quisk-3.6.18/setup.py quisk-3.7.6/setup.py --- quisk-3.6.18/setup.py 2014-05-31 16:42:58.000000000 +0000 +++ quisk-3.7.6/setup.py 2015-09-14 15:53:28.000000000 +0000 @@ -1,10 +1,11 @@ from distutils.core import setup, Extension import sys +import os # You must define the version here. A title string including # the version will be written to __init__.py and read by quisk.py. -Version = '3.6.18' +Version = '3.7.6' fp = open("__init__.py", "w") # write title string fp.write("#QUISK version %s\n" % Version) @@ -13,10 +14,10 @@ module1 = Extension ('quisk._quisk', #include_dirs = ['.'], #library_dirs = ['.'], - libraries = ['asound', 'portaudio', 'pulse-simple', 'fftw3', 'm'], + libraries = ['asound', 'portaudio', 'pulse', 'fftw3', 'm'], sources = ['quisk.c', 'sound.c', 'sound_alsa.c', 'sound_portaudio.c', 'sound_pulseaudio.c', 'is_key_down.c', 'microphone.c', 'utility.c', - 'filter.c', 'extdemod.c'], + 'filter.c', 'extdemod.c', 'freedv.c'], ) module2 = Extension ('quisk.sdriqpkg.sdriq', @@ -35,7 +36,7 @@ libraries = ['fftw3-3', 'WS2_32', 'Dxguid', 'Dsound'], sources = ['quisk.c', 'sound.c', 'sound_directx.c', 'is_key_down.c', 'microphone.c', 'utility.c', - 'filter.c', 'extdemod.c'], + 'filter.c', 'extdemod.c', 'freedv.c'], ) modulew2 = Extension ('quisk.sdriqpkg.sdriq', @@ -47,8 +48,50 @@ #extra_link_args = ['--enable-auto-import'], ) +# Changes for MacOS support thanks to Mario, DL3LSM. +modulem1 = Extension ('quisk._quisk', + #include_dirs = ['.'], + #library_dirs = ['.'], + libraries = ['portaudio', 'fftw3', 'm', 'pulse'], + sources = ['quisk.c', 'sound.c', 'sound_portaudio.c', + 'is_key_down.c', 'microphone.c', 'utility.c', + 'filter.c', 'extdemod.c', 'freedv.c'], + ) + +modulem2 = Extension ('quisk.sdriqpkg.sdriq', + #libraries = [':_quisk.so', 'm'], + libraries = ['m', 'ftd2xx'], + sources = ['import_quisk_api.c', 'sdriqpkg/sdriq.c'], + include_dirs = ['.', '..', '/opt/local/include'], + library_dirs = ['.', '/opt/local/lib'], + #runtime_library_dirs = ['.'], + ) + +# Changes for building from macports provided by Eric, KM4DSJ +modulemp1 = Extension ('quisk._quisk', + include_dirs = ['.', '/opt/local/include'], + library_dirs = ['.', '/opt/local/lib'], + libraries = ['portaudio', 'fftw3', 'm', 'pulse'], + sources = ['quisk.c', 'sound.c', 'sound_portaudio.c', + 'is_key_down.c', 'microphone.c', 'utility.c', + 'filter.c', 'extdemod.c', 'freedv.c', 'sound_pulseaudio.c'], + ) + +modulemp2 = Extension ('quisk.sdriqpkg.sdriq', + #libraries = [':_quisk.so', 'm'], + libraries = ['m', 'ftd2xx'], + sources = ['import_quisk_api.c', 'sdriqpkg/sdriq.c'], + include_dirs = ['.', '..', '/opt/local/include'], + library_dirs = ['.', '/opt/local/lib'], + #runtime_library_dirs = ['.'], + ) + if sys.platform == "win32": Modules = [modulew1, modulew2] +elif sys.platform == "darwin" and os.path.exists('/opt/local/lib'): + Modules = [modulemp1, modulemp2] +elif sys.platform == "darwin": + Modules = [modulem1, modulem2] else: Modules = [module1, module2] diff -Nru quisk-3.6.18/softrock/conf_rx_tx_ensemble.py quisk-3.7.6/softrock/conf_rx_tx_ensemble.py --- quisk-3.6.18/softrock/conf_rx_tx_ensemble.py 2014-05-25 21:11:40.000000000 +0000 +++ quisk-3.7.6/softrock/conf_rx_tx_ensemble.py 2015-08-13 17:47:27.000000000 +0000 @@ -45,3 +45,4 @@ mic_playback_rate = sample_rate # Playback rate for microphone #mic_out_volume = 0.6 # Transmit sound output volume (after all processing) as a fraction 0.0 to 1.0 +repeater_delay = 0.25 # delay for changing repeater frequency in seconds diff -Nru quisk-3.6.18/softrock/hardware_net.py quisk-3.7.6/softrock/hardware_net.py --- quisk-3.6.18/softrock/hardware_net.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/softrock/hardware_net.py 2015-09-14 14:51:33.000000000 +0000 @@ -0,0 +1,101 @@ + +from __future__ import print_function + +import threading, time, math, socket, re +from quisk_hardware_model import Hardware as BaseHardware + +DEBUG = 1 + +import _quisk as QS + +class Hardware(BaseHardware): + def __init__(self, app, conf): + BaseHardware.__init__(self, app, conf) + self.vfo = None + self.ptt_button = 0 + self.usbsr_ip_address = conf.usbsr_ip_address + self.usbsr_port = conf.usbsr_port + + def open(self): # Called once to open the Hardware + freq = self.GetFreq() + if freq: + print ('Run freq', freq) + text = "found usbsoftrock daemon" + self.application.bottom_widgets.info_text.SetLabel(text) + return True + else: + print ('cannot find usbsoftrock daemon') + text = "cannot find usbsoftrock daemon" + self.application.bottom_widgets.info_text.SetLabel(text) + return False + + def close(self): + pass + def ChangeFrequency(self, tune, vfo, source='', band='', event=None): + if self.vfo != vfo: + self.SetFreq(vfo) + self.vfo = vfo + return tune, vfo + + def ReturnFrequency(self): + # Return the current tuning and VFO frequency. If neither have changed, + # you can return (None, None). This is called at about 10 Hz by the main. + # return (tune, vfo) # return changed frequencies + return None, None # frequencies have not changed + + def ChangeBand(self, band): + # band is a string: "60", "40", "WWV", etc. + pass + + def HeartBeat(self): # Called at about 10 Hz by the main + pass + + def GetFreq(self): # return the running frequency + MESSAGE = "get freq" + srsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + srsock.settimeout(1) + srsock.sendto(MESSAGE, (self.usbsr_ip_address, self.usbsr_port)) + try: + data, addr = srsock.recvfrom(1024) # buffer size is 1024 bytes + except: + srsock.close() + print ('error') + return None #maybe return None instead to simplify if statement + else: + srsock.close() + print ('recieved data', data) + freq = float(re.findall("\d+.\d+", data)[0]) + freq = int(freq * 1.0e6) + return freq + + def SetFreq(self, freq): + if freq <= 0 or freq > 30000000: + return + freq = freq/float(1.0e6) + MESSAGE = "set freq " + str(freq) + srsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + srsock.sendto(MESSAGE, (self.usbsr_ip_address, self.usbsr_port)) + print (MESSAGE) + return True + + def OnButtonPTT(self, event=None): + if event: + if event.GetEventObject().GetValue(): + self.ptt_button = 1 + message = "set ptt on" + else: + self.ptt_button = 0 + message = "set ptt off" + srsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + srsock.settimeout(1) + srsock.sendto(message, (self.usbsr_ip_address, self.usbsr_port)) + data, addr = srsock.recvfrom(1024) # buffer size is 1024 bytes + srsock.close() + print (data) + if data == "ok": + QS.set_key_down(self.ptt_button) + else: + print ('error doing', message) + text = "error setting ptt on or off!" + self.application.bottom_widgets.info_text.SetLabel(text) + diff -Nru quisk-3.6.18/softrock/hardware_usb.py quisk-3.7.6/softrock/hardware_usb.py --- quisk-3.6.18/softrock/hardware_usb.py 2014-05-26 14:04:39.000000000 +0000 +++ quisk-3.7.6/softrock/hardware_usb.py 2015-08-13 19:02:47.000000000 +0000 @@ -36,6 +36,12 @@ BaseHardware.__init__(self, app, conf) self.usb_dev = None self.vfo = None + self.repeater_freq = None # original repeater output frequency + try: + self.repeater_delay = conf.repeater_delay # delay for changing repeater frequency in seconds + except: + self.repeater_delay = 0.25 + self.repeater_time0 = 0 # time of repeater change in frequency self.ptt_button = 0 self.is_cw = False self.key_thread = None @@ -84,9 +90,9 @@ def ChangeFrequency(self, tune, vfo, source='', band='', event=None): if self.usb_dev and self.vfo != vfo: if self.conf.si570_direct_control: - if self.SetFreqByDirect(vfo): + if self.SetFreqByDirect(vfo - self.transverter_offset): self.vfo = vfo - elif self.SetFreqByValue(vfo): + elif self.SetFreqByValue(vfo - self.transverter_offset): self.vfo = vfo if DEBUG: print ('Change to', vfo) @@ -97,6 +103,21 @@ # you can return (None, None). This is called at about 10 Hz by the main. # return (tune, vfo) # return changed frequencies return None, None # frequencies have not changed + def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx + if offset is None: # Return True if frequency change is complete + if time.time() > self.repeater_time0 + self.repeater_delay: + return True + elif offset == 0: # Change back to the original frequency + if self.repeater_freq is not None: + self.repeater_time0 = time.time() + self.ChangeFrequency(self.repeater_freq, self.repeater_freq, 'repeater') + self.repeater_freq = None + else: # Shift to repeater input frequency + self.repeater_time0 = time.time() + self.repeater_freq = self.vfo + vfo = self.vfo + int(offset * 1000) # Convert kHz to Hz + self.ChangeFrequency(vfo, vfo, 'repeater') + return False def ChangeMode(self, mode): # Change the tx/rx mode # mode is a string: "USB", "AM", etc. if mode in ('CWU', 'CWL'): @@ -109,7 +130,7 @@ self.OnButtonPTT() def ChangeBand(self, band): # band is a string: "60", "40", "WWV", etc. - pass + BaseHardware.ChangeBand(self, band) def OnSpot(self, level): if self.key_thread: self.key_thread.OnSpot(level) @@ -218,49 +239,59 @@ self.poll_secs = poll_secs self.key_hang_time = key_hang_time self.ptt_button = 0 - self.spot_level = 0 + self.spot_level = -1 # level is -1 for Spot button Off; else the Spot level 0 to 1000. self.currently_in_tx = 0 self.is_cw = False self.key_timer = 0 - self.is_transmit = 0 + self.key_transmit = 0 threading.Thread.__init__(self) self.doQuit = threading.Event() self.doQuit.clear() def run(self): while not self.doQuit.isSet(): + try: # Test key up/down state + ret = self.usb_dev.ctrl_transfer(IN, 0x51, 0, 0, 1) + except usb.core.USBError: + key_down = None + if DEBUG: traceback.print_exc() + else: + # bit 0x20 is the tip, bit 0x02 is the ring (ring not used) + if ret[0] & 0x20 == 0: # Tip: key is down + key_down = True + else: # key is up + key_down = False if self.is_cw: - try: # Test key up/down state - ret = self.usb_dev.ctrl_transfer(IN, 0x51, 0, 0, 1) - except usb.core.USBError: - if DEBUG: traceback.print_exc() - else: - # bit 0x20 is the tip, bit 0x02 is the ring (ring not used) - if self.spot_level or ret[0] & 0x20 == 0: # Tip: key is down - QS.set_key_down(1) - self.is_transmit = 1 - self.key_timer = time.time() - else: # key is up - QS.set_key_down(0) - if self.is_transmit and time.time() - self.key_timer > self.key_hang_time: - self.is_transmit = 0 - if self.is_transmit != self.currently_in_tx: - try: - self.usb_dev.ctrl_transfer(IN, 0x50, self.is_transmit, 0, 3) - except usb.core.USBError: - if DEBUG: traceback.print_exc() - else: - self.currently_in_tx = self.is_transmit # success - QS.set_transmit_mode(self.is_transmit) - if DEBUG: print ("Change currently_in_tx", self.currently_in_tx) - elif self.ptt_button != self.currently_in_tx: - QS.set_key_down(self.ptt_button) - try: - self.usb_dev.ctrl_transfer(IN, 0x50, self.ptt_button, 0, 3) - except usb.core.USBError: - if DEBUG: traceback.print_exc() + if self.spot_level >= 0 or key_down: # key is down + QS.set_key_down(1) + self.key_transmit = 1 + self.key_timer = time.time() + else: # key is up + QS.set_key_down(0) + if self.key_transmit and time.time() - self.key_timer > self.key_hang_time: + self.key_transmit = 0 + if self.key_transmit != self.currently_in_tx: + try: + self.usb_dev.ctrl_transfer(IN, 0x50, self.key_transmit, 0, 3) + except usb.core.USBError: + if DEBUG: traceback.print_exc() + else: + self.currently_in_tx = self.key_transmit # success + QS.set_transmit_mode(self.key_transmit) + if DEBUG: print ("Change CW currently_in_tx", self.currently_in_tx) + else: + if key_down or self.ptt_button: + self.key_transmit = 1 else: - self.currently_in_tx = self.ptt_button # success - if DEBUG: print ("Change currently_in_tx", self.currently_in_tx) + self.key_transmit = 0 + if self.key_transmit != self.currently_in_tx: + QS.set_key_down(self.key_transmit) + try: + self.usb_dev.ctrl_transfer(IN, 0x50, self.key_transmit, 0, 3) + except usb.core.USBError: + if DEBUG: traceback.print_exc() + else: + self.currently_in_tx = self.key_transmit # success + if DEBUG: print ("Change currently_in_tx", self.currently_in_tx) time.sleep(self.poll_secs) def stop(self): """Set a flag to indicate that the thread should end.""" diff -Nru quisk-3.6.18/sound_alsa.c quisk-3.7.6/sound_alsa.c --- quisk-3.6.18/sound_alsa.c 2014-05-30 17:05:27.000000000 +0000 +++ quisk-3.7.6/sound_alsa.c 2015-04-23 22:01:22.000000000 +0000 @@ -218,20 +218,20 @@ timer += nSamples; if (timer > playdev->sample_rate) { timer = 0; - printf("Samples new %d old %ld total %ld latency_frames %d\n", nSamples, delay, nSamples + delay, playdev->latency_frames); + printf("play_alsa %s: Samples new %d old %ld total %ld latency_frames %d\n", playdev->name, nSamples, delay, nSamples + delay, playdev->latency_frames); } #endif if (nSamples + delay > playdev->latency_frames * 9 / 10) { nSamples--; #if DEBUG_IO - printf("Remove a sample nSamples %d delay %d total %d\n", nSamples, (int)delay, nSamples + (int)delay); + printf("play_alsa %s: Remove a sample nSamples %d delay %d total %d\n", playdev->name, nSamples, (int)delay, nSamples + (int)delay); #endif } else if(nSamples + delay < playdev->latency_frames * 5 / 10) { cSamples[nSamples] = cSamples[nSamples - 1]; nSamples++; #if DEBUG_IO - printf ("Add a sample nSamples %d delay %d total %d\n", nSamples, (int)delay, nSamples + (int)delay); + printf ("play_alsa %s: Add a sample nSamples %d delay %d total %d\n", playdev->name, nSamples, (int)delay, nSamples + (int)delay); #endif } #endif @@ -240,8 +240,8 @@ quisk_sound_state.write_error++; playdev->dev_error++; #if DEBUG_IO - QuiskPrintTime("", 0); - printf("play_alsa: Discard %d of %d samples at %ld delay\n", index, nSamples, delay); + //QuiskPrintTime("", 0); + printf("play_alsa %s: Discard %d of %d samples at %ld delay\n", playdev->name, index, nSamples, delay); #endif } @@ -877,6 +877,7 @@ unsigned int idx; long imin, imax, tmp; snd_ctl_elem_type_t type; + unsigned int count; snd_ctl_elem_info_alloca(&info); snd_ctl_elem_id_alloca(&id); @@ -912,61 +913,63 @@ snd_ctl_elem_info_get_id(info, id); type = snd_ctl_elem_info_get_type(info); snd_ctl_elem_value_set_id(control, id); + count = snd_ctl_elem_info_get_count(info); - idx = 0; - switch (type) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - if (PyObject_IsTrue(value)) - snd_ctl_elem_value_set_boolean(control, idx, 1); - else - snd_ctl_elem_value_set_boolean(control, idx, 0); - break; - case SND_CTL_ELEM_TYPE_INTEGER: - imin = snd_ctl_elem_info_get_min(info); - imax = snd_ctl_elem_info_get_max(info); - if (PyFloat_CheckExact(value)) { - tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4); - snd_ctl_elem_value_set_integer(control, idx, tmp); - } - else if(PyInt_Check(value)) { - tmp = PyInt_AsLong(value); - snd_ctl_elem_value_set_integer(control, idx, tmp); - } - else { - snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); - } - break; - case SND_CTL_ELEM_TYPE_INTEGER64: - imin = snd_ctl_elem_info_get_min64(info); - imax = snd_ctl_elem_info_get_max64(info); - if (PyFloat_CheckExact(value)) { - tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4); - snd_ctl_elem_value_set_integer64(control, idx, tmp); - } - else if(PyInt_Check(value)) { - tmp = PyInt_AsLong(value); - snd_ctl_elem_value_set_integer64(control, idx, tmp); - } - else { - snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); - } - break; - case SND_CTL_ELEM_TYPE_ENUMERATED: - if(PyInt_Check(value)) { - tmp = PyInt_AsLong(value); - snd_ctl_elem_value_set_enumerated(control, idx, (unsigned int)tmp); - } - else { - snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); - } - break; - default: - snprintf (err_msg, err_size, "Control %s element has unknown type\n", card_name); - break; - } - if ((err = snd_ctl_elem_write(handle, control)) < 0) { - snprintf (err_msg, err_size, "Control %s element write error: %s\n", card_name, snd_strerror(err)); - return; + for (idx = 0; idx < count; idx++) { + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + if (PyObject_IsTrue(value)) + snd_ctl_elem_value_set_boolean(control, idx, 1); + else + snd_ctl_elem_value_set_boolean(control, idx, 0); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + imin = snd_ctl_elem_info_get_min(info); + imax = snd_ctl_elem_info_get_max(info); + if (PyFloat_CheckExact(value)) { + tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4); + snd_ctl_elem_value_set_integer(control, idx, tmp); + } + else if(PyInt_Check(value)) { + tmp = PyInt_AsLong(value); + snd_ctl_elem_value_set_integer(control, idx, tmp); + } + else { + snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); + } + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + imin = snd_ctl_elem_info_get_min64(info); + imax = snd_ctl_elem_info_get_max64(info); + if (PyFloat_CheckExact(value)) { + tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4); + snd_ctl_elem_value_set_integer64(control, idx, tmp); + } + else if(PyInt_Check(value)) { + tmp = PyInt_AsLong(value); + snd_ctl_elem_value_set_integer64(control, idx, tmp); + } + else { + snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); + } + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + if(PyInt_Check(value)) { + tmp = PyInt_AsLong(value); + snd_ctl_elem_value_set_enumerated(control, idx, (unsigned int)tmp); + } + else { + snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid); + } + break; + default: + snprintf (err_msg, err_size, "Control %s element has unknown type\n", card_name); + break; + } + if ((err = snd_ctl_elem_write(handle, control)) < 0) { + snprintf (err_msg, err_size, "Control %s element write error: %s\n", card_name, snd_strerror(err)); + return; + } } snd_ctl_close(handle); return; diff -Nru quisk-3.6.18/sound.c quisk-3.7.6/sound.c --- quisk-3.6.18/sound.c 2014-06-16 16:41:14.000000000 +0000 +++ quisk-3.7.6/sound.c 2015-09-16 12:46:41.000000000 +0000 @@ -6,6 +6,18 @@ #include #include #include + +#ifdef MS_WINDOWS +#include +#define QUISK_SHUT_RD SD_RECEIVE +#else +#include +#include +#include +#define INVALID_SOCKET -1 +#define QUISK_SHUT_RD SHUT_RD +#endif + #include "quisk.h" #include "filter.h" @@ -37,6 +49,10 @@ // These are arrays of all capture and playback devices, and MUST end with NULL: static struct sound_dev * CaptureDevices[] = {&Capture, &MicCapture, &DigitalInput, NULL}; static struct sound_dev * PlaybackDevices[] = {&Playback, &MicPlayback, &DigitalOutput, &RawSamplePlayback, NULL}; +static int radio_sound_socket = INVALID_SOCKET; // send radio sound samples to a socket +static int radio_sound_mic_socket = INVALID_SOCKET; // receive mic samples from a socket +static int radio_sound_nshorts; // number of shorts (two bytes) to send +static int radio_sound_mic_nshorts; // number of shorts (two bytes) to receive struct sound_conf quisk_sound_state; // Current sound status @@ -349,6 +365,64 @@ } } +static int read_radio_sound_socket(complex double * cSamples) +{ + int i, bytes, nSamples; + short s; + double d; + struct timeval tm_wait; + char buf[1500]; + fd_set fds; + static int started = 0; + + nSamples = 0; + while (1) { // read all available blocks + if (nSamples > SAMP_BUFFER_SIZE / 2) + break; + tm_wait.tv_sec = 0; + tm_wait.tv_usec = 0; + FD_ZERO (&fds); + FD_SET (radio_sound_mic_socket, &fds); + if (select (radio_sound_mic_socket + 1, &fds, NULL, NULL, &tm_wait) != 1) + break; + bytes = recv(radio_sound_mic_socket, buf, 1500, 0); + if (bytes == radio_sound_mic_nshorts * 2) { // required block size + started = 1; + for (i = 2; i < bytes; i += 2) { + memcpy(&s, buf + i, 2); + d = (double)s / CLIP16 * CLIP32; // convert 16-bit samples to 32 bits + cSamples[nSamples++] = d + I * d; + } + } + } + if ( ! started && nSamples == 0) { + i = send(radio_sound_mic_socket, "rr", 2, 0); + if (i != 2) + printf("read_radio_sound_mic_socket returned %d\n", i); + } + return nSamples; +} + +static void send_radio_sound_socket(complex double * cSamples, int count, double volume) +{ // Send count samples. Each sample is sent as two shorts (4 bytes) of I/Q data. + // Send an initial two bytes of zero for each block. + // Transmission is delayed until a whole block of data is available. + int i, sent; + static short udp_iq[750] = {0}; // Documented maximum radio sound samples is 367 + static int udp_size = 1; + + for (i = 0; i < count; i++) { + udp_iq[udp_size++] = (short)(creal(cSamples[i]) * volume * (double)CLIP16 / CLIP32); + udp_iq[udp_size++] = (short)(cimag(cSamples[i]) * volume * (double)CLIP16 / CLIP32); + if (udp_size >= radio_sound_nshorts) { // check count + sent = send(radio_sound_socket, (char *)udp_iq, udp_size * 2, 0); + if (sent != udp_size * 2) + printf("Send audio socket returned %d\n", sent); + udp_size = 1; + } + } +} + int quisk_read_sound(void) // Called from sound thread { // called in an infinite loop by the main program int i, nSamples, mic_count, mic_interp, retval, is_cw, mic_sample_rate; @@ -357,14 +431,48 @@ static double cwCount=0; static complex double tuneVector = (double)CLIP32 / CLIP16; // Convert 16-bit to 32-bit samples static struct quisk_cFilter filtInterp={NULL}; + int key_state; #if DEBUG_MIC == 1 complex double tmpSamples[SAMP_BUFFER_SIZE]; #endif quisk_sound_state.interupts++; + key_state = quisk_is_key_down(); //reading this once is important for predicable bevavior on cork/flush #if DEBUG_IO > 1 QuiskPrintTime("Start read_sound", 0); #endif + +#ifndef MS_WINDOWS + if (quisk_sound_state.IQ_server[0] && rxMode > 1) { + if (Capture.handle && Capture.driver == DEV_DRIVER_PULSEAUDIO) { + if (key_state == 1 && !Capture.cork_status) + quisk_cork_pulseaudio(&Capture, 1); + else if (key_state == 0 && Capture.cork_status) { + quisk_cork_pulseaudio(&Capture, 0); + quisk_flush_pulseaudio(&Capture); + } + } + if (MicPlayback.handle && MicPlayback.driver == DEV_DRIVER_PULSEAUDIO) { + if (key_state == 0 && !MicPlayback.cork_status) + quisk_cork_pulseaudio(&MicPlayback, 1); + else if (key_state == 1 && MicPlayback.cork_status) { + quisk_cork_pulseaudio(&MicPlayback, 0); + quisk_flush_pulseaudio(&MicPlayback); + } + } + } + else if (quisk_sound_state.IQ_server[0]) { + if (Capture.handle && Capture.driver == DEV_DRIVER_PULSEAUDIO) { + if (Capture.cork_status) + quisk_cork_pulseaudio(&Capture, 0); + } + if (MicPlayback.handle && MicPlayback.driver == DEV_DRIVER_PULSEAUDIO) { + if (MicPlayback.cork_status) + quisk_cork_pulseaudio(&MicPlayback, 0); + } + } +#endif + if (pt_sample_read) { // read samples from SDR-IQ or UDP nSamples = (*pt_sample_read)(cSamples); } @@ -414,12 +522,19 @@ #if DEBUG_IO > 1 QuiskPrintTime(" process samples", 0); #endif + + if (quisk_record_state == PLAYBACK) + quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound + else if (quisk_record_state == PLAY_FILE) + quisk_file_playback(cSamples, nSamples, 1.0); // replace radio sound // Play the demodulated audio play_sound_interface(&Playback, nSamples, cSamples, 1, quisk_audioVolume); + if (radio_sound_socket != INVALID_SOCKET) + send_radio_sound_socket(cSamples, nSamples, quisk_audioVolume); // Play digital if required - if (rxMode == 7 || rxMode == 8 || rxMode == 9) + if (rxMode == 7 || rxMode == 8 || rxMode == 9 || rxMode == 13) play_sound_interface(&DigitalOutput, nSamples, cSamples, 0, digital_output_level); // Perhaps record the speaker audio to a file @@ -445,10 +560,13 @@ mic_sample_rate = quisk_sound_state.mic_sample_rate; if (MicCapture.handle) mic_count = read_sound_interface(&MicCapture, cSamples); - if (quisk_record_state == PLAYBACK) { // Discard previous samples and replace with saved sound + else if (radio_sound_mic_socket != INVALID_SOCKET) + mic_count = read_radio_sound_socket(cSamples); + if (quisk_record_state == PLAYBACK) // Discard previous samples and replace with saved sound quisk_tmp_microphone(cSamples, mic_count); - } - if (rxMode == 7 || rxMode == 8 || rxMode == 9) { // Discard previous samples and use digital samples + else if (quisk_record_state == PLAY_FILE) // Discard previous samples and replace with saved sound + quisk_file_microphone(cSamples, mic_count); + if (rxMode == 7 || rxMode == 8 || rxMode == 9 || rxMode == 13) { // Discard previous samples and use digital samples if (DigitalInput.handle) { mic_sample_rate = DigitalInput.sample_rate; mic_count = read_sound_interface(&DigitalInput, cSamples); @@ -496,7 +614,7 @@ if (cwEnvelope > 1.0) cwEnvelope = 1.0; } - if (quiskSpotLevel) + if (quiskSpotLevel >= 0) cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * quiskSpotLevel / 1000.0 * tuneVector * quisk_sound_state.mic_out_volume; else cSamples[mic_count++] = (CLIP16 - 1) * cwEnvelope * tuneVector * quisk_sound_state.mic_out_volume; @@ -566,12 +684,31 @@ void quisk_close_sound(void) // Called from sound thread { +#ifdef MS_WINDOWS + int cleanup = radio_sound_socket != INVALID_SOCKET || radio_sound_mic_socket != INVALID_SOCKET; +#endif quisk_close_sound_portaudio(); quisk_close_sound_alsa(CaptureDevices, PlaybackDevices); - quisk_close_sound_pulseaudio(CaptureDevices, PlaybackDevices); + quisk_close_sound_pulseaudio(); if (pt_sample_stop) (*pt_sample_stop)(); strncpy (quisk_sound_state.err_msg, CLOSED_TEXT, QUISK_SC_SIZE); + if (radio_sound_socket != INVALID_SOCKET) { + close(radio_sound_socket); + radio_sound_socket = INVALID_SOCKET; + } + if (radio_sound_mic_socket != INVALID_SOCKET) { + shutdown(radio_sound_mic_socket, QUISK_SHUT_RD); + send(radio_sound_mic_socket, "ss", 2, 0); + send(radio_sound_mic_socket, "ss", 2, 0); + QuiskSleepMicrosec(1000000); + close(radio_sound_mic_socket); + radio_sound_mic_socket = INVALID_SOCKET; + } +#ifdef MS_WINDOWS + if (cleanup) + WSACleanup(); +#endif } static void set_num_channels(struct sound_dev * dev) @@ -625,6 +762,90 @@ } } +static void open_radio_sound_socket(void) +{ + struct sockaddr_in Addr; + int samples, port, sndsize = 48000; + char radio_sound_ip[QUISK_SC_SIZE]; + char radio_sound_mic_ip[QUISK_SC_SIZE]; +#ifdef MS_WINDOWS + WORD wVersionRequested; + WSADATA wsaData; +#endif + + strncpy(radio_sound_ip, QuiskGetConfigString ("radio_sound_ip", ""), QUISK_SC_SIZE); + strncpy(radio_sound_mic_ip, QuiskGetConfigString ("radio_sound_mic_ip", ""), QUISK_SC_SIZE); + if (radio_sound_ip[0] == 0 && radio_sound_mic_ip[0] == 0) + return; +#ifdef MS_WINDOWS + wVersionRequested = MAKEWORD(2, 2); + if (WSAStartup(wVersionRequested, &wsaData) != 0) { + printf("open_radio_sound_socket: Failure to start WinSock\n"); + return; // failure to start winsock + } +#endif + if (radio_sound_ip[0]) { + port = QuiskGetConfigInt ("radio_sound_port", 0); + samples = QuiskGetConfigInt ("radio_sound_nsamples", 360); + if (samples > 367) + samples = 367; + radio_sound_nshorts = samples * 2 + 1; + radio_sound_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (radio_sound_socket != INVALID_SOCKET) { + setsockopt(radio_sound_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize)); + Addr.sin_family = AF_INET; + Addr.sin_port = htons(port); +#ifdef MS_WINDOWS + Addr.sin_addr.S_un.S_addr = inet_addr(radio_sound_ip); +#else + inet_aton(radio_sound_ip, &Addr.sin_addr); +#endif + if (connect(radio_sound_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) { + close(radio_sound_socket); + radio_sound_socket = INVALID_SOCKET; + } + } + if (radio_sound_socket == INVALID_SOCKET) { + printf("open_radio_sound_socket: Failure to open socket\n"); + } + else { +#if DEBUG_IO + printf("open_radio_sound_socket: opened socket %s\n", radio_sound_ip); +#endif + } + } + if (radio_sound_mic_ip[0]) { + port = QuiskGetConfigInt ("radio_sound_mic_port", 0); + samples = QuiskGetConfigInt ("radio_sound_mic_nsamples", 720); + if (samples > 734) + samples = 734; + radio_sound_mic_nshorts = samples + 1; + radio_sound_mic_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (radio_sound_mic_socket != INVALID_SOCKET) { + setsockopt(radio_sound_mic_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize)); + Addr.sin_family = AF_INET; + Addr.sin_port = htons(port); +#ifdef MS_WINDOWS + Addr.sin_addr.S_un.S_addr = inet_addr(radio_sound_mic_ip); +#else + inet_aton(radio_sound_mic_ip, &Addr.sin_addr); +#endif + if (connect(radio_sound_mic_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) { + close(radio_sound_mic_socket); + radio_sound_mic_socket = INVALID_SOCKET; + } + } + if (radio_sound_mic_socket == INVALID_SOCKET) { + printf("open_radio_sound_mic_socket: Failure to open socket\n"); + } + else { +#if DEBUG_IO + printf("open_radio_sound_mic_socket: opened socket %s\n", radio_sound_mic_ip); +#endif + } + } +} + void quisk_open_sound(void) // Called from GUI thread { int i; @@ -689,6 +910,22 @@ set_num_channels (&DigitalOutput); set_num_channels (&RawSamplePlayback); + //Needed for pulse audio context connection (KM4DSJ) + Capture.stream_dir_record = 1; + Playback.stream_dir_record = 0; + MicCapture.stream_dir_record = 1; + MicPlayback.stream_dir_record= 0; + DigitalInput.stream_dir_record = 1; + DigitalOutput.stream_dir_record = 0; + RawSamplePlayback.stream_dir_record = 0; + + //For remote IQ server over pulseaudio (KM4DSJ) + if (quisk_sound_state.IQ_server[0]) { + strncpy(Capture.server, quisk_sound_state.IQ_server, IP_SIZE); + strncpy(MicPlayback.server, quisk_sound_state.IQ_server, IP_SIZE); + } + + #ifdef FIX_H101 Capture.channel_Delay = Capture.channel_Q; // Obsolete; do not use. #else @@ -722,6 +959,7 @@ // set capture and playback for raw samples RawSamplePlayback.read_frames = 0; RawSamplePlayback.latency_frames = RawSamplePlayback.sample_rate * 500 / 1000; // 500 milliseconds + open_radio_sound_socket(); #if DEBUG_IO printf("Sample buffer size %d, latency msec %d\n", SAMP_BUFFER_SIZE, quisk_sound_state.latency_millisecs); #endif @@ -807,6 +1045,14 @@ return Py_None; } +void quisk_udp_mic_error(char * msg) +{ + MicCapture.dev_error++; +#if DEBUG_IO + printf("%s\n", msg); +#endif +} + static void AddCard(struct sound_dev * dev, PyObject * pylist, const char * txt) { PyObject * v; diff -Nru quisk-3.6.18/sound_directx.c quisk-3.7.6/sound_directx.c --- quisk-3.6.18/sound_directx.c 2014-06-16 16:43:03.000000000 +0000 +++ quisk-3.7.6/sound_directx.c 2015-09-14 15:50:07.000000000 +0000 @@ -615,7 +615,7 @@ { } -void quisk_close_sound_pulseaudio(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) +void quisk_close_sound_pulseaudio() { } @@ -623,3 +623,17 @@ { err_msg[0] = 0; } + +PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args) +{ // Return a list of PulseAudio device names [pycapt, pyplay] + PyObject * pylist, * pycapt, * pyplay; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + pylist = PyList_New(0); // list [pycapt, pyplay] + pycapt = PyList_New(0); // list of capture devices + pyplay = PyList_New(0); // list of play devices + PyList_Append(pylist, pycapt); + PyList_Append(pylist, pyplay); + return pylist; +} diff -Nru quisk-3.6.18/sound_portaudio.c quisk-3.7.6/sound_portaudio.c --- quisk-3.6.18/sound_portaudio.c 2014-05-30 17:06:35.000000000 +0000 +++ quisk-3.7.6/sound_portaudio.c 2015-09-14 15:50:44.000000000 +0000 @@ -1,5 +1,5 @@ /* - * This modue provides sound access for QUISK using the portaudio library. + * This module provides sound access for QUISK using the portaudio library. */ #include #include @@ -406,3 +406,113 @@ { Pa_Terminate(); } + +// Changes for MacOS support (__MACH__) thanks to Mario, DL3LSM. +#if defined(__MACH__) + +static int device_list(PyObject * py, int input) +{ + int retNum = 0; + char buf100[100]; + + PaError err; + + err = Pa_Initialize(); + + if ( err == paNoError ) { + PaDeviceIndex numDev = Pa_GetDeviceCount(); + for (PaDeviceIndex dev = 0; dev < numDev; dev++) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(dev); +#if (0) + printf("found audio device: %d, name=%s, #inp %d, #outp %d defsample %f\n", dev, info->name, info->maxInputChannels, + info->maxOutputChannels, info->defaultSampleRate); +#endif + if ((input && info->maxInputChannels > 0) || + (!input && info->maxOutputChannels > 0)) { // check the available channels for the type) + // found one + if (py) { + snprintf(buf100, 100, "%s", info->name); + PyList_Append(py, PyString_FromString(buf100)); + } + } + } + Pa_Terminate(); + } + return retNum; + +} + +PyObject * quisk_sound_devices(PyObject * self, PyObject * args) +{ // Return a list of ALSA device names [pycapt, pyplay] + PyObject * pylist, * pycapt, * pyplay; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + // Each pycapt and pyplay is [pydev, pyname] + pylist = PyList_New(0); // list [pycapt, pyplay] + pycapt = PyList_New(0); // list of capture devices + pyplay = PyList_New(0); // list of play devices + PyList_Append(pylist, pycapt); + PyList_Append(pylist, pyplay); + device_list(pycapt, 1); + device_list(pyplay, 0); + return pylist; +} + +void quisk_play_alsa(struct sound_dev * dev, int nSamples, + complex double * cSamples, int report_latency, double volume) +{ +} + +void quisk_start_sound_alsa(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) +{ +} + +void quisk_close_sound_alsa(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) +{ +} + +int quisk_read_alsa(struct sound_dev * dev, complex double * samp) +{ + return 0; +} + +void quisk_mixer_set(char * card_name, int numid, PyObject * value, char * err_msg, int err_size) +{ + err_msg[0] = 0; +} + +#if !defined USE_MACPORTS +int quisk_read_pulseaudio(struct sound_dev * dev, complex double * samp) +{ + return 0; +} + +void quisk_play_pulseaudio(struct sound_dev * dev, int j, complex double * samp, int i, double volume) +{ +} + +void quisk_start_sound_pulseaudio(struct sound_dev ** pCapture, struct sound_dev ** pPlayback) +{ +} + +void quisk_close_sound_pulseaudio() +{ +} + + +PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args) +{ // Return a list of PulseAudio device names [pycapt, pyplay] + PyObject * pylist, * pycapt, * pyplay; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + pylist = PyList_New(0); // list [pycapt, pyplay] + pycapt = PyList_New(0); // list of capture devices + pyplay = PyList_New(0); // list of play devices + PyList_Append(pylist, pycapt); + PyList_Append(pylist, pyplay); + return pylist; +} +#endif +#endif diff -Nru quisk-3.6.18/sound_pulseaudio.c quisk-3.7.6/sound_pulseaudio.c --- quisk-3.6.18/sound_pulseaudio.c 2014-06-02 13:10:35.000000000 +0000 +++ quisk-3.7.6/sound_pulseaudio.c 2015-09-15 20:28:52.000000000 +0000 @@ -3,6 +3,11 @@ * authors: * * Philip G. Lee , 2014 + * Jim Ahlstrom, N2ADR, October, 2014 + * Eric Thornton, KM4DSJ, September, 2015 + * + * This code replaces the pulseaudio-simple version by Philip G. Lee. It + * uses the asynchronous pulseaudio API. It was written by Eric Thornton, 2015. * * Quisk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,236 +23,703 @@ * along with this program. If not, see . */ + +/* This pulseaudio interface was rewritten to utilize the features in the + * asynchronous API and threaded mainloop. + * Eric Thornton, KM4DSJ 2015 +*/ + #include #include +#include #include +#include +#include +#include #include "quisk.h" +#include -#include -#include +//compiling with verbose = 1 shows stream connection info. +//compiling with verbose = 2 shows stream latency on console @ TIME_EVENT_USEC frequency +int verbose = 0; + +//This is for verbose = 2 latency measurements +#define TIME_EVENT_USEC 100000 + +// From pulsecore/macro.h +#define pa_memzero(x,l) (memset((x), 0, (l))) +#define pa_zero(x) (pa_memzero(&(x), sizeof(x))) // Current sound status extern struct sound_conf quisk_sound_state; -// Buffer for float32 samples from sound -static float fbuffer[SAMP_BUFFER_SIZE]; +//pointers for aychronous threaded loop +static pa_threaded_mainloop *pa_ml; +static pa_mainloop_api *pa_mlapi; +static pa_context *pa_ctx; //local context +static pa_context *pa_IQ_ctx; //remote context for IQ audio +static pa_time_event *time_event_local; +static pa_time_event *time_event_IQ; +volatile int streams_ready = 0; //This is ++/-- by the mainloop thread + +// remember all open devices for easy cleanup on exit +static pa_stream* OpenPulseDevices[PA_LIST_SIZE * 2] = {NULL}; + +//sorted lists of local and remote devices +static struct sound_dev *LocalPulseDevices[PA_LIST_SIZE] = {NULL}; +static struct sound_dev *RemotePulseDevices[PA_LIST_SIZE] = {NULL}; -/*! - * \brief Read sound samples from PulseAudio. - * - * Samples are converted to 32 bits with a range of +/- CLIP32 and placed into - * cSamples. - * - * \param dev Input. The device from which to read audio. - * \param cSamples Output. The output samples. - * - * \returns the number of samples placed into \c cSamples + + +/* This callback happens any time a stream changes state. Here, it's primary used to + * tell the quisk thread when streams are ready. */ -int quisk_read_pulseaudio( - struct sound_dev* dev, - complex double * cSamples -) -{ - pa_simple* padev = (pa_simple*)(dev->handle); - int error = 0; - int ret = 0; - int i = 0; - int nSamples = 0; - int num_channels = dev->num_channels; - // A frame is a sample from each channel - int read_frames = dev->read_frames; - // If 0, we are expected to do non-blocking read, but ignore for now and - // keep doing the blocking read. - if( read_frames == 0 ) - read_frames = (int)(quisk_sound_state.data_poll_usec * 1e-6 * dev->sample_rate + 0.5); - int read_bytes = read_frames * num_channels * sizeof(float); - float fi=0.f, fq=0.f; - complex double c; - - // Read and check for errors. - ret = pa_simple_read( padev, fbuffer, read_bytes, &error ); - if( ret < 0 ) - { - dev->dev_error++; - - fprintf( - stderr, - __FILE__": quisk_read_pulseaudio() failed %s\n", - pa_strerror(error) - ); - - return 0; - } - - // Convert sampled data to complex data - for( i = 0, nSamples = 0; nSamples < read_frames; i += num_channels, ++nSamples ) - { - fi = fbuffer[i + dev->channel_I]; - fq = fbuffer[i + dev->channel_Q]; - if (fi >= 1.0 || fi <= -1.0) - dev->overrange++; - if (fq >= 1.0 || fq <= -1.0) - dev->overrange++; - cSamples[nSamples] = (fi + I * fq) * CLIP32; - } - - // DC removal; R.G. Lyons page 553 - for (i = 0; i < nSamples; i++) - { - c = cSamples[i] + dev->dc_remove * 0.95; - cSamples[i] = c - dev->dc_remove; - dev->dc_remove = c; - } - - return nSamples; + +void stream_state_callback(pa_stream *s, void *userdata) { + struct sound_dev *dev = userdata; + assert(s); + assert(dev); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_CREATING: + break; + + case PA_STREAM_TERMINATED: + if (verbose) + printf("stream %s terminated\n", dev->name); + streams_ready--; + break; + + case PA_STREAM_READY: + streams_ready++; //increment counter to tell other thread that this stream is ready + if (verbose) { + const pa_buffer_attr *a; + printf("Connected to device %s (%u, %ssuspended). ", + pa_stream_get_device_name(s), pa_stream_get_device_index(s), + pa_stream_is_suspended(s) ? "" : "not "); + + if (!(a = pa_stream_get_buffer_attr(s))) + printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + else if (!(a->prebuf)) { + printf("Buffer metrics %s: maxlength=%u, fragsize=%u\n", dev->name, a->maxlength, a->fragsize); + } + else { + printf("Buffer metrics %s: maxlength=%u, prebuf=%u, tlength=%u minreq=%u\n", + dev->name, a->maxlength, a->prebuf, a->tlength, a->minreq); + } + } + break; + + case PA_STREAM_FAILED: + default: + printf("Stream error: %s - %s\n", dev->name, pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + exit(1); + } +} + + +//Indicates underflow on passed stream (playback) +static void stream_underflow_callback(pa_stream *s, void *userdata) { + struct sound_dev *dev = userdata; + assert(s); + assert(dev); + + if (verbose) + printf("Stream underrun %s\n", dev->name); + dev->dev_error++; +} + +//Indicates overflow on passed stream (record) +static void stream_overflow_callback(pa_stream *s, void *userdata) { + struct sound_dev *dev = userdata; + assert(s); + + if (verbose) + printf("Stream overrun %s\n", dev->name); + dev->dev_error++; +} + +//Indicates stream has started +static void stream_started_callback(pa_stream *s, void *userdata) { + struct sound_dev *dev = userdata; + assert(s); + assert(dev); + + if (verbose) + printf("Stream started %s\n", dev->name); +} + +//Called when cork/uncork has succeeded on passed stream. Signals mainloop when complete. +static void stream_corked_callback(pa_stream *s, int success, void *userdata) { + assert(s); + struct sound_dev *dev = userdata; + + if (s) { + if (verbose) + printf("Stream cork/uncork %s success\n", dev->name); + pa_threaded_mainloop_signal(pa_ml, 0); + } + else { + if (verbose) + printf("Stream cork/uncork %s Failure!\n", dev->name); + exit(1); + } +} + +// Called when stream flush has completed. +static void stream_flushed_callback(pa_stream *s, int success, void *userdata) { + assert(s); + struct sound_dev *dev = userdata; + + if (s) { + printf("Stream flush %s success\n", dev->name); + pa_threaded_mainloop_signal(pa_ml, 0); + } + else { + printf("Stream flush %s Failure!\n", dev->name); + exit(1); + } +} + +// This is called by the play function when the timing structure is updated. +static void stream_timing_callback(pa_stream *s, int success, void *userdata) { + struct sound_dev *dev = userdata; + pa_usec_t l; + int negative = 0; + assert(s); + + if (!success || pa_stream_get_latency(s, &l, &negative) < 0) { + printf("pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + return; + } + + dev->dev_latency = (int)l; + if (negative) + dev->dev_latency *= -1; + pa_threaded_mainloop_signal(pa_ml, 0); +} + + + +// Show the current latency on the console for debugging purposes +static void stream_debug_timing_callback(pa_stream *s, int success, void *userdata) { + struct sound_dev *dev = userdata; + pa_usec_t l; + int negative = 0; + assert(s); + + if (!success || pa_stream_get_latency(s, &l, &negative) < 0) { + printf("pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + return; + } + + printf("%s Latency:%7.0fus", dev->stream_description, (float)(l)*(negative?-1.0f:1.0f)); +} + +//Called on frequency determined by TIME_EVENT_USEC +static void time_event_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + assert(userdata); + struct sound_dev **pDevices = userdata; + + while(*pDevices) { + struct sound_dev *dev = *pDevices++; + assert(dev); + pa_stream *s = dev->handle; + + if (s && pa_stream_get_state(s) == PA_STREAM_READY) { + pa_operation *o; + if (!(o = pa_stream_update_timing_info(s, stream_debug_timing_callback, dev))) + printf("pa_stream_update_timing_info() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + else + pa_operation_unref(o); + } + } + + printf("\r"); + fflush(stdout); + + if (e == time_event_IQ) + pa_context_rttime_restart(pa_IQ_ctx, e, pa_rtclock_now() + TIME_EVENT_USEC); + else + pa_context_rttime_restart(pa_ctx, e, pa_rtclock_now() + TIME_EVENT_USEC); +} + + +/* This callback allows us to read in the server side stream information so that we + * can match stream formats and sizes to what is configured in pulseaudio. +*/ + +static void server_info_cb(pa_context *c, const pa_server_info *info, void *userdata) { + struct sound_dev **pDevices = userdata; + pa_buffer_attr rec_ba; + pa_buffer_attr play_ba; + pa_sample_spec ss; + pa_sample_spec default_ss; + pa_stream_flags_t pb_flags = PA_STREAM_NOFLAGS; + pa_stream_flags_t rec_flags = PA_STREAM_ADJUST_LATENCY; + default_ss = info->sample_spec; + + printf("Connected to %s \n", info->host_name); + + while(*pDevices) { + struct sound_dev *dev = *pDevices++; + const char *dev_name; + pa_stream *s; + + pa_zero(rec_ba); + pa_zero(play_ba); + + if (dev->name[5] == ':') + dev_name = dev->name + 6; // the device name is given; "pulse:alsa_input.pci-0000_00_1b.0.analog-stereo" + else + dev_name = NULL; // the device name is "pulse" for the default device + + if (verbose) + printf("Opening Device %s ", dev_name); + + //Construct sample specification. Use S16LE if availiable. Default to Float32 for others. + //If the source/sink is not Float32, Pulseaudio will convert it (uses CPU) + //dev->sample_bytes = (int)pa_frame_size(&ss) / ss.channels; + if (default_ss.format == PA_SAMPLE_S16LE) { + dev->sample_bytes = 2; + ss.format = default_ss.format; + } + else { + dev->sample_bytes = 4; + ss.format = PA_SAMPLE_FLOAT32LE; + } + + ss.rate = dev->sample_rate; + ss.channels = dev->num_channels; + + rec_ba.maxlength = (uint32_t) -1; + rec_ba.fragsize = (uint32_t) SAMP_BUFFER_SIZE / 16; //higher numbers eat cpu on reading monitor streams. + + play_ba.maxlength = (uint32_t) -1; + play_ba.prebuf = (uint32_t) (dev->sample_bytes * ss.channels * dev->latency_frames); + //play_ba.tlength = (uint32_t) -1; + play_ba.tlength = play_ba.prebuf; + + if (dev->latency_frames == 0) + play_ba.minreq = (uint32_t) 0; //verify this is sane + else + play_ba.minreq = (uint32_t) -1; + + if (dev->stream_dir_record) { + + if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) { + printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c))); + exit(1); + } + if (pa_stream_connect_record(s, dev_name, &rec_ba, rec_flags) < 0) { + printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c))); + exit(1); + } + pa_stream_set_overflow_callback(s, stream_overflow_callback, dev); + } + + else { + pa_cvolume cv; + pa_volume_t volume = PA_VOLUME_NORM; + + if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) { + printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c))); + exit(1); + } + if (pa_stream_connect_playback(s, dev_name, &play_ba, pb_flags, pa_cvolume_set(&cv, ss.channels, volume), NULL) < 0) { + printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c))); + exit(1); + } + pa_stream_set_underflow_callback(s, stream_underflow_callback, dev); + } + + + pa_stream_set_state_callback(s, stream_state_callback, dev); + pa_stream_set_started_callback(s, stream_started_callback, dev); + + dev->handle = (void*)s; //save memory address for stream in handle + + int i; + for(i=0;i < PA_LIST_SIZE;i++) { //save address for stream for easy exit + if (!(OpenPulseDevices[i])) { + OpenPulseDevices[i] = dev->handle; + break; + } + } + } +} + + +//Context state callback. Basically here to pass initialization to server_info_cb +void state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + state = pa_context_get_state(c); + switch (state) { + // There are just here for reference + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + printf("Context Terminated"); + break; + case PA_CONTEXT_READY: { + pa_operation *o; + if (!(o = pa_context_get_server_info(c, server_info_cb, userdata))) + printf("pa_context_get_server_info() failed: %s", pa_strerror(pa_context_errno(c))); + else + pa_operation_unref(o); + } + } +} + + +#if 0 +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + struct sound_dev *dev = userdata; + + if (!success) { + printf("Failed to drain stream: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + quit(1); + } + + if (verbose) + printf("Playback stream %s drained.\n", dev->name); +} + + +/*drain stream function*/ +void quisk_drain_cork_stream(struct sound_dev *dev) { + pa_stream *s = dev->handle; + pa_operation *o; + + if (!(o = pa_stream_drain(s, stream_drain_complete, NULL))) { + printf("pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + exit(1); + } + + pa_operation_unref(o); +} + +#endif + +//Cork function. Holds mainloop lock until operation is completed. +void quisk_cork_pulseaudio(struct sound_dev *dev, int b) { + pa_stream *s = dev->handle; + pa_operation *o; + + pa_threaded_mainloop_lock(pa_ml); + + if (!(o = pa_stream_cork(s, b, stream_corked_callback, dev))) { + printf("pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + exit(1); + } + else { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(pa_ml); + + pa_operation_unref(o); + } + pa_threaded_mainloop_unlock(pa_ml); + + if (b) + dev->cork_status = 1; + else + dev->cork_status = 0; +} + +//Flush function. Holds mainloop lock until operation is completed. +void quisk_flush_pulseaudio(struct sound_dev *dev) { + pa_stream *s = dev->handle; + pa_operation *o; + + pa_threaded_mainloop_lock(pa_ml); + + if (!(o = pa_stream_flush(s, stream_flushed_callback, dev))) { + printf("pa_stream_flush(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + exit(1); + } + else { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(pa_ml); + pa_operation_unref(o); + } + pa_threaded_mainloop_unlock(pa_ml); +} + + +static void WaitForPoll(void) { // Implement a blocking read + static double time0 = 0; // start time in seconds + double timer; // time remaining from last poll usec + + timer = quisk_sound_state.data_poll_usec - (QuiskTimeSec() - time0) * 1e6; + + if (timer > 1000.0) // see if enough time has elapsed + QuiskSleepMicrosec((int)timer); // wait for the remainder of the poll interval + + time0 = QuiskTimeSec(); // reset starting time value +} + + +/* Read samples from the PulseAudio device. + * Samples are converted to complex form based upon format, counted + * and returned via cSamples pointer. + * Returns the number of samples placed into cSamples + */ +int quisk_read_pulseaudio(struct sound_dev *dev, complex double *cSamples) { + int i, nSamples; + int read_frames; // A frame is a sample from each channel + const void * fbuffer; + pa_stream *s = dev->handle; + size_t read_bytes; + + if (!dev) + return 0; + + if (dev->cork_status) + return 0; + + + // Note: Access to PulseAudio data from our sound thread requires locking the threaded mainloop. + if (dev->read_frames == 0) { // non-blocking: read available frames + pa_threaded_mainloop_lock(pa_ml); + read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes; + + if (read_frames == 0) { + pa_threaded_mainloop_unlock(pa_ml); + return 0; + } + dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000); + + } + + else { // Blocking read for dev->read_frames total frames + WaitForPoll(); + pa_threaded_mainloop_lock(pa_ml); + read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes; + + if (read_frames == 0) { + pa_threaded_mainloop_unlock(pa_ml); + return 0; + } + dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000); + read_frames = dev->read_frames; + } + + + nSamples = 0; + + while (nSamples < read_frames) { // read samples in chunks until we have enough samples + if (pa_stream_peek (s, &fbuffer, &read_bytes) < 0) { + printf("Failure of pa_stream_peek in quisk_read_pulseaudio\n"); + pa_threaded_mainloop_unlock(pa_ml); + return nSamples; + } + + if (fbuffer == NULL && read_bytes == 0) { // buffer is empty + break; + } + + if (fbuffer == NULL) { // there is a hole in the buffer + pa_stream_drop(s); + continue; + } + + if (nSamples * dev->num_channels * dev->sample_bytes + read_bytes >= SAMP_BUFFER_SIZE) { + if (verbose) + printf("buffer full on %s\n", dev->name); + pa_stream_drop(s); // limit read request to buffer size + break; + } + + // Convert sampled data to complex data. dev->num_channels must be 2. + if (dev->sample_bytes == 4) { //float32 + float fi, fq; + + for( i = 0; i < read_bytes; i += (dev->num_channels * 4)) { + memcpy(&fi, fbuffer + i + (dev->channel_I * 4), 4); + memcpy(&fq, fbuffer + i + (dev->channel_Q * 4), 4); + if (fi >= 1.0 || fi <= -1.0) + dev->overrange++; + if (fq >= 1.0 || fq <= -1.0) + dev->overrange++; + cSamples[nSamples++] = (fi + I * fq) * CLIP32; + } + } + + else if (dev->sample_bytes == 2) { //16bit integer little-endian + int16_t si, sq; + for( i = 0; i < read_bytes; i += (dev->num_channels * 2)) { + memcpy(&si, fbuffer + i + (dev->channel_I * 2), 2); + memcpy(&sq, fbuffer + i + (dev->channel_Q * 2), 2); + if (si >= CLIP16 || si <= -CLIP16) + dev->overrange++; + if (sq >= CLIP16 || sq <= -CLIP16) + dev->overrange++; + int ii = si << 16; + int qq = sq << 16; + cSamples[nSamples++] = ii + I * qq; + } + } + + else { + printf("Unknown sample size for %s", dev->name); + } + pa_stream_drop(s); + } + pa_threaded_mainloop_unlock(pa_ml); + + // DC removal; R.G. Lyons page 553 + complex double c; + for (i = 0; i < nSamples; i++) { + c = cSamples[i] + dev->dc_remove * 0.95; + cSamples[i] = c - dev->dc_remove; + dev->dc_remove = c; + } + return nSamples; } /*! - * \brief Play the samples; write them to PulseAudio. - * + * \Write outgoing samples directly to pulseaudio server. * \param playdev Input. Device to which to play the samples. * \param nSamples Input. Number of samples to play. * \param cSamples Input. Sample buffer to play from. * \param report_latency Input. 1 to update \c quisk_sound_state.latencyPlay, 0 otherwise. * \param volume Input. Ratio in [0,1] by which to scale the played samples. */ -void quisk_play_pulseaudio( - struct sound_dev* playdev, - int nSamples, - complex double * cSamples, - int report_latency, - double volume -) -{ - pa_simple* padev = (pa_simple*)(playdev->handle); - pa_usec_t latency = 0; - size_t nBytes = nSamples * playdev->num_channels * sizeof(float); - int error = 0; - int ret = 0; - float fi=0.f, fq=0.f; - int i=0, n=0; - - if( !padev || nSamples <= 0) - return; - - // Convert from complex data to framebuffer - for(i = 0, n = 0; n < nSamples; i += playdev->num_channels, ++n) - { - fi = volume * creal(cSamples[n]); - fq = volume * cimag(cSamples[n]); - fbuffer[i + playdev->channel_I] = fi / CLIP32; - fbuffer[i + playdev->channel_Q] = fq / CLIP32; - } - - // Report the latency, if requested. - if( report_latency ) - { - latency = pa_simple_get_latency( padev, &error ); - if( latency == (pa_usec_t)(-1) ) - { - fprintf( - stderr, - __FILE__": quisk_play_pulseaudio() failed %s\n", - pa_strerror(error) - ); - - playdev->dev_error++; - } - else - { - // Samples left in play buffer - //quisk_sound_state.latencyPlay = (latency * quisk_sound_state.playback_rate) / 1e6; - quisk_sound_state.latencyPlay = latency; - } - } - - // Write data and check for errors - ret = pa_simple_write( padev, fbuffer, nBytes, &error ); - if( ret < 0 ) - { - fprintf( - stderr, - __FILE__": quisk_play_pulseaudio() failed %s\n", - pa_strerror(error) - ); - - quisk_sound_state.write_error++; - playdev->dev_error++; - } +void quisk_play_pulseaudio(struct sound_dev *dev, int nSamples, complex double *cSamples, + int report_latency, double volume) { + pa_stream *s = dev->handle; + int i=0, n=0; + void *fbuffer; + int fbuffer_bytes = 0; + + if( !dev || nSamples <= 0) + return; + + if (dev->cork_status) + return; + + if (report_latency) { // Report the latency, if requested. + pa_operation *o; + + pa_threaded_mainloop_lock(pa_ml); + + if (!(o = pa_stream_update_timing_info(s, stream_timing_callback, dev))) { + printf("pa_stream_update_timing(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + } + else { + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(pa_ml); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(pa_ml); + + } + + + fbuffer = pa_xmalloc(nSamples * dev->num_channels * dev->sample_bytes); + + // Convert from complex data to framebuffer + + if (dev->sample_bytes == 4) { + float fi=0.f, fq=0.f; + for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 4), ++n) { + fi = (volume * creal(cSamples[n])) / CLIP32; + fq = (volume * cimag(cSamples[n])) / CLIP32; + memcpy(fbuffer + i + (dev->channel_I * 4), &fi, 4); + memcpy(fbuffer + i + (dev->channel_Q * 4), &fq, 4); + } + } + + else if (dev->sample_bytes == 2) { + int ii, qq; + for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 2), ++n) { + ii = (int)(volume * creal(cSamples[n]) / 65536); + qq = (int)(volume * cimag(cSamples[n]) / 65536); + memcpy(fbuffer + i + (dev->channel_I * 2), &ii, 2); + memcpy(fbuffer + i + (dev->channel_Q * 2), &qq, 2); + } + } + + else { + printf("Unknown sample size for %s", dev->name); + exit(1); + } + + + + fbuffer_bytes = nSamples * dev->num_channels * dev->sample_bytes; + pa_threaded_mainloop_lock(pa_ml); + size_t writable = pa_stream_writable_size(s); + + + if (writable > 0) { + if ( writable > 1024*1000 ) //sanity check to prevent pa_xmalloc from crashing on monitor streams + writable = 1024*1000; + if (fbuffer_bytes > writable) { + if (verbose) + printf("Truncating write by %u bytes\n", fbuffer_bytes - (int)writable); + fbuffer_bytes = writable; + } + pa_stream_write(dev->handle, fbuffer, (size_t)fbuffer_bytes, NULL, 0, PA_SEEK_RELATIVE); + //printf("wrote %d to %s\n", writable, dev->name); + } + else { + if (verbose) + printf("Can't write to stream %s. Dropping %d bytes\n", dev->name, fbuffer_bytes); + } + + pa_threaded_mainloop_unlock(pa_ml); + pa_xfree(fbuffer); + fbuffer=NULL; } -/*! - * \brief Open PulseAudio device. - * - * \param dev Input/Output. Device to initialize. \c dev->sample_rate and - * \c dev->num_channels must be properly set before calling. On return, - * \c dev->handle holds a \c pa_simple pointer, and \c dev->driver is - * set to \c DEV_DRIVER_PULSEAUDIO. - * \param direction Input. Usually either \c PA_STREAM_PLAYBACK or - * \c PA_STREAM_RECORD - * \param stream_description Input. Can be null for no description, but please - * provide a unique one - * - * \return zero for no error, and nonzero result of \c pa_simple_new() for - * error - */ -static int quisk_open_pulseaudio( - struct sound_dev* dev, - pa_stream_direction_t direction, - const char* stream_description -) -{ - pa_sample_spec ss; - //pa_buffer_attr ba; - int error = 0; - - // Device without name: sad. - if( ! dev->name[0] ) - return 0; - - // Construct sample specification - ss.format = PA_SAMPLE_FLOAT32LE; - ss.rate = dev->sample_rate; - ss.channels = dev->num_channels; - - // Construct buffer attributes. Letting PulseAudio do it's thing seems to be - // the best thing to do here. - //if( direction == PA_STREAM_PLAYBACK ) - // ba.maxlength = (sizeof(float)*dev->num_channels) * dev->sample_rate * quisk_sound_state.latency_millisecs / 1e3; - - pa_simple* s; - s = pa_simple_new( - NULL, // Default server - "Quisk", // Application name - direction, - NULL, // Default device - stream_description, - &ss, // Sample format - NULL, // Default channel map - //&ba, // Buffering attributes - NULL, - &error - ); - - if( error ) - { - fprintf( - stderr, - __FILE__": quisk_open_pulseaudio_capture() failed %s\n", - pa_strerror(error) - ); - - dev->handle = NULL; - dev->driver = DEV_DRIVER_NONE; - - return error; - } - - dev->handle = (void*)(s); - dev->driver = DEV_DRIVER_PULSEAUDIO; - return 0; + +//This is a function to sort the device list into local and remote lists. +void sort_devices(struct sound_dev **plist, struct sound_dev **pLocal, struct sound_dev **pRemote) { + + while(*plist) { + struct sound_dev *dev = *plist++; + + // Not a PulseAudio device + if( dev->driver != DEV_DRIVER_PULSEAUDIO ) + continue; + + // Device without name: sad. + if( !dev->name[0] ) + continue; + + // This is a remote device + if(dev->server[0]) { + int i; + for(i=0;i < PA_LIST_SIZE;i++) { + if (!(*(pRemote+i))) { + *(pRemote+i) = dev; + break; + } + } + } + + // This is a local device + else { + int i; + for(i=0;i < PA_LIST_SIZE; i++) { + if (!(*(pLocal+i))) { + *(pLocal+i) = dev; + break; + } + } + } + } } + /*! * \brief Search for and open PulseAudio devices. * @@ -258,114 +730,283 @@ * If a device has its \c sound_dev.driver field set to * \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording. */ -void quisk_start_sound_pulseaudio( - struct sound_dev** pCapture, - struct sound_dev** pPlayback -) -{ - struct sound_dev* dev; - - // Open all capture devices - while(1) - { - dev = *pCapture++; - // Done if null - if( !dev ) - break; - // Continue if it's not our device - else if( dev->driver != DEV_DRIVER_PULSEAUDIO ) - continue; - - if( quisk_open_pulseaudio(dev, PA_STREAM_RECORD, dev->stream_description) ) - return; - } - - // Open all playback devices - while(1) - { - dev = *pPlayback++; - // Done if null - if( !dev ) - break; - // Continue if it's not our device - else if( dev->driver != DEV_DRIVER_PULSEAUDIO ) - continue; - - if( quisk_open_pulseaudio(dev, PA_STREAM_PLAYBACK, dev->stream_description) ) - return; - } + +//sound_dev ** pointer(address) for list of addresses +//sound_dev * fpointer(address) for list of addresses + +void quisk_start_sound_pulseaudio(struct sound_dev **pCapture, struct sound_dev **pPlayback) { + int num_pa_devices = 0; + int i; + + sort_devices(pCapture, LocalPulseDevices, RemotePulseDevices); + sort_devices(pPlayback, LocalPulseDevices, RemotePulseDevices); + + // Create a mainloop API + pa_ml = pa_threaded_mainloop_new(); + pa_mlapi = pa_threaded_mainloop_get_api(pa_ml); + + assert(pa_signal_init(pa_mlapi) == 0); + + if (pa_threaded_mainloop_start(pa_ml) < 0) { + printf("pa_mainloop_run() failed."); + exit(1); + } + else + printf("Pulseaudio threaded mainloop started\n"); + + pa_threaded_mainloop_lock(pa_ml); + + if (RemotePulseDevices[0]) { //we've got at least 1 remote device + pa_IQ_ctx = pa_context_new(pa_mlapi, "Quisk-remote"); + if (pa_context_connect(pa_IQ_ctx, quisk_sound_state.IQ_server, 0, NULL) < 0) + printf("Failed to connect to remote Pulseaudio server\n"); + pa_context_set_state_callback(pa_IQ_ctx, state_cb, RemotePulseDevices); //send a list of remote devices to open + } + + if (LocalPulseDevices[0]) { //we've got at least 1 local device + pa_ctx = pa_context_new(pa_mlapi, "Quisk-local"); + if (pa_context_connect(pa_ctx, NULL, 0, NULL) < 0) + printf("Failed to connect to local Pulseaudio server\n"); + pa_context_set_state_callback(pa_ctx, state_cb, LocalPulseDevices); + } + + if (!RemotePulseDevices[0] && !LocalPulseDevices[0]) { //no pulseaudio devices to open! + if (verbose) + printf("No pulseaudio devices to open!\n"); + pa_threaded_mainloop_unlock(pa_ml); + pa_threaded_mainloop_stop(pa_ml); + pa_threaded_mainloop_free(pa_ml); + pa_ml=NULL; + return; + } + + pa_threaded_mainloop_unlock(pa_ml); + + for(i=0; LocalPulseDevices[i]; i++) { + num_pa_devices++; + } + + for(i=0; RemotePulseDevices[i]; i++) { + num_pa_devices++; + } + + if (verbose) + printf("Waiting for %d streams to start\n", num_pa_devices); + + while (streams_ready < num_pa_devices); // wait for all the devices to open + + if (verbose) + printf("All streams started\n"); + + if (pa_IQ_ctx && verbose == 2) { + if (!(time_event_IQ = pa_context_rttime_new(pa_IQ_ctx, pa_rtclock_now() + TIME_EVENT_USEC, + time_event_callback, RemotePulseDevices))) + printf("remote pa_context_rttime_new() failed."); + } + + if (pa_ctx && verbose == 2) { + if (!(time_event_local = pa_context_rttime_new(pa_ctx, pa_rtclock_now() + TIME_EVENT_USEC, + time_event_callback, LocalPulseDevices))) { + printf("local pa_context_rttime_new() failed."); + exit(1); + } + } } -/*! - * \brief Search for and close PulseAudio devices. - * - * \param pCapture Input/Output. Array of capture devices to search through. - * If a device has its \c sound_dev.driver field set to - * \c DEV_DRIVER_PULSEAUDIO, it will be closed. - * \param pPlayback Input/Output. Array of capture devices to search through. - * If a device has its \c sound_dev.driver field set to - * \c DEV_DRIVER_PULSEAUDIO, it will be closed. - */ -void quisk_close_sound_pulseaudio( - struct sound_dev** pCapture, - struct sound_dev** pPlayback -) -{ - struct sound_dev* dev; - int ret; - int error; - - while(1) - { - dev = *pCapture++; - - // Done if null - if( !dev ) - break; - // Continue if it's not our device - else if( dev->driver != DEV_DRIVER_PULSEAUDIO ) - continue; - - ret = pa_simple_drain( (pa_simple*)(dev->handle), &error ); - if( ret < 0 ) - { - fprintf( - stderr, - __FILE__": quisk_close_sound_pulseaudio() failed %s\n", - pa_strerror(error) - ); - } - - pa_simple_free( (pa_simple*)(dev->handle) ); - dev->handle = NULL; - dev->driver = DEV_DRIVER_NONE; - } - - while(1) - { - dev = *pPlayback++; - if( !dev ) - break; - - // Done if null - if( !dev ) - break; - // Continue if it's not our device - else if( dev->driver != DEV_DRIVER_PULSEAUDIO ) - continue; - - ret = pa_simple_flush( (pa_simple*)(dev->handle), &error ); - if( ret < 0 ) - { - fprintf( - stderr, - __FILE__": quisk_close_sound_pulseaudio() failed %s\n", - pa_strerror(error) - ); - } - - pa_simple_free( (pa_simple*)(dev->handle) ); - dev->handle = NULL; - dev->driver = DEV_DRIVER_NONE; - } + +// Close all streams/contexts/loops and return +void quisk_close_sound_pulseaudio() { + int i = 0; + + if (verbose) + printf("Closing Pulseaudio interfaces \n "); + + while (OpenPulseDevices[i]) { + pa_stream_disconnect(OpenPulseDevices[i]); + pa_stream_unref(OpenPulseDevices[i]); + i++; + } + + if (time_event_local) { + assert(pa_mlapi); + pa_mlapi->time_free(time_event_local); + } + + if (time_event_IQ) { + assert(pa_mlapi); + pa_mlapi->time_free(time_event_IQ); + } + + if (verbose) + printf("Waiting for %d streams to disconnect\n", streams_ready); + + while(streams_ready > 0); + + if (pa_IQ_ctx) { + pa_context_disconnect(pa_IQ_ctx); + pa_context_unref(pa_IQ_ctx); + } + + if (pa_ctx) { + pa_context_disconnect(pa_ctx); + pa_context_unref(pa_ctx); + } + + if (pa_ml) { + pa_threaded_mainloop_stop(pa_ml); + pa_threaded_mainloop_free(pa_ml); + } +} + + + +// Additional bugs added by N2ADR below this point. +// Code for quisk_pa_sound_devices is based on code by Igor Brezac and Eric Connell, and Jan Newmarch. +// This should only be called when Quisk first starts, but names changed to protect the other mainloop. + +// This callback gets called when our context changes state. We really only +// care about when it's ready or if it has failed. +static void pa_names_state_cb(pa_context *c, void *userdata) { + pa_context_state_t ctx_state; + int *main_state = userdata; + + ctx_state = pa_context_get_state(c); + switch (ctx_state) { + // There are just here for reference + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *main_state = 9; + break; + case PA_CONTEXT_READY: + *main_state = 1; + break; + } +} + +static void source_sink(const char * name, const char * descr, pa_proplist * props, PyObject * pylist) { + const char * value; + char buf300[300]; + PyObject * pytup; + + pytup = PyTuple_New(3); + PyList_Append(pylist, pytup); + PyTuple_SET_ITEM(pytup, 0, PyString_FromString(name)); + PyTuple_SET_ITEM(pytup, 1, PyString_FromString(descr)); + value = pa_proplist_gets(props, "device.api"); + + if (value && ! strcmp(value, "alsa")) { + snprintf(buf300, 300, "%s %s (hw:%s,%s)", pa_proplist_gets(props, "alsa.card_name"), pa_proplist_gets(props, "alsa.name"), + pa_proplist_gets(props, "alsa.card"), pa_proplist_gets(props, "alsa.device")); + + PyTuple_SET_ITEM(pytup, 2, PyString_FromString(buf300)); + } + else { + PyTuple_SET_ITEM(pytup, 2, PyString_FromString("")); + } +} + + + + +// pa_mainloop will call this function when it's ready to tell us about a sink. +static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { + if (eol > 0) // If eol is set to a positive number, you're at the end of the list + return; + source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); +} + +static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { + if (eol > 0) + return; + source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); +} + +PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args) +{ // Return a list of PulseAudio device names [pycapt, pyplay] + PyObject * pylist, * pycapt, * pyplay; + pa_mainloop *pa_names_ml; + pa_mainloop_api *pa_names_mlapi; + pa_operation *pa_op=NULL; + pa_context *pa_names_ctx; + int state = 0; + + if (!PyArg_ParseTuple (args, "")) + return NULL; + // Each pycapt and pyplay is (dev name, description, alsa name) + pylist = PyList_New(0); // list [pycapt, pyplay] + pycapt = PyList_New(0); // list of capture devices + pyplay = PyList_New(0); // list of play devices + PyList_Append(pylist, pycapt); + PyList_Append(pylist, pyplay); + + //printf("Starting name loop\n"); + + // Create a mainloop API and connection to the default server + pa_names_ml = pa_mainloop_new(); + pa_names_mlapi = pa_mainloop_get_api(pa_names_ml); + pa_names_ctx = pa_context_new(pa_names_mlapi, "DeviceNames"); + + // This function connects to the pulse server + if (pa_context_connect(pa_names_ctx, NULL, 0, NULL) < 0) { + if (verbose) + printf("No local daemon to connect to for show_pulse_audio_devices option\n"); + return pylist; + } + + // This function defines a callback so the server will tell us it's state. + pa_context_set_state_callback(pa_names_ctx, pa_names_state_cb, &state); + + // Now we'll enter into an infinite loop until we get the data we receive or if there's an error + while (state < 10) { + switch (state) { + case 0: // We can't do anything until PA is ready + pa_mainloop_iterate(pa_names_ml, 1, NULL); + break; + case 1: + // This sends an operation to the server. pa_sinklist_info is + // our callback function and a pointer to our devicelist will + // be passed to the callback. + pa_op = pa_context_get_sink_info_list(pa_names_ctx, pa_sinklist_cb, pyplay); + // Update state for next iteration through the loop + state++; + pa_mainloop_iterate(pa_names_ml, 1, NULL); + break; + case 2: + // Now we wait for our operation to complete. When it's + // complete our pa_output_devicelist is filled out, and we move + // along to the next state + if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { + pa_operation_unref(pa_op); + // Now we perform another operation to get the source + // (input device) list just like before. + pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt); + // Update the state so we know what to do next + state++; + } + pa_mainloop_iterate(pa_names_ml, 1, NULL); + break; + case 3: + if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { + pa_operation_unref(pa_op); + state = 9; + } + else + pa_mainloop_iterate(pa_names_ml, 1, NULL); + break; + case 9: // Now we're done, clean up and disconnect and return + pa_context_disconnect(pa_names_ctx); + pa_context_unref(pa_names_ctx); + pa_mainloop_free(pa_names_ml); + state = 99; + break; + } + } + //printf("Finished with name loop\n"); + return pylist; } diff -Nru quisk-3.6.18/usb/backend/__init__.py quisk-3.7.6/usb/backend/__init__.py --- quisk-3.6.18/usb/backend/__init__.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/backend/__init__.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -33,7 +33,7 @@ IBackend - backend interface. Backends are Python objects which implement the IBackend interface. -The easiest way to do so is inherinting from IBackend. +The easiest way to do so is inherinting from IBackend. PyUSB already provides backends for libusb versions 0.1 and 1.0, and OpenUSB library. Backends modules included with PyUSB are required to @@ -87,10 +87,11 @@ necessary. As Python is a dynamic typed language, you are not obligated to inherit from - IBackend: everything that bahaves like an IBackend is an IBackend. But you + IBackend: everything that behaves like an IBackend is an IBackend. But you are strongly recommended to do so, inheriting from IBackend provides consistent default behavior. """ + def enumerate_devices(self): r"""This function is required to return an iterable object which yields an implementation defined device identification for each @@ -120,7 +121,7 @@ fields acessible as member variables. They must be convertible (but not required to be equal) to the int type. - The dev parameter is the already described device identification object. + The dev parameter is the device identification object. config is the logical index of the configuration (not the bConfigurationValue field). By "logical index" we mean the relative order of the configurations returned by the peripheral as a result of GET_DESCRIPTOR request. @@ -134,7 +135,7 @@ fields accessible as member variables. They must be convertible (but not required to be equal) to the int type. - The dev parameter is the already described device identification object. + The dev parameter is the device identification object. The intf parameter is the interface logical index (not the bInterfaceNumber field) and alt is the alternate setting logical index (not the bAlternateSetting value). Not every interface has more than one alternate setting. In this case, the alt @@ -151,7 +152,7 @@ not required to be equal) to the int type. The ep parameter is the endpoint logical index (not the bEndpointAddress - field) of the endpoint descriptor desired. intf, alt and config are the same + field) of the endpoint descriptor desired. dev, intf, alt and config are the same values already described in the get_interface_descriptor() method. """ _not_implemented(self.get_endpoint_descriptor) @@ -162,7 +163,7 @@ This method opens the device identified by the dev parameter for communication. This method must be called before calling any communication related method, such as transfer methods. - + It returns a handle identifying the communication instance. This handle must be passed to the communication methods. """ @@ -186,12 +187,23 @@ """ _not_implemented(self.set_configuration) + def get_configuration(self, dev_handle): + r"""Get the current active device configuration. + + This method returns the bConfigurationValue of the currently + active configuration. Depending on the backend and the OS, + either a cached value may be returned or a control request may + be issued. The dev_handle parameter is the value returned by + the open_device method. + """ + _not_implemented(self.get_configuration) + def set_interface_altsetting(self, dev_handle, intf, altsetting): r"""Set the interface alternate setting. This method should only be called when the interface has more than one alternate setting. The dev_handle is the value returned by the - open_device() method. intf and altsetting are respectivelly the + open_device() method. intf and altsetting are respectivelly the bInterfaceNumber and bAlternateSetting fields of the related interface. """ _not_implemented(self.set_interface_altsetting) @@ -232,17 +244,18 @@ """ _not_implemented(self.bulk_write) - def bulk_read(self, dev_handle, ep, intf, size, timeout): + def bulk_read(self, dev_handle, ep, intf, buff, timeout): r"""Perform a bulk read. dev_handle is the value returned by the open_device() method. The ep parameter is the bEndpointAddress field whose endpoint the data will be received from. intf is the bInterfaceNumber field - of the interface containing the endpoint. The size parameter - is the number of bytes to be read. The timeout parameter specifies - a time limit to the operation in miliseconds. + of the interface containing the endpoint. The buff parameter + is the buffer to receive the data read, the length of the buffer + tells how many bytes should be read. The timeout parameter + specifies a time limit to the operation in miliseconds. - The method returns an array.array object containing the data read. + The method returns the number of bytes actually read. """ _not_implemented(self.bulk_read) @@ -267,11 +280,12 @@ dev_handle is the value returned by the open_device() method. The ep parameter is the bEndpointAddress field whose endpoint the data will be received from. intf is the bInterfaceNumber field - of the interface containing the endpoint. The size parameter - is the number of bytes to be read. The timeout parameter specifies - a time limit to the operation in miliseconds. + of the interface containing the endpoint. The buff parameter + is the buffer to receive the data read, the length of the buffer + tells how many bytes should be read. The timeout parameter + specifies a time limit to the operation in miliseconds. - The method returns an array.array object containing the data read. + The method returns the number of bytes actually read. """ _not_implemented(self.intr_read) @@ -282,7 +296,7 @@ The ep parameter is the bEndpointAddress field whose endpoint the data will be sent to. intf is the bInterfaceNumber field of the interface containing the endpoint. The data parameter - is the data to be sent.It must be an instance of the array.array + is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation in miliseconds. @@ -296,11 +310,12 @@ dev_handle is the value returned by the open_device() method. The ep parameter is the bEndpointAddress field whose endpoint the data will be received from. intf is the bInterfaceNumber field - of the interface containing the endpoint. The size parameter - is the number of bytes to be read. The timeout parameter specifies + of the interface containing the endpoint. The buff parameter + is buffer to receive the data read, the length of the buffer tells + how many bytes should be read. The timeout parameter specifies a time limit to the operation in miliseconds. - The method returns an array.array object containing the data read. + The method returns the number of bytes actually read. """ _not_implemented(self.iso_read) @@ -310,7 +325,7 @@ bRequest, wValue, wIndex, - data_or_wLength, + data, timeout): r"""Perform a control transfer on the endpoint 0. @@ -319,18 +334,22 @@ dev_handle is the value returned by the open_device() method. bmRequestType, bRequest, wValue and wIndex are the same fields - of the setup packet. data_or_wLength is either the payload to be sent - to the device, if any, as an array.array object (None there is no - payload) for OUT requests in the data stage or the wLength field - specifying the number of bytes to read for IN requests in the data - stage. The timeout parameter specifies a time limit to the operation - in miliseconds. + of the setup packet. data is an array object, for OUT requests + it contains the bytes to transmit in the data stage and for + IN requests it is the buffer to hold the data read. The number + of bytes requested to transmit or receive is equal to the length + of the array times the data.itemsize field. The timeout parameter + specifies a time limit to the operation in miliseconds. Return the number of bytes written (for OUT transfers) or the data read (for IN transfers), as an array.array object. """ _not_implemented(self.ctrl_transfer) + def clear_halt(self, dev_handle, ep): + r"""Clear the halt/stall condition for the endpoint.""" + _not_implemented(self.clear_halt) + def reset_device(self, dev_handle): r"""Reset the device.""" _not_implemented(self.reset_device) @@ -345,7 +364,7 @@ def detach_kernel_driver(self, dev_handle, intf): r"""Detach a kernel driver from an interface. - + If successful, you will then be able to claim the interface and perform I/O. """ Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/backend/__init__.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/backend/__init__.pyc differ Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/backend/libusb01.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/backend/libusb01.pyc differ diff -Nru quisk-3.6.18/usb/backend/libusb0.py quisk-3.7.6/usb/backend/libusb0.py --- quisk-3.6.18/usb/backend/libusb0.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/usb/backend/libusb0.py 2015-08-13 17:45:27.000000000 +0000 @@ -0,0 +1,580 @@ +# Copyright (C) 2009-2014 Wander Lairson Costa +# +# The following terms apply to all files associated +# with the software unless explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. + +from ctypes import * +import os +import usb.backend +import usb.util +import sys +from usb.core import USBError +from usb._debug import methodtrace +import usb._interop as _interop +import logging +import usb.libloader + +__author__ = 'Wander Lairson Costa' + +__all__ = ['get_backend'] + +_logger = logging.getLogger('usb.backend.libusb0') + +# usb.h + +_PC_PATH_MAX = 4 + +if sys.platform.find('bsd') != -1 or sys.platform.find('mac') != -1 or \ + sys.platform.find('darwin') != -1: + _PATH_MAX = 1024 +elif sys.platform == 'win32' or sys.platform == 'cygwin': + _PATH_MAX = 511 +else: + _PATH_MAX = os.pathconf('.', _PC_PATH_MAX) + +# libusb-win32 makes all structures packed, while +# default libusb only does for some structures +# _PackPolicy defines the structure packing according +# to the platform. +class _PackPolicy(object): + pass + +if sys.platform == 'win32' or sys.platform == 'cygwin': + _PackPolicy._pack_ = 1 + +# Data structures + +class _usb_descriptor_header(Structure): + _pack_ = 1 + _fields_ = [('blength', c_uint8), + ('bDescriptorType', c_uint8)] + +class _usb_string_descriptor(Structure): + _pack_ = 1 + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('wData', c_uint16)] + +class _usb_endpoint_descriptor(Structure, _PackPolicy): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bEndpointAddress', c_uint8), + ('bmAttributes', c_uint8), + ('wMaxPacketSize', c_uint16), + ('bInterval', c_uint8), + ('bRefresh', c_uint8), + ('bSynchAddress', c_uint8), + ('extra', POINTER(c_uint8)), + ('extralen', c_int)] + +class _usb_interface_descriptor(Structure, _PackPolicy): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bInterfaceNumber', c_uint8), + ('bAlternateSetting', c_uint8), + ('bNumEndpoints', c_uint8), + ('bInterfaceClass', c_uint8), + ('bInterfaceSubClass', c_uint8), + ('bInterfaceProtocol', c_uint8), + ('iInterface', c_uint8), + ('endpoint', POINTER(_usb_endpoint_descriptor)), + ('extra', POINTER(c_uint8)), + ('extralen', c_int)] + +class _usb_interface(Structure, _PackPolicy): + _fields_ = [('altsetting', POINTER(_usb_interface_descriptor)), + ('num_altsetting', c_int)] + +class _usb_config_descriptor(Structure, _PackPolicy): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('wTotalLength', c_uint16), + ('bNumInterfaces', c_uint8), + ('bConfigurationValue', c_uint8), + ('iConfiguration', c_uint8), + ('bmAttributes', c_uint8), + ('bMaxPower', c_uint8), + ('interface', POINTER(_usb_interface)), + ('extra', POINTER(c_uint8)), + ('extralen', c_int)] + +class _usb_device_descriptor(Structure, _PackPolicy): + _pack_ = 1 + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bcdUSB', c_uint16), + ('bDeviceClass', c_uint8), + ('bDeviceSubClass', c_uint8), + ('bDeviceProtocol', c_uint8), + ('bMaxPacketSize0', c_uint8), + ('idVendor', c_uint16), + ('idProduct', c_uint16), + ('bcdDevice', c_uint16), + ('iManufacturer', c_uint8), + ('iProduct', c_uint8), + ('iSerialNumber', c_uint8), + ('bNumConfigurations', c_uint8)] + +class _usb_device(Structure, _PackPolicy): + pass + +class _usb_bus(Structure, _PackPolicy): + pass + +_usb_device._fields_ = [('next', POINTER(_usb_device)), + ('prev', POINTER(_usb_device)), + ('filename', c_int8 * (_PATH_MAX + 1)), + ('bus', POINTER(_usb_bus)), + ('descriptor', _usb_device_descriptor), + ('config', POINTER(_usb_config_descriptor)), + ('dev', c_void_p), + ('devnum', c_uint8), + ('num_children', c_ubyte), + ('children', POINTER(POINTER(_usb_device)))] + +_usb_bus._fields_ = [('next', POINTER(_usb_bus)), + ('prev', POINTER(_usb_bus)), + ('dirname', c_char * (_PATH_MAX + 1)), + ('devices', POINTER(_usb_device)), + ('location', c_uint32), + ('root_dev', POINTER(_usb_device))] + +_usb_dev_handle = c_void_p + +class _DeviceDescriptor: + def __init__(self, dev): + desc = dev.descriptor + self.bLength = desc.bLength + self.bDescriptorType = desc.bDescriptorType + self.bcdUSB = desc.bcdUSB + self.bDeviceClass = desc.bDeviceClass + self.bDeviceSubClass = desc.bDeviceSubClass + self.bDeviceProtocol = desc.bDeviceProtocol + self.bMaxPacketSize0 = desc.bMaxPacketSize0 + self.idVendor = desc.idVendor + self.idProduct = desc.idProduct + self.bcdDevice = desc.bcdDevice + self.iManufacturer = desc.iManufacturer + self.iProduct = desc.iProduct + self.iSerialNumber = desc.iSerialNumber + self.bNumConfigurations = desc.bNumConfigurations + self.address = dev.devnum + self.bus = dev.bus[0].location + + self.port_number = None +_lib = None + +def _load_library(find_library=None): + return usb.libloader.load_locate_library( + ('usb-0.1', 'usb', 'libusb0'), + 'cygusb0.dll', 'Libusb 0', + find_library=find_library + ) + +def _setup_prototypes(lib): + # usb_dev_handle *usb_open(struct usb_device *dev); + lib.usb_open.argtypes = [POINTER(_usb_device)] + lib.usb_open.restype = _usb_dev_handle + + # int usb_close(usb_dev_handle *dev); + lib.usb_close.argtypes = [_usb_dev_handle] + + # int usb_get_string(usb_dev_handle *dev, + # int index, + # int langid, + # char *buf, + # size_t buflen); + lib.usb_get_string.argtypes = [ + _usb_dev_handle, + c_int, + c_int, + c_char_p, + c_size_t + ] + + # int usb_get_string_simple(usb_dev_handle *dev, + # int index, + # char *buf, + # size_t buflen); + lib.usb_get_string_simple.argtypes = [ + _usb_dev_handle, + c_int, + c_char_p, + c_size_t + ] + + # int usb_get_descriptor_by_endpoint(usb_dev_handle *udev, + # int ep, + # unsigned char type, + # unsigned char index, + # void *buf, + # int size); + lib.usb_get_descriptor_by_endpoint.argtypes = [ + _usb_dev_handle, + c_int, + c_ubyte, + c_ubyte, + c_void_p, + c_int + ] + + # int usb_get_descriptor(usb_dev_handle *udev, + # unsigned char type, + # unsigned char index, + # void *buf, + # int size); + lib.usb_get_descriptor.argtypes = [ + _usb_dev_handle, + c_ubyte, + c_ubyte, + c_void_p, + c_int + ] + + # int usb_bulk_write(usb_dev_handle *dev, + # int ep, + # const char *bytes, + # int size, + # int timeout); + lib.usb_bulk_write.argtypes = [ + _usb_dev_handle, + c_int, + c_char_p, + c_int, + c_int + ] + + # int usb_bulk_read(usb_dev_handle *dev, + # int ep, + # char *bytes, + # int size, + # int timeout); + lib.usb_bulk_read.argtypes = [ + _usb_dev_handle, + c_int, + c_char_p, + c_int, + c_int + ] + + # int usb_interrupt_write(usb_dev_handle *dev, + # int ep, + # const char *bytes, + # int size, + # int timeout); + lib.usb_interrupt_write.argtypes = [ + _usb_dev_handle, + c_int, + c_char_p, + c_int, + c_int + ] + + # int usb_interrupt_read(usb_dev_handle *dev, + # int ep, + # char *bytes, + # int size, + # int timeout); + lib.usb_interrupt_read.argtypes = [ + _usb_dev_handle, + c_int, + c_char_p, + c_int, + c_int + ] + + # int usb_control_msg(usb_dev_handle *dev, + # int requesttype, + # int request, + # int value, + # int index, + # char *bytes, + # int size, + # int timeout); + lib.usb_control_msg.argtypes = [ + _usb_dev_handle, + c_int, + c_int, + c_int, + c_int, + c_char_p, + c_int, + c_int + ] + + # int usb_set_configuration(usb_dev_handle *dev, int configuration); + lib.usb_set_configuration.argtypes = [_usb_dev_handle, c_int] + + # int usb_claim_interface(usb_dev_handle *dev, int interface); + lib.usb_claim_interface.argtypes = [_usb_dev_handle, c_int] + + # int usb_release_interface(usb_dev_handle *dev, int interface); + lib.usb_release_interface.argtypes = [_usb_dev_handle, c_int] + + # int usb_set_altinterface(usb_dev_handle *dev, int alternate); + lib.usb_set_altinterface.argtypes = [_usb_dev_handle, c_int] + + # int usb_resetep(usb_dev_handle *dev, unsigned int ep); + lib.usb_resetep.argtypes = [_usb_dev_handle, c_int] + + # int usb_clear_halt(usb_dev_handle *dev, unsigned int ep); + lib.usb_clear_halt.argtypes = [_usb_dev_handle, c_int] + + # int usb_reset(usb_dev_handle *dev); + lib.usb_reset.argtypes = [_usb_dev_handle] + + # char *usb_strerror(void); + lib.usb_strerror.argtypes = [] + lib.usb_strerror.restype = c_char_p + + # void usb_set_debug(int level); + lib.usb_set_debug.argtypes = [c_int] + + # struct usb_device *usb_device(usb_dev_handle *dev); + lib.usb_device.argtypes = [_usb_dev_handle] + lib.usb_device.restype = POINTER(_usb_device) + + # struct usb_bus *usb_get_busses(void); + lib.usb_get_busses.restype = POINTER(_usb_bus) + +def _check(ret): + if ret is None: + errmsg = _lib.usb_strerror() + else: + if hasattr(ret, 'value'): + ret = ret.value + + if ret < 0: + errmsg = _lib.usb_strerror() + # No error means that we need to get the error + # message from the return code + # Thanks to Nicholas Wheeler to point out the problem... + # Also see issue #2860940 + if errmsg.lower() == 'no error': + errmsg = os.strerror(-ret) + else: + return ret + raise USBError(errmsg, ret) + +# implementation of libusb 0.1.x backend +class _LibUSB(usb.backend.IBackend): + @methodtrace(_logger) + def enumerate_devices(self): + _check(_lib.usb_find_busses()) + _check(_lib.usb_find_devices()) + bus = _lib.usb_get_busses() + while bool(bus): + dev = bus[0].devices + while bool(dev): + yield dev[0] + dev = dev[0].next + bus = bus[0].next + + @methodtrace(_logger) + def get_device_descriptor(self, dev): + return _DeviceDescriptor(dev) + + @methodtrace(_logger) + def get_configuration_descriptor(self, dev, config): + if config >= dev.descriptor.bNumConfigurations: + raise IndexError('Invalid configuration index ' + str(config)) + config_desc = dev.config[config] + config_desc.extra_descriptors = config_desc.extra[:config_desc.extralen] + return config_desc + + @methodtrace(_logger) + def get_interface_descriptor(self, dev, intf, alt, config): + cfgdesc = self.get_configuration_descriptor(dev, config) + if intf >= cfgdesc.bNumInterfaces: + raise IndexError('Invalid interface index ' + str(interface)) + interface = cfgdesc.interface[intf] + if alt >= interface.num_altsetting: + raise IndexError('Invalid alternate setting index ' + str(alt)) + intf_desc = interface.altsetting[alt] + intf_desc.extra_descriptors = intf_desc.extra[:intf_desc.extralen] + return intf_desc + + @methodtrace(_logger) + def get_endpoint_descriptor(self, dev, ep, intf, alt, config): + interface = self.get_interface_descriptor(dev, intf, alt, config) + if ep >= interface.bNumEndpoints: + raise IndexError('Invalid endpoint index ' + str(ep)) + ep_desc = interface.endpoint[ep] + ep_desc.extra_descriptors = ep_desc.extra[:ep_desc.extralen] + return ep_desc + + @methodtrace(_logger) + def open_device(self, dev): + return _check(_lib.usb_open(dev)) + + @methodtrace(_logger) + def close_device(self, dev_handle): + _check(_lib.usb_close(dev_handle)) + + @methodtrace(_logger) + def set_configuration(self, dev_handle, config_value): + _check(_lib.usb_set_configuration(dev_handle, config_value)) + + @methodtrace(_logger) + def set_interface_altsetting(self, dev_handle, intf, altsetting): + _check(_lib.usb_set_altinterface(dev_handle, altsetting)) + + @methodtrace(_logger) + def get_configuration(self, dev_handle): + bmRequestType = usb.util.build_request_type( + usb.util.CTRL_IN, + usb.util.CTRL_TYPE_STANDARD, + usb.util.CTRL_RECIPIENT_DEVICE + ) + buff = usb.util.create_buffer(1) + ret = self.ctrl_transfer( + dev_handle, + bmRequestType, + 0x08, + 0, + 0, + buff, + 100) + + assert ret == 1 + return buff[0] + + + @methodtrace(_logger) + def claim_interface(self, dev_handle, intf): + _check(_lib.usb_claim_interface(dev_handle, intf)) + + @methodtrace(_logger) + def release_interface(self, dev_handle, intf): + _check(_lib.usb_release_interface(dev_handle, intf)) + + @methodtrace(_logger) + def bulk_write(self, dev_handle, ep, intf, data, timeout): + return self.__write(_lib.usb_bulk_write, + dev_handle, + ep, + intf, + data, timeout) + + @methodtrace(_logger) + def bulk_read(self, dev_handle, ep, intf, buff, timeout): + return self.__read(_lib.usb_bulk_read, + dev_handle, + ep, + intf, + buff, + timeout) + + @methodtrace(_logger) + def intr_write(self, dev_handle, ep, intf, data, timeout): + return self.__write(_lib.usb_interrupt_write, + dev_handle, + ep, + intf, + data, + timeout) + + @methodtrace(_logger) + def intr_read(self, dev_handle, ep, intf, buff, timeout): + return self.__read(_lib.usb_interrupt_read, + dev_handle, + ep, + intf, + buff, + timeout) + + @methodtrace(_logger) + def ctrl_transfer(self, + dev_handle, + bmRequestType, + bRequest, + wValue, + wIndex, + data, + timeout): + address, length = data.buffer_info() + length *= data.itemsize + return _check(_lib.usb_control_msg( + dev_handle, + bmRequestType, + bRequest, + wValue, + wIndex, + cast(address, c_char_p), + length, + timeout + )) + + @methodtrace(_logger) + def clear_halt(self, dev_handle, ep): + _check(_lib.usb_clear_halt(dev_handle, ep)) + + @methodtrace(_logger) + def reset_device(self, dev_handle): + _check(_lib.usb_reset(dev_handle)) + + @methodtrace(_logger) + def detach_kernel_driver(self, dev_handle, intf): + _check(_lib.usb_detach_kernel_driver_np(dev_handle, intf)) + + def __write(self, fn, dev_handle, ep, intf, data, timeout): + address, length = data.buffer_info() + length *= data.itemsize + return int(_check(fn( + dev_handle, + ep, + cast(address, c_char_p), + length, + timeout + ))) + + def __read(self, fn, dev_handle, ep, intf, buff, timeout): + address, length = buff.buffer_info() + length *= buff.itemsize + ret = int(_check(fn( + dev_handle, + ep, + cast(address, c_char_p), + length, + timeout + ))) + return ret + +def get_backend(find_library=None): + global _lib + try: + if _lib is None: + _lib = _load_library(find_library) + _setup_prototypes(_lib) + _lib.usb_init() + return _LibUSB() + except usb.libloader.LibaryException: + # exception already logged (if any) + _logger.error('Error loading libusb 0.1 backend', exc_info=False) + return None + except Exception: + _logger.error('Error loading libusb 0.1 backend', exc_info=True) + return None Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/backend/libusb10.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/backend/libusb10.pyc differ diff -Nru quisk-3.6.18/usb/backend/libusb1.py quisk-3.7.6/usb/backend/libusb1.py --- quisk-3.6.18/usb/backend/libusb1.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/usb/backend/libusb1.py 2015-08-13 17:45:27.000000000 +0000 @@ -0,0 +1,888 @@ +# Copyright (C) 2009-2014 Wander Lairson Costa +# +# The following terms apply to all files associated +# with the software unless explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. + +from ctypes import * +import usb.util +import sys +import logging +from usb._debug import methodtrace +import usb._interop as _interop +import errno +import math +from usb.core import USBError +import usb.libloader + +__author__ = 'Wander Lairson Costa' + +__all__ = [ + 'get_backend', + 'LIBUSB_SUCESS', + 'LIBUSB_ERROR_IO', + 'LIBUSB_ERROR_INVALID_PARAM', + 'LIBUSB_ERROR_ACCESS', + 'LIBUSB_ERROR_NO_DEVICE', + 'LIBUSB_ERROR_NOT_FOUND', + 'LIBUSB_ERROR_BUSY', + 'LIBUSB_ERROR_TIMEOUT', + 'LIBUSB_ERROR_OVERFLOW', + 'LIBUSB_ERROR_PIPE', + 'LIBUSB_ERROR_INTERRUPTED', + 'LIBUSB_ERROR_NO_MEM', + 'LIBUSB_ERROR_NOT_SUPPORTED', + 'LIBUSB_ERROR_OTHER' + 'LIBUSB_TRANSFER_COMPLETED', + 'LIBUSB_TRANSFER_ERROR', + 'LIBUSB_TRANSFER_TIMED_OUT', + 'LIBUSB_TRANSFER_CANCELLED', + 'LIBUSB_TRANSFER_STALL', + 'LIBUSB_TRANSFER_NO_DEVICE', + 'LIBUSB_TRANSFER_OVERFLOW' + ] + +_logger = logging.getLogger('usb.backend.libusb1') + +# libusb.h + +# transfer_type codes +# Control endpoint +_LIBUSB_TRANSFER_TYPE_CONTROL = 0, +# Isochronous endpoint +_LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1 +# Bulk endpoint +_LIBUSB_TRANSFER_TYPE_BULK = 2 +# Interrupt endpoint +_LIBUSB_TRANSFER_TYPE_INTERRUPT = 3 + +# return codes + +LIBUSB_SUCCESS = 0 +LIBUSB_ERROR_IO = -1 +LIBUSB_ERROR_INVALID_PARAM = -2 +LIBUSB_ERROR_ACCESS = -3 +LIBUSB_ERROR_NO_DEVICE = -4 +LIBUSB_ERROR_NOT_FOUND = -5 +LIBUSB_ERROR_BUSY = -6 +LIBUSB_ERROR_TIMEOUT = -7 +LIBUSB_ERROR_OVERFLOW = -8 +LIBUSB_ERROR_PIPE = -9 +LIBUSB_ERROR_INTERRUPTED = -10 +LIBUSB_ERROR_NO_MEM = -11 +LIBUSB_ERROR_NOT_SUPPORTED = -12 +LIBUSB_ERROR_OTHER = -99 + +# map return code to errno values +_libusb_errno = { + 0:None, + LIBUSB_ERROR_IO:errno.__dict__.get('EIO', None), + LIBUSB_ERROR_INVALID_PARAM:errno.__dict__.get('EINVAL', None), + LIBUSB_ERROR_ACCESS:errno.__dict__.get('EACCES', None), + LIBUSB_ERROR_NO_DEVICE:errno.__dict__.get('ENODEV', None), + LIBUSB_ERROR_NOT_FOUND:errno.__dict__.get('ENOENT', None), + LIBUSB_ERROR_BUSY:errno.__dict__.get('EBUSY', None), + LIBUSB_ERROR_TIMEOUT:errno.__dict__.get('ETIMEDOUT', None), + LIBUSB_ERROR_OVERFLOW:errno.__dict__.get('EOVERFLOW', None), + LIBUSB_ERROR_PIPE:errno.__dict__.get('EPIPE', None), + LIBUSB_ERROR_INTERRUPTED:errno.__dict__.get('EINTR', None), + LIBUSB_ERROR_NO_MEM:errno.__dict__.get('ENOMEM', None), + LIBUSB_ERROR_NOT_SUPPORTED:errno.__dict__.get('ENOSYS', None), + LIBUSB_ERROR_OTHER:None +} + +# Transfer status codes: +# Note that this does not indicate +# that the entire amount of requested data was transferred. +LIBUSB_TRANSFER_COMPLETED = 0 +LIBUSB_TRANSFER_ERROR = 1 +LIBUSB_TRANSFER_TIMED_OUT = 2 +LIBUSB_TRANSFER_CANCELLED = 3 +LIBUSB_TRANSFER_STALL = 4 +LIBUSB_TRANSFER_NO_DEVICE = 5 +LIBUSB_TRANSFER_OVERFLOW = 6 + +# map return codes to strings +_str_transfer_error = { + LIBUSB_TRANSFER_COMPLETED:'Success (no error)', + LIBUSB_TRANSFER_ERROR:'Transfer failed', + LIBUSB_TRANSFER_TIMED_OUT:'Transfer timed out', + LIBUSB_TRANSFER_CANCELLED:'Transfer was cancelled', + LIBUSB_TRANSFER_STALL:'For bulk/interrupt endpoints: halt condition '\ + 'detected (endpoint stalled). For control '\ + 'endpoints: control request not supported.', + LIBUSB_TRANSFER_NO_DEVICE:'Device was disconnected', + LIBUSB_TRANSFER_OVERFLOW:'Device sent more data than requested' +} + +# map transfer codes to errno codes +_transfer_errno = { + LIBUSB_TRANSFER_COMPLETED:0, + LIBUSB_TRANSFER_ERROR:errno.__dict__.get('EIO', None), + LIBUSB_TRANSFER_TIMED_OUT:errno.__dict__.get('ETIMEDOUT', None), + LIBUSB_TRANSFER_CANCELLED:errno.__dict__.get('EAGAIN', None), + LIBUSB_TRANSFER_STALL:errno.__dict__.get('EIO', None), + LIBUSB_TRANSFER_NO_DEVICE:errno.__dict__.get('ENODEV', None), + LIBUSB_TRANSFER_OVERFLOW:errno.__dict__.get('EOVERFLOW', None) +} + +# Data structures + +class _libusb_endpoint_descriptor(Structure): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bEndpointAddress', c_uint8), + ('bmAttributes', c_uint8), + ('wMaxPacketSize', c_uint16), + ('bInterval', c_uint8), + ('bRefresh', c_uint8), + ('bSynchAddress', c_uint8), + ('extra', POINTER(c_ubyte)), + ('extra_length', c_int)] + +class _libusb_interface_descriptor(Structure): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bInterfaceNumber', c_uint8), + ('bAlternateSetting', c_uint8), + ('bNumEndpoints', c_uint8), + ('bInterfaceClass', c_uint8), + ('bInterfaceSubClass', c_uint8), + ('bInterfaceProtocol', c_uint8), + ('iInterface', c_uint8), + ('endpoint', POINTER(_libusb_endpoint_descriptor)), + ('extra', POINTER(c_ubyte)), + ('extra_length', c_int)] + +class _libusb_interface(Structure): + _fields_ = [('altsetting', POINTER(_libusb_interface_descriptor)), + ('num_altsetting', c_int)] + +class _libusb_config_descriptor(Structure): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('wTotalLength', c_uint16), + ('bNumInterfaces', c_uint8), + ('bConfigurationValue', c_uint8), + ('iConfiguration', c_uint8), + ('bmAttributes', c_uint8), + ('bMaxPower', c_uint8), + ('interface', POINTER(_libusb_interface)), + ('extra', POINTER(c_ubyte)), + ('extra_length', c_int)] + +class _libusb_device_descriptor(Structure): + _fields_ = [('bLength', c_uint8), + ('bDescriptorType', c_uint8), + ('bcdUSB', c_uint16), + ('bDeviceClass', c_uint8), + ('bDeviceSubClass', c_uint8), + ('bDeviceProtocol', c_uint8), + ('bMaxPacketSize0', c_uint8), + ('idVendor', c_uint16), + ('idProduct', c_uint16), + ('bcdDevice', c_uint16), + ('iManufacturer', c_uint8), + ('iProduct', c_uint8), + ('iSerialNumber', c_uint8), + ('bNumConfigurations', c_uint8)] + + +# Isochronous packet descriptor. +class _libusb_iso_packet_descriptor(Structure): + _fields_ = [('length', c_uint), + ('actual_length', c_uint), + ('status', c_int)] # enum libusb_transfer_status + +_libusb_device_handle = c_void_p + +class _libusb_transfer(Structure): + pass +_libusb_transfer_p = POINTER(_libusb_transfer) + +_libusb_transfer_cb_fn_p = CFUNCTYPE(None, _libusb_transfer_p) + +_libusb_transfer._fields_ = [('dev_handle', _libusb_device_handle), + ('flags', c_uint8), + ('endpoint', c_uint8), + ('type', c_uint8), + ('timeout', c_uint), + ('status', c_int), # enum libusb_transfer_status + ('length', c_int), + ('actual_length', c_int), + ('callback', _libusb_transfer_cb_fn_p), + ('user_data', py_object), + ('buffer', c_void_p), + ('num_iso_packets', c_int), + ('iso_packet_desc', _libusb_iso_packet_descriptor) +] + +def _get_iso_packet_list(transfer): + list_type = _libusb_iso_packet_descriptor * transfer.num_iso_packets + return list_type.from_address(addressof(transfer.iso_packet_desc)) + +_lib = None + +def _load_library(find_library=None): + # Windows backend uses stdcall calling convention + # + # On FreeBSD 8/9, libusb 1.0 and libusb 0.1 are in the same shared + # object libusb.so, so if we found libusb library name, we must assure + # it is 1.0 version. We just try to get some symbol from 1.0 version + if sys.platform == 'win32': + win_cls = WinDLL + else: + win_cls = None + + return usb.libloader.load_locate_library( + ('usb-1.0', 'libusb-1.0', 'usb'), + 'cygusb-1.0.dll', 'Libusb 1', + win_cls=win_cls, + find_library=find_library, check_symbols=('libusb_init',)) + +def _setup_prototypes(lib): + # void libusb_set_debug (libusb_context *ctx, int level) + lib.libusb_set_debug.argtypes = [c_void_p, c_int] + + # int libusb_init (libusb_context **context) + lib.libusb_init.argtypes = [POINTER(c_void_p)] + + # void libusb_exit (struct libusb_context *ctx) + lib.libusb_exit.argtypes = [c_void_p] + + # ssize_t libusb_get_device_list (libusb_context *ctx, + # libusb_device ***list) + lib.libusb_get_device_list.argtypes = [ + c_void_p, + POINTER(POINTER(c_void_p)) + ] + + # void libusb_free_device_list (libusb_device **list, + # int unref_devices) + lib.libusb_free_device_list.argtypes = [ + POINTER(c_void_p), + c_int + ] + + # libusb_device *libusb_ref_device (libusb_device *dev) + lib.libusb_ref_device.argtypes = [c_void_p] + lib.libusb_ref_device.restype = c_void_p + + # void libusb_unref_device(libusb_device *dev) + lib.libusb_unref_device.argtypes = [c_void_p] + + # int libusb_open(libusb_device *dev, libusb_device_handle **handle) + lib.libusb_open.argtypes = [c_void_p, POINTER(_libusb_device_handle)] + + # void libusb_close(libusb_device_handle *dev_handle) + lib.libusb_close.argtypes = [_libusb_device_handle] + + # int libusb_set_configuration(libusb_device_handle *dev, + # int configuration) + lib.libusb_set_configuration.argtypes = [_libusb_device_handle, c_int] + + # int libusb_get_configuration(libusb_device_handle *dev, + # int *config) + lib.libusb_get_configuration.argtypes = [_libusb_device_handle, POINTER(c_int)] + + # int libusb_claim_interface(libusb_device_handle *dev, + # int interface_number) + lib.libusb_claim_interface.argtypes = [_libusb_device_handle, c_int] + + # int libusb_release_interface(libusb_device_handle *dev, + # int interface_number) + lib.libusb_release_interface.argtypes = [_libusb_device_handle, c_int] + + # int libusb_set_interface_alt_setting(libusb_device_handle *dev, + # int interface_number, + # int alternate_setting) + lib.libusb_set_interface_alt_setting.argtypes = [ + _libusb_device_handle, + c_int, + c_int + ] + + # int libusb_reset_device (libusb_device_handle *dev) + lib.libusb_reset_device.argtypes = [_libusb_device_handle] + + # int libusb_kernel_driver_active(libusb_device_handle *dev, + # int interface) + lib.libusb_kernel_driver_active.argtypes = [ + _libusb_device_handle, + c_int + ] + + # int libusb_detach_kernel_driver(libusb_device_handle *dev, + # int interface) + lib.libusb_detach_kernel_driver.argtypes = [ + _libusb_device_handle, + c_int + ] + + # int libusb_attach_kernel_driver(libusb_device_handle *dev, + # int interface) + lib.libusb_attach_kernel_driver.argtypes = [ + _libusb_device_handle, + c_int + ] + + # int libusb_get_device_descriptor( + # libusb_device *dev, + # struct libusb_device_descriptor *desc + # ) + lib.libusb_get_device_descriptor.argtypes = [ + c_void_p, + POINTER(_libusb_device_descriptor) + ] + + # int libusb_get_config_descriptor( + # libusb_device *dev, + # uint8_t config_index, + # struct libusb_config_descriptor **config + # ) + lib.libusb_get_config_descriptor.argtypes = [ + c_void_p, + c_uint8, + POINTER(POINTER(_libusb_config_descriptor)) + ] + + # void libusb_free_config_descriptor( + # struct libusb_config_descriptor *config + # ) + lib.libusb_free_config_descriptor.argtypes = [ + POINTER(_libusb_config_descriptor) + ] + + # int libusb_get_string_descriptor_ascii(libusb_device_handle *dev, + # uint8_t desc_index, + # unsigned char *data, + # int length) + lib.libusb_get_string_descriptor_ascii.argtypes = [ + _libusb_device_handle, + c_uint8, + POINTER(c_ubyte), + c_int + ] + + # int libusb_control_transfer(libusb_device_handle *dev_handle, + # uint8_t bmRequestType, + # uint8_t bRequest, + # uint16_t wValue, + # uint16_t wIndex, + # unsigned char *data, + # uint16_t wLength, + # unsigned int timeout) + lib.libusb_control_transfer.argtypes = [ + _libusb_device_handle, + c_uint8, + c_uint8, + c_uint16, + c_uint16, + POINTER(c_ubyte), + c_uint16, + c_uint + ] + + #int libusb_bulk_transfer( + # struct libusb_device_handle *dev_handle, + # unsigned char endpoint, + # unsigned char *data, + # int length, + # int *transferred, + # unsigned int timeout + # ) + lib.libusb_bulk_transfer.argtypes = [ + _libusb_device_handle, + c_ubyte, + POINTER(c_ubyte), + c_int, + POINTER(c_int), + c_uint + ] + + # int libusb_interrupt_transfer( + # libusb_device_handle *dev_handle, + # unsigned char endpoint, + # unsigned char *data, + # int length, + # int *actual_length, + # unsigned int timeout + # ); + lib.libusb_interrupt_transfer.argtypes = [ + _libusb_device_handle, + c_ubyte, + POINTER(c_ubyte), + c_int, + POINTER(c_int), + c_uint + ] + + # libusb_transfer* libusb_alloc_transfer(int iso_packets); + lib.libusb_alloc_transfer.argtypes = [c_int] + lib.libusb_alloc_transfer.restype = POINTER(_libusb_transfer) + + # void libusb_free_transfer(struct libusb_transfer *transfer) + lib.libusb_free_transfer.argtypes = [POINTER(_libusb_transfer)] + + # int libusb_submit_transfer(struct libusb_transfer *transfer); + lib.libusb_submit_transfer.argtypes = [POINTER(_libusb_transfer)] + + # const char *libusb_strerror(enum libusb_error errcode) + lib.libusb_strerror.argtypes = [c_uint] + lib.libusb_strerror.restype = c_char_p + + # int libusb_clear_halt(libusb_device_handle *dev, unsigned char endpoint) + lib.libusb_clear_halt.argtypes = [_libusb_device_handle, c_ubyte] + + # void libusb_set_iso_packet_lengths( + # libusb_transfer* transfer, + # unsigned int length + # ); + def libusb_set_iso_packet_lengths(transfer_p, length): + r"""This function is inline in the libusb.h file, so we must implement + it. + + lib.libusb_set_iso_packet_lengths.argtypes = [ + POINTER(_libusb_transfer), + c_int + ] + """ + transfer = transfer_p.contents + for iso_packet_desc in _get_iso_packet_list(transfer): + iso_packet_desc.length = length + lib.libusb_set_iso_packet_lengths = libusb_set_iso_packet_lengths + + #int libusb_get_max_iso_packet_size(libusb_device* dev, + # unsigned char endpoint); + lib.libusb_get_max_iso_packet_size.argtypes = [c_void_p, + c_ubyte] + + # void libusb_fill_iso_transfer( + # struct libusb_transfer* transfer, + # libusb_device_handle* dev_handle, + # unsigned char endpoint, + # unsigned char* buffer, + # int length, + # int num_iso_packets, + # libusb_transfer_cb_fn callback, + # void * user_data, + # unsigned int timeout + # ); + def libusb_fill_iso_transfer(_libusb_transfer_p, dev_handle, endpoint, buffer, length, + num_iso_packets, callback, user_data, timeout): + r"""This function is inline in the libusb.h file, so we must implement + it. + + lib.libusb_fill_iso_transfer.argtypes = [ + _libusb_transfer, + _libusb_device_handle, + c_ubyte, + POINTER(c_ubyte), + c_int, + c_int, + _libusb_transfer_cb_fn_p, + c_void_p, + c_uint + ] + """ + transfer = _libusb_transfer_p.contents + transfer.dev_handle = dev_handle + transfer.endpoint = endpoint + transfer.type = _LIBUSB_TRANSFER_TYPE_ISOCHRONOUS + transfer.timeout = timeout + transfer.buffer = cast(buffer, c_void_p) + transfer.length = length + transfer.num_iso_packets = num_iso_packets + transfer.user_data = user_data + transfer.callback = callback + lib.libusb_fill_iso_transfer = libusb_fill_iso_transfer + + # uint8_t libusb_get_bus_number(libusb_device *dev) + lib.libusb_get_bus_number.argtypes = [c_void_p] + lib.libusb_get_bus_number.restype = c_uint8 + + # uint8_t libusb_get_device_address(libusb_device *dev) + lib.libusb_get_device_address.argtypes = [c_void_p] + lib.libusb_get_device_address.restype = c_uint8 + + try: + # uint8_t libusb_get_port_number(libusb_device *dev) + lib.libusb_get_port_number.argtypes = [c_void_p] + lib.libusb_get_port_number.restype = c_uint8 + except AttributeError: + pass + + #int libusb_handle_events(libusb_context *ctx); + lib.libusb_handle_events.argtypes = [c_void_p] + +def _strerror(errcode): + return _lib.libusb_strerror(errcode).decode('utf8') + +# check a libusb function call +def _check(ret): + if hasattr(ret, 'value'): + ret = ret.value + + if ret < 0: + if ret == LIBUSB_ERROR_NOT_SUPPORTED: + raise NotImplementedError(_strerror(ret)) + else: + raise USBError(_strerror(ret), ret, _libusb_errno[ret]) + + return ret + +# wrap a device +class _Device(object): + def __init__(self, devid): + self.devid = _lib.libusb_ref_device(devid) + def __del__(self): + _lib.libusb_unref_device(self.devid) + +# wrap a descriptor and keep a reference to another object +# Thanks to Thomas Reitmayr. +class _WrapDescriptor(object): + def __init__(self, desc, obj = None): + self.obj = obj + self.desc = desc + def __getattr__(self, name): + return getattr(self.desc, name) + +# wrap a configuration descriptor +class _ConfigDescriptor(object): + def __init__(self, desc): + self.desc = desc + def __del__(self): + _lib.libusb_free_config_descriptor(self.desc) + def __getattr__(self, name): + return getattr(self.desc.contents, name) + + +# iterator for libusb devices +class _DevIterator(object): + def __init__(self, ctx): + self.dev_list = POINTER(c_void_p)() + self.num_devs = _check(_lib.libusb_get_device_list( + ctx, + byref(self.dev_list)) + ) + def __iter__(self): + for i in range(self.num_devs): + yield _Device(self.dev_list[i]) + def __del__(self): + _lib.libusb_free_device_list(self.dev_list, 1) + +class _DeviceHandle(object): + def __init__(self, dev): + self.handle = _libusb_device_handle() + self.devid = dev.devid + _check(_lib.libusb_open(self.devid, byref(self.handle))) + +class _IsoTransferHandler(object): + def __init__(self, dev_handle, ep, buff, timeout): + address, length = buff.buffer_info() + + packet_length = _lib.libusb_get_max_iso_packet_size(dev_handle.devid, ep) + packet_count = int(math.ceil(float(length) / packet_length)) + + self.transfer = _lib.libusb_alloc_transfer(packet_count) + + _lib.libusb_fill_iso_transfer(self.transfer, + dev_handle.handle, + ep, + cast(address, POINTER(c_ubyte)), + length, + packet_count, + _libusb_transfer_cb_fn_p(self.__callback), + None, + timeout) + + self.__set_packets_length(length, packet_length) + + def __del__(self): + _lib.libusb_free_transfer(self.transfer) + + def submit(self, ctx = None): + self.__callback_done = 0 + _check(_lib.libusb_submit_transfer(self.transfer)) + + while not self.__callback_done: + _check(_lib.libusb_handle_events(ctx)) + + return self.__compute_size_transf_data() + + def __compute_size_transf_data(self): + return sum([t.actual_length for t in + _get_iso_packet_list(self.transfer.contents)]) + + def __set_packets_length(self, n, packet_length): + _lib.libusb_set_iso_packet_lengths(self.transfer, packet_length) + r = n % packet_length + if r: + iso_packets = _get_iso_packet_list(self.transfer.contents) + iso_packets[-1].length = r + + def __callback(self, transfer): + if transfer.contents.status == LIBUSB_TRANSFER_COMPLETED: + self.__callback_done = 1 + else: + status = int(transfer.contents.status) + raise usb.USBError(_str_transfer_error[status], + status, + _transfer_errno[status]) + +# implementation of libusb 1.0 backend +class _LibUSB(usb.backend.IBackend): + @methodtrace(_logger) + def __init__(self, lib): + usb.backend.IBackend.__init__(self) + self.lib = lib + self.ctx = c_void_p() + _check(self.lib.libusb_init(byref(self.ctx))) + + @methodtrace(_logger) + def __del__(self): + self.lib.libusb_exit(self.ctx) + + + @methodtrace(_logger) + def enumerate_devices(self): + return _DevIterator(self.ctx) + + @methodtrace(_logger) + def get_device_descriptor(self, dev): + dev_desc = _libusb_device_descriptor() + _check(self.lib.libusb_get_device_descriptor(dev.devid, byref(dev_desc))) + dev_desc.bus = self.lib.libusb_get_bus_number(dev.devid) + dev_desc.address = self.lib.libusb_get_device_address(dev.devid) + + # Only available in newer versions of libusb + try: + dev_desc.port_number = self.lib.libusb_get_port_number(dev.devid) + except AttributeError: + dev_desc.port_number = None + + return dev_desc + + @methodtrace(_logger) + def get_configuration_descriptor(self, dev, config): + cfg = POINTER(_libusb_config_descriptor)() + _check(self.lib.libusb_get_config_descriptor( + dev.devid, + config, byref(cfg))) + config_desc = _ConfigDescriptor(cfg) + config_desc.extra_descriptors = ( + config_desc.extra[:config_desc.extra_length]) + return config_desc + + @methodtrace(_logger) + def get_interface_descriptor(self, dev, intf, alt, config): + cfg = self.get_configuration_descriptor(dev, config) + if intf >= cfg.bNumInterfaces: + raise IndexError('Invalid interface index ' + str(intf)) + i = cfg.interface[intf] + if alt >= i.num_altsetting: + raise IndexError('Invalid alternate setting index ' + str(alt)) + intf_desc = i.altsetting[alt] + intf_desc.extra_descriptors = intf_desc.extra[:intf_desc.extra_length] + return _WrapDescriptor(intf_desc, cfg) + + @methodtrace(_logger) + def get_endpoint_descriptor(self, dev, ep, intf, alt, config): + i = self.get_interface_descriptor(dev, intf, alt, config) + if ep > i.bNumEndpoints: + raise IndexError('Invalid endpoint index ' + str(ep)) + ep_desc = i.endpoint[ep] + ep_desc.extra_descriptors = ep_desc.extra[:ep_desc.extra_length] + return _WrapDescriptor(ep_desc, i) + + @methodtrace(_logger) + def open_device(self, dev): + return _DeviceHandle(dev) + + @methodtrace(_logger) + def close_device(self, dev_handle): + self.lib.libusb_close(dev_handle.handle) + + @methodtrace(_logger) + def set_configuration(self, dev_handle, config_value): + _check(self.lib.libusb_set_configuration(dev_handle.handle, config_value)) + + @methodtrace(_logger) + def get_configuration(self, dev_handle): + config = c_int() + _check(self.lib.libusb_get_configuration(dev_handle.handle, byref(config))) + return config.value + + @methodtrace(_logger) + def set_interface_altsetting(self, dev_handle, intf, altsetting): + _check(self.lib.libusb_set_interface_alt_setting( + dev_handle.handle, + intf, + altsetting)) + + @methodtrace(_logger) + def claim_interface(self, dev_handle, intf): + _check(self.lib.libusb_claim_interface(dev_handle.handle, intf)) + + @methodtrace(_logger) + def release_interface(self, dev_handle, intf): + _check(self.lib.libusb_release_interface(dev_handle.handle, intf)) + + @methodtrace(_logger) + def bulk_write(self, dev_handle, ep, intf, data, timeout): + return self.__write(self.lib.libusb_bulk_transfer, + dev_handle, + ep, + intf, + data, + timeout) + + @methodtrace(_logger) + def bulk_read(self, dev_handle, ep, intf, buff, timeout): + return self.__read(self.lib.libusb_bulk_transfer, + dev_handle, + ep, + intf, + buff, + timeout) + + @methodtrace(_logger) + def intr_write(self, dev_handle, ep, intf, data, timeout): + return self.__write(self.lib.libusb_interrupt_transfer, + dev_handle, + ep, + intf, + data, + timeout) + + @methodtrace(_logger) + def intr_read(self, dev_handle, ep, intf, buff, timeout): + return self.__read(self.lib.libusb_interrupt_transfer, + dev_handle, + ep, + intf, + buff, + timeout) + + @methodtrace(_logger) + def iso_write(self, dev_handle, ep, intf, data, timeout): + handler = _IsoTransferHandler(dev_handle, ep, data, timeout) + return handler.submit(self.ctx) + + @methodtrace(_logger) + def iso_read(self, dev_handle, ep, intf, buff, timeout): + handler = _IsoTransferHandler(dev_handle, ep, buff, timeout) + return handler.submit(self.ctx) + + @methodtrace(_logger) + def ctrl_transfer(self, + dev_handle, + bmRequestType, + bRequest, + wValue, + wIndex, + data, + timeout): + addr, length = data.buffer_info() + length *= data.itemsize + + ret = _check(self.lib.libusb_control_transfer( + dev_handle.handle, + bmRequestType, + bRequest, + wValue, + wIndex, + cast(addr, POINTER(c_ubyte)), + length, + timeout)) + + return ret + + @methodtrace(_logger) + def clear_halt(self, dev_handle, ep): + _check(self.lib.libusb_clear_halt(dev_handle.handle, ep)) + + @methodtrace(_logger) + def reset_device(self, dev_handle): + _check(self.lib.libusb_reset_device(dev_handle.handle)) + + @methodtrace(_logger) + def is_kernel_driver_active(self, dev_handle, intf): + return bool(_check(self.lib.libusb_kernel_driver_active(dev_handle.handle, + intf))) + + @methodtrace(_logger) + def detach_kernel_driver(self, dev_handle, intf): + _check(self.lib.libusb_detach_kernel_driver(dev_handle.handle, intf)) + + @methodtrace(_logger) + def attach_kernel_driver(self, dev_handle, intf): + _check(self.lib.libusb_attach_kernel_driver(dev_handle.handle, intf)) + + def __write(self, fn, dev_handle, ep, intf, data, timeout): + address, length = data.buffer_info() + length *= data.itemsize + transferred = c_int() + retval = fn(dev_handle.handle, + ep, + cast(address, POINTER(c_ubyte)), + length, + byref(transferred), + timeout) + # do not assume LIBUSB_ERROR_TIMEOUT means no I/O. + if not (transferred.value and retval == LIBUSB_ERROR_TIMEOUT): + _check(retval) + + return transferred.value + + def __read(self, fn, dev_handle, ep, intf, buff, timeout): + address, length = buff.buffer_info() + length *= buff.itemsize + transferred = c_int() + retval = fn(dev_handle.handle, + ep, + cast(address, POINTER(c_ubyte)), + length, + byref(transferred), + timeout) + # do not assume LIBUSB_ERROR_TIMEOUT means no I/O. + if not (transferred.value and retval == LIBUSB_ERROR_TIMEOUT): + _check(retval) + return transferred.value + +def get_backend(find_library=None): + global _lib + try: + if _lib is None: + _lib = _load_library(find_library=find_library) + _setup_prototypes(_lib) + return _LibUSB(_lib) + except usb.libloader.LibaryException: + # exception already logged (if any) + _logger.error('Error loading libusb 1.0 backend', exc_info=False) + return None + except Exception: + _logger.error('Error loading libusb 1.0 backend', exc_info=True) + return None diff -Nru quisk-3.6.18/usb/backend/openusb.py quisk-3.7.6/usb/backend/openusb.py --- quisk-3.6.18/usb/backend/openusb.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/backend/openusb.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -29,16 +29,120 @@ from ctypes import * import ctypes.util import usb.util -import sys from usb._debug import methodtrace import logging +import errno +import sys +import usb._interop as _interop +import usb.util as util +import usb.libloader +from usb.core import USBError __author__ = 'Wander Lairson Costa' -__all__ = ['get_backend'] +__all__ = [ + 'get_backend' + 'OPENUSB_SUCCESS' + 'OPENUSB_PLATFORM_FAILURE' + 'OPENUSB_NO_RESOURCES' + 'OPENUSB_NO_BANDWIDTH' + 'OPENUSB_NOT_SUPPORTED' + 'OPENUSB_HC_HARDWARE_ERROR' + 'OPENUSB_INVALID_PERM' + 'OPENUSB_BUSY' + 'OPENUSB_BADARG' + 'OPENUSB_NOACCESS' + 'OPENUSB_PARSE_ERROR' + 'OPENUSB_UNKNOWN_DEVICE' + 'OPENUSB_INVALID_HANDLE' + 'OPENUSB_SYS_FUNC_FAILURE' + 'OPENUSB_NULL_LIST' + 'OPENUSB_CB_CONTINUE' + 'OPENUSB_CB_TERMINATE' + 'OPENUSB_IO_STALL' + 'OPENUSB_IO_CRC_ERROR' + 'OPENUSB_IO_DEVICE_HUNG' + 'OPENUSB_IO_REQ_TOO_BIG' + 'OPENUSB_IO_BIT_STUFFING' + 'OPENUSB_IO_UNEXPECTED_PID' + 'OPENUSB_IO_DATA_OVERRUN' + 'OPENUSB_IO_DATA_UNDERRUN' + 'OPENUSB_IO_BUFFER_OVERRUN' + 'OPENUSB_IO_BUFFER_UNDERRUN' + 'OPENUSB_IO_PID_CHECK_FAILURE' + 'OPENUSB_IO_DATA_TOGGLE_MISMATCH' + 'OPENUSB_IO_TIMEOUT' + 'OPENUSB_IO_CANCELED' + ] _logger = logging.getLogger('usb.backend.openusb') +OPENUSB_SUCCESS = 0 +OPENUSB_PLATFORM_FAILURE = -1 +OPENUSB_NO_RESOURCES = -2 +OPENUSB_NO_BANDWIDTH = -3 +OPENUSB_NOT_SUPPORTED = -4 +OPENUSB_HC_HARDWARE_ERROR = -5 +OPENUSB_INVALID_PERM = -6 +OPENUSB_BUSY = -7 +OPENUSB_BADARG = -8 +OPENUSB_NOACCESS = -9 +OPENUSB_PARSE_ERROR = -10 +OPENUSB_UNKNOWN_DEVICE = -11 +OPENUSB_INVALID_HANDLE = -12 +OPENUSB_SYS_FUNC_FAILURE = -13 +OPENUSB_NULL_LIST = -14 +OPENUSB_CB_CONTINUE = -20 +OPENUSB_CB_TERMINATE = -21 +OPENUSB_IO_STALL = -50 +OPENUSB_IO_CRC_ERROR = -51 +OPENUSB_IO_DEVICE_HUNG = -52 +OPENUSB_IO_REQ_TOO_BIG = -53 +OPENUSB_IO_BIT_STUFFING = -54 +OPENUSB_IO_UNEXPECTED_PID = -55 +OPENUSB_IO_DATA_OVERRUN = -56 +OPENUSB_IO_DATA_UNDERRUN = -57 +OPENUSB_IO_BUFFER_OVERRUN = -58 +OPENUSB_IO_BUFFER_UNDERRUN = -59 +OPENUSB_IO_PID_CHECK_FAILURE = -60 +OPENUSB_IO_DATA_TOGGLE_MISMATCH = -61 +OPENUSB_IO_TIMEOUT = -62 +OPENUSB_IO_CANCELED = -63 + +_openusb_errno = { + OPENUSB_SUCCESS:None, + OPENUSB_PLATFORM_FAILURE:None, + OPENUSB_NO_RESOURCES:errno.__dict__.get('ENOMEM', None), + OPENUSB_NO_BANDWIDTH:None, + OPENUSB_NOT_SUPPORTED:errno.__dict__.get('ENOSYS', None), + OPENUSB_HC_HARDWARE_ERROR:errno.__dict__.get('EIO', None), + OPENUSB_INVALID_PERM:errno.__dict__.get('EBADF', None), + OPENUSB_BUSY:errno.__dict__.get('EBUSY', None), + OPENUSB_BADARG:errno.__dict__.get('EINVAL', None), + OPENUSB_NOACCESS:errno.__dict__.get('EACCES', None), + OPENUSB_PARSE_ERROR:None, + OPENUSB_UNKNOWN_DEVICE:errno.__dict__.get('ENODEV', None), + OPENUSB_INVALID_HANDLE:errno.__dict__.get('EINVAL', None), + OPENUSB_SYS_FUNC_FAILURE:None, + OPENUSB_NULL_LIST:None, + OPENUSB_CB_CONTINUE:None, + OPENUSB_CB_TERMINATE:None, + OPENUSB_IO_STALL:errno.__dict__.get('EIO', None), + OPENUSB_IO_CRC_ERROR:errno.__dict__.get('EIO', None), + OPENUSB_IO_DEVICE_HUNG:errno.__dict__.get('EIO', None), + OPENUSB_IO_REQ_TOO_BIG:errno.__dict__.get('E2BIG', None), + OPENUSB_IO_BIT_STUFFING:None, + OPENUSB_IO_UNEXPECTED_PID:errno.__dict__.get('ESRCH', None), + OPENUSB_IO_DATA_OVERRUN:errno.__dict__.get('EOVERFLOW', None), + OPENUSB_IO_DATA_UNDERRUN:None, + OPENUSB_IO_BUFFER_OVERRUN:errno.__dict__.get('EOVERFLOW', None), + OPENUSB_IO_BUFFER_UNDERRUN:None, + OPENUSB_IO_PID_CHECK_FAILURE:None, + OPENUSB_IO_DATA_TOGGLE_MISMATCH:None, + OPENUSB_IO_TIMEOUT:errno.__dict__.get('ETIMEDOUT', None), + OPENUSB_IO_CANCELED:errno.__dict__.get('EINTR', None) +} + class _usb_endpoint_desc(Structure): _fields_ = [('bLength', c_uint8), ('bDescriptorType', c_uint8), @@ -88,15 +192,30 @@ class _openusb_request_result(Structure): _fields_ = [('status', c_int32), - ('transfered_bytes', c_uint32)] + ('transferred_bytes', c_uint32)] class _openusb_ctrl_request(Structure): + def __init__(self): + super(_openusb_ctrl_request, self).__init__() + self.setup.bmRequestType = 0 + self.setup.bRequest = 0 + self.setup.wValue = 0 + self.setup.wIndex = 0 + self.payload = None + self.length = 0 + self.timeout = 0 + self.flags = 0 + self.result.status = 0 + self.result.transferred_bytes = 0 + self.next = None + class _openusb_ctrl_setup(Structure): _fields_ = [('bmRequestType', c_uint8), ('bRequest', c_uint8), ('wValue', c_uint16), ('wIndex', c_uint16)] - _fields_ = [('payload', POINTER(c_uint8)), + _fields_ = [('setup', _openusb_ctrl_setup), + ('payload', POINTER(c_uint8)), ('length', c_uint32), ('timeout', c_uint32), ('flags', c_uint32), @@ -143,16 +262,18 @@ _lib = None _ctx = None -def _load_library(): - libname = ctypes.util.find_library('openusb') - if libname is None: - raise OSError('USB library could not be found') - return CDLL(libname) +def _load_library(find_library=None): + # FIXME: cygwin name is "openusb"? + # (that's what the original _load_library() function + # would have searched for) + return usb.libloader.load_locate_library( + ('openusb',), 'openusb', "OpenUSB library", find_library=find_library + ) def _setup_prototypes(lib): # int32_t openusb_init(uint32_t flags , openusb_handle_t *handle); lib.openusb_init.argtypes = [c_uint32, POINTER(_openusb_handle)] - lib.openusb.restype = c_int32 + lib.openusb_init.restype = c_int32 # void openusb_fini(openusb_handle_t handle ); lib.openusb_fini.argtypes = [_openusb_handle] @@ -207,6 +328,11 @@ lib.openusb_set_configuration.argtypes = [_openusb_dev_handle, c_uint8] lib.openusb_set_configuration.restype = c_int32 + # int32_t openusb_get_configuration(openusb_dev_handle_t dev, + # uint8_t *cfg); + lib.openusb_get_configuration.argtypes = [_openusb_dev_handle, POINTER(c_uint8)] + lib.openusb_get_configuration.restype = c_int32 + # int32_t openusb_claim_interface(openusb_dev_handle_t dev, # uint8_t ifc, # openusb_init_flag_t flags); @@ -292,7 +418,7 @@ ] lib.openusb_parse_interface_desc.restype = c_int32 - + # int32_t openusb_parse_endpoint_desc(openusb_handle_t handle, # openusb_devid_t devid, # uint8_t *buffer, @@ -372,11 +498,13 @@ lib.openusb_isoc_xfer.restype = c_int32 -def _check(retval): - if retval.value != 0: - from usb.core import USBError - raise USBError(_lib.openusb_strerror(retval).value) - return retval +def _check(ret): + if hasattr(ret, 'value'): + ret = ret.value + + if ret != 0: + raise USBError(_lib.openusb_strerror(ret), ret, _openusb_errno[ret]) + return ret class _Context(object): def __init__(self): @@ -387,7 +515,7 @@ class _BusIterator(object): def __init__(self): - self.buslist = POINTER(openusb_busid)() + self.buslist = POINTER(_openusb_busid)() num_busids = c_uint32() _check(_lib.openusb_get_busid_list(_ctx.handle, byref(self.buslist), @@ -429,6 +557,9 @@ None, 0, byref(desc))) + desc.bus = None + desc.address = None + desc.port_number = None return desc @methodtrace(_logger) @@ -440,6 +571,7 @@ 0, config, byref(desc))) + desc.extra_descriptors = None return desc @methodtrace(_logger) @@ -453,6 +585,7 @@ intf, alt, byref(desc))) + desc.extra_descriptors = None return desc @methodtrace(_logger) @@ -467,6 +600,7 @@ alt, ep, byref(desc))) + desc.extra_descriptors = None return desc @methodtrace(_logger) @@ -484,8 +618,14 @@ _check(_lib.openusb_set_configuration(dev_handle, config_value)) @methodtrace(_logger) + def get_configuration(self, dev_handle): + config = c_uint8() + _check(_lib.openusb_get_configuration(dev_handle, byref(config))) + return config.value + + @methodtrace(_logger) def set_interface_altsetting(self, dev_handle, intf, altsetting): - _check(_lib.set_altsetting(dev_handle, intf, altsetting)) + _check(_lib.openusb_set_altsetting(dev_handle, intf, altsetting)) @methodtrace(_logger) def claim_interface(self, dev_handle, intf): @@ -499,20 +639,21 @@ def bulk_write(self, dev_handle, ep, intf, data, timeout): request = _openusb_bulk_request() memset(byref(request), 0, sizeof(request)) - request.payload, request.length = data.buffer_info() + payload, request.length = data.buffer_info() + request.payload = cast(payload, POINTER(c_uint8)) request.timeout = timeout _check(_lib.openusb_bulk_xfer(dev_handle, intf, ep, byref(request))) - return request.transfered_bytes.value + return request.result.transferred_bytes @methodtrace(_logger) - def bulk_read(self, dev_handle, ep, intf, size, timeout): + def bulk_read(self, dev_handle, ep, intf, buff, timeout): request = _openusb_bulk_request() - buffer = array.array('B', '\x00' * size) memset(byref(request), 0, sizeof(request)) - request.payload, request.length = buffer.buffer_info() + payload, request.length = buff.buffer_info() + request.payload = cast(payload, POINTER(c_uint8)) request.timeout = timeout _check(_lib.openusb_bulk_xfer(dev_handle, intf, ep, byref(request))) - return buffer[:request.transfered_bytes.value] + return request.result.transferred_bytes @methodtrace(_logger) def intr_write(self, dev_handle, ep, intf, data, timeout): @@ -522,18 +663,17 @@ request.payload = cast(payload, POINTER(c_uint8)) request.timeout = timeout _check(_lib.openusb_intr_xfer(dev_handle, intf, ep, byref(request))) - return request.transfered_bytes.value + return request.result.transferred_bytes @methodtrace(_logger) - def intr_read(self, dev_handle, ep, intf, size, timeout): + def intr_read(self, dev_handle, ep, intf, buff, timeout): request = _openusb_intr_request() - buffer = array.array('B', '\x00' * size) memset(byref(request), 0, sizeof(request)) - payload, request.length = buffer.buffer_info() + payload, request.length = buff.buffer_info() request.payload = cast(payload, POINTER(c_uint8)) request.timeout = timeout _check(_lib.openusb_intr_xfer(dev_handle, intf, ep, byref(request))) - return buffer[:request.transfered_bytes.value] + return request.result.transferred_bytes # TODO: implement isochronous # @methodtrace(_logger) @@ -551,7 +691,7 @@ bRequest, wValue, wIndex, - data_or_wLength, + data, timeout): request = _openusb_ctrl_request() request.setup.bmRequestType = bmRequestType @@ -562,33 +702,45 @@ direction = usb.util.ctrl_direction(bmRequestType) - if direction == ENDPOINT_OUT: - buffer = data_or_wLength - else: - buffer = array.array('B', '\x00' * data_or_wLength) - - payload, request.length = buffer.buffer_info() + payload, request.length = data.buffer_info() + request.length *= data.itemsize request.payload = cast(payload, POINTER(c_uint8)) - ret = _check(_lib.openusb_ctrl_xfer(dev_handle, 0, 0, byref(request))) + _check(_lib.openusb_ctrl_xfer(dev_handle, 0, 0, byref(request))) - if direction == ENDPOINT_OUT: - ret - else: - buffer[:ret] + return request.result.transferred_bytes @methodtrace(_logger) def reset_device(self, dev_handle): _check(_lib.openusb_reset(dev_handle)) -def get_backend(): + @methodtrace(_logger) + def clear_halt(self, dev_handle, ep): + bmRequestType = util.build_request_type( + util.CTRL_OUT, + util.CTRL_TYPE_STANDARD, + util.CTRL_RECIPIENT_ENDPOINT) + self.ctrl_transfer( + dev_handle, + bmRequestType, + 0x03, + 0, + ep, + _interop.as_array(), + 1000) + +def get_backend(find_library=None): try: global _lib, _ctx if _lib is None: - _lib = _load_library() + _lib = _load_library(find_library) _setup_prototypes(_lib) _ctx = _Context() return _OpenUSB() + except usb.libloader.LibaryException: + # exception already logged (if any) + _logger.error('Error loading OpenUSB backend', exc_info=False) + return None except Exception: _logger.error('Error loading OpenUSB backend', exc_info=True) return None Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/backend/openusb.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/backend/openusb.pyc differ diff -Nru quisk-3.6.18/usb/control.py quisk-3.7.6/usb/control.py --- quisk-3.6.18/usb/control.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/usb/control.py 2015-08-13 17:45:27.000000000 +0000 @@ -0,0 +1,253 @@ +# Copyright (C) 2009-2014 Wander Lairson Costa +# +# The following terms apply to all files associated +# with the software unless explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. + +r"""usb.control - USB standard control requests + +This module exports: + +get_status - get recipeint status +clear_feature - clear a recipient feature +set_feature - set a recipient feature +get_descriptor - get a device descriptor +set_descriptor - set a device descriptor +get_configuration - get a device configuration +set_configuration - set a device configuration +get_interface - get a device interface +set_interface - set a device interface +""" + +__author__ = 'Wander Lairson Costa' + +__all__ = ['get_status', + 'clear_feature', + 'set_feature', + 'get_descriptor', + 'set_descriptor', + 'get_configuration', + 'set_configuration', + 'get_interface', + 'set_interface', + 'ENDPOINT_HALT', + 'FUNCTION_SUSPEND', + 'DEVICE_REMOTE_WAKEUP', + 'U1_ENABLE', + 'U2_ENABLE', + 'LTM_ENABLE'] + +import usb.util as util +import usb.core as core + +def _parse_recipient(recipient, direction): + if recipient is None: + r = util.CTRL_RECIPIENT_DEVICE + wIndex = 0 + elif isinstance(recipient, core.Interface): + r = util.CTRL_RECIPIENT_INTERFACE + wIndex = recipient.bInterfaceNumber + elif isinstance(recipient, core.Endpoint): + r = util.CTRL_RECIPIENT_ENDPOINT + wIndex = recipient.bEndpointAddress + else: + raise ValueError('Invalid recipient.') + bmRequestType = util.build_request_type( + direction, + util.CTRL_TYPE_STANDARD, + r + ) + return (bmRequestType, wIndex) + +# standard feature selectors from USB 2.0/3.0 +ENDPOINT_HALT = 0 +FUNCTION_SUSPEND = 0 +DEVICE_REMOTE_WAKEUP = 1 +U1_ENABLE = 48 +U2_ENABLE = 49 +LTM_ENABLE = 50 + +def get_status(dev, recipient = None): + r"""Return the status for the specified recipient. + + dev is the Device object to which the request will be + sent to. + + The recipient can be None (on which the status will be queried + from the device), an Interface or Endpoint descriptors. + + The status value is returned as an integer with the lower + word being the two bytes status value. + """ + bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_IN) + ret = dev.ctrl_transfer(bmRequestType = bmRequestType, + bRequest = 0x00, + wIndex = wIndex, + data_or_wLength = 2) + return ret[0] | (ret[1] << 8) + +def clear_feature(dev, feature, recipient = None): + r"""Clear/disable a specific feature. + + dev is the Device object to which the request will be + sent to. + + feature is the feature you want to disable. + + The recipient can be None (on which the status will be queried + from the device), an Interface or Endpoint descriptors. + """ + if feature == ENDPOINT_HALT: + dev.clear_halt(recipient) + else: + bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_OUT) + dev.ctrl_transfer(bmRequestType = bmRequestType, + bRequest = 0x01, + wIndex = wIndex, + wValue = feature) + +def set_feature(dev, feature, recipient = None): + r"""Set/enable a specific feature. + + dev is the Device object to which the request will be + sent to. + + feature is the feature you want to enable. + + The recipient can be None (on which the status will be queried + from the device), an Interface or Endpoint descriptors. + """ + bmRequestType, wIndex = _parse_recipient(recipient, util.CTRL_OUT) + dev.ctrl_transfer(bmRequestType = bmRequestType, + bRequest = 0x03, + wIndex = wIndex, + wValue = feature) + +def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex = 0): + r"""Return the specified descriptor. + + dev is the Device object to which the request will be + sent to. + + desc_size is the descriptor size. + + desc_type and desc_index are the descriptor type and index, + respectively. wIndex index is used for string descriptors + and represents the Language ID. For other types of descriptors, + it is zero. + """ + wValue = desc_index | (desc_type << 8) + + bmRequestType = util.build_request_type( + util.CTRL_IN, + util.CTRL_TYPE_STANDARD, + util.CTRL_RECIPIENT_DEVICE) + + return dev.ctrl_transfer( + bmRequestType = bmRequestType, + bRequest = 0x06, + wValue = wValue, + wIndex = wIndex, + data_or_wLength = desc_size) + +def set_descriptor(dev, desc, desc_type, desc_index, wIndex = None): + r"""Update an existing descriptor or add a new one. + + dev is the Device object to which the request will be + sent to. + + The desc parameter is the descriptor to be sent to the device. + desc_type and desc_index are the descriptor type and index, + respectively. wIndex index is used for string descriptors + and represents the Language ID. For other types of descriptors, + it is zero. + """ + wValue = desc_index | (desc_type << 8) + + bmRequestType = util.build_request_type( + util.CTRL_OUT, + util.CTRL_TYPE_STANDARD, + util.CTRL_RECIPIENT_DEVICE) + + dev.ctrl_transfer( + bmRequestType = bmRequestType, + bRequest = 0x07, + wValue = wValue, + wIndex = wIndex, + data_or_wLength = desc) + +def get_configuration(dev): + r"""Get the current active configuration of the device. + + dev is the Device object to which the request will be + sent to. + + This function differs from the Device.get_active_configuration + method because the later may use cached data, while this + function always does a device request. + """ + bmRequestType = util.build_request_type( + util.CTRL_IN, + util.CTRL_TYPE_STANDARD, + util.CTRL_RECIPIENT_DEVICE) + + return dev.ctrl_transfer( + bmRequestType, + bRequest = 0x08, + data_or_wLength = 1)[0] + +def set_configuration(dev, bConfigurationNumber): + r"""Set the current device configuration. + + dev is the Device object to which the request will be + sent to. + """ + dev.set_configuration(bConfigurationNumber) + +def get_interface(dev, bInterfaceNumber): + r"""Get the current alternate setting of the interface. + + dev is the Device object to which the request will be + sent to. + """ + bmRequestType = util.build_request_type( + util.CTRL_IN, + util.CTRL_TYPE_STANDARD, + util.CTRL_RECIPIENT_INTERFACE) + + return dev.ctrl_transfer( + bmRequestType = bmRequestType, + bRequest = 0x0a, + wIndex = bInterfaceNumber, + data_or_wLength = 1)[0] + +def set_interface(dev, bInterfaceNumber, bAlternateSetting): + r"""Set the alternate setting of the interface. + + dev is the Device object to which the request will be + sent to. + """ + dev.set_interface_altsetting(bInterfaceNumber, bAlternateSetting) + diff -Nru quisk-3.6.18/usb/core.py quisk-3.7.6/usb/core.py --- quisk-3.6.18/usb/core.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/core.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -35,19 +35,21 @@ Interface - a class representing an interface descriptor. Endpoint - a class representing an endpoint descriptor. find() - a function to find USB devices. +show_devices() - a function to show the devices present. """ __author__ = 'Wander Lairson Costa' -__all__ = ['Device', 'Configuration', 'Interface', 'Endpoint', 'find'] +__all__ = [ 'Device', 'Configuration', 'Interface', 'Endpoint', 'find', + 'show_devices' ] -import array import usb.util as util import copy -import sys import operator import usb._interop as _interop +import usb._lookup as _lu import logging +import array _logger = logging.getLogger('usb.core') @@ -55,54 +57,95 @@ def _set_attr(input, output, fields): for f in fields: - setattr(output, f, int(getattr(input, f))) + setattr(output, f, getattr(input, f)) + +def _try_get_string(dev, index, langid = None, default_str_i0 = "", + default_access_error = "Error Accessing String"): + """ try to get a string, but return a string no matter what + """ + if index == 0 : + string = default_str_i0 + else: + try: + if langid is None: + string = util.get_string(dev, index) + else: + string = util.get_string(dev, index, langid) + except : + string = default_access_error + return string + +def _try_lookup(table, value, default = ""): + """ try to get a string from the lookup table, return "" instead of key + error + """ + try: + string = table[ value ] + except KeyError: + string = default + return string + +class _DescriptorInfo(str): + """ this class is used so that when a descriptor is shown on the + terminal it is propely formatted """ + def __repr__(self): + return self class _ResourceManager(object): - def __init__(self, parent, dev, backend): + def __init__(self, dev, backend): self.backend = backend self._active_cfg_index = None self.dev = dev self.handle = None self._claimed_intf = _interop._set() - self._alt_set = {} - self._ep_type_map = {} + self._ep_info = {} + def managed_open(self): if self.handle is None: self.handle = self.backend.open_device(self.dev) return self.handle + def managed_close(self): if self.handle is not None: self.backend.close_device(self.handle) self.handle = None + def managed_set_configuration(self, device, config): if config is None: cfg = device[0] elif isinstance(config, Configuration): cfg = config + elif config == 0: # unconfigured state + class MockConfiguration(object): + def __init__(self): + self.index = None + self.bConfigurationValue = 0 + cfg = MockConfiguration() else: cfg = util.find_descriptor(device, bConfigurationValue=config) + self.managed_open() self.backend.set_configuration(self.handle, cfg.bConfigurationValue) + # cache the index instead of the object to avoid cyclic references # of the device and Configuration (Device tracks the _ResourceManager, # which tracks the Configuration, which tracks the Device) self._active_cfg_index = cfg.index - # after changing configuration, our alternate setting and endpoint type caches - # are not valid anymore - self._ep_type_map.clear() - self._alt_set.clear() + + self._ep_info.clear() + def managed_claim_interface(self, device, intf): self.managed_open() - if intf is None: - cfg = self.get_active_configuration() - i = cfg[(0,0)].bInterfaceNumber - elif isinstance(intf, Interface): + + if isinstance(intf, Interface): i = intf.bInterfaceNumber else: i = intf + if i not in self._claimed_intf: self.backend.claim_interface(self.handle, i) self._claimed_intf.add(i) + def managed_release_interface(self, device, intf): if intf is None: cfg = self.get_active_configuration(device) @@ -111,87 +154,107 @@ i = intf.bInterfaceNumber else: i = intf + if i in self._claimed_intf: self.backend.release_interface(self.handle, i) self._claimed_intf.remove(i) + def managed_set_interface(self, device, intf, alt): - if intf is None: - i = self.get_interface(device, intf) - elif isinstance(intf, Interface): + if isinstance(intf, Interface): i = intf else: cfg = self.get_active_configuration(device) + if intf is None: + intf = cfg[(0,0)].bInterfaceNumber if alt is not None: i = util.find_descriptor(cfg, bInterfaceNumber=intf, bAlternateSetting=alt) else: i = util.find_descriptor(cfg, bInterfaceNumber=intf) + self.managed_claim_interface(device, i) + if alt is None: alt = i.bAlternateSetting + self.backend.set_interface_altsetting(self.handle, i.bInterfaceNumber, alt) - self._alt_set[i.bInterfaceNumber] = alt - def get_interface(self, device, intf): - # TODO: check the viability of issuing a GET_INTERFACE - # request when we don't have a alternate setting cached - if intf is None: - cfg = self.get_active_configuration(device) - return cfg[(0,0)] - elif isinstance(intf, Interface): - return intf + + def setup_request(self, device, endpoint): + # we need the endpoint address, but the "endpoint" parameter + # can be either the a Endpoint object or the endpoint address itself + if isinstance(endpoint, Endpoint): + endpoint_address = endpoint.bEndpointAddress else: - cfg = self.get_active_configuration(device) - if intf in self._alt_set: - return util.find_descriptor(cfg, - bInterfaceNumber=intf, - bAlternateSetting=self._alt_set[intf]) - else: - return util.find_descriptor(cfg, bInterfaceNumber=intf) + endpoint_address = endpoint + + intf, ep = self.get_interface_and_endpoint(device, endpoint_address) + self.managed_claim_interface(device, intf) + return (intf, ep) + + # Find the interface and endpoint objects which endpoint address belongs to + def get_interface_and_endpoint(self, device, endpoint_address): + try: + return self._ep_info[endpoint_address] + except KeyError: + for intf in self.get_active_configuration(device): + ep = util.find_descriptor(intf, bEndpointAddress=endpoint_address) + if ep is not None: + self._ep_info[endpoint_address] = (intf, ep) + return intf, ep + + raise ValueError('Invalid endpoint address ' + hex(endpoint_address)) + def get_active_configuration(self, device): - # TODO: when we haven't called managed_set_configuration, - # issue a get_configuration request to discover the current configuration - # See patch #283765. - # Meanwhile, we just return the first configuration found if self._active_cfg_index is None: - cfg = device[0] + self.managed_open() + cfg = util.find_descriptor( + device, + bConfigurationValue=self.backend.get_configuration(self.handle) + ) + if cfg is None: + raise USBError('Configuration not set') self._active_cfg_index = cfg.index return cfg return device[self._active_cfg_index] - def get_endpoint_type(self, device, address, intf): - intf = self.get_interface(device, intf) - key = (address, intf.bInterfaceNumber, intf.bAlternateSetting) - try: - return self._ep_type_map[key] - except KeyError: - e = util.find_descriptor(intf, bEndpointAddress=address) - type = util.endpoint_type(e.bmAttributes) - self._ep_type_map[key] = type - return type + def release_all_interfaces(self, device): claimed = copy.copy(self._claimed_intf) for i in claimed: self.managed_release_interface(device, i) + def dispose(self, device, close_handle = True): self.release_all_interfaces(device) if close_handle: self.managed_close() - self._ep_type_map.clear() - self._alt_set.clear() + self._ep_info.clear() self._active_cfg_index = None + class USBError(IOError): r"""Exception class for USB errors. - - Backends must raise this exception when USB related errors occur. + + Backends must raise this exception when USB related errors occur. The + backend specific error code is available through the 'backend_error_code' + member variable. """ - pass + + def __init__(self, strerror, error_code = None, errno = None): + r"""Initialize the object. + + This initializes the USBError object. The strerror and errno are passed + to the parent object. The error_code parameter is attributed to the + backend_error_code member variable. + """ + + IOError.__init__(self, errno, strerror) + self.backend_error_code = error_code + class Endpoint(object): r"""Represent an endpoint object. - This class contains all fields of the Endpoint Descriptor - according to the USB Specification. You may access them as class - properties. For example, to access the field bEndpointAddress - of the endpoint descriptor: + This class contains all fields of the Endpoint Descriptor according to the + USB Specification. You can access them as class properties. For example, to + access the field bEndpointAddress of the endpoint descriptor, you can do so: >>> import usb.core >>> dev = usb.core.find() @@ -206,19 +269,18 @@ r"""Initialize the Endpoint object. The device parameter is the device object returned by the find() - function. endpoint is the endpoint logical index (not the endpoint address). - The configuration parameter is the logical index of the + function. endpoint is the endpoint logical index (not the endpoint + address). The configuration parameter is the logical index of the configuration (not the bConfigurationValue field). The interface - parameter is the interface logical index (not the bInterfaceNumber field) - and alternate_setting is the alternate setting logical index (not the - bAlternateSetting value). Not every interface has more than one alternate - setting. In this case, the alternate_setting parameter should be zero. - By "logical index" we mean the relative order of the configurations returned by the - peripheral as a result of GET_DESCRIPTOR request. + parameter is the interface logical index (not the bInterfaceNumber + field) and alternate_setting is the alternate setting logical index + (not the bAlternateSetting value). An interface may have only one + alternate setting. In this case, the alternate_setting parameter + should be zero. By "logical index" we mean the relative order of the + configurations returned by the peripheral as a result of GET_DESCRIPTOR + request. """ self.device = device - intf = Interface(device, interface, alternate_setting, configuration) - self.interface = intf.bInterfaceNumber self.index = endpoint backend = device._ctx.backend @@ -242,13 +304,40 @@ 'wMaxPacketSize', 'bInterval', 'bRefresh', - 'bSynchAddress' + 'bSynchAddress', + 'extra_descriptors' ) ) + def __repr__(self): + return "<" + self._str() + ">" + + def __str__(self): + headstr = " " + self._str() + " " + + if util.endpoint_direction(self.bEndpointAddress) == util.ENDPOINT_IN: + direction = "IN" + else: + direction = "OUT" + + return "%s%s\n" % (headstr, "=" * (60 - len(headstr))) + \ + " %-17s:%#7x (7 bytes)\n" % ( + "bLength", self.bLength) + \ + " %-17s:%#7x %s\n" % ( + "bDescriptorType", self.bDescriptorType, + _try_lookup(_lu.descriptors, self.bDescriptorType)) + \ + " %-17s:%#7x %s\n" % ( + "bEndpointAddress", self.bEndpointAddress, direction) + \ + " %-17s:%#7x %s\n" % ( + "bmAttributes", self.bmAttributes, + _lu.ep_attributes[(self.bmAttributes & 0x3)]) + \ + " %-17s:%#7x (%d bytes)\n" % ( + "wMaxPacketSize", self.wMaxPacketSize, self.wMaxPacketSize) + \ + " %-17s:%#7x" % ("bInterval", self.bInterval) + def write(self, data, timeout = None): r"""Write data to the endpoint. - + The parameter data contains the data to be sent to the endpoint and timeout is the time limit of the operation. The transfer type and endpoint address are automatically inferred. @@ -257,28 +346,45 @@ For details, see the Device.write() method. """ - return self.device.write(self.bEndpointAddress, data, self.interface, timeout) + return self.device.write(self, data, timeout) - def read(self, size, timeout = None): + def read(self, size_or_buffer, timeout = None): r"""Read data from the endpoint. - - The parameter size is the number of bytes to read and timeout is the - time limit of the operation.The transfer type and endpoint address + + The parameter size_or_buffer is either the number of bytes to + read or an array object where the data will be put in and timeout is the + time limit of the operation. The transfer type and endpoint address are automatically inferred. - The method returns an array.array object with the data read. + The method returns either an array object or the number of bytes + actually read. For details, see the Device.read() method. """ - return self.device.read(self.bEndpointAddress, size, self.interface, timeout) + return self.device.read(self, size_or_buffer, timeout) + + def clear_halt(self): + r"""Clear the halt/status condition of the endpoint.""" + self.device.clear_halt(self.bEndpointAddress) + + def _str(self): + if util.endpoint_direction(self.bEndpointAddress) == util.ENDPOINT_IN: + direction = "IN" + else: + direction = "OUT" + + return ( + "ENDPOINT 0x%X: %s %s" % (self.bEndpointAddress, + _lu.ep_attributes[(self.bmAttributes & 0x3)], + direction)) class Interface(object): r"""Represent an interface object. This class contains all fields of the Interface Descriptor according to the USB Specification. You may access them as class - properties. For example, to access the field bInterfaceNumber - of the interface descriptor: + properties. For example, to access the field bInterfaceNumber + of the interface descriptor, you can do so: >>> import usb.core >>> dev = usb.core.find() @@ -294,12 +400,13 @@ The device parameter is the device object returned by the find() function. The configuration parameter is the logical index of the configuration (not the bConfigurationValue field). The interface - parameter is the interface logical index (not the bInterfaceNumber field) - and alternate_setting is the alternate setting logical index (not the - bAlternateSetting value). Not every interface has more than one alternate - setting. In this case, the alternate_setting parameter should be zero. - By "logical index" we mean the relative order of the configurations returned by the - peripheral as a result of GET_DESCRIPTOR request. + parameter is the interface logical index (not the bInterfaceNumber + field) and alternate_setting is the alternate setting logical index + (not the bAlternateSetting value). An interface may have only one + alternate setting. In this case, the alternate_setting parameter + should be zero. By "logical index" we mean the relative order of + the configurations returned by the peripheral as a result of + GET_DESCRIPTOR request. """ self.device = device self.alternate_index = alternate_setting @@ -327,16 +434,31 @@ 'bInterfaceClass', 'bInterfaceSubClass', 'bInterfaceProtocol', - 'iInterface' + 'iInterface', + 'extra_descriptors' ) ) + def __repr__(self): + return "<" + self._str() + ">" + + def __str__(self): + """Show all information for the interface.""" + + string = self._get_full_descriptor_str() + for endpoint in self: + string += "\n" + str(endpoint) + return string + + def endpoints(self): + r"""Return a tuple of the interface endpoints.""" + return tuple(self) + def set_altsetting(self): r"""Set the interface alternate setting.""" self.device.set_interface_altsetting( self.bInterfaceNumber, - self.bAlternateSetting - ) + self.bAlternateSetting) def __iter__(self): r"""Iterate over all endpoints of the interface.""" @@ -346,8 +468,8 @@ i, self.index, self.alternate_index, - self.configuration - ) + self.configuration) + def __getitem__(self, index): r"""Return the Endpoint object in the given position.""" return Endpoint( @@ -355,16 +477,51 @@ index, self.index, self.alternate_index, - self.configuration - ) + self.configuration) + + def _str(self): + if self.bAlternateSetting: + alt_setting = ", %d" % self.bAlternateSetting + else: + alt_setting = "" + + return "INTERFACE %d%s: %s" % (self.bInterfaceNumber, alt_setting, + _try_lookup(_lu.interface_classes, self.bInterfaceClass, + default = "Unknown Class")) + + def _get_full_descriptor_str(self): + headstr = " " + self._str() + " " + return "%s%s\n" % (headstr, "=" * (60 - len(headstr))) + \ + " %-19s:%#7x (9 bytes)\n" % ( + "bLength", self.bLength) + \ + " %-19s:%#7x %s\n" % ( + "bDescriptorType", self.bDescriptorType, + _try_lookup(_lu.descriptors, self.bDescriptorType)) + \ + " %-19s:%#7x\n" % ( + "bInterfaceNumber", self.bInterfaceNumber) + \ + " %-19s:%#7x\n" % ( + "bAlternateSetting", self.bAlternateSetting) + \ + " %-19s:%#7x\n" % ( + "bNumEndpoints", self.bNumEndpoints) + \ + " %-19s:%#7x %s\n" % ( + "bInterfaceClass", self.bInterfaceClass, + _try_lookup(_lu.interface_classes, self.bInterfaceClass)) + \ + " %-19s:%#7x\n" % ( + "bInterfaceSubClass", self.bInterfaceSubClass) + \ + " %-19s:%#7x\n" % ( + "bInterfaceProtocol", self.bInterfaceProtocol) + \ + " %-19s:%#7x %s" % ( + "iInterface", self.iInterface, + _try_get_string(self.device, self.iInterface)) + class Configuration(object): r"""Represent a configuration object. - - This class contains all fields of the Configuration Descriptor - according to the USB Specification. You may access them as class - properties. For example, to access the field bConfigurationValue - of the configuration descriptor: + + This class contains all fields of the Configuration Descriptor according to + the USB Specification. You may access them as class properties. For + example, to access the field bConfigurationValue of the configuration + descriptor, you can do so: >>> import usb.core >>> dev = usb.core.find() @@ -402,10 +559,24 @@ 'bConfigurationValue', 'iConfiguration', 'bmAttributes', - 'bMaxPower' + 'bMaxPower', + 'extra_descriptors' ) ) + def __repr__(self): + return "<" + self._str() + ">" + + def __str__(self): + string = self._get_full_descriptor_str() + for interface in self: + string += "\n%s" % str(interface) + return string + + def interfaces(self): + r"""Return a tuple of the configuration interfaces.""" + return tuple(self) + def set(self): r"""Set this configuration as the active one.""" self.device.set_configuration(self.bConfigurationValue) @@ -420,6 +591,7 @@ alt += 1 except (USBError, IndexError): pass + def __getitem__(self, index): r"""Return the Interface object in the given position. @@ -431,42 +603,103 @@ return Interface(self.device, index[0], index[1], self.index) + def _str(self): + return "CONFIGURATION %d: %d mA" % ( + self.bConfigurationValue, + _lu.MAX_POWER_UNITS_USB2p0 * self.bMaxPower) + + def _get_full_descriptor_str(self): + headstr = " " + self._str() + " " + if self.bmAttributes & (1<<6): + powered = "Self" + else: + powered = "Bus" + + if self.bmAttributes & (1<<5): + remote_wakeup = ", Remote Wakeup" + else: + remote_wakeup = "" + + return "%s%s\n" % (headstr, "=" * (60 - len(headstr))) + \ + " %-21s:%#7x (9 bytes)\n" % ( + "bLength", self.bLength) + \ + " %-21s:%#7x %s\n" % ( + "bDescriptorType", self.bDescriptorType, + _try_lookup(_lu.descriptors, self.bDescriptorType)) + \ + " %-21s:%#7x (%d bytes)\n" % ( + "wTotalLength", self.wTotalLength, self.wTotalLength) + \ + " %-21s:%#7x\n" % ( + "bNumInterfaces", self.bNumInterfaces) + \ + " %-21s:%#7x\n" % ( + "bConfigurationValue", self.bConfigurationValue) + \ + " %-21s:%#7x %s\n" % ( + "iConfiguration", self.iConfiguration, + _try_get_string(self.device, self.iConfiguration)) + \ + " %-21s:%#7x %s Powered%s\n" % ( + "bmAttributes", self.bmAttributes, powered, remote_wakeup + # bit 7 is high, bit 4..0 are 0 + ) + \ + " %-21s:%#7x (%d mA)" % ( + "bMaxPower", self.bMaxPower, + _lu.MAX_POWER_UNITS_USB2p0 * self.bMaxPower) + # FIXME : add a check for superspeed vs usb 2.0 + class Device(object): r"""Device object. - - This class contains all fields of the Device Descriptor according - to the USB Specification. You may access them as class properties. - For example, to access the field bDescriptorType of the device - descriptor: + + This class contains all fields of the Device Descriptor according to the + USB Specification. You may access them as class properties. For example, + to access the field bDescriptorType of the device descriptor, you can + do so: >>> import usb.core >>> dev = usb.core.find() >>> dev.bDescriptorType - Additionally, the class provides methods to communicate with - the hardware. Typically, an application will first call the - set_configuration() method to put the device in a known configured - state, optionally call the set_interface_altsetting() to select the - alternate setting (if there is more than one) of the interface used, - and call the write() and read() method to send and receive data. + Additionally, the class provides methods to communicate with the hardware. + Typically, an application will first call the set_configuration() method to + put the device in a known configured state, optionally call the + set_interface_altsetting() to select the alternate setting (if there is + more than one) of the interface used, and call the write() and read() + methods to send and receive data, respectively. - When working in a new hardware, one first try would be like this: + When working in a new hardware, the first try could be like this: >>> import usb.core >>> dev = usb.core.find(idVendor=myVendorId, idProduct=myProductId) >>> dev.set_configuration() - >>> dev.write(1, 'teste') + >>> dev.write(1, 'test') - This sample finds the device of interest (myVendorId and myProductId should be - replaced by the corresponding values of your device), then configures the device - (by default, the configuration value is 1, which is a typical value for most - devices) and then writes some data to the endpoint 0x01. - - Timeout values for the write, read and ctrl_transfer methods are specified in - miliseconds. If the parameter is omitted, Device.default_timeout value will - be used instead. This property can be set by the user at anytime. + This sample finds the device of interest (myVendorId and myProductId should + be replaced by the corresponding values of your device), then configures + the device (by default, the configuration value is 1, which is a typical + value for most devices) and then writes some data to the endpoint 0x01. + + Timeout values for the write, read and ctrl_transfer methods are specified + in miliseconds. If the parameter is omitted, Device.default_timeout value + will be used instead. This property can be set by the user at anytime. """ + def __repr__(self): + return "<" + self._str() + ">" + + def __str__(self): + string = self._get_full_descriptor_str() + try: + for configuration in self: + string += "\n%s" % str(configuration) + except USBError: + try: + configuration = self.get_active_configuration() + string += "\n%s" % (configuration.info) + except USBError: + string += " USBError Accessing Configurations" + return string + + def configurations(self): + r"""Return a tuple of the device configurations.""" + return tuple(self) + def __init__(self, dev, backend): r"""Initialize the Device object. @@ -476,8 +709,9 @@ of it. The backend parameter is a instance of a backend object. """ - self._ctx = _ResourceManager(self, dev, backend) + self._ctx = _ResourceManager(dev, backend) self.__default_timeout = _DEFAULT_TIMEOUT + self._serial_number, self._product, self._manufacturer = None, None, None desc = backend.get_device_descriptor(dev) @@ -498,43 +732,97 @@ 'iManufacturer', 'iProduct', 'iSerialNumber', - 'bNumConfigurations' + 'bNumConfigurations', + 'address', + 'bus', + 'port_number' ) ) + if desc.bus is not None: + self.bus = int(desc.bus) + else: + self.bus = None + + if desc.address is not None: + self.address = int(desc.address) + else: + self.address = None + + if desc.port_number is not None: + self.port_number = int(desc.port_number) + else: + self.port_number = None + + @property + def serial_number(self): + """ Return the USB device's serial number string descriptor. + + This property will cause some USB traffic the first time it is accessed + and cache the resulting value for future use. + """ + if self._serial_number is None: + self._serial_number = util.get_string(self, self.iSerialNumber) + return self._serial_number + + @property + def product(self): + """ Return the USB device's product string descriptor. + + This property will cause some USB traffic the first time it is accessed + and cache the resulting value for future use. + """ + if self._product is None: + self._product = util.get_string(self, self.iProduct) + return self._product + + @property + def manufacturer(self): + """ Return the USB device's manufacturer string descriptor. + + This property will cause some USB traffic the first time it is accessed + and cache the resulting value for future use. + """ + if self._manufacturer is None: + self._manufacturer = util.get_string(self, self.iManufacturer) + return self._manufacturer + def set_configuration(self, configuration = None): r"""Set the active configuration. - + The configuration parameter is the bConfigurationValue field of the configuration you want to set as active. If you call this method - without parameter, it will use the first configuration found. - As a device hardly ever has more than one configuration, calling - the method without parameter is enough to get the device ready. + without parameter, it will use the first configuration found. As a + device hardly ever has more than one configuration, calling the method + without arguments is enough to get the device ready. """ self._ctx.managed_set_configuration(self, configuration) def get_active_configuration(self): - r"""Return a Configuration object representing the current configuration set.""" + r"""Return a Configuration object representing the current + configuration set. + """ return self._ctx.get_active_configuration(self) def set_interface_altsetting(self, interface = None, alternate_setting = None): r"""Set the alternate setting for an interface. - - When you want to use an interface and it has more than one alternate setting, - you should call this method to select the alternate setting you would like - to use. If you call the method without one or the two parameters, it will - be selected the first one found in the Device in the same way of set_configuration - method. + + When you want to use an interface and it has more than one alternate + setting, you should call this method to select the appropriate + alternate setting. If you call the method without one or the two + parameters, it will be selected the first one found in the Device in + the same way of the set_configuration method. Commonly, an interface has only one alternate setting and this call is - not necessary. For most of the devices, either it has more than one alternate - setting or not, it is not harmful to make a call to this method with no arguments, - as devices will silently ignore the request when there is only one alternate - setting, though the USB Spec allows devices with no additional alternate setting - return an error to the Host in response to a SET_INTERFACE request. + not necessary. For most devices, either it has more than one + alternate setting or not, it is not harmful to make a call to this + method with no arguments, as devices will silently ignore the request + when there is only one alternate setting, though the USB Spec allows + devices with no additional alternate setting return an error to the + Host in response to a SET_INTERFACE request. - If you are in doubt, you may want to call it with no arguments wrapped by - a try/except clause: + If you are in doubt, you may want to call it with no arguments wrapped + by a try/except clause: >>> try: >>> dev.set_interface_altsetting() @@ -543,27 +831,29 @@ """ self._ctx.managed_set_interface(self, interface, alternate_setting) - def get_interface_altsetting(self, interface = None): - r"""Get the active alternate setting of the given interface.""" - return self._ctx.get_interface(self, interface) + def clear_halt(self, ep): + r""" Clear the halt/stall condition for the endpoint ep.""" + if isinstance(ep, Endpoint): + ep = ep.bEndpointAddress + self._ctx.managed_open() + self._ctx.backend.clear_halt(self._ctx.handle, ep) def reset(self): r"""Reset the device.""" + self._ctx.managed_open() self._ctx.dispose(self, False) self._ctx.backend.reset_device(self._ctx.handle) + self._ctx.dispose(self, True) - def write(self, endpoint, data, interface = None, timeout = None): + def write(self, endpoint, data, timeout = None): r"""Write data to the endpoint. This method is used to send data to the device. The endpoint parameter corresponds to the bEndpointAddress member whose endpoint you want to - communicate with. The interface parameter is the bInterfaceNumber field - of the interface descriptor which contains the endpoint. If you do not - provide one, the first one found will be used, as explained in the - set_interface_altsetting() method. + communicate with. The data parameter should be a sequence like type convertible to - array type (see array module). + the array type (see array module). The timeout is specified in miliseconds. @@ -577,32 +867,32 @@ util.ENDPOINT_TYPE_ISO:backend.iso_write } - intf = self._ctx.get_interface(self, interface) - fn = fn_map[self._ctx.get_endpoint_type(self, endpoint, intf)] - self._ctx.managed_claim_interface(self, intf) + intf, ep = self._ctx.setup_request(self, endpoint) + fn = fn_map[util.endpoint_type(ep.bmAttributes)] return fn( self._ctx.handle, - endpoint, + ep.bEndpointAddress, intf.bInterfaceNumber, - array.array('B', data), + _interop.as_array(data), self.__get_timeout(timeout) ) - def read(self, endpoint, size, interface = None, timeout = None): + def read(self, endpoint, size_or_buffer, timeout = None): r"""Read data from the endpoint. - This method is used to receive data from the device. The endpoint parameter - corresponds to the bEndpointAddress member whose endpoint you want to - communicate with. The interface parameter is the bInterfaceNumber field - of the interface descriptor which contains the endpoint. If you do not - provide one, the first one found will be used, as explained in the - set_interface_altsetting() method. The size parameters tells how many - bytes you want to read. + This method is used to receive data from the device. The endpoint + parameter corresponds to the bEndpointAddress member whose endpoint + you want to communicate with. The size_or_buffer parameter either + tells how many bytes you want to read or supplies the buffer to + receive the data (it *must* be an object of the type array). The timeout is specified in miliseconds. - The method returns an array object with the data read. + If the size_or_buffer parameter is the number of bytes to read, the + method returns an array object with the data read. If the + size_or_buffer parameter is an array object, it returns the number + of bytes actually read. """ backend = self._ctx.backend @@ -612,83 +902,119 @@ util.ENDPOINT_TYPE_ISO:backend.iso_read } - intf = self._ctx.get_interface(self, interface) - fn = fn_map[self._ctx.get_endpoint_type(self, endpoint, intf)] - self._ctx.managed_claim_interface(self, intf) + intf, ep = self._ctx.setup_request(self, endpoint) + fn = fn_map[util.endpoint_type(ep.bmAttributes)] - return fn( + if isinstance(size_or_buffer, array.array): + buff = size_or_buffer + else: # here we consider it is a integer + buff = util.create_buffer(size_or_buffer) + + ret = fn( self._ctx.handle, - endpoint, + ep.bEndpointAddress, intf.bInterfaceNumber, - size, - self.__get_timeout(timeout) - ) + buff, + self.__get_timeout(timeout)) + if isinstance(size_or_buffer, array.array): + return ret + elif ret != len(buff) * buff.itemsize: + return buff[:ret] + else: + return buff def ctrl_transfer(self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength = None, timeout = None): r"""Do a control transfer on the endpoint 0. - This method is used to issue a control transfer over the - endpoint 0(endpoint 0 is required to always be a control endpoint). + This method is used to issue a control transfer over the endpoint 0 + (endpoint 0 is required to always be a control endpoint). - The parameters bmRequestType, bRequest, wValue and wIndex are the - same of the USB Standard Control Request format. + The parameters bmRequestType, bRequest, wValue and wIndex are the same + of the USB Standard Control Request format. Control requests may or may not have a data payload to write/read. In cases which it has, the direction bit of the bmRequestType field is used to infere the desired request direction. For host to device requests (OUT), data_or_wLength parameter is the data payload to send, and it must be a sequence type convertible - to an array object. In this case, the return value is the number of data - payload written. For device to host requests (IN), data_or_wLength - is the wLength parameter of the control request specifying the - number of bytes to read in data payload. In this case, the return - value is the data payload read, as an array object. - """ - if util.ctrl_direction(bmRequestType) == util.CTRL_OUT: - if data_or_wLength is None: - a = array.array('B') - else: - a = array.array('B', data_or_wLength) - elif data_or_wLength is None: - a = 0 - else: - a = data_or_wLength + to an array object. In this case, the return value is the number + of bytes written in the data payload. For device to host requests + (IN), data_or_wLength is either the wLength parameter of the control + request specifying the number of bytes to read in data payload, and + the return value is an array object with data read, or an array + object which the data will be read to, and the return value is the + number of bytes read. + """ + try: + buff = util.create_buffer(data_or_wLength) + except TypeError: + buff = _interop.as_array(data_or_wLength) self._ctx.managed_open() - return self._ctx.backend.ctrl_transfer( + # Thanks to Johannes Stezenbach to point me out that we need to + # claim the recipient interface + recipient = bmRequestType & 3 + if recipient == util.CTRL_RECIPIENT_INTERFACE: + interface_number = wIndex & 0xff + self._ctx.managed_claim_interface(self, interface_number) + + ret = self._ctx.backend.ctrl_transfer( self._ctx.handle, bmRequestType, bRequest, wValue, wIndex, - a, - self.__get_timeout(timeout) - ) + buff, + self.__get_timeout(timeout)) + + if isinstance(data_or_wLength, array.array) \ + or util.ctrl_direction(bmRequestType) == util.CTRL_OUT: + return ret + elif ret != len(buff) * buff.itemsize: + return buff[:ret] + else: + return buff def is_kernel_driver_active(self, interface): r"""Determine if there is kernel driver associated with the interface. - If a kernel driver is active, and the object will be unable to perform I/O. + If a kernel driver is active, the object will be unable to perform + I/O. + + The interface parameter is the device interface number to check. """ self._ctx.managed_open() - return self._ctx.backend.is_kernel_driver_active(self._ctx.handle, interface) + return self._ctx.backend.is_kernel_driver_active( + self._ctx.handle, + interface) def detach_kernel_driver(self, interface): r"""Detach a kernel driver. If successful, you will then be able to perform I/O. + + The interface parameter is the device interface number to detach the + driver from. """ self._ctx.managed_open() - self._ctx.backend.detach_kernel_driver(self._ctx.handle, interface) + self._ctx.backend.detach_kernel_driver( + self._ctx.handle, + interface) def attach_kernel_driver(self, interface): r"""Re-attach an interface's kernel driver, which was previously - detached using detach_kernel_driver().""" + detached using detach_kernel_driver(). + + The interface parameter is the device interface number to attach the + driver to. + """ self._ctx.managed_open() - self._ctx.backend.attach_kernel_driver(self._ctx.handle, interface) + self._ctx.backend.attach_kernel_driver( + self._ctx.handle, + interface) def __iter__(self): r"""Iterate over all configurations of the device.""" @@ -715,37 +1041,91 @@ def __get_def_tmo(self): return self.__default_timeout + def _str(self): + return "DEVICE ID %04x:%04x on Bus %03d Address %03d" % ( + self.idVendor, self.idProduct, self.bus, self.address) + + def _get_full_descriptor_str(self): + headstr = self._str() + " " + + if self.bcdUSB & 0xf: + low_bcd_usb = str(self.bcdUSB & 0xf) + else: + low_bcd_usb = "" + + if self.bcdDevice & 0xf: + low_bcd_device = str(self.bcdDevice & 0xf) + else: + low_bcd_device = "" + + return "%s%s\n" % (headstr, "=" * (60 - len(headstr))) + \ + " %-23s:%#7x (18 bytes)\n" % ( + "bLength", self.bLength) + \ + " %-23s:%#7x %s\n" % ( + "bDescriptorType", self.bDescriptorType, + _try_lookup(_lu.descriptors, self.bDescriptorType)) + \ + " %-23s:%#7x USB %d.%d%s\n" % ( + "bcdUSB", self.bcdUSB, (self.bcdUSB & 0xff00)>>8, + (self.bcdUSB & 0xf0) >> 4, low_bcd_usb) + \ + " %-23s:%#7x %s\n" % ( + "bDeviceClass", self.bDeviceClass, + _try_lookup(_lu.device_classes, self.bDeviceClass)) + \ + " %-23s:%#7x\n" % ( + "bDeviceSubClass", self.bDeviceSubClass) + \ + " %-23s:%#7x\n" % ( + "bDeviceProtocol", self.bDeviceProtocol) + \ + " %-23s:%#7x (%d bytes)\n" % ( + "bMaxPacketSize0", self.bMaxPacketSize0, self.bMaxPacketSize0) + \ + " %-23s: %#06x\n" % ( + "idVendor", self.idVendor) + \ + " %-23s: %#06x\n" % ( + "idProduct", self.idProduct) + \ + " %-23s:%#7x Device %d.%d%s\n" % ( + "bcdDevice", self.bcdDevice, (self.bcdDevice & 0xff00)>>8, + (self.bcdDevice & 0xf0) >> 4, low_bcd_device) + \ + " %-23s:%#7x %s\n" % ( + "iManufacturer", self.iManufacturer, + _try_get_string(self, self.iManufacturer)) + \ + " %-23s:%#7x %s\n" % ( + "iProduct", self.iProduct, + _try_get_string(self, self.iProduct)) + \ + " %-23s:%#7x %s\n" % ( + "iSerialNumber", self.iSerialNumber, + _try_get_string(self, self.iSerialNumber)) + \ + " %-23s:%#7x" % ( + "bNumConfigurations", self.bNumConfigurations) + default_timeout = property( __get_def_tmo, __set_def_tmo, doc = 'Default timeout for transfer I/O functions' ) + def find(find_all=False, backend = None, custom_match = None, **args): r"""Find an USB device and return it. - find() is the function used to discover USB devices. - You can pass as arguments any combination of the - USB Device Descriptor fields to match a device. For example: + find() is the function used to discover USB devices. You can pass as + arguments any combination of the USB Device Descriptor fields to match a + device. For example: find(idVendor=0x3f4, idProduct=0x2009) - will return the Device object for the device with - idVendor Device descriptor field equals to 0x3f4 and - idProduct equals to 0x2009. - - If there is more than one device which matchs the criteria, - the first one found will be returned. If a matching device cannot - be found the function returns None. If you want to get all - devices, you can set the parameter find_all to True, then find - will return an list with all matched devices. If no matching device - is found, it will return an empty list. Example: - - printers = find(find_all=True, bDeviceClass=7) - - This call will get all the USB printers connected to the system. - (actually may be not, because some devices put their class - information in the Interface Descriptor). + will return the Device object for the device with idVendor field equals + to 0x3f4 and idProduct equals to 0x2009. + + If there is more than one device which matchs the criteria, the first one + found will be returned. If a matching device cannot be found the function + returns None. If you want to get all devices, you can set the parameter + find_all to True, then find will return an iterator with all matched devices. + If no matching device is found, it will return an empty iterator. Example: + + for printer in find(find_all=True, bDeviceClass=7): + print (printer) + + This call will get all the USB printers connected to the system. (actually + may be not, because some devices put their class information in the + Interface Descriptor). You can also use a customized match criteria: @@ -759,33 +1139,34 @@ if dev.bDeviceClass == 7: return True for cfg in dev: - if util.find_descriptor(cfg, bInterfaceClass=7) is not None: + if usb.util.find_descriptor(cfg, bInterfaceClass=7) is not None: return True - printers = find(find_all=True, custom_match = is_printer) + for printer in find(find_all=True, custom_match = is_printer): + print (printer) Now even if the device class code is in the interface descriptor the printer will be found. You can combine a customized match with device descriptor fields. In this - case, the fields must match and the custom_match must return True. In the our - previous example, if we would like to get all printers belonging to the + case, the fields must match and the custom_match must return True. In the + our previous example, if we would like to get all printers belonging to the manufacturer 0x3f4, the code would be like so: - printers = find(find_all=True, idVendor=0x3f4, custom_match=is_printer) + printers = list(find(find_all=True, idVendor=0x3f4, custom_match=is_printer)) If you want to use find as a 'list all devices' function, just call it with find_all = True: - devices = find(find_all=True) + devices = list(find(find_all=True)) - Finally, you may pass a custom backend to the find function: + Finally, you can pass a custom backend to the find function: find(backend = MyBackend()) - PyUSB has builtin backends for libusb 0.1, libusb 1.0 and OpenUSB. - If you do not supply a backend explicitly, find() function will select - one of the predefineds backends according to system availability. + PyUSB has builtin backends for libusb 0.1, libusb 1.0 and OpenUSB. If you + do not supply a backend explicitly, find() function will select one of the + predefineds backends according to system availability. Backends are explained in the usb.backend module. """ @@ -793,8 +1174,7 @@ def device_iter(k, v): for dev in backend.enumerate_devices(): d = Device(dev, backend) - if (custom_match is None or custom_match(d)) and \ - _interop._reduce( + if _interop._reduce( lambda a, b: a and b, map( operator.eq, @@ -802,15 +1182,15 @@ map(lambda i: getattr(d, i), k) ), True - ): + ) and (custom_match is None or custom_match(d)): yield d if backend is None: - import usb.backend.libusb10 as libusb10 - import usb.backend.libusb01 as libusb01 + import usb.backend.libusb1 as libusb1 + import usb.backend.libusb0 as libusb0 import usb.backend.openusb as openusb - for m in (libusb10, openusb, libusb01): + for m in (libusb1, openusb, libusb0): backend = m.get_backend() if backend is not None: _logger.info('find(): using backend "%s"', m.__name__) @@ -819,11 +1199,29 @@ raise ValueError('No backend available') k, v = args.keys(), args.values() - + if find_all: - return [d for d in device_iter(k, v)] + return device_iter(k, v) else: try: return _interop._next(device_iter(k, v)) except StopIteration: return None + +def show_devices(verbose=False, **kwargs): + """Show information about connected devices. + + The verbose flag sets to verbose or not. + **kwargs are passed directly to the find() function. + """ + kwargs["find_all"] = True + devices = find(**kwargs) + strings = "" + for device in devices: + if not verbose: + strings += "%s, %s\n" % (device._str(), _try_lookup( + _lu.device_classes, device.bDeviceClass)) + else: + strings += "%s\n\n" % str(device) + + return _DescriptorInfo(strings) Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/core.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/core.pyc differ diff -Nru quisk-3.6.18/usb/_debug.py quisk-3.7.6/usb/_debug.py --- quisk-3.6.18/usb/_debug.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/_debug.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -31,9 +31,14 @@ __all__ = ['methodtrace', 'functiontrace'] import logging -import types import usb._interop as _interop +_enable_tracing = False + +def enable_tracing(enable): + global _enable_tracing + _enable_tracing = enable + def _trace_function_call(logger, fname, *args, **named_args): logger.debug( # TODO: check if 'f' is a method or a free function @@ -45,6 +50,8 @@ # decorator for methods calls tracing def methodtrace(logger): def decorator_logging(f): + if not _enable_tracing: + return f def do_trace(*args, **named_args): # this if is just a optimization to avoid unecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): @@ -58,10 +65,12 @@ # decorator for methods calls tracing def functiontrace(logger): def decorator_logging(f): + if not _enable_tracing: + return f def do_trace(*args, **named_args): # this if is just a optimization to avoid unecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): - _trace_function_call(logger, f.__name__, *uargs, **named_args) + _trace_function_call(logger, f.__name__, *args, **named_args) return f(*args, **named_args) _interop._update_wrapper(do_trace, f) return do_trace Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/_debug.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/_debug.pyc differ diff -Nru quisk-3.6.18/usb/__init__.py quisk-3.7.6/usb/__init__.py --- quisk-3.6.18/usb/__init__.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/__init__.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -33,6 +33,8 @@ core - the main USB implementation legacy - the compatibility layer with 0.x version backend - the support for backend implementations. + control - USB standard control requests. + libloader - helper module for backend library loading. Since version 1.0, main PyUSB implementation lives in the 'usb.core' module. New applications are encouraged to use it. @@ -43,14 +45,19 @@ __author__ = 'Wander Lairson Costa' -__all__ = ['legacy', 'core', 'backend', 'util'] +# Use Semantic Versioning, http://semver.org/ +version_info = (1, 0, 0, 'b2') +__version__ = '%d.%d.%d%s' % version_info +__all__ = ['legacy', 'control', 'core', 'backend', 'util', 'libloader'] def _setup_log(): + from usb import _debug logger = logging.getLogger('usb') - debug_level = os.getenv('PYUSB_DEBUG_LEVEL') + debug_level = os.getenv('PYUSB_DEBUG') if debug_level is not None: + _debug.enable_tracing(True) filename = os.getenv('PYUSB_LOG_FILENAME') LEVELS = {'debug': logging.DEBUG, @@ -75,11 +82,16 @@ def emit(self, record): pass + # We set the log level to avoid delegation to the + # parent log handler (if there is one). + # Thanks to Chris Clark to pointing this out. + logger.setLevel(logging.CRITICAL + 10) + logger.addHandler(NullHandler()) _setup_log() -# We import all 'legacy' module symbols to provide compatility +# We import all 'legacy' module symbols to provide compatibility # with applications that use 0.x versions. from usb.legacy import * Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/__init__.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/__init__.pyc differ diff -Nru quisk-3.6.18/usb/_interop.py quisk-3.7.6/usb/_interop.py --- quisk-3.6.18/usb/_interop.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/_interop.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -32,6 +32,7 @@ # hack we need to do, this makes maintenance easier... ^^ import sys +import array __all__ = ['_reduce', '_set', '_next', '_groupby', '_sorted', '_update_wrapper'] @@ -121,3 +122,20 @@ wrapper.__module__ = wrapped.__module__ wrapper.__doc__ = wrapped.__doc__ wrapper.__dict__ = wrapped.__dict__ + +def as_array(data=None): + if data is None: + return array.array('B') + + if isinstance(data, array.array): + return data + + try: + return array.array('B', data) + except TypeError: + # When you pass a unicode string or a character sequence, + # you get a TypeError if the first parameter does not match + a = array.array('B') + a.fromstring(data) + return a + Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/_interop.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/_interop.pyc differ diff -Nru quisk-3.6.18/usb/legacy.py quisk-3.7.6/usb/legacy.py --- quisk-3.6.18/usb/legacy.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/legacy.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -29,10 +29,12 @@ import usb.core as core import usb.util as util import usb._interop as _interop -from usb.core import USBError +import usb.control as control __author__ = 'Wander Lairson Costa' +USBError = core.USBError + CLASS_AUDIO = 1 CLASS_COMM = 2 CLASS_DATA = 10 @@ -114,7 +116,7 @@ r"""Configuration descriptor object.""" def __init__(self, cfg): self.iConfiguration = cfg.iConfiguration - self.maxPower = cfg.bMaxPower << 2 + self.maxPower = cfg.bMaxPower << 1 self.remoteWakeup = (cfg.bmAttributes >> 5) & 1 self.selfPowered = (cfg.bmAttributes >> 6) & 1 self.totalLength = cfg.wTotalLength @@ -131,7 +133,7 @@ class DeviceHandle(object): def __init__(self, dev): self.dev = dev - self.__claimed_interface = -1 + self.__claimed_interface = None def bulkWrite(self, endpoint, buffer, timeout = 100): r"""Perform a bulk write request to the endpoint specified. @@ -143,7 +145,7 @@ timeout: operation timeout in miliseconds. (default: 100) Returns the number of bytes written. """ - return self.dev.write(endpoint, buffer, self.__claimed_interface, timeout) + return self.dev.write(endpoint, buffer, timeout) def bulkRead(self, endpoint, size, timeout = 100): r"""Performs a bulk read request to the endpoint specified. @@ -154,7 +156,7 @@ timeout: operation timeout in miliseconds. (default: 100) Return a tuple with the data read. """ - return self.dev.read(endpoint, size, self.__claimed_interface, timeout) + return self.dev.read(endpoint, size, timeout) def interruptWrite(self, endpoint, buffer, timeout = 100): r"""Perform a interrupt write request to the endpoint specified. @@ -166,7 +168,7 @@ timeout: operation timeout in miliseconds. (default: 100) Returns the number of bytes written. """ - return self.dev.write(endpoint, buffer, self.__claimed_interface, timeout) + return self.dev.write(endpoint, buffer, timeout) def interruptRead(self, endpoint, size, timeout = 100): r"""Performs a interrupt read request to the endpoint specified. @@ -177,7 +179,7 @@ timeout: operation timeout in miliseconds. (default: 100) Return a tuple with the data read. """ - return self.dev.read(endpoint, size, self.__claimed_interface, timeout) + return self.dev.read(endpoint, size, timeout) def controlMsg(self, requestType, request, buffer, value = 0, index = 0, timeout = 100): r"""Perform a control request to the default control pipe on a device. @@ -186,7 +188,7 @@ requestType: specifies the direction of data flow, the type of request, and the recipient. request: specifies the request. - buffer: if the transfer is a write transfer, buffer is a sequence + buffer: if the transfer is a write transfer, buffer is a sequence with the transfer data, otherwise, buffer is the number of bytes to read. value: specific information to pass to the device. (default: 0) @@ -200,8 +202,7 @@ wValue = value, wIndex = index, data_or_wLength = buffer, - timeout = timeout - ) + timeout = timeout) def clearHalt(self, endpoint): r"""Clears any halt status on the specified endpoint. @@ -209,7 +210,7 @@ Arguments: endpoint: endpoint number. """ - raise NotImplemented('This function has not been implemented yet') + self.dev.clear_halt(endpoint) def claimInterface(self, interface): r"""Claims the interface with the Operating System. @@ -217,8 +218,13 @@ Arguments: interface: interface number or an Interface object. """ - util.claim_interface(self.dev, interface) - self.__claimed_interface = interface + if isinstance(interface, Interface): + if_num = interface.interfaceNumber + else: + if_num = interface + + util.claim_interface(self.dev, if_num) + self.__claimed_interface = if_num def releaseInterface(self): r"""Release an interface previously claimed with claimInterface.""" @@ -254,30 +260,29 @@ """ self.dev.set_interface_altsetting(self.__claimed_interface, alternate) - def getString(self, index, len, langid = -1): + def getString(self, index, length, langid = None): r"""Retrieve the string descriptor specified by index and langid from a device. Arguments: index: index of descriptor in the device. - len: number of bytes of the string + length: number of bytes of the string (ignored) langid: Language ID. If it is omittedi, will be used the first language. """ - raise NotImplemented('This function has not been implemented yet') + return util.get_string(self.dev, index, langid).encode('ascii') - def getDescriptor(self, type, index, len, endpoint = -1): + def getDescriptor(self, desc_type, desc_index, length, endpoint = -1): r"""Retrieves a descriptor from the device identified by the type and index of the descriptor. Arguments: - type: descriptor type. - index: index of the descriptor. + desc_type: descriptor type. + desc_index: index of the descriptor. len: descriptor length. - endpoint: endpoint number from descriptor is read. If it is - omitted, the descriptor is read from default control pipe. + endpoint: ignored. """ - raise NotImplemented('This function has not been implemented yet') + return control.get_descriptor(self.dev, length, desc_type, desc_index) def detachKernelDriver(self, interface): r"""Detach a kernel driver from the interface (if one is attached, @@ -294,7 +299,11 @@ self.deviceClass = dev.bDeviceClass self.deviceSubClass = dev.bDeviceSubClass self.deviceProtocol = dev.bDeviceProtocol - self.deviceVersion = dev.bcdDevice + self.deviceVersion = str((dev.bcdDevice >> 12) & 0xf) + \ + str((dev.bcdDevice >> 8) & 0xf) + \ + '.' + \ + str((dev.bcdDevice >> 4) & 0xf) + \ + str(dev.bcdDevice & 0xf) self.devnum = None self.filename = '' self.iManufacturer = dev.iManufacturer @@ -303,7 +312,11 @@ self.idProduct = dev.idProduct self.idVendor = dev.idVendor self.maxPacketSize = dev.bMaxPacketSize0 - self.usbVersion = dev.bcdUSB + self.usbVersion = str((dev.bcdUSB >> 12) & 0xf) + \ + str((dev.bcdUSB >> 8) & 0xf) + \ + '.' + \ + str((dev.bcdUSB >> 4) & 0xf) + \ + str(dev.bcdUSB & 0xf) self.configurations = [Configuration(c) for c in dev] self.dev = dev @@ -316,12 +329,14 @@ class Bus(object): r"""Bus object.""" - def __init__(self): + def __init__(self, devices): self.dirname = '' - self.localtion = 0 - self.devices = [Device(d) for d in core.find(find_all=True)] + self.location = 0 + self.devices = [Device(d) for d in devices] def busses(): r"""Return a tuple with the usb busses.""" - return (Bus(),) + return (Bus(g) for k, g in _interop._groupby( + _interop._sorted(core.find(find_all=True), key=lambda d: d.bus), + lambda d: d.bus)) Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/legacy.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/legacy.pyc differ diff -Nru quisk-3.6.18/usb/libloader.py quisk-3.7.6/usb/libloader.py --- quisk-3.6.18/usb/libloader.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/usb/libloader.py 2015-08-13 17:45:27.000000000 +0000 @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2014 AndrĂ© Erdmann +# +# The following terms apply to all files associated +# with the software unless explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. + +import ctypes +import ctypes.util +import logging +import sys + +__all__ = [ + 'LibaryException', + 'LibraryNotFoundException', + 'NoLibraryCandidatesException', + 'LibraryNotLoadedException', + 'LibraryMissingSymbolsException', + 'locate_library', + 'load_library', + 'load_locate_library' +] + + +_LOGGER = logging.getLogger('usb.libloader') + + +class LibaryException(OSError): + pass + +class LibraryNotFoundException(LibaryException): + pass + +class NoLibraryCandidatesException(LibraryNotFoundException): + pass + +class LibraryNotLoadedException(LibaryException): + pass + +class LibraryMissingSymbolsException(LibaryException): + pass + + +def locate_library (candidates, find_library=ctypes.util.find_library): + """Tries to locate a library listed in candidates using the given + find_library() function (or ctypes.util.find_library). + Returns the first library found, which can be the library's name + or the path to the library file, depending on find_library(). + Returns None if no library is found. + + arguments: + * candidates -- iterable with library names + * find_library -- function that takes one positional arg (candidate) + and returns a non-empty str if a library has been found. + Any "false" value (None,False,empty str) is interpreted + as "library not found". + Defaults to ctypes.util.find_library if not given or + None. + """ + if find_library is None: + find_library = ctypes.util.find_library + + use_dll_workaround = ( + sys.platform == 'win32' and find_library is ctypes.util.find_library + ) + + for candidate in candidates: + # Workaround for CPython 3.3 issue#16283 / pyusb #14 + if use_dll_workaround: + candidate += '.dll' + + libname = find_library(candidate) + if libname: + return libname + # -- end for + return None + +def load_library(lib, name=None, lib_cls=None): + """Loads a library. Catches and logs exceptions. + + Returns: the loaded library or None + + arguments: + * lib -- path to/name of the library to be loaded + * name -- the library's identifier (for logging) + Defaults to None. + * lib_cls -- library class. Defaults to None (-> ctypes.CDLL). + """ + try: + if lib_cls: + return lib_cls(lib) + else: + return ctypes.CDLL(lib) + except Exception: + if name: + lib_msg = '%s (%s)' % (name, lib) + else: + lib_msg = lib + + lib_msg += ' could not be loaded' + + if sys.platform == 'cygwin': + lib_msg += ' in cygwin' + _LOGGER.error(lib_msg, exc_info=True) + return None + +def load_locate_library(candidates, cygwin_lib, name, + win_cls=None, cygwin_cls=None, others_cls=None, + find_library=None, check_symbols=None): + """Locates and loads a library. + + Returns: the loaded library + + arguments: + * candidates -- candidates list for locate_library() + * cygwin_lib -- name of the cygwin library + * name -- lib identifier (for logging). Defaults to None. + * win_cls -- class that is used to instantiate the library on + win32 platforms. Defaults to None (-> ctypes.CDLL). + * other_cls -- library class for cygwin platforms. + Defaults to None (-> ctypes.CDLL). + * cygwin_cls -- library class for all other platforms. + Defaults to None (-> ctypes.CDLL). + * find_library -- see locate_library(). Defaults to None. + * check_symbols -- either None or a list of symbols that the loaded lib + must provide (hasattr(<>)) in order to be considered + valid. LibraryMissingSymbolsException is raised if + any symbol is missing. + + raises: + * NoLibraryCandidatesException + * LibraryNotFoundException + * LibraryNotLoadedException + * LibraryMissingSymbolsException + """ + if sys.platform == 'cygwin': + if cygwin_lib: + loaded_lib = load_library(cygwin_lib, name, cygwin_cls) + else: + raise NoLibraryCandidatesException(name) + elif candidates: + lib = locate_library(candidates, find_library) + if lib: + if sys.platform == 'win32': + loaded_lib = load_library(lib, name, win_cls) + else: + loaded_lib = load_library(lib, name, others_cls) + else: + _LOGGER.error('%r could not be found', (name or candidates)) + raise LibraryNotFoundException(name) + else: + raise NoLibraryCandidatesException(name) + + if loaded_lib is None: + raise LibraryNotLoadedException(name) + elif check_symbols: + symbols_missing = [ + s for s in check_symbols if not hasattr(loaded_lib, s) + ] + if symbols_missing: + msg = ('%r, missing symbols: %r', lib, symbols_missing ) + _LOGGER.error(msg) + raise LibraryMissingSymbolsException(lib) + else: + return loaded_lib + else: + return loaded_lib diff -Nru quisk-3.6.18/usb/_lookup.py quisk-3.7.6/usb/_lookup.py --- quisk-3.6.18/usb/_lookup.py 1970-01-01 00:00:00.000000000 +0000 +++ quisk-3.7.6/usb/_lookup.py 2015-08-13 17:45:27.000000000 +0000 @@ -0,0 +1,94 @@ +# Copyright (C) 2009-2014 Walker Inman +# +# The following terms apply to all files associated +# with the software unless explicitly disclaimed in individual files. +# +# The authors hereby grant permission to use, copy, modify, distribute, +# and license this software and its documentation for any purpose, provided +# that existing copyright notices are retained in all copies and that this +# notice is included verbatim in any distributions. No written agreement, +# license, or royalty fee is required for any of the authorized uses. +# Modifications to this software may be copyrighted by their authors +# and need not follow the licensing terms described here, provided that +# the new terms are clearly indicated on the first page of each file where +# they apply. +# +# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +# IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +# NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +# MODIFICATIONS. + +r"""usb._lookups - Lookup tables for USB +""" + +descriptors = { + 0x1 : "Device", + 0x2 : "Configuration", + 0x3 : "String", + 0x4 : "Interface", + 0x5 : "Endpoint", + 0x6 : "Device qualifier", + 0x7 : "Other speed configuration", + 0x8 : "Interface power", + 0x9 : "OTG", + 0xA : "Debug", + 0xB : "Interface association", + 0xC : "Security", + 0xD : "Key", + 0xE : "Encryption type", + 0xF : "Binary device object store (BOS)", + 0x10 : "Device capability", + 0x11 : "Wireless endpoint companion", + 0x30 : "SuperSpeed endpoint companion", + } + +device_classes = { + 0x0 : "Specified at interface", + 0x2 : "Communications Device", + 0x9 : "Hub", + 0xF : "Personal Healthcare Device", + 0xDC : "Diagnostic Device", + 0xE0 : "Wireless Controller", + 0xEF : "Miscellaneous", + 0xFF : "Vendor-specific", + } + +interface_classes = { + 0x0 : "Reserved", + 0x1 : "Audio", + 0x2 : "CDC Communication", + 0x3 : "Human Interface Device", + 0x5 : "Physical", + 0x6 : "Image", + 0x7 : "Printer", + 0x8 : "Mass Storage", + 0x9 : "Hub", + 0xA : "CDC Data", + 0xB : "Smart Card", + 0xD : "Content Security", + 0xE : "Video", + 0xF : "Personal Healthcare", + 0xDC : "Diagnostic Device", + 0xE0 : "Wireless Controller", + 0xEF : "Miscellaneous", + 0xFE : "Application Specific", + 0xFF : "Vendor Specific", + } + +ep_attributes = { + 0x0 : "Control", + 0x1 : "Isochronous", + 0x2 : "Bulk", + 0x3 : "Interrupt", + } + +MAX_POWER_UNITS_USB2p0 = 2 # mA +MAX_POWER_UNITS_USB_SUPERSPEED = 8 # mA diff -Nru quisk-3.6.18/usb/util.py quisk-3.7.6/usb/util.py --- quisk-3.6.18/usb/util.py 2011-09-09 11:12:20.000000000 +0000 +++ quisk-3.7.6/usb/util.py 2015-08-13 17:45:27.000000000 +0000 @@ -1,8 +1,8 @@ -# Copyright (C) 2009-2010 Wander Lairson Costa -# +# Copyright (C) 2009-2014 Wander Lairson Costa +# # The following terms apply to all files associated # with the software unless explicitly disclaimed in individual files. -# +# # The authors hereby grant permission to use, copy, modify, distribute, # and license this software and its documentation for any purpose, provided # that existing copyright notices are retained in all copies and that this @@ -12,13 +12,13 @@ # and need not follow the licensing terms described here, provided that # the new terms are clearly indicated on the first page of each file where # they apply. -# +# # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# +# # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE @@ -26,11 +26,26 @@ # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR # MODIFICATIONS. -r"""usb.util - Utility functions.""" +r"""usb.util - Utility functions. + +This module exports: + +endpoint_address - return the endpoint absolute address. +endpoint_direction - return the endpoint transfer direction. +endpoint_type - return the endpoint type +ctrl_direction - return the direction of a control transfer +build_request_type - build a bmRequestType field of a control transfer. +find_descriptor - find an inner descriptor. +claim_interface - explicitly claim an interface. +release_interface - explicitly release an interface. +dispose_resources - release internal resources allocated by the object. +get_string - retrieve a string descriptor from the device. +""" __author__ = 'Wander Lairson Costa' import operator +import array import usb._interop as _interop # descriptor type @@ -51,10 +66,10 @@ ENDPOINT_TYPE_INTR = 0x03 # control request type -CTRL_TYPE_STANDARD = 0 -CTRL_TYPE_CLASS = 1 -CTRL_TYPE_VENDOR = 2 -CTRL_TYPE_RESERVED = 3 +CTRL_TYPE_STANDARD = (0 << 5) +CTRL_TYPE_CLASS = (1 << 5) +CTRL_TYPE_VENDOR = (2 << 5) +CTRL_TYPE_RESERVED = (3 << 5) # control request recipient CTRL_RECIPIENT_DEVICE = 0 @@ -71,9 +86,12 @@ _ENDPOINT_TRANSFER_TYPE_MASK = 0x03 _CTRL_DIR_MASK = 0x80 +# For compatibility between Python 2 and 3 +_dummy_s = '\x00'.encode('utf-8') + def endpoint_address(address): r"""Return the endpoint absolute address. - + The address parameter is the bEndpointAddress field of the endpoint descriptor. """ @@ -90,7 +108,7 @@ def endpoint_type(bmAttributes): r"""Return the transfer type of the endpoint. - + The bmAttributes parameter is the bmAttributes field of the endpoint descriptor. The possible return values are: ENDPOINT_TYPE_CTRL, @@ -100,7 +118,7 @@ def ctrl_direction(bmRequestType): r"""Return the direction of a control request. - + The bmRequestType parameter is the value of the bmRequestType field of a control transfer. The possible return values are CTRL_OUT or CTRL_IN. @@ -121,12 +139,23 @@ Return the bmRequestType value. """ - return recipient | (type << 5) | direction + return recipient | type | direction + +def create_buffer(length): + r"""Create a buffer to be passed to a read function. + + A read function may receive an out buffer so the data + is read inplace and the object can be reused, avoiding + the overhead of creating a new object at each new read + call. This function creates a compatible sequence buffer + of the given length. + """ + return array.array('B', _dummy_s * length) def find_descriptor(desc, find_all=False, custom_match=None, **args): r"""Find an inner descriptor. - find_descriptor works in the same way the core.find() function does, + find_descriptor works in the same way as the core.find() function does, but it acts on general descriptor objects. For example, suppose you have a Device object called dev and want a Configuration of this object with its bConfigurationValue equals to 1, the code would @@ -137,7 +166,7 @@ You can use any field of the Descriptor as a match criteria, and you can supply a customized match just like core.find() does. The find_descriptor function also accepts the find_all parameter to get - a list of descriptor instead of just one. + an iterator instead of just one descriptor. """ def desc_iter(k, v): for d in desc: @@ -156,7 +185,7 @@ k, v = args.keys(), args.values() if find_all: - return [d for d in desc_iter(k, v)] + return desc_iter(k, v) else: try: return _interop._next(desc_iter(k, v)) @@ -200,6 +229,45 @@ After calling this function, you can continue using the device object normally. If the resources will be necessary again, it - will allocate them automatically. + will be allocated automatically. """ device._ctx.dispose(device) + +def get_string(dev, index, langid = None): + r"""Retrieve a string descriptor from the device. + + dev is the Device object which the string will be + read from. + + index is the string descriptor index and langid is the Language + ID of the descriptor. If langid is omitted, the string descriptor + of the first Language ID will be returned. + + The return value is the unicode string present in the descriptor. + """ + from usb.control import get_descriptor + if langid is None: + # Asking for the zero'th index is special - it returns a string + # descriptor that contains all the language IDs supported by the device. + # Typically there aren't many - often only one. The language IDs are 16 + # bit numbers, and they start at the third byte in the descriptor. See + # USB 2.0 specification section 9.6.7 for more information. + # + # Note from libusb 1.0 sources (descriptor.c) + buf = get_descriptor( + dev, + 254, + DESC_TYPE_STRING, + 0 + ) + assert len(buf) >= 4 + langid = buf[2] | (buf[3] << 8) + + buf = get_descriptor( + dev, + 255, # Maximum descriptor size + DESC_TYPE_STRING, + index, + langid + ) + return buf[2:buf[0]].tostring().decode('utf-16-le') Binary files /tmp/edonsy5nYK/quisk-3.6.18/usb/util.pyc and /tmp/ZwEcPOdYqm/quisk-3.7.6/usb/util.pyc differ diff -Nru quisk-3.6.18/utility.c quisk-3.7.6/utility.c --- quisk-3.6.18/utility.c 2012-03-02 20:42:44.000000000 +0000 +++ quisk-3.7.6/utility.c 2015-08-26 16:01:44.000000000 +0000 @@ -160,3 +160,22 @@ nanosleep(&tspec, NULL); #endif } + +void QuiskMeasureRate(const char * msg, int count) +{ //measure the sample rate + double tm; + static int total; + static double time0=0, time_pr; + + if (count && time0 == 0) { // init + time0 = time_pr = QuiskTimeSec(); + return; + } + if (time0 == 0) + return; + total += count; + if (QuiskTimeSec() > time_pr + 1.0) { // time to print + time_pr = tm = QuiskTimeSec(); + printf("%s count %d, time %.3lf, rate %.3lf\n", msg, total, tm - time0, total / (tm - time0)); + } +}