From 42c87903d605b37213b8231f148a5894393f5752 Mon Sep 17 00:00:00 2001 From: hobbes1069 Date: Wed, 19 Jul 2017 13:22:47 +0000 Subject: [PATCH] SVN is stupid so I have to add all these files that should have been tracked by using 'svn cp' but were not. git-svn-id: https://svn.code.sf.net/p/freetel/code@3315 01035d8c-6547-0410-b346-abe4f91aad63 --- codec2-dev/stm32/CMakeLists.txt | 941 ++++ codec2-dev/stm32/cmake/arm_toolchain.txt | 13 + codec2-dev/stm32/cmake/cmake_header.txt | 64 + codec2/branches/0.6/stm32/CMakeLists.txt | 941 ++++ .../0.6/stm32/cmake/arm_toolchain.txt | 13 + .../branches/0.6/stm32/cmake/cmake_header.txt | 64 + codec2/branches/0.7/stm32/CMakeLists.txt | 941 ++++ .../0.7/stm32/cmake/arm_toolchain.txt | 13 + .../branches/0.7/stm32/cmake/cmake_header.txt | 64 + codec2/tags/0.6/stm32/CMakeLists.txt | 941 ++++ codec2/tags/0.6/stm32/cmake/arm_toolchain.txt | 13 + codec2/tags/0.6/stm32/cmake/cmake_header.txt | 64 + codec2/tags/0.7/stm32/CMakeLists.txt | 941 ++++ codec2/tags/0.7/stm32/cmake/arm_toolchain.txt | 13 + codec2/tags/0.7/stm32/cmake/cmake_header.txt | 64 + freedv-dev/configure_speexdsp_osx.sh | 3 + freedv/tags/1.2.2/.clang/.gitignore | 0 freedv/tags/1.2.2/CMakeLists.txt | 463 ++ freedv/tags/1.2.2/COPYING | 502 ++ freedv/tags/1.2.2/README.osx | 107 + freedv/tags/1.2.2/README.txt | 233 + freedv/tags/1.2.2/RELEASE_NOTES.txt | 7 + freedv/tags/1.2.2/USER_MANUAL.txt | 100 + freedv/tags/1.2.2/cmake/BuildCodec2.cmake | 26 + freedv/tags/1.2.2/cmake/BuildHamlib.cmake | 20 + freedv/tags/1.2.2/cmake/BuildPortaudio.cmake | 52 + freedv/tags/1.2.2/cmake/BuildSamplerate.cmake | 27 + freedv/tags/1.2.2/cmake/BuildSndfile.cmake | 26 + freedv/tags/1.2.2/cmake/BuildSpeex.cmake | 30 + freedv/tags/1.2.2/cmake/BuildWxWidgets.cmake | 43 + freedv/tags/1.2.2/cmake/FindPortaudio.cmake | 107 + .../tags/1.2.2/cmake/GetDependencies.cmake.in | 37 + freedv/tags/1.2.2/cmake/MinGW.cmake | 8 + .../cmake/Toolchain-Ubuntu-mingw32.cmake | 25 + freedv/tags/1.2.2/cmake/config.h.in | 19 + freedv/tags/1.2.2/cmake/soxconfig.h.in | 16 + freedv/tags/1.2.2/cmake/version.h.in | 11 + freedv/tags/1.2.2/contrib/CMakeLists.txt | 22 + freedv/tags/1.2.2/contrib/LICENSE | 393 ++ freedv/tags/1.2.2/contrib/freedv.desktop | 8 + freedv/tags/1.2.2/contrib/freedv.ico | Bin 0 -> 364646 bytes freedv/tags/1.2.2/contrib/freedv.rc | 1 + freedv/tags/1.2.2/contrib/freedv128x128.png | Bin 0 -> 22063 bytes freedv/tags/1.2.2/contrib/freedv256x256.png | Bin 0 -> 72148 bytes freedv/tags/1.2.2/contrib/freedv48x48.png | Bin 0 -> 3787 bytes freedv/tags/1.2.2/contrib/freedv64x64.png | Bin 0 -> 6289 bytes .../tags/1.2.2/contrib/freedv_screenshot.png | Bin 0 -> 81690 bytes freedv/tags/1.2.2/credits.txt | 13 + freedv/tags/1.2.2/db/current | 1 + freedv/tags/1.2.2/db/format | 2 + freedv/tags/1.2.2/db/fs-type | 1 + freedv/tags/1.2.2/db/fsfs.conf | 38 + freedv/tags/1.2.2/db/min-unpacked-rev | 1 + freedv/tags/1.2.2/db/rep-cache.db | Bin 0 -> 4096 bytes freedv/tags/1.2.2/db/revprops/0/0 | 5 + freedv/tags/1.2.2/db/revprops/0/1 | 13 + freedv/tags/1.2.2/db/revs/0/0 | 11 + freedv/tags/1.2.2/db/revs/0/1 | 49 + freedv/tags/1.2.2/db/transactions/.gitignore | 0 freedv/tags/1.2.2/db/txn-current | 1 + freedv/tags/1.2.2/db/txn-current-lock | 0 freedv/tags/1.2.2/db/txn-protorevs/.gitignore | 0 freedv/tags/1.2.2/db/uuid | 1 + freedv/tags/1.2.2/db/write-lock | 0 freedv/tags/1.2.2/debian/changelog | 5 + freedv/tags/1.2.2/debian/compat | 1 + freedv/tags/1.2.2/debian/control | 19 + freedv/tags/1.2.2/debian/copyright | 38 + freedv/tags/1.2.2/debian/docs | 3 + freedv/tags/1.2.2/debian/format | 1 + freedv/tags/1.2.2/debian/rules | 30 + freedv/tags/1.2.2/script/spot.sh | 29 + freedv/tags/1.2.2/src/CMakeLists.txt | 79 + freedv/tags/1.2.2/src/Makefile.win32 | 52 + freedv/tags/1.2.2/src/afreedvplugin.c | 117 + freedv/tags/1.2.2/src/comp.h | 39 + freedv/tags/1.2.2/src/dlg_audiooptions.cpp | 1264 ++++++ freedv/tags/1.2.2/src/dlg_audiooptions.h | 176 + freedv/tags/1.2.2/src/dlg_filter.cpp | 785 ++++ freedv/tags/1.2.2/src/dlg_filter.h | 166 + freedv/tags/1.2.2/src/dlg_options.cpp | 616 +++ freedv/tags/1.2.2/src/dlg_options.h | 137 + freedv/tags/1.2.2/src/dlg_plugin.cpp | 148 + freedv/tags/1.2.2/src/dlg_plugin.h | 65 + freedv/tags/1.2.2/src/dlg_ptt.cpp | 570 +++ freedv/tags/1.2.2/src/dlg_ptt.h | 95 + freedv/tags/1.2.2/src/fdmdv2_defines.h | 106 + freedv/tags/1.2.2/src/fdmdv2_main.cpp | 4042 +++++++++++++++++ freedv/tags/1.2.2/src/fdmdv2_main.h | 681 +++ freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.cpp | 324 ++ freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.h | 115 + freedv/tags/1.2.2/src/fdmdv2_plot.cpp | 283 ++ freedv/tags/1.2.2/src/fdmdv2_plot.h | 150 + freedv/tags/1.2.2/src/fdmdv2_plot_scalar.cpp | 344 ++ freedv/tags/1.2.2/src/fdmdv2_plot_scalar.h | 78 + freedv/tags/1.2.2/src/fdmdv2_plot_scatter.cpp | 289 ++ freedv/tags/1.2.2/src/fdmdv2_plot_scatter.h | 65 + .../tags/1.2.2/src/fdmdv2_plot_spectrum.cpp | 267 ++ freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.h | 58 + .../tags/1.2.2/src/fdmdv2_plot_waterfall.cpp | 483 ++ freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.h | 73 + freedv/tags/1.2.2/src/freedv.icns | Bin 0 -> 92136 bytes freedv/tags/1.2.2/src/hamlib.cpp | 160 + freedv/tags/1.2.2/src/hamlib.h | 31 + freedv/tags/1.2.2/src/info.plist | 104 + freedv/tags/1.2.2/src/serialport.cpp | 234 + freedv/tags/1.2.2/src/serialport.h | 42 + freedv/tags/1.2.2/src/sox/band.h | 47 + freedv/tags/1.2.2/src/sox/biquad.c | 178 + freedv/tags/1.2.2/src/sox/biquad.h | 78 + freedv/tags/1.2.2/src/sox/biquads.c | 400 ++ freedv/tags/1.2.2/src/sox/effects.c | 544 +++ freedv/tags/1.2.2/src/sox/effects.h | 22 + freedv/tags/1.2.2/src/sox/effects_i.c | 379 ++ freedv/tags/1.2.2/src/sox/formats_i.c | 487 ++ freedv/tags/1.2.2/src/sox/libsox.c | 225 + freedv/tags/1.2.2/src/sox/sox.h | 2608 +++++++++++ freedv/tags/1.2.2/src/sox/sox_i.h | 417 ++ freedv/tags/1.2.2/src/sox/soxomp.h | 38 + freedv/tags/1.2.2/src/sox/util.h | 231 + freedv/tags/1.2.2/src/sox/xmalloc.c | 43 + freedv/tags/1.2.2/src/sox/xmalloc.h | 34 + freedv/tags/1.2.2/src/sox_biquad.c | 134 + freedv/tags/1.2.2/src/sox_biquad.h | 40 + freedv/tags/1.2.2/src/topFrame.cpp | 595 +++ freedv/tags/1.2.2/src/topFrame.h | 194 + 126 files changed, 26570 insertions(+) create mode 100644 codec2-dev/stm32/CMakeLists.txt create mode 100644 codec2-dev/stm32/cmake/arm_toolchain.txt create mode 100644 codec2-dev/stm32/cmake/cmake_header.txt create mode 100644 codec2/branches/0.6/stm32/CMakeLists.txt create mode 100644 codec2/branches/0.6/stm32/cmake/arm_toolchain.txt create mode 100644 codec2/branches/0.6/stm32/cmake/cmake_header.txt create mode 100644 codec2/branches/0.7/stm32/CMakeLists.txt create mode 100644 codec2/branches/0.7/stm32/cmake/arm_toolchain.txt create mode 100644 codec2/branches/0.7/stm32/cmake/cmake_header.txt create mode 100644 codec2/tags/0.6/stm32/CMakeLists.txt create mode 100644 codec2/tags/0.6/stm32/cmake/arm_toolchain.txt create mode 100644 codec2/tags/0.6/stm32/cmake/cmake_header.txt create mode 100644 codec2/tags/0.7/stm32/CMakeLists.txt create mode 100644 codec2/tags/0.7/stm32/cmake/arm_toolchain.txt create mode 100644 codec2/tags/0.7/stm32/cmake/cmake_header.txt create mode 100644 freedv-dev/configure_speexdsp_osx.sh create mode 100644 freedv/tags/1.2.2/.clang/.gitignore create mode 100644 freedv/tags/1.2.2/CMakeLists.txt create mode 100644 freedv/tags/1.2.2/COPYING create mode 100644 freedv/tags/1.2.2/README.osx create mode 100644 freedv/tags/1.2.2/README.txt create mode 100644 freedv/tags/1.2.2/RELEASE_NOTES.txt create mode 100644 freedv/tags/1.2.2/USER_MANUAL.txt create mode 100644 freedv/tags/1.2.2/cmake/BuildCodec2.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildHamlib.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildPortaudio.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildSamplerate.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildSndfile.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildSpeex.cmake create mode 100644 freedv/tags/1.2.2/cmake/BuildWxWidgets.cmake create mode 100644 freedv/tags/1.2.2/cmake/FindPortaudio.cmake create mode 100644 freedv/tags/1.2.2/cmake/GetDependencies.cmake.in create mode 100644 freedv/tags/1.2.2/cmake/MinGW.cmake create mode 100644 freedv/tags/1.2.2/cmake/Toolchain-Ubuntu-mingw32.cmake create mode 100644 freedv/tags/1.2.2/cmake/config.h.in create mode 100644 freedv/tags/1.2.2/cmake/soxconfig.h.in create mode 100644 freedv/tags/1.2.2/cmake/version.h.in create mode 100644 freedv/tags/1.2.2/contrib/CMakeLists.txt create mode 100644 freedv/tags/1.2.2/contrib/LICENSE create mode 100644 freedv/tags/1.2.2/contrib/freedv.desktop create mode 100644 freedv/tags/1.2.2/contrib/freedv.ico create mode 100644 freedv/tags/1.2.2/contrib/freedv.rc create mode 100644 freedv/tags/1.2.2/contrib/freedv128x128.png create mode 100644 freedv/tags/1.2.2/contrib/freedv256x256.png create mode 100644 freedv/tags/1.2.2/contrib/freedv48x48.png create mode 100644 freedv/tags/1.2.2/contrib/freedv64x64.png create mode 100644 freedv/tags/1.2.2/contrib/freedv_screenshot.png create mode 100644 freedv/tags/1.2.2/credits.txt create mode 100644 freedv/tags/1.2.2/db/current create mode 100644 freedv/tags/1.2.2/db/format create mode 100644 freedv/tags/1.2.2/db/fs-type create mode 100644 freedv/tags/1.2.2/db/fsfs.conf create mode 100644 freedv/tags/1.2.2/db/min-unpacked-rev create mode 100644 freedv/tags/1.2.2/db/rep-cache.db create mode 100644 freedv/tags/1.2.2/db/revprops/0/0 create mode 100644 freedv/tags/1.2.2/db/revprops/0/1 create mode 100644 freedv/tags/1.2.2/db/revs/0/0 create mode 100644 freedv/tags/1.2.2/db/revs/0/1 create mode 100644 freedv/tags/1.2.2/db/transactions/.gitignore create mode 100644 freedv/tags/1.2.2/db/txn-current create mode 100644 freedv/tags/1.2.2/db/txn-current-lock create mode 100644 freedv/tags/1.2.2/db/txn-protorevs/.gitignore create mode 100644 freedv/tags/1.2.2/db/uuid create mode 100644 freedv/tags/1.2.2/db/write-lock create mode 100644 freedv/tags/1.2.2/debian/changelog create mode 100644 freedv/tags/1.2.2/debian/compat create mode 100644 freedv/tags/1.2.2/debian/control create mode 100644 freedv/tags/1.2.2/debian/copyright create mode 100644 freedv/tags/1.2.2/debian/docs create mode 100644 freedv/tags/1.2.2/debian/format create mode 100755 freedv/tags/1.2.2/debian/rules create mode 100644 freedv/tags/1.2.2/script/spot.sh create mode 100644 freedv/tags/1.2.2/src/CMakeLists.txt create mode 100644 freedv/tags/1.2.2/src/Makefile.win32 create mode 100644 freedv/tags/1.2.2/src/afreedvplugin.c create mode 100644 freedv/tags/1.2.2/src/comp.h create mode 100644 freedv/tags/1.2.2/src/dlg_audiooptions.cpp create mode 100644 freedv/tags/1.2.2/src/dlg_audiooptions.h create mode 100644 freedv/tags/1.2.2/src/dlg_filter.cpp create mode 100644 freedv/tags/1.2.2/src/dlg_filter.h create mode 100644 freedv/tags/1.2.2/src/dlg_options.cpp create mode 100644 freedv/tags/1.2.2/src/dlg_options.h create mode 100644 freedv/tags/1.2.2/src/dlg_plugin.cpp create mode 100644 freedv/tags/1.2.2/src/dlg_plugin.h create mode 100644 freedv/tags/1.2.2/src/dlg_ptt.cpp create mode 100644 freedv/tags/1.2.2/src/dlg_ptt.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_defines.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_main.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_main.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_scalar.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_scalar.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_scatter.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_scatter.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.h create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.cpp create mode 100644 freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.h create mode 100644 freedv/tags/1.2.2/src/freedv.icns create mode 100644 freedv/tags/1.2.2/src/hamlib.cpp create mode 100644 freedv/tags/1.2.2/src/hamlib.h create mode 100644 freedv/tags/1.2.2/src/info.plist create mode 100644 freedv/tags/1.2.2/src/serialport.cpp create mode 100644 freedv/tags/1.2.2/src/serialport.h create mode 100644 freedv/tags/1.2.2/src/sox/band.h create mode 100644 freedv/tags/1.2.2/src/sox/biquad.c create mode 100644 freedv/tags/1.2.2/src/sox/biquad.h create mode 100644 freedv/tags/1.2.2/src/sox/biquads.c create mode 100644 freedv/tags/1.2.2/src/sox/effects.c create mode 100644 freedv/tags/1.2.2/src/sox/effects.h create mode 100644 freedv/tags/1.2.2/src/sox/effects_i.c create mode 100644 freedv/tags/1.2.2/src/sox/formats_i.c create mode 100644 freedv/tags/1.2.2/src/sox/libsox.c create mode 100644 freedv/tags/1.2.2/src/sox/sox.h create mode 100644 freedv/tags/1.2.2/src/sox/sox_i.h create mode 100644 freedv/tags/1.2.2/src/sox/soxomp.h create mode 100644 freedv/tags/1.2.2/src/sox/util.h create mode 100644 freedv/tags/1.2.2/src/sox/xmalloc.c create mode 100644 freedv/tags/1.2.2/src/sox/xmalloc.h create mode 100644 freedv/tags/1.2.2/src/sox_biquad.c create mode 100644 freedv/tags/1.2.2/src/sox_biquad.h create mode 100644 freedv/tags/1.2.2/src/topFrame.cpp create mode 100644 freedv/tags/1.2.2/src/topFrame.h diff --git a/codec2-dev/stm32/CMakeLists.txt b/codec2-dev/stm32/CMakeLists.txt new file mode 100644 index 00000000..d131758a --- /dev/null +++ b/codec2-dev/stm32/CMakeLists.txt @@ -0,0 +1,941 @@ +# +# stm32f4 Codec2 test programs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# +project(codec2 C) + +cmake_minimum_required(VERSION 2.8) + +include(GNUInstallDirs) +mark_as_advanced(CLEAR + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR +) + +# Include local definitions if they exist. +#-include local.mak + +################################################### + +set(FLOAT_TYPE "hard" CACHE) + +################################################### +# Replace with toolchain file +#CROSS_COMPILE ?= arm-none-eabi- +#CC=$(BINPATH)$(CROSS_COMPILE)gcc +#AS=$(BINPATH)$(CROSS_COMPILE)as +#OBJCOPY=$(BINPATH)$(CROSS_COMPILE)objcopy +#SIZE=$(BINPATH)$(CROSS_COMPILE)size +#SUDO ?= sudo + +################################################### + +# Set default C++ flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -std=gnu11 -Tstm32_flash.ld -DSTM32F40_41xxx -DCORTEX_M4 -mlittle-endian -mthumb -mthumb-interwork -nostartfiles -mcpu=cortex-m4") + +if(FLOAT_TYPE hard) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant -Wdouble-promotion -mfpu=fpv4-sp-d16 -mfloat-abi=hard -D__FPU_PRESENT=1 -D__FPU_USED=1") + #CFLAGS += -fsingle-precision-constant +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") +endif() + +# Sync up build flags if other build types are specified. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") + +################################################### + +# Definitions for the STM32F4 Standard Peripheral Library + +PERIPHLIBURL = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware +PERIPHLIBZIP = stm32f4_dsp_stdperiph_lib.zip +PERIPHLIBVER = V1.7.1 +PERIPHLIBNAME = STM32F4xx_DSP_StdPeriph_Lib +PERIPHLIBDIR = $(PERIPHLIBNAME) +CMSIS = $(PERIPHLIBDIR)/Libraries/CMSIS +STM32F4LIB = $(PERIPHLIBDIR)/Libraries/STM32F4xx_StdPeriph_Driver +STM32F4TEMPLATE = $(PERIPHLIBDIR)/Project/STM32F4xx_StdPeriph_Templates +DSPLIB = $(PERIPHLIBDIR)/Libraries/CMSIS/DSP_Lib + +CFLAGS += -DUSE_STDPERIPH_DRIVER -I$(STM32F4LIB)/inc -I$(STM32F4TEMPLATE) +CFLAGS += -I$(CMSIS)/Include -I$(CMSIS)/Device/ST/STM32F4xx/Include +CFLAGS += -DARM_MATH_CM4 + +# Precious files that should be preserved at all cost! +.PRECIOUS: dl/$(PERIPHLIBZIP) + +set(STM32F4LIB_SRCS +$(STM32F4LIB)/src/misc.c\ +$(STM32F4LIB)/src/stm32f4xx_adc.c\ +$(STM32F4LIB)/src/stm32f4xx_can.c\ +$(STM32F4LIB)/src/stm32f4xx_cec.c\ +$(STM32F4LIB)/src/stm32f4xx_crc.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_aes.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_des.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_tdes.c\ +$(STM32F4LIB)/src/stm32f4xx_dac.c\ +$(STM32F4LIB)/src/stm32f4xx_dbgmcu.c\ +$(STM32F4LIB)/src/stm32f4xx_dcmi.c\ +$(STM32F4LIB)/src/stm32f4xx_dma2d.c\ +$(STM32F4LIB)/src/stm32f4xx_dma.c\ +$(STM32F4LIB)/src/stm32f4xx_exti.c\ +$(STM32F4LIB)/src/stm32f4xx_flash.c\ +$(STM32F4LIB)/src/stm32f4xx_flash_ramfunc.c\ +$(STM32F4LIB)/src/stm32f4xx_fmpi2c.c\ +$(STM32F4LIB)/src/stm32f4xx_fsmc.c\ +$(STM32F4LIB)/src/stm32f4xx_gpio.c\ +$(STM32F4LIB)/src/stm32f4xx_hash.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_md5.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_sha1.c\ +$(STM32F4LIB)/src/stm32f4xx_i2c.c\ +$(STM32F4LIB)/src/stm32f4xx_iwdg.c\ +$(STM32F4LIB)/src/stm32f4xx_ltdc.c\ +$(STM32F4LIB)/src/stm32f4xx_pwr.c\ +$(STM32F4LIB)/src/stm32f4xx_qspi.c\ +$(STM32F4LIB)/src/stm32f4xx_rcc.c\ +$(STM32F4LIB)/src/stm32f4xx_rng.c\ +$(STM32F4LIB)/src/stm32f4xx_rtc.c\ +$(STM32F4LIB)/src/stm32f4xx_sai.c\ +$(STM32F4LIB)/src/stm32f4xx_sdio.c\ +$(STM32F4LIB)/src/stm32f4xx_spdifrx.c\ +$(STM32F4LIB)/src/stm32f4xx_spi.c\ +$(STM32F4LIB)/src/stm32f4xx_syscfg.c\ +$(STM32F4LIB)/src/stm32f4xx_tim.c\ +$(STM32F4LIB)/src/stm32f4xx_usart.c\ +$(STM32F4LIB)/src/stm32f4xx_wwdg.c +# Not compiling for now +# $(STM32F4LIB)/src/stm32f4xx_fmc.c +) + + +set(STM32F4LIB_OBJS $(STM32F4LIB_SRCS:.c=.o)) + +set(CMSIS_SRCS +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q7.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_common_tables.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_const_structs.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f64.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix8_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c\ +) + +CMSIS_OBJS = $(CMSIS_SRCS:.c=.o) $(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal2.o + +################################################### + +# Codec 2 + +set(CODEC2_SRC ../src) +set(CODEC2_SRCS +$(CODEC2_SRC)/lpc.c \ +$(CODEC2_SRC)/nlp.c \ +$(CODEC2_SRC)/postfilter.c \ +$(CODEC2_SRC)/sine.c \ +$(CODEC2_SRC)/codec2.c \ +$(CODEC2_SRC)/codec2_fft.c \ +$(CODEC2_SRC)/kiss_fft.c \ +$(CODEC2_SRC)/kiss_fftr.c \ +$(CODEC2_SRC)/interp.c \ +$(CODEC2_SRC)/lsp.c \ +$(CODEC2_SRC)/phase.c \ +$(CODEC2_SRC)/quantise.c \ +$(CODEC2_SRC)/pack.c \ +$(CODEC2_SRC)/codebook.c \ +$(CODEC2_SRC)/codebookd.c \ +$(CODEC2_SRC)/codebookjvm.c \ +$(CODEC2_SRC)/codebookge.c \ +$(CODEC2_SRC)/dump.c \ +$(CODEC2_SRC)/fdmdv.c \ +$(CODEC2_SRC)/freedv_api.c \ +$(CODEC2_SRC)/varicode.c \ +$(CODEC2_SRC)/golay23.c \ +$(CODEC2_SRC)/fsk.c \ +$(CODEC2_SRC)/fmfsk.c \ +$(CODEC2_SRC)/freedv_vhf_framing.c \ +$(CODEC2_SRC)/freedv_data_channel.c +) +CFLAGS += -D__EMBEDDED__ + +#enable this for dump files to help verify optimisation +#CFLAGS += -DDUMP + +include_directories(../src ../unittest inc) + +FFT_TEST_SRCS = \ +$(DSPLIB)/Examples/arm_fft_bin_example/GCC/arm_fft_bin_data.c \ +fft_test.c \ +src/startup_stm32f4xx.s \ +src/system_stm32f4xx.c \ +stm32f4_machdep.c \ +gdb_stdio.c \ +../src/kiss_fft.c + +################################################### + +vpath %.c src +vpath %.a lib + +ROOT=$(shell pwd) + +# Library paths + +LIBPATHS = + +# Libraries to link + +LIBS = -lg -lnosys -lm +#Uncomment for standard arm semihosting +#LIBS = -lg -lrdimon -lm --specs=rdimon.specs + +# startup file + +SRCS += src/startup_stm32f4xx.s src/init.c + +#OBJS = $(SRCS:.c=.o) + +#all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin mco_ut.bin sm2000_stw.bin sm2000_adcdump.bin sm2000_rxdemo.bin + +# Rule for making directories automatically. +# Note we don't use -p as it's a GNU extension. +%/.md: + parent=$(shell dirname $(@D) ); \ + [ -d $${parent} ] || $(MAKE) $${parent}/.md + [ -d $(@D) ] || mkdir $(@D) + touch $@ + +dl/$(PERIPHLIBZIP): dl/.md + wget -O$@.part -c $(PERIPHLIBURL)/$(PERIPHLIBZIP) + mv $@.part $@ + touch $@ + +$(PERIPHLIBDIR)/.unpack: dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR)_$(PERIPHLIBVER) || \ + rm -fr $(PERIPHLIBDIR)_$(PERIPHLIBVER) + unzip dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR) || rm -fr $(PERIPHLIBDIR) + mv $(PERIPHLIBDIR)_$(PERIPHLIBVER) $(PERIPHLIBDIR) + touch $@ + +$(CMSIS_OBJS) $(STM32F4LIB_OBJS): $(PERIPHLIBDIR)/.unpack + +libstm32f4.a: $(CMSIS_OBJS) $(STM32F4LIB_OBJS) + find $(PERIPHLIBDIR) -type f -name '*.o' -exec $(AR) crs libstm32f4.a {} ";" + +# Kludgy target to build a file with CFLAGS -O3 +%.O3.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -O3 -c -o $@ $< + +# Kludgy target to build a file with CFLAGS -DPROFILE +%.profile.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILE -c -o $@ $< + +# Rule for building .bin files from a .elf +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +#################################################### + +CODEC2_PROFILE_SRCS=\ +src/codec2_profile.c \ +src/gdb_stdio.c \ +src/stm32f4_machdep.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/system_stm32f4xx.c +CODEC2_PROFILE_SRCS += $(CODEC2_SRCS) + +codec2_profile.elf: $(CODEC2_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +fft_test.elf: $(FFT_TEST_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_UT_SRCS=\ +src/dac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_ut.elf: $(DAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +FAST_DAC_UT_SRCS=\ +src/fast_dac_ut.c \ +../src/fifo.c \ +src/iir_duc.c \ +src/gdb_stdio.c \ +src/stm32f4_dacduc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +fast_dac_ut.elf: $(FAST_DAC_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADCDAC_UT_SRCS=\ +src/adcdac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adcdac_ut.elf: $(ADCDAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_PLAY_SRCS=\ +src/dac_play.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_play.elf: $(DAC_PLAY_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_SRCS=\ +src/adc_rec.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_rec.elf: $(ADC_REC_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_SD_SRCS=\ +src/adc_sd.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_sd.elf: $(ADC_SD_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +PWM_UT_SRCS=\ +gdb_stdio.c \ +src/stm32f4_pwm.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +pwm_ut.elf: $(PWM_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +POWER_UT_SRCS=\ +src/power_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c \ + +POWER_UT_SRCS += $(CODEC2_SRCS) + +power_ut.elf: $(POWER_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +USB_VCP=\ +usb_conf/usb_bsp.c \ +usb_conf/usbd_desc.c \ +usb_conf/usbd_usr.c \ +usb_lib/cdc/usbd_cdc_core.c \ +usb_lib/cdc/usbd_cdc_vcp.c \ +usb_lib/core/usbd_core.c \ +usb_lib/core/usbd_ioreq.c \ +usb_lib/core/usbd_req.c \ +usb_lib/otg/usb_core.c \ +usb_lib/otg/usb_dcd.c \ +usb_lib/otg/usb_dcd_int.c + +USB_VCP_UT=\ +src/usb_vcp_ut.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +USB_VCP_UT+=$(USB_VCP) + +CFLAGS += -DUSE_USB_OTG_FS -DUSE_ULPI_PHY -Iusb_conf -Iusb_lib/cdc -Iusb_lib/core -Iusb_lib/otg + +usb_vcp_ut.elf: $(USB_VCP_UT:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_USB_SRCS=\ +src/adc_rec_usb.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +ADC_REC_USB_SRCS+=$(USB_VCP) + +adc_rec_usb.elf: $(ADC_REC_USB_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + + +FDMDV_PROFILE_SRCS=\ +src/fdmdv_profile.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c + +FDMDV_PROFILE_SRCS += $(CODEC2_SRCS) + +fdmdv_profile.elf: $(FDMDV_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_LEDS_SWITCHES_UT_SRCS=\ +src/sm1000_leds_switches_ut.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +sm1000_leds_switches_ut.elf: $(SM1000_LEDS_SWITCHES_UT_SRCS:.c=.o) \ + libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_SRCS=\ +src/sm1000_main.c \ +src/tone.c \ +src/sfx.c \ +src/sounds.c \ +src/morse.c \ +src/menu.c \ +src/tot.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/stm32f4_vrom.c \ +src/init.c + +SM1000_SRCS += $(CODEC2_SRCS) + +src/stm32f4_dac.o: src/stm32f4_dac.c + $(CC) $(CFLAGS) $^ -c -o $@ + +src/stm32f4_adc.o: src/stm32f4_adc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +sm1000.elf: $(SM1000_SRCS:.c=.O3.o) src/stm32f4_dac.O3.o \ + src/stm32f4_adc.O3.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_TX_PROFILE_SRCS=\ +src/freedv_tx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_TX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_tx_profile.elf: $(FREEDV_TX_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_RX_PROFILE_SRCS=\ +src/freedv_rx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_RX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_rx_profile.elf: $(FREEDV_RX_PROFILE_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +FDMDV_DUMP_RT_SRCS=\ +src/fdmdv_dump_rt.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FDMDV_DUMP_RT_SRCS += $(CODEC2_SRCS) + +fdmdv_dump_rt.elf: $(FDMDV_DUMP_RT_SRCS:.c=.O3.o) \ + src/stm32f4_dac.O3.o src/stm32f4_adc.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +TUNER_UT_SRCS=\ +src/tuner_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +../src/fm.c + +# this needs to be compiled without the optimiser or ugly things happen +# would be nice to work out why as ISRs need to run fast + +src/stm32f4_adc_tuner.o: src/stm32f4_adc_tuner.c + $(CC) $(CFLAGS) $^ -c -o $@ + +tuner_ut.elf: $(TUNER_UT_SRCS:.c=.O3.o) \ + src/stm32f4_adc_tuner.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +ADC_SFDR_UT_SRCS=\ +src/adc_sfdr_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ + libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + + +FM_LODUC_PLAY_SRCS=\ +src/fm_loduc_play.c \ +gdb_stdio.c \ +../src/fifo.c \ +../src/fm.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +src/stm32f4_dacloduc.o: src/stm32f4_dacloduc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +MCO_UT_SRCS=\ +src/mco_ut.c \ +src/tm_stm32f4_mco_output.c \ +src/tm_stm32f4_gpio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +mco_ut.elf: $(MCO_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- + +SM2000_RXDEMO_SRCS=\ +src/sm2000_rxdemo.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_RXDEMO_SRCS+=$(CODEC2_SRCS) + +sm2000_rxdemo.elf: $(SM2000_RXDEMO_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_STW_SRCS=\ +src/sm2000_stw.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_STW_SRCS+=$(CODEC2_SRCS) + +#SM2000_STW_SRCS+=$(USB_VCP) + +sm2000_stw.elf: $(SM2000_STW_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_ADCDUMP_SRCS=\ +src/sm2000_adc_dump.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_usb_vcp.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_ADCDUMP_SRCS_SRCS+=$(CODEC2_SRCS) + +SM2000_ADCDUMP_SRCS+=$(USB_VCP) + +sm2000_adcdump.elf: $(SM2000_ADCDUMP_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# Objects that require the peripheral library +src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack +src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack + +# --------------------------------------------------------------------------------- + +clean: + rm -f *.elf *.bin + rm -f libstm32f4.a + find . ../src -type f -name '*.o' | xargs rm -f diff --git a/codec2-dev/stm32/cmake/arm_toolchain.txt b/codec2-dev/stm32/cmake/arm_toolchain.txt new file mode 100644 index 00000000..69e08a3d --- /dev/null +++ b/codec2-dev/stm32/cmake/arm_toolchain.txt @@ -0,0 +1,13 @@ +INCLUDE(CMakeForceCompiler) + +SET(CMAKE_SYSTEM_NAME Generic) +SET(CMAKE_SYSTEM_VERSION 1) + +# specify the cross compiler +CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU) +CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU) + +SET(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -mthumb-interwork -msoft-float -ffunction-sections -fdata-sections -g -fno-common -fmessage-length=0") +SET(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=gnu++0x") +SET(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu99") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections ") diff --git a/codec2-dev/stm32/cmake/cmake_header.txt b/codec2-dev/stm32/cmake/cmake_header.txt new file mode 100644 index 00000000..7be411fe --- /dev/null +++ b/codec2-dev/stm32/cmake/cmake_header.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) + +#custom command to use objcopy to create .bin files out of ELF files +function(make_mbed_firmware INPUT) + add_custom_command(TARGET ${INPUT} + COMMAND arm-none-eabi-objcopy -O binary ${INPUT} ${INPUT}_${MBED_TARGET}.bin + COMMENT "objcopying to make mbed compatible firmware") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${INPUT}_${MBED_TARGET}.bin) +endfunction(make_mbed_firmware) + +#assume we're using an LPC1768 model if it's not specified by -DMBED_TARGET= + if( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + message(STATUS "invalid or no mbed target specified. Options are LPC1768, LPC2368 or LPC11U24. Assuming LPC1768 for now. + Target may be specified using -DMBED_TARGET=") + set(MBED_TARGET "LPC1768") +endif( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + +set(MBED_INCLUDE "${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/") + +#setup target specific object files +if(MBED_TARGET MATCHES "LPC1768") + set(MBED_PREFIX "LPC17") + set(CORE "cm3") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC17xx.o) +elseif(MBED_TARGET MATCHES "LPC2368") + set(CHIP ${MBED_INCLUDE}vector_functions.o + ${MBED_INCLUDE}vector_realmonitor.o + ${MBED_INCLUDE}vector_table.o) + set(MBED_PREFIX "LPC23") + set(CORE "arm7") +elseif(MBED_TARGET MATCHES "LPC11U24") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC11xx.o) + set(CORE "cm0") + set(MBED_PREFIX "LPC11U") +endif(MBED_TARGET MATCHES "LPC1768") + +#setup precompiled mbed files which will be needed for all projects +set(CHIP ${CHIP} + ${MBED_INCLUDE}system_${MBED_PREFIX}xx.o + ${MBED_INCLUDE}cmsis_nvic.o + ${MBED_INCLUDE}core_${CORE}.o) + +#force min size build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif(NOT CMAKE_BUILD_TYPE) + +#set correct linker script +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \"-T${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/${MBED_TARGET}.ld\" -static") + +#find CodeSourcery Toolchain for appropriate include dirs +find_path(CSPATH arm-none-eabi-g++ PATHS ENV) +message(STATUS "${CSPATH} is where CodeSourcery is installed") + +#setup directories for appropriate C, C++, mbed libraries and includes +include_directories(${MBED_INCLUDE}) +include_directories(mbed) +include_directories(${CSPATH}../arm-none-eabi/include) +include_directories(${CSPATH}../arm-none-eabi/include/c++/4.6.1) +link_directories(${MBED_INCLUDE}) diff --git a/codec2/branches/0.6/stm32/CMakeLists.txt b/codec2/branches/0.6/stm32/CMakeLists.txt new file mode 100644 index 00000000..d131758a --- /dev/null +++ b/codec2/branches/0.6/stm32/CMakeLists.txt @@ -0,0 +1,941 @@ +# +# stm32f4 Codec2 test programs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# +project(codec2 C) + +cmake_minimum_required(VERSION 2.8) + +include(GNUInstallDirs) +mark_as_advanced(CLEAR + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR +) + +# Include local definitions if they exist. +#-include local.mak + +################################################### + +set(FLOAT_TYPE "hard" CACHE) + +################################################### +# Replace with toolchain file +#CROSS_COMPILE ?= arm-none-eabi- +#CC=$(BINPATH)$(CROSS_COMPILE)gcc +#AS=$(BINPATH)$(CROSS_COMPILE)as +#OBJCOPY=$(BINPATH)$(CROSS_COMPILE)objcopy +#SIZE=$(BINPATH)$(CROSS_COMPILE)size +#SUDO ?= sudo + +################################################### + +# Set default C++ flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -std=gnu11 -Tstm32_flash.ld -DSTM32F40_41xxx -DCORTEX_M4 -mlittle-endian -mthumb -mthumb-interwork -nostartfiles -mcpu=cortex-m4") + +if(FLOAT_TYPE hard) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant -Wdouble-promotion -mfpu=fpv4-sp-d16 -mfloat-abi=hard -D__FPU_PRESENT=1 -D__FPU_USED=1") + #CFLAGS += -fsingle-precision-constant +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") +endif() + +# Sync up build flags if other build types are specified. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") + +################################################### + +# Definitions for the STM32F4 Standard Peripheral Library + +PERIPHLIBURL = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware +PERIPHLIBZIP = stm32f4_dsp_stdperiph_lib.zip +PERIPHLIBVER = V1.7.1 +PERIPHLIBNAME = STM32F4xx_DSP_StdPeriph_Lib +PERIPHLIBDIR = $(PERIPHLIBNAME) +CMSIS = $(PERIPHLIBDIR)/Libraries/CMSIS +STM32F4LIB = $(PERIPHLIBDIR)/Libraries/STM32F4xx_StdPeriph_Driver +STM32F4TEMPLATE = $(PERIPHLIBDIR)/Project/STM32F4xx_StdPeriph_Templates +DSPLIB = $(PERIPHLIBDIR)/Libraries/CMSIS/DSP_Lib + +CFLAGS += -DUSE_STDPERIPH_DRIVER -I$(STM32F4LIB)/inc -I$(STM32F4TEMPLATE) +CFLAGS += -I$(CMSIS)/Include -I$(CMSIS)/Device/ST/STM32F4xx/Include +CFLAGS += -DARM_MATH_CM4 + +# Precious files that should be preserved at all cost! +.PRECIOUS: dl/$(PERIPHLIBZIP) + +set(STM32F4LIB_SRCS +$(STM32F4LIB)/src/misc.c\ +$(STM32F4LIB)/src/stm32f4xx_adc.c\ +$(STM32F4LIB)/src/stm32f4xx_can.c\ +$(STM32F4LIB)/src/stm32f4xx_cec.c\ +$(STM32F4LIB)/src/stm32f4xx_crc.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_aes.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_des.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_tdes.c\ +$(STM32F4LIB)/src/stm32f4xx_dac.c\ +$(STM32F4LIB)/src/stm32f4xx_dbgmcu.c\ +$(STM32F4LIB)/src/stm32f4xx_dcmi.c\ +$(STM32F4LIB)/src/stm32f4xx_dma2d.c\ +$(STM32F4LIB)/src/stm32f4xx_dma.c\ +$(STM32F4LIB)/src/stm32f4xx_exti.c\ +$(STM32F4LIB)/src/stm32f4xx_flash.c\ +$(STM32F4LIB)/src/stm32f4xx_flash_ramfunc.c\ +$(STM32F4LIB)/src/stm32f4xx_fmpi2c.c\ +$(STM32F4LIB)/src/stm32f4xx_fsmc.c\ +$(STM32F4LIB)/src/stm32f4xx_gpio.c\ +$(STM32F4LIB)/src/stm32f4xx_hash.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_md5.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_sha1.c\ +$(STM32F4LIB)/src/stm32f4xx_i2c.c\ +$(STM32F4LIB)/src/stm32f4xx_iwdg.c\ +$(STM32F4LIB)/src/stm32f4xx_ltdc.c\ +$(STM32F4LIB)/src/stm32f4xx_pwr.c\ +$(STM32F4LIB)/src/stm32f4xx_qspi.c\ +$(STM32F4LIB)/src/stm32f4xx_rcc.c\ +$(STM32F4LIB)/src/stm32f4xx_rng.c\ +$(STM32F4LIB)/src/stm32f4xx_rtc.c\ +$(STM32F4LIB)/src/stm32f4xx_sai.c\ +$(STM32F4LIB)/src/stm32f4xx_sdio.c\ +$(STM32F4LIB)/src/stm32f4xx_spdifrx.c\ +$(STM32F4LIB)/src/stm32f4xx_spi.c\ +$(STM32F4LIB)/src/stm32f4xx_syscfg.c\ +$(STM32F4LIB)/src/stm32f4xx_tim.c\ +$(STM32F4LIB)/src/stm32f4xx_usart.c\ +$(STM32F4LIB)/src/stm32f4xx_wwdg.c +# Not compiling for now +# $(STM32F4LIB)/src/stm32f4xx_fmc.c +) + + +set(STM32F4LIB_OBJS $(STM32F4LIB_SRCS:.c=.o)) + +set(CMSIS_SRCS +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q7.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_common_tables.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_const_structs.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f64.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix8_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c\ +) + +CMSIS_OBJS = $(CMSIS_SRCS:.c=.o) $(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal2.o + +################################################### + +# Codec 2 + +set(CODEC2_SRC ../src) +set(CODEC2_SRCS +$(CODEC2_SRC)/lpc.c \ +$(CODEC2_SRC)/nlp.c \ +$(CODEC2_SRC)/postfilter.c \ +$(CODEC2_SRC)/sine.c \ +$(CODEC2_SRC)/codec2.c \ +$(CODEC2_SRC)/codec2_fft.c \ +$(CODEC2_SRC)/kiss_fft.c \ +$(CODEC2_SRC)/kiss_fftr.c \ +$(CODEC2_SRC)/interp.c \ +$(CODEC2_SRC)/lsp.c \ +$(CODEC2_SRC)/phase.c \ +$(CODEC2_SRC)/quantise.c \ +$(CODEC2_SRC)/pack.c \ +$(CODEC2_SRC)/codebook.c \ +$(CODEC2_SRC)/codebookd.c \ +$(CODEC2_SRC)/codebookjvm.c \ +$(CODEC2_SRC)/codebookge.c \ +$(CODEC2_SRC)/dump.c \ +$(CODEC2_SRC)/fdmdv.c \ +$(CODEC2_SRC)/freedv_api.c \ +$(CODEC2_SRC)/varicode.c \ +$(CODEC2_SRC)/golay23.c \ +$(CODEC2_SRC)/fsk.c \ +$(CODEC2_SRC)/fmfsk.c \ +$(CODEC2_SRC)/freedv_vhf_framing.c \ +$(CODEC2_SRC)/freedv_data_channel.c +) +CFLAGS += -D__EMBEDDED__ + +#enable this for dump files to help verify optimisation +#CFLAGS += -DDUMP + +include_directories(../src ../unittest inc) + +FFT_TEST_SRCS = \ +$(DSPLIB)/Examples/arm_fft_bin_example/GCC/arm_fft_bin_data.c \ +fft_test.c \ +src/startup_stm32f4xx.s \ +src/system_stm32f4xx.c \ +stm32f4_machdep.c \ +gdb_stdio.c \ +../src/kiss_fft.c + +################################################### + +vpath %.c src +vpath %.a lib + +ROOT=$(shell pwd) + +# Library paths + +LIBPATHS = + +# Libraries to link + +LIBS = -lg -lnosys -lm +#Uncomment for standard arm semihosting +#LIBS = -lg -lrdimon -lm --specs=rdimon.specs + +# startup file + +SRCS += src/startup_stm32f4xx.s src/init.c + +#OBJS = $(SRCS:.c=.o) + +#all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin mco_ut.bin sm2000_stw.bin sm2000_adcdump.bin sm2000_rxdemo.bin + +# Rule for making directories automatically. +# Note we don't use -p as it's a GNU extension. +%/.md: + parent=$(shell dirname $(@D) ); \ + [ -d $${parent} ] || $(MAKE) $${parent}/.md + [ -d $(@D) ] || mkdir $(@D) + touch $@ + +dl/$(PERIPHLIBZIP): dl/.md + wget -O$@.part -c $(PERIPHLIBURL)/$(PERIPHLIBZIP) + mv $@.part $@ + touch $@ + +$(PERIPHLIBDIR)/.unpack: dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR)_$(PERIPHLIBVER) || \ + rm -fr $(PERIPHLIBDIR)_$(PERIPHLIBVER) + unzip dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR) || rm -fr $(PERIPHLIBDIR) + mv $(PERIPHLIBDIR)_$(PERIPHLIBVER) $(PERIPHLIBDIR) + touch $@ + +$(CMSIS_OBJS) $(STM32F4LIB_OBJS): $(PERIPHLIBDIR)/.unpack + +libstm32f4.a: $(CMSIS_OBJS) $(STM32F4LIB_OBJS) + find $(PERIPHLIBDIR) -type f -name '*.o' -exec $(AR) crs libstm32f4.a {} ";" + +# Kludgy target to build a file with CFLAGS -O3 +%.O3.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -O3 -c -o $@ $< + +# Kludgy target to build a file with CFLAGS -DPROFILE +%.profile.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILE -c -o $@ $< + +# Rule for building .bin files from a .elf +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +#################################################### + +CODEC2_PROFILE_SRCS=\ +src/codec2_profile.c \ +src/gdb_stdio.c \ +src/stm32f4_machdep.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/system_stm32f4xx.c +CODEC2_PROFILE_SRCS += $(CODEC2_SRCS) + +codec2_profile.elf: $(CODEC2_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +fft_test.elf: $(FFT_TEST_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_UT_SRCS=\ +src/dac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_ut.elf: $(DAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +FAST_DAC_UT_SRCS=\ +src/fast_dac_ut.c \ +../src/fifo.c \ +src/iir_duc.c \ +src/gdb_stdio.c \ +src/stm32f4_dacduc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +fast_dac_ut.elf: $(FAST_DAC_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADCDAC_UT_SRCS=\ +src/adcdac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adcdac_ut.elf: $(ADCDAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_PLAY_SRCS=\ +src/dac_play.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_play.elf: $(DAC_PLAY_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_SRCS=\ +src/adc_rec.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_rec.elf: $(ADC_REC_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_SD_SRCS=\ +src/adc_sd.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_sd.elf: $(ADC_SD_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +PWM_UT_SRCS=\ +gdb_stdio.c \ +src/stm32f4_pwm.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +pwm_ut.elf: $(PWM_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +POWER_UT_SRCS=\ +src/power_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c \ + +POWER_UT_SRCS += $(CODEC2_SRCS) + +power_ut.elf: $(POWER_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +USB_VCP=\ +usb_conf/usb_bsp.c \ +usb_conf/usbd_desc.c \ +usb_conf/usbd_usr.c \ +usb_lib/cdc/usbd_cdc_core.c \ +usb_lib/cdc/usbd_cdc_vcp.c \ +usb_lib/core/usbd_core.c \ +usb_lib/core/usbd_ioreq.c \ +usb_lib/core/usbd_req.c \ +usb_lib/otg/usb_core.c \ +usb_lib/otg/usb_dcd.c \ +usb_lib/otg/usb_dcd_int.c + +USB_VCP_UT=\ +src/usb_vcp_ut.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +USB_VCP_UT+=$(USB_VCP) + +CFLAGS += -DUSE_USB_OTG_FS -DUSE_ULPI_PHY -Iusb_conf -Iusb_lib/cdc -Iusb_lib/core -Iusb_lib/otg + +usb_vcp_ut.elf: $(USB_VCP_UT:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_USB_SRCS=\ +src/adc_rec_usb.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +ADC_REC_USB_SRCS+=$(USB_VCP) + +adc_rec_usb.elf: $(ADC_REC_USB_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + + +FDMDV_PROFILE_SRCS=\ +src/fdmdv_profile.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c + +FDMDV_PROFILE_SRCS += $(CODEC2_SRCS) + +fdmdv_profile.elf: $(FDMDV_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_LEDS_SWITCHES_UT_SRCS=\ +src/sm1000_leds_switches_ut.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +sm1000_leds_switches_ut.elf: $(SM1000_LEDS_SWITCHES_UT_SRCS:.c=.o) \ + libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_SRCS=\ +src/sm1000_main.c \ +src/tone.c \ +src/sfx.c \ +src/sounds.c \ +src/morse.c \ +src/menu.c \ +src/tot.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/stm32f4_vrom.c \ +src/init.c + +SM1000_SRCS += $(CODEC2_SRCS) + +src/stm32f4_dac.o: src/stm32f4_dac.c + $(CC) $(CFLAGS) $^ -c -o $@ + +src/stm32f4_adc.o: src/stm32f4_adc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +sm1000.elf: $(SM1000_SRCS:.c=.O3.o) src/stm32f4_dac.O3.o \ + src/stm32f4_adc.O3.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_TX_PROFILE_SRCS=\ +src/freedv_tx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_TX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_tx_profile.elf: $(FREEDV_TX_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_RX_PROFILE_SRCS=\ +src/freedv_rx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_RX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_rx_profile.elf: $(FREEDV_RX_PROFILE_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +FDMDV_DUMP_RT_SRCS=\ +src/fdmdv_dump_rt.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FDMDV_DUMP_RT_SRCS += $(CODEC2_SRCS) + +fdmdv_dump_rt.elf: $(FDMDV_DUMP_RT_SRCS:.c=.O3.o) \ + src/stm32f4_dac.O3.o src/stm32f4_adc.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +TUNER_UT_SRCS=\ +src/tuner_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +../src/fm.c + +# this needs to be compiled without the optimiser or ugly things happen +# would be nice to work out why as ISRs need to run fast + +src/stm32f4_adc_tuner.o: src/stm32f4_adc_tuner.c + $(CC) $(CFLAGS) $^ -c -o $@ + +tuner_ut.elf: $(TUNER_UT_SRCS:.c=.O3.o) \ + src/stm32f4_adc_tuner.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +ADC_SFDR_UT_SRCS=\ +src/adc_sfdr_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ + libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + + +FM_LODUC_PLAY_SRCS=\ +src/fm_loduc_play.c \ +gdb_stdio.c \ +../src/fifo.c \ +../src/fm.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +src/stm32f4_dacloduc.o: src/stm32f4_dacloduc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +MCO_UT_SRCS=\ +src/mco_ut.c \ +src/tm_stm32f4_mco_output.c \ +src/tm_stm32f4_gpio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +mco_ut.elf: $(MCO_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- + +SM2000_RXDEMO_SRCS=\ +src/sm2000_rxdemo.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_RXDEMO_SRCS+=$(CODEC2_SRCS) + +sm2000_rxdemo.elf: $(SM2000_RXDEMO_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_STW_SRCS=\ +src/sm2000_stw.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_STW_SRCS+=$(CODEC2_SRCS) + +#SM2000_STW_SRCS+=$(USB_VCP) + +sm2000_stw.elf: $(SM2000_STW_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_ADCDUMP_SRCS=\ +src/sm2000_adc_dump.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_usb_vcp.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_ADCDUMP_SRCS_SRCS+=$(CODEC2_SRCS) + +SM2000_ADCDUMP_SRCS+=$(USB_VCP) + +sm2000_adcdump.elf: $(SM2000_ADCDUMP_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# Objects that require the peripheral library +src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack +src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack + +# --------------------------------------------------------------------------------- + +clean: + rm -f *.elf *.bin + rm -f libstm32f4.a + find . ../src -type f -name '*.o' | xargs rm -f diff --git a/codec2/branches/0.6/stm32/cmake/arm_toolchain.txt b/codec2/branches/0.6/stm32/cmake/arm_toolchain.txt new file mode 100644 index 00000000..69e08a3d --- /dev/null +++ b/codec2/branches/0.6/stm32/cmake/arm_toolchain.txt @@ -0,0 +1,13 @@ +INCLUDE(CMakeForceCompiler) + +SET(CMAKE_SYSTEM_NAME Generic) +SET(CMAKE_SYSTEM_VERSION 1) + +# specify the cross compiler +CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU) +CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU) + +SET(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -mthumb-interwork -msoft-float -ffunction-sections -fdata-sections -g -fno-common -fmessage-length=0") +SET(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=gnu++0x") +SET(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu99") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections ") diff --git a/codec2/branches/0.6/stm32/cmake/cmake_header.txt b/codec2/branches/0.6/stm32/cmake/cmake_header.txt new file mode 100644 index 00000000..7be411fe --- /dev/null +++ b/codec2/branches/0.6/stm32/cmake/cmake_header.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) + +#custom command to use objcopy to create .bin files out of ELF files +function(make_mbed_firmware INPUT) + add_custom_command(TARGET ${INPUT} + COMMAND arm-none-eabi-objcopy -O binary ${INPUT} ${INPUT}_${MBED_TARGET}.bin + COMMENT "objcopying to make mbed compatible firmware") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${INPUT}_${MBED_TARGET}.bin) +endfunction(make_mbed_firmware) + +#assume we're using an LPC1768 model if it's not specified by -DMBED_TARGET= + if( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + message(STATUS "invalid or no mbed target specified. Options are LPC1768, LPC2368 or LPC11U24. Assuming LPC1768 for now. + Target may be specified using -DMBED_TARGET=") + set(MBED_TARGET "LPC1768") +endif( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + +set(MBED_INCLUDE "${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/") + +#setup target specific object files +if(MBED_TARGET MATCHES "LPC1768") + set(MBED_PREFIX "LPC17") + set(CORE "cm3") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC17xx.o) +elseif(MBED_TARGET MATCHES "LPC2368") + set(CHIP ${MBED_INCLUDE}vector_functions.o + ${MBED_INCLUDE}vector_realmonitor.o + ${MBED_INCLUDE}vector_table.o) + set(MBED_PREFIX "LPC23") + set(CORE "arm7") +elseif(MBED_TARGET MATCHES "LPC11U24") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC11xx.o) + set(CORE "cm0") + set(MBED_PREFIX "LPC11U") +endif(MBED_TARGET MATCHES "LPC1768") + +#setup precompiled mbed files which will be needed for all projects +set(CHIP ${CHIP} + ${MBED_INCLUDE}system_${MBED_PREFIX}xx.o + ${MBED_INCLUDE}cmsis_nvic.o + ${MBED_INCLUDE}core_${CORE}.o) + +#force min size build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif(NOT CMAKE_BUILD_TYPE) + +#set correct linker script +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \"-T${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/${MBED_TARGET}.ld\" -static") + +#find CodeSourcery Toolchain for appropriate include dirs +find_path(CSPATH arm-none-eabi-g++ PATHS ENV) +message(STATUS "${CSPATH} is where CodeSourcery is installed") + +#setup directories for appropriate C, C++, mbed libraries and includes +include_directories(${MBED_INCLUDE}) +include_directories(mbed) +include_directories(${CSPATH}../arm-none-eabi/include) +include_directories(${CSPATH}../arm-none-eabi/include/c++/4.6.1) +link_directories(${MBED_INCLUDE}) diff --git a/codec2/branches/0.7/stm32/CMakeLists.txt b/codec2/branches/0.7/stm32/CMakeLists.txt new file mode 100644 index 00000000..d131758a --- /dev/null +++ b/codec2/branches/0.7/stm32/CMakeLists.txt @@ -0,0 +1,941 @@ +# +# stm32f4 Codec2 test programs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# +project(codec2 C) + +cmake_minimum_required(VERSION 2.8) + +include(GNUInstallDirs) +mark_as_advanced(CLEAR + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR +) + +# Include local definitions if they exist. +#-include local.mak + +################################################### + +set(FLOAT_TYPE "hard" CACHE) + +################################################### +# Replace with toolchain file +#CROSS_COMPILE ?= arm-none-eabi- +#CC=$(BINPATH)$(CROSS_COMPILE)gcc +#AS=$(BINPATH)$(CROSS_COMPILE)as +#OBJCOPY=$(BINPATH)$(CROSS_COMPILE)objcopy +#SIZE=$(BINPATH)$(CROSS_COMPILE)size +#SUDO ?= sudo + +################################################### + +# Set default C++ flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -std=gnu11 -Tstm32_flash.ld -DSTM32F40_41xxx -DCORTEX_M4 -mlittle-endian -mthumb -mthumb-interwork -nostartfiles -mcpu=cortex-m4") + +if(FLOAT_TYPE hard) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant -Wdouble-promotion -mfpu=fpv4-sp-d16 -mfloat-abi=hard -D__FPU_PRESENT=1 -D__FPU_USED=1") + #CFLAGS += -fsingle-precision-constant +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") +endif() + +# Sync up build flags if other build types are specified. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") + +################################################### + +# Definitions for the STM32F4 Standard Peripheral Library + +PERIPHLIBURL = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware +PERIPHLIBZIP = stm32f4_dsp_stdperiph_lib.zip +PERIPHLIBVER = V1.7.1 +PERIPHLIBNAME = STM32F4xx_DSP_StdPeriph_Lib +PERIPHLIBDIR = $(PERIPHLIBNAME) +CMSIS = $(PERIPHLIBDIR)/Libraries/CMSIS +STM32F4LIB = $(PERIPHLIBDIR)/Libraries/STM32F4xx_StdPeriph_Driver +STM32F4TEMPLATE = $(PERIPHLIBDIR)/Project/STM32F4xx_StdPeriph_Templates +DSPLIB = $(PERIPHLIBDIR)/Libraries/CMSIS/DSP_Lib + +CFLAGS += -DUSE_STDPERIPH_DRIVER -I$(STM32F4LIB)/inc -I$(STM32F4TEMPLATE) +CFLAGS += -I$(CMSIS)/Include -I$(CMSIS)/Device/ST/STM32F4xx/Include +CFLAGS += -DARM_MATH_CM4 + +# Precious files that should be preserved at all cost! +.PRECIOUS: dl/$(PERIPHLIBZIP) + +set(STM32F4LIB_SRCS +$(STM32F4LIB)/src/misc.c\ +$(STM32F4LIB)/src/stm32f4xx_adc.c\ +$(STM32F4LIB)/src/stm32f4xx_can.c\ +$(STM32F4LIB)/src/stm32f4xx_cec.c\ +$(STM32F4LIB)/src/stm32f4xx_crc.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_aes.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_des.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_tdes.c\ +$(STM32F4LIB)/src/stm32f4xx_dac.c\ +$(STM32F4LIB)/src/stm32f4xx_dbgmcu.c\ +$(STM32F4LIB)/src/stm32f4xx_dcmi.c\ +$(STM32F4LIB)/src/stm32f4xx_dma2d.c\ +$(STM32F4LIB)/src/stm32f4xx_dma.c\ +$(STM32F4LIB)/src/stm32f4xx_exti.c\ +$(STM32F4LIB)/src/stm32f4xx_flash.c\ +$(STM32F4LIB)/src/stm32f4xx_flash_ramfunc.c\ +$(STM32F4LIB)/src/stm32f4xx_fmpi2c.c\ +$(STM32F4LIB)/src/stm32f4xx_fsmc.c\ +$(STM32F4LIB)/src/stm32f4xx_gpio.c\ +$(STM32F4LIB)/src/stm32f4xx_hash.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_md5.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_sha1.c\ +$(STM32F4LIB)/src/stm32f4xx_i2c.c\ +$(STM32F4LIB)/src/stm32f4xx_iwdg.c\ +$(STM32F4LIB)/src/stm32f4xx_ltdc.c\ +$(STM32F4LIB)/src/stm32f4xx_pwr.c\ +$(STM32F4LIB)/src/stm32f4xx_qspi.c\ +$(STM32F4LIB)/src/stm32f4xx_rcc.c\ +$(STM32F4LIB)/src/stm32f4xx_rng.c\ +$(STM32F4LIB)/src/stm32f4xx_rtc.c\ +$(STM32F4LIB)/src/stm32f4xx_sai.c\ +$(STM32F4LIB)/src/stm32f4xx_sdio.c\ +$(STM32F4LIB)/src/stm32f4xx_spdifrx.c\ +$(STM32F4LIB)/src/stm32f4xx_spi.c\ +$(STM32F4LIB)/src/stm32f4xx_syscfg.c\ +$(STM32F4LIB)/src/stm32f4xx_tim.c\ +$(STM32F4LIB)/src/stm32f4xx_usart.c\ +$(STM32F4LIB)/src/stm32f4xx_wwdg.c +# Not compiling for now +# $(STM32F4LIB)/src/stm32f4xx_fmc.c +) + + +set(STM32F4LIB_OBJS $(STM32F4LIB_SRCS:.c=.o)) + +set(CMSIS_SRCS +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q7.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_common_tables.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_const_structs.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f64.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix8_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c\ +) + +CMSIS_OBJS = $(CMSIS_SRCS:.c=.o) $(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal2.o + +################################################### + +# Codec 2 + +set(CODEC2_SRC ../src) +set(CODEC2_SRCS +$(CODEC2_SRC)/lpc.c \ +$(CODEC2_SRC)/nlp.c \ +$(CODEC2_SRC)/postfilter.c \ +$(CODEC2_SRC)/sine.c \ +$(CODEC2_SRC)/codec2.c \ +$(CODEC2_SRC)/codec2_fft.c \ +$(CODEC2_SRC)/kiss_fft.c \ +$(CODEC2_SRC)/kiss_fftr.c \ +$(CODEC2_SRC)/interp.c \ +$(CODEC2_SRC)/lsp.c \ +$(CODEC2_SRC)/phase.c \ +$(CODEC2_SRC)/quantise.c \ +$(CODEC2_SRC)/pack.c \ +$(CODEC2_SRC)/codebook.c \ +$(CODEC2_SRC)/codebookd.c \ +$(CODEC2_SRC)/codebookjvm.c \ +$(CODEC2_SRC)/codebookge.c \ +$(CODEC2_SRC)/dump.c \ +$(CODEC2_SRC)/fdmdv.c \ +$(CODEC2_SRC)/freedv_api.c \ +$(CODEC2_SRC)/varicode.c \ +$(CODEC2_SRC)/golay23.c \ +$(CODEC2_SRC)/fsk.c \ +$(CODEC2_SRC)/fmfsk.c \ +$(CODEC2_SRC)/freedv_vhf_framing.c \ +$(CODEC2_SRC)/freedv_data_channel.c +) +CFLAGS += -D__EMBEDDED__ + +#enable this for dump files to help verify optimisation +#CFLAGS += -DDUMP + +include_directories(../src ../unittest inc) + +FFT_TEST_SRCS = \ +$(DSPLIB)/Examples/arm_fft_bin_example/GCC/arm_fft_bin_data.c \ +fft_test.c \ +src/startup_stm32f4xx.s \ +src/system_stm32f4xx.c \ +stm32f4_machdep.c \ +gdb_stdio.c \ +../src/kiss_fft.c + +################################################### + +vpath %.c src +vpath %.a lib + +ROOT=$(shell pwd) + +# Library paths + +LIBPATHS = + +# Libraries to link + +LIBS = -lg -lnosys -lm +#Uncomment for standard arm semihosting +#LIBS = -lg -lrdimon -lm --specs=rdimon.specs + +# startup file + +SRCS += src/startup_stm32f4xx.s src/init.c + +#OBJS = $(SRCS:.c=.o) + +#all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin mco_ut.bin sm2000_stw.bin sm2000_adcdump.bin sm2000_rxdemo.bin + +# Rule for making directories automatically. +# Note we don't use -p as it's a GNU extension. +%/.md: + parent=$(shell dirname $(@D) ); \ + [ -d $${parent} ] || $(MAKE) $${parent}/.md + [ -d $(@D) ] || mkdir $(@D) + touch $@ + +dl/$(PERIPHLIBZIP): dl/.md + wget -O$@.part -c $(PERIPHLIBURL)/$(PERIPHLIBZIP) + mv $@.part $@ + touch $@ + +$(PERIPHLIBDIR)/.unpack: dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR)_$(PERIPHLIBVER) || \ + rm -fr $(PERIPHLIBDIR)_$(PERIPHLIBVER) + unzip dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR) || rm -fr $(PERIPHLIBDIR) + mv $(PERIPHLIBDIR)_$(PERIPHLIBVER) $(PERIPHLIBDIR) + touch $@ + +$(CMSIS_OBJS) $(STM32F4LIB_OBJS): $(PERIPHLIBDIR)/.unpack + +libstm32f4.a: $(CMSIS_OBJS) $(STM32F4LIB_OBJS) + find $(PERIPHLIBDIR) -type f -name '*.o' -exec $(AR) crs libstm32f4.a {} ";" + +# Kludgy target to build a file with CFLAGS -O3 +%.O3.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -O3 -c -o $@ $< + +# Kludgy target to build a file with CFLAGS -DPROFILE +%.profile.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILE -c -o $@ $< + +# Rule for building .bin files from a .elf +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +#################################################### + +CODEC2_PROFILE_SRCS=\ +src/codec2_profile.c \ +src/gdb_stdio.c \ +src/stm32f4_machdep.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/system_stm32f4xx.c +CODEC2_PROFILE_SRCS += $(CODEC2_SRCS) + +codec2_profile.elf: $(CODEC2_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +fft_test.elf: $(FFT_TEST_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_UT_SRCS=\ +src/dac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_ut.elf: $(DAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +FAST_DAC_UT_SRCS=\ +src/fast_dac_ut.c \ +../src/fifo.c \ +src/iir_duc.c \ +src/gdb_stdio.c \ +src/stm32f4_dacduc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +fast_dac_ut.elf: $(FAST_DAC_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADCDAC_UT_SRCS=\ +src/adcdac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adcdac_ut.elf: $(ADCDAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_PLAY_SRCS=\ +src/dac_play.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_play.elf: $(DAC_PLAY_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_SRCS=\ +src/adc_rec.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_rec.elf: $(ADC_REC_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_SD_SRCS=\ +src/adc_sd.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_sd.elf: $(ADC_SD_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +PWM_UT_SRCS=\ +gdb_stdio.c \ +src/stm32f4_pwm.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +pwm_ut.elf: $(PWM_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +POWER_UT_SRCS=\ +src/power_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c \ + +POWER_UT_SRCS += $(CODEC2_SRCS) + +power_ut.elf: $(POWER_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +USB_VCP=\ +usb_conf/usb_bsp.c \ +usb_conf/usbd_desc.c \ +usb_conf/usbd_usr.c \ +usb_lib/cdc/usbd_cdc_core.c \ +usb_lib/cdc/usbd_cdc_vcp.c \ +usb_lib/core/usbd_core.c \ +usb_lib/core/usbd_ioreq.c \ +usb_lib/core/usbd_req.c \ +usb_lib/otg/usb_core.c \ +usb_lib/otg/usb_dcd.c \ +usb_lib/otg/usb_dcd_int.c + +USB_VCP_UT=\ +src/usb_vcp_ut.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +USB_VCP_UT+=$(USB_VCP) + +CFLAGS += -DUSE_USB_OTG_FS -DUSE_ULPI_PHY -Iusb_conf -Iusb_lib/cdc -Iusb_lib/core -Iusb_lib/otg + +usb_vcp_ut.elf: $(USB_VCP_UT:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_USB_SRCS=\ +src/adc_rec_usb.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +ADC_REC_USB_SRCS+=$(USB_VCP) + +adc_rec_usb.elf: $(ADC_REC_USB_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + + +FDMDV_PROFILE_SRCS=\ +src/fdmdv_profile.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c + +FDMDV_PROFILE_SRCS += $(CODEC2_SRCS) + +fdmdv_profile.elf: $(FDMDV_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_LEDS_SWITCHES_UT_SRCS=\ +src/sm1000_leds_switches_ut.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +sm1000_leds_switches_ut.elf: $(SM1000_LEDS_SWITCHES_UT_SRCS:.c=.o) \ + libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_SRCS=\ +src/sm1000_main.c \ +src/tone.c \ +src/sfx.c \ +src/sounds.c \ +src/morse.c \ +src/menu.c \ +src/tot.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/stm32f4_vrom.c \ +src/init.c + +SM1000_SRCS += $(CODEC2_SRCS) + +src/stm32f4_dac.o: src/stm32f4_dac.c + $(CC) $(CFLAGS) $^ -c -o $@ + +src/stm32f4_adc.o: src/stm32f4_adc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +sm1000.elf: $(SM1000_SRCS:.c=.O3.o) src/stm32f4_dac.O3.o \ + src/stm32f4_adc.O3.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_TX_PROFILE_SRCS=\ +src/freedv_tx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_TX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_tx_profile.elf: $(FREEDV_TX_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_RX_PROFILE_SRCS=\ +src/freedv_rx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_RX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_rx_profile.elf: $(FREEDV_RX_PROFILE_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +FDMDV_DUMP_RT_SRCS=\ +src/fdmdv_dump_rt.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FDMDV_DUMP_RT_SRCS += $(CODEC2_SRCS) + +fdmdv_dump_rt.elf: $(FDMDV_DUMP_RT_SRCS:.c=.O3.o) \ + src/stm32f4_dac.O3.o src/stm32f4_adc.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +TUNER_UT_SRCS=\ +src/tuner_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +../src/fm.c + +# this needs to be compiled without the optimiser or ugly things happen +# would be nice to work out why as ISRs need to run fast + +src/stm32f4_adc_tuner.o: src/stm32f4_adc_tuner.c + $(CC) $(CFLAGS) $^ -c -o $@ + +tuner_ut.elf: $(TUNER_UT_SRCS:.c=.O3.o) \ + src/stm32f4_adc_tuner.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +ADC_SFDR_UT_SRCS=\ +src/adc_sfdr_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ + libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + + +FM_LODUC_PLAY_SRCS=\ +src/fm_loduc_play.c \ +gdb_stdio.c \ +../src/fifo.c \ +../src/fm.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +src/stm32f4_dacloduc.o: src/stm32f4_dacloduc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +MCO_UT_SRCS=\ +src/mco_ut.c \ +src/tm_stm32f4_mco_output.c \ +src/tm_stm32f4_gpio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +mco_ut.elf: $(MCO_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- + +SM2000_RXDEMO_SRCS=\ +src/sm2000_rxdemo.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_RXDEMO_SRCS+=$(CODEC2_SRCS) + +sm2000_rxdemo.elf: $(SM2000_RXDEMO_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_STW_SRCS=\ +src/sm2000_stw.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_STW_SRCS+=$(CODEC2_SRCS) + +#SM2000_STW_SRCS+=$(USB_VCP) + +sm2000_stw.elf: $(SM2000_STW_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_ADCDUMP_SRCS=\ +src/sm2000_adc_dump.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_usb_vcp.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_ADCDUMP_SRCS_SRCS+=$(CODEC2_SRCS) + +SM2000_ADCDUMP_SRCS+=$(USB_VCP) + +sm2000_adcdump.elf: $(SM2000_ADCDUMP_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# Objects that require the peripheral library +src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack +src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack + +# --------------------------------------------------------------------------------- + +clean: + rm -f *.elf *.bin + rm -f libstm32f4.a + find . ../src -type f -name '*.o' | xargs rm -f diff --git a/codec2/branches/0.7/stm32/cmake/arm_toolchain.txt b/codec2/branches/0.7/stm32/cmake/arm_toolchain.txt new file mode 100644 index 00000000..69e08a3d --- /dev/null +++ b/codec2/branches/0.7/stm32/cmake/arm_toolchain.txt @@ -0,0 +1,13 @@ +INCLUDE(CMakeForceCompiler) + +SET(CMAKE_SYSTEM_NAME Generic) +SET(CMAKE_SYSTEM_VERSION 1) + +# specify the cross compiler +CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU) +CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU) + +SET(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -mthumb-interwork -msoft-float -ffunction-sections -fdata-sections -g -fno-common -fmessage-length=0") +SET(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=gnu++0x") +SET(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu99") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections ") diff --git a/codec2/branches/0.7/stm32/cmake/cmake_header.txt b/codec2/branches/0.7/stm32/cmake/cmake_header.txt new file mode 100644 index 00000000..7be411fe --- /dev/null +++ b/codec2/branches/0.7/stm32/cmake/cmake_header.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) + +#custom command to use objcopy to create .bin files out of ELF files +function(make_mbed_firmware INPUT) + add_custom_command(TARGET ${INPUT} + COMMAND arm-none-eabi-objcopy -O binary ${INPUT} ${INPUT}_${MBED_TARGET}.bin + COMMENT "objcopying to make mbed compatible firmware") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${INPUT}_${MBED_TARGET}.bin) +endfunction(make_mbed_firmware) + +#assume we're using an LPC1768 model if it's not specified by -DMBED_TARGET= + if( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + message(STATUS "invalid or no mbed target specified. Options are LPC1768, LPC2368 or LPC11U24. Assuming LPC1768 for now. + Target may be specified using -DMBED_TARGET=") + set(MBED_TARGET "LPC1768") +endif( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + +set(MBED_INCLUDE "${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/") + +#setup target specific object files +if(MBED_TARGET MATCHES "LPC1768") + set(MBED_PREFIX "LPC17") + set(CORE "cm3") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC17xx.o) +elseif(MBED_TARGET MATCHES "LPC2368") + set(CHIP ${MBED_INCLUDE}vector_functions.o + ${MBED_INCLUDE}vector_realmonitor.o + ${MBED_INCLUDE}vector_table.o) + set(MBED_PREFIX "LPC23") + set(CORE "arm7") +elseif(MBED_TARGET MATCHES "LPC11U24") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC11xx.o) + set(CORE "cm0") + set(MBED_PREFIX "LPC11U") +endif(MBED_TARGET MATCHES "LPC1768") + +#setup precompiled mbed files which will be needed for all projects +set(CHIP ${CHIP} + ${MBED_INCLUDE}system_${MBED_PREFIX}xx.o + ${MBED_INCLUDE}cmsis_nvic.o + ${MBED_INCLUDE}core_${CORE}.o) + +#force min size build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif(NOT CMAKE_BUILD_TYPE) + +#set correct linker script +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \"-T${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/${MBED_TARGET}.ld\" -static") + +#find CodeSourcery Toolchain for appropriate include dirs +find_path(CSPATH arm-none-eabi-g++ PATHS ENV) +message(STATUS "${CSPATH} is where CodeSourcery is installed") + +#setup directories for appropriate C, C++, mbed libraries and includes +include_directories(${MBED_INCLUDE}) +include_directories(mbed) +include_directories(${CSPATH}../arm-none-eabi/include) +include_directories(${CSPATH}../arm-none-eabi/include/c++/4.6.1) +link_directories(${MBED_INCLUDE}) diff --git a/codec2/tags/0.6/stm32/CMakeLists.txt b/codec2/tags/0.6/stm32/CMakeLists.txt new file mode 100644 index 00000000..d131758a --- /dev/null +++ b/codec2/tags/0.6/stm32/CMakeLists.txt @@ -0,0 +1,941 @@ +# +# stm32f4 Codec2 test programs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# +project(codec2 C) + +cmake_minimum_required(VERSION 2.8) + +include(GNUInstallDirs) +mark_as_advanced(CLEAR + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR +) + +# Include local definitions if they exist. +#-include local.mak + +################################################### + +set(FLOAT_TYPE "hard" CACHE) + +################################################### +# Replace with toolchain file +#CROSS_COMPILE ?= arm-none-eabi- +#CC=$(BINPATH)$(CROSS_COMPILE)gcc +#AS=$(BINPATH)$(CROSS_COMPILE)as +#OBJCOPY=$(BINPATH)$(CROSS_COMPILE)objcopy +#SIZE=$(BINPATH)$(CROSS_COMPILE)size +#SUDO ?= sudo + +################################################### + +# Set default C++ flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -std=gnu11 -Tstm32_flash.ld -DSTM32F40_41xxx -DCORTEX_M4 -mlittle-endian -mthumb -mthumb-interwork -nostartfiles -mcpu=cortex-m4") + +if(FLOAT_TYPE hard) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant -Wdouble-promotion -mfpu=fpv4-sp-d16 -mfloat-abi=hard -D__FPU_PRESENT=1 -D__FPU_USED=1") + #CFLAGS += -fsingle-precision-constant +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") +endif() + +# Sync up build flags if other build types are specified. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") + +################################################### + +# Definitions for the STM32F4 Standard Peripheral Library + +PERIPHLIBURL = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware +PERIPHLIBZIP = stm32f4_dsp_stdperiph_lib.zip +PERIPHLIBVER = V1.7.1 +PERIPHLIBNAME = STM32F4xx_DSP_StdPeriph_Lib +PERIPHLIBDIR = $(PERIPHLIBNAME) +CMSIS = $(PERIPHLIBDIR)/Libraries/CMSIS +STM32F4LIB = $(PERIPHLIBDIR)/Libraries/STM32F4xx_StdPeriph_Driver +STM32F4TEMPLATE = $(PERIPHLIBDIR)/Project/STM32F4xx_StdPeriph_Templates +DSPLIB = $(PERIPHLIBDIR)/Libraries/CMSIS/DSP_Lib + +CFLAGS += -DUSE_STDPERIPH_DRIVER -I$(STM32F4LIB)/inc -I$(STM32F4TEMPLATE) +CFLAGS += -I$(CMSIS)/Include -I$(CMSIS)/Device/ST/STM32F4xx/Include +CFLAGS += -DARM_MATH_CM4 + +# Precious files that should be preserved at all cost! +.PRECIOUS: dl/$(PERIPHLIBZIP) + +set(STM32F4LIB_SRCS +$(STM32F4LIB)/src/misc.c\ +$(STM32F4LIB)/src/stm32f4xx_adc.c\ +$(STM32F4LIB)/src/stm32f4xx_can.c\ +$(STM32F4LIB)/src/stm32f4xx_cec.c\ +$(STM32F4LIB)/src/stm32f4xx_crc.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_aes.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_des.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_tdes.c\ +$(STM32F4LIB)/src/stm32f4xx_dac.c\ +$(STM32F4LIB)/src/stm32f4xx_dbgmcu.c\ +$(STM32F4LIB)/src/stm32f4xx_dcmi.c\ +$(STM32F4LIB)/src/stm32f4xx_dma2d.c\ +$(STM32F4LIB)/src/stm32f4xx_dma.c\ +$(STM32F4LIB)/src/stm32f4xx_exti.c\ +$(STM32F4LIB)/src/stm32f4xx_flash.c\ +$(STM32F4LIB)/src/stm32f4xx_flash_ramfunc.c\ +$(STM32F4LIB)/src/stm32f4xx_fmpi2c.c\ +$(STM32F4LIB)/src/stm32f4xx_fsmc.c\ +$(STM32F4LIB)/src/stm32f4xx_gpio.c\ +$(STM32F4LIB)/src/stm32f4xx_hash.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_md5.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_sha1.c\ +$(STM32F4LIB)/src/stm32f4xx_i2c.c\ +$(STM32F4LIB)/src/stm32f4xx_iwdg.c\ +$(STM32F4LIB)/src/stm32f4xx_ltdc.c\ +$(STM32F4LIB)/src/stm32f4xx_pwr.c\ +$(STM32F4LIB)/src/stm32f4xx_qspi.c\ +$(STM32F4LIB)/src/stm32f4xx_rcc.c\ +$(STM32F4LIB)/src/stm32f4xx_rng.c\ +$(STM32F4LIB)/src/stm32f4xx_rtc.c\ +$(STM32F4LIB)/src/stm32f4xx_sai.c\ +$(STM32F4LIB)/src/stm32f4xx_sdio.c\ +$(STM32F4LIB)/src/stm32f4xx_spdifrx.c\ +$(STM32F4LIB)/src/stm32f4xx_spi.c\ +$(STM32F4LIB)/src/stm32f4xx_syscfg.c\ +$(STM32F4LIB)/src/stm32f4xx_tim.c\ +$(STM32F4LIB)/src/stm32f4xx_usart.c\ +$(STM32F4LIB)/src/stm32f4xx_wwdg.c +# Not compiling for now +# $(STM32F4LIB)/src/stm32f4xx_fmc.c +) + + +set(STM32F4LIB_OBJS $(STM32F4LIB_SRCS:.c=.o)) + +set(CMSIS_SRCS +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q7.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_common_tables.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_const_structs.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f64.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix8_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c\ +) + +CMSIS_OBJS = $(CMSIS_SRCS:.c=.o) $(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal2.o + +################################################### + +# Codec 2 + +set(CODEC2_SRC ../src) +set(CODEC2_SRCS +$(CODEC2_SRC)/lpc.c \ +$(CODEC2_SRC)/nlp.c \ +$(CODEC2_SRC)/postfilter.c \ +$(CODEC2_SRC)/sine.c \ +$(CODEC2_SRC)/codec2.c \ +$(CODEC2_SRC)/codec2_fft.c \ +$(CODEC2_SRC)/kiss_fft.c \ +$(CODEC2_SRC)/kiss_fftr.c \ +$(CODEC2_SRC)/interp.c \ +$(CODEC2_SRC)/lsp.c \ +$(CODEC2_SRC)/phase.c \ +$(CODEC2_SRC)/quantise.c \ +$(CODEC2_SRC)/pack.c \ +$(CODEC2_SRC)/codebook.c \ +$(CODEC2_SRC)/codebookd.c \ +$(CODEC2_SRC)/codebookjvm.c \ +$(CODEC2_SRC)/codebookge.c \ +$(CODEC2_SRC)/dump.c \ +$(CODEC2_SRC)/fdmdv.c \ +$(CODEC2_SRC)/freedv_api.c \ +$(CODEC2_SRC)/varicode.c \ +$(CODEC2_SRC)/golay23.c \ +$(CODEC2_SRC)/fsk.c \ +$(CODEC2_SRC)/fmfsk.c \ +$(CODEC2_SRC)/freedv_vhf_framing.c \ +$(CODEC2_SRC)/freedv_data_channel.c +) +CFLAGS += -D__EMBEDDED__ + +#enable this for dump files to help verify optimisation +#CFLAGS += -DDUMP + +include_directories(../src ../unittest inc) + +FFT_TEST_SRCS = \ +$(DSPLIB)/Examples/arm_fft_bin_example/GCC/arm_fft_bin_data.c \ +fft_test.c \ +src/startup_stm32f4xx.s \ +src/system_stm32f4xx.c \ +stm32f4_machdep.c \ +gdb_stdio.c \ +../src/kiss_fft.c + +################################################### + +vpath %.c src +vpath %.a lib + +ROOT=$(shell pwd) + +# Library paths + +LIBPATHS = + +# Libraries to link + +LIBS = -lg -lnosys -lm +#Uncomment for standard arm semihosting +#LIBS = -lg -lrdimon -lm --specs=rdimon.specs + +# startup file + +SRCS += src/startup_stm32f4xx.s src/init.c + +#OBJS = $(SRCS:.c=.o) + +#all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin mco_ut.bin sm2000_stw.bin sm2000_adcdump.bin sm2000_rxdemo.bin + +# Rule for making directories automatically. +# Note we don't use -p as it's a GNU extension. +%/.md: + parent=$(shell dirname $(@D) ); \ + [ -d $${parent} ] || $(MAKE) $${parent}/.md + [ -d $(@D) ] || mkdir $(@D) + touch $@ + +dl/$(PERIPHLIBZIP): dl/.md + wget -O$@.part -c $(PERIPHLIBURL)/$(PERIPHLIBZIP) + mv $@.part $@ + touch $@ + +$(PERIPHLIBDIR)/.unpack: dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR)_$(PERIPHLIBVER) || \ + rm -fr $(PERIPHLIBDIR)_$(PERIPHLIBVER) + unzip dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR) || rm -fr $(PERIPHLIBDIR) + mv $(PERIPHLIBDIR)_$(PERIPHLIBVER) $(PERIPHLIBDIR) + touch $@ + +$(CMSIS_OBJS) $(STM32F4LIB_OBJS): $(PERIPHLIBDIR)/.unpack + +libstm32f4.a: $(CMSIS_OBJS) $(STM32F4LIB_OBJS) + find $(PERIPHLIBDIR) -type f -name '*.o' -exec $(AR) crs libstm32f4.a {} ";" + +# Kludgy target to build a file with CFLAGS -O3 +%.O3.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -O3 -c -o $@ $< + +# Kludgy target to build a file with CFLAGS -DPROFILE +%.profile.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILE -c -o $@ $< + +# Rule for building .bin files from a .elf +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +#################################################### + +CODEC2_PROFILE_SRCS=\ +src/codec2_profile.c \ +src/gdb_stdio.c \ +src/stm32f4_machdep.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/system_stm32f4xx.c +CODEC2_PROFILE_SRCS += $(CODEC2_SRCS) + +codec2_profile.elf: $(CODEC2_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +fft_test.elf: $(FFT_TEST_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_UT_SRCS=\ +src/dac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_ut.elf: $(DAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +FAST_DAC_UT_SRCS=\ +src/fast_dac_ut.c \ +../src/fifo.c \ +src/iir_duc.c \ +src/gdb_stdio.c \ +src/stm32f4_dacduc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +fast_dac_ut.elf: $(FAST_DAC_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADCDAC_UT_SRCS=\ +src/adcdac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adcdac_ut.elf: $(ADCDAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_PLAY_SRCS=\ +src/dac_play.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_play.elf: $(DAC_PLAY_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_SRCS=\ +src/adc_rec.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_rec.elf: $(ADC_REC_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_SD_SRCS=\ +src/adc_sd.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_sd.elf: $(ADC_SD_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +PWM_UT_SRCS=\ +gdb_stdio.c \ +src/stm32f4_pwm.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +pwm_ut.elf: $(PWM_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +POWER_UT_SRCS=\ +src/power_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c \ + +POWER_UT_SRCS += $(CODEC2_SRCS) + +power_ut.elf: $(POWER_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +USB_VCP=\ +usb_conf/usb_bsp.c \ +usb_conf/usbd_desc.c \ +usb_conf/usbd_usr.c \ +usb_lib/cdc/usbd_cdc_core.c \ +usb_lib/cdc/usbd_cdc_vcp.c \ +usb_lib/core/usbd_core.c \ +usb_lib/core/usbd_ioreq.c \ +usb_lib/core/usbd_req.c \ +usb_lib/otg/usb_core.c \ +usb_lib/otg/usb_dcd.c \ +usb_lib/otg/usb_dcd_int.c + +USB_VCP_UT=\ +src/usb_vcp_ut.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +USB_VCP_UT+=$(USB_VCP) + +CFLAGS += -DUSE_USB_OTG_FS -DUSE_ULPI_PHY -Iusb_conf -Iusb_lib/cdc -Iusb_lib/core -Iusb_lib/otg + +usb_vcp_ut.elf: $(USB_VCP_UT:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_USB_SRCS=\ +src/adc_rec_usb.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +ADC_REC_USB_SRCS+=$(USB_VCP) + +adc_rec_usb.elf: $(ADC_REC_USB_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + + +FDMDV_PROFILE_SRCS=\ +src/fdmdv_profile.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c + +FDMDV_PROFILE_SRCS += $(CODEC2_SRCS) + +fdmdv_profile.elf: $(FDMDV_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_LEDS_SWITCHES_UT_SRCS=\ +src/sm1000_leds_switches_ut.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +sm1000_leds_switches_ut.elf: $(SM1000_LEDS_SWITCHES_UT_SRCS:.c=.o) \ + libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_SRCS=\ +src/sm1000_main.c \ +src/tone.c \ +src/sfx.c \ +src/sounds.c \ +src/morse.c \ +src/menu.c \ +src/tot.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/stm32f4_vrom.c \ +src/init.c + +SM1000_SRCS += $(CODEC2_SRCS) + +src/stm32f4_dac.o: src/stm32f4_dac.c + $(CC) $(CFLAGS) $^ -c -o $@ + +src/stm32f4_adc.o: src/stm32f4_adc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +sm1000.elf: $(SM1000_SRCS:.c=.O3.o) src/stm32f4_dac.O3.o \ + src/stm32f4_adc.O3.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_TX_PROFILE_SRCS=\ +src/freedv_tx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_TX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_tx_profile.elf: $(FREEDV_TX_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_RX_PROFILE_SRCS=\ +src/freedv_rx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_RX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_rx_profile.elf: $(FREEDV_RX_PROFILE_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +FDMDV_DUMP_RT_SRCS=\ +src/fdmdv_dump_rt.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FDMDV_DUMP_RT_SRCS += $(CODEC2_SRCS) + +fdmdv_dump_rt.elf: $(FDMDV_DUMP_RT_SRCS:.c=.O3.o) \ + src/stm32f4_dac.O3.o src/stm32f4_adc.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +TUNER_UT_SRCS=\ +src/tuner_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +../src/fm.c + +# this needs to be compiled without the optimiser or ugly things happen +# would be nice to work out why as ISRs need to run fast + +src/stm32f4_adc_tuner.o: src/stm32f4_adc_tuner.c + $(CC) $(CFLAGS) $^ -c -o $@ + +tuner_ut.elf: $(TUNER_UT_SRCS:.c=.O3.o) \ + src/stm32f4_adc_tuner.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +ADC_SFDR_UT_SRCS=\ +src/adc_sfdr_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ + libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + + +FM_LODUC_PLAY_SRCS=\ +src/fm_loduc_play.c \ +gdb_stdio.c \ +../src/fifo.c \ +../src/fm.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +src/stm32f4_dacloduc.o: src/stm32f4_dacloduc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +MCO_UT_SRCS=\ +src/mco_ut.c \ +src/tm_stm32f4_mco_output.c \ +src/tm_stm32f4_gpio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +mco_ut.elf: $(MCO_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- + +SM2000_RXDEMO_SRCS=\ +src/sm2000_rxdemo.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_RXDEMO_SRCS+=$(CODEC2_SRCS) + +sm2000_rxdemo.elf: $(SM2000_RXDEMO_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_STW_SRCS=\ +src/sm2000_stw.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_STW_SRCS+=$(CODEC2_SRCS) + +#SM2000_STW_SRCS+=$(USB_VCP) + +sm2000_stw.elf: $(SM2000_STW_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_ADCDUMP_SRCS=\ +src/sm2000_adc_dump.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_usb_vcp.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_ADCDUMP_SRCS_SRCS+=$(CODEC2_SRCS) + +SM2000_ADCDUMP_SRCS+=$(USB_VCP) + +sm2000_adcdump.elf: $(SM2000_ADCDUMP_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# Objects that require the peripheral library +src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack +src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack + +# --------------------------------------------------------------------------------- + +clean: + rm -f *.elf *.bin + rm -f libstm32f4.a + find . ../src -type f -name '*.o' | xargs rm -f diff --git a/codec2/tags/0.6/stm32/cmake/arm_toolchain.txt b/codec2/tags/0.6/stm32/cmake/arm_toolchain.txt new file mode 100644 index 00000000..69e08a3d --- /dev/null +++ b/codec2/tags/0.6/stm32/cmake/arm_toolchain.txt @@ -0,0 +1,13 @@ +INCLUDE(CMakeForceCompiler) + +SET(CMAKE_SYSTEM_NAME Generic) +SET(CMAKE_SYSTEM_VERSION 1) + +# specify the cross compiler +CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU) +CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU) + +SET(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -mthumb-interwork -msoft-float -ffunction-sections -fdata-sections -g -fno-common -fmessage-length=0") +SET(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=gnu++0x") +SET(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu99") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections ") diff --git a/codec2/tags/0.6/stm32/cmake/cmake_header.txt b/codec2/tags/0.6/stm32/cmake/cmake_header.txt new file mode 100644 index 00000000..7be411fe --- /dev/null +++ b/codec2/tags/0.6/stm32/cmake/cmake_header.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) + +#custom command to use objcopy to create .bin files out of ELF files +function(make_mbed_firmware INPUT) + add_custom_command(TARGET ${INPUT} + COMMAND arm-none-eabi-objcopy -O binary ${INPUT} ${INPUT}_${MBED_TARGET}.bin + COMMENT "objcopying to make mbed compatible firmware") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${INPUT}_${MBED_TARGET}.bin) +endfunction(make_mbed_firmware) + +#assume we're using an LPC1768 model if it's not specified by -DMBED_TARGET= + if( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + message(STATUS "invalid or no mbed target specified. Options are LPC1768, LPC2368 or LPC11U24. Assuming LPC1768 for now. + Target may be specified using -DMBED_TARGET=") + set(MBED_TARGET "LPC1768") +endif( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + +set(MBED_INCLUDE "${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/") + +#setup target specific object files +if(MBED_TARGET MATCHES "LPC1768") + set(MBED_PREFIX "LPC17") + set(CORE "cm3") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC17xx.o) +elseif(MBED_TARGET MATCHES "LPC2368") + set(CHIP ${MBED_INCLUDE}vector_functions.o + ${MBED_INCLUDE}vector_realmonitor.o + ${MBED_INCLUDE}vector_table.o) + set(MBED_PREFIX "LPC23") + set(CORE "arm7") +elseif(MBED_TARGET MATCHES "LPC11U24") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC11xx.o) + set(CORE "cm0") + set(MBED_PREFIX "LPC11U") +endif(MBED_TARGET MATCHES "LPC1768") + +#setup precompiled mbed files which will be needed for all projects +set(CHIP ${CHIP} + ${MBED_INCLUDE}system_${MBED_PREFIX}xx.o + ${MBED_INCLUDE}cmsis_nvic.o + ${MBED_INCLUDE}core_${CORE}.o) + +#force min size build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif(NOT CMAKE_BUILD_TYPE) + +#set correct linker script +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \"-T${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/${MBED_TARGET}.ld\" -static") + +#find CodeSourcery Toolchain for appropriate include dirs +find_path(CSPATH arm-none-eabi-g++ PATHS ENV) +message(STATUS "${CSPATH} is where CodeSourcery is installed") + +#setup directories for appropriate C, C++, mbed libraries and includes +include_directories(${MBED_INCLUDE}) +include_directories(mbed) +include_directories(${CSPATH}../arm-none-eabi/include) +include_directories(${CSPATH}../arm-none-eabi/include/c++/4.6.1) +link_directories(${MBED_INCLUDE}) diff --git a/codec2/tags/0.7/stm32/CMakeLists.txt b/codec2/tags/0.7/stm32/CMakeLists.txt new file mode 100644 index 00000000..d131758a --- /dev/null +++ b/codec2/tags/0.7/stm32/CMakeLists.txt @@ -0,0 +1,941 @@ +# +# stm32f4 Codec2 test programs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# +project(codec2 C) + +cmake_minimum_required(VERSION 2.8) + +include(GNUInstallDirs) +mark_as_advanced(CLEAR + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR +) + +# Include local definitions if they exist. +#-include local.mak + +################################################### + +set(FLOAT_TYPE "hard" CACHE) + +################################################### +# Replace with toolchain file +#CROSS_COMPILE ?= arm-none-eabi- +#CC=$(BINPATH)$(CROSS_COMPILE)gcc +#AS=$(BINPATH)$(CROSS_COMPILE)as +#OBJCOPY=$(BINPATH)$(CROSS_COMPILE)objcopy +#SIZE=$(BINPATH)$(CROSS_COMPILE)size +#SUDO ?= sudo + +################################################### + +# Set default C++ flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -std=gnu11 -Tstm32_flash.ld -DSTM32F40_41xxx -DCORTEX_M4 -mlittle-endian -mthumb -mthumb-interwork -nostartfiles -mcpu=cortex-m4") + +if(FLOAT_TYPE hard) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsingle-precision-constant -Wdouble-promotion -mfpu=fpv4-sp-d16 -mfloat-abi=hard -D__FPU_PRESENT=1 -D__FPU_USED=1") + #CFLAGS += -fsingle-precision-constant +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msoft-float") +endif() + +# Sync up build flags if other build types are specified. +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") + +################################################### + +# Definitions for the STM32F4 Standard Peripheral Library + +PERIPHLIBURL = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware +PERIPHLIBZIP = stm32f4_dsp_stdperiph_lib.zip +PERIPHLIBVER = V1.7.1 +PERIPHLIBNAME = STM32F4xx_DSP_StdPeriph_Lib +PERIPHLIBDIR = $(PERIPHLIBNAME) +CMSIS = $(PERIPHLIBDIR)/Libraries/CMSIS +STM32F4LIB = $(PERIPHLIBDIR)/Libraries/STM32F4xx_StdPeriph_Driver +STM32F4TEMPLATE = $(PERIPHLIBDIR)/Project/STM32F4xx_StdPeriph_Templates +DSPLIB = $(PERIPHLIBDIR)/Libraries/CMSIS/DSP_Lib + +CFLAGS += -DUSE_STDPERIPH_DRIVER -I$(STM32F4LIB)/inc -I$(STM32F4TEMPLATE) +CFLAGS += -I$(CMSIS)/Include -I$(CMSIS)/Device/ST/STM32F4xx/Include +CFLAGS += -DARM_MATH_CM4 + +# Precious files that should be preserved at all cost! +.PRECIOUS: dl/$(PERIPHLIBZIP) + +set(STM32F4LIB_SRCS +$(STM32F4LIB)/src/misc.c\ +$(STM32F4LIB)/src/stm32f4xx_adc.c\ +$(STM32F4LIB)/src/stm32f4xx_can.c\ +$(STM32F4LIB)/src/stm32f4xx_cec.c\ +$(STM32F4LIB)/src/stm32f4xx_crc.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_aes.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_des.c\ +$(STM32F4LIB)/src/stm32f4xx_cryp_tdes.c\ +$(STM32F4LIB)/src/stm32f4xx_dac.c\ +$(STM32F4LIB)/src/stm32f4xx_dbgmcu.c\ +$(STM32F4LIB)/src/stm32f4xx_dcmi.c\ +$(STM32F4LIB)/src/stm32f4xx_dma2d.c\ +$(STM32F4LIB)/src/stm32f4xx_dma.c\ +$(STM32F4LIB)/src/stm32f4xx_exti.c\ +$(STM32F4LIB)/src/stm32f4xx_flash.c\ +$(STM32F4LIB)/src/stm32f4xx_flash_ramfunc.c\ +$(STM32F4LIB)/src/stm32f4xx_fmpi2c.c\ +$(STM32F4LIB)/src/stm32f4xx_fsmc.c\ +$(STM32F4LIB)/src/stm32f4xx_gpio.c\ +$(STM32F4LIB)/src/stm32f4xx_hash.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_md5.c\ +$(STM32F4LIB)/src/stm32f4xx_hash_sha1.c\ +$(STM32F4LIB)/src/stm32f4xx_i2c.c\ +$(STM32F4LIB)/src/stm32f4xx_iwdg.c\ +$(STM32F4LIB)/src/stm32f4xx_ltdc.c\ +$(STM32F4LIB)/src/stm32f4xx_pwr.c\ +$(STM32F4LIB)/src/stm32f4xx_qspi.c\ +$(STM32F4LIB)/src/stm32f4xx_rcc.c\ +$(STM32F4LIB)/src/stm32f4xx_rng.c\ +$(STM32F4LIB)/src/stm32f4xx_rtc.c\ +$(STM32F4LIB)/src/stm32f4xx_sai.c\ +$(STM32F4LIB)/src/stm32f4xx_sdio.c\ +$(STM32F4LIB)/src/stm32f4xx_spdifrx.c\ +$(STM32F4LIB)/src/stm32f4xx_spi.c\ +$(STM32F4LIB)/src/stm32f4xx_syscfg.c\ +$(STM32F4LIB)/src/stm32f4xx_tim.c\ +$(STM32F4LIB)/src/stm32f4xx_usart.c\ +$(STM32F4LIB)/src/stm32f4xx_wwdg.c +# Not compiling for now +# $(STM32F4LIB)/src/stm32f4xx_fmc.c +) + + +set(STM32F4LIB_OBJS $(STM32F4LIB_SRCS:.c=.o)) + +set(CMSIS_SRCS +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_abs_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_add_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_dot_prod_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_mult_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_negate_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_offset_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_scale_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_shift_q7.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/BasicMathFunctions/arm_sub_q7.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_common_tables.c\ +$(CMSIS)/DSP_Lib/Source/CommonTables/arm_const_structs.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_conj_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_dot_prod_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mag_squared_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_cmplx_q31.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_f32.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q15.c\ +$(CMSIS)/DSP_Lib/Source/ComplexMathFunctions/arm_cmplx_mult_real_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q15.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_pid_reset_q31.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/ControllerFunctions/arm_sin_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_cos_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_f32.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sin_q31.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FastMathFunctions/arm_sqrt_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_32x64_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df2T_init_f64.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_stereo_df2T_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_partial_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_conv_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_opt_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_correlate_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_decimate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_interpolate_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_init_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_fir_sparse_q7.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_iir_lattice_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_norm_q31.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q15.c\ +$(CMSIS)/DSP_Lib/Source/FilteringFunctions/arm_lms_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_add_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_cmplx_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_inverse_f64.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_fast_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_mult_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_scale_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_sub_q31.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_f32.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q15.c\ +$(CMSIS)/DSP_Lib/Source/MatrixFunctions/arm_mat_trans_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_max_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_mean_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_min_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_power_q7.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_rms_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_std_q31.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_f32.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q15.c\ +$(CMSIS)/DSP_Lib/Source/StatisticsFunctions/arm_var_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_copy_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_f32.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_fill_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_float_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q15_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q31_to_q7.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_float.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q15.c\ +$(CMSIS)/DSP_Lib/Source/SupportFunctions/arm_q7_to_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix2_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_cfft_radix8_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_dct4_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_fast_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_f32.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c\ +$(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c\ +) + +CMSIS_OBJS = $(CMSIS_SRCS:.c=.o) $(CMSIS)/DSP_Lib/Source/TransformFunctions/arm_bitreversal2.o + +################################################### + +# Codec 2 + +set(CODEC2_SRC ../src) +set(CODEC2_SRCS +$(CODEC2_SRC)/lpc.c \ +$(CODEC2_SRC)/nlp.c \ +$(CODEC2_SRC)/postfilter.c \ +$(CODEC2_SRC)/sine.c \ +$(CODEC2_SRC)/codec2.c \ +$(CODEC2_SRC)/codec2_fft.c \ +$(CODEC2_SRC)/kiss_fft.c \ +$(CODEC2_SRC)/kiss_fftr.c \ +$(CODEC2_SRC)/interp.c \ +$(CODEC2_SRC)/lsp.c \ +$(CODEC2_SRC)/phase.c \ +$(CODEC2_SRC)/quantise.c \ +$(CODEC2_SRC)/pack.c \ +$(CODEC2_SRC)/codebook.c \ +$(CODEC2_SRC)/codebookd.c \ +$(CODEC2_SRC)/codebookjvm.c \ +$(CODEC2_SRC)/codebookge.c \ +$(CODEC2_SRC)/dump.c \ +$(CODEC2_SRC)/fdmdv.c \ +$(CODEC2_SRC)/freedv_api.c \ +$(CODEC2_SRC)/varicode.c \ +$(CODEC2_SRC)/golay23.c \ +$(CODEC2_SRC)/fsk.c \ +$(CODEC2_SRC)/fmfsk.c \ +$(CODEC2_SRC)/freedv_vhf_framing.c \ +$(CODEC2_SRC)/freedv_data_channel.c +) +CFLAGS += -D__EMBEDDED__ + +#enable this for dump files to help verify optimisation +#CFLAGS += -DDUMP + +include_directories(../src ../unittest inc) + +FFT_TEST_SRCS = \ +$(DSPLIB)/Examples/arm_fft_bin_example/GCC/arm_fft_bin_data.c \ +fft_test.c \ +src/startup_stm32f4xx.s \ +src/system_stm32f4xx.c \ +stm32f4_machdep.c \ +gdb_stdio.c \ +../src/kiss_fft.c + +################################################### + +vpath %.c src +vpath %.a lib + +ROOT=$(shell pwd) + +# Library paths + +LIBPATHS = + +# Libraries to link + +LIBS = -lg -lnosys -lm +#Uncomment for standard arm semihosting +#LIBS = -lg -lrdimon -lm --specs=rdimon.specs + +# startup file + +SRCS += src/startup_stm32f4xx.s src/init.c + +#OBJS = $(SRCS:.c=.o) + +#all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin mco_ut.bin sm2000_stw.bin sm2000_adcdump.bin sm2000_rxdemo.bin + +# Rule for making directories automatically. +# Note we don't use -p as it's a GNU extension. +%/.md: + parent=$(shell dirname $(@D) ); \ + [ -d $${parent} ] || $(MAKE) $${parent}/.md + [ -d $(@D) ] || mkdir $(@D) + touch $@ + +dl/$(PERIPHLIBZIP): dl/.md + wget -O$@.part -c $(PERIPHLIBURL)/$(PERIPHLIBZIP) + mv $@.part $@ + touch $@ + +$(PERIPHLIBDIR)/.unpack: dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR)_$(PERIPHLIBVER) || \ + rm -fr $(PERIPHLIBDIR)_$(PERIPHLIBVER) + unzip dl/$(PERIPHLIBZIP) + test ! -d $(PERIPHLIBDIR) || rm -fr $(PERIPHLIBDIR) + mv $(PERIPHLIBDIR)_$(PERIPHLIBVER) $(PERIPHLIBDIR) + touch $@ + +$(CMSIS_OBJS) $(STM32F4LIB_OBJS): $(PERIPHLIBDIR)/.unpack + +libstm32f4.a: $(CMSIS_OBJS) $(STM32F4LIB_OBJS) + find $(PERIPHLIBDIR) -type f -name '*.o' -exec $(AR) crs libstm32f4.a {} ";" + +# Kludgy target to build a file with CFLAGS -O3 +%.O3.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -O3 -c -o $@ $< + +# Kludgy target to build a file with CFLAGS -DPROFILE +%.profile.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILE -c -o $@ $< + +# Rule for building .bin files from a .elf +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +# Rule for programming the SM1000 +%.pgm: %.bin + $(SUDO) dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D $< + +#################################################### + +CODEC2_PROFILE_SRCS=\ +src/codec2_profile.c \ +src/gdb_stdio.c \ +src/stm32f4_machdep.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/system_stm32f4xx.c +CODEC2_PROFILE_SRCS += $(CODEC2_SRCS) + +codec2_profile.elf: $(CODEC2_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +fft_test.elf: $(FFT_TEST_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_UT_SRCS=\ +src/dac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_ut.elf: $(DAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +FAST_DAC_UT_SRCS=\ +src/fast_dac_ut.c \ +../src/fifo.c \ +src/iir_duc.c \ +src/gdb_stdio.c \ +src/stm32f4_dacduc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +fast_dac_ut.elf: $(FAST_DAC_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADCDAC_UT_SRCS=\ +src/adcdac_ut.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adcdac_ut.elf: $(ADCDAC_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +DAC_PLAY_SRCS=\ +src/dac_play.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +dac_play.elf: $(DAC_PLAY_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) -O0 $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_SRCS=\ +src/adc_rec.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_rec.elf: $(ADC_REC_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_SD_SRCS=\ +src/adc_sd.c \ +../src/fifo.c \ +gdb_stdio.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +adc_sd.elf: $(ADC_SD_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +PWM_UT_SRCS=\ +gdb_stdio.c \ +src/stm32f4_pwm.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +pwm_ut.elf: $(PWM_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +POWER_UT_SRCS=\ +src/power_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_dac.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c \ + +POWER_UT_SRCS += $(CODEC2_SRCS) + +power_ut.elf: $(POWER_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +USB_VCP=\ +usb_conf/usb_bsp.c \ +usb_conf/usbd_desc.c \ +usb_conf/usbd_usr.c \ +usb_lib/cdc/usbd_cdc_core.c \ +usb_lib/cdc/usbd_cdc_vcp.c \ +usb_lib/core/usbd_core.c \ +usb_lib/core/usbd_ioreq.c \ +usb_lib/core/usbd_req.c \ +usb_lib/otg/usb_core.c \ +usb_lib/otg/usb_dcd.c \ +usb_lib/otg/usb_dcd_int.c + +USB_VCP_UT=\ +src/usb_vcp_ut.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +USB_VCP_UT+=$(USB_VCP) + +CFLAGS += -DUSE_USB_OTG_FS -DUSE_ULPI_PHY -Iusb_conf -Iusb_lib/cdc -Iusb_lib/core -Iusb_lib/otg + +usb_vcp_ut.elf: $(USB_VCP_UT:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +ADC_REC_USB_SRCS=\ +src/adc_rec_usb.c \ +../src/fifo.c \ +src/stm32f4_adc.c \ +src/stm32f4_usb_vcp.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +ADC_REC_USB_SRCS+=$(USB_VCP) + +adc_rec_usb.elf: $(ADC_REC_USB_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + + +FDMDV_PROFILE_SRCS=\ +src/fdmdv_profile.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +src/stm32f4_machdep.c + +FDMDV_PROFILE_SRCS += $(CODEC2_SRCS) + +fdmdv_profile.elf: $(FDMDV_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_LEDS_SWITCHES_UT_SRCS=\ +src/sm1000_leds_switches_ut.c \ +src/sm1000_leds_switches.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +sm1000_leds_switches_ut.elf: $(SM1000_LEDS_SWITCHES_UT_SRCS:.c=.o) \ + libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +SM1000_SRCS=\ +src/sm1000_main.c \ +src/tone.c \ +src/sfx.c \ +src/sounds.c \ +src/morse.c \ +src/menu.c \ +src/tot.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/stm32f4_vrom.c \ +src/init.c + +SM1000_SRCS += $(CODEC2_SRCS) + +src/stm32f4_dac.o: src/stm32f4_dac.c + $(CC) $(CFLAGS) $^ -c -o $@ + +src/stm32f4_adc.o: src/stm32f4_adc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +sm1000.elf: $(SM1000_SRCS:.c=.O3.o) src/stm32f4_dac.O3.o \ + src/stm32f4_adc.O3.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_TX_PROFILE_SRCS=\ +src/freedv_tx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_TX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_tx_profile.elf: $(FREEDV_TX_PROFILE_SRCS:.c=.profile.o) libstm32f4.a + $(CC) $(CFLAGS) -DPROFILE $^ -o $@ $(LIBPATHS) $(LIBS) + +FREEDV_RX_PROFILE_SRCS=\ +src/freedv_rx_profile.c \ +src/stm32f4_machdep.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FREEDV_RX_PROFILE_SRCS += $(CODEC2_SRCS) + +freedv_rx_profile.elf: $(FREEDV_RX_PROFILE_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +FDMDV_DUMP_RT_SRCS=\ +src/fdmdv_dump_rt.c \ +src/sm1000_leds_switches.c \ +../src/fifo.c \ +src/debugblinky.c \ +gdb_stdio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +FDMDV_DUMP_RT_SRCS += $(CODEC2_SRCS) + +fdmdv_dump_rt.elf: $(FDMDV_DUMP_RT_SRCS:.c=.O3.o) \ + src/stm32f4_dac.O3.o src/stm32f4_adc.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +TUNER_UT_SRCS=\ +src/tuner_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/stm32f4_dac.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ +../src/fm.c + +# this needs to be compiled without the optimiser or ugly things happen +# would be nice to work out why as ISRs need to run fast + +src/stm32f4_adc_tuner.o: src/stm32f4_adc_tuner.c + $(CC) $(CFLAGS) $^ -c -o $@ + +tuner_ut.elf: $(TUNER_UT_SRCS:.c=.O3.o) \ + src/stm32f4_adc_tuner.o libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------- + +ADC_SFDR_UT_SRCS=\ +src/adc_sfdr_ut.c \ +gdb_stdio.c \ +../src/fifo.c \ +src/iir_tuner.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ + libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + + +FM_LODUC_PLAY_SRCS=\ +src/fm_loduc_play.c \ +gdb_stdio.c \ +../src/fifo.c \ +../src/fm.c \ +src/debugblinky.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +src/stm32f4_dacloduc.o: src/stm32f4_dacloduc.c + $(CC) $(CFLAGS) $^ -c -o $@ + +fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +MCO_UT_SRCS=\ +src/mco_ut.c \ +src/tm_stm32f4_mco_output.c \ +src/tm_stm32f4_gpio.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +mco_ut.elf: $(MCO_UT_SRCS:.c=.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- + +SM2000_RXDEMO_SRCS=\ +src/sm2000_rxdemo.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_RXDEMO_SRCS+=$(CODEC2_SRCS) + +sm2000_rxdemo.elf: $(SM2000_RXDEMO_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_STW_SRCS=\ +src/sm2000_stw.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_STW_SRCS+=$(CODEC2_SRCS) + +#SM2000_STW_SRCS+=$(USB_VCP) + +sm2000_stw.elf: $(SM2000_STW_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +SM2000_ADCDUMP_SRCS=\ +src/sm2000_adc_dump.c \ +src/sm1000_leds_switches.c \ +src/debugblinky.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/stm32f4_dac.c \ +src/stm32f4_usb_vcp.c \ +src/stm32f4_adc.c \ +../src/fifo.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c + +SM2000_ADCDUMP_SRCS_SRCS+=$(CODEC2_SRCS) + +SM2000_ADCDUMP_SRCS+=$(USB_VCP) + +sm2000_adcdump.elf: $(SM2000_ADCDUMP_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + +# Objects that require the peripheral library +src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack +src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack + +# --------------------------------------------------------------------------------- + +clean: + rm -f *.elf *.bin + rm -f libstm32f4.a + find . ../src -type f -name '*.o' | xargs rm -f diff --git a/codec2/tags/0.7/stm32/cmake/arm_toolchain.txt b/codec2/tags/0.7/stm32/cmake/arm_toolchain.txt new file mode 100644 index 00000000..69e08a3d --- /dev/null +++ b/codec2/tags/0.7/stm32/cmake/arm_toolchain.txt @@ -0,0 +1,13 @@ +INCLUDE(CMakeForceCompiler) + +SET(CMAKE_SYSTEM_NAME Generic) +SET(CMAKE_SYSTEM_VERSION 1) + +# specify the cross compiler +CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU) +CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU) + +SET(COMMON_FLAGS "-mcpu=cortex-m3 -mthumb -mthumb-interwork -msoft-float -ffunction-sections -fdata-sections -g -fno-common -fmessage-length=0") +SET(CMAKE_CXX_FLAGS "${COMMON_FLAGS} -std=gnu++0x") +SET(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu99") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-gc-sections ") diff --git a/codec2/tags/0.7/stm32/cmake/cmake_header.txt b/codec2/tags/0.7/stm32/cmake/cmake_header.txt new file mode 100644 index 00000000..7be411fe --- /dev/null +++ b/codec2/tags/0.7/stm32/cmake/cmake_header.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) + +#custom command to use objcopy to create .bin files out of ELF files +function(make_mbed_firmware INPUT) + add_custom_command(TARGET ${INPUT} + COMMAND arm-none-eabi-objcopy -O binary ${INPUT} ${INPUT}_${MBED_TARGET}.bin + COMMENT "objcopying to make mbed compatible firmware") + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${INPUT}_${MBED_TARGET}.bin) +endfunction(make_mbed_firmware) + +#assume we're using an LPC1768 model if it's not specified by -DMBED_TARGET= + if( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + message(STATUS "invalid or no mbed target specified. Options are LPC1768, LPC2368 or LPC11U24. Assuming LPC1768 for now. + Target may be specified using -DMBED_TARGET=") + set(MBED_TARGET "LPC1768") +endif( NOT MBED_TARGET MATCHES "LPC1768" AND NOT MBED_TARGET MATCHES "LPC2368" AND NOT MBED_TARGET MATCHES "LPC11U24") + +set(MBED_INCLUDE "${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/") + +#setup target specific object files +if(MBED_TARGET MATCHES "LPC1768") + set(MBED_PREFIX "LPC17") + set(CORE "cm3") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC17xx.o) +elseif(MBED_TARGET MATCHES "LPC2368") + set(CHIP ${MBED_INCLUDE}vector_functions.o + ${MBED_INCLUDE}vector_realmonitor.o + ${MBED_INCLUDE}vector_table.o) + set(MBED_PREFIX "LPC23") + set(CORE "arm7") +elseif(MBED_TARGET MATCHES "LPC11U24") + set(CHIP ${MBED_INCLUDE}sys.o + ${MBED_INCLUDE}startup_LPC11xx.o) + set(CORE "cm0") + set(MBED_PREFIX "LPC11U") +endif(MBED_TARGET MATCHES "LPC1768") + +#setup precompiled mbed files which will be needed for all projects +set(CHIP ${CHIP} + ${MBED_INCLUDE}system_${MBED_PREFIX}xx.o + ${MBED_INCLUDE}cmsis_nvic.o + ${MBED_INCLUDE}core_${CORE}.o) + +#force min size build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif(NOT CMAKE_BUILD_TYPE) + +#set correct linker script +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \"-T${CMAKE_SOURCE_DIR}/mbed/${MBED_TARGET}/GCC_CS/${MBED_TARGET}.ld\" -static") + +#find CodeSourcery Toolchain for appropriate include dirs +find_path(CSPATH arm-none-eabi-g++ PATHS ENV) +message(STATUS "${CSPATH} is where CodeSourcery is installed") + +#setup directories for appropriate C, C++, mbed libraries and includes +include_directories(${MBED_INCLUDE}) +include_directories(mbed) +include_directories(${CSPATH}../arm-none-eabi/include) +include_directories(${CSPATH}../arm-none-eabi/include/c++/4.6.1) +link_directories(${MBED_INCLUDE}) diff --git a/freedv-dev/configure_speexdsp_osx.sh b/freedv-dev/configure_speexdsp_osx.sh new file mode 100644 index 00000000..dce37c55 --- /dev/null +++ b/freedv-dev/configure_speexdsp_osx.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +CFLAGS="-g -O2 -mmacosx-version-min=10.7" ./configure --prefix=$1 --disable-examples diff --git a/freedv/tags/1.2.2/.clang/.gitignore b/freedv/tags/1.2.2/.clang/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/freedv/tags/1.2.2/CMakeLists.txt b/freedv/tags/1.2.2/CMakeLists.txt new file mode 100644 index 00000000..a9f70496 --- /dev/null +++ b/freedv/tags/1.2.2/CMakeLists.txt @@ -0,0 +1,463 @@ +# +# FreeDV - HF Digital Voice for Radio Amateurs +# +# CMake configuration contributed by Richard Shaw (KF5OIM) +# Please report questions, comments, problems, or patches to the freetel +# mailing list: https://lists.sourceforge.net/lists/listinfo/freetel-codec2 +# + +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7" CACHE STRING "Minimum OS X deployment version") + +cmake_minimum_required(VERSION 2.8) + +# Prevent in-source builds to protect automake/autoconf config. +# If an in-source build is attempted, you will still need to clean up a few +# files manually. +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + message(FATAL_ERROR "In-source builds in ${CMAKE_BINARY_DIR} are not " + "allowed, please remove ./CMakeCache.txt and ./CMakeFiles/, create a " + "separate build directory and run cmake from there.") +endif("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + +# Set local module path. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +project(FreeDV) + +# +# Set FreeDV version and generate src/version.h +# +set(FREEDV_VERSION_MAJOR 1) +set(FREEDV_VERSION_MINOR 2) +set(FREEDV_VERSION_PATCH 2) +set(FREEDV_VERSION ${FREEDV_VERSION_MAJOR}.${FREEDV_VERSION_MINOR}) +if(FREEDV_VERSION_PATCH) + set(FREEDV_VERSION ${FREEDV_VERSION}.${FREEDV_VERSION_PATCH}) +endif() +set(FREEDV_VERSION_SUFFIX FALSE) +if(FREEDV_VERSION_SUFFIX) + set(FREEDV_VERSION_STRING "${FREEDV_VERSION} ${FREEDV_VERSION_SUFFIX}") +else() + set(FREEDV_VERSION_STRING "${FREEDV_VERSION}") +endif() +message(STATUS "FreeDV version: ${FREEDV_VERSION_STRING}") +configure_file(cmake/version.h.in src/version.h @ONLY) + +# Set default build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") + message(STATUS "Build type not specified, defaulting to ${CMAKE_BUILD_TYPE}") +endif(NOT CMAKE_BUILD_TYPE) + +# Work around for not using a svn working copy. +add_definitions(-D_NO_AUTOTOOLS_) +find_program(SVN_PATH svn) +if(SVN_PATH) + execute_process(COMMAND ${SVN_PATH} info --show-item revision + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE SVN_REVISION_RESULT + OUTPUT_VARIABLE SVN_CURRENT_REVISION + ERROR_QUIET + ) +else() + set(SVN_REVISION_RESULT 1) +endif() + +if(SVN_REVISION_RESULT EQUAL 0) + string(STRIP ${SVN_CURRENT_REVISION} SVN_REVISION) + add_definitions(-DSVN_REVISION="${SVN_REVISION}") +else() + add_definitions(-DSVN_REVISION="None") +endif() + + +# Set default build flags. +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11") +endif(APPLE) + +# +# Setup cmake options +# +set(CMAKE_VERBOSE_MAKEFILE TRUE CACHE BOOL "Verbose makefile.") +set(USE_STATIC_DEPS FALSE CACHE BOOL + "Download and build static libraries instead of system libraries.") +set(USE_STATIC_PORTAUDIO FALSE CACHE BOOL + "Download and build static portaudio instead of the system library.") +set(USE_STATIC_SNDFILE FALSE CACHE BOOL + "Download and build static sndfile instead of the system library.") +set(USE_STATIC_SAMPLERATE FALSE CACHE BOOL + "Download and build static samplerate instead of the system library.") +set(USE_STATIC_CODEC2 TRUE CACHE BOOL + "Download and build static codec2 instead of the system library.") +set(USE_STATIC_SPEEXDSP TRUE CACHE BOOL + "Download and build static speex instead of the system library.") +set(BOOTSTRAP_WXWIDGETS FALSE CACHE BOOL + "Download and build static wxWidgets instead of the system library.") + +if(USE_STATIC_DEPS) + set(USE_STATIC_PORTAUDIO TRUE FORCE) + set(USE_STATIC_SNDFILE TRUE FORCE) + set(USE_STATIC_SAMPLERATE TRUE FORCE) + set(USE_STATIC_CODEC2 TRUE FORCE) +endif(USE_STATIC_DEPS) + +# +# Pull in external wxWidgets target if performing static build. +# +if(BOOTSTRAP_WXWIDGETS) + message(STATUS "Adding wxWidgets build target...") + include(cmake/BuildWxWidgets.cmake) +endif(BOOTSTRAP_WXWIDGETS) + +# +# Perform bootstrap build of wxWidgets +# +if(BOOTSTRAP_WXWIDGETS AND NOT EXISTS ${WXCONFIG}) + message(STATUS "Will perform bootstrap build of wxWidgets. + After make step completes, re-run cmake and make again to perform FreeDV build.") +# +# Continue normal build if not bootstrapping wxWidgets or is already built. +# +else(BOOTSTRAP_WXWIDGETS AND NOT EXISTS ${WXCONFIG}) + + +# +# Various hacks and work arounds for building under MinGW. +# +if(MINGW) + message(STATUS "System is MinGW.") + # Setup HOST variable. + include(cmake/MinGW.cmake) + # This sets up the exe icon for windows under mingw. + set(RES_FILES "") + set(RES_FILES "${CMAKE_SOURCE_DIR}/contrib/freedv.rc") + set(CMAKE_RC_COMPILER_INIT windres) + enable_language(RC) + set(CMAKE_RC_COMPILE_OBJECT + " -O coff -i -o ") + include(InstallRequiredSystemLibraries) +endif(MINGW) + +# Math library is automatic on MinGW +if(UNIX) + set(CMAKE_REQUIRED_INCLUDES math.h) + set(CMAKE_REQUIRED_LIBRARIES m) +endif(UNIX) + +# Find some standard headers and functions. +include(CheckIncludeFiles) +check_include_files("byteswap.h" HAVE_BYTESWAP_H) +check_include_files("limits.h" HAVE_LIMITS_H) +check_include_files("stddef.h" HAVE_STDDEF_H) +check_include_files("stdlib.h" HAVE_STDLIB_H) +check_include_files("string.h" HAVE_STRING_H) +check_include_files("strings.h" HAVE_STRINGS_H) +check_include_files("ltdl.h" HAVE_LTDL_H) +check_include_files("inttypes.h" HAVE_INTTYPES_H) +check_include_files("sys/stat.h" HAVE_SYS_STAT_H) +check_include_files("sys/types.h" HAVE_SYS_TYPES_H) + +include(CheckTypeSize) +check_type_size("int" SIZEOF_INT) + +include(CheckFunctionExists) +check_function_exists(floor HAVE_FLOOR) +check_function_exists(memset HAVE_MEMSET) +check_function_exists(pow HAVE_POW) +check_function_exists(sqrt HAVE_SQRT) +check_function_exists(fseeko HAVE_FSEEKO) +check_function_exists(fmemopen HAVE_FMEMOPEN) +check_function_exists(strcasecmp HAVE_STRCASECMP) +check_function_exists(vsnprintf HAVE_VSNPRINTF) + +include(CheckSymbolExists) +check_symbol_exists("_fseeki64" "stdio.h" HAVE__FSEEKI64) + +# fdmdv2_main.h requires patching to find config.h as it current looks in the +# source directory and the generated file goes in the binary directory. +configure_file ("${PROJECT_SOURCE_DIR}/cmake/config.h.in" + "${PROJECT_BINARY_DIR}/config.h" ) +include_directories(${PROJECT_BINARY_DIR}) +add_definitions(-DHAVE_CONFIG_H) + +# Config file for bundled sox sources +configure_file("${PROJECT_SOURCE_DIR}/cmake/soxconfig.h.in" + "${PROJECT_BINARY_DIR}/soxconfig.h") + +# Pthread Library +find_package(Threads REQUIRED) +message(STATUS "Threads library flags: ${CMAKE_THREAD_LIBS_INIT}") + +# +# Find codec2 +# +if(NOT USE_STATIC_CODEC2) + message(STATUS "Looking for codec2...") + # 'CONFIG' removed due to incompatibility with cmake version + # in Ubuntu 12.04 (Precise) -- Stuart Longland + find_package(codec2 QUIET) + if(codec2_FOUND) + get_target_property(CODEC2_LIBRARY codec2 LOCATION) + message(STATUS " codec2 library: ${CODEC2_LIBRARY}") + message(STATUS " codec2 headers: ${codec2_INCLUDE_DIRS}") + else() + # Try to find manually + find_path(CODEC2_INCLUDE_DIRS codec2.h + PATH_SUFFIXES codec2) + find_library(CODEC2_LIBRARY NAMES codec2) + if(CODEC2_LIBRARY AND CODEC2_INCLUDE_DIRS) + message(STATUS " codec2 library: ${CODEC2_LIBRARY}") + message(STATUS " codec2 headers: ${CODEC2_INCLUDE_DIRS}") + list(APPEND FREEDV_LINK_LIBS ${CODEC2_LIBRARY}) + include_directories(${CODEC2_INCLUDE_DIRS}) + else() + message(FATAL_ERROR "codec2 library not found. +Linux: +Codec2 may not be in your distribution so build yourself or use the cmake option to build statically into FreeDV. +Windws: +It's easiest to use the cmake option: USE_STATIC_CODEC2" + ) + endif() + endif() +else(NOT USE_STATIC_CODEC2) + message(STATUS "Will attempt static build of codec2.") + include(cmake/BuildCodec2.cmake) +endif(NOT USE_STATIC_CODEC2) + +# +# Find or build portaudio Library +# +if(NOT USE_STATIC_PORTAUDIO) + message(STATUS "Looking for portaudio...") + find_package(Portaudio REQUIRED) + if(PORTAUDIO_FOUND) + message(STATUS " portaudio library: ${PORTAUDIO_LIBRARIES}") + message(STATUS " portaudio headers: ${PORTAUDIO_INCLUDE_DIRS}") + list(APPEND FREEDV_LINK_LIBS ${PORTAUDIO_LIBRARIES}) + include_directories(${PORTAUDIO_INCLUDE_DIRS}) + else() + message(FATAL_ERROR "portaudio library not found. +On Linux systems try installing: + portaudio-devel (RPM based systems) + libportaudio-dev (DEB based systems) +On Windows it's easiest to use the cmake option: USE_STATIC_PORTAUDIO" + ) + endif() + if(NOT ${PORTAUDIO_VERSION} EQUAL 19) + message(WARNING "Portaudio versions other than 19 are known to have issues. You have been warned!") + endif() +else(NOT USE_STATIC_PORTAUDIO) + message(STATUS "Will attempt static build of portaudio.") + include(cmake/BuildPortaudio.cmake) +endif(NOT USE_STATIC_PORTAUDIO) + +# +# Hamlib library +# +message(STATUS "Looking for hamlib...") +find_path(HAMLIB_INCLUDE_DIR hamlib/rig.h) +find_library(HAMLIB_LIBRARY hamlib PATH_SUFFIXES hamlib) +message(STATUS "Hamlib library: ${HAMLIB_LIBRARY}") +message(STATUS "Hamlib headers: ${HAMLIB_INCLUDE_DIR}") +if(HAMLIB_LIBRARY AND HAMLIB_INCLUDE_DIR) + message(STATUS "Hamlib library found.") + include_directories(${HAMLIB_INCLUDE_DIR}) + list(APPEND FREEDV_LINK_LIBS ${HAMLIB_LIBRARY}) +else(HAMLIB_LIBRARY AND HAMLIB_INCLUDE_DIR) + message(FATAL_ERROR "hamlib not found. +On Linux systems try installing: + hamlib-devel (RPM based systems) + libhamlib-dev (DEB based systems)" + ) +endif(HAMLIB_LIBRARY AND HAMLIB_INCLUDE_DIR) + + +# +# Samplerate Library +# +if(NOT USE_STATIC_SAMPLERATE) + message(STATUS "Looking for samplerate...") + find_library(LIBSAMPLERATE samplerate) + find_path(LIBSAMPLERATE_INCLUDE_DIR samplerate.h) + message(STATUS " samplerate library: ${LIBSAMPLERATE}") + message(STATUS " samplerate headers: ${LIBSAMPLERATE_INCLUDE_DIR}") + if(LIBSAMPLERATE AND LIBSAMPLERATE_INCLUDE_DIR) + list(APPEND FREEDV_LINK_LIBS ${LIBSAMPLERATE}) + include_directories(${LIBSAMPLERATE_INCLUDE_DIR}) + else(LIBSTAMPLERATE AND LIBSAMPLERATE_INCLUDE_DIR) + message(FATAL_ERROR "samplerate library not found. +On Linux systems try installing: + samplerate-devel (RPM based systems) + libsamplerate-dev (DEB based systems) +On Windows it's easiest to use the cmake option: USE_STATIC_SAMPLERATE" + ) + endif(LIBSAMPLERATE AND LIBSAMPLERATE_INCLUDE_DIR) +else(NOT USE_STATIC_SAMPLERATE) + message(STATUS "Will attempt static build of samplerate.") + include(cmake/BuildSamplerate.cmake) +endif(NOT USE_STATIC_SAMPLERATE) + +# +# sndfile Library +# +if(NOT USE_STATIC_SNDFILE) + message(STATUS "Looking for sndfile...") + find_library(LIBSNDFILE sndfile) + find_path(LIBSNDFILE_INCLUDE_DIR sndfile.h) + message(STATUS " sndfile library: ${LIBSNDFILE}") + message(STATUS " sndfile headers: ${LIBSNDFILE_INCLUDE_DIR}") + if(LIBSNDFILE AND LIBSNDFILE_INCLUDE_DIR) + list(APPEND FREEDV_LINK_LIBS ${LIBSNDFILE}) + else(LIBSNDFILE AND LIBSNDFILE_INCLUDE_DIR) + message(FATAL_ERROR "sndfile library not found. +On Linux systems try installing: + libsndfile-devel (RPM based systems) + libsndfile-dev (DEB based systems) +On Windows it's easiest to use the cmake option: USE_STATIC_SNDFILE" + ) + endif(LIBSNDFILE AND LIBSNDFILE_INCLUDE_DIR) +else(NOT USE_STATIC_SNDFILE) + message(STATUS "Will attempt static build of sndfile.") + include(cmake/BuildSndfile.cmake) +endif(NOT USE_STATIC_SNDFILE) + +# +# Find wxWidgets +# +if(NOT BOOTSTRAP_WXWIDGETS) + set(WXCONFIG "" CACHE FILEPATH "Location of wx-config binary.") + set(WXRC "" CACHE FILEPATH "Location of wxrc binary.") +endif(NOT BOOTSTRAP_WXWIDGETS) +#if(BOOTSTRAP_WXWIDGETS) +# set(WXCONFIG "${CMAKE_BINARY_DIR}/external/dist/bin/wx-config") +# set(WXRC "${CMAKE_BINARY_DIR}/external/dist/bin/wxrc") +# list(APPEND FREEDV_STATIC_DEPS wxWidgets) +#endif(BOOTSTRAP_WXWIDGETS) +message(STATUS "Looking for wxWidgets...") +if(WXCONFIG) + message(STATUS "wx-config: ${WXCONFIG}") + set(wxWidgets_CONFIG_EXECUTABLE ${WXCONFIG}) +endif(WXCONFIG) +if(WXRC) + message(STATUS "wxrc: ${WXRC}") + set(wxWidgets_wxrc_EXECUTABLE ${WXRC}) +endif(WXRC) +set(WX_VERSION_MIN 3.0.0) +find_package(wxWidgets REQUIRED core base aui html net adv) +execute_process(COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" --version + OUTPUT_VARIABLE WX_VERSION) +string(STRIP ${WX_VERSION} WX_VERSION) +if(WX_VERSION VERSION_EQUAL ${WX_VERSION_MIN} + OR WX_VERSION VERSION_GREATER ${WX_VERSION_MIN}) + message(STATUS "wxWidgets version: ${WX_VERSION}") +else() + message(FATAL_ERROR "wxWidgets must be installed on your system. +Please check that wx-config is in path, the directory +where wxWidgets libraries are installed (returned by +'wx-config --libs' or 'wx-config --static --libs' command) +is in LD_LIBRARY_PATH or equivalent variable and +wxWidgets version is ${WX_VERSION_MIN} or above.") +endif() +if(wxWidgets_FOUND) + include("${wxWidgets_USE_FILE}") + list(APPEND FREEDV_LINK_LIBS ${wxWidgets_LIBRARIES}) +endif(wxWidgets_FOUND) + +# +# Find speex library +# +if(NOT USE_STATIC_SPEEXDSP) + message(STATUS "Looking for Speex DSP library.") + find_path(SPEEXDSP_INCLUDE_DIR NAMES speex/speex.h speex/speexdsp_types.h) + find_library(SPEEXDSP_LIBRARY speexdsp) + message(STATUS " Speex DSP headers: ${SPEEXDSP_INCLUDE_DIR}") + message(STATUS " Speex DSP library: ${SPEEXDSP_LIBRARY}") + if(SPEEXDSP_INCLUDE_DIR AND SPEEXDSP_LIBRARY) + include_directories(${SPEEXDSP_INCLUDE_DIR}) + list(APPEND FREEDV_LINK_LIBS ${SPEEXDSP_LIBRARY}) + else(SPEEXDSP_INCLUDE_DIR AND SPEEXDSP_LIBRARY) + message(FATAL_ERROR "Speex DSP library not found!") + endif(SPEEXDSP_INCLUDE_DIR AND SPEEXDSP_LIBRARY) +else() + message(STATUS "Will attempt static build of speex.") + include(cmake/BuildSpeex.cmake) +endif() + +# +# Find libdl for dlopen/dlclose +# +if(UNIX) + message(STATUS "Looking for dl library.") + find_library(DL_LIBRARY dl) + if(DL_LIBRARY) + message(STATUS " dl library: ${DL_LIBRARY}") + list(APPEND FREEDV_LINK_LIBS ${DL_LIBRARY}) + else() + message(FATAL_ERROR "dl library not found. +On Linux systems try installing: + glibc-devel (RPM based systems) + glibc-dev (DEB based systems)" + ) + endif() +endif(UNIX) + + +#Freedv +add_subdirectory(src) + +# Icons and desktop file +add_subdirectory(contrib) + +message(STATUS "Build type will be: ${CMAKE_BUILD_TYPE}") + +# +# Cpack NSIS configuration for Windows. +# +if(WIN32) + # Detect if we're doing a 32-bit or 64-bit windows build. + if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(CMAKE_CL_64 TRUE) + set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64") + endif() + if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(CPACK_STRIP_FILES TRUE) + endif() + configure_file(cmake/GetDependencies.cmake.in cmake/GetDependencies.cmake + @ONLY + ) + install(SCRIPT ${CMAKE_BINARY_DIR}/cmake/GetDependencies.cmake) + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "HF Digital Voice for Radio Amateurs") + set(CPACK_PACKAGE_VENDOR "CMake") + #set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") + set(CPACK_PACKAGE_VERSION_MAJOR ${FREEDV_VERSION_MAJOR}) + set(CPACK_PACKAGE_VERSION_MINOR ${FREEDV_VERSION_MINOR}) + # CPack expects a patch level version so set it here and override if we + # are actually setting one. + set(CPACK_PACKAGE_VERSION_PATCH 0) + if(FREEDV_VERSION_PATCH) + set(CPACK_PACKAGE_VERSION_PATCH ${FREEDV_VERSION_PATCH}) + endif() + if(FREEDV_VERSION_SUFFIX) + set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH}-${FREEDV_VERSION_SUFFIX}") + endif() + # There is a bug in NSI that does not handle full unix paths properly. Make + # sure there is at least one set of four (4) backlasshes. + #set(CPACK_PACKAGE_ICON "${CMake_SOURCE_DIR}/Utilities/Release\\\\InstallIcon.bmp") + set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\freedv.exe") + set(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY}") + set(CPACK_NSIS_PACKAGE_NAME "FreeDV") + set(CPACK_PACKAGE_EXECUTABLES freedv;FreeDV) + set(CPACK_NSIS_URL_INFO_ABOUT "http://freedv.org") + set(CPACK_NSIS_MODIFY_PATH OFF) + set(CPACK_NSIS_MENU_LINKS + "http://freedv.org" "FreeDV Homepage") + include(CPack) +endif(WIN32) + +endif(BOOTSTRAP_WXWIDGETS AND NOT EXISTS ${WXCONFIG}) diff --git a/freedv/tags/1.2.2/COPYING b/freedv/tags/1.2.2/COPYING new file mode 100644 index 00000000..cfd4e991 --- /dev/null +++ b/freedv/tags/1.2.2/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see + . + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/freedv/tags/1.2.2/README.osx b/freedv/tags/1.2.2/README.osx new file mode 100644 index 00000000..c71544ff --- /dev/null +++ b/freedv/tags/1.2.2/README.osx @@ -0,0 +1,107 @@ +Building under OSX is similar to building under linux, but there are some additional steps that need to be performed to produce a working app-bundle. + +For the following instructions, I'm assuming you will be placing everything in: +/Users//Dev/ + +1/ DEPENDENCIES +Using Macports, most of the appropriate dependencies can be installed by: + +$ sudo port install subversion git libtool libsamplerate sox portaudio dylibbundler cmake + +It should be fairly similar using HomeBrew, but you will need to replace all the /opt/ paths in the following instructions. + +1.1/ HAMLIB +First, we will need to build hamlib from source, as we need hamlib to be statically compiled (Macports won't do this..) + +$ git clone git://git.code.sf.net/p/hamlib/code hamlib-code +$ cd hamlib-code + +You will now need to edit line 12 of autogen.sh, to change "libtoolize" to "glibtoolize" + +$ ./autogen.sh +$ ./configure --disable-shared --prefix /Users//Dev/hamlib +$ make +$ make install + +You should now have an installation of hamlib in ~/Dev/hamlib + +Just in case you have hamlib installed via Macports, it may be a good idea to run +$ sudo port deactivate hamlib + +1.2/ WXWIDGETS +To be able to produce an appbundle, we need wxWidgets to be build statically. Again, Macports won't do this out of the box. + +Edit the wxWidgets-3.0 port file using: +$ sudo port edit wxWidgets-3.0 + +and add the following to the bottom of the file: + +variant static description { build a static version of the libraries with some other options... } { + configure.args-append --enable-std_iostreams + configure.args-append --disable-shared + configure.args-delete --with-sdl + configure.args-delete --with-opengl + set installtype release-static +} + +Now you can build and install a static variant of wxWidgets with: +$ sudo port install wxWidgets-3.0 +static + +Note: This will probably break anything else which is using wxWidgets. Once you have finished building FreeDV, you may +want to go back to the dynamically compiled version using: +$ sudo port install wxWidgets-3.0 + +HomeBrew Users: Anyone know how to do the above? + +1.3/ CODEC2 LIBRARIES +The FreeDV CMake procedure will automatically checkout and compile Codec2. +If you want to build and install your own copy (i.e. for access to the command-line tools), you can do so: + +$ wget http://files.freedv.org/codec2/codec2-0.4.tar.gz +or +$ svn checkout https://svn.code.sf.net/p/freetel/code/codec2-dev/ + +$ cd codec2-0.4 +or +cd codec2-dev +$ mkdir build_osx && cd build_osx +$ cmake ../ && make +$ sudo make install + +3/ BUILDING FREEDV +Get the FreeDV source by either: + +Getting the current 'stable' release (1.0): +$ wget http://files.freedv.org/freedv/freedv-1.0.tar.gz +$ tar -xzf freedv-1.0.tar.gz + +or + +Checking the latest revision out from SVN: +$ svn checkout https://svn.code.sf.net/p/freetel/code/freedv-dev/ + +$ cd freedv-1.0 +or +$ cd freedv-dev + +$ mkdir build_osx && cd build_osx + +Assuming you are intending on building Codec2 as part of the build process, run: + +$ cmake -DWXCONFIG=/opt/local/Library/Frameworks/wxWidgets.framework/Versions/wxWidgets/3.0/lib/wx/config/osx_cocoa-unicode-static-3.0 -DCMAKE_EXE_LINKER_FLAGS="-L/opt/local/lib" -DHAMLIB_INCLUDE_DIR=../../hamlib/include -DHAMLIB_LIBRARY=../../hamlib/lib/libhamlib.a ../ + +Then, build FreeDV: +$ make + +The build process will create an appbundle (FreeDV.app) and a compressed disk image (FreeDV.dmg) in ./build_osx/src +Move these to wherever you want, and run! + +Happy DVing! + +Acknowledgements: +A big thank you to Mooneer Salem, K6AQ, for walking me through this process, and figuring out how to solve the wxWidgets and Hamlib issues. + +Please e-mail any corrections to either the digitalvoice google group list, or myself, at: +vk5qi(at)rfhead.net +Mark Jessop VK5QI + diff --git a/freedv/tags/1.2.2/README.txt b/freedv/tags/1.2.2/README.txt new file mode 100644 index 00000000..26e731d1 --- /dev/null +++ b/freedv/tags/1.2.2/README.txt @@ -0,0 +1,233 @@ +================================== + FreeDV GUI README.txt +================================== + +This document describes how to build the FreeDV GUI program for +various operating systems. See also: + + http://freedv.org - introduction, documentation, downloads + RELEASE_NOTES.txt - changes made to each version + USER_MANUAL.txt - FreeDV GUI Manual + +================================== + Building and installing on Linux +================================== + +First the basics: + + $ sudo apt-get install build-essential cmake subversion + +To install the required development libraries instead of building them +statically: + +Debian/Ubuntu: + + $ sudo apt-get install libwxgtk3.0-dev portaudio19-dev \ + libhamlib-dev libsamplerate-dev libasound2-dev libao-dev libgsm1-dev \ + libsndfile-dev + +Fedora: + + $ sudo dnf install wxGTK3-devel portaudio-devel libsamplerate-devel \ + libsndfile-devel speexdsp-devel hamlib-devel alsa-lib-devel libao-devel \ + gsm-devel + + +RHEL/CentOS and derivitves (requires Fedora EPEL repository) + + $ sudo yum install wxGTK3-devel portaudio-devel libsamplerate-devel \ + libsndfile-devel speexdsp-devel hamlib-devel alsa-lib-devel libao-devel \ + gsm-devel + + +Quickstart 1 +------------ + +1/ Using a modern Linux, and the development library packages + installed above: + + $ cd /path/to/freedv + $ mkdir build_linux + $ cd build_linux + $ cmake ../ + $ make + $ ./src/freedv + +Quickstart 2 +------------ + +Builds static versions of wxWidgets, portaudio, codec2-dev, which are commonly +missing on older Linux systems. + +1/ Assumes static build of wxWidgets and the freedv-dev source is checked out into ~/freedv-dev: + + $ cd ~/freedv-dev + $ mkdir build_linux + $ cd build_linux + $ cmake -DBOOTSTRAP_WXWIDGETS=TRUE ../ + $ make + +2/ Then you can configure FreeDV using your local codec-dev, something like: + + $ cmake -DCMAKE_BUILD_TYPE=Debug -DBOOTSTRAP_WXWIDGETS=TRUE -DCODEC2_INCLUDE_DIRS=/path/to/codec2-dev/src -DCODEC2_LIBRARY=/path/to/codec2-dev/build_linux/src/libcodec2.so -DUSE_STATIC_CODEC2=FALSE -DUSE_STATIC_PORTAUDIO=TRUE ../ + +3/ OR build a local copy of codec2-dev: + + $ cmake -DBOOTSTRAP_WXWIDGETS=TRUE -DUSE_STATIC_CODEC2=TRUE -DUSE_STATIC_PORTAUDIO=TRUE ../ + +4/ Build and run FreeDV: + + $ make + $ ./src/freedv + +======================================================= + Building for Windows on Ubuntu Linux (Cross compiling) +======================================================= + +1/ Install the cross compiling toolchain: + + $ sudo apt-get install mingw-w64 + +2/ Patch cmake using: http://www.cmake.org/gitweb?p=stage/cmake.git;a=patch;h=33286235048495ceafb636d549d9a4e8891967ae + +3/ Checkout a fresh copy of codec2-dev and build for Windows, pointing to the generate_codebook built by a linux build of generate_codebook, using this cmake line + + $ cmake .. -DCMAKE_TOOLCHAIN_FILE=/home/david/freedv-dev/cmake/Toolchain-Ubuntu-mingw32.cmake -DUNITTEST=FALSE -DGENERATE_CODEBOOK=/home/david/codec2-dev/build_linux/src/generate_codebook + +4/ Build WxWidgets + + $ cd /path/to/freedv-dev + $ mkdir build_windows + $ cd build_windows + $ cmake -DBOOTSTRAP_WXWIDGETS=TRUE .. -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-Ubuntu-mingw32.cmake -DCMAKE_BUILD_TYPE=Debug + $ make + +5/ Download and install the Windows version of Hamlib: + + $ wget http://internode.dl.sourceforge.net/project/hamlib/hamlib/1.2.15.3/hamlib-win32-1.2.15.3.zip + $ unzip hamlib-win32-1.2.15.3.zip + +6/ Build All the libraries and FreeDV: + + $ cmake -DBOOTSTRAP_WXWIDGETS=TRUE -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-Ubuntu-mingw32.cmake -DUSE_STATIC_PORTAUDIO=TRUE -DUSE_STATIC_SNDFILE=TRUE -DUSE_STATIC_SAMPLERATE=TRUE -DUSE_STATIC_CODEC2=FALSE -DCODEC2_INCLUDE_DIRS=/home/david/tmp/codec2-dev/src -DCODEC2_LIBRARY=/home/david/tmp/codec2-dev/build_windows/src/libcodec2.dll.a -DHAMLIB_INCLUDE_DIR=hamlib-win32-1.2.15.3/include -DHAMLIB_LIBRARY=hamlib-win32-1.2.15.3/lib/gcc/libhamlib.dll.a -DCMAKE_BUILD_TYPE=Debug .. + $ make + +7/ Test on Linux with "wine", this will tell you if any DLLs are missing: + + $ wine src/freedv.exe + +8/ When moving to an actual Windows machine, I needed: + + /usr/lib/gcc/i686-w64-mingw32/4.8/libstdc++-6.dll + /usr/lib/gcc/i686-w64-mingw32/4.8/libgcc_s_sjlj-1.dll + /usr/i686-w64-mingw32/lib/libwinpthread-1.dll + + Wine seems to find these automagically, so I found them on my system by + looking at ~/.wine/system.reg for PATH: + + [System\\CurrentControlSet\\Control\\Session Manager\\Environment] 1423800803 + "PATH"=str(2):"C:\\windows\\system32;C:\\windows;C:\\windows\\system32\\wbem;Z:\\usr\\i686-w64-mingw32\\lib;Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\4.8" + + +==================================== + Building and installing on Windows +==================================== + +The windows build is similar to linux and follows the same basic workflow, +however, while codec2 and FreeDV (freedv) build well on windows, some of the +dependencies do not. For that reson current windows releases are cross-compiled +from linux. + +Only MinGW is supported. While it is likely possible to perform a pure MinGW +build, installing MSYS2 will make your life easier. + +CMake may not automatically detect that you're in the MSYS environment. If this +occurs you need to pass cmake the proper generator: + +cmake -G"MSYS Makefiles" [other options] + +=============================== + Bootstrapping wxWidgets build +=============================== + +If wxWidgets (>= 3.0) is not available then one option is to have CMake boot- +strap the build for FreeDV. + +This is required because the tool wx-config is used to get the correct compiler +and linker flags of the wxWidgets components needed by FreeDV. Since this is +normally done at configure time, not during "make", it is not possible for CMake +or have this information prior to building wxWidgets. + +In order to work around this issue you can "bootstrap" the wxWidgets build using +the CMake option, "BOOTSTRAP_WXWIDGETS". wxWidgets will be built using static +libraries. + +NOTE: This forces "USE_STATIC_WXWIDGETS" to be true internally regarless of the +value set manually. + +(from any directory, but empty directory outside of the source is prefered.) +$ cmake -DBOOTSTRAP_WXWIDGETS=TRUE /path/to/freedv +$ make +(wxWidgets is downloaded and built) +$ cmake . +(wxWidgets build should be detected) +$ make +(if all goes well, as root) +$ make install + +==================================== + Building and installing on OSX +==================================== + +Pls see README.osx + +==================================== + Building and installing on FreeBSD +==================================== + +As per "Quickstart 2" above but change build_linux to build_freebsd + +======= +Editing +======= + +Please make sure your text editor does not insert tabs, and +used indents of 4 spaces. The following .emacs code was used to +configure emacs: + +(setq-default indent-tabs-mode nil) + +(add-hook 'c-mode-common-hook + (function (lambda () + (setq c-basic-offset 4) + ))) + +FreeDV GUI TODO List +-------------------- + +[ ] Ubuntu packaging +[ ] default sound card in/out setting for rx out of the box +[ ] When application close on windows while "Start" down sometimes crashes + + Also on Linux it reports an unterminated thread when exiting +[ ] Tool-Audio Config Dialog sound device names truncated on Windows +[ ] Serialport::closeport() on Linux takes about 1 second + + delays 'Stop' on main window test on Tools-PTT Test +[ ] Voice keyer file name at bottom on main screen truncated + + need a bigger field +[ ] Start/Stop file rec/playback, work out a better UI, + maybe buttons on front page +[ ] feature for evaluating yr own sound quality + + trap bad mic response/levels + + zero in on different sound quality from different users +[ ] feeding audio over UDP say from from gqrx + + could also be used to netcat stored files +[ ] refactoring + [ ] fdmdv2_main.cpp is way too long + [ ] rename fdmdv2*.cpp -> freedv*.cpp + [ ] dlg_ptt uses ComPortsDlg name internally, rename PttDlg or similar +[ ] Add RSID + + use case, when would it be used? +[ ] clean up dialogs + + were based on auto generation code + + must be an easier/clearer way to write them + diff --git a/freedv/tags/1.2.2/RELEASE_NOTES.txt b/freedv/tags/1.2.2/RELEASE_NOTES.txt new file mode 100644 index 00000000..79eaeca0 --- /dev/null +++ b/freedv/tags/1.2.2/RELEASE_NOTES.txt @@ -0,0 +1,7 @@ +V1.2.2 July 2017 +---------------- + +1/ Improvements to Hamlib support, error message reporting, serial rate box. + +2/ Disabled unused UDP comms/egexp processing to clean up Options dialog. + diff --git a/freedv/tags/1.2.2/USER_MANUAL.txt b/freedv/tags/1.2.2/USER_MANUAL.txt new file mode 100644 index 00000000..2cc44945 --- /dev/null +++ b/freedv/tags/1.2.2/USER_MANUAL.txt @@ -0,0 +1,100 @@ +====================== +FREEDV GUI USER MANUAL +====================== + +Introduction +------------ + +This document describes additional features in the latest FreeDV +releases that haven't been documented in other sources. See also +freedv.org + +PTT Configuration +----------------- + +Tools-PTT Dialog + +Hamlib comes with a default serial rate for each radio. If your radio +has a different serial rate change the Serial Rate drop down box to +match your radio. + +When "Test" is pressed, the "Serial Params" field is populated and +displayed. This will help track down any mis-matches between Hamlib +and your radio. + +Voice Keyer +----------- + +Voice Keyer Button on Front Page +Options-PTT Dialog + +Puts FreeDV and your radio into transmit, reads a wave file of your +voice to call CQ, then switches to receive to see if anyone is +replying. If you press space bar the voice keyer stops. If a signal +with a valid sync is received for a few seconds the voice keyer stops. + +Options-PTT dialog can be used to select the wave file, set the Rx +delay, and number of times the tx/rx cycle repeats. + +The wave file for the voice keyer should be in 8kHz mono 16 bit sample +form. Use a free application such as Audacity to convert a file you +have recorded to this format. + +Test Frame Histogram +-------------------- + +Test Frame Histogram tab on Front Page + +Displays BER of each carrier when in "test frame" mode. As each QPSK +carrier has 2 bits there are 2*Nc histogram points. + +Ideally all carriers will have about the same BER (+/- 20% after 5000 +total bit errors). However problems can occur with filtering in the +tx path. If one carrier has less power, then it will have a higher +BER. The errors in this carrier will tend to dominate overall +BER. For example if one carrier is attenuated due to SSB filter ripple +in the tx path then the BER on that carrier will be higher. This is +bad news for DV. + +Suggested usage: + +i) Transmit FreeDV in test frame mode. Use a 2nd rx (or +get a friend) to monitor your rx signal with FreeDV in test frame +mode. + +ii) Adjust your rx SNR to get a BER of a few % (e.g. reduce tx +power, use a short antenna for the rx, point your beam away, adjust rx +RF gain). + +iii) Monitor the error histogram for a few minutes, until you +have say 5000 total bit errors. You have a problem if the BER of any +carrier is more than 20% different from the rest. + +A typical issue will be one carrier at 1.0, the others at 0.5, +indicating the poorer carrier BER is twice the larger. + +Full Duplex Testing with loopback +--------------------------------- + +Options - Half Duplex check box + +FreeDV GUI can operate in full duplex mode which is useful for +development of listening to your own FreeDV signal as only one PC is +required. Normal operation is half duplex. + +Tx and Rx signals can be looped back via an analog connection between +the sound cards. + +On Linux, using the Alsa loopback module: + + $ sudo modprobe snd-aloop + $ ./freedv + + In Tools - Audio Config - Receive Tab - From Radio select -> Loopback: Loopback PCM (hw:1,0) + - Transmit Tab - To Radio select -> Loopback: Loopback PCM (hw:1,1) + +TODO +---- + +[ ] Merge this information into existing start up guides + diff --git a/freedv/tags/1.2.2/cmake/BuildCodec2.cmake b/freedv/tags/1.2.2/cmake/BuildCodec2.cmake new file mode 100644 index 00000000..678ce2ef --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildCodec2.cmake @@ -0,0 +1,26 @@ +set(SPEEXDSP_CMAKE_ARGS -DBUILD_SHARED_LIBS=FALSE -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external/dist) + +if(USE_STATIC_SPEEXDSP) + list(APPEND SPEEXDSP_CMAKE_ARGS + -DSPEEXDSP_LIBRARIES=${CMAKE_BINARY_DIR}/external/dist/lib/libspeexdsp.a + -DSPEEXDSP_INCLUDE_DIR=${CMAKE_BINARY_DIR}/external/dist/include) +endif() + +set(CODEC2_CMAKE_ARGS -DUNITTEST=FALSE) + +if(CMAKE_CROSSCOMPILING) + set(CODEC2_CMAKE_ARGS ${CODEC2_CMAKE_ARGS} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}) +endif() + +include(ExternalProject) +ExternalProject_Add(codec2 + SVN_REPOSITORY https://svn.code.sf.net/p/freetel/code/codec2/tags/0.7 + CMAKE_ARGS ${CODEC2_CMAKE_ARGS} ${SPEEXDSP_CMAKE_ARGS} + CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:string=10.7 + INSTALL_COMMAND "" +) +set(CODEC2_LIBRARIES + ${CMAKE_BINARY_DIR}/codec2-prefix/src/codec2-build/src/libcodec2.a) +include_directories(${CMAKE_BINARY_DIR}/codec2-prefix/src/codec2/src) +list(APPEND FREEDV_LINK_LIBS ${CODEC2_LIBRARIES}) +list(APPEND FREEDV_STATIC_DEPS codec2) diff --git a/freedv/tags/1.2.2/cmake/BuildHamlib.cmake b/freedv/tags/1.2.2/cmake/BuildHamlib.cmake new file mode 100644 index 00000000..4166f5ae --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildHamlib.cmake @@ -0,0 +1,20 @@ +set(HAMLIB_TARBALL "hamlib-1.2.15.3") + +include(ExternalProject) +ExternalProject_Add(hamlib + URL http://downloads.sourceforge.net/hamlib/${HAMLIB_TARBALL}.tar.gz + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external/dist + BUILD_COMMAND $(MAKE) + INSTALL_COMMAND $(MAKE) install +) +if(WIN32) + set(HAMLIB_LIBRARIES ${CMAKE_BINARY_DIR}/external/dist/lib/portaudio.lib) +else(WIN32) + set(HAMLIB_LIBRARIES + ) +endif(WIN32) +include_directories(${CMAKE_BINARY_DIR}/external/dist/include) +list(APPEND FREEDV_LINK_LIBS ${HAMLIB_LIBRARIES}) +list(APPEND FREEDV_STATIC_DEPS hamlib) diff --git a/freedv/tags/1.2.2/cmake/BuildPortaudio.cmake b/freedv/tags/1.2.2/cmake/BuildPortaudio.cmake new file mode 100644 index 00000000..cc33d061 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildPortaudio.cmake @@ -0,0 +1,52 @@ +set(PORTAUDIO_TARBALL "pa_stable_v19_20111121") + +# required linking libraries on linux. Not sure about windows. +find_library(ALSA_LIBRARIES asound) + +if(UNIX AND NOT ALSA_LIBRARIES) + message(ERROR "Could not find alsa library which is required for portaudio. +On Linux systems try installing: + alsa-lib-devel (RPM based systems) + libasound2-dev (DEB based systems)" + ) +endif() + +# Make sure that configure knows what system we're using when cross-compiling. +if(MINGW AND CMAKE_CROSSCOMPILING) + include(cmake/MinGW.cmake) + set(CONFIGURE_COMMAND ./configure --build=${HOST} --host=${HOST} --target=${HOST} --enable-cxx --without-jack --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist) +else() + set(CONFIGURE_COMMAND ./configure --enable-cxx --without-jack --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist) +endif() + +include(ExternalProject) +ExternalProject_Add(portaudio + URL http://www.portaudio.com/archives/${PORTAUDIO_TARBALL}.tgz + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ${CONFIGURE_COMMAND} + BUILD_COMMAND $(MAKE) + INSTALL_COMMAND $(MAKE) install +) +if(WIN32) + set(PORTAUDIO_LIBRARIES + ${CMAKE_BINARY_DIR}/external/dist/lib/libportaudio.a + ${CMAKE_BINARY_DIR}/external/dist/lib/libportaudiocpp.a +) +else(WIN32) + find_library(RT rt) + find_library(ASOUND asound) + set(PORTAUDIO_LIBRARIES + ${CMAKE_BINARY_DIR}/external/dist/lib/libportaudio.a + ${CMAKE_BINARY_DIR}/external/dist/lib/libportaudiocpp.a + ${RT} + ${ASOUND} + ) +endif(WIN32) +include_directories(${CMAKE_BINARY_DIR}/external/dist/include) + +# Add the portaudio library to the list of libraries that must be linked. +list(APPEND FREEDV_LINK_LIBS ${PORTAUDIO_LIBRARIES}) + +# Setup a dependency so that this gets built before linking to freedv. +list(APPEND FREEDV_STATIC_DEPS portaudio) diff --git a/freedv/tags/1.2.2/cmake/BuildSamplerate.cmake b/freedv/tags/1.2.2/cmake/BuildSamplerate.cmake new file mode 100644 index 00000000..8b6b7a36 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildSamplerate.cmake @@ -0,0 +1,27 @@ +set(SAMPLERATE_TARBALL "libsamplerate-0.1.8") + +if(MINGW AND CMAKE_CROSSCOMPILING) + set(CONFIGURE_COMMAND ./configure --build=${HOST} --host=${HOST} --target=${HOST} --prefix=${CMAKE_BINARY_DIR}/external/dist --disable-sndfile --disable-fftw) +else() + set(CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external/dist) +endif() + +include(ExternalProject) +ExternalProject_Add(samplerate + URL http://www.mega-nerd.com/SRC/${SAMPLERATE_TARBALL}.tar.gz + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ${CONFIGURE_COMMAND} + BUILD_COMMAND $(MAKE) + INSTALL_COMMAND $(MAKE) install +) +if(WIN32) + set(SAMPLERATE_LIBRARIES + ${CMAKE_BINARY_DIR}/external/dist/lib/libsamplerate.a) +else(WIN32) + set(SAMPLERATE_LIBRARIES + ${CMAKE_BINARY_DIR}/external/dist/lib/libsamplerate.a) +endif(WIN32) +include_directories(${CMAKE_BINARY_DIR}/external/dist/include) +list(APPEND FREEDV_LINK_LIBS ${SAMPLERATE_LIBRARIES}) +list(APPEND FREEDV_STATIC_DEPS samplerate) diff --git a/freedv/tags/1.2.2/cmake/BuildSndfile.cmake b/freedv/tags/1.2.2/cmake/BuildSndfile.cmake new file mode 100644 index 00000000..c49b6388 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildSndfile.cmake @@ -0,0 +1,26 @@ +set(SNDFILE_TARBALL "libsndfile-1.0.25") + +if(MINGW AND CMAKE_CROSSCOMPILING) + set(CONFIGURE_COMMAND ./configure --host=${HOST} --prefix=${CMAKE_BINARY_DIR}/external/dist --disable-external-libs --disable-shared --disable-sqlite) +else() + set(CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external/dist --disable-external-libs --disable-shared --disable-external-libs) +endif() + +include(ExternalProject) +ExternalProject_Add(sndfile + URL http://www.mega-nerd.com/libsndfile/files/${SNDFILE_TARBALL}.tar.gz + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ${CONFIGURE_COMMAND} + BUILD_COMMAND $(MAKE) V=1 + INSTALL_COMMAND $(MAKE) install +) +if(MINGW) + set(SNDFILE_LIBRARIES ${CMAKE_BINARY_DIR}/external/dist/lib/libsndfile.a) +else() + set(SNDFILE_LIBRARIES ${CMAKE_BINARY_DIR}/external/dist/lib/libsndfile.a) +endif() + +include_directories(${CMAKE_BINARY_DIR}/external/dist/include) +list(APPEND FREEDV_LINK_LIBS ${SNDFILE_LIBRARIES}) +list(APPEND FREEDV_STATIC_DEPS sndfile) diff --git a/freedv/tags/1.2.2/cmake/BuildSpeex.cmake b/freedv/tags/1.2.2/cmake/BuildSpeex.cmake new file mode 100644 index 00000000..8d287ead --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildSpeex.cmake @@ -0,0 +1,30 @@ +set(SPEEXDSP_TARBALL "speexdsp-1.2rc3.tar.gz") + +if(MINGW AND CMAKE_CROSSCOMPILING) + include(cmake/MinGW.cmake) + set(CONFIGURE_COMMAND ./configure --host=${HOST} --prefix=${CMAKE_BINARY_DIR}/external/dist --disable-examples) +else() + if(APPLE) + set(CONFIGURE_COMMAND ${CMAKE_BINARY_DIR}/../configure_speexdsp_osx.sh ${CMAKE_BINARY_DIR}/external/dist) + else() + set(CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external/dist --disable-examples) + endif() +endif() + +include(ExternalProject) +ExternalProject_Add(speex + URL http://downloads.xiph.org/releases/speex/${SPEEXDSP_TARBALL} + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ${CONFIGURE_COMMAND} + BUILD_COMMAND $(MAKE) + INSTALL_COMMAND $(MAKE) install +) + +set(SPEEXDSP_LIBRARIES ${CMAKE_BINARY_DIR}/external/dist/lib/libspeexdsp.a) +include_directories(${CMAKE_BINARY_DIR}/external/dist/include) +list(APPEND FREEDV_LINK_LIBS ${SPEEXDSP_LIBRARIES}) +list(APPEND FREEDV_STATIC_DEPS speex) +if(USE_STATIC_CODEC2) + add_dependencies(codec2 speex) +endif() diff --git a/freedv/tags/1.2.2/cmake/BuildWxWidgets.cmake b/freedv/tags/1.2.2/cmake/BuildWxWidgets.cmake new file mode 100644 index 00000000..901d8062 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/BuildWxWidgets.cmake @@ -0,0 +1,43 @@ +set(WXWIDGETS_TARBALL "wxWidgets-3.0.2") + +# If we're cross-compiling then we need to set the target host manually. +if(MINGW AND CMAKE_CROSSCOMPILING) + include(cmake/MinGW.cmake) +endif() + +# If not cross-compiling then use the built-in makefile, otherwise use standard configure. +if(MINGW AND NOT CMAKE_CROSSCOMPILING) +# set(CONFIGURE_COMMAND "true") +# set(MAKE_COMMAND $(MAKE) -C build/msw -f makefile.gcc SHARED=0 UNICODE=1 BUILD=release PREFIX=${CMAKE_BINARY_DIR}/external/dist) + set(CONFIGURE_COMMAND ./configure --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist) + set(MAKE_COMMAND $(MAKE)) +endif() + +if(MINGW AND CMAKE_CROSSCOMPILING) + set(CONFIGURE_COMMAND ./configure --build=${HOST} --host=${HOST} --target=${HOST} --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist) + set(MAKE_COMMAND $(MAKE)) +endif() + +if(NOT MINGW) + set(CONFIGURE_COMMAND ./configure --host=${HOST} --target=${HOST} --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist) + set(MAKE_COMMAND $(MAKE)) +endif() + +include(ExternalProject) +ExternalProject_Add(wxWidgets + URL http://downloads.sourceforge.net/wxwindows/${WXWIDGETS_TARBALL}.tar.bz2 + BUILD_IN_SOURCE 1 + INSTALL_DIR external/dist + CONFIGURE_COMMAND ${CONFIGURE_COMMAND} + BUILD_COMMAND ${MAKE_COMMAND} + INSTALL_COMMAND $(MAKE) install +) + +ExternalProject_Get_Property(wxWidgets install_dir) +message(STATUS "wxWidgets install dir: ${install_dir}") +if(NOT WXCONFIG) + set(WXCONFIG "${install_dir}/bin/wx-config") +endif() +if(EXISTS ${WXCONFIG}) + set(BS_WX_DONE TRUE) +endif() diff --git a/freedv/tags/1.2.2/cmake/FindPortaudio.cmake b/freedv/tags/1.2.2/cmake/FindPortaudio.cmake new file mode 100644 index 00000000..158e20ee --- /dev/null +++ b/freedv/tags/1.2.2/cmake/FindPortaudio.cmake @@ -0,0 +1,107 @@ +# - Try to find Portaudio +# Once done this will define +# +# PORTAUDIO_FOUND - system has Portaudio +# PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory +# PORTAUDIO_LIBRARIES - Link these to use Portaudio +# PORTAUDIO_DEFINITIONS - Compiler switches required for using Portaudio +# PORTAUDIO_VERSION - Portaudio version +# +# Copyright (c) 2006 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + # in cache already + set(PORTAUDIO_FOUND TRUE) +else (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + if (NOT WIN32) + include(FindPkgConfig) + pkg_check_modules(PORTAUDIO2 portaudio-2.0) + endif (NOT WIN32) + + if (PORTAUDIO2_FOUND) + set(PORTAUDIO_INCLUDE_DIRS + ${PORTAUDIO2_INCLUDE_DIRS} + ) + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_LIBRARIES "${PORTAUDIO2_LIBRARY_DIRS}/lib${PORTAUDIO2_LIBRARIES}.dylib") + else (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_LIBRARIES + ${PORTAUDIO2_LIBRARIES} + ) + endif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_VERSION + 19 + ) + set(PORTAUDIO_FOUND TRUE) + else (PORTAUDIO2_FOUND) + find_path(PORTAUDIO_INCLUDE_DIR + NAMES + portaudio.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(PORTAUDIO_LIBRARY + NAMES + portaudio + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + find_path(PORTAUDIO_LIBRARY_DIR + NAMES + portaudio + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(PORTAUDIO_INCLUDE_DIRS + ${PORTAUDIO_INCLUDE_DIR} + ) + set(PORTAUDIO_LIBRARIES + ${PORTAUDIO_LIBRARY} + ) + + set(PORTAUDIO_LIBRARY_DIRS + ${PORTAUDIO_LIBRARY_DIR} + ) + + set(PORTAUDIO_VERSION + 18 + ) + + if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + set(PORTAUDIO_FOUND TRUE) + endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + + if (PORTAUDIO_FOUND) + if (NOT Portaudio_FIND_QUIETLY) + message(STATUS "Found Portaudio: ${PORTAUDIO_LIBRARIES}") + endif (NOT Portaudio_FIND_QUIETLY) + else (PORTAUDIO_FOUND) + if (Portaudio_FIND_REQUIRED) + message(FATAL_ERROR "Could not find Portaudio") + endif (Portaudio_FIND_REQUIRED) + endif (PORTAUDIO_FOUND) + endif (PORTAUDIO2_FOUND) + + + # show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view + mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) + +endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + diff --git a/freedv/tags/1.2.2/cmake/GetDependencies.cmake.in b/freedv/tags/1.2.2/cmake/GetDependencies.cmake.in new file mode 100644 index 00000000..7470aa6d --- /dev/null +++ b/freedv/tags/1.2.2/cmake/GetDependencies.cmake.in @@ -0,0 +1,37 @@ +# As this script is run in a new cmake instance, it does not have access to +# the existing cache variables. Pass them in via the configure_file command. +set(CMAKE_BINARY_DIR @CMAKE_BINARY_DIR@) +set(CMAKE_SOURCE_DIR @CMAKE_SOURCE_DIR@) +set(UNIX @UNIX@) +set(WIN32 @WIN32@) +set(CMAKE_CROSSCOMPILING @CMAKE_CROSSCOMPILING@) +set(CMAKE_FIND_LIBRARY_SUFFIXES @CMAKE_FIND_LIBRARY_SUFFIXES@) +set(CMAKE_FIND_LIBRARY_PREFIXES @CMAKE_FIND_LIBRARY_PREFIXES@) +set(CMAKE_SYSTEM_LIBRARY_PATH @CMAKE_SYSTEM_LIBRARY_PATH@) +set(CMAKE_FIND_ROOT_PATH @CMAKE_FIND_ROOT_PATH@) + +set(FREEDV_EXE ${CMAKE_BINARY_DIR}/src/freedv.exe) + +include(GetPrerequisites) +get_prerequisites("${FREEDV_EXE}" _deps 1 0 "" "") +foreach(_runtime ${_deps}) + message("Looking for ${_runtime}") + find_library(RUNTIME_${_runtime} ${_runtime}) + message("${RUNTIME_${_runtime}}") + if(RUNTIME_${_runtime}) + message("Looking for dependencies of ${_runtime}") + get_prerequisites("${RUNTIME_${_runtime}}" _deps2 1 0 "" "") + foreach(_runtime2 ${_deps2}) + find_library(RUNTIME_${_runtime2} ${_runtime2}) + message("${RUNTIME_${_runtime2}}") + if(RUNTIME_${_runtime2}) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" + TYPE EXECUTABLE FILES "${RUNTIME_${_runtime2}}") + endif() + endforeach() + endif() + if(RUNTIME_${_runtime}) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" + TYPE EXECUTABLE FILES "${RUNTIME_${_runtime}}") + endif() +endforeach() diff --git a/freedv/tags/1.2.2/cmake/MinGW.cmake b/freedv/tags/1.2.2/cmake/MinGW.cmake new file mode 100644 index 00000000..333c1dc0 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/MinGW.cmake @@ -0,0 +1,8 @@ +# If we're cross-compiling then we need to set the target host manually. +if(MINGW AND CMAKE_CROSSCOMPILING) + if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(HOST x86_64-w64-mingw32) + else() + set(HOST i686-w64-mingw32) + endif() +endif() diff --git a/freedv/tags/1.2.2/cmake/Toolchain-Ubuntu-mingw32.cmake b/freedv/tags/1.2.2/cmake/Toolchain-Ubuntu-mingw32.cmake new file mode 100644 index 00000000..3507d720 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/Toolchain-Ubuntu-mingw32.cmake @@ -0,0 +1,25 @@ +# Sample toolchain file for building for Windows from an Ubuntu Linux system. +# +# Typical usage: +# *) install cross compiler: `sudo apt-get install mingw-w64 g++-mingw-w64` +# *) cd build +# *) cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-Ubuntu-mingw32.cmake .. + +set(CMAKE_SYSTEM_NAME Windows) +set(TOOLCHAIN_PREFIX i686-w64-mingw32) + +# cross compilers to use for C and C++ +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) +set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) + +# target environment on the build host system +# set 1st to dir with the cross compiler's C/C++ headers/libs +set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) + +# modify default behavior of FIND_XXX() commands to +# search for headers/libs in the target environment and +# search for programs in the build host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/freedv/tags/1.2.2/cmake/config.h.in b/freedv/tags/1.2.2/cmake/config.h.in new file mode 100644 index 00000000..8e3ab76b --- /dev/null +++ b/freedv/tags/1.2.2/cmake/config.h.in @@ -0,0 +1,19 @@ +/*-------------------------------------------------------------------------- + ** This file is autogenerated from config.h.in + ** during the cmake configuration of your project. If you need to make changes + ** edit the original file NOT THIS FILE. + ** --------------------------------------------------------------------------*/ +#ifndef _CONFIGURATION_HEADER_GUARD_H_ +#define _CONFIGURATION_HEADER_GUARD_H_ + +#define SIZEOF_INT @SIZEOF_INT@ +#cmakedefine HAVE_LIMITS_H @HAVE_LIMITS_H@ +#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ +#cmakedefine HAVE_STDDEF_H @HAVE_STDDEF_H@ +#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ +#cmakedefine HAVE_STRING_H @HAVE_STRING_H@ +#cmakedefine HAVE_FLOOR @HAVE_FLOOR@ +#cmakedefine HAVE_MEMSET @HAVE_MEMSET@ +#cmakedefine HAVE_POW @HAVE_POW@ +#cmakedefine HAVE_SQRT @HAVE_SQRT@ +#endif diff --git a/freedv/tags/1.2.2/cmake/soxconfig.h.in b/freedv/tags/1.2.2/cmake/soxconfig.h.in new file mode 100644 index 00000000..fb38608e --- /dev/null +++ b/freedv/tags/1.2.2/cmake/soxconfig.h.in @@ -0,0 +1,16 @@ +#define PACKAGE_VERSION "14.4.2" + +#cmakedefine HAVE_BYTESWAP_H 1 +#cmakedefine HAVE_FMEMOPEN 1 +#cmakedefine HAVE_FSEEKO 1 +#cmakedefine HAVE__FSEEKOI64 1 +#cmakedefine HAVE_LTDL_H 1 +#cmakedefine HAVE_MAGIC 1 +#cmakedefine HAVE_POPEN 1 +#cmakedefine HAVE_STDINT_H 1 +#cmakedefine HAVE_INTTYPES_H 1 +#cmakedefine HAVE_STRCASECMP 1 +#cmakedefine HAVE_STRINGS_H 1 +#cmakedefine HAVE_SYS_STAT_H 1 +#cmakedefine HAVE_SYS_TYPES_H 1 +#cmakedefine HAVE_VSNPRINTF 1 diff --git a/freedv/tags/1.2.2/cmake/version.h.in b/freedv/tags/1.2.2/cmake/version.h.in new file mode 100644 index 00000000..43b3b7a8 --- /dev/null +++ b/freedv/tags/1.2.2/cmake/version.h.in @@ -0,0 +1,11 @@ +#ifndef FREEDV_VER_DOT_H +#define FREEDV_VER_DOT_H 1 + +#define FREEDV_VERSION_MAJOR @FREEDV_VERSION_MAJOR@ +#define FREEDV_VERSION_MINOR @FREEDV_VERSION_MINOR@ +#define FREEDV_VERSION_PATCH @FREEDV_VERSION_PATCH@ +#define FREEDV_VERSION_SUFFIX "@FREEDV_VERSION_SUFFIX@" + +#define FREEDV_VERSION "@FREEDV_VERSION_STRING@" + +#endif //FREEDV_VER_DOT_H diff --git a/freedv/tags/1.2.2/contrib/CMakeLists.txt b/freedv/tags/1.2.2/contrib/CMakeLists.txt new file mode 100644 index 00000000..3f4b7e02 --- /dev/null +++ b/freedv/tags/1.2.2/contrib/CMakeLists.txt @@ -0,0 +1,22 @@ +# Install icons if we're on most *nix systems. +if(UNIX AND NOT APPLE) + set(ICON_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor + CACHE PATH "Prefix to use for installing icons.") + install(FILES freedv48x48.png + DESTINATION ${ICON_INSTALL_PREFIX}/48x48/apps + RENAME freedv.png) + install(FILES freedv64x64.png + DESTINATION ${ICON_INSTALL_PREFIX}/64x64/apps + RENAME freedv.png) + install(FILES freedv128x128.png + DESTINATION ${ICON_INSTALL_PREFIX}/128x128/apps + RENAME freedv.png) + install(FILES freedv256x256.png + DESTINATION ${ICON_INSTALL_PREFIX}/256x256/apps + RENAME freedv.png) + + set(DESKTOP_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/share/applications + CACHE PATH "Location to install desktop files.") + install(FILES freedv.desktop + DESTINATION ${DESKTOP_INSTALL_DIR}) +endif(UNIX AND NOT APPLE) diff --git a/freedv/tags/1.2.2/contrib/LICENSE b/freedv/tags/1.2.2/contrib/LICENSE new file mode 100644 index 00000000..dc8853a7 --- /dev/null +++ b/freedv/tags/1.2.2/contrib/LICENSE @@ -0,0 +1,393 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/freedv/tags/1.2.2/contrib/freedv.desktop b/freedv/tags/1.2.2/contrib/freedv.desktop new file mode 100644 index 00000000..96e82931 --- /dev/null +++ b/freedv/tags/1.2.2/contrib/freedv.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=FreeDV +Exec=freedv +Icon=freedv +Type=Application +Terminal=false +Categories=GTK;GNOME;AudioVideo;Audio;HamRadio; diff --git a/freedv/tags/1.2.2/contrib/freedv.ico b/freedv/tags/1.2.2/contrib/freedv.ico new file mode 100644 index 0000000000000000000000000000000000000000..e6b9a2087ddae6e530734e7293da08b1d12d4dd0 GIT binary patch literal 364646 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 B>KOn4 literal 0 HcmV?d00001 diff --git a/freedv/tags/1.2.2/contrib/freedv64x64.png b/freedv/tags/1.2.2/contrib/freedv64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..eb89773bbcd93a4ed507271bd728734d0a5d7f4d GIT binary patch literal 6289 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

dh=(PosX@Z;=^IUxT@l?9i6IK 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 X%Xb{{VgDe5fxZ-ERb@)0jeP$Hyj%aW literal 0 HcmV?d00001 diff --git a/freedv/tags/1.2.2/credits.txt b/freedv/tags/1.2.2/credits.txt new file mode 100644 index 00000000..431e399c --- /dev/null +++ b/freedv/tags/1.2.2/credits.txt @@ -0,0 +1,13 @@ +Credits (code or ideas borrowed from): +============================================== +Dave Witten and David Rowe (obviously) +Mel Whitten K0PFX (material and moral support) +Bruce Perens (cheerleader, promotion and publicity) +Mooneer Salem KG6AOV(Mac OSX Patch) +Soeren Straarup OZ2DAK (FreeBSD Port) +Don Mak +Steve Nance (K5FR) +Joel Stanley (Hamlib prototyping) and Mark Jessop (Mac OSX) +James Ahlstrom (Quisk) +FLDIGI +All the folks on the digital voice google group... diff --git a/freedv/tags/1.2.2/db/current b/freedv/tags/1.2.2/db/current new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/freedv/tags/1.2.2/db/current @@ -0,0 +1 @@ +1 diff --git a/freedv/tags/1.2.2/db/format b/freedv/tags/1.2.2/db/format new file mode 100644 index 00000000..db06890e --- /dev/null +++ b/freedv/tags/1.2.2/db/format @@ -0,0 +1,2 @@ +4 +layout sharded 1000 diff --git a/freedv/tags/1.2.2/db/fs-type b/freedv/tags/1.2.2/db/fs-type new file mode 100644 index 00000000..4fdd9531 --- /dev/null +++ b/freedv/tags/1.2.2/db/fs-type @@ -0,0 +1 @@ +fsfs diff --git a/freedv/tags/1.2.2/db/fsfs.conf b/freedv/tags/1.2.2/db/fsfs.conf new file mode 100644 index 00000000..cc08cebb --- /dev/null +++ b/freedv/tags/1.2.2/db/fsfs.conf @@ -0,0 +1,38 @@ +### This file controls the configuration of the FSFS filesystem. + +[memcached-servers] +### These options name memcached servers used to cache internal FSFS +### data. See http://www.danga.com/memcached/ for more information on +### memcached. To use memcached with FSFS, run one or more memcached +### servers, and specify each of them as an option like so: +# first-server = 127.0.0.1:11211 +# remote-memcached = mymemcached.corp.example.com:11212 +### The option name is ignored; the value is of the form HOST:PORT. +### memcached servers can be shared between multiple repositories; +### however, if you do this, you *must* ensure that repositories have +### distinct UUIDs and paths, or else cached data from one repository +### might be used by another accidentally. Note also that memcached has +### no authentication for reads or writes, so you must ensure that your +### memcached servers are only accessible by trusted users. + +[caches] +### When a cache-related error occurs, normally Subversion ignores it +### and continues, logging an error if the server is appropriately +### configured (and ignoring it with file:// access). To make +### Subversion never ignore cache errors, uncomment this line. +# fail-stop = true + +[rep-sharing] +### To conserve space, the filesystem can optionally avoid storing +### duplicate representations. This comes at a slight cost in +### performance, as maintaining a database of shared representations can +### increase commit times. The space savings are dependent upon the size +### of the repository, the number of objects it contains and the amount of +### duplication between them, usually a function of the branching and +### merging process. +### +### The following parameter enables rep-sharing in the repository. It can +### be switched on and off at will, but for best space-saving results +### should be enabled consistently over the life of the repository. +### rep-sharing is enabled by default. +# enable-rep-sharing = true diff --git a/freedv/tags/1.2.2/db/min-unpacked-rev b/freedv/tags/1.2.2/db/min-unpacked-rev new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/freedv/tags/1.2.2/db/min-unpacked-rev @@ -0,0 +1 @@ +0 diff --git a/freedv/tags/1.2.2/db/rep-cache.db b/freedv/tags/1.2.2/db/rep-cache.db new file mode 100644 index 0000000000000000000000000000000000000000..63c6f0b8a5181c3954b89f8a6fb505c09e81c4e3 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlJz3U}R))P*7lCU|#F<%m)i%A($C- zAIbAF|6yQa`p&@go%tjdHKRhKAwcgCX!K`f7nhf3Y|1T3Ov*_uN-c;_PE5`~FqoZ# zTpdGP6+#@Hd|Vaa@(LOX3JMvC#Tg1At`Q*$e*Qol>f@sj5aj9W7!;}C?HZ{AR8f># zmRX#cpQqsI7vk#f8U$AelFUy_D^4xJDpj0Wm5Nm&wW1&~FC{f49;*tVp_+zFY~rr+ zj0~ATWfjGRIlx>UpIBOw59Y_iJrHjQXM*xD3phi=ay7kUVbs3S5Eu=C0Sy6OknPB| j{D8V<)bh~~7!3h>h5#4HveEoc&mbSQYcvD~O$Y!0OWInE literal 0 HcmV?d00001 diff --git a/freedv/tags/1.2.2/db/revprops/0/0 b/freedv/tags/1.2.2/db/revprops/0/0 new file mode 100644 index 00000000..d0b90dee --- /dev/null +++ b/freedv/tags/1.2.2/db/revprops/0/0 @@ -0,0 +1,5 @@ +K 8 +svn:date +V 27 +2012-08-21T18:27:59.389906Z +END diff --git a/freedv/tags/1.2.2/db/revprops/0/1 b/freedv/tags/1.2.2/db/revprops/0/1 new file mode 100644 index 00000000..0af71a2e --- /dev/null +++ b/freedv/tags/1.2.2/db/revprops/0/1 @@ -0,0 +1,13 @@ +K 10 +svn:author +V 9 +OFA-Staff +K 8 +svn:date +V 27 +2012-08-21T18:28:08.741468Z +K 7 +svn:log +V 25 +Imported folder structure +END diff --git a/freedv/tags/1.2.2/db/revs/0/0 b/freedv/tags/1.2.2/db/revs/0/0 new file mode 100644 index 00000000..10f5c45f --- /dev/null +++ b/freedv/tags/1.2.2/db/revs/0/0 @@ -0,0 +1,11 @@ +PLAIN +END +ENDREP +id: 0.0.r0/17 +type: dir +count: 0 +text: 0 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e +cpath: / + + +17 107 diff --git a/freedv/tags/1.2.2/db/revs/0/1 b/freedv/tags/1.2.2/db/revs/0/1 new file mode 100644 index 00000000..fd802a9f --- /dev/null +++ b/freedv/tags/1.2.2/db/revs/0/1 @@ -0,0 +1,49 @@ +id: 3-1.0.r1/0 +type: dir +count: 0 +cpath: /tags +copyroot: 0 / + +id: 0-1.0.r1/62 +type: dir +count: 0 +cpath: /trunk +copyroot: 0 / + +id: 2-1.0.r1/126 +type: dir +count: 0 +cpath: /branches +copyroot: 0 / + +PLAIN +K 8 +branches +V 16 +dir 2-1.0.r1/126 +K 4 +tags +V 14 +dir 3-1.0.r1/0 +K 5 +trunk +V 15 +dir 0-1.0.r1/62 +END +ENDREP +id: 0.0.r1/306 +type: dir +pred: 0.0.r0/17 +count: 1 +text: 1 194 99 99 7b6cc14dddba4e09be5255b475d1a0a8 +cpath: / +copyroot: 0 / + +_0.0.t0-0 add-dir false false /trunk + +_2.0.t0-0 add-dir false false /branches + +_3.0.t0-0 add-dir false false /tags + + +306 431 diff --git a/freedv/tags/1.2.2/db/transactions/.gitignore b/freedv/tags/1.2.2/db/transactions/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/freedv/tags/1.2.2/db/txn-current b/freedv/tags/1.2.2/db/txn-current new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/freedv/tags/1.2.2/db/txn-current @@ -0,0 +1 @@ +1 diff --git a/freedv/tags/1.2.2/db/txn-current-lock b/freedv/tags/1.2.2/db/txn-current-lock new file mode 100644 index 00000000..e69de29b diff --git a/freedv/tags/1.2.2/db/txn-protorevs/.gitignore b/freedv/tags/1.2.2/db/txn-protorevs/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/freedv/tags/1.2.2/db/uuid b/freedv/tags/1.2.2/db/uuid new file mode 100644 index 00000000..0f362976 --- /dev/null +++ b/freedv/tags/1.2.2/db/uuid @@ -0,0 +1 @@ +a56d66ce-6468-4744-9be7-52ce95ca47a4 diff --git a/freedv/tags/1.2.2/db/write-lock b/freedv/tags/1.2.2/db/write-lock new file mode 100644 index 00000000..e69de29b diff --git a/freedv/tags/1.2.2/debian/changelog b/freedv/tags/1.2.2/debian/changelog new file mode 100644 index 00000000..ddfe80b5 --- /dev/null +++ b/freedv/tags/1.2.2/debian/changelog @@ -0,0 +1,5 @@ +freedv (1.0-150830) unstable; urgency=low + + * Subversion snapshot of tag 1.0. + + -- Stuart Longland Sun, 30 Aug 2015 09:01:13 +1000 diff --git a/freedv/tags/1.2.2/debian/compat b/freedv/tags/1.2.2/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/freedv/tags/1.2.2/debian/compat @@ -0,0 +1 @@ +9 diff --git a/freedv/tags/1.2.2/debian/control b/freedv/tags/1.2.2/debian/control new file mode 100644 index 00000000..d1472b25 --- /dev/null +++ b/freedv/tags/1.2.2/debian/control @@ -0,0 +1,19 @@ +Source: fdmdv2 +Section: main +Priority: optional +Maintainer: Stuart Longland +Build-Depends: debhelper (>= 9), cmake, libcodec2-dev, libgtk2.0-dev, + libhamlib-dev, libsamplerate-dev, libasound2-dev, libao-dev, libgsm1-dev, + portaudio19-dev, libsox-dev, libsndfile1-dev, libwxgtk3.0-dev +Standards-Version: 3.9.5 +Homepage: http://www.freedv.org +#Vcs-Git: git://anonscm.debian.org/collab-maint/freedv.git +#Vcs-Browser: http://anonscm.debian.org/?p=collab-maint/freedv.git;a=summary + +Package: freedv +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libcodec2 +Description: FreeDV: Open-Source Digital Voice modem + FreeDV is a digital voice modem that can transmit voice-quality + audio digitally over HF radio links in as little as 1.25kHz + bandwidth in varying conditions. diff --git a/freedv/tags/1.2.2/debian/copyright b/freedv/tags/1.2.2/debian/copyright new file mode 100644 index 00000000..b55a293b --- /dev/null +++ b/freedv/tags/1.2.2/debian/copyright @@ -0,0 +1,38 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: freedv +Source: + +Files: * +Copyright: + +License: + + + . + + +# If you want to use GPL v2 or later for the /debian/* files use +# the following clauses, or change it to suit. Delete these two lines +Files: debian/* +Copyright: 2015 unknown +License: GPL-2+ + This package 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 package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +# Please avoid to pick license terms that are more restrictive than the +# packaged work, as it may make Debian's contributions unacceptable upstream. diff --git a/freedv/tags/1.2.2/debian/docs b/freedv/tags/1.2.2/debian/docs new file mode 100644 index 00000000..acfbcb33 --- /dev/null +++ b/freedv/tags/1.2.2/debian/docs @@ -0,0 +1,3 @@ +credits.txt +README.txt +README.txt diff --git a/freedv/tags/1.2.2/debian/format b/freedv/tags/1.2.2/debian/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/freedv/tags/1.2.2/debian/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/freedv/tags/1.2.2/debian/rules b/freedv/tags/1.2.2/debian/rules new file mode 100755 index 00000000..ad892150 --- /dev/null +++ b/freedv/tags/1.2.2/debian/rules @@ -0,0 +1,30 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#DH_VERBOSE = 1 + +# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +# main packaging script based on dh7 syntax +%: + dh $@ + +# debmake generated override targets +# This is example for Cmake (See http://bugs.debian.org/641051 ) +override_dh_auto_configure: + dh_auto_configure -- \ + -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) \ + -DUSE_STATIC_CODEC2=FALSE \ + -DUSE_STATIC_SPEEXDSP=FALSE diff --git a/freedv/tags/1.2.2/script/spot.sh b/freedv/tags/1.2.2/script/spot.sh new file mode 100644 index 00000000..cb1309a2 --- /dev/null +++ b/freedv/tags/1.2.2/script/spot.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# spot.sh +# David Rowe Sep 2015 +# + +# Demo script for "spotting" based on FreeDV txt string. Posts a +# date-stamped text file to a web server. Called from FreeDV GUI +# program when a callsign is received in the txt msg. + + +# Q: how to remove repeated spots, or those close in time? +# +# Set up automated lftp login: +# +# $ lftp ftp://username@server +# Password: +# lftp username@server:~> set bmk:save-passwords true +# lftp username@server:~> bookmark add yourserver +# lftp username@server:~> bookmark list +# lftp username@server:~> quit + +SPOTFILE=/home/david/tmp/freedvspot.html +FTPSERVER=ftp.rowetel.com + +echo `date -u` " " $1 "
" >> $SPOTFILE +tail -n 25 $SPOTFILE > /tmp/spot.tmp1 +mv /tmp/spot.tmp1 $SPOTFILE +lftp -e "cd www;put $SPOTFILE;quit" $FTPSERVER diff --git a/freedv/tags/1.2.2/src/CMakeLists.txt b/freedv/tags/1.2.2/src/CMakeLists.txt new file mode 100644 index 00000000..ef0798b3 --- /dev/null +++ b/freedv/tags/1.2.2/src/CMakeLists.txt @@ -0,0 +1,79 @@ +set(FREEDV_SOURCES + dlg_audiooptions.cpp + dlg_filter.cpp + dlg_options.cpp + dlg_ptt.cpp + dlg_plugin.cpp + fdmdv2_main.cpp + fdmdv2_pa_wrapper.cpp + fdmdv2_plot.cpp + fdmdv2_plot_scalar.cpp + fdmdv2_plot_scatter.cpp + fdmdv2_plot_spectrum.cpp + fdmdv2_plot_waterfall.cpp + hamlib.cpp + serialport.cpp + topFrame.cpp + sox_biquad.c + comp.h + dlg_audiooptions.h + dlg_filter.h + dlg_options.h + dlg_ptt.h + fdmdv2_defines.h + fdmdv2_main.h + fdmdv2_pa_wrapper.h + fdmdv2_plot.h + fdmdv2_plot_scalar.h + fdmdv2_plot_scatter.h + fdmdv2_plot_spectrum.h + fdmdv2_plot_waterfall.h + hamlib.h + sox_biquad.h + sox/band.h + sox/biquad.c + sox/biquads.c + sox/biquad.h + sox/effects.c + sox/effects.h + sox/effects_i.c + sox/formats_i.c + sox/libsox.c + sox/sox.h + sox/sox_i.h + sox/soxomp.h + sox/util.h + sox/xmalloc.h + sox/xmalloc.c + topFrame.h + version.h +) + +# WIN32 is needed for Windows GUI apps and is ignored for UNIX like systems. +add_executable(freedv WIN32 ${FREEDV_SOURCES} ${RES_FILES}) +target_link_libraries(freedv ${FREEDV_LINK_LIBS}) +include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +if(FREEDV_STATIC_DEPS) + add_dependencies(freedv ${FREEDV_STATIC_DEPS}) +endif(FREEDV_STATIC_DEPS) +install(TARGETS freedv + RUNTIME DESTINATION bin) + +# Custom commands to build OSX images. +if(APPLE) + add_custom_command( + TARGET freedv + POST_BUILD + COMMAND mkdir ARGS -p FreeDV.app/Contents/MacOS + COMMAND mkdir ARGS -p FreeDV.app/Contents/Resources/English.lproj + COMMAND cp ARGS ${CMAKE_CURRENT_SOURCE_DIR}/info.plist FreeDV.app/Contents + COMMAND cp ARGS ${CMAKE_CURRENT_SOURCE_DIR}/freedv.icns FreeDV.app/Contents/Resources + COMMAND echo ARGS -n "APPL????" > FreeDV.app/Contents/PkgInfo + COMMAND cp ARGS freedv FreeDV.app/Contents/MacOS/FreeDV + COMMAND dylibbundler ARGS -od -b -x FreeDV.app/Contents/MacOS/FreeDV -d FreeDV.app/Contents/libs -p @executable_path/../libs/ + COMMAND mkdir dist_tmp + COMMAND cp -r FreeDV.app dist_tmp + COMMAND hdiutil create -srcfolder dist_tmp/ -volname FreeDV -format UDZO ./FreeDV.dmg + COMMAND rm -rf dist_tmp + ) +endif(APPLE) diff --git a/freedv/tags/1.2.2/src/Makefile.win32 b/freedv/tags/1.2.2/src/Makefile.win32 new file mode 100644 index 00000000..932e8518 --- /dev/null +++ b/freedv/tags/1.2.2/src/Makefile.win32 @@ -0,0 +1,52 @@ +# src/Makefile.win32 +# David Rowe 26 Oct 2012 +# +# Makefile for Win32 on msys/Mingw to help David R get up to speed +# +# $ make -f Makefile.Win32 + +CODEC2_PATH=$(HOME)/codec2-dev +INCLUDE_PATH=/usr/local/include + +WX_CONFIG=wx-config +WX_CPPFLAGS = $(shell $(WX_CONFIG) --cxxflags) -D__WXDEBUG__ +WX_LIBS = $(shell $(WX_CONFIG) --libs core, base, aui, adv, net) +SVN_REVISION=$(shell svnversion) +CODEC2_INC=$(CODEC2_PATH)/src +CODEC2_LIB=$(CODEC2_PATH)/build_win32/src/ + +CPP_FLAGS = -D_NO_AUTOTOOLS_ -I$(INCLUDE_PATH) $(WX_CPPFLAGS) -I$(CODEC2_INC) -I../extern/include -I. -g -Wall -DSVN_REVISION=\"$(SVN_REVISION)\" +LIBS = $(WX_LIBS) -L$(CODEC2_LIB) -lcodec2 -lm -lportaudiocpp -lportaudio -lpthread -lsndfile -lsamplerate -lhamlib -lsox -lspeexdsp + +OBJS = topFrame.o \ +fdmdv2_main.o \ +fdmdv2_plot.o \ +fdmdv2_plot_scalar.o \ +fdmdv2_plot_scatter.o \ +fdmdv2_plot_spectrum.o \ +fdmdv2_plot_waterfall.o \ +fdmdv2_pa_wrapper.o \ +dlg_audiooptions.o \ +dlg_ptt.o \ +dlg_options.o \ +dlg_filter.o \ +sox_biquad.o \ +hamlib.o \ +../../codec2-dev/src/golay23.o + +HDRS = version.h dlg_audiooptions.h dlg_ptt.h dlg_filter.h fdmdv2_main.h fdmdv2_defines.h fdmdv2_plot.h fdmdv2_plot_scalar.h fdmdv2_plot_waterfall.h fdmdv2_plot_scatter.h fdmdv2_plot_spectrum.h fdmdv2_pa_wrapper.h topFrame.h dlg_audiooptions.h topFrame.h varicode.h ../../codec2-dev/src/golay23.h hamlib.h + +all: freedv + +freedv: $(OBJS) + g++ -o freedv $(OBJS) $(CPP_FLAGS) $(LIBS) + +%.o: %.cpp $(HDRS) Makefile.win32 + g++ $(CPP_FLAGS) -c $< -o $@ + +%.o: %.c $(HDRS) Makefile.win32 + gcc $(CPP_FLAGS) -c $< -o $@ + +clean: + rm -f *.o fdmdv2 + diff --git a/freedv/tags/1.2.2/src/afreedvplugin.c b/freedv/tags/1.2.2/src/afreedvplugin.c new file mode 100644 index 00000000..5fdc424e --- /dev/null +++ b/freedv/tags/1.2.2/src/afreedvplugin.c @@ -0,0 +1,117 @@ +/* + afreedvplugin.c + David Rowe Feb 2016 + + Sample FreeDV plugin + + TODO: + [ ] plugin to call back to functions + [ ] ability to list .so's/DLLs and scan + [ ] where do we put plugins? + [ ] Windows build and test environment + + linux .so: + $ gcc -Wall -fPIC -c afreedvplugin.c + $ gcc -shared -Wl,-soname,afreedvplugin.so -o afreedvplugin.so afreedvplugin.o + win32 .dll: + $ i686-w64-mingw32-gcc -c afreedvplugin.c + $ i686-w64-mingw32-gcc -shared -o afreedvplugin.dll afreedvplugin.o -Wl,--out-implib,afreedvplugin_dll.a + +*/ + +#include +#include +#include + +#ifdef _WIN32_ +#define DLL __declspec(dllexport) +#else +#define DLL +#endif + + +#ifdef LATER +/* functions plugin can call - not sure how to link to these */ + +int plugin_alert(char string[]); +int plugin_get_persistant(char name[], char value[]); +int plugin_set_persistant(char name[], char value[]); +#endif +static int (*plugin_get_persistant)(char name[], char value[]); + +struct APLUGIN_STATES { + int symbol_rate; + int num_tones; + int counter; +}; + +/* plugin functions called by host, we need to write these */ + +void DLL plugin_name(char name[]) { + + sprintf(name, "aFreeDVplugIn"); +} + +/* + Text fields will be created for nparams, using the names + in *param_names[]. These fields we be saved to persistent + storage as name/param_names[0], name/param_names[1] .... +*/ + +void DLL *plugin_open(char *param_names[], + int *nparams, + int (*aplugin_get_persistant)(char *, char *)) +{ + struct APLUGIN_STATES *states; + + /* set up function ptrs */ + + plugin_get_persistant = aplugin_get_persistant; + + /* tell host how many persistent parameters we have and their names */ + + strcpy(param_names[0], "SymbolRate"); + strcpy(param_names[1], "NumTones"); + *nparams = 2; + + /* init local states */ + + states = (struct APLUGIN_STATES *)malloc(sizeof(struct APLUGIN_STATES)); + if (states == NULL) { + // TODO: plugin_alert("Problem starting plugin!"); + return NULL; + } + states->counter = 0; + + return (void*)states; +} + +void DLL plugin_close(void *states) { + free(states); +} + +void DLL plugin_start(void *s) { + struct APLUGIN_STATES *states = (struct APLUGIN_STATES*)s; + char txt[80]; + + fprintf(stderr, "\nplugin_start\n"); + + (plugin_get_persistant)("SymbolRate",txt); + states->symbol_rate = atoi(txt); + + (plugin_get_persistant)("NumTones",txt); + states->num_tones = atoi(txt); + + fprintf(stderr, "symbol_rate: %d num_tones: %d\n", states->symbol_rate, states->num_tones); +} + +void DLL plugin_stop(void *states) { + fprintf(stderr, "\nplugin_stop\n"); +} + +void DLL plugin_rx_samples(void *s, short samples[], int n) { + struct APLUGIN_STATES *states = (struct APLUGIN_STATES*)s; + //fprintf(stderr, "Got n=%d samples!\n", n); + //fprintf(stderr, "samples[0] = %d samples[%d-1] = %d counter = %d\n", samples[0], n, samples[n-1], states->counter++); +} + diff --git a/freedv/tags/1.2.2/src/comp.h b/freedv/tags/1.2.2/src/comp.h new file mode 100644 index 00000000..a3a1bd9b --- /dev/null +++ b/freedv/tags/1.2.2/src/comp.h @@ -0,0 +1,39 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: comp.h + AUTHOR......: David Rowe + DATE CREATED: 24/08/09 + + Complex number definition. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2009 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef __COMP__ +#define __COMP__ + +/* Complex number */ + +typedef struct +{ + float real; + float imag; +} COMP; + +#endif diff --git a/freedv/tags/1.2.2/src/dlg_audiooptions.cpp b/freedv/tags/1.2.2/src/dlg_audiooptions.cpp new file mode 100644 index 00000000..4e8cd981 --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_audiooptions.cpp @@ -0,0 +1,1264 @@ +//========================================================================= +// Name: AudioOptsDialog.cpp +// Purpose: Implements an Audio options selection dialog. +// +// Authors: David Rowe, David Witten +// License: +// +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================= +#include "fdmdv2_main.h" +#include "dlg_audiooptions.h" + +// constants for test waveform plots + +#define TEST_WAVEFORM_X 180 +#define TEST_WAVEFORM_Y 180 +#define TEST_WAVEFORM_PLOT_TIME 2.0 +#define TEST_WAVEFORM_PLOT_FS 400 +#define TEST_BUF_SIZE 1024 +#define TEST_FS 48000.0 +#define TEST_DT 0.1 // time between plot updates in seconds +#define TEST_WAVEFORM_PLOT_BUF ((int)(DT*400)) + +void AudioOptsDialog::Pa_Init(void) +{ + m_isPaInitialized = false; + + if((pa_err = Pa_Initialize()) == paNoError) + { + m_isPaInitialized = true; + } + else + { + wxMessageBox(wxT("Port Audio failed to initialize"), wxT("Pa_Initialize"), wxOK); + return; + } +} + + +void AudioOptsDialog::buildTestControls(PlotScalar **plotScalar, wxButton **btnTest, + wxPanel *parentPanel, wxBoxSizer *bSizer, wxString buttonLabel) +{ + wxBoxSizer* bSizer1 = new wxBoxSizer(wxVERTICAL); + + wxPanel *panel = new wxPanel(parentPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + *plotScalar = new PlotScalar((wxFrame*) panel, 1, TEST_WAVEFORM_PLOT_TIME, 1.0/TEST_WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "", 1); + (*plotScalar)->SetClientSize(wxSize(TEST_WAVEFORM_X,TEST_WAVEFORM_Y)); + bSizer1->Add(panel, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 8); + + *btnTest = new wxButton(parentPanel, wxID_ANY, buttonLabel, wxDefaultPosition, wxDefaultSize); + bSizer1->Add(*btnTest, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 0); + + bSizer->Add(bSizer1, 0, wxALIGN_CENTER_HORIZONTAL |wxALIGN_CENTER_VERTICAL ); +} + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// AudioOptsDialog() +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +AudioOptsDialog::AudioOptsDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +{ + //this->SetSizeHints(wxSize(850, 600), wxDefaultSize); + fprintf(stderr, "pos %d %d\n", pos.x, pos.y); + Pa_Init(); + + wxBoxSizer* mainSizer; + mainSizer = new wxBoxSizer(wxVERTICAL); + m_panel1 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer4; + bSizer4 = new wxBoxSizer(wxVERTICAL); + m_notebook1 = new wxNotebook(m_panel1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM); + m_panelRx = new wxPanel(m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer20; + bSizer20 = new wxBoxSizer(wxVERTICAL); + wxGridSizer* gSizer4; + gSizer4 = new wxGridSizer(2, 1, 0, 0); + + // Rx In ----------------------------------------------------------------------- + + wxStaticBoxSizer* sbSizer2; + sbSizer2 = new wxStaticBoxSizer(new wxStaticBox(m_panelRx, wxID_ANY, _("From Radio")), wxHORIZONTAL); + + wxBoxSizer* bSizer811a = new wxBoxSizer(wxVERTICAL); + + m_listCtrlRxInDevices = new wxListCtrl(m_panelRx, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_VRULES); + bSizer811a->Add(m_listCtrlRxInDevices, 1, wxALL|wxEXPAND, 1); + + wxBoxSizer* bSizer811; + bSizer811 = new wxBoxSizer(wxHORIZONTAL); + m_staticText51 = new wxStaticText(m_panelRx, wxID_ANY, _("Device:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText51->Wrap(-1); + bSizer811->Add(m_staticText51, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_textCtrlRxIn = new wxTextCtrl(m_panelRx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + bSizer811->Add(m_textCtrlRxIn, 1, wxALIGN_CENTER_VERTICAL|wxALL, 1); + m_staticText6 = new wxStaticText(m_panelRx, wxID_ANY, _("Sample Rate:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText6->Wrap(-1); + bSizer811->Add(m_staticText6, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_cbSampleRateRxIn = new wxComboBox(m_panelRx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(90,-1), 0, NULL, wxCB_DROPDOWN); + bSizer811->Add(m_cbSampleRateRxIn, 0, wxALIGN_CENTER_VERTICAL|wxALL, 1); + + bSizer811a->Add(bSizer811, 0, wxEXPAND, 5); + + sbSizer2->Add(bSizer811a, 1, wxEXPAND, 2); + buildTestControls(&m_plotScalarRxIn, &m_btnRxInTest, m_panelRx, sbSizer2, _("Rec 2s")); + + gSizer4->Add(sbSizer2, 1, wxEXPAND, 5); + + // Rx Out ----------------------------------------------------------------------- + + wxStaticBoxSizer* sbSizer3; + sbSizer3 = new wxStaticBoxSizer(new wxStaticBox(m_panelRx, wxID_ANY, _("To Speaker/Headphones")), wxHORIZONTAL); + + wxBoxSizer* bSizer81a = new wxBoxSizer(wxVERTICAL); + + m_listCtrlRxOutDevices = new wxListCtrl(m_panelRx, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_VRULES); + bSizer81a->Add(m_listCtrlRxOutDevices, 1, wxALL|wxEXPAND, 1); + + wxBoxSizer* bSizer81; + bSizer81 = new wxBoxSizer(wxHORIZONTAL); + m_staticText9 = new wxStaticText(m_panelRx, wxID_ANY, _("Device:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText9->Wrap(-1); + bSizer81->Add(m_staticText9, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5); + m_textCtrlRxOut = new wxTextCtrl(m_panelRx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + bSizer81->Add(m_textCtrlRxOut, 1, wxALIGN_CENTER_VERTICAL|wxALL, 1); + m_staticText10 = new wxStaticText(m_panelRx, wxID_ANY, _("Sample Rate:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText10->Wrap(-1); + bSizer81->Add(m_staticText10, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_cbSampleRateRxOut = new wxComboBox(m_panelRx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(90,-1), 0, NULL, wxCB_DROPDOWN); + bSizer81->Add(m_cbSampleRateRxOut, 0, wxALIGN_CENTER_VERTICAL|wxALL, 1); + + bSizer81a->Add(bSizer81, 0, wxEXPAND, 5); + + sbSizer3->Add(bSizer81a, 1, wxEXPAND, 2); + buildTestControls(&m_plotScalarRxOut, &m_btnRxOutTest, m_panelRx, sbSizer3, _("Play 2s")); + + gSizer4->Add(sbSizer3, 1, wxEXPAND, 2); + bSizer20->Add(gSizer4, 1, wxEXPAND, 1); + m_panelRx->SetSizer(bSizer20); + m_panelRx->Layout(); + bSizer20->Fit(m_panelRx); + m_notebook1->AddPage(m_panelRx, _("Receive"), true); + + // Tx Tab ------------------------------------------------------------------------------- + + m_panelTx = new wxPanel(m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer18; + bSizer18 = new wxBoxSizer(wxVERTICAL); + wxGridSizer* gSizer2; + gSizer2 = new wxGridSizer(2, 1, 0, 0); + + // Tx In ---------------------------------------------------------------------------------- + + wxStaticBoxSizer* sbSizer22; + sbSizer22 = new wxStaticBoxSizer(new wxStaticBox(m_panelTx, wxID_ANY, _("From Microphone")), wxHORIZONTAL); + + wxBoxSizer* bSizer83a = new wxBoxSizer(wxVERTICAL); + + m_listCtrlTxInDevices = new wxListCtrl(m_panelTx, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_VRULES); + bSizer83a->Add(m_listCtrlTxInDevices, 1, wxALL|wxEXPAND, 1); + wxBoxSizer* bSizer83; + bSizer83 = new wxBoxSizer(wxHORIZONTAL); + m_staticText12 = new wxStaticText(m_panelTx, wxID_ANY, _("Device:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText12->Wrap(-1); + bSizer83->Add(m_staticText12, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_textCtrlTxIn = new wxTextCtrl(m_panelTx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + bSizer83->Add(m_textCtrlTxIn, 1, wxALIGN_CENTER_VERTICAL|wxALL, 1); + m_staticText11 = new wxStaticText(m_panelTx, wxID_ANY, _("Sample Rate:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText11->Wrap(-1); + bSizer83->Add(m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_cbSampleRateTxIn = new wxComboBox(m_panelTx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(90,-1), 0, NULL, wxCB_DROPDOWN); + bSizer83->Add(m_cbSampleRateTxIn, 0, wxALL, 1); + + bSizer83a->Add(bSizer83, 0, wxEXPAND, 5); + + sbSizer22->Add(bSizer83a, 1, wxEXPAND, 2); + buildTestControls(&m_plotScalarTxIn, &m_btnTxInTest, m_panelTx, sbSizer22, _("Rec 2s")); + + gSizer2->Add(sbSizer22, 1, wxEXPAND, 5); + + // Tx Out ---------------------------------------------------------------------------------- + + wxStaticBoxSizer* sbSizer21; + sbSizer21 = new wxStaticBoxSizer(new wxStaticBox(m_panelTx, wxID_ANY, _("To Radio")), wxHORIZONTAL); + + wxBoxSizer* bSizer82a = new wxBoxSizer(wxVERTICAL); + + m_listCtrlTxOutDevices = new wxListCtrl(m_panelTx, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_VRULES); + bSizer82a->Add(m_listCtrlTxOutDevices, 1, wxALL|wxEXPAND, 2); + wxBoxSizer* bSizer82; + bSizer82 = new wxBoxSizer(wxHORIZONTAL); + m_staticText81 = new wxStaticText(m_panelTx, wxID_ANY, _("Device:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText81->Wrap(-1); + bSizer82->Add(m_staticText81, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5); + m_textCtrlTxOut = new wxTextCtrl(m_panelTx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + bSizer82->Add(m_textCtrlTxOut, 1, wxALIGN_CENTER_VERTICAL|wxALL, 1); + m_staticText71 = new wxStaticText(m_panelTx, wxID_ANY, _("Sample Rate:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText71->Wrap(-1); + bSizer82->Add(m_staticText71, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5); + m_cbSampleRateTxOut = new wxComboBox(m_panelTx, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(90,-1), 0, NULL, wxCB_DROPDOWN); + bSizer82->Add(m_cbSampleRateTxOut, 0, wxALL, 1); + + bSizer82a->Add(bSizer82, 0, wxEXPAND, 5); + + sbSizer21->Add(bSizer82a, 1, wxEXPAND, 2); + buildTestControls(&m_plotScalarTxOut, &m_btnTxOutTest, m_panelTx, sbSizer21, _("Play 2s")); + + gSizer2->Add(sbSizer21, 1, wxEXPAND, 5); + bSizer18->Add(gSizer2, 1, wxEXPAND, 1); + m_panelTx->SetSizer(bSizer18); + m_panelTx->Layout(); + bSizer18->Fit(m_panelTx); + m_notebook1->AddPage(m_panelTx, _("Transmit"), false); + + // API Tab ------------------------------------------------------------------- + + m_panelAPI = new wxPanel(m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer12; + bSizer12 = new wxBoxSizer(wxHORIZONTAL); + wxGridSizer* gSizer31; + gSizer31 = new wxGridSizer(2, 1, 0, 0); + wxStaticBoxSizer* sbSizer1; + sbSizer1 = new wxStaticBoxSizer(new wxStaticBox(m_panelAPI, wxID_ANY, _("PortAudio")), wxVERTICAL); + + wxGridSizer* gSizer3; + gSizer3 = new wxGridSizer(4, 2, 0, 0); + + m_staticText7 = new wxStaticText(m_panelAPI, wxID_ANY, _("PortAudio Version String:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText7->Wrap(-1); + gSizer3->Add(m_staticText7, 1, wxALIGN_RIGHT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + m_textStringVer = new wxStaticText(m_panelAPI, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + gSizer3->Add(m_textStringVer, 1, wxALIGN_LEFT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + + m_staticText8 = new wxStaticText(m_panelAPI, wxID_ANY, _("PortAudio Int Version:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText8->Wrap(-1); + gSizer3->Add(m_staticText8, 1, wxALIGN_RIGHT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + m_textIntVer = new wxStaticText(m_panelAPI, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(45,-1), 0); + gSizer3->Add(m_textIntVer, 1, wxALIGN_LEFT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + + m_staticText5 = new wxStaticText(m_panelAPI, wxID_ANY, _("Device Count:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText5->Wrap(-1); + gSizer3->Add(m_staticText5, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 10); + m_textCDevCount = new wxStaticText(m_panelAPI, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(45,-1), 0); + gSizer3->Add(m_textCDevCount, 1, wxALIGN_LEFT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + + m_staticText4 = new wxStaticText(m_panelAPI, wxID_ANY, _("API Count:"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText4->Wrap(-1); + gSizer3->Add(m_staticText4, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 10); + m_textAPICount = new wxStaticText(m_panelAPI, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(45,-1), 0); + m_textAPICount->SetMaxSize(wxSize(45,-1)); + gSizer3->Add(m_textAPICount, 1, wxALIGN_LEFT|wxALL|wxALIGN_CENTER_VERTICAL, 10); + + sbSizer1->Add(gSizer3, 1, wxEXPAND, 2); + gSizer31->Add(sbSizer1, 1, wxEXPAND, 2); + wxStaticBoxSizer* sbSizer6; + sbSizer6 = new wxStaticBoxSizer(new wxStaticBox(m_panelAPI, wxID_ANY, _("Other")), wxVERTICAL); + gSizer31->Add(sbSizer6, 1, wxEXPAND, 5); + bSizer12->Add(gSizer31, 1, wxEXPAND, 5); + m_panelAPI->SetSizer(bSizer12); + m_panelAPI->Layout(); + bSizer12->Fit(m_panelAPI); + m_notebook1->AddPage(m_panelAPI, _("API Info"), false); + bSizer4->Add(m_notebook1, 1, wxEXPAND | wxALL, 0); + m_panel1->SetSizer(bSizer4); + m_panel1->Layout(); + bSizer4->Fit(m_panel1); + mainSizer->Add(m_panel1, 1, wxEXPAND | wxALL, 1); + + wxBoxSizer* bSizer6; + bSizer6 = new wxBoxSizer(wxHORIZONTAL); + m_btnRefresh = new wxButton(this, wxID_ANY, _("Refresh"), wxDefaultPosition, wxDefaultSize, 0); + bSizer6->Add(m_btnRefresh, 0, wxALIGN_CENTER|wxALL, 2); + + m_sdbSizer1 = new wxStdDialogButtonSizer(); + + m_sdbSizer1OK = new wxButton(this, wxID_OK); + m_sdbSizer1->AddButton(m_sdbSizer1OK); + + m_sdbSizer1Cancel = new wxButton(this, wxID_CANCEL); + m_sdbSizer1->AddButton(m_sdbSizer1Cancel); + + m_sdbSizer1Apply = new wxButton(this, wxID_APPLY); + m_sdbSizer1->AddButton(m_sdbSizer1Apply); + + m_sdbSizer1->Realize(); + + bSizer6->Add(m_sdbSizer1, 1, wxALIGN_CENTER_VERTICAL, 2); + mainSizer->Add(bSizer6, 0, wxEXPAND, 2); + this->SetSizer(mainSizer); + this->Layout(); + this->Centre(wxBOTH); +// this->Centre(wxBOTH); + + m_notebook1->SetSelection(0); + + showAPIInfo(); + m_RxInDevices.m_listDevices = m_listCtrlRxInDevices; + m_RxInDevices.direction = AUDIO_IN; + m_RxInDevices.m_textDevice = m_textCtrlRxIn; + m_RxInDevices.m_cbSampleRate = m_cbSampleRateRxIn; + + m_RxOutDevices.m_listDevices = m_listCtrlRxOutDevices; + m_RxOutDevices.direction = AUDIO_OUT; + m_RxOutDevices.m_textDevice = m_textCtrlRxOut; + m_RxOutDevices.m_cbSampleRate = m_cbSampleRateRxOut; + + m_TxInDevices.m_listDevices = m_listCtrlTxInDevices; + m_TxInDevices.direction = AUDIO_IN; + m_TxInDevices.m_textDevice = m_textCtrlTxIn; + m_TxInDevices.m_cbSampleRate = m_cbSampleRateTxIn; + + m_TxOutDevices.m_listDevices = m_listCtrlTxOutDevices; + m_TxOutDevices.direction = AUDIO_OUT; + m_TxOutDevices.m_textDevice = m_textCtrlTxOut; + m_TxOutDevices.m_cbSampleRate = m_cbSampleRateTxOut; + + populateParams(m_RxInDevices); + populateParams(m_RxOutDevices); + populateParams(m_TxInDevices); + populateParams(m_TxOutDevices); + + m_listCtrlRxInDevices->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( AudioOptsDialog::OnRxInDeviceSelect ), NULL, this ); + m_listCtrlRxOutDevices->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( AudioOptsDialog::OnRxOutDeviceSelect ), NULL, this ); + m_listCtrlTxInDevices->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( AudioOptsDialog::OnTxInDeviceSelect ), NULL, this ); + m_listCtrlTxOutDevices->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( AudioOptsDialog::OnTxOutDeviceSelect ), NULL, this ); + + // wire up test buttons + m_btnRxInTest->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnRxInTest ), NULL, this ); + m_btnRxOutTest->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnRxOutTest ), NULL, this ); + m_btnTxInTest->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnTxInTest ), NULL, this ); + m_btnTxOutTest->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnTxOutTest ), NULL, this ); + + m_btnRefresh->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnRefreshClick ), NULL, this ); + m_sdbSizer1Apply->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnApplyAudioParameters ), NULL, this ); + m_sdbSizer1Cancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnCancelAudioParameters ), NULL, this ); + m_sdbSizer1OK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnOkAudioParameters ), NULL, this ); +/* + void OnClose( wxCloseEvent& event ) { event.Skip(); } + void OnHibernate( wxActivateEvent& event ) { event.Skip(); } + void OnIconize( wxIconizeEvent& event ) { event.Skip(); } + void OnInitDialog( wxInitDialogEvent& event ) { event.Skip(); } +*/ +// this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(AudioOptsDialog::OnClose)); + this->Connect(wxEVT_HIBERNATE, wxActivateEventHandler(AudioOptsDialog::OnHibernate)); + this->Connect(wxEVT_ICONIZE, wxIconizeEventHandler(AudioOptsDialog::OnIconize)); + this->Connect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(AudioOptsDialog::OnInitDialog)); +} + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// ~AudioOptsDialog() +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +AudioOptsDialog::~AudioOptsDialog() +{ + Pa_Terminate(); + + // Disconnect Events + this->Disconnect(wxEVT_HIBERNATE, wxActivateEventHandler(AudioOptsDialog::OnHibernate)); + this->Disconnect(wxEVT_ICONIZE, wxIconizeEventHandler(AudioOptsDialog::OnIconize)); + this->Disconnect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(AudioOptsDialog::OnInitDialog)); + + m_listCtrlRxInDevices->Disconnect(wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(AudioOptsDialog::OnRxInDeviceSelect), NULL, this); + m_listCtrlRxOutDevices->Disconnect(wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(AudioOptsDialog::OnRxOutDeviceSelect), NULL, this); + m_listCtrlTxInDevices->Disconnect(wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(AudioOptsDialog::OnTxInDeviceSelect), NULL, this); + m_listCtrlTxOutDevices->Disconnect(wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(AudioOptsDialog::OnTxOutDeviceSelect), NULL, this); + + m_btnRxInTest->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnRxInTest ), NULL, this ); + m_btnRxOutTest->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnRxOutTest ), NULL, this ); + m_btnTxInTest->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnTxInTest ), NULL, this ); + m_btnTxOutTest->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AudioOptsDialog::OnTxOutTest ), NULL, this ); + + m_btnRefresh->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(AudioOptsDialog::OnRefreshClick), NULL, this); + m_sdbSizer1Apply->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(AudioOptsDialog::OnApplyAudioParameters), NULL, this); + m_sdbSizer1Cancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(AudioOptsDialog::OnCancelAudioParameters), NULL, this); + m_sdbSizer1OK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(AudioOptsDialog::OnOkAudioParameters), NULL, this); + +} + +//------------------------------------------------------------------------- +// OnInitDialog() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnInitDialog( wxInitDialogEvent& event ) +{ + ExchangeData(EXCHANGE_DATA_IN); +} + +//------------------------------------------------------------------------- +// OnInitDialog() +// +// helper function to look up name of devNum, and if it exists write +// name to textCtrl. Used to trap dissapearing devices. +//------------------------------------------------------------------------- +int AudioOptsDialog::setTextCtrlIfDevNumValid(wxTextCtrl *textCtrl, wxListCtrl *listCtrl, int devNum) +{ + int i, aDevNum, found_devNum; + + // ignore last list entry as it is the "none" entry + + found_devNum = 0; + for(i=0; iGetItemCount()-1; i++) { + aDevNum = wxAtoi(listCtrl->GetItemText(i, 1)); + //printf("aDevNum: %d devNum: %d\n", aDevNum, devNum); + if (aDevNum == devNum) { + found_devNum = 1; + textCtrl->SetValue(listCtrl->GetItemText(i, 0) + " (" + wxString::Format(wxT("%i"),devNum) + ")"); + printf("setting focus of %d\n", i); + listCtrl->SetItemState(i, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); + } + } + + if (found_devNum) + return devNum; + else { + textCtrl->SetValue("none"); + return -1; + } +} + +//------------------------------------------------------------------------- +// ExchangeData() +//------------------------------------------------------------------------- +int AudioOptsDialog::ExchangeData(int inout) +{ + if(inout == EXCHANGE_DATA_IN) + { + // Map sound card device numbers to tx/rx device numbers depending + // on number of sound cards in use + + printf("EXCHANGE_DATA_IN:\n"); + printf(" g_nSoundCards: %d\n", g_nSoundCards); + printf(" g_soundCard1InDeviceNum: %d\n", g_soundCard1InDeviceNum); + printf(" g_soundCard1OutDeviceNum: %d\n", g_soundCard1OutDeviceNum); + printf(" g_soundCard1SampleRate: %d\n", g_soundCard1SampleRate); + printf(" g_soundCard2InDeviceNum: %d\n", g_soundCard2InDeviceNum); + printf(" g_soundCard2OutDeviceNum: %d\n", g_soundCard2OutDeviceNum); + printf(" g_soundCard2SampleRate: %d\n", g_soundCard2SampleRate); + + if (g_nSoundCards == 0) { + m_textCtrlRxIn ->SetValue("none"); rxInAudioDeviceNum = -1; + m_textCtrlRxOut->SetValue("none"); rxOutAudioDeviceNum = -1; + m_textCtrlTxIn ->SetValue("none"); txInAudioDeviceNum = -1; + m_textCtrlTxOut->SetValue("none"); txOutAudioDeviceNum = -1; + } + + if (g_nSoundCards == 1) { + rxInAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlRxIn, + m_listCtrlRxInDevices, + g_soundCard1InDeviceNum); + + rxOutAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlRxOut, + m_listCtrlRxOutDevices, + g_soundCard1OutDeviceNum); + + if ((rxInAudioDeviceNum != -1) && (rxInAudioDeviceNum != -1)) { + m_cbSampleRateRxIn->SetValue(wxString::Format(wxT("%i"),g_soundCard1SampleRate)); + m_cbSampleRateRxOut->SetValue(wxString::Format(wxT("%i"),g_soundCard1SampleRate)); + } + + m_textCtrlTxIn ->SetValue("none"); txInAudioDeviceNum = -1; + m_textCtrlTxOut->SetValue("none"); txOutAudioDeviceNum = -1; + } + + if (g_nSoundCards == 2) { + + rxInAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlRxIn, + m_listCtrlRxInDevices, + g_soundCard1InDeviceNum); + + rxOutAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlRxOut, + m_listCtrlRxOutDevices, + g_soundCard2OutDeviceNum); + + txInAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlTxIn, + m_listCtrlTxInDevices, + g_soundCard2InDeviceNum); + + txOutAudioDeviceNum = setTextCtrlIfDevNumValid(m_textCtrlTxOut, + m_listCtrlTxOutDevices, + g_soundCard1OutDeviceNum); + + if ((rxInAudioDeviceNum != -1) && (txOutAudioDeviceNum != -1)) { + m_cbSampleRateRxIn->SetValue(wxString::Format(wxT("%i"),g_soundCard1SampleRate)); + m_cbSampleRateTxOut->SetValue(wxString::Format(wxT("%i"),g_soundCard1SampleRate)); + } + + if ((txInAudioDeviceNum != -1) && (rxOutAudioDeviceNum != -1)) { + m_cbSampleRateTxIn->SetValue(wxString::Format(wxT("%i"),g_soundCard2SampleRate)); + m_cbSampleRateRxOut->SetValue(wxString::Format(wxT("%i"),g_soundCard2SampleRate)); + } + } + printf(" rxInAudioDeviceNum: %d\n rxOutAudioDeviceNum: %d\n txInAudioDeviceNum: %d\n txOutAudioDeviceNum: %d\n", + rxInAudioDeviceNum, rxOutAudioDeviceNum, txInAudioDeviceNum, txOutAudioDeviceNum); + } + + if(inout == EXCHANGE_DATA_OUT) + { + int valid_one_card_config = 0; + int valid_two_card_config = 0; + wxString sampleRate1, sampleRate2; + + printf("EXCHANGE_DATA_OUT:\n"); + printf(" rxInAudioDeviceNum: %d\n rxOutAudioDeviceNum: %d\n txInAudioDeviceNum: %d\n txOutAudioDeviceNum: %d\n", + rxInAudioDeviceNum, rxOutAudioDeviceNum, txInAudioDeviceNum, txOutAudioDeviceNum); + + // --------------------------------------------------------------- + // check we have a valid 1 or 2 sound card configuration + // --------------------------------------------------------------- + + // one sound card config, tx device numbers should be set to -1 + + if ((rxInAudioDeviceNum != -1) && (rxOutAudioDeviceNum != -1) && + (txInAudioDeviceNum == -1) && (txOutAudioDeviceNum == -1)) { + + valid_one_card_config = 1; + + // in and out sample rate must be the same, as there is one callback + + sampleRate1 = m_cbSampleRateRxIn->GetValue(); + if (!sampleRate1.IsSameAs(m_cbSampleRateRxOut->GetValue())) { + wxMessageBox(wxT("With a single sound card the Sample Rate of " + "From Radio and To Speaker/Headphones must be the same."), wxT(""), wxOK); + return -1; + } + } + + // two card configuration + + if ((rxInAudioDeviceNum != -1) && (rxOutAudioDeviceNum != -1) && + (txInAudioDeviceNum != -1) && (txOutAudioDeviceNum != -1)) { + + valid_two_card_config = 1; + + // Check we haven't doubled up on sound devices + + if (rxInAudioDeviceNum == txInAudioDeviceNum) { + wxMessageBox(wxT("You must use different devices for From Radio and From Microphone"), wxT(""), wxOK); + return -1; + } + + if (rxOutAudioDeviceNum == txOutAudioDeviceNum) { + wxMessageBox(wxT("You must use different devices for To Radio and To Speaker/Headphones"), wxT(""), wxOK); + return -1; + } + + // Check sample rates for callback 1 devices are the same, + // as input and output are handled synchronously by one + // portaudio callback + + sampleRate1 = m_cbSampleRateRxIn->GetValue(); + if (!sampleRate1.IsSameAs(m_cbSampleRateTxOut->GetValue())) { + wxMessageBox(wxT("With two sound cards the Sample Rate " + "of From Radio and To Radio must be the same."), wxT(""), wxOK); + return -1; + } + + // check sample rate for callback 2 devices is the same + + sampleRate2 = m_cbSampleRateTxIn->GetValue(); + if (!sampleRate2.IsSameAs(m_cbSampleRateRxOut->GetValue())) { + wxMessageBox(wxT("With two sound cards the Sample Rate of " + "From Microphone and To Speaker/Headphones must be the same."), wxT(""), wxOK); + return -1; + } + + } + + printf(" valid_one_card_config: %d valid_two_card_config: %d\n", valid_one_card_config, valid_two_card_config); + + if (!valid_one_card_config && !valid_two_card_config) { + wxMessageBox(wxT("Invalid one or two sound card configuration"), wxT(""), wxOK); + return -1; + } + + // --------------------------------------------------------------- + // Map Rx/TX device numbers to sound card device numbers used + // in callbacks. Portaudio uses one callback per sound card so + // we have to be soundcard oriented at run time rather than + // Tx/Rx oriented as in this dialog. + // --------------------------------------------------------------- + g_nSoundCards = 0; + g_soundCard1InDeviceNum = g_soundCard1OutDeviceNum = g_soundCard2InDeviceNum = g_soundCard2OutDeviceNum = -1; + + if (valid_one_card_config) { + + // Only callback 1 used + + g_nSoundCards = 1; + g_soundCard1InDeviceNum = rxInAudioDeviceNum; + g_soundCard1OutDeviceNum = rxOutAudioDeviceNum; + g_soundCard1SampleRate = wxAtoi(sampleRate1); + } + + if (valid_two_card_config) { + g_nSoundCards = 2; + g_soundCard1InDeviceNum = rxInAudioDeviceNum; + g_soundCard1OutDeviceNum = txOutAudioDeviceNum; + g_soundCard1SampleRate = wxAtoi(sampleRate1); + g_soundCard2InDeviceNum = txInAudioDeviceNum; + g_soundCard2OutDeviceNum = rxOutAudioDeviceNum; + g_soundCard2SampleRate = wxAtoi(sampleRate2); + } + + printf(" g_nSoundCards: %d\n", g_nSoundCards); + printf(" g_soundCard1InDeviceNum: %d\n", g_soundCard1InDeviceNum); + printf(" g_soundCard1OutDeviceNum: %d\n", g_soundCard1OutDeviceNum); + printf(" g_soundCard1SampleRate: %d\n", g_soundCard1SampleRate); + printf(" g_soundCard2InDeviceNum: %d\n", g_soundCard2InDeviceNum); + printf(" g_soundCard2OutDeviceNum: %d\n", g_soundCard2OutDeviceNum); + printf(" g_soundCard2SampleRate: %d\n", g_soundCard2SampleRate); + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(wxT("/Audio/soundCard1InDeviceNum"), g_soundCard1InDeviceNum); + pConfig->Write(wxT("/Audio/soundCard1OutDeviceNum"), g_soundCard1OutDeviceNum); + pConfig->Write(wxT("/Audio/soundCard1SampleRate"), g_soundCard1SampleRate ); + + pConfig->Write(wxT("/Audio/soundCard2InDeviceNum"), g_soundCard2InDeviceNum); + pConfig->Write(wxT("/Audio/soundCard2OutDeviceNum"), g_soundCard2OutDeviceNum); + pConfig->Write(wxT("/Audio/soundCard2SampleRate"), g_soundCard2SampleRate ); + + pConfig->Flush(); + delete wxConfigBase::Set((wxConfigBase *) NULL); + } + + return 0; +} + +//------------------------------------------------------------------------- +// buildListOfSupportedSampleRates() +//------------------------------------------------------------------------- +int AudioOptsDialog:: buildListOfSupportedSampleRates(wxComboBox *cbSampleRate, int devNum, int in_out) +{ + // every sound device has a different list of supported sample rates, so + // we work out which ones are supported and populate the list ctrl + + static double standardSampleRates[] = + { + 8000.0, 9600.0, + 11025.0, 12000.0, + 16000.0, 22050.0, + 24000.0, 32000.0, + 44100.0, 48000.0, + 88200.0, 96000.0, + 192000.0, -1 // negative terminated list + }; + + const PaDeviceInfo *deviceInfo; + PaStreamParameters inputParameters, outputParameters; + PaError err; + wxString str; + int i, numSampleRates; + + deviceInfo = Pa_GetDeviceInfo(devNum); + if (deviceInfo == NULL) { + printf("Pa_GetDeviceInfo(%d) failed!\n", devNum); + cbSampleRate->Clear(); + return 0; + } + + inputParameters.device = devNum; + inputParameters.channelCount = deviceInfo->maxInputChannels; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = 0; + inputParameters.hostApiSpecificStreamInfo = NULL; + + outputParameters.device = devNum; + outputParameters.channelCount = deviceInfo->maxOutputChannels; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = 0; + outputParameters.hostApiSpecificStreamInfo = NULL; + + cbSampleRate->Clear(); + //printf("devNum %d supports: ", devNum); + numSampleRates = 0; + for(i = 0; standardSampleRates[i] > 0; i++) + { + if (in_out == AUDIO_IN) + err = Pa_IsFormatSupported(&inputParameters, NULL, standardSampleRates[i]); + else + err = Pa_IsFormatSupported(NULL, &outputParameters, standardSampleRates[i]); + + if( err == paFormatIsSupported ) { + str.Printf("%i", (int)standardSampleRates[i]); + cbSampleRate->AppendString(str); + printf("%i ", (int)standardSampleRates[i]); + numSampleRates++; + } + } + printf("\n"); + + return numSampleRates; +} + +//------------------------------------------------------------------------- +// showAPIInfo() +//------------------------------------------------------------------------- +void AudioOptsDialog::showAPIInfo() +{ + wxString strval; + int apiVersion; + int apiCount = 0; + int numDevices = 0; + + strval = Pa_GetVersionText(); + m_textStringVer->SetLabel(strval); + + apiVersion = Pa_GetVersion(); + strval.Printf(wxT("%d"), apiVersion); + m_textIntVer->SetLabel(strval); + + apiCount = Pa_GetHostApiCount(); + strval.Printf(wxT("%d"), apiCount); + m_textAPICount->SetLabel(strval); + + numDevices = Pa_GetDeviceCount(); + strval.Printf(wxT("%d"), numDevices); + m_textCDevCount->SetLabel(strval); +} + +//------------------------------------------------------------------------- +// populateParams() +//------------------------------------------------------------------------- +void AudioOptsDialog::populateParams(AudioInfoDisplay ai) +{ + const PaDeviceInfo *deviceInfo = NULL; + wxListCtrl* ctrl = ai.m_listDevices; + int in_out = ai.direction; + long idx; + int numDevices; + wxListItem listItem; + wxString buf; + int devn; + int col = 0; + + numDevices = Pa_GetDeviceCount(); + + if(ctrl->GetColumnCount() > 0) + { + ctrl->ClearAll(); + } + + listItem.SetAlign(wxLIST_FORMAT_LEFT); + listItem.SetText(wxT("Device")); + idx = ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 300); + + listItem.SetAlign(wxLIST_FORMAT_CENTRE); + listItem.SetText(wxT("ID")); + idx = ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 45); + + listItem.SetAlign(wxLIST_FORMAT_LEFT); + listItem.SetText(wxT("API")); + idx = ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 100); + + if(in_out == AUDIO_IN) + { + listItem.SetAlign(wxLIST_FORMAT_CENTRE); + listItem.SetText(wxT("Default Sample Rate")); + idx = ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 160); + } + else if(in_out == AUDIO_OUT) + { + listItem.SetAlign(wxLIST_FORMAT_CENTRE); + listItem.SetText(wxT("Default Sample Rate")); + idx = ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 160); + } + + #ifdef LATENCY + listItem.SetAlign(wxLIST_FORMAT_CENTRE); + listItem.SetText(wxT("Min Latency")); + ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 100); + + listItem.SetAlign(wxLIST_FORMAT_CENTRE); + listItem.SetText(wxT("Max Latency")); + ctrl->InsertColumn(col, listItem); + ctrl->SetColumnWidth(col++, 100); + #endif + + for(devn = 0; devn < numDevices; devn++) + { + buf.Printf(wxT("")); + deviceInfo = Pa_GetDeviceInfo(devn); + if( ((in_out == AUDIO_IN) && (deviceInfo->maxInputChannels > 0)) || + ((in_out == AUDIO_OUT) && (deviceInfo->maxOutputChannels > 0))) + { + col = 0; + buf.Printf(wxT("%s"), deviceInfo->name); + idx = ctrl->InsertItem(ctrl->GetItemCount(), buf); + col++; + + buf.Printf(wxT("%d"), devn); + ctrl->SetItem(idx, col++, buf); + + buf.Printf(wxT("%s"), Pa_GetHostApiInfo(deviceInfo->hostApi)->name); + ctrl->SetItem(idx, col++, buf); + + buf.Printf(wxT("%i"), (int)deviceInfo->defaultSampleRate); + ctrl->SetItem(idx, col++, buf); + + #ifdef LATENCY + if (in_out == AUDIO_IN) + buf.Printf(wxT("%8.4f"), deviceInfo->defaultLowInputLatency); + else + buf.Printf(wxT("%8.4f"), deviceInfo->defaultLowOutputLatency); + ctrl->SetItem(idx, col++, buf); + + if (in_out == AUDIO_IN) + buf.Printf(wxT("%8.4f"), deviceInfo->defaultHighInputLatency); + else + buf.Printf(wxT("%8.4f"), deviceInfo->defaultHighOutputLatency); + ctrl->SetItem(idx, col++, buf); + #endif + } + } + + // add "none" option at end + + buf.Printf(wxT("%s"), "none"); + idx = ctrl->InsertItem(ctrl->GetItemCount(), buf); +} + +//------------------------------------------------------------------------- +// OnDeviceSelect() +// +// helper function to set up "Device:" and "Sample Rate:" fields when +// we click on a line in the list of devices box +//------------------------------------------------------------------------- +void AudioOptsDialog::OnDeviceSelect(wxComboBox *cbSampleRate, + wxTextCtrl *textCtrl, + int *devNum, + wxListCtrl *listCtrlDevices, + int index, + int in_out) +{ + + wxString devName = listCtrlDevices->GetItemText(index, 0); + if (devName.IsSameAs("none")) { + *devNum = -1; + textCtrl->SetValue("none"); + } + else { + *devNum = wxAtoi(listCtrlDevices->GetItemText(index, 1)); + textCtrl->SetValue(devName + " (" + wxString::Format(wxT("%i"),*devNum) + ")"); + + int numSampleRates = buildListOfSupportedSampleRates(cbSampleRate, *devNum, in_out); + if (numSampleRates) { + wxString defSampleRate = listCtrlDevices->GetItemText(index, 3); + cbSampleRate->SetValue(defSampleRate); + } + else { + cbSampleRate->SetValue("None"); + } + } +} + +//------------------------------------------------------------------------- +// OnRxInDeviceSelect() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnRxInDeviceSelect(wxListEvent& evt) +{ + OnDeviceSelect(m_cbSampleRateRxIn, + m_textCtrlRxIn, + &rxInAudioDeviceNum, + m_listCtrlRxInDevices, + evt.GetIndex(), + AUDIO_IN); +} + +//------------------------------------------------------------------------- +// OnRxOutDeviceSelect() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnRxOutDeviceSelect(wxListEvent& evt) +{ + OnDeviceSelect(m_cbSampleRateRxOut, + m_textCtrlRxOut, + &rxOutAudioDeviceNum, + m_listCtrlRxOutDevices, + evt.GetIndex(), + AUDIO_OUT); +} + +//------------------------------------------------------------------------- +// OnTxInDeviceSelect() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnTxInDeviceSelect(wxListEvent& evt) +{ + OnDeviceSelect(m_cbSampleRateTxIn, + m_textCtrlTxIn, + &txInAudioDeviceNum, + m_listCtrlTxInDevices, + evt.GetIndex(), + AUDIO_IN); +} + +//------------------------------------------------------------------------- +// OnTxOutDeviceSelect() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnTxOutDeviceSelect(wxListEvent& evt) +{ + OnDeviceSelect(m_cbSampleRateTxOut, + m_textCtrlTxOut, + &txOutAudioDeviceNum, + m_listCtrlTxOutDevices, + evt.GetIndex(), + AUDIO_OUT); +} + +//------------------------------------------------------------------------- +// plotDeviceInputForAFewSecs() +// +// opens a record device and plots the input speech for a few seconds. This is "modal" using +// synchronous portaudio functions, so the GUI will not respond until after test sample has been +// taken +//------------------------------------------------------------------------- +void AudioOptsDialog::plotDeviceInputForAFewSecs(int devNum, PlotScalar *plotScalar) { + PaStreamParameters inputParameters; + const PaDeviceInfo *deviceInfo = NULL; + PaStream *stream = NULL; + PaError err; + short in48k_stereo_short[2*TEST_BUF_SIZE]; + short in48k_short[TEST_BUF_SIZE]; + short in8k_short[TEST_BUF_SIZE]; + int numDevices, nBufs, i, j, src_error,inputChannels; + float t; + SRC_STATE *src; + FIFO *fifo; + + // a basic sanity check + numDevices = Pa_GetDeviceCount(); + if (devNum >= numDevices) + return; + if (devNum < 0) + return; + printf("devNum %d\n", devNum); + + fifo = fifo_create((int)(DT*TEST_WAVEFORM_PLOT_FS*2)); assert(fifo != NULL); + src = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(src != NULL); + + // work out how many input channels this device supports. + + deviceInfo = Pa_GetDeviceInfo(devNum); + if (deviceInfo == NULL) { + wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card "), wxT("Error"), wxOK); + return; + } + if (deviceInfo->maxInputChannels == 1) + inputChannels = 1; + else + inputChannels = 2; + + // open device + + inputParameters.device = devNum; + inputParameters.channelCount = inputChannels; + inputParameters.sampleFormat = paInt16; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + nBufs = TEST_WAVEFORM_PLOT_TIME*TEST_FS/TEST_BUF_SIZE; + printf("inputChannels: %d nBufs %d\n", inputChannels, nBufs); + + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, + TEST_FS, + TEST_BUF_SIZE, + paClipOff, + NULL, // no callback, use blocking API + NULL ); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't initialise sound device."), wxT("Error"), wxOK); + return; + } + + err = Pa_StartStream(stream); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't start sound device."), wxT("Error"), wxOK); + return; + } + + for(i=0, t=0.0; i TEST_DT) { + t -= TEST_DT; + short plotSamples[TEST_WAVEFORM_PLOT_BUF]; + if (fifo_read(fifo, plotSamples, TEST_WAVEFORM_PLOT_BUF)) + memset(plotSamples, 0, TEST_WAVEFORM_PLOT_BUF*sizeof(short)); + plotScalar->add_new_short_samples(0, plotSamples, TEST_WAVEFORM_PLOT_BUF, 32767); + plotScalar->Refresh(); + } + } + + err = Pa_StopStream(stream); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't stop sound device."), wxT("Error"), wxOK); + return; + } + Pa_CloseStream(stream); + + fifo_destroy(fifo); + src_delete(src); +} + +//------------------------------------------------------------------------- +// plotDeviceOutputForAFewSecs() +// +// opens a play device and plays a tone for a few seconds. This is "modal" using +// synchronous portaudio functions, so the GUI will not respond until after test sample has been +// taken. Also plots a pretty picture like the record versions +//------------------------------------------------------------------------- +void AudioOptsDialog::plotDeviceOutputForAFewSecs(int devNum, PlotScalar *plotScalar) { + PaStreamParameters outputParameters; + const PaDeviceInfo *deviceInfo = NULL; + PaStream *stream = NULL; + PaError err; + short out48k_stereo_short[2*TEST_BUF_SIZE]; + short out48k_short[TEST_BUF_SIZE]; + short out8k_short[TEST_BUF_SIZE]; + int numDevices, nBufs, i, j, src_error, n, outputChannels; + float t; + SRC_STATE *src; + FIFO *fifo; + + // a basic sanity check + numDevices = Pa_GetDeviceCount(); + if (devNum >= numDevices) + return; + if (devNum < 0) + return; + + fifo = fifo_create((int)(DT*TEST_WAVEFORM_PLOT_FS*2)); assert(fifo != NULL); + src = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(src != NULL); + + // work out how many output channels this device supports. + + deviceInfo = Pa_GetDeviceInfo(devNum); + if (deviceInfo == NULL) { + wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card "), wxT("Error"), wxOK); + return; + } + if (deviceInfo->maxOutputChannels == 1) + outputChannels = 1; + else + outputChannels = 2; + + printf("outputChannels: %d\n", outputChannels); + + outputParameters.device = devNum; + outputParameters.channelCount = outputChannels; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + nBufs = TEST_WAVEFORM_PLOT_TIME*TEST_FS/TEST_BUF_SIZE; + + err = Pa_OpenStream( + &stream, + NULL, + &outputParameters, + TEST_FS, + TEST_BUF_SIZE, + paClipOff, + NULL, // no callback, use blocking API + NULL ); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't initialise sound device."), wxT("Error"), wxOK); + return; + } + + err = Pa_StartStream(stream); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't start sound device."), wxT("Error"), wxOK); + return; + } + + for(i=0, t=0.0, n=0; i TEST_DT) { + t -= TEST_DT; + short plotSamples[TEST_WAVEFORM_PLOT_BUF]; + if (fifo_read(fifo, plotSamples, TEST_WAVEFORM_PLOT_BUF)) + memset(plotSamples, 0, TEST_WAVEFORM_PLOT_BUF*sizeof(short)); + plotScalar->add_new_short_samples(0, plotSamples, TEST_WAVEFORM_PLOT_BUF, 32767); + plotScalar->Refresh(); + } + } + + err = Pa_StopStream(stream); + if (err != paNoError) { + wxMessageBox(wxT("Couldn't stop sound device."), wxT("Error"), wxOK); + return; + } + Pa_CloseStream(stream); + + fifo_destroy(fifo); + src_delete(src); +} + +//------------------------------------------------------------------------- +// OnRxInTest() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnRxInTest(wxCommandEvent& event) +{ + plotDeviceInputForAFewSecs(rxInAudioDeviceNum, m_plotScalarRxIn); +} + +//------------------------------------------------------------------------- +// OnRxOutTest() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnRxOutTest(wxCommandEvent& event) +{ + plotDeviceOutputForAFewSecs(rxOutAudioDeviceNum, m_plotScalarRxOut); +} + +//------------------------------------------------------------------------- +// OnTxInTest() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnTxInTest(wxCommandEvent& event) +{ + plotDeviceInputForAFewSecs(txInAudioDeviceNum, m_plotScalarTxIn); +} + +//------------------------------------------------------------------------- +// OnTxOutTest() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnTxOutTest(wxCommandEvent& event) +{ + plotDeviceOutputForAFewSecs(txOutAudioDeviceNum, m_plotScalarTxOut); +} + +//------------------------------------------------------------------------- +// OnRefreshClick() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnRefreshClick(wxCommandEvent& event) +{ + // restart portaudio, to re-sample available devices + + Pa_Terminate(); + Pa_Init(); + + m_notebook1->SetSelection(0); + showAPIInfo(); + populateParams(m_RxInDevices); + populateParams(m_RxOutDevices); + populateParams(m_TxInDevices); + populateParams(m_TxOutDevices); + + // some devices may have dissapeared, so possibily change sound + // card config + + ExchangeData(EXCHANGE_DATA_IN); +} + +//------------------------------------------------------------------------- +// OnApplyAudioParameters() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnApplyAudioParameters(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT); + if(m_isPaInitialized) + { + if((pa_err = Pa_Terminate()) == paNoError) + { + m_isPaInitialized = false; + } + else + { + wxMessageBox(wxT("Port Audio failed to Terminate"), wxT("Pa_Terminate"), wxOK); + } + } +} + +//------------------------------------------------------------------------- +// OnCancelAudioParameters() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnCancelAudioParameters(wxCommandEvent& event) +{ + if(m_isPaInitialized) + { + if((pa_err = Pa_Terminate()) == paNoError) + { + m_isPaInitialized = false; + } + else + { + wxMessageBox(wxT("Port Audio failed to Terminate"), wxT("Pa_Terminate"), wxOK); + } + } + EndModal(wxCANCEL); +} + +//------------------------------------------------------------------------- +// OnOkAudioParameters() +//------------------------------------------------------------------------- +void AudioOptsDialog::OnOkAudioParameters(wxCommandEvent& event) +{ + int status = ExchangeData(EXCHANGE_DATA_OUT); + + // We only accept OK if config sucessful + + printf("status: %d m_isPaInitialized: %d\n", status, m_isPaInitialized); + if (status == 0) { + if(m_isPaInitialized) + { + if((pa_err = Pa_Terminate()) == paNoError) + { + printf("terminated OK\n"); + m_isPaInitialized = false; + } + else + { + wxMessageBox(wxT("Port Audio failed to Terminate"), wxT("Pa_Terminate"), wxOK); + } + } + EndModal(wxOK); + } + +} diff --git a/freedv/tags/1.2.2/src/dlg_audiooptions.h b/freedv/tags/1.2.2/src/dlg_audiooptions.h new file mode 100644 index 00000000..5aa6741d --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_audiooptions.h @@ -0,0 +1,176 @@ +//========================================================================= +// Name: AudioInfoDisplay.h +// Purpose: Declares simple wxWidgets application with GUI +// created using wxFormBuilder. +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================= +#ifndef __AudioOptsDialog__ +#define __AudioOptsDialog__ + +#include "fdmdv2_main.h" + +#define ID_AUDIO_OPTIONS 1000 +#define AUDIO_IN 0 +#define AUDIO_OUT 1 + +#include "portaudio.h" +#ifdef WIN32 +#if PA_USE_ASIO +#include "pa_asio.h" +#endif +#endif +#include "codec2_fifo.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// AudioInfoDisplay +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class AudioInfoDisplay +{ + public: + wxListCtrl* m_listDevices; + int direction; + wxTextCtrl* m_textDevice; + wxComboBox* m_cbSampleRate; +}; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// class AudioOptsDialog +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class AudioOptsDialog : public wxDialog +{ + private: + + protected: + PaError pa_err; + bool m_isPaInitialized; + + int rxInAudioDeviceNum; + int rxOutAudioDeviceNum; + int txInAudioDeviceNum; + int txOutAudioDeviceNum; + + void buildTestControls(PlotScalar **plotScalar, wxButton **btnTest, + wxPanel *parentPanel, wxBoxSizer *bSizer, wxString buttonLabel); + void plotDeviceInputForAFewSecs(int devNum, PlotScalar *plotScalar); + void plotDeviceOutputForAFewSecs(int devNum, PlotScalar *plotScalar); + + int buildListOfSupportedSampleRates(wxComboBox *cbSampleRate, int devNum, int in_out); + void populateParams(AudioInfoDisplay); + void showAPIInfo(); + int setTextCtrlIfDevNumValid(wxTextCtrl *textCtrl, wxListCtrl *listCtrl, int devNum); + void Pa_Init(void); + void OnDeviceSelect(wxComboBox *cbSampleRate, + wxTextCtrl *textCtrl, + int *devNum, + wxListCtrl *listCtrlDevices, + int index, + int in_out); + + AudioInfoDisplay m_RxInDevices; + AudioInfoDisplay m_RxOutDevices; + AudioInfoDisplay m_TxInDevices; + AudioInfoDisplay m_TxOutDevices; + wxPanel* m_panel1; + wxNotebook* m_notebook1; + + wxPanel* m_panelRx; + + wxListCtrl* m_listCtrlRxInDevices; + wxStaticText* m_staticText51; + wxTextCtrl* m_textCtrlRxIn; + wxStaticText* m_staticText6; + wxComboBox* m_cbSampleRateRxIn; + + wxButton* m_btnRxInTest; + PlotScalar* m_plotScalarRxIn; + + wxListCtrl* m_listCtrlRxOutDevices; + wxStaticText* m_staticText9; + wxTextCtrl* m_textCtrlRxOut; + wxStaticText* m_staticText10; + wxComboBox* m_cbSampleRateRxOut; + + wxButton* m_btnRxOutTest; + PlotScalar* m_plotScalarRxOut; + + wxPanel* m_panelTx; + + wxListCtrl* m_listCtrlTxInDevices; + wxStaticText* m_staticText12; + wxTextCtrl* m_textCtrlTxIn; + wxStaticText* m_staticText11; + wxComboBox* m_cbSampleRateTxIn; + + wxButton* m_btnTxInTest; + PlotScalar* m_plotScalarTxIn; + + wxListCtrl* m_listCtrlTxOutDevices; + wxStaticText* m_staticText81; + wxTextCtrl* m_textCtrlTxOut; + wxStaticText* m_staticText71; + wxComboBox* m_cbSampleRateTxOut; + + wxButton* m_btnTxOutTest; + PlotScalar* m_plotScalarTxOut; + + wxPanel* m_panelAPI; + + wxStaticText* m_staticText7; + wxStaticText* m_textStringVer; + wxStaticText* m_staticText8; + wxStaticText* m_textIntVer; + wxStaticText* m_staticText5; + wxStaticText* m_textCDevCount; + wxStaticText* m_staticText4; + wxStaticText* m_textAPICount; + wxButton* m_btnRefresh; + wxStdDialogButtonSizer* m_sdbSizer1; + wxButton* m_sdbSizer1OK; + wxButton* m_sdbSizer1Apply; + wxButton* m_sdbSizer1Cancel; + + // Virtual event handlers, overide them in your derived class + //virtual void OnActivateApp( wxActivateEvent& event ) { event.Skip(); } +// virtual void OnCloseFrame( wxCloseEvent& event ) { event.Skip(); } + + void OnRxInDeviceSelect( wxListEvent& event ); + + void OnRxInTest( wxCommandEvent& event ); + void OnRxOutTest( wxCommandEvent& event ); + void OnTxInTest( wxCommandEvent& event ); + void OnTxOutTest( wxCommandEvent& event ); + + void OnRxOutDeviceSelect( wxListEvent& event ); + void OnTxInDeviceSelect( wxListEvent& event ); + void OnTxOutDeviceSelect( wxListEvent& event ); + void OnRefreshClick( wxCommandEvent& event ); + void OnApplyAudioParameters( wxCommandEvent& event ); + void OnCancelAudioParameters( wxCommandEvent& event ); + void OnOkAudioParameters( wxCommandEvent& event ); + // Virtual event handlers, overide them in your derived class + void OnClose( wxCloseEvent& event ) { event.Skip(); } + void OnHibernate( wxActivateEvent& event ) { event.Skip(); } + void OnIconize( wxIconizeEvent& event ) { event.Skip(); } + void OnInitDialog( wxInitDialogEvent& event ); + + public: + + AudioOptsDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Audio Config"), const wxPoint& pos = wxPoint(1,1), const wxSize& size = wxSize( 800, 650 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~AudioOptsDialog(); + int ExchangeData(int inout); +}; +#endif //__AudioOptsDialog__ diff --git a/freedv/tags/1.2.2/src/dlg_filter.cpp b/freedv/tags/1.2.2/src/dlg_filter.cpp new file mode 100644 index 00000000..5a5294a9 --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_filter.cpp @@ -0,0 +1,785 @@ +//========================================================================== +// Name: dlg_filter.cpp +// Purpose: Dialog for controlling Codec audio filtering +// Date: Nov 25 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include "dlg_filter.h" + +#define SLIDER_MAX 100 +#define SLIDER_LENGTH 100 + +#define FILTER_MIN_MAG_DB -20.0 +#define FILTER_MAX_MAG_DB 20.0 + +#define MAX_FREQ_BASS 600.00 +#define MAX_FREQ_TREBLE 3900.00 +#define MAX_FREQ_DEF 3000.00 + +#define MIN_GAIN -20 +#define MAX_GAIN 20 + +#define MAX_LOG10_Q 1.0 +#define MIN_LOG10_Q -1.0 + +// DFT parameters + +#define IMP_AMP 2000.0 // amplitude of impulse +#define NIMP 50 // number of samples in impulse response +#define F_STEP_DFT 10.0 // frequency steps to sample spectrum +#define F_MAG_N (int)(MAX_F_HZ/F_STEP_DFT) // number of frequency steps + +extern struct freedv *g_pfreedv; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class FilterDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +FilterDlg::FilterDlg(wxWindow* parent, bool running, bool *newMicInFilter, bool *newSpkOutFilter, + wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +{ + m_running = running; + m_newMicInFilter = newMicInFilter; + m_newSpkOutFilter = newSpkOutFilter; + + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + wxBoxSizer* bSizer30; + bSizer30 = new wxBoxSizer(wxVERTICAL); + + // LPC Post Filter -------------------------------------------------------- + + wxStaticBoxSizer* lpcpfs = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("LPC Post Filter")), wxHORIZONTAL); + + wxBoxSizer* left = new wxBoxSizer(wxVERTICAL); + + m_codec2LPCPostFilterEnable = new wxCheckBox(this, wxID_ANY, _("Enable"), wxDefaultPosition,wxDefaultSize, wxCHK_2STATE); + left->Add(m_codec2LPCPostFilterEnable); + + m_codec2LPCPostFilterBassBoost = new wxCheckBox(this, wxID_ANY, _("0-1 kHz 3dB Boost"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + left->Add(m_codec2LPCPostFilterBassBoost); + lpcpfs->Add(left, 0, wxALL, 5); + + newLPCPFControl(&m_codec2LPCPostFilterBeta, &m_staticTextBeta, lpcpfs, "Beta"); + newLPCPFControl(&m_codec2LPCPostFilterGamma, &m_staticTextGamma, lpcpfs, "Gamma"); + + m_LPCPostFilterDefault = new wxButton(this, wxID_ANY, wxT("Default")); + lpcpfs->Add(m_LPCPostFilterDefault, 0, wxALL|wxALIGN_CENTRE_HORIZONTAL|wxALIGN_CENTRE_VERTICAL, 5); + + bSizer30->Add(lpcpfs, 0, wxALL, 0); + + // Speex pre-processor -------------------------------------------------- + + wxStaticBoxSizer* sbSizer_speexpp; + wxStaticBox *sb_speexpp = new wxStaticBox(this, wxID_ANY, _("Speex Mic Audio Pre-Processor")); + sbSizer_speexpp = new wxStaticBoxSizer(sb_speexpp, wxVERTICAL); + + m_ckboxSpeexpp = new wxCheckBox(this, wxID_ANY, _("Enable"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sb_speexpp->SetToolTip(_("Enable noise supression, dereverberation, AGC of mic signal")); + sbSizer_speexpp->Add(m_ckboxSpeexpp, wxALIGN_LEFT, 2); + + bSizer30->Add(sbSizer_speexpp, 0, wxALL, 0); + + // EQ Filters ----------------------------------------------------------- + + wxStaticBoxSizer* eqMicInSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Mic In Equaliser")), wxVERTICAL); + wxBoxSizer* eqMicInSizer1 = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* eqMicInSizer2 = new wxBoxSizer(wxHORIZONTAL); + + m_MicInBass = newEQ(eqMicInSizer1, "Bass" , MAX_FREQ_BASS, disableQ); + m_MicInTreble = newEQ(eqMicInSizer1, "Treble", MAX_FREQ_TREBLE, disableQ); + eqMicInSizer->Add(eqMicInSizer1); + + m_MicInEnable = new wxCheckBox(this, wxID_ANY, _("Enable"), wxDefaultPosition,wxDefaultSize, wxCHK_2STATE); + eqMicInSizer2->Add(m_MicInEnable,0,wxALIGN_CENTRE_VERTICAL|wxRIGHT,10); + m_MicInMid = newEQ(eqMicInSizer2, "Mid" , MAX_FREQ_DEF, enableQ); + m_MicInDefault = new wxButton(this, wxID_ANY, wxT("Default")); + eqMicInSizer2->Add(m_MicInDefault,0,wxALIGN_CENTRE_VERTICAL|wxLEFT,20); + eqMicInSizer->Add(eqMicInSizer2); + + wxStaticBoxSizer* eqSpkOutSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Speaker Out Equaliser")), wxVERTICAL); + wxBoxSizer* eqSpkOutSizer1 = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* eqSpkOutSizer2 = new wxBoxSizer(wxHORIZONTAL); + + m_SpkOutBass = newEQ(eqSpkOutSizer1, "Bass" , MAX_FREQ_BASS, disableQ); + m_SpkOutTreble = newEQ(eqSpkOutSizer1, "Treble", MAX_FREQ_TREBLE, disableQ); + eqSpkOutSizer->Add(eqSpkOutSizer1); + + m_SpkOutEnable = new wxCheckBox(this, wxID_ANY, _("Enable"), wxDefaultPosition,wxDefaultSize, wxCHK_2STATE); + eqSpkOutSizer2->Add(m_SpkOutEnable,0,wxALIGN_CENTRE_VERTICAL|wxRIGHT,10); + m_SpkOutMid = newEQ(eqSpkOutSizer2, "Mid" , MAX_FREQ_DEF, enableQ); + m_SpkOutDefault = new wxButton(this, wxID_ANY, wxT("Default")); + eqSpkOutSizer2->Add(m_SpkOutDefault,0,wxALIGN_CENTRE_VERTICAL|wxLEFT,20); + eqSpkOutSizer->Add(eqSpkOutSizer2); + + bSizer30->Add(eqMicInSizer, 0, wxALL, 0); + bSizer30->Add(eqSpkOutSizer, 0, wxALL, 0); + + // Storgage for spectrum magnitude plots ------------------------------------ + + m_MicInMagdB = new float[F_MAG_N]; + for(int i=0; iSetFont(wxFont(8, 70, 90, 90, false, wxEmptyString)); + + bSizer30->Add(m_auiNotebook, 0, wxEXPAND|wxALL, 3); + + m_MicInFreqRespPlot = new PlotSpectrum((wxFrame*) m_auiNotebook, m_MicInMagdB, F_MAG_N, FILTER_MIN_MAG_DB, FILTER_MAX_MAG_DB); + m_auiNotebook->AddPage(m_MicInFreqRespPlot, _("Microphone In Equaliser")); + + m_SpkOutFreqRespPlot = new PlotSpectrum((wxFrame*)m_auiNotebook, m_SpkOutMagdB, F_MAG_N, FILTER_MIN_MAG_DB, FILTER_MAX_MAG_DB); + m_auiNotebook->AddPage(m_SpkOutFreqRespPlot, _("Speaker Out Equaliser")); + + // OK - Cancel buttons at the bottom -------------------------- + + wxBoxSizer* bSizer31 = new wxBoxSizer(wxHORIZONTAL); + + m_sdbSizer5OK = new wxButton(this, wxID_OK); + bSizer31->Add(m_sdbSizer5OK, 0, wxALL, 2); + + m_sdbSizer5Cancel = new wxButton(this, wxID_CANCEL); + bSizer31->Add(m_sdbSizer5Cancel, 0, wxALL, 2); + + bSizer30->Add(bSizer31, 0, wxALIGN_RIGHT|wxALL, 0); + + this->SetSizer(bSizer30); + this->Layout(); + + this->Centre(wxBOTH); + + // Connect Events ------------------------------------------------------- + + this->Connect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(FilterDlg::OnInitDialog)); + + m_codec2LPCPostFilterEnable->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnEnable), NULL, this); + m_codec2LPCPostFilterBassBoost->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnBassBoost), NULL, this); + m_codec2LPCPostFilterBeta->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnBetaScroll), NULL, this); + m_codec2LPCPostFilterGamma->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnGammaScroll), NULL, this); + m_LPCPostFilterDefault->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnLPCPostFilterDefault), NULL, this); + + m_ckboxSpeexpp->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnSpeexppEnable), NULL, this); + + m_MicInBass.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInBassFreqScroll), NULL, this); + m_MicInBass.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInBassGainScroll), NULL, this); + m_MicInTreble.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInTrebleFreqScroll), NULL, this); + m_MicInTreble.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInTrebleGainScroll), NULL, this); + m_MicInMid.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidFreqScroll), NULL, this); + m_MicInMid.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidGainScroll), NULL, this); + m_MicInMid.sliderQ->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidQScroll), NULL, this); + m_MicInEnable->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnMicInEnable), NULL, this); + m_MicInDefault->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnMicInDefault), NULL, this); + + m_SpkOutBass.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutBassFreqScroll), NULL, this); + m_SpkOutBass.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutBassGainScroll), NULL, this); + m_SpkOutTreble.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutTrebleFreqScroll), NULL, this); + m_SpkOutTreble.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutTrebleGainScroll), NULL, this); + m_SpkOutMid.sliderFreq->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidFreqScroll), NULL, this); + m_SpkOutMid.sliderGain->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidGainScroll), NULL, this); + m_SpkOutMid.sliderQ->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidQScroll), NULL, this); + m_SpkOutEnable->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnSpkOutEnable), NULL, this); + m_SpkOutDefault->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnSpkOutDefault), NULL, this); + + m_sdbSizer5Cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnCancel), NULL, this); + m_sdbSizer5OK->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnOK), NULL, this); + +} + +//------------------------------------------------------------------------- +// ~FilterDlg() +//------------------------------------------------------------------------- +FilterDlg::~FilterDlg() +{ + delete m_MicInMagdB; + delete m_SpkOutMagdB; + + // Disconnect Events + + this->Disconnect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(FilterDlg::OnInitDialog)); + + m_codec2LPCPostFilterEnable->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnEnable), NULL, this); + m_codec2LPCPostFilterBassBoost->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnBassBoost), NULL, this); + m_codec2LPCPostFilterBeta->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnBetaScroll), NULL, this); + m_codec2LPCPostFilterGamma->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnGammaScroll), NULL, this); + m_LPCPostFilterDefault->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnLPCPostFilterDefault), NULL, this); + + m_MicInBass.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInBassFreqScroll), NULL, this); + m_MicInBass.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInBassGainScroll), NULL, this); + m_MicInTreble.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInTrebleFreqScroll), NULL, this); + m_MicInTreble.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInTrebleGainScroll), NULL, this); + m_MicInMid.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidFreqScroll), NULL, this); + m_MicInMid.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidGainScroll), NULL, this); + m_MicInMid.sliderQ->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnMicInMidQScroll), NULL, this); + m_MicInEnable->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnMicInEnable), NULL, this); + m_MicInDefault->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnMicInDefault), NULL, this); + + m_SpkOutBass.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutBassFreqScroll), NULL, this); + m_SpkOutBass.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutBassGainScroll), NULL, this); + m_SpkOutTreble.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutTrebleFreqScroll), NULL, this); + m_SpkOutTreble.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutTrebleGainScroll), NULL, this); + m_SpkOutMid.sliderFreq->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidFreqScroll), NULL, this); + m_SpkOutMid.sliderGain->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidGainScroll), NULL, this); + m_SpkOutMid.sliderQ->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(FilterDlg::OnSpkOutMidQScroll), NULL, this); + m_SpkOutEnable->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(FilterDlg::OnSpkOutEnable), NULL, this); + m_SpkOutDefault->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnSpkOutDefault), NULL, this); + + m_sdbSizer5Cancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnCancel), NULL, this); + m_sdbSizer5OK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FilterDlg::OnOK), NULL, this); +} + +void FilterDlg::newLPCPFControl(wxSlider **slider, wxStaticText **stValue, wxSizer *s, wxString controlName) +{ + wxBoxSizer *bs = new wxBoxSizer(wxHORIZONTAL); + + wxStaticText* st = new wxStaticText(this, wxID_ANY, controlName, wxDefaultPosition, wxSize(70,-1), wxALIGN_RIGHT); + bs->Add(st, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 2); + + *slider = new wxSlider(this, wxID_ANY, 0, 0, SLIDER_MAX, wxDefaultPosition, wxSize(SLIDER_LENGTH,wxDefaultCoord)); + bs->Add(*slider, 1, wxALIGN_CENTER_VERTICAL|wxALL, 2); + + *stValue = new wxStaticText(this, wxID_ANY, wxT("0.0"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + bs->Add(*stValue, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 2); + + s->Add(bs, 0); +} + +void FilterDlg::newEQControl(wxSlider** slider, wxStaticText** value, wxStaticBoxSizer *bs, wxString controlName) +{ + wxStaticText* label = new wxStaticText(this, wxID_ANY, controlName, wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + bs->Add(label, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 0); + + *slider = new wxSlider(this, wxID_ANY, 0, 0, SLIDER_MAX, wxDefaultPosition, wxSize(SLIDER_LENGTH,wxDefaultCoord)); + bs->Add(*slider, 1, wxALIGN_CENTER_VERTICAL|wxALL, 0); + + *value = new wxStaticText(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(40,-1), wxALIGN_LEFT); + bs->Add(*value, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxRIGHT, 5); +} + +EQ FilterDlg::newEQ(wxSizer *bs, wxString eqName, float maxFreqHz, bool enableQ) +{ + EQ eq; + + wxStaticBoxSizer *bsEQ = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, eqName), wxHORIZONTAL); + + newEQControl(&eq.sliderFreq, &eq.valueFreq, bsEQ, "Freq"); + eq.maxFreqHz = maxFreqHz; + eq.sliderFreqId = eq.sliderFreq->GetId(); + + newEQControl(&eq.sliderGain, &eq.valueGain, bsEQ, "Gain"); + if (enableQ) + newEQControl(&eq.sliderQ, &eq.valueQ, bsEQ, "Q"); + else + eq.sliderQ = NULL; + + bs->Add(bsEQ); + + return eq; +} + +//------------------------------------------------------------------------- +// ExchangeData() +//------------------------------------------------------------------------- +void FilterDlg::ExchangeData(int inout, bool storePersistent) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + if(inout == EXCHANGE_DATA_IN) + { + // LPC Post filter + + m_codec2LPCPostFilterEnable->SetValue(wxGetApp().m_codec2LPCPostFilterEnable); + m_codec2LPCPostFilterBassBoost->SetValue(wxGetApp().m_codec2LPCPostFilterBassBoost); + m_beta = wxGetApp().m_codec2LPCPostFilterBeta; setBeta(); + m_gamma = wxGetApp().m_codec2LPCPostFilterGamma; setGamma(); + + // Speex Pre-Processor + + m_ckboxSpeexpp->SetValue(wxGetApp().m_speexpp_enable); + + // Mic In Equaliser + + m_MicInBass.freqHz = wxGetApp().m_MicInBassFreqHz; + m_MicInBass.freqHz = limit(m_MicInBass.freqHz, 1.0, MAX_FREQ_BASS); + setFreq(&m_MicInBass); + m_MicInBass.gaindB = wxGetApp().m_MicInBassGaindB; + m_MicInBass.gaindB = limit(m_MicInBass.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_MicInBass); + + m_MicInTreble.freqHz = wxGetApp().m_MicInTrebleFreqHz; + m_MicInTreble.freqHz = limit(m_MicInTreble.freqHz, 1.0, MAX_FREQ_TREBLE); + setFreq(&m_MicInTreble); + m_MicInTreble.gaindB = wxGetApp().m_MicInTrebleGaindB; + m_MicInTreble.gaindB = limit(m_MicInTreble.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_MicInTreble); + + m_MicInMid.freqHz = wxGetApp().m_MicInMidFreqHz; + m_MicInMid.freqHz = limit(m_MicInMid.freqHz, 1.0, MAX_FREQ_TREBLE); + setFreq(&m_MicInMid); + m_MicInMid.gaindB = wxGetApp().m_MicInMidGaindB; + m_MicInMid.gaindB = limit(m_MicInMid.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_MicInMid); + m_MicInMid.Q = wxGetApp().m_MicInMidQ; + m_MicInMid.Q = limit(m_MicInMid.Q, pow(10.0,MIN_LOG10_Q), pow(10.0, MAX_LOG10_Q)); + setQ(&m_MicInMid); + + m_MicInEnable->SetValue(wxGetApp().m_MicInEQEnable); + + plotMicInFilterSpectrum(); + + // Spk Out Equaliser + + m_SpkOutBass.freqHz = wxGetApp().m_SpkOutBassFreqHz; + m_SpkOutBass.freqHz = limit(m_SpkOutBass.freqHz, 1.0, MAX_FREQ_BASS); + setFreq(&m_SpkOutBass); + m_SpkOutBass.gaindB = wxGetApp().m_SpkOutBassGaindB; + m_SpkOutBass.gaindB = limit(m_SpkOutBass.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_SpkOutBass); + + m_SpkOutTreble.freqHz = wxGetApp().m_SpkOutTrebleFreqHz; + m_SpkOutTreble.freqHz = limit(m_SpkOutTreble.freqHz, 1.0, MAX_FREQ_TREBLE); + setFreq(&m_SpkOutTreble); + m_SpkOutTreble.gaindB = wxGetApp().m_SpkOutTrebleGaindB; + m_SpkOutTreble.gaindB = limit(m_SpkOutTreble.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_SpkOutTreble); + + m_SpkOutMid.freqHz = wxGetApp().m_SpkOutMidFreqHz; + m_SpkOutMid.freqHz = limit(m_SpkOutMid.freqHz, 1.0, MAX_FREQ_TREBLE); + setFreq(&m_SpkOutMid); + m_SpkOutMid.gaindB = wxGetApp().m_SpkOutMidGaindB; + m_SpkOutMid.gaindB = limit(m_SpkOutMid.gaindB, MIN_GAIN, MAX_GAIN); + setGain(&m_SpkOutMid); + m_SpkOutMid.Q = wxGetApp().m_SpkOutMidQ; + m_SpkOutMid.Q = limit(m_SpkOutMid.Q, pow(10.0,MIN_LOG10_Q), pow(10.0, MAX_LOG10_Q)); + setQ(&m_SpkOutMid); + + m_SpkOutEnable->SetValue(wxGetApp().m_SpkOutEQEnable); + + plotSpkOutFilterSpectrum(); + } + if(inout == EXCHANGE_DATA_OUT) + { + // LPC Post filter + + wxGetApp().m_codec2LPCPostFilterEnable = m_codec2LPCPostFilterEnable->GetValue(); + wxGetApp().m_codec2LPCPostFilterBassBoost = m_codec2LPCPostFilterBassBoost->GetValue(); + wxGetApp().m_codec2LPCPostFilterBeta = m_beta; + wxGetApp().m_codec2LPCPostFilterGamma = m_gamma; + + // Speex Pre-Processor + + wxGetApp().m_speexpp_enable = m_ckboxSpeexpp->GetValue(); + + // Mic In Equaliser + + wxGetApp().m_MicInBassFreqHz = m_MicInBass.freqHz; + wxGetApp().m_MicInBassGaindB = m_MicInBass.gaindB; + + wxGetApp().m_MicInTrebleFreqHz = m_MicInTreble.freqHz; + wxGetApp().m_MicInTrebleGaindB = m_MicInTreble.gaindB; + + wxGetApp().m_MicInMidFreqHz = m_MicInMid.freqHz; + wxGetApp().m_MicInMidGaindB = m_MicInMid.gaindB; + wxGetApp().m_MicInMidQ = m_MicInMid.Q; + + // Spk Out Equaliser + + wxGetApp().m_SpkOutBassFreqHz = m_SpkOutBass.freqHz; + wxGetApp().m_SpkOutBassGaindB = m_SpkOutBass.gaindB; + + wxGetApp().m_SpkOutTrebleFreqHz = m_SpkOutTreble.freqHz; + wxGetApp().m_SpkOutTrebleGaindB = m_SpkOutTreble.gaindB; + + wxGetApp().m_SpkOutMidFreqHz = m_SpkOutMid.freqHz; + wxGetApp().m_SpkOutMidGaindB = m_SpkOutMid.gaindB; + wxGetApp().m_SpkOutMidQ = m_SpkOutMid.Q; + + if (storePersistent) { + pConfig->Write(wxT("/Filter/codec2LPCPostFilterEnable"), wxGetApp().m_codec2LPCPostFilterEnable); + pConfig->Write(wxT("/Filter/codec2LPCPostFilterBassBoost"), wxGetApp().m_codec2LPCPostFilterBassBoost); + pConfig->Write(wxT("/Filter/codec2LPCPostFilterBeta"), (int)(m_beta*100.0)); + pConfig->Write(wxT("/Filter/codec2LPCPostFilterGamma"), (int)(m_gamma*100.0)); + + pConfig->Write(wxT("/Filter/speexpp_enable"), wxGetApp().m_speexpp_enable); + + pConfig->Write(wxT("/Filter/MicInBassFreqHz"), (int)m_MicInBass.freqHz); + pConfig->Write(wxT("/Filter/MicInBassGaindB"), (int)(10.0*m_MicInBass.gaindB)); + pConfig->Write(wxT("/Filter/MicInTrebleFreqHz"), (int)m_MicInTreble.freqHz); + pConfig->Write(wxT("/Filter/MicInTrebleGaindB"), (int)(10.0*m_MicInTreble.gaindB)); + pConfig->Write(wxT("/Filter/MicInMidFreqHz"), (int)m_MicInMid.freqHz); + pConfig->Write(wxT("/Filter/MicInMidGaindB"), (int)(10.0*m_MicInMid.gaindB)); + pConfig->Write(wxT("/Filter/MicInMidQ"), (int)(100.0*m_MicInMid.Q)); + + pConfig->Write(wxT("/Filter/SpkOutBassFreqHz"), (int)m_SpkOutBass.freqHz); + pConfig->Write(wxT("/Filter/SpkOutBassGaindB"), (int)(10.0*m_SpkOutBass.gaindB)); + pConfig->Write(wxT("/Filter/SpkOutTrebleFreqHz"), (int)m_SpkOutTreble.freqHz); + pConfig->Write(wxT("/Filter/SpkOutTrebleGaindB"), (int)(10.0*m_SpkOutTreble.gaindB)); + pConfig->Write(wxT("/Filter/SpkOutMidQ"), (int)(100.0*m_SpkOutMid.Q)); + pConfig->Write(wxT("/Filter/SpkOutMidFreqHz"), (int)m_SpkOutMid.freqHz); + pConfig->Write(wxT("/Filter/SpkOutMidGaindB"), (int)(10.0*m_SpkOutMid.gaindB)); + + pConfig->Flush(); + } + } + delete wxConfigBase::Set((wxConfigBase *) NULL); +} + +float FilterDlg::limit(float value, float min, float max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +//------------------------------------------------------------------------- +// OnCancel() +//------------------------------------------------------------------------- +void FilterDlg::OnCancel(wxCommandEvent& event) +{ + this->EndModal(wxID_CANCEL); +} + +//------------------------------------------------------------------------- +// OnDefault() +//------------------------------------------------------------------------- + +void FilterDlg::OnLPCPostFilterDefault(wxCommandEvent& event) +{ + m_beta = CODEC2_LPC_PF_BETA; setBeta(); + m_gamma = CODEC2_LPC_PF_GAMMA; setGamma(); + m_codec2LPCPostFilterEnable->SetValue(true); + m_codec2LPCPostFilterBassBoost->SetValue(true); +} + +void FilterDlg::OnMicInDefault(wxCommandEvent& event) +{ + m_MicInBass.freqHz = 100.0; + m_MicInBass.gaindB = 0.0; + setFreq(&m_MicInBass); setGain(&m_MicInBass); + + m_MicInTreble.freqHz = 3000.0; + m_MicInTreble.gaindB = 0.0; + setFreq(&m_MicInTreble); setGain(&m_MicInTreble); + + m_MicInMid.freqHz = 1500.0; + m_MicInMid.gaindB = 0.0; + m_MicInMid.Q = 1.0; + setFreq(&m_MicInMid); setGain(&m_MicInMid); setQ(&m_MicInMid); + + plotMicInFilterSpectrum(); +} + +void FilterDlg::OnSpkOutDefault(wxCommandEvent& event) +{ + m_SpkOutBass.freqHz = 100.0; + m_SpkOutBass.gaindB = 0.0; + setFreq(&m_SpkOutBass); setGain(&m_SpkOutBass); + + m_SpkOutTreble.freqHz = 3000.0; + m_SpkOutTreble.gaindB = 0.0; + setFreq(&m_SpkOutTreble); setGain(&m_SpkOutTreble); + + m_SpkOutMid.freqHz = 1500.0; + m_SpkOutMid.gaindB = 0.0; + m_SpkOutMid.Q = 1.0; + setFreq(&m_SpkOutMid); setGain(&m_SpkOutMid); setQ(&m_SpkOutMid); + + plotSpkOutFilterSpectrum(); +} + +//------------------------------------------------------------------------- +// OnOK() +//------------------------------------------------------------------------- +void FilterDlg::OnOK(wxCommandEvent& event) +{ + //printf("FilterDlg::OnOK\n"); + ExchangeData(EXCHANGE_DATA_OUT, true); + this->EndModal(wxID_OK); +} + +//------------------------------------------------------------------------- +// OnClose() +//------------------------------------------------------------------------- +void FilterDlg::OnClose(wxCloseEvent& event) +{ + this->EndModal(wxID_OK); +} + +//------------------------------------------------------------------------- +// OnInitDialog() +//------------------------------------------------------------------------- +void FilterDlg::OnInitDialog(wxInitDialogEvent& event) +{ + //printf("FilterDlg::OnInitDialog\n"); + ExchangeData(EXCHANGE_DATA_IN, false); + //printf("m_beta: %f\n", m_beta); +} + +void FilterDlg::setBeta(void) { + wxString buf; + buf.Printf(wxT("%3.2f"), m_beta); + m_staticTextBeta->SetLabel(buf); + int slider = (int)(m_beta*SLIDER_MAX + 0.5); + m_codec2LPCPostFilterBeta->SetValue(slider); +} + +void FilterDlg::setCodec2(void) { + if (m_running) { + codec2_set_lpc_post_filter(freedv_get_codec2(g_pfreedv), + m_codec2LPCPostFilterEnable->GetValue(), + m_codec2LPCPostFilterBassBoost->GetValue(), + m_beta, m_gamma); + } +} + +void FilterDlg::setGamma(void) { + wxString buf; + buf.Printf(wxT("%3.2f"), m_gamma); + m_staticTextGamma->SetLabel(buf); + int slider = (int)(m_gamma*SLIDER_MAX + 0.5); + m_codec2LPCPostFilterGamma->SetValue(slider); +} + +void FilterDlg::OnEnable(wxScrollEvent& event) { + setCodec2(); +} + +void FilterDlg::OnBassBoost(wxScrollEvent& event) { + setCodec2(); +} + +void FilterDlg::OnBetaScroll(wxScrollEvent& event) { + m_beta = (float)m_codec2LPCPostFilterBeta->GetValue()/SLIDER_MAX; + setBeta(); + setCodec2(); +} + +void FilterDlg::OnGammaScroll(wxScrollEvent& event) { + m_gamma = (float)m_codec2LPCPostFilterGamma->GetValue()/SLIDER_MAX; + setGamma(); + setCodec2(); +} + +// immediately change enable flags rather using ExchangeData() so we can switch on and off at run time + +void FilterDlg::OnSpeexppEnable(wxScrollEvent& event) { + wxGetApp().m_speexpp_enable = m_ckboxSpeexpp->GetValue(); +} + +void FilterDlg::OnMicInEnable(wxScrollEvent& event) { + wxGetApp().m_MicInEQEnable = m_MicInEnable->GetValue(); +} + +void FilterDlg::OnSpkOutEnable(wxScrollEvent& event) { + wxGetApp().m_SpkOutEQEnable = m_SpkOutEnable->GetValue(); + //printf("wxGetApp().m_SpkOutEQEnable: %d\n", wxGetApp().m_SpkOutEQEnable); +} + +void FilterDlg::setFreq(EQ *eq) +{ + wxString buf; + buf.Printf(wxT("%3.0f"), eq->freqHz); + eq->valueFreq->SetLabel(buf); + int slider = (int)((eq->freqHz/eq->maxFreqHz)*SLIDER_MAX + 0.5); + eq->sliderFreq->SetValue(slider); +} + +void FilterDlg::sliderToFreq(EQ *eq, bool micIn) +{ + eq->freqHz = ((float)eq->sliderFreq->GetValue()/SLIDER_MAX)*eq->maxFreqHz; + if (eq->freqHz < 1.0) eq->freqHz = 1.0; // sox doesn't like 0 Hz; + setFreq(eq); + if (micIn) { + plotMicInFilterSpectrum(); + adjRunTimeMicInFilter(); + } + else { + plotSpkOutFilterSpectrum(); + adjRunTimeSpkOutFilter(); + } +} + +void FilterDlg::setGain(EQ *eq) +{ + wxString buf; + buf.Printf(wxT("%3.1f"), eq->gaindB); + eq->valueGain->SetLabel(buf); + int slider = (int)(((eq->gaindB-MIN_GAIN)/(MAX_GAIN-MIN_GAIN))*SLIDER_MAX + 0.5); + eq->sliderGain->SetValue(slider); +} + +void FilterDlg::sliderToGain(EQ *eq, bool micIn) +{ + float range = MAX_GAIN-MIN_GAIN; + + eq->gaindB = MIN_GAIN + range*((float)eq->sliderGain->GetValue()/SLIDER_MAX); + //printf("gaindB: %f\n", eq->gaindB); + setGain(eq); + if (micIn) { + plotMicInFilterSpectrum(); + adjRunTimeMicInFilter(); + } + else { + plotSpkOutFilterSpectrum(); + adjRunTimeSpkOutFilter(); + } + +} + +void FilterDlg::setQ(EQ *eq) +{ + wxString buf; + buf.Printf(wxT("%2.1f"), eq->Q); + eq->valueQ->SetLabel(buf); + + float log10_range = MAX_LOG10_Q - MIN_LOG10_Q; + + int slider = (int)(((log10(eq->Q+1E-6)-MIN_LOG10_Q)/log10_range)*SLIDER_MAX + 0.5); + eq->sliderQ->SetValue(slider); +} + +void FilterDlg::sliderToQ(EQ *eq, bool micIn) +{ + float log10_range = MAX_LOG10_Q - MIN_LOG10_Q; + + float sliderNorm = (float)eq->sliderQ->GetValue()/SLIDER_MAX; + float log10Q = MIN_LOG10_Q + sliderNorm*(log10_range); + eq->Q = pow(10.0, log10Q); + //printf("log10Q: %f eq->Q: %f\n", log10Q, eq->Q); + setQ(eq); + if (micIn) { + plotMicInFilterSpectrum(); + adjRunTimeMicInFilter(); + } + else { + plotSpkOutFilterSpectrum(); + adjRunTimeSpkOutFilter(); + } +} + +void FilterDlg::plotMicInFilterSpectrum(void) { + plotFilterSpectrum(&m_MicInBass, &m_MicInMid, &m_MicInTreble, m_MicInFreqRespPlot, m_MicInMagdB); +} + +void FilterDlg::plotSpkOutFilterSpectrum(void) { + plotFilterSpectrum(&m_SpkOutBass, &m_SpkOutMid, &m_SpkOutTreble, m_SpkOutFreqRespPlot, m_SpkOutMagdB); +} + +void FilterDlg::adjRunTimeMicInFilter(void) { + // signal an adjustment in running filter coeffs + + if (m_running) { + ExchangeData(EXCHANGE_DATA_OUT, false); + *m_newMicInFilter = true; + } +} + +void FilterDlg::adjRunTimeSpkOutFilter(void) { + // signal an adjustment in running filter coeffs + + if (m_running) { + ExchangeData(EXCHANGE_DATA_OUT, false); + *m_newSpkOutFilter = true; + } +} + + +void FilterDlg::plotFilterSpectrum(EQ *eqBass, EQ *eqMid, EQ *eqTreble, PlotSpectrum* freqRespPlot, float *magdB) { + char *argBass[10]; + char *argTreble[10]; + char *argMid[10]; + char argstorage[10][80]; + float magBass[F_MAG_N]; + float magTreble[F_MAG_N]; + float magMid[F_MAG_N]; + int i; + + for(i=0; i<10; i++) { + argBass[i] = &argstorage[i][0]; + argTreble[i] = &argstorage[i][0]; + argMid[i] = &argstorage[i][0]; + } + sprintf(argBass[0], "bass"); + sprintf(argBass[1], "%f", eqBass->gaindB+1E-6); + sprintf(argBass[2], "%f", eqBass->freqHz); + + calcFilterSpectrum(magBass, 2, argBass); + + sprintf(argTreble[0], "treble"); + sprintf(argTreble[1], "%f", eqTreble->gaindB+1E-6); + sprintf(argTreble[2], "%f", eqTreble->freqHz); + + calcFilterSpectrum(magTreble, 2, argTreble); + + sprintf(argTreble[0], "equalizer"); + sprintf(argTreble[1], "%f", eqMid->freqHz); + sprintf(argTreble[2], "%f", eqMid->Q); + sprintf(argTreble[3], "%f", eqMid->gaindB+1E-6); + + calcFilterSpectrum(magMid, 3, argMid); + + for(i=0; im_newdata = true; + freqRespPlot->Refresh(); +} + +void FilterDlg::calcFilterSpectrum(float magdB[], int argc, char *argv[]) { + void *sbq; + short in[NIMP]; + short out[NIMP]; + COMP X[F_MAG_N]; + float f, w; + int i, k; + + // find impulse response ----------------------------------- + + for(i=0; i. +// +//========================================================================== + +#ifndef __FILTER_DIALOG__ +#define __FILTER_DIALOG__ + +#include "fdmdv2_main.h" + +enum {disableQ = false, enableQ = true}; + +typedef struct { + wxSlider *sliderFreq; + wxStaticText *valueFreq; + wxSlider *sliderGain; + wxStaticText *valueGain; + wxSlider *sliderQ; + wxStaticText *valueQ; + + int sliderFreqId; + int sliderGainId; + int sliderQId; + + float freqHz; + float gaindB; + float Q; + + float maxFreqHz; +} EQ; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class FilterDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class FilterDlg : public wxDialog +{ + public: + FilterDlg( wxWindow* parent, bool running, bool *newMicInFilter, bool *newSpkOutFilter, + wxWindowID id = wxID_ANY, const wxString& title = _("Filter"), + const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 800, 630 ), + long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~FilterDlg(); + + void ExchangeData(int inout, bool storePersistent); + + protected: + // Handlers for events. + void OnCancel(wxCommandEvent& event); + void OnOK(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void OnInitDialog(wxInitDialogEvent& event); + void OnLPCPostFilterDefault(wxCommandEvent& event); + + void OnBetaScroll(wxScrollEvent& event); + void OnGammaScroll(wxScrollEvent& event); + void OnEnable(wxScrollEvent& event); + void OnBassBoost(wxScrollEvent& event); + + void OnSpeexppEnable(wxScrollEvent& event); + + void OnMicInBassFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_MicInBass, true); } + void OnMicInBassGainScroll(wxScrollEvent& event) { sliderToGain(&m_MicInBass, true); } + void OnMicInTrebleFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_MicInTreble, true); } + void OnMicInTrebleGainScroll(wxScrollEvent& event) { sliderToGain(&m_MicInTreble, true); } + void OnMicInMidFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_MicInMid, true); } + void OnMicInMidGainScroll(wxScrollEvent& event) { sliderToGain(&m_MicInMid, true); } + void OnMicInMidQScroll(wxScrollEvent& event) { sliderToQ(&m_MicInMid, true); } + void OnMicInEnable(wxScrollEvent& event); + void OnMicInDefault(wxCommandEvent& event); + + void OnSpkOutBassFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_SpkOutBass, false); } + void OnSpkOutBassGainScroll(wxScrollEvent& event) { sliderToGain(&m_SpkOutBass, false); } + void OnSpkOutTrebleFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_SpkOutTreble, false); } + void OnSpkOutTrebleGainScroll(wxScrollEvent& event) { sliderToGain(&m_SpkOutTreble, false); } + void OnSpkOutMidFreqScroll(wxScrollEvent& event) { sliderToFreq(&m_SpkOutMid, false); } + void OnSpkOutMidGainScroll(wxScrollEvent& event) { sliderToGain(&m_SpkOutMid, false); } + void OnSpkOutMidQScroll(wxScrollEvent& event) { sliderToQ(&m_SpkOutMid, false); } + void OnSpkOutEnable(wxScrollEvent& event); + void OnSpkOutDefault(wxCommandEvent& event); + + wxStaticText* m_staticText8; + wxCheckBox* m_codec2LPCPostFilterEnable; + wxStaticText* m_staticText9; + wxCheckBox* m_codec2LPCPostFilterBassBoost; + wxStaticText* m_staticText91; + wxSlider* m_codec2LPCPostFilterBeta; + wxStaticText* m_staticTextBeta; + wxStaticText* m_staticText911; + wxSlider* m_codec2LPCPostFilterGamma; + wxStaticText* m_staticTextGamma; + wxButton* m_LPCPostFilterDefault; + + wxCheckBox* m_ckboxSpeexpp; + + wxStdDialogButtonSizer* m_sdbSizer5; + wxButton* m_sdbSizer5OK; + wxButton* m_sdbSizer5Cancel; + PlotSpectrum* m_MicInFreqRespPlot; + PlotSpectrum* m_SpkOutFreqRespPlot; + + wxCheckBox* m_MicInEnable; + wxButton* m_MicInDefault; + wxCheckBox* m_SpkOutEnable; + wxButton* m_SpkOutDefault; + + float *m_MicInMagdB; + float *m_SpkOutMagdB; + + private: + bool m_running; + float m_beta; + float m_gamma; + + void setBeta(void); // sets slider and static text from m_beta + void setGamma(void); // sets slider and static text from m_gamma + void setCodec2(void); + + void newEQControl(wxSlider** slider, wxStaticText** value, wxStaticBoxSizer *bs, wxString controlName); + EQ newEQ(wxSizer *bs, wxString eqName, float maxFreqHz, bool enableQ); + void newLPCPFControl(wxSlider **slider, wxStaticText **stValue, wxSizer *sbs, wxString controlName); + wxAuiNotebook *m_auiNotebook; + void setFreq(EQ *eq); + void setGain(EQ *eq); + void setQ(EQ *eq); + void sliderToFreq(EQ *eq, bool micIn); + void sliderToGain(EQ *eq, bool micIn); + void sliderToQ(EQ *eq, bool micIn); + void plotFilterSpectrum(EQ *eqBass, EQ *eqMid, EQ* eqTreble, PlotSpectrum* freqRespPlot, float *magdB); + void calcFilterSpectrum(float magdB[], int arc, char *argv[]); + void plotMicInFilterSpectrum(void); + void plotSpkOutFilterSpectrum(void); + void adjRunTimeMicInFilter(void); + void adjRunTimeSpkOutFilter(void); + + EQ m_MicInBass; + EQ m_MicInMid; + EQ m_MicInTreble; + + EQ m_SpkOutBass; + EQ m_SpkOutMid; + EQ m_SpkOutTreble; + + float limit(float value, float min, float max); + + bool *m_newMicInFilter; + bool *m_newSpkOutFilter; + +}; + +#endif // __FILTER_DIALOG__ diff --git a/freedv/tags/1.2.2/src/dlg_options.cpp b/freedv/tags/1.2.2/src/dlg_options.cpp new file mode 100644 index 00000000..79b6c87f --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_options.cpp @@ -0,0 +1,616 @@ +//========================================================================== +// Name: dlg_options.cpp +// Purpose: Dialog for controlling misc FreeDV options +// Date: May 24 2013 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== + +#include "dlg_options.h" + +extern bool g_modal; +extern struct freedv *g_pfreedv; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class OptionsDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +OptionsDlg::OptionsDlg(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +{ + + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + wxBoxSizer* bSizer30; + bSizer30 = new wxBoxSizer(wxVERTICAL); + + //------------------------------ + // Txt Msg Text Box + //------------------------------ + + wxStaticBoxSizer* sbSizer_callSign; + wxStaticBox *sb_textMsg = new wxStaticBox(this, wxID_ANY, _("Txt Msg")); + sbSizer_callSign = new wxStaticBoxSizer(sb_textMsg, wxVERTICAL); + + m_txtCtrlCallSign = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + m_txtCtrlCallSign->SetToolTip(_("Txt Msg you can send along with Voice")); + sbSizer_callSign->Add(m_txtCtrlCallSign, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 3); + + bSizer30->Add(sbSizer_callSign,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //---------------------------------------------------------------------- + // Voice Keyer + //---------------------------------------------------------------------- + + wxStaticBoxSizer* staticBoxSizer28a = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("Voice Keyer")), wxHORIZONTAL); + + wxStaticText *m_staticText28b = new wxStaticText(this, wxID_ANY, _("Wave File: "), wxDefaultPosition, wxDefaultSize, 0); + staticBoxSizer28a->Add(m_staticText28b, 0, wxALIGN_CENTER_VERTICAL, 5); + m_txtCtrlVoiceKeyerWaveFile = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(300,-1), 0); + m_txtCtrlVoiceKeyerWaveFile->SetToolTip(_("Wave file to play for Voice Keyer")); + staticBoxSizer28a->Add(m_txtCtrlVoiceKeyerWaveFile, 0, 0, 5); + + m_buttonChooseVoiceKeyerWaveFile = new wxButton(this, wxID_APPLY, _("Choose"), wxDefaultPosition, wxSize(-1,-1), 0); + staticBoxSizer28a->Add(m_buttonChooseVoiceKeyerWaveFile, 0, wxALIGN_CENTER_VERTICAL, 5); + + wxStaticText *m_staticText28c = new wxStaticText(this, wxID_ANY, _(" Rx Pause: "), wxDefaultPosition, wxDefaultSize, 0); + staticBoxSizer28a->Add(m_staticText28c, 0, wxALIGN_CENTER_VERTICAL , 5); + m_txtCtrlVoiceKeyerRxPause = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(40,-1), 0); + m_txtCtrlVoiceKeyerRxPause->SetToolTip(_("How long to wait in Rx mode before repeat")); + staticBoxSizer28a->Add(m_txtCtrlVoiceKeyerRxPause, 0, 0, 5); + + wxStaticText *m_staticText28d = new wxStaticText(this, wxID_ANY, _(" Repeats: "), wxDefaultPosition, wxDefaultSize, 0); + staticBoxSizer28a->Add(m_staticText28d, 0, wxALIGN_CENTER_VERTICAL, 5); + m_txtCtrlVoiceKeyerRepeats = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(40,-1), 0); + m_txtCtrlVoiceKeyerRepeats->SetToolTip(_("How long to wait in Rx mode before repeat")); + staticBoxSizer28a->Add(m_txtCtrlVoiceKeyerRepeats, 0, 0, 5); + + bSizer30->Add(staticBoxSizer28a,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + +#ifdef __WXMSW__ + //------------------------------ + // debug console, for WIndows build make console pop up for debug messages + //------------------------------ + + wxStaticBoxSizer* sbSizer_console; + wxStaticBox *sb_console = new wxStaticBox(this, wxID_ANY, _("Debug")); + sbSizer_console = new wxStaticBoxSizer(sb_console, wxHORIZONTAL); + + m_ckboxDebugConsole = new wxCheckBox(this, wxID_ANY, _("Show Console"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_console->Add(m_ckboxDebugConsole, 0, wxALIGN_LEFT, 0); + + bSizer30->Add(sbSizer_console,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); +#endif + + //------------------------------ + // FreeDV 700 Options + //------------------------------ + + wxStaticBoxSizer* sbSizer_freedv700; + wxStaticBox *sb_freedv700 = new wxStaticBox(this, wxID_ANY, _("FreeDV 700 Options")); + sbSizer_freedv700 = new wxStaticBoxSizer(sb_freedv700, wxHORIZONTAL); + + m_ckboxFreeDV700txClip = new wxCheckBox(this, wxID_ANY, _("Clipping"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_freedv700->Add(m_ckboxFreeDV700txClip, 0, wxALIGN_LEFT, 0); + m_ckboxFreeDV700Combine = new wxCheckBox(this, wxID_ANY, _("Diversity Combine for plots"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_freedv700->Add(m_ckboxFreeDV700Combine, 0, wxALIGN_LEFT, 0); + + bSizer30->Add(sbSizer_freedv700, 0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // Half/Full duplex selection + //------------------------------ + + wxStaticBox *sb_duplex = new wxStaticBox(this, wxID_ANY, _("Half/Full Duplex Operation")); + wxStaticBoxSizer* sbSizer_duplex = new wxStaticBoxSizer(sb_duplex, wxHORIZONTAL); + m_ckHalfDuplex = new wxCheckBox(this, wxID_ANY, _("Half Duplex"), wxDefaultPosition, wxSize(-1,-1), 0); + sbSizer_duplex->Add(m_ckHalfDuplex, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); + bSizer30->Add(sbSizer_duplex,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // Test Frames/Channel simulation check box + //------------------------------ + + wxStaticBoxSizer* sbSizer_testFrames; + wxStaticBox *sb_testFrames = new wxStaticBox(this, wxID_ANY, _("Testing and Channel Simulation")); + sbSizer_testFrames = new wxStaticBoxSizer(sb_testFrames, wxHORIZONTAL); + + m_ckboxTestFrame = new wxCheckBox(this, wxID_ANY, _("Test Frames"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_testFrames->Add(m_ckboxTestFrame, 0, wxALIGN_LEFT, 0); + + m_ckboxChannelNoise = new wxCheckBox(this, wxID_ANY, _("Channel Noise SNR (dB):"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_testFrames->Add(m_ckboxChannelNoise, 0, wxALIGN_LEFT, 0); + m_txtNoiseSNR = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(30,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + sbSizer_testFrames->Add(m_txtNoiseSNR, 0, wxALIGN_LEFT, 0); + + m_ckboxAttnCarrierEn = new wxCheckBox(this, wxID_ANY, _("Attn Carrier Carrier:"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_testFrames->Add(m_ckboxAttnCarrierEn, 0, wxALIGN_LEFT, 0); + m_txtAttnCarrier = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(30,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + sbSizer_testFrames->Add(m_txtAttnCarrier, 0, wxALIGN_LEFT, 0); + + bSizer30->Add(sbSizer_testFrames,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // Interfering tone + //------------------------------ + + wxStaticBoxSizer* sbSizer_tone; + wxStaticBox *sb_tone = new wxStaticBox(this, wxID_ANY, _("Simulated Interference Tone")); + sbSizer_tone = new wxStaticBoxSizer(sb_tone, wxHORIZONTAL); + + m_ckboxTone = new wxCheckBox(this, wxID_ANY, _("Tone Freq (Hz):"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_tone->Add(m_ckboxTone, 0, wxALIGN_LEFT, 0); + m_txtToneFreqHz = new wxTextCtrl(this, wxID_ANY, "1000", wxDefaultPosition, wxSize(60,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + sbSizer_tone->Add(m_txtToneFreqHz, 0, wxALIGN_LEFT, 0); + wxStaticText *m_staticTextta = new wxStaticText(this, wxID_ANY, _(" Amplitude (pk): "), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_tone->Add(m_staticTextta, 0, wxALIGN_CENTER_VERTICAL, 5); + m_txtToneAmplitude = new wxTextCtrl(this, wxID_ANY, "1000", wxDefaultPosition, wxSize(60,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + sbSizer_tone->Add(m_txtToneAmplitude, 0, wxALIGN_LEFT, 0); + + bSizer30->Add(sbSizer_tone,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + +#ifdef __EXPERIMENTAL_UDP__ + //------------------------------ + // Txt Encoding + //------------------------------ + + wxStaticBoxSizer* sbSizer_encoding = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Text Encoding")), wxHORIZONTAL); + +#ifdef SHORT_VARICODE + m_rb_textEncoding1 = new wxRadioButton( this, wxID_ANY, wxT("Long varicode"), wxDefaultPosition, wxDefaultSize, 0); + m_rb_textEncoding1->SetValue(true); + sbSizer_encoding->Add(m_rb_textEncoding1, 0, wxALIGN_LEFT|wxALL, 1); + m_rb_textEncoding2 = new wxRadioButton( this, wxID_ANY, wxT("Short Varicode"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_encoding->Add(m_rb_textEncoding2, 0, wxALIGN_LEFT|wxALL, 1); +#endif + + m_ckboxEnableChecksum = new wxCheckBox(this, wxID_ANY, _("Use Checksum on Rx"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_encoding->Add(m_ckboxEnableChecksum, 0, wxALIGN_LEFT, 0); + + bSizer30->Add(sbSizer_encoding,0, wxALL|wxEXPAND, 3); + + //------------------------------ + // Event processing + //------------------------------ + + wxStaticBoxSizer* sbSizer_events; + wxStaticBox *sb_events = new wxStaticBox(this, wxID_ANY, _("Event Processing")); + sbSizer_events = new wxStaticBoxSizer(sb_events, wxVERTICAL); + + // event processing enable and spam timer + + wxStaticBoxSizer* sbSizer_events_top; + wxStaticBox* sb_events1 = new wxStaticBox(this, wxID_ANY, _("")); + sbSizer_events_top = new wxStaticBoxSizer(sb_events1, wxHORIZONTAL); + + m_ckbox_events = new wxCheckBox(this, wxID_ANY, _("Enable System Calls Syscall Spam Timer"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sb_events->SetToolTip(_("Enable processing of events and generation of system calls")); + sbSizer_events_top->Add(m_ckbox_events, 0, 0, 5); + m_txt_spam_timer = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(40,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + m_txt_spam_timer->SetToolTip(_("Many matching events can cause a flood of syscalls. Set minimum time (seconds) between syscalls for each event here")); + sbSizer_events_top->Add(m_txt_spam_timer, 0, 0, 5); + m_rb_spam_timer = new wxRadioButton( this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + m_rb_spam_timer->SetForegroundColour( wxColour(0, 255, 0 ) ); + sbSizer_events_top->Add(m_rb_spam_timer, 0, 0, 10); + sbSizer_events->Add(sbSizer_events_top, 0, 0, 5); + + // list of regexps + + wxStaticBoxSizer* sbSizer_regexp = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Regular Expressions to Process Events")), wxHORIZONTAL); + m_txt_events_regexp_match = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,100), wxTE_MULTILINE); + m_txt_events_regexp_match->SetToolTip(_("Enter regular expressions to match events")); + sbSizer_regexp->Add(m_txt_events_regexp_match, 1, wxEXPAND, 5); + m_txt_events_regexp_replace = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,100), wxTE_MULTILINE); + m_txt_events_regexp_replace->SetToolTip(_("Enter regular expressions to replace events")); + sbSizer_regexp->Add(m_txt_events_regexp_replace, 1, wxEXPAND, 5); + sbSizer_events->Add(sbSizer_regexp, 1, wxEXPAND, 5); + + // log of events and responses + + wxStaticBoxSizer* sbSizer_event_log = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Log of Events and Responses")), wxVERTICAL); + wxBoxSizer* bSizer33 = new wxBoxSizer(wxHORIZONTAL); + m_txt_events_in = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,50), wxTE_MULTILINE | wxTE_READONLY); + bSizer33->Add(m_txt_events_in, 1, wxEXPAND, 5); + m_txt_events_out = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,50), wxTE_MULTILINE | wxTE_READONLY); + bSizer33->Add(m_txt_events_out, 1, wxEXPAND, 5); + sbSizer_event_log->Add(bSizer33, 1, wxEXPAND, 5); + sbSizer_events->Add(sbSizer_event_log, 1, wxEXPAND, 5); + + bSizer30->Add(sbSizer_events,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // UDP control port + //------------------------------ + + wxStaticBoxSizer* sbSizer_udp; + wxStaticBox* sb_udp = new wxStaticBox(this, wxID_ANY, _("UDP Control Port")); + sbSizer_udp = new wxStaticBoxSizer(sb_udp, wxHORIZONTAL); + m_ckbox_udp_enable = new wxCheckBox(this, wxID_ANY, _("Enable UDP Control Port UDP Port Number:"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sb_udp->SetToolTip(_("Enable control of FreeDV via UDP port")); + sbSizer_udp->Add(m_ckbox_udp_enable, 0, wxALIGN_CENTER_HORIZONTAL, 5); + m_txt_udp_port = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(50,-1), 0, wxTextValidator(wxFILTER_DIGITS)); + sbSizer_udp->Add(m_txt_udp_port, 0, wxALIGN_CENTER_HORIZONTAL, 5); + + bSizer30->Add(sbSizer_udp,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); +#endif + + //------------------------------ + // OK - Cancel - Apply Buttons + //------------------------------ + + wxBoxSizer* bSizer31 = new wxBoxSizer(wxHORIZONTAL); + + m_sdbSizer5OK = new wxButton(this, wxID_OK); + bSizer31->Add(m_sdbSizer5OK, 0, wxALL, 2); + + m_sdbSizer5Cancel = new wxButton(this, wxID_CANCEL); + bSizer31->Add(m_sdbSizer5Cancel, 0, wxALL, 2); + + m_sdbSizer5Apply = new wxButton(this, wxID_APPLY); + bSizer31->Add(m_sdbSizer5Apply, 0, wxALL, 2); + + bSizer30->Add(bSizer31, 0, wxALIGN_CENTER, 0); + + this->SetSizer(bSizer30); + if ( GetSizer() ) + { + GetSizer()->Fit(this); + } + this->Layout(); + + this->Centre(wxBOTH); + + // Connect Events ------------------------------------------------------- + + this->Connect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(OptionsDlg::OnInitDialog)); + + m_sdbSizer5OK->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnOK), NULL, this); + m_sdbSizer5Cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnCancel), NULL, this); + m_sdbSizer5Apply->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnApply), NULL, this); + + m_ckboxTestFrame->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnTestFrame), NULL, this); + m_ckboxChannelNoise->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnChannelNoise), NULL, this); + m_ckboxAttnCarrierEn->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnAttnCarrierEn), NULL, this); + + m_ckboxFreeDV700txClip->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnFreeDV700txClip), NULL, this); + m_ckboxFreeDV700Combine->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnFreeDV700Combine), NULL, this); + +#ifdef __WXMSW__ + m_ckboxDebugConsole->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnDebugConsole), NULL, this); +#endif + + m_buttonChooseVoiceKeyerWaveFile->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnChooseVoiceKeyerWaveFile), NULL, this); + + event_in_serial = 0; + event_out_serial = 0; +} + +//------------------------------------------------------------------------- +// ~OptionsDlg() +//------------------------------------------------------------------------- +OptionsDlg::~OptionsDlg() +{ + + // Disconnect Events + + this->Disconnect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(OptionsDlg::OnInitDialog)); + + m_sdbSizer5OK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnOK), NULL, this); + m_sdbSizer5Cancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnCancel), NULL, this); + m_sdbSizer5Apply->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnApply), NULL, this); + + m_ckboxTestFrame->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnTestFrame), NULL, this); + m_ckboxChannelNoise->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnChannelNoise), NULL, this); + m_ckboxAttnCarrierEn->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnAttnCarrierEn), NULL, this); + + m_ckboxFreeDV700txClip->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnFreeDV700txClip), NULL, this); + m_ckboxFreeDV700Combine->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnFreeDV700Combine), NULL, this); + m_buttonChooseVoiceKeyerWaveFile->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(OptionsDlg::OnChooseVoiceKeyerWaveFile), NULL, this); + +#ifdef __WXMSW__ + m_ckboxDebugConsole->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxScrollEventHandler(OptionsDlg::OnDebugConsole), NULL, this); +#endif +} + + +//------------------------------------------------------------------------- +// ExchangeData() +//------------------------------------------------------------------------- +void OptionsDlg::ExchangeData(int inout, bool storePersistent) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + + if(inout == EXCHANGE_DATA_IN) + { + m_txtCtrlCallSign->SetValue(wxGetApp().m_callSign); + + /* Voice Keyer */ + + m_txtCtrlVoiceKeyerWaveFile->SetValue(wxGetApp().m_txtVoiceKeyerWaveFile); + m_txtCtrlVoiceKeyerRxPause->SetValue(wxString::Format(wxT("%i"), wxGetApp().m_intVoiceKeyerRxPause)); + m_txtCtrlVoiceKeyerRepeats->SetValue(wxString::Format(wxT("%i"), wxGetApp().m_intVoiceKeyerRepeats)); + + m_ckHalfDuplex->SetValue(wxGetApp().m_boolHalfDuplex); + + m_ckboxTestFrame->SetValue(wxGetApp().m_testFrames); + + m_ckboxChannelNoise->SetValue(wxGetApp().m_channel_noise); + m_txtNoiseSNR->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_noise_snr)); + + m_ckboxTone->SetValue(wxGetApp().m_tone); + m_txtToneFreqHz->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_tone_freq_hz)); + m_txtToneAmplitude->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_tone_amplitude)); + + m_ckboxAttnCarrierEn->SetValue(wxGetApp().m_attn_carrier_en); + m_txtAttnCarrier->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_attn_carrier)); + +#ifdef __EXPERIMENTAL_UDP__ + m_ckbox_events->SetValue(wxGetApp().m_events); + m_txt_spam_timer->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_events_spam_timer)); + + m_txt_events_regexp_match->SetValue(wxGetApp().m_events_regexp_match); + m_txt_events_regexp_replace->SetValue(wxGetApp().m_events_regexp_replace); + + m_ckbox_udp_enable->SetValue(wxGetApp().m_udp_enable); + m_txt_udp_port->SetValue(wxString::Format(wxT("%i"),wxGetApp().m_udp_port)); + +#ifdef SHORT_VARICODE + if (wxGetApp().m_textEncoding == 1) + m_rb_textEncoding1->SetValue(true); + if (wxGetApp().m_textEncoding == 2) + m_rb_textEncoding2->SetValue(true); +#endif + m_ckboxEnableChecksum->SetValue(wxGetApp().m_enable_checksum); +#endif + + m_ckboxFreeDV700txClip->SetValue(wxGetApp().m_FreeDV700txClip); + m_ckboxFreeDV700Combine->SetValue(wxGetApp().m_FreeDV700Combine); + +#ifdef __WXMSW__ + m_ckboxDebugConsole->SetValue(wxGetApp().m_debug_console); +#endif + } + + if(inout == EXCHANGE_DATA_OUT) + { + wxGetApp().m_callSign = m_txtCtrlCallSign->GetValue(); + + wxGetApp().m_boolHalfDuplex = m_ckHalfDuplex->GetValue(); + pConfig->Write(wxT("/Rig/HalfDuplex"), wxGetApp().m_boolHalfDuplex); + + /* Voice Keyer */ + + wxGetApp().m_txtVoiceKeyerWaveFile = m_txtCtrlVoiceKeyerWaveFile->GetValue(); + pConfig->Write(wxT("/VoiceKeyer/WaveFile"), wxGetApp().m_txtVoiceKeyerWaveFile); + long tmp; + m_txtCtrlVoiceKeyerRxPause->GetValue().ToLong(&tmp); if (tmp < 0) tmp = 0; wxGetApp().m_intVoiceKeyerRxPause = (int)tmp; + pConfig->Write(wxT("/VoiceKeyer/RxPause"), wxGetApp().m_intVoiceKeyerRxPause); + m_txtCtrlVoiceKeyerRepeats->GetValue().ToLong(&tmp); + if (tmp < 0) tmp = 0; if (tmp > 100) tmp = 100; + wxGetApp().m_intVoiceKeyerRepeats = (int)tmp; + pConfig->Write(wxT("/VoiceKeyer/Repeats"), wxGetApp().m_intVoiceKeyerRepeats); + + wxGetApp().m_testFrames = m_ckboxTestFrame->GetValue(); + + wxGetApp().m_channel_noise = m_ckboxChannelNoise->GetValue(); + long noise_snr; + m_txtNoiseSNR->GetValue().ToLong(&noise_snr); + wxGetApp().m_noise_snr = (int)noise_snr; + + wxGetApp().m_tone = m_ckboxTone->GetValue(); + long tone_freq_hz, tone_amplitude; + m_txtToneFreqHz->GetValue().ToLong(&tone_freq_hz); + wxGetApp().m_tone_freq_hz = (int)tone_freq_hz; + m_txtToneAmplitude->GetValue().ToLong(&tone_amplitude); + wxGetApp().m_tone_amplitude = (int)tone_amplitude; + + wxGetApp().m_attn_carrier_en = m_ckboxAttnCarrierEn->GetValue(); + long attn_carrier; + m_txtAttnCarrier->GetValue().ToLong(&attn_carrier); + wxGetApp().m_attn_carrier = (int)attn_carrier; + +#ifdef __EXPERIMENTAL_UDP__ + wxGetApp().m_events = m_ckbox_events->GetValue(); + long spam_timer; + m_txt_spam_timer->GetValue().ToLong(&spam_timer); + wxGetApp().m_events_spam_timer = (int)spam_timer; + + // make sure regexp lists are terminated by a \n + + if (m_txt_events_regexp_match->GetValue().Last() != '\n') { + m_txt_events_regexp_match->SetValue(m_txt_events_regexp_match->GetValue()+'\n'); + } + if (m_txt_events_regexp_replace->GetValue().Last() != '\n') { + m_txt_events_regexp_replace->SetValue(m_txt_events_regexp_replace->GetValue()+'\n'); + } + wxGetApp().m_events_regexp_match = m_txt_events_regexp_match->GetValue(); + wxGetApp().m_events_regexp_replace = m_txt_events_regexp_replace->GetValue(); + + wxGetApp().m_udp_enable = m_ckbox_udp_enable->GetValue(); + long port; + m_txt_udp_port->GetValue().ToLong(&port); + wxGetApp().m_udp_port = (int)port; + +#ifdef SHORT_VARICODE + if (m_rb_textEncoding1->GetValue()) + wxGetApp().m_textEncoding = 1; + if (m_rb_textEncoding2->GetValue()) + wxGetApp().m_textEncoding = 2; +#endif + wxGetApp().m_enable_checksum = m_ckboxEnableChecksum->GetValue(); +#endif + + wxGetApp().m_FreeDV700txClip = m_ckboxFreeDV700txClip->GetValue(); + wxGetApp().m_FreeDV700Combine = m_ckboxFreeDV700Combine->GetValue(); + +#ifdef __WXMSW__ + wxGetApp().m_debug_console = m_ckboxDebugConsole->GetValue(); +#endif + + if (storePersistent) { + pConfig->Write(wxT("/Data/CallSign"), wxGetApp().m_callSign); +#ifdef SHORT_VARICODE + pConfig->Write(wxT("/Data/TextEncoding"), wxGetApp().m_textEncoding); +#endif + pConfig->Write(wxT("/Data/EnableChecksumOnMsgRx"), wxGetApp().m_enable_checksum); + + pConfig->Write(wxT("/Events/enable"), wxGetApp().m_events); + pConfig->Write(wxT("/Events/spam_timer"), wxGetApp().m_events_spam_timer); + pConfig->Write(wxT("/Events/regexp_match"), wxGetApp().m_events_regexp_match); + pConfig->Write(wxT("/Events/regexp_replace"), wxGetApp().m_events_regexp_replace); + + pConfig->Write(wxT("/UDP/enable"), wxGetApp().m_udp_enable); + pConfig->Write(wxT("/UDP/port"), wxGetApp().m_udp_port); + + pConfig->Write(wxT("/Events/spam_timer"), wxGetApp().m_events_spam_timer); + + pConfig->Write(wxT("/FreeDV700/txClip"), wxGetApp().m_FreeDV700txClip); + + pConfig->Write(wxT("/Noise/noise_snr"), wxGetApp().m_noise_snr); + +#ifdef __WXMSW__ + pConfig->Write(wxT("/Debug/console"), wxGetApp().m_debug_console); +#endif + + pConfig->Flush(); + } + } + delete wxConfigBase::Set((wxConfigBase *) NULL); +} + +//------------------------------------------------------------------------- +// OnOK() +//------------------------------------------------------------------------- +void OptionsDlg::OnOK(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT, true); + //this->EndModal(wxID_OK); + g_modal = false; + this->Show(false); +} + +//------------------------------------------------------------------------- +// OnCancel() +//------------------------------------------------------------------------- +void OptionsDlg::OnCancel(wxCommandEvent& event) +{ + //this->EndModal(wxID_CANCEL); + g_modal = false; + this->Show(false); +} + +//------------------------------------------------------------------------- +// OnApply() +//------------------------------------------------------------------------- +void OptionsDlg::OnApply(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT, true); +} + +//------------------------------------------------------------------------- +// OnInitDialog() +//------------------------------------------------------------------------- +void OptionsDlg::OnInitDialog(wxInitDialogEvent& event) +{ + ExchangeData(EXCHANGE_DATA_IN, false); +} + +// immediately change flags rather using ExchangeData() so we can switch on and off at run time + +void OptionsDlg::OnTestFrame(wxScrollEvent& event) { + wxGetApp().m_testFrames = m_ckboxTestFrame->GetValue(); +} + +void OptionsDlg::OnChannelNoise(wxScrollEvent& event) { + wxGetApp().m_channel_noise = m_ckboxChannelNoise->GetValue(); +} + + +void OptionsDlg::OnChooseVoiceKeyerWaveFile(wxCommandEvent& event) { + wxFileDialog openFileDialog( + this, + wxT("Voice Keyer wave file"), + wxGetApp().m_txtVoiceKeyerWaveFilePath, + wxEmptyString, + wxT("WAV files (*.wav)|*.wav"), + wxFD_OPEN + ); + if(openFileDialog.ShowModal() == wxID_CANCEL) { + return; // the user changed their mind... + } + + wxString fileName, extension; + wxGetApp().m_txtVoiceKeyerWaveFile = openFileDialog.GetPath(); + wxFileName::SplitPath(wxGetApp().m_txtVoiceKeyerWaveFile, &wxGetApp().m_txtVoiceKeyerWaveFilePath, &fileName, &extension); + m_txtCtrlVoiceKeyerWaveFile->SetValue(wxGetApp().m_txtVoiceKeyerWaveFile); +} + +// Run time update of carrier amplitude attenuation + +void OptionsDlg::OnAttnCarrierEn(wxScrollEvent& event) { + long attn_carrier; + m_txtAttnCarrier->GetValue().ToLong(&attn_carrier); + wxGetApp().m_attn_carrier = (int)attn_carrier; + + /* uncheck -> checked, attenuate selected carrier */ + + if (m_ckboxAttnCarrierEn->GetValue() && !wxGetApp().m_attn_carrier_en) { + if (freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C) { + freedv_set_carrier_ampl(g_pfreedv, wxGetApp().m_attn_carrier, 0.25); + } else { + wxMessageBox("Carrier attenuation feature only works on 700C", wxT("Warning"), wxOK | wxICON_WARNING, this); + } + } + + /* checked -> unchecked, reset selected carrier */ + + if (!m_ckboxAttnCarrierEn->GetValue() && wxGetApp().m_attn_carrier_en) { + if (freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C) { + freedv_set_carrier_ampl(g_pfreedv, wxGetApp().m_attn_carrier, 1.0); + } + } + + wxGetApp().m_attn_carrier_en = m_ckboxAttnCarrierEn->GetValue(); +} + +void OptionsDlg::OnFreeDV700txClip(wxScrollEvent& event) { + wxGetApp().m_FreeDV700txClip = m_ckboxFreeDV700txClip->GetValue(); +} + +void OptionsDlg::OnFreeDV700Combine(wxScrollEvent& event) { + wxGetApp().m_FreeDV700Combine = m_ckboxFreeDV700Combine->GetValue(); +} + +void OptionsDlg::updateEventLog(wxString event_in, wxString event_out) { + wxString event_in_with_serial, event_out_with_serial; + event_in_with_serial.Printf(_T("[%d] %s"), event_in_serial++, event_in); + event_out_with_serial.Printf(_T("[%d] %s"), event_out_serial++, event_out); + + m_txt_events_in->AppendText(event_in_with_serial+"\n"); + m_txt_events_out->AppendText(event_out_with_serial+"\n"); +} + + +void OptionsDlg::OnDebugConsole(wxScrollEvent& event) { + wxGetApp().m_debug_console = m_ckboxDebugConsole->GetValue(); +#ifdef __WXMSW__ + // somewhere to send printfs while developing, causes conmsole to pop up on Windows + if (wxGetApp().m_debug_console) { + int ret = AllocConsole(); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + fprintf(stderr, "AllocConsole: %d m_debug_console: %d\n", ret, wxGetApp().m_debug_console); + } +#endif +} diff --git a/freedv/tags/1.2.2/src/dlg_options.h b/freedv/tags/1.2.2/src/dlg_options.h new file mode 100644 index 00000000..081448cb --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_options.h @@ -0,0 +1,137 @@ +//========================================================================== +// Name: dlg_options.h +// Purpose: Dialog for controlling misc FreeDV options +// Created: Nov 25 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== + +#ifndef __OPTIONS_DIALOG__ +#define __OPTIONS_DIALOG__ + +#include "fdmdv2_main.h" +#include "fdmdv2_defines.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class OptionsDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class OptionsDlg : public wxDialog +{ + public: + OptionsDlg( wxWindow* parent, + wxWindowID id = wxID_ANY, const wxString& title = _("Options"), + const wxPoint& pos = wxDefaultPosition, +#ifdef __WXMSW__ + /* we add debug console check box for windows */ + const wxSize& size = wxSize(600,410), +#else + const wxSize& size = wxSize(600,380), +#endif + long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~OptionsDlg(); + + void ExchangeData(int inout, bool storePersistent); + void updateEventLog(wxString event_in, wxString event_out); + + bool enableEventsChecked() {return m_ckbox_events->GetValue();} + + void SetSpamTimerLight(bool state) { + + // Colours don't work on Windows + + if (state) { + m_rb_spam_timer->SetForegroundColour( wxColour( 255,0 , 0 ) ); // red + m_rb_spam_timer->SetValue(true); + } + else { + m_rb_spam_timer->SetForegroundColour( wxColour( 0, 255, 0 ) ); // green + m_rb_spam_timer->SetValue(false); + } + } + + + protected: + + // Handlers for events. + + void OnOK(wxCommandEvent& event); + void OnCancel(wxCommandEvent& event); + void OnApply(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void OnInitDialog(wxInitDialogEvent& event); + + void OnTestFrame(wxScrollEvent& event); + void OnChannelNoise(wxScrollEvent& event); + void OnAttnCarrierEn(wxScrollEvent& event); + void OnFreeDV700txClip(wxScrollEvent& event); + void OnFreeDV700Combine(wxScrollEvent& event); + void OnDebugConsole(wxScrollEvent& event); + + wxTextCtrl *m_txtCtrlCallSign; // TODO: this should be renamed to tx_txtmsg, and rename all related incl persis strge + + wxCheckBox* m_ckHalfDuplex; + + /* Voice Keyer */ + + wxButton *m_buttonChooseVoiceKeyerWaveFile; + wxTextCtrl *m_txtCtrlVoiceKeyerWaveFile; + wxTextCtrl *m_txtCtrlVoiceKeyerRxPause; + wxTextCtrl *m_txtCtrlVoiceKeyerRepeats; + + /* test frames, other simulated channel impairments */ + + wxCheckBox *m_ckboxTestFrame; + wxCheckBox *m_ckboxChannelNoise; + wxTextCtrl *m_txtNoiseSNR; + wxCheckBox *m_ckboxAttnCarrierEn; + wxTextCtrl *m_txtAttnCarrier; + + wxCheckBox *m_ckboxTone; + wxTextCtrl *m_txtToneFreqHz; + wxTextCtrl *m_txtToneAmplitude; + + wxCheckBox *m_ckboxFreeDV700txClip; + wxCheckBox *m_ckboxFreeDV700Combine; + + wxRadioButton *m_rb_textEncoding1; + wxRadioButton *m_rb_textEncoding2; + wxCheckBox *m_ckboxEnableChecksum; + + wxCheckBox *m_ckbox_events; + wxTextCtrl *m_txt_events_regexp_match; + wxTextCtrl *m_txt_events_regexp_replace; + wxTextCtrl *m_txt_events_in; + wxTextCtrl *m_txt_events_out; + wxTextCtrl *m_txt_spam_timer; + wxRadioButton *m_rb_spam_timer; + + wxCheckBox *m_ckbox_udp_enable; + wxTextCtrl *m_txt_udp_port; + + wxButton* m_sdbSizer5OK; + wxButton* m_sdbSizer5Cancel; + wxButton* m_sdbSizer5Apply; + + wxCheckBox *m_ckboxDebugConsole; + + unsigned int event_in_serial, event_out_serial; + + void OnChooseVoiceKeyerWaveFile(wxCommandEvent& event); + + private: +}; + +#endif // __OPTIONS_DIALOG__ diff --git a/freedv/tags/1.2.2/src/dlg_plugin.cpp b/freedv/tags/1.2.2/src/dlg_plugin.cpp new file mode 100644 index 00000000..68610f42 --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_plugin.cpp @@ -0,0 +1,148 @@ +//========================================================================== +// Name: dlg_plugin.cpp +// Purpose: Subclasses dialog GUI for PlugIn Config. Creates simple +// wxWidgets dialog GUI to set a few text strings. +// Date: Jan 2016 +// Authors: David Rowe +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== + +#include "dlg_plugin.h" +#include "fdmdv2_main.h" + +#ifdef __WIN32__ +#include +#endif +#if defined(__FreeBSD__) || defined(__WXOSX__) +#include +#include +#endif + +#include + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlugInDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PlugInDlg::PlugInDlg(const wxString& title, int numParam, wxString paramName[], wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +{ + m_name = title; + m_numParam = numParam; + assert(m_numParam <= PLUGIN_MAX_PARAMS); + + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + this->SetSizer(mainSizer); + + int i; + for (i=0; iAdd(m_txtCtrlParam[i], 0, 0, 5); + mainSizer->Add(staticBoxSizer28a, 0, wxEXPAND, 5); + } + + //---------------------------------------------------------------------- + // OK - Cancel - Apply + //---------------------------------------------------------------------- + + wxBoxSizer* boxSizer12 = new wxBoxSizer(wxHORIZONTAL); + + m_buttonOK = new wxButton(this, wxID_OK, _("OK"), wxDefaultPosition, wxSize(-1,-1), 0); + m_buttonOK->SetDefault(); + boxSizer12->Add(m_buttonOK, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + m_buttonCancel = new wxButton(this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize(-1,-1), 0); + boxSizer12->Add(m_buttonCancel, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + mainSizer->Add(boxSizer12, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM|wxALIGN_CENTER_HORIZONTAL, 5); + + if ( GetSizer() ) + { + GetSizer()->Fit(this); + } + Centre(wxBOTH); + + // Connect events + this->Connect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(PlugInDlg::OnInitDialog), NULL, this); + m_buttonOK->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(PlugInDlg::OnOK), NULL, this); + m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(PlugInDlg::OnCancel), NULL, this); + +} + +//------------------------------------------------------------------------- +// ~PlugInDlg() +//------------------------------------------------------------------------- +PlugInDlg::~PlugInDlg() +{ + // Disconnect Events + this->Disconnect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(PlugInDlg::OnInitDialog), NULL, this); + m_buttonOK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(PlugInDlg::OnOK), NULL, this); + m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(PlugInDlg::OnCancel), NULL, this); +} + +//------------------------------------------------------------------------- +// OnInitDialog() +//------------------------------------------------------------------------- +void PlugInDlg::OnInitDialog(wxInitDialogEvent& event) +{ + ExchangeData(EXCHANGE_DATA_IN); +} + + +//------------------------------------------------------------------------- +// ExchangeData() +//------------------------------------------------------------------------- +void PlugInDlg::ExchangeData(int inout) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + wxString str; + int i; + + if(inout == EXCHANGE_DATA_IN) + { + for (i=0; iSetValue(wxGetApp().m_txtPlugInParam[i]); + } + } + if(inout == EXCHANGE_DATA_OUT) + { + for (i=0; iGetValue(); + wxString configStr = "/" + m_name + "/" + m_paramName[i]; + pConfig->Write(configStr, wxGetApp().m_txtPlugInParam[i]); + } + pConfig->Flush(); + } + delete wxConfigBase::Set((wxConfigBase *) NULL); +} + + +//------------------------------------------------------------------------- +// OnCancel() +//------------------------------------------------------------------------- +void PlugInDlg::OnCancel(wxCommandEvent& event) +{ + this->EndModal(wxID_CANCEL); +} + +//------------------------------------------------------------------------- +// OnClose() +//------------------------------------------------------------------------- +void PlugInDlg::OnOK(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT); + this->EndModal(wxID_OK); +} diff --git a/freedv/tags/1.2.2/src/dlg_plugin.h b/freedv/tags/1.2.2/src/dlg_plugin.h new file mode 100644 index 00000000..72efc7bb --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_plugin.h @@ -0,0 +1,65 @@ +//========================================================================== +// Name: dlg_ptt.h +// Purpose: Subclasses dialog GUI for PTT Config. +// +// Created: May. 11, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __PLUGIN_DIALOG__ +#define __PLUGIN_DIALOG__ + +#include "fdmdv2_main.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlugInDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlugInDlg : public wxDialog +{ + public: + PlugInDlg(const wxString& title = _("PTT Config"), int numParam = 0, wxString paramNames[]=NULL, wxWindow* parent=NULL, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(450,300), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER); + virtual ~PlugInDlg(); + void ExchangeData(int inout); + + protected: + wxString m_name; + int m_numParam; + wxString m_paramName[PLUGIN_MAX_PARAMS]; + + wxTextCtrl* m_txtCtrlParam[PLUGIN_MAX_PARAMS]; + wxButton* m_buttonOK; + wxButton* m_buttonCancel; + + +protected: + + void OnOK(wxCommandEvent& event); + void OnCancel(wxCommandEvent& event); + virtual void OnInitDialog(wxInitDialogEvent& event); +}; + +#endif // __PLUGIN_DIALOG__ diff --git a/freedv/tags/1.2.2/src/dlg_ptt.cpp b/freedv/tags/1.2.2/src/dlg_ptt.cpp new file mode 100644 index 00000000..1a04c9c4 --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_ptt.cpp @@ -0,0 +1,570 @@ +//========================================================================== +// Name: dlg_ptt.cpp +// Purpose: Subclasses dialog GUI for PTT Config. Creates simple +// wxWidgets dialog GUI to select real/virtual Comm ports. +// Date: May 11 2012 +// Authors: David Rowe, David Witten, Joel Stanley +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include "dlg_ptt.h" +#include "fdmdv2_main.h" + +#ifdef __WIN32__ +#include +#endif +#if defined(__FreeBSD__) || defined(__WXOSX__) +#include +#include +#endif + +#include + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class ComPortsDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +ComPortsDlg::ComPortsDlg(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxDialog(parent, id, title, pos, size, style) +{ + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + this->SetSizer(mainSizer); + + //---------------------------------------------------------------------- + // Vox tone option + //---------------------------------------------------------------------- + + wxStaticBoxSizer* staticBoxSizer28 = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("VOX PTT Settings")), wxHORIZONTAL); + m_ckLeftChannelVoxTone = new wxCheckBox(this, wxID_ANY, _("Left Channel Vox Tone"), wxDefaultPosition, wxSize(-1,-1), 0); + staticBoxSizer28->Add(m_ckLeftChannelVoxTone, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); + + mainSizer->Add(staticBoxSizer28, 0, wxEXPAND, 5); + + //---------------------------------------------------------------------- + // Hamlib for CAT PTT + //---------------------------------------------------------------------- + + wxStaticBoxSizer* staticBoxSizer18 = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("Hamlib Settings")), wxHORIZONTAL); + wxGridSizer* gridSizerhl = new wxGridSizer(5, 2, 0, 0); + staticBoxSizer18->Add(gridSizerhl, 1, wxEXPAND|wxALIGN_LEFT, 5); + + /* Use Hamlib for PTT checkbox. */ + + m_ckUseHamlibPTT = new wxCheckBox(this, wxID_ANY, _("Use Hamlib PTT"), wxDefaultPosition, wxSize(-1, -1), 0); + m_ckUseHamlibPTT->SetValue(false); + gridSizerhl->Add(m_ckUseHamlibPTT, 0, wxALIGN_CENTER_VERTICAL, 0); + gridSizerhl->Add(new wxStaticText(this, -1, wxT("")), 0, wxEXPAND); + + /* Hamlib Rig Type combobox. */ + + gridSizerhl->Add(new wxStaticText(this, wxID_ANY, _("Rig Model:"), wxDefaultPosition, wxDefaultSize, 0), + 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 20); + m_cbRigName = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(250, -1), 0, NULL, wxCB_DROPDOWN); + wxGetApp().m_hamlib->populateComboBox(m_cbRigName); + m_cbRigName->SetSelection(wxGetApp().m_intHamlibRig); + gridSizerhl->Add(m_cbRigName, 0, wxALIGN_CENTER_VERTICAL, 0); + + /* Hamlib Serial Port combobox. */ + + gridSizerhl->Add(new wxStaticText(this, wxID_ANY, _("Serial Device:"), wxDefaultPosition, wxDefaultSize, 0), + 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 20); + m_cbSerialPort = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(140, -1), 0, NULL, wxCB_DROPDOWN); + gridSizerhl->Add(m_cbSerialPort, 0, wxALIGN_CENTER_VERTICAL, 0); + + /* Hamlib Serial Rate combobox. */ + + gridSizerhl->Add(new wxStaticText(this, wxID_ANY, _("Serial Rate:"), wxDefaultPosition, wxDefaultSize, 0), + 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 20); + m_cbSerialRate = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(140, -1), 0, NULL, wxCB_DROPDOWN); + gridSizerhl->Add(m_cbSerialRate, 0, wxALIGN_CENTER_VERTICAL, 0); + + gridSizerhl->Add(new wxStaticText(this, wxID_ANY, _("Serial Params:"), wxDefaultPosition, wxDefaultSize, 0), + 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 20); + m_cbSerialParams = new wxStaticText(this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, 0); + gridSizerhl->Add(m_cbSerialParams, 0, wxALIGN_CENTER_VERTICAL, 0); + + mainSizer->Add(staticBoxSizer18, 0, wxEXPAND, 5); + + //---------------------------------------------------------------------- + // Serial port PTT + //---------------------------------------------------------------------- + + wxStaticBoxSizer* staticBoxSizer17 = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("Serial Port Settings")), wxVERTICAL); + mainSizer->Add(staticBoxSizer17, 1, wxEXPAND, 5); + wxStaticBoxSizer* staticBoxSizer31 = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("PTT Port")), wxVERTICAL); + staticBoxSizer17->Add(staticBoxSizer31, 1, wxEXPAND, 5); + +#ifdef __WXMSW__ + m_ckUseSerialPTT = new wxCheckBox(this, wxID_ANY, _("Use Serial Port PTT"), wxDefaultPosition, wxSize(-1,-1), 0); + m_ckUseSerialPTT->SetValue(false); + staticBoxSizer31->Add(m_ckUseSerialPTT, 0, wxALIGN_LEFT, 20); + + wxArrayString m_listCtrlPortsArr; + m_listCtrlPorts = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(-1,45), m_listCtrlPortsArr, wxLB_SINGLE | wxLB_SORT); + staticBoxSizer31->Add(m_listCtrlPorts, 1, wxALIGN_CENTER, 0); +#endif + +#if defined(__WXOSX__) || defined(__WXGTK__) + wxBoxSizer* bSizer83; + bSizer83 = new wxBoxSizer(wxHORIZONTAL); + + wxGridSizer* gridSizer200 = new wxGridSizer(1, 3, 0, 0); + + m_ckUseSerialPTT = new wxCheckBox(this, wxID_ANY, _("Use Serial Port PTT"), wxDefaultPosition, wxSize(-1,-1), 0); + m_ckUseSerialPTT->SetValue(false); + gridSizer200->Add(m_ckUseSerialPTT, 1, wxALIGN_CENTER|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 2); + + m_staticText12 = new wxStaticText(this, wxID_ANY, _("Serial Device: "), wxDefaultPosition, wxDefaultSize, 0); + m_staticText12->Wrap(-1); + gridSizer200->Add(m_staticText12, 1,wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 2); + + m_cbCtlDevicePath = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(140, -1), 0, NULL, wxCB_DROPDOWN); + gridSizer200->Add(m_cbCtlDevicePath, 1, wxEXPAND|wxALIGN_CENTER|wxALIGN_RIGHT, 2); + + bSizer83->Add(gridSizer200, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 2); + staticBoxSizer31->Add(bSizer83, 1, wxALIGN_CENTER_VERTICAL|wxALL, 1); +#endif + + wxBoxSizer* boxSizer19 = new wxBoxSizer(wxVERTICAL); + staticBoxSizer17->Add(boxSizer19, 1, wxEXPAND, 5); + wxStaticBoxSizer* staticBoxSizer16 = new wxStaticBoxSizer( new wxStaticBox(this, wxID_ANY, _("Signal polarity")), wxHORIZONTAL); + boxSizer19->Add(staticBoxSizer16, 1, wxEXPAND|wxALIGN_CENTER|wxALIGN_RIGHT, 5); + + wxGridSizer* gridSizer17 = new wxGridSizer(2, 2, 0, 0); + staticBoxSizer16->Add(gridSizer17, 1, wxEXPAND|wxALIGN_RIGHT, 5); + + m_rbUseDTR = new wxRadioButton(this, wxID_ANY, _("Use DTR"), wxDefaultPosition, wxSize(-1,-1), 0); + m_rbUseDTR->SetToolTip(_("Toggle DTR line for PTT")); + m_rbUseDTR->SetValue(1); + gridSizer17->Add(m_rbUseDTR, 0, wxALIGN_CENTER|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5); + + m_ckDTRPos = new wxCheckBox(this, wxID_ANY, _("DTR = +V"), wxDefaultPosition, wxSize(-1,-1), 0); + m_ckDTRPos->SetToolTip(_("Set Polarity of the DTR line")); + m_ckDTRPos->SetValue(false); + gridSizer17->Add(m_ckDTRPos, 0, wxALIGN_CENTER|wxALIGN_RIGHT, 5); + + m_rbUseRTS = new wxRadioButton(this, wxID_ANY, _("Use RTS"), wxDefaultPosition, wxSize(-1,-1), 0); + m_rbUseRTS->SetToolTip(_("Toggle the RTS pin for PTT")); + m_rbUseRTS->SetValue(1); + gridSizer17->Add(m_rbUseRTS, 0, wxALIGN_CENTER|wxALIGN_RIGHT, 5); + + m_ckRTSPos = new wxCheckBox(this, wxID_ANY, _("RTS = +V"), wxDefaultPosition, wxSize(-1,-1), 0); + m_ckRTSPos->SetValue(false); + m_ckRTSPos->SetToolTip(_("Set Polarity of the RTS line")); + gridSizer17->Add(m_ckRTSPos, 0, wxALIGN_CENTER|wxALIGN_RIGHT, 5); + + //---------------------------------------------------------------------- + // OK - Cancel - Apply + //---------------------------------------------------------------------- + + wxBoxSizer* boxSizer12 = new wxBoxSizer(wxHORIZONTAL); + + m_buttonTest = new wxButton(this, wxID_APPLY, _("Test PTT"), wxDefaultPosition, wxSize(-1,-1), 0); + boxSizer12->Add(m_buttonTest, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + m_buttonOK = new wxButton(this, wxID_OK, _("OK"), wxDefaultPosition, wxSize(-1,-1), 0); + m_buttonOK->SetDefault(); + boxSizer12->Add(m_buttonOK, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + m_buttonCancel = new wxButton(this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize(-1,-1), 0); + boxSizer12->Add(m_buttonCancel, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + m_buttonApply = new wxButton(this, wxID_APPLY, _("Apply"), wxDefaultPosition, wxSize(-1,-1), 0); + boxSizer12->Add(m_buttonApply, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM, 5); + + mainSizer->Add(boxSizer12, 0, wxLEFT|wxRIGHT|wxTOP|wxBOTTOM|wxALIGN_CENTER_HORIZONTAL, 5); + + if ( GetSizer() ) + { + GetSizer()->Fit(this); + } + Centre(wxBOTH); + + // Connect events + this->Connect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(ComPortsDlg::OnInitDialog), NULL, this); + m_ckUseHamlibPTT->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(ComPortsDlg::PTTUseHamLibClicked), NULL, this); + m_ckUseSerialPTT->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(ComPortsDlg::PTTUseSerialClicked), NULL, this); + m_buttonOK->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnOK), NULL, this); + m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnCancel), NULL, this); + m_buttonApply->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnApply), NULL, this); + m_buttonTest->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnTest), NULL, this); +} + +//------------------------------------------------------------------------- +// ~ComPortsDlg() +//------------------------------------------------------------------------- +ComPortsDlg::~ComPortsDlg() +{ + // Disconnect Events + this->Disconnect(wxEVT_INIT_DIALOG, wxInitDialogEventHandler(ComPortsDlg::OnInitDialog), NULL, this); + m_ckUseHamlibPTT->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(ComPortsDlg::PTTUseHamLibClicked), NULL, this); + m_ckUseSerialPTT->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(ComPortsDlg::PTTUseSerialClicked), NULL, this); + m_buttonOK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnOK), NULL, this); + m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnCancel), NULL, this); + m_buttonApply->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnApply), NULL, this); + m_buttonTest->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ComPortsDlg::OnTest), NULL, this); +} + +//------------------------------------------------------------------------- +// OnInitDialog() +//------------------------------------------------------------------------- +void ComPortsDlg::OnInitDialog(wxInitDialogEvent& event) +{ + populatePortList(); + ExchangeData(EXCHANGE_DATA_IN); +} + +//------------------------------------------------------------------------- +// populatePortList() +//------------------------------------------------------------------------- +void ComPortsDlg::populatePortList() +{ + + /* populate Hamlib serial rate combo box */ + + wxString serialRates[] = {"default", "300", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"}; + for(int i=0; iAppend(serialRates[i]); + } + +#ifdef __WXMSW__ + m_listCtrlPorts->Clear(); + m_cbSerialPort->Clear(); + wxArrayString aStr; + wxRegKey key(wxRegKey::HKLM, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM")); + if(!key.Exists()) + { + return; + } + else + { + // Get the number of subkeys and enumerate them. + if(!key.Open(wxRegKey::Read)) + { + return; + } + size_t subkeys; + size_t values; + if(!key.GetKeyInfo(&subkeys, NULL, &values, NULL)) + { + return; + } + if(!key.HasValues()) + { + return; + } + wxString key_name; + long el = 1; + key.GetFirstValue(key_name, el); + wxString valType; + wxString key_data; + for(unsigned int i = 0; i < values; i++) + { + key.QueryValue(key_name, key_data); + //wxPrintf("Value: %s Data: %s\n", key_name, key_data); + aStr.Add(key_data, 1); + key.GetNextValue(key_name, el); + } + } + m_listCtrlPorts->Append(aStr); + m_cbSerialPort->Append(aStr); +#endif +#if defined(__WXGTK__) || defined(__WXOSX__) + m_cbSerialPort->Clear(); + m_cbCtlDevicePath->Clear(); +#if defined(__FreeBSD__) || defined(__WXOSX__) + glob_t gl; +#ifdef __FreeBSD__ + if(glob("/dev/tty*", GLOB_MARK, NULL, &gl)==0) { +#else + if(glob("/dev/tty.*", GLOB_MARK, NULL, &gl)==0) { +#endif + for(unsigned int i=0; i= 'l' && gl.gl_pathv[i][8] <= 's') + continue; + if(gl.gl_pathv[i][8] >= 'L' && gl.gl_pathv[i][8] <= 'S') + continue; + + /* Exclude virtual TTYs */ + if(gl.gl_pathv[i][8] == 'v') + continue; + + /* Exclude initial-state and lock-state devices */ +#ifndef __WXOSX__ + if(strchr(gl.gl_pathv[i], '.') != NULL) + continue; +#endif + + m_cbSerialPort->Append(gl.gl_pathv[i]); + m_cbCtlDevicePath->Append(gl.gl_pathv[i]); + } + globfree(&gl); + } +#else + /* TODO(Joel): http://stackoverflow.com/questions/2530096/how-to-find-all-serial-devices-ttys-ttyusb-on-linux-without-opening-them */ + m_cbSerialPort->Append("/dev/ttyUSB0"); + m_cbSerialPort->Append("/dev/ttyUSB1"); + m_cbSerialPort->Append("/dev/ttyS0"); + m_cbSerialPort->Append("/dev/ttyS1"); + + m_cbCtlDevicePath->Clear(); + m_cbCtlDevicePath->Append("/dev/ttyUSB0"); + m_cbCtlDevicePath->Append("/dev/ttyUSB1"); + m_cbCtlDevicePath->Append("/dev/ttyS0"); + m_cbCtlDevicePath->Append("/dev/ttyS1"); +#endif +#endif + + +} + +//------------------------------------------------------------------------- +// ExchangeData() +//------------------------------------------------------------------------- +void ComPortsDlg::ExchangeData(int inout) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + wxString str; + + if(inout == EXCHANGE_DATA_IN) { + m_ckLeftChannelVoxTone->SetValue(wxGetApp().m_leftChannelVoxTone); + + /* Hamlib */ + + m_ckUseHamlibPTT->SetValue(wxGetApp().m_boolHamlibUseForPTT); + m_cbRigName->SetSelection(wxGetApp().m_intHamlibRig); + m_cbSerialPort->SetValue(wxGetApp().m_strHamlibSerialPort); + + if (wxGetApp().m_intHamlibSerialRate == 0) { + m_cbSerialRate->SetSelection(0); + } else { + m_cbSerialRate->SetValue(wxString::Format(wxT("%i"), wxGetApp().m_intHamlibSerialRate)); + } + + /* Serial PTT */ + + m_ckUseSerialPTT->SetValue(wxGetApp().m_boolUseSerialPTT); + str = wxGetApp().m_strRigCtrlPort; +#ifdef __WXMSW__ + m_listCtrlPorts->SetStringSelection(str); +#endif +#if defined(__WXOSX__) || defined(__WXGTK__) + m_cbCtlDevicePath->SetValue(str); +#endif + m_rbUseRTS->SetValue(wxGetApp().m_boolUseRTS); + m_ckRTSPos->SetValue(wxGetApp().m_boolRTSPos); + m_rbUseDTR->SetValue(wxGetApp().m_boolUseDTR); + m_ckDTRPos->SetValue(wxGetApp().m_boolDTRPos); + } + + if (inout == EXCHANGE_DATA_OUT) { + wxGetApp().m_leftChannelVoxTone = m_ckLeftChannelVoxTone->GetValue(); + pConfig->Write(wxT("/Rig/leftChannelVoxTone"), wxGetApp().m_leftChannelVoxTone); + + /* Hamlib settings. */ + + wxGetApp().m_boolHamlibUseForPTT = m_ckUseHamlibPTT->GetValue(); + wxGetApp().m_intHamlibRig = m_cbRigName->GetSelection(); + wxGetApp().m_strHamlibSerialPort = m_cbSerialPort->GetValue(); + + wxString s = m_cbSerialRate->GetValue(); + if (s == "default") { + wxGetApp().m_intHamlibSerialRate = 0; + } else { + long tmp; + m_cbSerialRate->GetValue().ToLong(&tmp); + wxGetApp().m_intHamlibSerialRate = tmp; + } + fprintf(stderr, "serial rate: %d\n", wxGetApp().m_intHamlibSerialRate); + + pConfig->Write(wxT("/Hamlib/UseForPTT"), wxGetApp().m_boolHamlibUseForPTT); + pConfig->Write(wxT("/Hamlib/RigName"), wxGetApp().m_intHamlibRig); + pConfig->Write(wxT("/Hamlib/SerialPort"), wxGetApp().m_strHamlibSerialPort); + pConfig->Write(wxT("/Hamlib/SerialRate"), wxGetApp().m_intHamlibSerialRate); + + /* Serial settings */ + + wxGetApp().m_boolUseSerialPTT = m_ckUseSerialPTT->IsChecked(); +#ifdef __WXMSW__ + wxGetApp().m_strRigCtrlPort = m_listCtrlPorts->GetStringSelection(); +#endif +#if defined(__WXGTK__) || defined(__WXOSX__) + wxGetApp().m_strRigCtrlPort = m_cbCtlDevicePath->GetValue(); +#endif + wxGetApp().m_boolUseRTS = m_rbUseRTS->GetValue(); + wxGetApp().m_boolRTSPos = m_ckRTSPos->IsChecked(); + wxGetApp().m_boolUseDTR = m_rbUseDTR->GetValue(); + wxGetApp().m_boolDTRPos = m_ckDTRPos->IsChecked(); + + pConfig->Write(wxT("/Rig/UseSerialPTT"), wxGetApp().m_boolUseSerialPTT); + pConfig->Write(wxT("/Rig/Port"), wxGetApp().m_strRigCtrlPort); + pConfig->Write(wxT("/Rig/UseRTS"), wxGetApp().m_boolUseRTS); + pConfig->Write(wxT("/Rig/RTSPolarity"), wxGetApp().m_boolRTSPos); + pConfig->Write(wxT("/Rig/UseDTR"), wxGetApp().m_boolUseDTR); + pConfig->Write(wxT("/Rig/DTRPolarity"), wxGetApp().m_boolDTRPos); + + pConfig->Flush(); + } + delete wxConfigBase::Set((wxConfigBase *) NULL); +} + +//------------------------------------------------------------------------- +// PTTUseHamLibClicked() +//------------------------------------------------------------------------- +void ComPortsDlg::PTTUseHamLibClicked(wxCommandEvent& event) +{ + m_ckUseSerialPTT->SetValue(false); +} + + +/* Attempt to toggle PTT for 1 second */ + +void ComPortsDlg::OnTest(wxCommandEvent& event) { + + /* Tone PTT */ + + if (m_ckLeftChannelVoxTone->GetValue()) { + wxMessageBox("Testing of tone based PTT not supported; try PTT after pressing Start on main window", + wxT("Error"), wxOK | wxICON_ERROR, this); + } + + /* Hamlib PTT */ + + if (m_ckUseHamlibPTT->GetValue()) { + + // set up current hamlib config from GUI + + int rig = m_cbRigName->GetSelection(); + wxString port = m_cbSerialPort->GetValue(); + wxString s = m_cbSerialRate->GetValue(); + int serial_rate; + if (s == "default") { + serial_rate = 0; + } else { + long tmp; + m_cbSerialRate->GetValue().ToLong(&tmp); + serial_rate = tmp; + } + + // display serial params + + fprintf(stderr, "serial rate: %d\n", serial_rate); + + // try to open rig + + Hamlib *hamlib = wxGetApp().m_hamlib; + bool status = hamlib->connect(rig, port.mb_str(wxConvUTF8), serial_rate); + if (status == false) { + wxMessageBox("Couldn't connect to Radio with hamlib", wxT("Error"), wxOK | wxICON_ERROR, this); + return; + } + else { + wxString hamlib_serial_config; + hamlib_serial_config.sprintf(" %d, %d, %d", + hamlib->get_serial_rate(), + hamlib->get_data_bits(), + hamlib->get_stop_bits()); + m_cbSerialParams->SetLabel(hamlib_serial_config); + } + + // toggle PTT + + wxString hamlibError; + if (hamlib->ptt(true, hamlibError) == false) { + wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this); + return; + } + + wxSleep(1); + + if (hamlib->ptt(false, hamlibError) == false) { + wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this); + } + } + + /* Serial PTT */ + + if (m_ckUseSerialPTT->IsChecked()) { + Serialport *serialport = wxGetApp().m_serialport; + + wxString ctrlport; +#ifdef __WXMSW__ + ctrlport = m_listCtrlPorts->GetStringSelection(); +#endif +#if defined(__WXGTK__) || defined(__WXOSX__) + ctrlport = m_cbCtlDevicePath->GetValue(); +#endif + fprintf(stderr, "opening serial port\n"); + + bool success = serialport->openport(ctrlport.c_str(), + m_rbUseRTS->GetValue(), + m_ckRTSPos->IsChecked(), + m_rbUseDTR->GetValue(), + m_ckDTRPos->IsChecked()); + + fprintf(stderr, "serial port open\n"); + + if (!success) { + wxMessageBox("Couldn't open serial port", wxT("Error"), wxOK | wxICON_ERROR, this); + } + + // assert PTT port for 1 sec + + serialport->ptt(true); + wxSleep(1); + serialport->ptt(false); + + fprintf(stderr, "closing serial port\n"); + serialport->closeport(); + fprintf(stderr, "serial port closed\n"); + } + +} + + +//------------------------------------------------------------------------- +// PTTUseSerialClicked() +//------------------------------------------------------------------------- +void ComPortsDlg::PTTUseSerialClicked(wxCommandEvent& event) +{ + m_ckUseHamlibPTT->SetValue(false); +} + +//------------------------------------------------------------------------- +// OnApply() +//------------------------------------------------------------------------- +void ComPortsDlg::OnApply(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT); +} + +//------------------------------------------------------------------------- +// OnCancel() +//------------------------------------------------------------------------- +void ComPortsDlg::OnCancel(wxCommandEvent& event) +{ + this->EndModal(wxID_CANCEL); +} + +//------------------------------------------------------------------------- +// OnClose() +//------------------------------------------------------------------------- +void ComPortsDlg::OnOK(wxCommandEvent& event) +{ + ExchangeData(EXCHANGE_DATA_OUT); + this->EndModal(wxID_OK); +} diff --git a/freedv/tags/1.2.2/src/dlg_ptt.h b/freedv/tags/1.2.2/src/dlg_ptt.h new file mode 100644 index 00000000..5a35c3bd --- /dev/null +++ b/freedv/tags/1.2.2/src/dlg_ptt.h @@ -0,0 +1,95 @@ +//========================================================================== +// Name: dlg_ptt.h +// Purpose: Subclasses dialog GUI for PTT Config. +// +// Created: May. 11, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __COMPORTS_DIALOG__ +#define __COMPORTS_DIALOG__ + +#include "fdmdv2_main.h" +#include "hamlib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class ComPortsDlg +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class ComPortsDlg : public wxDialog +{ + public: + ComPortsDlg(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("PTT Config"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(450,300), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER); + virtual ~ComPortsDlg(); + void ExchangeData(int inout); + + protected: + wxCheckBox* m_ckLeftChannelVoxTone; + + /* Hamlib settings.*/ + + wxCheckBox *m_ckUseHamlibPTT; + wxComboBox *m_cbRigName; + wxComboBox *m_cbSerialPort; + wxComboBox *m_cbSerialRate; + wxStaticText *m_cbSerialParams; + Hamlib *m_hamlib; + + /* Serial Settings */ + + wxListBox *m_listCtrlPorts; + wxCheckBox *m_ckUseSerialPTT; + wxStaticText *m_staticText12; + wxComboBox *m_cbCtlDevicePath; + wxRadioButton *m_rbUseDTR; + wxCheckBox *m_ckRTSPos; + wxRadioButton *m_rbUseRTS; + wxCheckBox *m_ckDTRPos; + + /* Test - Ok - Cancel - Apply */ + + wxButton* m_buttonTest; + wxButton* m_buttonOK; + wxButton* m_buttonCancel; + wxButton* m_buttonApply; + + +protected: + void populatePortList(); + + void PTTUseHamLibClicked(wxCommandEvent& event); + void PTTUseSerialClicked(wxCommandEvent& event); + + void OnTest(wxCommandEvent& event); + + void OnOK(wxCommandEvent& event); + void OnCancel(wxCommandEvent& event); + void OnApply(wxCommandEvent& event); + virtual void OnInitDialog(wxInitDialogEvent& event); + +}; + +#endif // __COMPORTS_DIALOG__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_defines.h b/freedv/tags/1.2.2/src/fdmdv2_defines.h new file mode 100644 index 00000000..a6878890 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_defines.h @@ -0,0 +1,106 @@ +//========================================================================== +// Name: fdmdv2_defines.h +// Purpose: Definitions used by plots derived from fdmdv2_plot class. +// Created: August 27, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __FDMDV2_DEFINES__ +#define __FDMDV2_DEFINES__ + +#include "wx/wx.h" +#include "freedv_api.h" +#include "modem_stats.h" + +// Spectrogram and Waterfall + +#define MIN_MAG_DB -40.0 // min of spectrogram/waterfall magnitude axis +#define MAX_MAG_DB 0.0 // max of spectrogram/waterfall magnitude axis +#define STEP_MAG_DB 5.0 // magnitude axis step +#define BETA 0.95 // constant for time averaging spectrum data +#define MIN_F_HZ 0 // min freq on Waterfall and Spectrum +#define MAX_F_HZ 3000 // max freq on Waterfall and Spectrum +#define STEP_F_HZ 500 // major (e.g. text legend) freq step on Waterfall and Spectrum graticule +#define STEP_MINOR_F_HZ 100 // minor (ticks) freq step on Waterfall and Spectrum graticule +#define WATERFALL_SECS_Y 30 // number of seconds respresented by y axis of waterfall +#define WATERFALL_SECS_STEP 5 // graticule y axis steps of waterfall +#define DT 0.1 // time between real time graphing updates +#define FS 8000 // FDMDV modem sample rate + +// Scatter diagram + +#define SCATTER_MEM_SECS 10 +// (symbols/frame)/(graphics update period) = symbols/s sent to scatter memory +// memory (symbols) = secs of memory * symbols/sec +#define SCATTER_MEM_SYMS_MAX ((int)(SCATTER_MEM_SECS*((MODEM_STATS_NC_MAX+1)/DT))) +#define SCATTER_EYE_MEM_ROWS ((int)(SCATTER_MEM_SECS/DT)) + +// Waveform plotting constants + +#define WAVEFORM_PLOT_FS 400 // sample rate (points/s) of waveform plotted to screen +#define WAVEFORM_PLOT_TIME 5 // length or entire waveform on screen +#define WAVEFORM_PLOT_BUF ((int)(DT*WAVEFORM_PLOT_FS)) // number of new samples we plot per DT + +// sample rate I/O & conversion constants + +#define MAX_FPB 8096 // maximum value of portAudio framesPerBuffer +#define PA_FPB 1024 // nominal value of portAudio framesPerBuffer +#define SAMPLE_RATE 48000 // 48 kHz sampling rate rec. as we can trust accuracy of sound card +#define N8 160 // processing buffer size at 8 kHz +#define MEM8 (FDMDV_OS_TAPS/FDMDV_OS) +#define N48 (N8*SAMPLE_RATE/FS) // processing buffer size at 48 kHz +#define NUM_CHANNELS 2 // I think most sound cards prefer stereo we will convert to mono +#define VOX_TONE_FREQ 1000.0 // optional left channel vox tone freq +#define VOX_TONE_AMP 30000 // optional left channel vox tone amp + +#define MAX_BITS_PER_CODEC_FRAME 64 // 1600 bit/s mode +#define MAX_BYTES_PER_CODEC_FRAME (MAX_BITS_PER_CODEC_FRAME/8) +#define MAX_BITS_PER_FDMDV_FRAME 40 // 2000 bit/s mode + +// Squelch +#define SQ_DEFAULT_SNR 2.0 + +// Level Gauge +#define FROM_RADIO_MAX 0.8 +#define FROM_MIC_MAX 0.8 +#define LEVEL_BETA 0.99 + +// SNR +#define SNRSLOW_BETA 0.5 // time constant for slow SNR for display + +// Text messaging Data +#define MAX_CALLSIGN 80 +#define MAX_EVENT_LOG 10 +#define MAX_EVENT_RULES 100 + +enum +{ + ID_ROTATE_LEFT = wxID_HIGHEST + 1, + ID_ROTATE_RIGHT, + ID_RESIZE, + ID_PAINT_BG +}; + +// Codec 2 LPC Post Filter defaults, from codec-dev/src/quantise.c + +#define CODEC2_LPC_PF_GAMMA 0.5 +#define CODEC2_LPC_PF_BETA 0.2 + +// PlugIns ... + +#define PLUGIN_MAX_PARAMS 4 + +#endif //__FDMDV2_DEFINES__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_main.cpp b/freedv/tags/1.2.2/src/fdmdv2_main.cpp new file mode 100644 index 00000000..506406c1 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_main.cpp @@ -0,0 +1,4042 @@ +//========================================================================== +// Name: fdmdv2_main.cpp +// +// Purpose: FreeDV main() +// Created: Apr. 9, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== + +#include "fdmdv2_main.h" + +#define wxUSE_FILEDLG 1 +#define wxUSE_LIBPNG 1 +#define wxUSE_LIBJPEG 1 +#define wxUSE_GIF 1 +#define wxUSE_PCX 1 +#define wxUSE_LIBTIFF 1 + +//------------------------------------------------------------------- +// Bunch of globals used for communication with sound card call +// back functions +// ------------------------------------------------------------------ + +int g_in, g_out; + +// Global Codec2 & modem states - just one reqd for tx & rx +int g_Nc; +int g_mode; +struct freedv *g_pfreedv; +struct MODEM_STATS g_stats; +float g_pwr_scale; +int g_clip; + +// test Frames +int g_testFrames; +int g_test_frame_sync_state; +int g_test_frame_count; +int g_channel_noise; +int g_resyncs; +float g_sig_pwr_av = 0.0; +struct FIFO *g_error_pattern_fifo; +short *g_error_hist, *g_error_histn; +float g_tone_phase; + +// time averaged magnitude spectrum used for waterfall and spectrum display +float g_avmag[MODEM_STATS_NSPEC]; + +// GUI controls that affect rx and tx processes +int g_SquelchActive; +float g_SquelchLevel; +int g_analog; +int g_split; +int g_tx; +float g_snr; +bool g_half_duplex; +bool g_modal; + +// sending and receiving Call Sign data +struct FIFO *g_txDataInFifo; +struct FIFO *g_rxDataOutFifo; + +// tx/rx processing states +int g_State, g_prev_State; +paCallBackData *g_rxUserdata; + +// FIFOs used for plotting waveforms +struct FIFO *g_plotDemodInFifo; +struct FIFO *g_plotSpeechOutFifo; +struct FIFO *g_plotSpeechInFifo; + +// Soundcard config +int g_nSoundCards; +int g_soundCard1InDeviceNum; +int g_soundCard1OutDeviceNum; +int g_soundCard1SampleRate; +int g_soundCard2InDeviceNum; +int g_soundCard2OutDeviceNum; +int g_soundCard2SampleRate; + +// playing and recording from sound files + +SNDFILE *g_sfPlayFile; +bool g_playFileToMicIn; +bool g_loopPlayFileToMicIn; +int g_playFileToMicInEventId; + +SNDFILE *g_sfRecFile; +bool g_recFileFromRadio; +unsigned int g_recFromRadioSamples; +int g_recFileFromRadioEventId; + +SNDFILE *g_sfPlayFileFromRadio; +bool g_playFileFromRadio; +int g_sfFs; +bool g_loopPlayFileFromRadio; +int g_playFileFromRadioEventId; +float g_blink; + +wxWindow *g_parent; + +// Click to tune rx and tx frequency offset states +float g_RxFreqOffsetHz; +COMP g_RxFreqOffsetPhaseRect; +float g_TxFreqOffsetHz; +COMP g_TxFreqOffsetPhaseRect; + +// experimental mutex to make sound card callbacks mutually exclusive +// TODO: review code and see if we need this any more, as fifos should +// now be thread safe + +wxMutex g_mutexProtectingCallbackData; + +// Speex pre-processor states + +SpeexPreprocessState *g_speex_st; + +// WxWidgets - initialize the application +IMPLEMENT_APP(MainApp); + +//FILE *ftest; +FILE *g_logfile; + +//------------------------------------------------------------------------- +// OnInit() +//------------------------------------------------------------------------- +bool MainApp::OnInit() +{ + if(!wxApp::OnInit()) + { + return false; + } + SetVendorName(wxT("CODEC2-Project")); + SetAppName(wxT("FreeDV")); // not needed, it's the default value + +#ifdef FILE_RATHER_THAN_REGISTRY + // Force use of file-based configuration persistance on Windows platforma + wxConfig *pConfig = new wxConfig(); + wxFileConfig *pFConfig = new wxFileConfig(wxT("FreeDV"), wxT("CODEC2-Project"), wxT("FreeDV.conf"), wxT("FreeDV.conf"), wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_RELATIVE_PATH); + pConfig->Set(pFConfig); + pConfig->SetRecordDefaults(); +#else + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->SetRecordDefaults(); +#endif + + m_rTopWindow = wxRect(0, 0, 0, 0); + m_strRxInAudio.Empty(); + m_strRxOutAudio.Empty(); + m_textVoiceInput.Empty(); + m_textVoiceOutput.Empty(); + m_strSampleRate.Empty(); + m_strBitrate.Empty(); + + // Look for Plug In + + m_plugIn = false; + #ifdef __WXMSW__ + wchar_t dll_path[] = L"afreedvplugin.dll"; + m_plugInHandle = LoadLibrary(dll_path); + #else + m_plugInHandle = dlopen("afreedvplugin.so", RTLD_LAZY); + #endif + + if (m_plugInHandle) { + printf("plugin: .so found\n"); + + // lets get some information abt the plugIn + + void (*plugin_namefp)(char s[]); + void *(*plugin_openfp)(char *param_names[], int *nparams, int (*aplugin_get_persistant)(char *, char *)); + + #ifdef __WXMSW__ + plugin_namefp = (void (*)(char*))GetProcAddress((HMODULE)m_plugInHandle, "plugin_name"); + plugin_openfp = (void* (*)(char**,int *, int (*)(char *, char *)))GetProcAddress((HMODULE)m_plugInHandle, "plugin_open"); + m_plugin_startfp = (void (*)(void *))GetProcAddress((HMODULE)m_plugInHandle, "plugin_start"); + m_plugin_stopfp = (void (*)(void *))GetProcAddress((HMODULE)m_plugInHandle, "plugin_stop"); + m_plugin_rx_samplesfp = (void (*)(void *, short *, int))GetProcAddress((HMODULE)m_plugInHandle, "plugin_rx_samples"); + #else + plugin_namefp = (void (*)(char*))dlsym(m_plugInHandle, "plugin_name"); + plugin_openfp = (void* (*)(char**,int *, int (*)(char *, char *)))dlsym(m_plugInHandle, "plugin_open"); + m_plugin_startfp = (void (*)(void *))dlsym(m_plugInHandle, "plugin_start"); + m_plugin_stopfp = (void (*)(void *))dlsym(m_plugInHandle, "plugin_stop"); + m_plugin_rx_samplesfp = (void (*)(void *, short *, int))dlsym(m_plugInHandle, "plugin_rx_samples"); + #endif + + if ((plugin_namefp != NULL) && (plugin_openfp != NULL)) { + + char s[256]; + m_plugIn = true; + (plugin_namefp)(s); + fprintf(stderr, "plugin name: %s\n", s); + m_plugInName = s; + + char param_name1[80], param_name2[80]; + char *param_names[2] = {param_name1, param_name2}; + int nparams, i; + m_plugInStates = (plugin_openfp)(param_names, &nparams, plugin_get_persistant); + m_numPlugInParam = nparams; + for(i=0; iRead(configStr, wxT("")); + //fprintf(stderr, " plugin param name[%d]: %s\n", i, param_names[i]); + fprintf(stderr, " plugin param name[%d]: %s values: %s\n", i, m_plugInParamName[i].mb_str().data(), m_txtPlugInParam[i].mb_str().data()); + } + } + + else { + fprintf(stderr, "plugin: fps not found...\n"); + } + } + else { + fprintf(stderr, "plugin not found...\n"); + } + + // Create the main application window + + frame = new MainFrame(m_plugInName, NULL); + SetTopWindow(frame); + + // Should guarantee that the first plot tab defined is the one + // displayed. But it doesn't when built from command line. Why? + + frame->m_auiNbookCtrl->ChangeSelection(0); + frame->Layout(); + frame->Show(); + g_parent =frame; + + + return true; +} + +//------------------------------------------------------------------------- +// OnExit() +//------------------------------------------------------------------------- +int MainApp::OnExit() +{ + fprintf(stderr, "MainApp::OnExit\n"); + if (m_plugIn) { + #ifdef __WXMSW__ + FreeLibrary((HMODULE)m_plugInHandle); + #else + dlclose(m_plugInHandle); + #endif + } + + return 0; +} + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class MainFrame(wxFrame* pa->ent) : TopFrame(parent) +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +MainFrame::MainFrame(wxString plugInName, wxWindow *parent) : TopFrame(plugInName, parent) +{ + m_zoom = 1.; + + #ifdef __WXMSW__ + g_logfile = stderr; + #else + g_logfile = stderr; + #endif + + + SetMinSize(wxSize(400,400)); + + // Init Hamlib library, but we dont start talking to any rigs yet + + wxGetApp().m_hamlib = new Hamlib(); + + // Init Serialport library, but as for Hamlib we dont start talking to any rigs yet + + wxGetApp().m_serialport = new Serialport(); + + tools->AppendSeparator(); + wxMenuItem* m_menuItemToolsConfigDelete; + m_menuItemToolsConfigDelete = new wxMenuItem(tools, wxID_ANY, wxString(_("&Restore defaults")) , wxT("Delete config file/keys and restore defaults"), wxITEM_NORMAL); + this->Connect(m_menuItemToolsConfigDelete->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainFrame::OnDeleteConfig)); + + tools->Append(m_menuItemToolsConfigDelete); + + wxConfigBase *pConfig = wxConfigBase::Get(); + + // restore frame position and size + int x = pConfig->Read(wxT("/MainFrame/left"), 20); + int y = pConfig->Read(wxT("/MainFrame/top"), 20); + int w = pConfig->Read(wxT("/MainFrame/width"), 800); + int h = pConfig->Read(wxT("/MainFrame/height"), 550); + + // sanitise frame position as a first pass at Win32 registry bug + + fprintf(g_logfile, "x = %d y = %d w = %d h = %d\n", x,y,w,h); + if (x < 0 || x > 2048) x = 20; + if (y < 0 || y > 2048) y = 20; + if (w < 0 || w > 2048) w = 800; + if (h < 0 || h > 2048) h = 550; + + wxGetApp().m_show_wf = pConfig->Read(wxT("/MainFrame/show_wf"), 1); + wxGetApp().m_show_spect = pConfig->Read(wxT("/MainFrame/show_spect"), 1); + wxGetApp().m_show_scatter = pConfig->Read(wxT("/MainFrame/show_scatter"), 1); + wxGetApp().m_show_timing = pConfig->Read(wxT("/MainFrame/show_timing"), 1); + wxGetApp().m_show_freq = pConfig->Read(wxT("/MainFrame/show_freq"), 1); + wxGetApp().m_show_speech_in = pConfig->Read(wxT("/MainFrame/show_speech_in"), 1); + wxGetApp().m_show_speech_out = pConfig->Read(wxT("/MainFrame/show_speech_out"), 1); + wxGetApp().m_show_demod_in = pConfig->Read(wxT("/MainFrame/show_demod_in"), 1); + wxGetApp().m_show_test_frame_errors = pConfig->Read(wxT("/MainFrame/show_test_frame_errors"), 1); + wxGetApp().m_show_test_frame_errors_hist = pConfig->Read(wxT("/MainFrame/show_test_frame_errors_hist"), 1); + + wxGetApp().m_rxNbookCtrl = pConfig->Read(wxT("/MainFrame/rxNbookCtrl"), (long)0); + + g_SquelchActive = pConfig->Read(wxT("/Audio/SquelchActive"), (long)0); + g_SquelchLevel = pConfig->Read(wxT("/Audio/SquelchLevel"), (int)(SQ_DEFAULT_SNR*2)); + g_SquelchLevel /= 2.0; + + Move(x, y); + SetClientSize(w, h); + + if(wxGetApp().m_show_wf) + { + // Add Waterfall Plot window + m_panelWaterfall = new PlotWaterfall((wxFrame*) m_auiNbookCtrl, false, 0); + m_panelWaterfall->SetToolTip(_("Left click to tune, Right click to toggle mono/colour")); + m_auiNbookCtrl->AddPage(m_panelWaterfall, _("Waterfall"), true, wxNullBitmap); + } + if(wxGetApp().m_show_spect) + { + // Add Spectrum Plot window + m_panelSpectrum = new PlotSpectrum((wxFrame*) m_auiNbookCtrl, g_avmag, + MODEM_STATS_NSPEC*((float)MAX_F_HZ/MODEM_STATS_MAX_F_HZ)); + m_panelSpectrum->SetToolTip(_("Left click to tune")); + m_auiNbookCtrl->AddPage(m_panelSpectrum, _("Spectrum"), true, wxNullBitmap); + } + if(wxGetApp().m_show_scatter) + { + // Add Scatter Plot window + m_panelScatter = new PlotScatter((wxFrame*) m_auiNbookCtrl); + m_auiNbookCtrl->AddPage(m_panelScatter, _("Scatter"), true, wxNullBitmap); + } + if(wxGetApp().m_show_demod_in) + { + // Add Demod Input window + m_panelDemodIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0); + m_auiNbookCtrl->AddPage(m_panelDemodIn, _("Frm Radio"), true, wxNullBitmap); + g_plotDemodInFifo = fifo_create(2*WAVEFORM_PLOT_BUF); + } + + if(wxGetApp().m_show_speech_in) + { + // Add Speech Input window + m_panelSpeechIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0); + m_auiNbookCtrl->AddPage(m_panelSpeechIn, _("Frm Mic"), true, wxNullBitmap); + g_plotSpeechInFifo = fifo_create(4*WAVEFORM_PLOT_BUF); + } + + if(wxGetApp().m_show_speech_out) + { + // Add Speech Output window + m_panelSpeechOut = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0); + m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("To Spkr/Hdphns"), true, wxNullBitmap); + g_plotSpeechOutFifo = fifo_create(2*WAVEFORM_PLOT_BUF); + } + + if(wxGetApp().m_show_timing) + { + // Add Timing Offset window + m_panelTimeOffset = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 5.0, DT, -0.5, 0.5, 1, 0.1, "%2.1f", 0); + m_auiNbookCtrl->AddPage(m_panelTimeOffset, L"Timing \u0394", true, wxNullBitmap); + } + if(wxGetApp().m_show_freq) + { + // Add Frequency Offset window + m_panelFreqOffset = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 5.0, DT, -200, 200, 1, 50, "%3.0fHz", 0); + m_auiNbookCtrl->AddPage(m_panelFreqOffset, L"Frequency \u0394", true, wxNullBitmap); + } + + if(wxGetApp().m_show_test_frame_errors) + { + // Add Test Frame Errors window + m_panelTestFrameErrors = new PlotScalar((wxFrame*) m_auiNbookCtrl, 2*MODEM_STATS_NC_MAX, 30.0, DT, 0, 2*MODEM_STATS_NC_MAX+2, 1, 1, "", 1); + m_auiNbookCtrl->AddPage(m_panelTestFrameErrors, L"Test Frame Errors", true, wxNullBitmap); + } + + if(wxGetApp().m_show_test_frame_errors_hist) + { + // Add Test Frame Historgram window. 1 column for every bit, 2 bits per carrier + m_panelTestFrameErrorsHist = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 1.0, 1.0/(2*FDMDV_NC_MAX), 0.001, 0.1, 1.0/FDMDV_NC_MAX, 0.1, "%0.0E", 0); + m_auiNbookCtrl->AddPage(m_panelTestFrameErrorsHist, L"Test Frame Histogram", true, wxNullBitmap); + m_panelTestFrameErrorsHist->setBarGraph(1); + m_panelTestFrameErrorsHist->setLogY(1); + } + + wxGetApp().m_framesPerBuffer = pConfig->Read(wxT("/Audio/framesPerBuffer"), PA_FPB); + + g_soundCard1InDeviceNum = pConfig->Read(wxT("/Audio/soundCard1InDeviceNum"), -1); + g_soundCard1OutDeviceNum = pConfig->Read(wxT("/Audio/soundCard1OutDeviceNum"), -1); + g_soundCard1SampleRate = pConfig->Read(wxT("/Audio/soundCard1SampleRate"), -1); + + g_soundCard2InDeviceNum = pConfig->Read(wxT("/Audio/soundCard2InDeviceNum"), -1); + g_soundCard2OutDeviceNum = pConfig->Read(wxT("/Audio/soundCard2OutDeviceNum"), -1); + g_soundCard2SampleRate = pConfig->Read(wxT("/Audio/soundCard2SampleRate"), -1); + + g_nSoundCards = 0; + if ((g_soundCard1InDeviceNum > -1) && (g_soundCard1OutDeviceNum > -1)) { + g_nSoundCards = 1; + if ((g_soundCard2InDeviceNum > -1) && (g_soundCard2OutDeviceNum > -1)) + g_nSoundCards = 2; + } + + wxGetApp().m_playFileToMicInPath = pConfig->Read("/File/playFileToMicInPath", wxT("")); + wxGetApp().m_recFileFromRadioPath = pConfig->Read("/File/recFileFromRadioPath", wxT("")); + wxGetApp().m_recFileFromRadioSecs = pConfig->Read("/File/recFileFromRadioSecs", 30); + wxGetApp().m_playFileFromRadioPath = pConfig->Read("/File/playFileFromRadioPath", wxT("")); + + // PTT ------------------------------------------------------------------- + + wxGetApp().m_boolHalfDuplex = pConfig->ReadBool(wxT("/Rig/HalfDuplex"), true); + wxGetApp().m_leftChannelVoxTone = pConfig->ReadBool("/Rig/leftChannelVoxTone", false); + + wxGetApp().m_txtVoiceKeyerWaveFilePath = pConfig->Read(wxT("/VoiceKeyer/WaveFilePath"), wxT("")); + wxGetApp().m_txtVoiceKeyerWaveFile = pConfig->Read(wxT("/VoiceKeyer/WaveFile"), wxT("voicekeyer.wav")); + wxGetApp().m_intVoiceKeyerRxPause = pConfig->Read(wxT("/VoiceKeyer/RxPause"), 10); + wxGetApp().m_intVoiceKeyerRepeats = pConfig->Read(wxT("/VoiceKeyer/Repeats"), 5); + + wxGetApp().m_boolHamlibUseForPTT = pConfig->ReadBool("/Hamlib/UseForPTT", false); + wxGetApp().m_intHamlibRig = pConfig->ReadLong("/Hamlib/RigName", 0); + wxGetApp().m_strHamlibSerialPort = pConfig->Read("/Hamlib/SerialPort", ""); + wxGetApp().m_intHamlibSerialRate = pConfig->ReadLong("/Hamlib/SerialRate", 0); + + wxGetApp().m_boolUseSerialPTT = pConfig->ReadBool(wxT("/Rig/UseSerialPTT"), false); + wxGetApp().m_strRigCtrlPort = pConfig->Read(wxT("/Rig/Port"), wxT("")); + wxGetApp().m_boolUseRTS = pConfig->ReadBool(wxT("/Rig/UseRTS"), true); + wxGetApp().m_boolRTSPos = pConfig->ReadBool(wxT("/Rig/RTSPolarity"), true); + wxGetApp().m_boolUseDTR = pConfig->ReadBool(wxT("/Rig/UseDTR"), false); + wxGetApp().m_boolDTRPos = pConfig->ReadBool(wxT("/Rig/DTRPolarity"), false); + + assert(wxGetApp().m_serialport != NULL); + + // ----------------------------------------------------------------------- + + bool slow = false; // prevents compile error when using default bool + wxGetApp().m_snrSlow = pConfig->Read("/Audio/snrSlow", slow); + + bool t = true; // prevents compile error when using default bool + wxGetApp().m_codec2LPCPostFilterEnable = pConfig->Read(wxT("/Filter/codec2LPCPostFilterEnable"), t); + wxGetApp().m_codec2LPCPostFilterBassBoost = pConfig->Read(wxT("/Filter/codec2LPCPostFilterBassBoost"), t); + wxGetApp().m_codec2LPCPostFilterGamma = (float)pConfig->Read(wxT("/Filter/codec2LPCPostFilterGamma"), CODEC2_LPC_PF_GAMMA*100)/100.0; + wxGetApp().m_codec2LPCPostFilterBeta = (float)pConfig->Read(wxT("/Filter/codec2LPCPostFilterBeta"), CODEC2_LPC_PF_BETA*100)/100.0; + //printf("main(): m_codec2LPCPostFilterBeta: %f\n", wxGetApp().m_codec2LPCPostFilterBeta); + + wxGetApp().m_speexpp_enable = pConfig->Read(wxT("/Filter/speexpp_enable"), t); + + wxGetApp().m_MicInBassFreqHz = (float)pConfig->Read(wxT("/Filter/MicInBassFreqHz"), 1); + wxGetApp().m_MicInBassGaindB = (float)pConfig->Read(wxT("/Filter/MicInBassGaindB"), (long)0)/10.0; + wxGetApp().m_MicInTrebleFreqHz = (float)pConfig->Read(wxT("/Filter/MicInTrebleFreqHz"), 1); + wxGetApp().m_MicInTrebleGaindB = (float)pConfig->Read(wxT("/Filter/MicInTrebleGaindB"), (long)0)/10.0; + wxGetApp().m_MicInMidFreqHz = (float)pConfig->Read(wxT("/Filter/MicInMidFreqHz"), 1); + wxGetApp().m_MicInMidGaindB = (float)pConfig->Read(wxT("/Filter/MicInMidGaindB"), (long)0)/10.0; + wxGetApp().m_MicInMidQ = (float)pConfig->Read(wxT("/Filter/MicInMidQ"), (long)100)/100.0; + + bool f = false; + wxGetApp().m_MicInEQEnable = (float)pConfig->Read(wxT("/Filter/MicInEQEnable"), f); + + wxGetApp().m_SpkOutBassFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutBassFreqHz"), 1); + wxGetApp().m_SpkOutBassGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutBassGaindB"), (long)0)/10.0; + wxGetApp().m_SpkOutTrebleFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutTrebleFreqHz"), 1); + wxGetApp().m_SpkOutTrebleGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutTrebleGaindB"), (long)0)/10.0; + wxGetApp().m_SpkOutMidFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutMidFreqHz"), 1); + wxGetApp().m_SpkOutMidGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutMidGaindB"), (long)0)/10.0; + wxGetApp().m_SpkOutMidQ = (float)pConfig->Read(wxT("/Filter/SpkOutMidQ"), (long)100)/100.0; + + wxGetApp().m_SpkOutEQEnable = (float)pConfig->Read(wxT("/Filter/SpkOutEQEnable"), f); + + wxGetApp().m_callSign = pConfig->Read("/Data/CallSign", wxT("")); + wxGetApp().m_textEncoding = pConfig->Read("/Data/TextEncoding", 1); + wxGetApp().m_enable_checksum = pConfig->Read("/Data/EnableChecksumOnMsgRx", f); + + wxGetApp().m_events = pConfig->Read("/Events/enable", f); + wxGetApp().m_events_spam_timer = (int)pConfig->Read(wxT("/Events/spam_timer"), 10); + wxGetApp().m_events_regexp_match = pConfig->Read("/Events/regexp_match", wxT("s=(.*)")); + wxGetApp().m_events_regexp_replace = pConfig->Read("/Events/regexp_replace", + wxT("curl http://qso.freedv.org/cgi-bin/onspot.cgi?s=\\1")); + // make sure regexp lists are terminated by a \n + + if (wxGetApp().m_events_regexp_match.Last() != '\n') { + wxGetApp().m_events_regexp_match = wxGetApp().m_events_regexp_match+'\n'; + } + if (wxGetApp().m_events_regexp_replace.Last() != '\n') { + wxGetApp().m_events_regexp_replace = wxGetApp().m_events_regexp_replace+'\n'; + } + + wxGetApp().m_udp_enable = (float)pConfig->Read(wxT("/UDP/enable"), f); + wxGetApp().m_udp_port = (int)pConfig->Read(wxT("/UDP/port"), 3000); + + wxGetApp().m_FreeDV700txClip = (float)pConfig->Read(wxT("/FreeDV700/txClip"), t); + wxGetApp().m_FreeDV700Combine = 1; + wxGetApp().m_noise_snr = (float)pConfig->Read(wxT("/Noise/noise_snr"), 2); + + wxGetApp().m_debug_console = (float)pConfig->Read(wxT("/Debug/console"), f); + + wxGetApp().m_attn_carrier_en = 0; + wxGetApp().m_attn_carrier = 0; + + wxGetApp().m_tone = 0; + wxGetApp().m_tone_freq_hz = 1000; + wxGetApp().m_tone_amplitude = 500; + + int mode = pConfig->Read(wxT("/Audio/mode"), (long)0); + if (mode == 0) + m_rb1600->SetValue(1); + //if (mode == 2) + // m_rb700b->SetValue(1); + if (mode == 3) + m_rb700c->SetValue(1); + if (mode == 4) + m_rb800xa->SetValue(1); + + pConfig->SetPath(wxT("/")); + +// this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI)); + //m_togRxID->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnRxIDUI), NULL, this); + //m_togTxID->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnTxIDUI), NULL, this); + m_togBtnOnOff->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnOnOffUI), NULL, this); + m_togBtnSplit->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnSplitClickUI), NULL, this); + m_togBtnAnalog->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnAnalogClickUI), NULL, this); + //m_togBtnALC->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnALCClickUI), NULL, this); + // m_btnTogPTT->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnPTT_UI), NULL, this); + + m_togBtnSplit->Disable(); + //m_togRxID->Disable(); + //m_togTxID->Disable(); + m_togBtnAnalog->Disable(); + m_btnTogPTT->Disable(); + m_togBtnVoiceKeyer->Disable(); + //m_togBtnALC->Disable(); + + // squelch settings + char sqsnr[15]; + m_sliderSQ->SetValue((int)((g_SquelchLevel+5.0)*2.0)); + sprintf(sqsnr, "%4.1f", g_SquelchLevel); + wxString sqsnr_string(sqsnr); + m_textSQ->SetLabel(sqsnr_string); + m_ckboxSQ->SetValue(g_SquelchActive); + + // SNR settings + + m_ckboxSNR->SetValue(wxGetApp().m_snrSlow); + setsnrBeta(wxGetApp().m_snrSlow); + +#ifdef _USE_TIMER + Bind(wxEVT_TIMER, &MainFrame::OnTimer, this); // ID_MY_WINDOW); + m_plotTimer.SetOwner(this, ID_TIMER_WATERFALL); + //m_panelWaterfall->Refresh(); +#endif + + m_RxRunning = false; + +#ifdef _USE_ONIDLE + Connect(wxEVT_IDLE, wxIdleEventHandler(MainFrame::OnIdle), NULL, this); +#endif //_USE_ONIDLE + + g_sfPlayFile = NULL; + g_playFileToMicIn = false; + g_loopPlayFileToMicIn = false; + + g_sfRecFile = NULL; + g_recFileFromRadio = false; + + g_sfPlayFileFromRadio = NULL; + g_playFileFromRadio = false; + g_loopPlayFileFromRadio = false; + + // init click-tune states + + g_RxFreqOffsetHz = 0.0; + g_RxFreqOffsetPhaseRect.real = cos(0.0); + g_RxFreqOffsetPhaseRect.imag = sin(0.0); + m_panelWaterfall->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz); + m_panelSpectrum->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz); + + g_TxFreqOffsetHz = 0.0; + g_TxFreqOffsetPhaseRect.real = cos(0.0); + g_TxFreqOffsetPhaseRect.imag = sin(0.0); + + g_tx = 0; + g_split = 0; + + // data states + g_txDataInFifo = fifo_create(MAX_CALLSIGN*VARICODE_MAX_BITS); + g_rxDataOutFifo = fifo_create(MAX_CALLSIGN*VARICODE_MAX_BITS); + + sox_biquad_start(); + + g_testFrames = 0; + g_test_frame_sync_state = 0; + g_resyncs = 0; + wxGetApp().m_testFrames = false; + g_tone_phase = 0.0; + + g_modal = false; + +#ifdef __EXPERIMENTAL_UDP__ + // Start UDP listener thread + + m_UDPThread = NULL; + startUDPThread(); +#endif + + optionsDlg = new OptionsDlg(NULL); + m_schedule_restore = false; + + vk_state = VK_IDLE; + + // Init optional Windows debug console so we can see all those printfs + +#ifdef __WXMSW__ + if (wxGetApp().m_debug_console) { + // somewhere to send printfs while developing + int ret = AllocConsole(); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + fprintf(stderr, "AllocConsole: %d m_debug_console: %d\n", ret, wxGetApp().m_debug_console); + } +#endif + + //ftest = fopen("ftest.raw", "wb"); + //assert(ftest != NULL); +} + +//------------------------------------------------------------------------- +// ~MainFrame() +//------------------------------------------------------------------------- +MainFrame::~MainFrame() +{ + int x; + int y; + int w; + int h; + + fprintf(stderr, "MainFrame::~MainFrame()\n"); + //fclose(ftest); + #ifdef __WXMSW__ + fclose(g_logfile); + #endif + + if (optionsDlg != NULL) { + delete optionsDlg; + optionsDlg = NULL; + } + +#ifdef __EXPERIMENTAL_UDP__ + stopUDPThread(); +#endif + + if (wxGetApp().m_hamlib) delete wxGetApp().m_hamlib; + if (wxGetApp().m_serialport) delete wxGetApp().m_serialport; + + wxConfigBase *pConfig = wxConfigBase::Get(); + if(pConfig) + { + if (!IsIconized()) { + GetClientSize(&w, &h); + GetPosition(&x, &y); + printf("x = %d y = %d w = %d h = %d\n", x,y,w,h); + pConfig->Write(wxT("/MainFrame/left"), (long) x); + pConfig->Write(wxT("/MainFrame/top"), (long) y); + pConfig->Write(wxT("/MainFrame/width"), (long) w); + pConfig->Write(wxT("/MainFrame/height"), (long) h); + } + pConfig->Write(wxT("/MainFrame/show_wf"), wxGetApp().m_show_wf); + pConfig->Write(wxT("/MainFrame/show_spect"), wxGetApp().m_show_spect); + pConfig->Write(wxT("/MainFrame/show_scatter"), wxGetApp().m_show_scatter); + pConfig->Write(wxT("/MainFrame/show_timing"), wxGetApp().m_show_timing); + pConfig->Write(wxT("/MainFrame/show_freq"), wxGetApp().m_show_freq); + pConfig->Write(wxT("/MainFrame/show_speech_in"), wxGetApp().m_show_speech_in); + pConfig->Write(wxT("/MainFrame/show_speech_out"), wxGetApp().m_show_speech_out); + pConfig->Write(wxT("/MainFrame/show_demod_in"), wxGetApp().m_show_demod_in); + pConfig->Write(wxT("/MainFrame/show_test_frame_errors"), wxGetApp().m_show_test_frame_errors); + pConfig->Write(wxT("/MainFrame/show_test_frame_errors_hist"), wxGetApp().m_show_test_frame_errors_hist); + + pConfig->Write(wxT("/MainFrame/rxNbookCtrl"), wxGetApp().m_rxNbookCtrl); + + pConfig->Write(wxT("/Audio/SquelchActive"), g_SquelchActive); + pConfig->Write(wxT("/Audio/SquelchLevel"), (int)(g_SquelchLevel*2.0)); + + pConfig->Write(wxT("/Audio/framesPerBuffer"), wxGetApp().m_framesPerBuffer); + + pConfig->Write(wxT("/Audio/soundCard1InDeviceNum"), g_soundCard1InDeviceNum); + pConfig->Write(wxT("/Audio/soundCard1OutDeviceNum"), g_soundCard1OutDeviceNum); + pConfig->Write(wxT("/Audio/soundCard1SampleRate"), g_soundCard1SampleRate ); + + pConfig->Write(wxT("/Audio/soundCard2InDeviceNum"), g_soundCard2InDeviceNum); + pConfig->Write(wxT("/Audio/soundCard2OutDeviceNum"), g_soundCard2OutDeviceNum); + pConfig->Write(wxT("/Audio/soundCard2SampleRate"), g_soundCard2SampleRate ); + + pConfig->Write(wxT("/VoiceKeyer/WaveFilePath"), wxGetApp().m_txtVoiceKeyerWaveFilePath); + pConfig->Write(wxT("/VoiceKeyer/WaveFile"), wxGetApp().m_txtVoiceKeyerWaveFile); + pConfig->Write(wxT("/VoiceKeyer/RxPause"), wxGetApp().m_intVoiceKeyerRxPause); + pConfig->Write(wxT("/VoiceKeyer/Repeats"), wxGetApp().m_intVoiceKeyerRepeats); + + pConfig->Write(wxT("/Rig/HalfDuplex"), wxGetApp().m_boolHalfDuplex); + pConfig->Write(wxT("/Rig/leftChannelVoxTone"), wxGetApp().m_leftChannelVoxTone); + pConfig->Write("/Hamlib/UseForPTT", wxGetApp().m_boolHamlibUseForPTT); + pConfig->Write("/Hamlib/RigName", wxGetApp().m_intHamlibRig); + pConfig->Write("/Hamlib/SerialPort", wxGetApp().m_strHamlibSerialPort); + pConfig->Write("/Hamlib/SerialRate", wxGetApp().m_intHamlibSerialRate); + + + pConfig->Write(wxT("/File/playFileToMicInPath"), wxGetApp().m_playFileToMicInPath); + pConfig->Write(wxT("/File/recFileFromRadioPath"), wxGetApp().m_recFileFromRadioPath); + pConfig->Write(wxT("/File/recFileFromRadioSecs"), wxGetApp().m_recFileFromRadioSecs); + pConfig->Write(wxT("/File/playFileFromRadioPath"), wxGetApp().m_playFileFromRadioPath); + + pConfig->Write(wxT("/Audio/snrSlow"), wxGetApp().m_snrSlow); + + pConfig->Write(wxT("/Data/CallSign"), wxGetApp().m_callSign); + pConfig->Write(wxT("/Data/TextEncoding"), wxGetApp().m_textEncoding); + pConfig->Write(wxT("/Data/EnableChecksumOnMsgRx"), wxGetApp().m_enable_checksum); + pConfig->Write(wxT("/Events/enable"), wxGetApp().m_events); + pConfig->Write(wxT("/Events/spam_timer"), wxGetApp().m_events_spam_timer); + pConfig->Write(wxT("/Events/regexp_match"), wxGetApp().m_events_regexp_match); + pConfig->Write(wxT("/Events/regexp_replace"), wxGetApp().m_events_regexp_replace); + + pConfig->Write(wxT("/UDP/enable"), wxGetApp().m_udp_enable); + pConfig->Write(wxT("/UDP/port"), wxGetApp().m_udp_port); + + pConfig->Write(wxT("/Filter/MicInEQEnable"), wxGetApp().m_MicInEQEnable); + pConfig->Write(wxT("/Filter/SpkOutEQEnable"), wxGetApp().m_SpkOutEQEnable); + + pConfig->Write(wxT("/FreeDV700/txClip"), wxGetApp().m_FreeDV700txClip); + pConfig->Write(wxT("/Noise/noise_snr"), wxGetApp().m_noise_snr); + + pConfig->Write(wxT("/Debug/console"), wxGetApp().m_debug_console); + + int mode; + if (m_rb1600->GetValue()) + mode = 0; + //if (m_rb700b->GetValue()) + // mode = 2; + if (m_rb700c->GetValue()) + mode = 3; + if (m_rb800xa->GetValue()) + mode = 4; + pConfig->Write(wxT("/Audio/mode"), mode); + } + + //m_togRxID->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnRxIDUI), NULL, this); + //m_togTxID->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnTxIDUI), NULL, this); + m_togBtnOnOff->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnOnOffUI), NULL, this); + m_togBtnSplit->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnSplitClickUI), NULL, this); + m_togBtnAnalog->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnAnalogClickUI), NULL, this); + //m_togBtnALC->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnALCClickUI), NULL, this); + //m_btnTogPTT->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnPTT_UI), NULL, this); + + sox_biquad_finish(); + + if (m_RxRunning) + { + stopRxStream(); + } + if (g_sfPlayFile != NULL) + { + sf_close(g_sfPlayFile); + g_sfPlayFile = NULL; + } + if (g_sfRecFile != NULL) + { + sf_close(g_sfRecFile); + g_sfRecFile = NULL; + } +#ifdef _USE_TIMER + if(m_plotTimer.IsRunning()) + { + m_plotTimer.Stop(); + Unbind(wxEVT_TIMER, &MainFrame::OnTimer, this); + } +#endif //_USE_TIMER + +#ifdef _USE_ONIDLE + Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainFrame::OnIdle), NULL, this); +#endif // _USE_ONIDLE + + delete wxConfigBase::Set((wxConfigBase *) NULL); +} + + +#ifdef _USE_ONIDLE +void MainFrame::OnIdle(wxIdleEvent &evt) { +} +#endif + + +#ifdef _USE_TIMER +//---------------------------------------------------------------- +// OnTimer() +// +// when the timer fires every DT seconds we update the GUI displays. +// the tabs only the plot that is visible actually gets updated, this +// keeps CPU load reasonable +//---------------------------------------------------------------- +void MainFrame::OnTimer(wxTimerEvent &evt) +{ + + int r,c; + + if (m_panelWaterfall->checkDT()) { + m_panelWaterfall->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz); + m_panelWaterfall->m_newdata = true; + m_panelWaterfall->Refresh(); + } + + m_panelSpectrum->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz); + m_panelSpectrum->m_newdata = true; + m_panelSpectrum->Refresh(); + + /* update scatter/eye plot ------------------------------------------------------------*/ + + if (freedv_get_mode(g_pfreedv) == FREEDV_MODE_800XA) { + /* FSK Mode - eye diagram ---------------------------------------------------------*/ + + /* add samples row by row */ + + int i; + for (i=0; iadd_new_samples_eye(&g_stats.rx_eye[i][0], g_stats.neyesamp); + } + } + else { + /* PSK Modes - scatter plot -------------------------------------------------------*/ + for (r=0; radd_new_samples_scatter(&g_stats.rx_symbols[r][0]); + } + + if (/*(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700B) ||*/(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C)) { + + if (wxGetApp().m_FreeDV700Combine) { + m_panelScatter->setNc(g_Nc/2); /* m_FreeDV700Combine may have changed at run time */ + + /* + FreeDV 700 uses diversity, so optionaly combine + symbols for scatter plot, as combined symbols are + used for demodulation. Note we need to use a copy + of the symbols, as we are not sure when the stats + will be updated. + */ + + COMP rx_symbols_copy[g_Nc/2]; + + for(c=0; cadd_new_samples_scatter(rx_symbols_copy); + } + else { + m_panelScatter->setNc(g_Nc); /* m_FreeDV700Combine may have changed at run time */ + /* + Sometimes useful to plot carriers separately, e.g. to determine if tx carrier power is constant + across carriers. + */ + m_panelScatter->add_new_samples_scatter(&g_stats.rx_symbols[r][0]); + } + } + + } + } + + m_panelScatter->Refresh(); + + // Oscilliscope type speech plots ------------------------------------------------------- + + short speechInPlotSamples[WAVEFORM_PLOT_BUF]; + if (fifo_read(g_plotSpeechInFifo, speechInPlotSamples, WAVEFORM_PLOT_BUF)) { + memset(speechInPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short)); + //fprintf(stderr, "empty!\n"); + } + m_panelSpeechIn->add_new_short_samples(0, speechInPlotSamples, WAVEFORM_PLOT_BUF, 32767); + m_panelSpeechIn->Refresh(); + + short speechOutPlotSamples[WAVEFORM_PLOT_BUF]; + if (fifo_read(g_plotSpeechOutFifo, speechOutPlotSamples, WAVEFORM_PLOT_BUF)) + memset(speechOutPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short)); + m_panelSpeechOut->add_new_short_samples(0, speechOutPlotSamples, WAVEFORM_PLOT_BUF, 32767); + m_panelSpeechOut->Refresh(); + + short demodInPlotSamples[WAVEFORM_PLOT_BUF]; + if (fifo_read(g_plotDemodInFifo, demodInPlotSamples, WAVEFORM_PLOT_BUF)) { + memset(demodInPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short)); + } + m_panelDemodIn->add_new_short_samples(0,demodInPlotSamples, WAVEFORM_PLOT_BUF, 32767); + m_panelDemodIn->Refresh(); + + // Demod states ----------------------------------------------------------------------- + + m_panelTimeOffset->add_new_sample(0, (float)g_stats.rx_timing/FDMDV_NOM_SAMPLES_PER_FRAME); + m_panelTimeOffset->Refresh(); + + m_panelFreqOffset->add_new_sample(0, g_stats.foff); + m_panelFreqOffset->Refresh(); + + // SNR text box and gauge ------------------------------------------------------------ + + // LP filter g_stats.snr_est some more to stabilise the + // display. g_stats.snr_est already has some low pass filtering + // but we need it fairly fast to activate squelch. So we + // optionally perform some further filtering for the display + // version of SNR. The "Slow" checkbox controls the amount of + // filtering. The filtered snr also controls the squelch + + g_snr = m_snrBeta*g_snr + (1.0 - m_snrBeta)*g_stats.snr_est; + float snr_limited = g_snr; + if (snr_limited < -5.0) snr_limited = -5.0; + if (snr_limited > 20.0) snr_limited = 20.0; + + char snr[15]; + sprintf(snr, "%d", (int)(g_snr+0.5)); // round to nearest dB + + //printf("snr_est: %f m_snrBeta: %f g_snr: %f snr_limited: %f\n", g_stats.snr_est, m_snrBeta, g_snr, snr_limited); + + wxString snr_string(snr); + m_textSNR->SetLabel(snr_string); + m_gaugeSNR->SetValue((int)(snr_limited+5)); + + + // Level Gauge ----------------------------------------------------------------------- + + float tooHighThresh; + if (!g_tx && m_RxRunning) + { + // receive mode - display From Radio peaks + // peak from this DT sampling period + int maxDemodIn = 0; + for(int i=0; i m_maxLevel) + m_maxLevel = maxDemodIn; + + tooHighThresh = FROM_RADIO_MAX; + } + else + { + // transmit mode - display From Mic peaks + + // peak from this DT sampling period + int maxSpeechIn = 0; + for(int i=0; i m_maxLevel) + m_maxLevel = maxSpeechIn; + + tooHighThresh = FROM_MIC_MAX; + } + + // Peak Reading meter: updates peaks immediately, then slowly decays + int maxScaled = (int)(100.0 * ((float)m_maxLevel/32767.0)); + m_gaugeLevel->SetValue(maxScaled); + //printf("maxScaled: %d\n", maxScaled); + if (((float)maxScaled/100) > tooHighThresh) + m_textLevel->SetLabel("Too High"); + else + m_textLevel->SetLabel(""); + + m_maxLevel *= LEVEL_BETA; + + // sync LED (Colours don't work on Windows) ------------------------ + + //fprintf(stderr, "g_State: %d m_rbSync->GetValue(): %d\n", g_State, m_rbSync->GetValue()); + if (g_State) { + if (g_prev_State == 0) { + g_resyncs++; + } + m_rbSync->SetForegroundColour( wxColour( 0, 255, 0 ) ); // green + m_rbSync->SetValue(true); + } + else { + m_rbSync->SetForegroundColour( wxColour( 255, 0, 0 ) ); // red + m_rbSync->SetValue(false); + } + g_prev_State = g_State; + + // send Callsign ---------------------------------------------------- + + char callsign[MAX_CALLSIGN]; + strncpy(callsign, (const char*) wxGetApp().m_callSign.mb_str(wxConvUTF8), MAX_CALLSIGN-1); + + // buffer 1 txt message to ensure tx data fifo doesn't "run dry" + + if ((unsigned)fifo_used(g_txDataInFifo) < strlen(callsign)) { + unsigned int i; + + //fprintf(g_logfile, "tx callsign: %s.\n", callsign); + + /* optionally append checksum */ + + if (wxGetApp().m_enable_checksum) { + + unsigned char checksum = 0; + char callsign_checksum_cr[MAX_CALLSIGN+1]; + + for(i=0; i MAX_CALLSIGN-1)) { + // CR completes line + *m_pcallsign = 0; + + /* Checksums can be disabled, e.g. for compatability with + older vesions. In that case we print msg but don't do + any event processing. If checksums enabled, only print + out when checksum is good. */ + + if (wxGetApp().m_enable_checksum) { + // lets see if checksum is OK + + unsigned char checksum_rx = 0; + if (strlen(m_callsign) > 2) { + for(unsigned int i=0; iSetValue(s); + +#ifdef __UDP_EXPERIMENTAL__ + char s1[MAX_CALLSIGN]; + sprintf(s1,"rx_txtmsg %s", m_callsign); + processTxtEvent(s1); + + m_checksumGood++; + s.Printf("%d", m_checksumGood); + m_txtChecksumGood->SetLabel(s); +#endif + } + else { +#ifdef __UDP_EXPERIMENTAL__ + m_checksumBad++; + s.Printf("%d", m_checksumBad); + m_txtChecksumBad->SetLabel(s); +#endif + } + } + + //fprintf(g_logfile,"resetting callsign %s %ld\n", m_callsign, m_pcallsign-m_callsign); + // reset ptr to start of string + m_pcallsign = m_callsign; + } + else { + //fprintf(g_logfile, "new char %d %c\n", ashort, (char)ashort); + *m_pcallsign++ = (char)ashort; + } + + /* If checksums disabled, display txt chars as they arrive */ + + if (!wxGetApp().m_enable_checksum) { + m_txtCtrlCallSign->SetValue(m_callsign); + } + } + + + // Run time update of EQ filters ----------------------------------- + + if (m_newMicInFilter || m_newSpkOutFilter) { + g_mutexProtectingCallbackData.Lock(); + deleteEQFilters(g_rxUserdata); + designEQFilters(g_rxUserdata); + g_mutexProtectingCallbackData.Unlock(); + m_newMicInFilter = m_newSpkOutFilter = false; + } + g_rxUserdata->micInEQEnable = wxGetApp().m_MicInEQEnable; + g_rxUserdata->spkOutEQEnable = wxGetApp().m_SpkOutEQEnable; + + + if (g_mode != -1) { + + // Run time update of FreeDV 700 tx clipper + + freedv_set_clip(g_pfreedv, (int)wxGetApp().m_FreeDV700txClip); + + // Test Frame Bit Error Updates ------------------------------------ + + // Toggle test frame mode at run time + + if (!freedv_get_test_frames(g_pfreedv) && wxGetApp().m_testFrames) { + + // reset stats on check box off to on transition + + freedv_set_test_frames(g_pfreedv, 1); + freedv_set_total_bits(g_pfreedv, 0); + freedv_set_total_bit_errors(g_pfreedv, 0); + } + freedv_set_test_frames(g_pfreedv, wxGetApp().m_testFrames); + freedv_set_test_frames_diversity(g_pfreedv, wxGetApp().m_FreeDV700Combine); + g_channel_noise = wxGetApp().m_channel_noise; + + if (g_State) { + char bits[80], errors[80], ber[80], resyncs[80]; + + // update stats on main page + + sprintf(bits, "Bits: %d", freedv_get_total_bits(g_pfreedv)); wxString bits_string(bits); m_textBits->SetLabel(bits_string); + sprintf(errors, "Errs: %d", freedv_get_total_bit_errors(g_pfreedv)); wxString errors_string(errors); m_textErrors->SetLabel(errors_string); + float b = (float)freedv_get_total_bit_errors(g_pfreedv)/(1E-6+freedv_get_total_bits(g_pfreedv)); + sprintf(ber, "BER: %4.3f", b); wxString ber_string(ber); m_textBER->SetLabel(ber_string); + sprintf(resyncs, "Resyncs: %d", g_resyncs); wxString resyncs_string(resyncs); m_textResyncs->SetLabel(resyncs_string); + + // update error pattern plots if supported + + int sz_error_pattern = freedv_get_sz_error_pattern(g_pfreedv); + //fprintf(stderr, "sz_error_pattern: %d\n", sz_error_pattern); + if (sz_error_pattern) { + short error_pattern[sz_error_pattern]; + + if (fifo_read(g_error_pattern_fifo, error_pattern, sz_error_pattern) == 0) { + int i,b; + + /* both modes map IQ to alternate bits, but on same carrier */ + + if (freedv_get_mode(g_pfreedv) == FREEDV_MODE_1600) { + /* FreeDV 1600 mapping from error pattern to two bits on each carrier */ + + for(b=0; badd_new_sample(b, b + 0.8*error_pattern[i]); + g_error_hist[b] += error_pattern[i]; + g_error_histn[b]++; + } + //if (b%2) + // printf("g_error_hist[%d]: %d\n", b/2, g_error_hist[b/2]); + } + + /* calculate BERs and send to plot */ + + float ber[2*FDMDV_NC_MAX]; + for(b=0; b<2*FDMDV_NC_MAX; b++) { + ber[b] = 0.0; + } + for(b=0; badd_new_samples(0, ber, 2*FDMDV_NC_MAX); + } + + if (/*(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700B) || */(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C)) { + int c; + //fprintf(stderr, "after g_error_pattern_fifo read 2\n"); + + /* + FreeDV 700 mapping from error pattern to bit on each carrier, see + data bit to carrier mapping in: + + codec2-dev/octave/cohpsk_frame_design.ods + + We can plot a histogram of the errors/carrier before or after diversity + recombination. Actually one bar for each IQ bit in carrier order. + */ + + int hist_Nc = sz_error_pattern/4; + //fprintf(stderr, "hist_Nc: %d\n", hist_Nc); + + for(i=0; iadd_new_sample(c, c + 0.8*error_pattern[i]); + g_error_hist[c] += error_pattern[i]; + g_error_histn[c]++; + //printf("i: %d c: %d\n", i, c); + } + for(; i<2*MODEM_STATS_NC_MAX*4; i++) { + c = floor(i/4); + m_panelTestFrameErrors->add_new_sample(c, c); + //printf("i: %d c: %d\n", i, c); + } + + /* calculate BERs and send to plot */ + + float ber[2*FDMDV_NC_MAX]; + for(b=0; b<2*FDMDV_NC_MAX; b++) { + ber[b] = 0.0; + } + for(b=0; badd_new_samples(0, ber, 2*FDMDV_NC_MAX); + } + + m_panelTestFrameErrors->Refresh(); + m_panelTestFrameErrorsHist->Refresh(); + } + } + } + } + + // command from UDP thread that is best processed in main thread to avoid seg faults + + if (m_schedule_restore) { + if (IsIconized()) + Restore(); + m_schedule_restore = false; + } + +#ifdef __UDP_EXPERIMENTAL__ + // Light Spam Timer LED if at least one timer is running + + int i; + optionsDlg->SetSpamTimerLight(false); + for(i=0; iSetSpamTimerLight(true); +#endif + + // Blink file playback status line + + if (g_playFileFromRadio) { + g_blink += DT; + //fprintf(g_logfile, "g_blink: %f\n", g_blink); + if ((g_blink >= 1.0) && (g_blink < 2.0)) + SetStatusText(wxT("Playing into from radio"), 0); + if (g_blink >= 2.0) { + SetStatusText(wxT(""), 0); + g_blink = 0.0; + } + } + + // Voice Keyer state machine + + VoiceKeyerProcessEvent(VK_DT); +} +#endif + + +//------------------------------------------------------------------------- +// OnCloseFrame() +//------------------------------------------------------------------------- +void MainFrame::OnCloseFrame(wxCloseEvent& event) +{ + fprintf(stderr, "MainFrame::OnCloseFrame()\n"); + Pa_Terminate(); + Destroy(); +} + +//------------------------------------------------------------------------- +// OnTop() +//------------------------------------------------------------------------- +void MainFrame::OnTop(wxCommandEvent& event) +{ + int style = GetWindowStyle(); + + if (style & wxSTAY_ON_TOP) + { + style &= ~wxSTAY_ON_TOP; + } + else + { + style |= wxSTAY_ON_TOP; + } + SetWindowStyle(style); +} + +//------------------------------------------------------------------------- +// OnDeleteConfig() +//------------------------------------------------------------------------- +void MainFrame::OnDeleteConfig(wxCommandEvent&) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + if(pConfig->DeleteAll()) + { + wxLogMessage(wxT("Config file/registry key successfully deleted.")); + + delete wxConfigBase::Set(NULL); + wxConfigBase::DontCreateOnDemand(); + } + else + { + wxLogError(wxT("Deleting config file/registry key failed.")); + } +} + +//------------------------------------------------------------------------- +// Paint() +//------------------------------------------------------------------------- +void MainFrame::OnPaint(wxPaintEvent& WXUNUSED(event)) +{ + wxPaintDC dc(this); + + if(GetMenuBar()->IsChecked(ID_PAINT_BG)) + { + dc.Clear(); + } + dc.SetUserScale(m_zoom, m_zoom); +} + +//------------------------------------------------------------------------- +// OnCmdSliderScroll() +//------------------------------------------------------------------------- +void MainFrame::OnCmdSliderScroll(wxScrollEvent& event) +{ + char sqsnr[15]; + g_SquelchLevel = (float)m_sliderSQ->GetValue()/2.0 - 5.0; + sprintf(sqsnr, "%4.1f", g_SquelchLevel); // 0.5 dB steps + wxString sqsnr_string(sqsnr); + m_textSQ->SetLabel(sqsnr_string); + + event.Skip(); +} + +//------------------------------------------------------------------------- +// OnCheckSQClick() +//------------------------------------------------------------------------- +void MainFrame::OnCheckSQClick(wxCommandEvent& event) +{ + if(!g_SquelchActive) + { + g_SquelchActive = true; + } + else + { + g_SquelchActive = false; + } +} + +void MainFrame::setsnrBeta(bool snrSlow) +{ + if(snrSlow) + { + m_snrBeta = 0.95; // make this closer to 1.0 to smooth SNR est further + } + else + { + m_snrBeta = 0.0; // no smoothing of SNR estimate from demodulator + } +} + +//------------------------------------------------------------------------- +// OnCheckSQClick() +//------------------------------------------------------------------------- +void MainFrame::OnCheckSNRClick(wxCommandEvent& event) +{ + wxGetApp().m_snrSlow = m_ckboxSNR->GetValue(); + setsnrBeta(wxGetApp().m_snrSlow); + //printf("m_snrSlow: %d\n", (int)wxGetApp().m_snrSlow); +} + +// check for space bar press (only when running) + +int MainApp::FilterEvent(wxEvent& event) +{ + if ((event.GetEventType() == wxEVT_KEY_DOWN) && + (((wxKeyEvent&)event).GetKeyCode() == WXK_SPACE)) + { + // only use space to toggle PTT if we are running and no modal dialogs (like options) up + //fprintf(stderr,"frame->m_RxRunning: %d g_modal: %d\n", + // frame->m_RxRunning, g_modal); + if (frame->m_RxRunning && !g_modal) { + + // space bar controls rx/rx if keyer not running + if (frame->vk_state == VK_IDLE) { + if (frame->m_btnTogPTT->GetValue()) + frame->m_btnTogPTT->SetValue(false); + else + frame->m_btnTogPTT->SetValue(true); + + frame->togglePTT(); + } + else // spavce bar stops keyer + frame->VoiceKeyerProcessEvent(VK_SPACE_BAR); + + return true; // absorb space so we don't toggle control with focus (e.g. Start) + + } + } + + return -1; +} + +//------------------------------------------------------------------------- +// OnTogBtnPTT () +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnPTT (wxCommandEvent& event) +{ + togglePTT(); + event.Skip(); +} + +void MainFrame::togglePTT(void) { + + // Change tabbed page in centre panel depending on PTT state + + if (g_tx) + { + // tx-> rx transition, swap to the page we were on for last rx + m_auiNbookCtrl->ChangeSelection(wxGetApp().m_rxNbookCtrl); + } + else + { + // rx-> tx transition, swap to Mic In page to monitor speech + wxGetApp().m_rxNbookCtrl = m_auiNbookCtrl->GetSelection(); + m_auiNbookCtrl->ChangeSelection(m_auiNbookCtrl->GetPageIndex((wxWindow *)m_panelSpeechIn)); +#ifdef __UDP_EXPERIMENTAL__ + char e[80]; sprintf(e,"ptt"); processTxtEvent(e); +#endif + } + + g_tx = m_btnTogPTT->GetValue(); + + // Hamlib PTT + + if (wxGetApp().m_boolHamlibUseForPTT) { + Hamlib *hamlib = wxGetApp().m_hamlib; + wxString hamlibError; + if (wxGetApp().m_boolHamlibUseForPTT && hamlib != NULL) { + if (hamlib->ptt(g_tx, hamlibError) == false) { + wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this); + } + } + } + + // Serial PTT + + if (wxGetApp().m_boolUseSerialPTT && (wxGetApp().m_serialport->isopen())) { + wxGetApp().m_serialport->ptt(g_tx); + } + + // reset level gauge + + m_maxLevel = 0; + m_textLevel->SetLabel(wxT("")); + m_gaugeLevel->SetValue(0); +} + +/* + Voice Keyer: + + + space bar turns keyer off + + 5 secs of valid sync turns it off + + [X] complete state machine and builds OK + [ ] file select dialog + [ ] test all states + [ ] restore size +*/ + +void MainFrame::OnTogBtnVoiceKeyerClick (wxCommandEvent& event) +{ + if (vk_state == VK_IDLE) + VoiceKeyerProcessEvent(VK_START); + else + VoiceKeyerProcessEvent(VK_SPACE_BAR); + + event.Skip(); +} + + +int MainFrame::VoiceKeyerStartTx(void) +{ + int next_state; + + // start playing wave file or die trying + + SF_INFO sfInfo; + sfInfo.format = 0; + + g_sfPlayFile = sf_open(wxGetApp().m_txtVoiceKeyerWaveFile, SFM_READ, &sfInfo); + if(g_sfPlayFile == NULL) { + wxString strErr = sf_strerror(NULL); + wxMessageBox(strErr, wxT("Couldn't open:") + wxGetApp().m_txtVoiceKeyerWaveFile, wxOK); + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + else { + SetStatusText(wxT("Voice Keyer: Playing File") + wxGetApp().m_txtVoiceKeyerWaveFile + wxT(" to Mic Input") , 0); + g_loopPlayFileToMicIn = false; + g_playFileToMicIn = true; + + m_btnTogPTT->SetValue(true); togglePTT(); + next_state = VK_TX; + } + + return next_state; +} + + +void MainFrame::VoiceKeyerProcessEvent(int vk_event) { + int next_state = vk_state; + + switch(vk_state) { + + case VK_IDLE: + if (vk_event == VK_START) { + // sample these puppies at start just in case they are changed while VK running + vk_rx_pause = wxGetApp().m_intVoiceKeyerRxPause; + vk_repeats = wxGetApp().m_intVoiceKeyerRepeats; + fprintf(stderr, "vk_rx_pause: %d vk_repeats: %d\n", vk_rx_pause, vk_repeats); + + vk_repeat_counter = 0; + next_state = VoiceKeyerStartTx(); + } + break; + + case VK_TX: + + // In this state we are transmitting and playing a wave file + // to Mic In + + if (vk_event == VK_SPACE_BAR) { + m_btnTogPTT->SetValue(false); togglePTT(); + StopPlayFileToMicIn(); + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + + if (vk_event == VK_PLAY_FINISHED) { + m_btnTogPTT->SetValue(false); togglePTT(); + vk_repeat_counter++; + if (vk_repeat_counter > vk_repeats) { + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + else { + vk_rx_time = 0.0; + next_state = VK_RX; + } + } + + break; + + case VK_RX: + + // in this state we are receiving and waiting for + // delay timer or valid sync + + if (vk_event == VK_DT) { + if (freedv_get_sync(g_pfreedv) == 1) { + // if we detect sync simulate a smooth transition to SYNC_WAIT State - TODO: review + if (vk_rx_time >= DT) { + vk_rx_time -= DT; + } else { + next_state = VK_SYNC_WAIT; + } + } else { + vk_rx_time += DT; + if (vk_rx_time >= vk_rx_pause) { + next_state = VoiceKeyerStartTx(); + } + } + } + + if (vk_event == VK_SPACE_BAR) { + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + + break; + + case VK_SYNC_WAIT: + + // In this state we wait for valid sync to last + // VK_SYNC_WAIT_TIME seconds + + if (vk_event == VK_SPACE_BAR) { + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + + if (vk_event == VK_DT) { + if (freedv_get_sync(g_pfreedv) == 0) { + // if we lose sync simulate a smooth transition to return in RX State - TODO: review + if (vk_rx_time >= DT) { + vk_rx_time -= DT; + } else { + next_state = VK_RX; + } + } else { + vk_rx_time += DT; + } + + // drop out of voice keyer if we get a few seconds of valid sync + + if (vk_rx_time >= VK_SYNC_WAIT_TIME) { + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + } + break; + + default: + // catch anything we missed + + m_btnTogPTT->SetValue(false); togglePTT(); + m_togBtnVoiceKeyer->SetValue(false); + next_state = VK_IDLE; + } + + //if ((vk_event != VK_DT) || (vk_state != next_state)) + // fprintf(stderr, "VoiceKeyerProcessEvent: vk_state: %d vk_event: %d next_state: %d vk_repeat_counter: %d\n", vk_state, vk_event, next_state, vk_repeat_counter); + vk_state = next_state; +} + + +//------------------------------------------------------------------------- +// OnTogBtnRxID() +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnRxID(wxCommandEvent& event) +{ + // empty any junk in rx data FIFO + short junk; + while(fifo_read(g_rxDataOutFifo,&junk,1) == 0); + event.Skip(); +} + +//------------------------------------------------------------------------- +// OnTogBtnTxID() +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnTxID(wxCommandEvent& event) +{ + event.Skip(); +} + +void MainFrame::OnTogBtnSplitClick(wxCommandEvent& event) { + if (g_split) + g_split = 0; + else + g_split = 1; + event.Skip(); +} + +//------------------------------------------------------------------------- +// OnTogBtnAnalogClick() +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnAnalogClick (wxCommandEvent& event) +{ + if (g_analog == 0) { + g_analog = 1; + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(FS/2))); + m_panelWaterfall->setFs(FS); + } + else { + g_analog = 0; + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedv_get_modem_sample_rate(g_pfreedv)/2))); + m_panelWaterfall->setFs(freedv_get_modem_sample_rate(g_pfreedv)); + } + + g_State = g_prev_State = 0; + g_stats.snr_est = 0; + + event.Skip(); +} + +void MainFrame::OnCallSignReset(wxCommandEvent& event) +{ + m_pcallsign = m_callsign; + memset(m_callsign, 0, MAX_CALLSIGN); + wxString s; + s.Printf("%s", m_callsign); + m_txtCtrlCallSign->SetValue(s); +#ifdef __UDP__EXPERIMENTAL__ + m_checksumGood = m_checksumBad = 0; + m_txtChecksumGood->SetLabel(_("0")); + m_txtChecksumBad->SetLabel(_("0")); +#endif +} + +void MainFrame::OnBerReset(wxCommandEvent& event) +{ + freedv_set_total_bits(g_pfreedv, 0); + freedv_set_total_bit_errors(g_pfreedv, 0); + g_resyncs = 0; + int i; + for(i=0; i<2*g_Nc; i++) { + g_error_hist[i] = 0; + g_error_histn[i] = 0; + } + +} + +#ifdef ALC +//------------------------------------------------------------------------- +// OnTogBtnALCClick() +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnALCClick(wxCommandEvent& event) +{ + wxMessageBox(wxT("Got Click!"), wxT("OnTogBtnALCClick"), wxOK); + + event.Skip(); +} +#endif + +// extra panel added to file open dialog to add loop checkbox +MyExtraPlayFilePanel::MyExtraPlayFilePanel(wxWindow *parent): wxPanel(parent) +{ + m_cb = new wxCheckBox(this, -1, wxT("Loop")); + m_cb->SetToolTip(_("When checked file will repeat forever")); + m_cb->SetValue(g_loopPlayFileToMicIn); + + // bug: I can't this to align right..... + wxBoxSizer *sizerTop = new wxBoxSizer(wxHORIZONTAL); + sizerTop->Add(m_cb, 0, wxALIGN_RIGHT, 0); + SetSizerAndFit(sizerTop); +} + +static wxWindow* createMyExtraPlayFilePanel(wxWindow *parent) +{ + return new MyExtraPlayFilePanel(parent); +} + +void MainFrame::StopPlayFileToMicIn(void) +{ + g_mutexProtectingCallbackData.Lock(); + g_playFileToMicIn = false; + sf_close(g_sfPlayFile); + SetStatusText(wxT("")); + g_mutexProtectingCallbackData.Unlock(); +} + +//------------------------------------------------------------------------- +// OnPlayFileToMicIn() +//------------------------------------------------------------------------- +void MainFrame::OnPlayFileToMicIn(wxCommandEvent& event) +{ + wxUnusedVar(event); + + if(g_playFileToMicIn) { + StopPlayFileToMicIn(); + VoiceKeyerProcessEvent(VK_PLAY_FINISHED); + } + else + { + wxString soundFile; + SF_INFO sfInfo; + + wxFileDialog openFileDialog( + this, + wxT("Play File to Mic In"), + wxGetApp().m_playFileToMicInPath, + wxEmptyString, + wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|") + wxT("All files (*.*)|*.*"), + wxFD_OPEN | wxFD_FILE_MUST_EXIST + ); + + // add the loop check box + openFileDialog.SetExtraControlCreator(&createMyExtraPlayFilePanel); + + if(openFileDialog.ShowModal() == wxID_CANCEL) + { + return; // the user changed their mind... + } + + wxString fileName, extension; + soundFile = openFileDialog.GetPath(); + wxFileName::SplitPath(soundFile, &wxGetApp().m_playFileToMicInPath, &fileName, &extension); + //wxLogDebug("m_playFileToMicInPath: %s", wxGetApp().m_playFileToMicInPath); + sfInfo.format = 0; + + if(!extension.IsEmpty()) + { + extension.LowerCase(); + if(extension == wxT("raw")) + { + sfInfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16; + sfInfo.channels = 1; + sfInfo.samplerate = FS; + } + } + g_sfPlayFile = sf_open(soundFile.c_str(), SFM_READ, &sfInfo); + if(g_sfPlayFile == NULL) + { + wxString strErr = sf_strerror(NULL); + wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK); + return; + } + + wxWindow * const ctrl = openFileDialog.GetExtraControl(); + + // Huh?! I just copied wxWidgets-2.9.4/samples/dialogs .... + g_loopPlayFileToMicIn = static_cast(ctrl)->getLoopPlayFileToMicIn(); + + SetStatusText(wxT("Playing File: ") + fileName + wxT(" to Mic Input") , 0); + g_playFileToMicIn = true; + } +} + +//------------------------------------------------------------------------- +// OnPlayFileFromRadio() +// This puppy "plays" a recorded file into the demodulator input, allowing us +// to replay off air signals. +//------------------------------------------------------------------------- +void MainFrame::OnPlayFileFromRadio(wxCommandEvent& event) +{ + wxUnusedVar(event); + + printf("OnPlayFileFromRadio:: %d\n", (int)g_playFileFromRadio); + if (g_playFileFromRadio) + { + printf("OnPlayFileFromRadio:: Stop\n"); + g_mutexProtectingCallbackData.Lock(); + g_playFileFromRadio = false; + sf_close(g_sfPlayFileFromRadio); + SetStatusText(wxT(""),0); + SetStatusText(wxT(""),1); + g_mutexProtectingCallbackData.Unlock(); + } + else + { + wxString soundFile; + SF_INFO sfInfo; + + wxFileDialog openFileDialog( + this, + wxT("Play File - From Radio"), + wxGetApp().m_playFileFromRadioPath, + wxEmptyString, + wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|") + wxT("All files (*.*)|*.*"), + wxFD_OPEN | wxFD_FILE_MUST_EXIST + ); + + // add the loop check box + openFileDialog.SetExtraControlCreator(&createMyExtraPlayFilePanel); + + if(openFileDialog.ShowModal() == wxID_CANCEL) + { + return; // the user changed their mind... + } + + wxString fileName, extension; + soundFile = openFileDialog.GetPath(); + wxFileName::SplitPath(soundFile, &wxGetApp().m_playFileFromRadioPath, &fileName, &extension); + //wxLogDebug("m_playFileToFromRadioPath: %s", wxGetApp().m_playFileFromRadioPath); + sfInfo.format = 0; + + if(!extension.IsEmpty()) + { + extension.LowerCase(); + if(extension == wxT("raw")) + { + sfInfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16; + sfInfo.channels = 1; + sfInfo.samplerate = freedv_get_modem_sample_rate(g_pfreedv); + } + } + g_sfPlayFileFromRadio = sf_open(soundFile.c_str(), SFM_READ, &sfInfo); + g_sfFs = sfInfo.samplerate; + if(g_sfPlayFileFromRadio == NULL) + { + wxString strErr = sf_strerror(NULL); + wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK); + return; + } + + wxWindow * const ctrl = openFileDialog.GetExtraControl(); + + // Huh?! I just copied wxWidgets-2.9.4/samples/dialogs .... + g_loopPlayFileFromRadio = static_cast(ctrl)->getLoopPlayFileToMicIn(); + + SetStatusText(wxT("Playing into from radio"), 0); + if(extension == wxT("raw")) { + wxString stringnumber = wxString::Format(wxT("%d"), (int)sfInfo.samplerate); + SetStatusText(wxT("raw file assuming Fs=") + stringnumber, 1); + } + fprintf(g_logfile, "OnPlayFileFromRadio:: Playing File\n"); + g_playFileFromRadio = true; + g_blink = 0.0; + } +} + +// extra panel added to file save dialog to set number of seconds to record for + +MyExtraRecFilePanel::MyExtraRecFilePanel(wxWindow *parent): wxPanel(parent) +{ + wxBoxSizer *sizerTop = new wxBoxSizer(wxHORIZONTAL); + + wxStaticText* staticText = new wxStaticText(this, wxID_ANY, _("Seconds:"), wxDefaultPosition, wxDefaultSize, 0); + sizerTop->Add(staticText, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5); + m_secondsToRecord = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0); + m_secondsToRecord->SetToolTip(_("Number of seconds to record for")); + m_secondsToRecord->SetValue(wxString::Format(wxT("%i"), wxGetApp().m_recFileFromRadioSecs)); + sizerTop->Add(m_secondsToRecord, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5); + SetSizerAndFit(sizerTop); +} + +static wxWindow* createMyExtraRecFilePanel(wxWindow *parent) +{ + return new MyExtraRecFilePanel(parent); +} + +//------------------------------------------------------------------------- +// OnRecFileFromRadio() +//------------------------------------------------------------------------- +void MainFrame::OnRecFileFromRadio(wxCommandEvent& event) +{ + wxUnusedVar(event); + + if (g_recFileFromRadio) { + printf("Stopping Record....\n"); + g_mutexProtectingCallbackData.Lock(); + g_recFileFromRadio = false; + sf_close(g_sfRecFile); + SetStatusText(wxT("")); + g_mutexProtectingCallbackData.Unlock(); + } + else { + + wxString soundFile; + SF_INFO sfInfo; + + wxFileDialog openFileDialog( + this, + wxT("Record File From Radio"), + wxGetApp().m_recFileFromRadioPath, + wxEmptyString, + wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|") + wxT("All files (*.*)|*.*"), + wxFD_SAVE + ); + + // add the loop check box + openFileDialog.SetExtraControlCreator(&createMyExtraRecFilePanel); + + if(openFileDialog.ShowModal() == wxID_CANCEL) + { + return; // the user changed their mind... + } + + wxString fileName, extension; + soundFile = openFileDialog.GetPath(); + wxFileName::SplitPath(soundFile, &wxGetApp().m_recFileFromRadioPath, &fileName, &extension); + wxLogDebug("m_recFileFromRadioPath: %s", wxGetApp().m_recFileFromRadioPath); + wxLogDebug("soundFile: %s", soundFile); + sfInfo.format = 0; + + if(!extension.IsEmpty()) + { + extension.LowerCase(); + if(extension == wxT("raw")) + { + sfInfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16; + sfInfo.channels = 1; + sfInfo.samplerate = freedv_get_modem_sample_rate(g_pfreedv); + } + else if(extension == wxT("wav")) + { + sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + sfInfo.channels = 1; + sfInfo.samplerate = freedv_get_modem_sample_rate(g_pfreedv); + } else { + wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK); + return; + } + } + else { + wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK); + return; + } + + // Bug: on Win32 I cant read m_recFileFromRadioSecs, so have hard coded it +#ifdef __WIN32__ + long secs = wxGetApp().m_recFileFromRadioSecs; + g_recFromRadioSamples = FS*(unsigned int)secs; +#else + // work out number of samples to record + + wxWindow * const ctrl = openFileDialog.GetExtraControl(); + wxString secsString = static_cast(ctrl)->getSecondsToRecord(); + wxLogDebug("test: %s secsString: %s\n", wxT("testing 123"), secsString); + + long secs; + if (secsString.ToLong(&secs)) { + wxGetApp().m_recFileFromRadioSecs = (unsigned int)secs; + //printf(" secondsToRecord: %d\n", (unsigned int)secs); + g_recFromRadioSamples = FS*(unsigned int)secs; + //printf("g_recFromRadioSamples: %d\n", g_recFromRadioSamples); + } + else { + wxMessageBox(wxT("Invalid number of Seconds"), wxT("Record File From Radio"), wxOK); + return; + } +#endif + + g_sfRecFile = sf_open(soundFile.c_str(), SFM_WRITE, &sfInfo); + if(g_sfRecFile == NULL) + { + wxString strErr = sf_strerror(NULL); + wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK); + return; + } + + SetStatusText(wxT("Recording File: ") + fileName + wxT(" From Radio") , 0); + g_recFileFromRadio = true; + } + +} + +//------------------------------------------------------------------------- +// OnExit() +//------------------------------------------------------------------------- +void MainFrame::OnExit(wxCommandEvent& event) +{ + fprintf(stderr, "MainFrame::OnExit\n"); + wxUnusedVar(event); +#ifdef _USE_TIMER + m_plotTimer.Stop(); +#endif // _USE_TIMER + if(g_sfPlayFile != NULL) + { + sf_close(g_sfPlayFile); + g_sfPlayFile = NULL; + } + if(g_sfRecFile != NULL) + { + sf_close(g_sfRecFile); + g_sfRecFile = NULL; + } + if(m_RxRunning) + { + stopRxStream(); + } + m_togBtnSplit->Disable(); + //m_togRxID->Disable(); + //m_togTxID->Disable(); + m_togBtnAnalog->Disable(); + //m_togBtnALC->Disable(); + //m_btnTogPTT->Disable(); + Pa_Terminate(); + Destroy(); +} + +//------------------------------------------------------------------------- +// OnExitClick() +//------------------------------------------------------------------------- +void MainFrame::OnExitClick(wxCommandEvent& event) +{ + OnExit(event); +} + +//------------------------------------------------------------------------- +// OnToolsAudio() +//------------------------------------------------------------------------- +void MainFrame::OnToolsAudio(wxCommandEvent& event) +{ + wxUnusedVar(event); + int rv = 0; + AudioOptsDialog *dlg = new AudioOptsDialog(NULL); + rv = dlg->ShowModal(); + if(rv == wxID_OK) + { + dlg->ExchangeData(EXCHANGE_DATA_OUT); + } + delete dlg; +} + +//------------------------------------------------------------------------- +// OnToolsAudioUI() +//------------------------------------------------------------------------- +void MainFrame::OnToolsAudioUI(wxUpdateUIEvent& event) +{ + event.Enable(!m_RxRunning); +} + +//------------------------------------------------------------------------- +// OnToolsFilter() +//------------------------------------------------------------------------- +void MainFrame::OnToolsFilter(wxCommandEvent& event) +{ + wxUnusedVar(event); + FilterDlg *dlg = new FilterDlg(NULL, m_RxRunning, &m_newMicInFilter, &m_newSpkOutFilter); + dlg->ShowModal(); + delete dlg; +} + +//------------------------------------------------------------------------- +// OnToolsOptions() +//------------------------------------------------------------------------- +void MainFrame::OnToolsOptions(wxCommandEvent& event) +{ + wxUnusedVar(event); + g_modal = true; + //fprintf(stderr,"g_modal: %d\n", g_modal); + optionsDlg->Show(); +} + +//------------------------------------------------------------------------- +// OnToolsOptionsUI() +//------------------------------------------------------------------------- +void MainFrame::OnToolsOptionsUI(wxUpdateUIEvent& event) +{ +} + +//------------------------------------------------------------------------- +// OnToolsComCfg() +//------------------------------------------------------------------------- +void MainFrame::OnToolsComCfg(wxCommandEvent& event) +{ + wxUnusedVar(event); + + ComPortsDlg *dlg = new ComPortsDlg(NULL); + + dlg->ShowModal(); + + delete dlg; +} + +//------------------------------------------------------------------------- +// OnToolsComCfgUI() +//------------------------------------------------------------------------- +void MainFrame::OnToolsComCfgUI(wxUpdateUIEvent& event) +{ + event.Enable(!m_RxRunning); +} + +//------------------------------------------------------------------------- +// OnToolsPlugInCfg() +//------------------------------------------------------------------------- +void MainFrame::OnToolsPlugInCfg(wxCommandEvent& event) +{ + wxUnusedVar(event); + PlugInDlg *dlg = new PlugInDlg(wxGetApp().m_plugInName, wxGetApp().m_numPlugInParam, wxGetApp().m_plugInParamName); + dlg->ShowModal(); + delete dlg; +} + +void MainFrame::OnToolsPlugInCfgUI(wxUpdateUIEvent& event) +{ + event.Enable(!m_RxRunning && wxGetApp().m_plugIn); +} + + +//------------------------------------------------------------------------- +// OnHelpCheckUpdates() +//------------------------------------------------------------------------- +void MainFrame::OnHelpCheckUpdates(wxCommandEvent& event) +{ + wxMessageBox("Got Click!", "OnHelpCheckUpdates", wxOK); + event.Skip(); +} + +//------------------------------------------------------------------------- +// OnHelpCheckUpdatesUI() +//------------------------------------------------------------------------- +void MainFrame::OnHelpCheckUpdatesUI(wxUpdateUIEvent& event) +{ + event.Enable(false); +} + +//------------------------------------------------------------------------- +//OnHelpAbout() +//------------------------------------------------------------------------- +void MainFrame::OnHelpAbout(wxCommandEvent& event) +{ + wxUnusedVar(event); + wxString msg; + msg.Printf( wxT("FreeDV %s\n\n") + wxT("Open Source Digital Voice\n\n") + wxT("For Help and Support visit: http://freedv.org\n\n") + + wxT("GNU Public License V2.1\n\n") + wxT("Copyright (c) David Witten KD0EAG and David Rowe VK5DGR\n\n") + wxT("svn revision: %s\n"), FREEDV_VERSION, SVN_REVISION); + + wxMessageBox(msg, wxT("About"), wxOK | wxICON_INFORMATION, this); +} + + +// Attempt to talk to rig using Hamlib + +bool MainFrame::OpenHamlibRig() { + if (wxGetApp().m_boolHamlibUseForPTT != true) + return false; + if (wxGetApp().m_intHamlibRig == 0) + return false; + if (wxGetApp().m_hamlib == NULL) + return false; + + int rig = wxGetApp().m_intHamlibRig; + wxString port = wxGetApp().m_strHamlibSerialPort; + int serial_rate = wxGetApp().m_intHamlibSerialRate; + bool status = wxGetApp().m_hamlib->connect(rig, port.mb_str(wxConvUTF8), serial_rate); + if (status == false) + wxMessageBox("Couldn't connect to Radio with hamlib", wxT("Error"), wxOK | wxICON_ERROR, this); + + return status; +} + +//------------------------------------------------------------------------- +// OnTogBtnOnOff() +//------------------------------------------------------------------------- +void MainFrame::OnTogBtnOnOff(wxCommandEvent& event) +{ + wxString startStop = m_togBtnOnOff->GetLabel(); + + // we are attempting to start + + if (startStop.IsSameAs("Start")) + { + // + // Start Running ------------------------------------------------- + // + + // modify some button states when running + + m_togBtnSplit->Enable(); + m_togBtnAnalog->Enable(); + m_togBtnOnOff->SetLabel(wxT("Stop")); + m_btnTogPTT->Enable(); + m_togBtnVoiceKeyer->Enable(); + vk_state = VK_IDLE; + + m_rb1600->Disable(); + //m_rb700b->Disable(); + m_rb700c->Disable(); + m_rb800xa->Disable(); + if (m_rbPlugIn != NULL) + m_rbPlugIn->Disable(); + + // determine what mode we are using + + if (m_rb1600->GetValue()) { + g_mode = FREEDV_MODE_1600; + g_Nc = 16; + m_panelScatter->setNc(g_Nc+1); /* +1 for BPSK pilot */ + } + #ifdef DISABLED + if (m_rb700b->GetValue()) { + g_mode = FREEDV_MODE_700B; + g_Nc = 14; + if (wxGetApp().m_FreeDV700Combine) { + m_panelScatter->setNc(g_Nc/2); /* diversity combnation */ + } + else { + m_panelScatter->setNc(g_Nc); + } + } + #endif + if (m_rb700c->GetValue()) { + g_mode = FREEDV_MODE_700C; + g_Nc = 14; + if (wxGetApp().m_FreeDV700Combine) { + m_panelScatter->setNc(g_Nc/2); /* diversity combnation */ + } + else { + m_panelScatter->setNc(g_Nc); + } + } + if (m_rb800xa->GetValue()) { + g_mode = FREEDV_MODE_800XA; + } + if (m_rbPlugIn != NULL) { + if (m_rbPlugIn->GetValue()) { + g_mode = -1; /* TODO; a better way of handling (enumarating?) non-freedv modes */ + + /* scale plots assuming Fs = 8000 Hz for now */ + + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ)/8000.0); + m_panelWaterfall->setFs(8000.0); + + (wxGetApp().m_plugin_startfp)(wxGetApp().m_plugInStates); + } + } + + if (g_mode != -1) { + // init freedv states + + g_pfreedv = freedv_open(g_mode); + freedv_set_callback_txt(g_pfreedv, &my_put_next_rx_char, &my_get_next_tx_char, NULL); + + freedv_set_callback_error_pattern(g_pfreedv, my_freedv_put_error_pattern, (void*)m_panelTestFrameErrors); + g_error_pattern_fifo = fifo_create(2*freedv_get_sz_error_pattern(g_pfreedv)+1); + g_error_hist = new short[FDMDV_NC_MAX*2]; + g_error_histn = new short[FDMDV_NC_MAX*2]; + int i; + for(i=0; i<2*FDMDV_NC_MAX; i++) { + g_error_hist[i] = 0; + g_error_histn[i] = 0; + } + + assert(g_pfreedv != NULL); + + // init Codec 2 LPC Post Filter + + codec2_set_lpc_post_filter(freedv_get_codec2(g_pfreedv), + wxGetApp().m_codec2LPCPostFilterEnable, + wxGetApp().m_codec2LPCPostFilterBassBoost, + wxGetApp().m_codec2LPCPostFilterBeta, + wxGetApp().m_codec2LPCPostFilterGamma); + + // Init Speex pre-processor states + // by inspecting Speex source it seems that only denoiser is on be default + + g_speex_st = speex_preprocess_state_init(freedv_get_n_speech_samples(g_pfreedv), FS); + + // adjust spectrum and waterfall freq scaling base on mode + + m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedv_get_modem_sample_rate(g_pfreedv)/2))); + m_panelWaterfall->setFs(freedv_get_modem_sample_rate(g_pfreedv)); + + // Init text msg decoding + + freedv_set_varicode_code_num(g_pfreedv, wxGetApp().m_textEncoding); + } + + modem_stats_open(&g_stats); + g_State = g_prev_State = 0; + g_snr = 0.0; + g_half_duplex = wxGetApp().m_boolHalfDuplex; + + if (g_mode == FREEDV_MODE_800XA) { + m_panelScatter->setEyeScatter(PLOT_SCATTER_MODE_EYE); + } + else { + m_panelScatter->setEyeScatter(PLOT_SCATTER_MODE_SCATTER); + } + + m_pcallsign = m_callsign; + memset(m_callsign, 0, sizeof(m_callsign)); +#ifdef __UDP_EXPERIMENTAL__ + m_checksumGood = m_checksumBad = 0; + wxString s; + s.Printf("%d", m_checksumGood); + m_txtChecksumGood->SetLabel(s); + s.Printf("%d", m_checksumBad); + m_txtChecksumBad->SetLabel(s); +#endif + + m_maxLevel = 0; + m_textLevel->SetLabel(wxT("")); + m_gaugeLevel->SetValue(0); + + //printf("m_textEncoding = %d\n", wxGetApp().m_textEncoding); + //printf("g_stats.snr: %f\n", g_stats.snr_est); + + // attempt to start PTT ...... + + if (wxGetApp().m_boolHamlibUseForPTT) + OpenHamlibRig(); + if (wxGetApp().m_boolUseSerialPTT) { + OpenSerialPort(); + } + + // attempt to start sound cards and tx/rx processing + + startRxStream(); + + if (m_RxRunning) + { +#ifdef _USE_TIMER + m_plotTimer.Start(_REFRESH_TIMER_PERIOD, wxTIMER_CONTINUOUS); +#endif // _USE_TIMER + } +#ifdef __UDP_EXPERIMENTAL__ + char e[80]; sprintf(e,"start"); processTxtEvent(e); +#endif + } + + // Stop was pressed or start up failed + + if (startStop.IsSameAs("Stop") || !m_RxRunning ) { + + // + // Stop Running ------------------------------------------------- + // + +#ifdef __UDP_EXPERIMENTAL__ + optionsDlg->SetSpamTimerLight(false); +#endif + +#ifdef _USE_TIMER + m_plotTimer.Stop(); +#endif // _USE_TIMER + + // ensure we are not transmitting and shut down audio processing + + if (wxGetApp().m_boolHamlibUseForPTT) { + Hamlib *hamlib = wxGetApp().m_hamlib; + wxString hamlibError; + if (wxGetApp().m_boolHamlibUseForPTT && hamlib != NULL) { + if (hamlib->ptt(false, hamlibError) == false) { + wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this); + } + hamlib->close(); + } + } + + if (wxGetApp().m_boolUseSerialPTT) { + CloseSerialPort(); + } + + m_btnTogPTT->SetValue(false); + VoiceKeyerProcessEvent(VK_SPACE_BAR); + + stopRxStream(); + modem_stats_close(&g_stats); + + // free up states, clean up + + if (g_mode == -1) { + // PlugIn clean up + (wxGetApp().m_plugin_stopfp)(wxGetApp().m_plugInStates); + } + else { + // FreeDV clean up + delete g_error_hist; + delete g_error_histn; + fifo_destroy(g_error_pattern_fifo); + freedv_close(g_pfreedv); + speex_preprocess_state_destroy(g_speex_st); + } + + m_newMicInFilter = m_newSpkOutFilter = true; + + m_togBtnSplit->Disable(); + //m_togRxID->Disable(); + //m_togTxID->Disable(); + m_togBtnAnalog->Disable(); + m_btnTogPTT->Disable(); + m_togBtnVoiceKeyer->Disable(); + m_togBtnOnOff->SetLabel(wxT("Start")); + m_rb1600->Enable(); + //m_rb700b->Enable(); + m_rb700c->Enable(); + m_rb800xa->Enable(); + if (m_rbPlugIn != NULL) + m_rbPlugIn->Enable(); + +#ifdef DISABLED_FEATURE + m_rb700->Enable(); + m_rb1400old->Enable(); + m_rb1600Wide->Enable(); + m_rb1400->Enable(); + m_rb2000->Enable(); +#endif +#ifdef __UDP_EXPERIMENTAL__ + char e[80]; sprintf(e,"stop"); processTxtEvent(e); +#endif + } +} + +//------------------------------------------------------------------------- +// stopRxStream() +//------------------------------------------------------------------------- +void MainFrame::stopRxStream() +{ + if(m_RxRunning) + { + m_RxRunning = false; + + fprintf(stderr, "waiting for thread to stop\n"); + m_txRxThread->m_run = 0; + m_txRxThread->Wait(); + fprintf(stderr, "thread stopped\n"); + + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + if(m_rxOutPa != m_rxInPa) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + + if (g_nSoundCards == 2) { + m_txInPa->stop(); + m_txInPa->streamClose(); + delete m_txInPa; + if(m_txInPa != m_txOutPa) { + m_txOutPa->stop(); + m_txOutPa->streamClose(); + delete m_txOutPa; + } + } + + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + } +} + +void MainFrame::destroy_fifos(void) +{ + fifo_destroy(g_rxUserdata->infifo1); + fifo_destroy(g_rxUserdata->outfifo1); + fifo_destroy(g_rxUserdata->infifo2); + fifo_destroy(g_rxUserdata->outfifo2); + fifo_destroy(g_rxUserdata->rxinfifo); + fifo_destroy(g_rxUserdata->rxoutfifo); +} + +void MainFrame::destroy_src(void) +{ + src_delete(g_rxUserdata->insrc1); + src_delete(g_rxUserdata->outsrc1); + src_delete(g_rxUserdata->insrc2); + src_delete(g_rxUserdata->outsrc2); + src_delete(g_rxUserdata->insrcsf); +} + +void MainFrame::initPortAudioDevice(PortAudioWrap *pa, int inDevice, int outDevice, + int soundCard, int sampleRate, int inputChannels) +{ + // Note all of the wrapper functions below just set values in a + // portaudio struct so can't return any errors. So no need to trap + // any errors in this function. + + // init input params + + pa->setInputDevice(inDevice); + if(inDevice != paNoDevice) { + pa->setInputChannelCount(inputChannels); // stereo input + pa->setInputSampleFormat(PA_SAMPLE_TYPE); + pa->setInputLatency(pa->getInputDefaultLowLatency()); + pa->setInputHostApiStreamInfo(NULL); + } + + pa->setOutputDevice(paNoDevice); + + // init output params + + pa->setOutputDevice(outDevice); + if(outDevice != paNoDevice) { + pa->setOutputChannelCount(2); // stereo output + pa->setOutputSampleFormat(PA_SAMPLE_TYPE); + pa->setOutputLatency(pa->getOutputDefaultLowLatency()); + pa->setOutputHostApiStreamInfo(NULL); + } + + // init params that affect input and output + + /* + On Linux, setting this to wxGetApp().m_framesPerBuffer caused + intermittant break up on the audio from my IC7200 on Ubuntu 14. + After a day of bug hunting I found that 0, as recommended by the + PortAudio docunmentation, fixed the problem. + */ + + //pa->setFramesPerBuffer(wxGetApp().m_framesPerBuffer); + pa->setFramesPerBuffer(0); + + pa->setSampleRate(sampleRate); + pa->setStreamFlags(paClipOff); +} + +//------------------------------------------------------------------------- +// startRxStream() +//------------------------------------------------------------------------- +void MainFrame::startRxStream() +{ + int src_error; + const PaDeviceInfo *deviceInfo1 = NULL, *deviceInfo2 = NULL; + int inputChannels1, inputChannels2; + bool two_rx=false; + bool two_tx=false; + + if(!m_RxRunning) { + m_RxRunning = true; + + if(Pa_Initialize()) + { + wxMessageBox(wxT("Port Audio failed to initialize"), wxT("Pa_Initialize"), wxOK); + } + + m_rxInPa = new PortAudioWrap(); + if(g_soundCard1InDeviceNum != g_soundCard1OutDeviceNum) + two_rx=true; + if(g_soundCard2InDeviceNum != g_soundCard2OutDeviceNum) + two_tx=true; + + fprintf(g_logfile, "two_rx: %d two_tx: %d\n", two_rx, two_tx); + if(two_rx) + m_rxOutPa = new PortAudioWrap(); + else + m_rxOutPa = m_rxInPa; + + if (g_nSoundCards == 0) { + wxMessageBox(wxT("No Sound Cards configured, use Tools - Audio Config to configure"), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + m_RxRunning = false; + return; + } + + // Init Sound card 1 ---------------------------------------------- + // sanity check on sound card device numbers + + if ((m_rxInPa->getDeviceCount() <= g_soundCard1InDeviceNum) || + (m_rxOutPa->getDeviceCount() <= g_soundCard1OutDeviceNum)) { + wxMessageBox(wxT("Sound Card 1 not present"), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + m_RxRunning = false; + return; + } + + // work out how many input channels this device supports. + + deviceInfo1 = Pa_GetDeviceInfo(g_soundCard1InDeviceNum); + if (deviceInfo1 == NULL) { + wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card 1"), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + m_RxRunning = false; + return; + } + if (deviceInfo1->maxInputChannels == 1) + inputChannels1 = 1; + else + inputChannels1 = 2; + + if(two_rx) { + initPortAudioDevice(m_rxInPa, g_soundCard1InDeviceNum, paNoDevice, 1, + g_soundCard1SampleRate, inputChannels1); + initPortAudioDevice(m_rxOutPa, paNoDevice, g_soundCard1OutDeviceNum, 1, + g_soundCard1SampleRate, inputChannels1); + } + else + initPortAudioDevice(m_rxInPa, g_soundCard1InDeviceNum, g_soundCard1OutDeviceNum, 1, + g_soundCard1SampleRate, inputChannels1); + + // Init Sound Card 2 ------------------------------------------------ + + if (g_nSoundCards == 2) { + + m_txInPa = new PortAudioWrap(); + if(two_tx) + m_txOutPa = new PortAudioWrap(); + else + m_txOutPa = m_txInPa; + + // sanity check on sound card device numbers + + //printf("m_txInPa->getDeviceCount(): %d\n", m_txInPa->getDeviceCount()); + //printf("g_soundCard2InDeviceNum: %d\n", g_soundCard2InDeviceNum); + //printf("g_soundCard2OutDeviceNum: %d\n", g_soundCard2OutDeviceNum); + + if ((m_txInPa->getDeviceCount() <= g_soundCard2InDeviceNum) || + (m_txOutPa->getDeviceCount() <= g_soundCard2OutDeviceNum)) { + wxMessageBox(wxT("Sound Card 2 not present"), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + m_RxRunning = false; + return; + } + + deviceInfo2 = Pa_GetDeviceInfo(g_soundCard2InDeviceNum); + if (deviceInfo2 == NULL) { + wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card 1"), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + m_RxRunning = false; + return; + } + if (deviceInfo2->maxInputChannels == 1) + inputChannels2 = 1; + else + inputChannels2 = 2; + + if(two_tx) { + initPortAudioDevice(m_txInPa, g_soundCard2InDeviceNum, paNoDevice, 2, + g_soundCard2SampleRate, inputChannels2); + initPortAudioDevice(m_txOutPa, paNoDevice, g_soundCard2OutDeviceNum, 2, + g_soundCard2SampleRate, inputChannels2); + } + else + initPortAudioDevice(m_txInPa, g_soundCard2InDeviceNum, g_soundCard2OutDeviceNum, 2, + g_soundCard2SampleRate, inputChannels2); + } + + // Init call back data structure ---------------------------------------------- + + g_rxUserdata = new paCallBackData; + g_rxUserdata->inputChannels1 = inputChannels1; + if (deviceInfo2 != NULL) + g_rxUserdata->inputChannels2 = inputChannels2; + + // init sample rate conversion states + + g_rxUserdata->insrc1 = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->insrc1 != NULL); + g_rxUserdata->outsrc1 = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->outsrc1 != NULL); + g_rxUserdata->insrc2 = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->insrc2 != NULL); + g_rxUserdata->outsrc2 = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->outsrc2 != NULL); + + g_rxUserdata->insrcsf = src_new(SRC_SINC_FASTEST, 1, &src_error); + assert(g_rxUserdata->insrcsf != NULL); + + // create FIFOs used to interface between different buffer sizes + + g_rxUserdata->infifo1 = fifo_create(8*N48); + g_rxUserdata->outfifo1 = fifo_create(10*N48); + g_rxUserdata->outfifo2 = fifo_create(8*N48); + g_rxUserdata->infifo2 = fifo_create(8*N48); + printf("N48: %d\n", N48); + + g_rxUserdata->rxinfifo = fifo_create(10 * N8); + g_rxUserdata->rxoutfifo = fifo_create(10 * N8); + + // Init Equaliser Filters ------------------------------------------------------ + + m_newMicInFilter = m_newSpkOutFilter = true; + designEQFilters(g_rxUserdata); + g_rxUserdata->micInEQEnable = wxGetApp().m_MicInEQEnable; + g_rxUserdata->spkOutEQEnable = wxGetApp().m_SpkOutEQEnable; + + // optional tone in left channel to reliably trigger vox + + g_rxUserdata->leftChannelVoxTone = wxGetApp().m_leftChannelVoxTone; + g_rxUserdata->voxTonePhase = 0; + + // Start sound card 1 ---------------------------------------------------------- + + m_rxInPa->setUserData(g_rxUserdata); + m_rxErr = m_rxInPa->setCallback(rxCallback); + + m_rxErr = m_rxInPa->streamOpen(); + + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Open/Setup error."), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + + m_rxErr = m_rxInPa->streamStart(); + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Stream Start Error."), wxT("Error"), wxOK); + delete m_rxInPa; + if(two_rx) + delete m_rxOutPa; + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + + // Start separate output stream if needed + + if(two_rx) { + m_rxOutPa->setUserData(g_rxUserdata); + m_rxErr = m_rxOutPa->setCallback(rxCallback); + + m_rxErr = m_rxOutPa->streamOpen(); + + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Second Stream Open/Setup error."), wxT("Error"), wxOK); + delete m_rxInPa; + delete m_rxOutPa; + delete m_txOutPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + + m_rxErr = m_rxOutPa->streamStart(); + if(m_rxErr != paNoError) { + wxMessageBox(wxT("Sound Card 1 Second Stream Start Error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + delete m_rxOutPa; + delete m_txOutPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + } + + // Start sound card 2 ---------------------------------------------------------- + + if (g_nSoundCards == 2) { + + // question: can we use same callback data + // (g_rxUserdata)or both sound card callbacks? Is there a + // chance of them both being called at the same time? We + // could need a mutex ... + + m_txInPa->setUserData(g_rxUserdata); + m_txErr = m_txInPa->setCallback(txCallback); + m_txErr = m_txInPa->streamOpen(); + + if(m_txErr != paNoError) { + fprintf(stderr, "Err: %d\n", m_txErr); + wxMessageBox(wxT("Sound Card 2 Open/Setup error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + m_txErr = m_txInPa->streamStart(); + if(m_txErr != paNoError) { + wxMessageBox(wxT("Sound Card 2 Start Error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + delete m_txInPa; + if(two_tx) + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + + // Start separate output stream if needed + + if (two_tx) { + + // question: can we use same callback data + // (g_rxUserdata)or both sound card callbacks? Is there a + // chance of them both being called at the same time? We + // could need a mutex ... + + m_txOutPa->setUserData(g_rxUserdata); + m_txErr = m_txOutPa->setCallback(txCallback); + m_txErr = m_txOutPa->streamOpen(); + + if(m_txErr != paNoError) { + wxMessageBox(wxT("Sound Card 2 Second Stream Open/Setup error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + delete m_rxInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + m_txInPa->stop(); + m_txInPa->streamClose(); + delete m_txInPa; + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + m_txErr = m_txOutPa->streamStart(); + if(m_txErr != paNoError) { + wxMessageBox(wxT("Sound Card 2 Second Stream Start Error."), wxT("Error"), wxOK); + m_rxInPa->stop(); + m_rxInPa->streamClose(); + m_txInPa->stop(); + m_txInPa->streamClose(); + delete m_txInPa; + if(two_rx) { + m_rxOutPa->stop(); + m_rxOutPa->streamClose(); + delete m_rxOutPa; + } + delete m_txInPa; + delete m_txOutPa; + destroy_fifos(); + destroy_src(); + deleteEQFilters(g_rxUserdata); + delete g_rxUserdata; + m_RxRunning = false; + return; + } + } + } + + // start tx/rx processing thread + + m_txRxThread = new txRxThread; + + if ( m_txRxThread->Create() != wxTHREAD_NO_ERROR ) + { + wxLogError(wxT("Can't create thread!")); + } + + m_txRxThread->SetPriority(WXTHREAD_MAX_PRIORITY); + + if ( m_txRxThread->Run() != wxTHREAD_NO_ERROR ) + { + wxLogError(wxT("Can't start thread!")); + } + + } +} + + +void MainFrame::processTxtEvent(char event[]) { + int rule = 0; + + //printf("processTxtEvent:\n"); + //printf(" event: %s\n", event); + + // process with regexp and issue system command + + // Each regexp in our list is separated by a newline. We want to try all of them. + + wxString event_str(event); + int match_end, replace_end; + match_end = replace_end = 0; + wxString regexp_match_list = wxGetApp().m_events_regexp_match; + wxString regexp_replace_list = wxGetApp().m_events_regexp_replace; + + bool found_match = false; + + while (((match_end = regexp_match_list.Find('\n')) != wxNOT_FOUND) && (rule < MAX_EVENT_RULES)) { + //printf("match_end: %d\n", match_end); + if ((replace_end = regexp_replace_list.Find('\n')) != wxNOT_FOUND) { + //printf("replace_end = %d\n", replace_end); + + // candidate match and replace regexps strings exist, so lets try them + + wxString regexp_match = regexp_match_list.SubString(0, match_end-1); + wxString regexp_replace = regexp_replace_list.SubString(0, replace_end-1); + //printf("match: %s replace: %s\n", (const char *)regexp_match.c_str(), (const char *)regexp_replace.c_str()); + wxRegEx re(regexp_match); + //printf(" checking for match against: %s\n", (const char *)regexp_match.c_str()); + + // if we found a match, lets run the replace regexp and issue the system command + + wxString event_str_rep = event_str; + + if (re.Replace(&event_str_rep, regexp_replace) != 0) { + fprintf(stderr, " found match! event_str: %s\n", (const char *)event_str.c_str()); + found_match = true; + + bool enableSystem = false; + if (wxGetApp().m_events) + enableSystem = true; + + // no syscall if spam timer still running + + if (spamTimer[rule].IsRunning()) { + enableSystem = false; + fprintf(stderr, " spam timer running\n"); + } + + const char *event_out = event_str_rep.ToUTF8(); + wxString event_out_with_return_code; + + if (enableSystem) { + int ret = wxExecute(event_str_rep); + event_out_with_return_code.Printf(_T("%s -> returned %d"), event_out, ret); + spamTimer[rule].Start((wxGetApp().m_events_spam_timer)*1000, wxTIMER_ONE_SHOT); + } + else + event_out_with_return_code.Printf(_T("%s T: %d"), event_out, spamTimer[rule].IsRunning()); + + // update event log GUI if currently displayed + + if (optionsDlg != NULL) { + optionsDlg->updateEventLog(wxString(event), event_out_with_return_code); + } + } + } + regexp_match_list = regexp_match_list.SubString(match_end+1, regexp_match_list.length()); + regexp_replace_list = regexp_replace_list.SubString(replace_end+1, regexp_replace_list.length()); + + rule++; + } + + if ((optionsDlg != NULL) && !found_match) { + optionsDlg->updateEventLog(wxString(event), _("")); + } +} + + +#define SBQ_MAX_ARGS 4 + +void *MainFrame::designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q) +{ + char *arg[SBQ_MAX_ARGS]; + char argstorage[SBQ_MAX_ARGS][80]; + void *sbq; + int i, argc; + + assert((strcmp(filterType, "bass") == 0) || + (strcmp(filterType, "treble") == 0) || + (strcmp(filterType, "equalizer") == 0)); + + for(i=0; isbqMicInBass = designAnEQFilter("bass", wxGetApp().m_MicInBassFreqHz, wxGetApp().m_MicInBassGaindB); + cb->sbqMicInTreble = designAnEQFilter("treble", wxGetApp().m_MicInTrebleFreqHz, wxGetApp().m_MicInTrebleGaindB); + cb->sbqMicInMid = designAnEQFilter("equalizer", wxGetApp().m_MicInMidFreqHz, wxGetApp().m_MicInMidGaindB, wxGetApp().m_MicInMidQ); + } + + // init Spk Out Equaliser Filters + + if (m_newSpkOutFilter) { + //printf("designing new Spk Out filters\n"); + //printf("designEQFilters: wxGetApp().m_SpkOutBassFreqHz: %f\n",wxGetApp().m_SpkOutBassFreqHz); + cb->sbqSpkOutBass = designAnEQFilter("bass", wxGetApp().m_SpkOutBassFreqHz, wxGetApp().m_SpkOutBassGaindB); + cb->sbqSpkOutTreble = designAnEQFilter("treble", wxGetApp().m_SpkOutTrebleFreqHz, wxGetApp().m_SpkOutTrebleGaindB); + cb->sbqSpkOutMid = designAnEQFilter("equalizer", wxGetApp().m_SpkOutMidFreqHz, wxGetApp().m_SpkOutMidGaindB, wxGetApp().m_SpkOutMidQ); + } +} + +void MainFrame::deleteEQFilters(paCallBackData *cb) +{ + if (m_newMicInFilter) { + sox_biquad_destroy(cb->sbqMicInBass); + sox_biquad_destroy(cb->sbqMicInTreble); + sox_biquad_destroy(cb->sbqMicInMid); + } + if (m_newSpkOutFilter) { + sox_biquad_destroy(cb->sbqSpkOutBass); + sox_biquad_destroy(cb->sbqSpkOutTreble); + sox_biquad_destroy(cb->sbqSpkOutMid); + } +} + +// returns number of output samples generated by resampling +int resample(SRC_STATE *src, + short output_short[], + short input_short[], + int output_sample_rate, + int input_sample_rate, + int length_output_short, // maximum output array length in samples + int length_input_short + ) +{ + SRC_DATA src_data; + float input[N48*4]; + float output[N48*4]; + int ret; + + assert(src != NULL); + assert(length_input_short <= N48*4); + assert(length_output_short <= N48*4); + + src_short_to_float_array(input_short, input, length_input_short); + + src_data.data_in = input; + src_data.data_out = output; + src_data.input_frames = length_input_short; + src_data.output_frames = length_output_short; + src_data.end_of_input = 0; + src_data.src_ratio = (float)output_sample_rate/input_sample_rate; + + ret = src_process(src, &src_data); + assert(ret == 0); + + assert(src_data.output_frames_gen <= length_output_short); + src_float_to_short_array(output, output_short, src_data.output_frames_gen); + + return src_data.output_frames_gen; +} + + +// Decimates samples using an algorithm that produces nice plots of +// speech signals at a low sample rate. We want a low sample rate so +// we don't hammer the graphics system too hard. Saves decimated data +// to a fifo for plotting on screen. +void resample_for_plot(struct FIFO *plotFifo, short buf[], int length, int fs) +{ + int decimation = fs/WAVEFORM_PLOT_FS; + int nSamples, sample; + int i, st, en, max, min; + short dec_samples[length]; + + nSamples = length/decimation; + + for(sample = 0; sample < nSamples; sample += 2) + { + st = decimation*sample; + en = decimation*(sample+2); + max = min = 0; + for(i=st; i buf[i]) min = buf[i]; + } + dec_samples[sample] = max; + dec_samples[sample+1] = min; + } + fifo_write(plotFifo, dec_samples, nSamples); +} + +void txRxProcessing() +{ + + paCallBackData *cbData = g_rxUserdata; + + // Buffers re-used by tx and rx processing + // signals in in48k/out48k are at a maximum sample rate of 48k, could be 44.1kHz + // depending on sound hardware. + + short in8k_short[4*N8]; + short in48k_short[4*N48]; + short out8k_short[4*N8]; + short out48k_short[4*N48]; + int nout, samplerate, n_samples; + + //fprintf(g_logfile, "start infifo1: %5d outfifo2: %5d\n", fifo_used(cbData->infifo1), fifo_used(cbData->outfifo2)); + + // FreeDV 700 uses a modem sample rate of 7500 Hz which requires some special treatment + + if (g_analog || g_mode == -1) + samplerate = FS; + else + samplerate = freedv_get_modem_sample_rate(g_pfreedv); + + // + // RX side processing -------------------------------------------- + // + + // while we have enough input samples available ... + + int nsam = g_soundCard1SampleRate * (float)N8/FS; + assert(nsam <= N48); + g_mutexProtectingCallbackData.Lock(); + while ((fifo_read(cbData->infifo1, in48k_short, nsam) == 0) && ((g_half_duplex && !g_tx) || !g_half_duplex)) + { + g_mutexProtectingCallbackData.Unlock(); + unsigned int n8k; + + n8k = resample(cbData->insrc1, in8k_short, in48k_short, samplerate, g_soundCard1SampleRate, N8, nsam); + assert(n8k <= N8); + + // optionally save "from radio" signal (write demod input to file) + // Really useful for testing and development as it allows us + // to repeat tests using off air signals + + g_mutexProtectingCallbackData.Lock(); + if (g_recFileFromRadio && (g_sfRecFile != NULL)) { + //printf("g_recFromRadioSamples: %d n8k: %d \n", g_recFromRadioSamples); + if (g_recFromRadioSamples < n8k) { + sf_write_short(g_sfRecFile, in8k_short, g_recFromRadioSamples); + wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_recFileFromRadioEventId ); + // call stop/start record menu item, should be thread safe + g_parent->GetEventHandler()->AddPendingEvent( event ); + g_recFromRadioSamples = 0; + //printf("finished recording g_recFromRadioSamples: %d n8k: %d!\n", g_recFileFromRadio, n8k); + } + else { + sf_write_short(g_sfRecFile, in8k_short, n8k); + g_recFromRadioSamples -= n8k; + } + } + g_mutexProtectingCallbackData.Unlock(); + + // optionally read "from radio" signal from file (read demod input from file) + + g_mutexProtectingCallbackData.Lock(); + if (g_playFileFromRadio && (g_sfPlayFileFromRadio != NULL)) { + unsigned int nsf = n8k*g_sfFs/samplerate; + short insf_short[nsf]; + unsigned int n = sf_read_short(g_sfPlayFileFromRadio, insf_short, nsf); + n8k = resample(cbData->insrcsf, in8k_short, insf_short, samplerate, g_sfFs, N8, nsf); + //fprintf(g_logfile, "n: %d nsf: %d n8k: %d samplerate: %d\n", n, nsf, n8k, samplerate); + assert(n8k <= N8); + + if (n == 0) { + if (g_loopPlayFileFromRadio) + sf_seek(g_sfPlayFileFromRadio, 0, SEEK_SET); + else { + printf("playFileFromRadio finished, issuing event!\n"); + wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_playFileFromRadioEventId ); + // call stop/start play menu item, should be thread safe + g_parent->GetEventHandler()->AddPendingEvent( event ); + } + } + } + g_mutexProtectingCallbackData.Unlock(); + + resample_for_plot(g_plotDemodInFifo, in8k_short, n8k, samplerate); + + if (g_mode != -1) { + // send latest squelch level to FreeDV API, as it handles squelch internally + + freedv_set_squelch_en(g_pfreedv, g_SquelchActive); + freedv_set_snr_squelch_thresh(g_pfreedv, g_SquelchLevel); + } + + // Optional tone interferer + + if (wxGetApp().m_tone) { + float w = 2.0*M_PI*wxGetApp().m_tone_freq_hz/freedv_get_modem_sample_rate(g_pfreedv); + float s; + unsigned int i; + for(i=0; isnr_squelch_thresh); + + // compute rx spectrum - do here so update rate in constant + + COMP rx_fdm[n8k]; + float rx_spec[MODEM_STATS_NSPEC]; + unsigned int i; + + for(i=0; irxinfifo, in8k_short, n8k); + per_frame_rx_processing(cbData->rxoutfifo, cbData->rxinfifo); + memset(out8k_short, 0, sizeof(short)*N8); + fifo_read(cbData->rxoutfifo, out8k_short, N8); + //printf("out8k_short: %d\n", out8k_short[0]); + } + + + // Optional Spk Out EQ Filtering, need mutex as filter can change at run time + g_mutexProtectingCallbackData.Lock(); + if (cbData->spkOutEQEnable) { + sox_biquad_filter(cbData->sbqSpkOutBass, out8k_short, out8k_short, N8); + sox_biquad_filter(cbData->sbqSpkOutTreble, out8k_short, out8k_short, N8); + sox_biquad_filter(cbData->sbqSpkOutMid, out8k_short, out8k_short, N8); + } + g_mutexProtectingCallbackData.Unlock(); + + resample_for_plot(g_plotSpeechOutFifo, out8k_short, N8, FS); + + g_mutexProtectingCallbackData.Lock(); + if (g_nSoundCards == 1) { + nout = resample(cbData->outsrc2, out48k_short, out8k_short, g_soundCard1SampleRate, FS, N48, N8); + fifo_write(cbData->outfifo1, out48k_short, nout); + } + else { + nout = resample(cbData->outsrc2, out48k_short, out8k_short, g_soundCard2SampleRate, FS, N48, N8); + fifo_write(cbData->outfifo2, out48k_short, nout); + } + } + g_mutexProtectingCallbackData.Unlock(); + + // + // TX side processing -------------------------------------------- + // + + if ((g_mode != -1) && ((g_nSoundCards == 2) && ((g_half_duplex && g_tx) || !g_half_duplex))) { + int ret; + + // Make sure we have q few frames of modulator output + // samples. This also locks the modulator to the sample rate + // of sound card 1. We want to make sure that modulator + // samples are uninterrupted by differences in sample rate + // between this sound card and sound card 2. + + g_mutexProtectingCallbackData.Lock(); + while((unsigned)fifo_used(cbData->outfifo1) < 6*N48) + { + g_mutexProtectingCallbackData.Unlock(); + + int nsam = g_soundCard2SampleRate * freedv_get_n_speech_samples(g_pfreedv)/FS; + assert(nsam <= 4*N48); + + // infifo2 is written to by another sound card so it may + // over or underflow, but we don't realy care. It will + // just result in a short interruption in audio being fed + // to codec2_enc, possibly making a click every now and + // again in the decoded audio at the other end. + + // zero speech input just in case infifo2 underflows + memset(in48k_short, 0, nsam*sizeof(short)); + fifo_read(cbData->infifo2, in48k_short, nsam); + + nout = resample(cbData->insrc2, in8k_short, in48k_short, FS, g_soundCard2SampleRate, 4*N8, nsam); + + // optionally use file for mic input signal + + g_mutexProtectingCallbackData.Lock(); + if (g_playFileToMicIn && (g_sfPlayFile != NULL)) { + int n = sf_read_short(g_sfPlayFile, in8k_short, nout); + //fprintf(stderr, "n: %d nout: %d\n", n, nout); + if (n != nout) { + if (g_loopPlayFileToMicIn) + sf_seek(g_sfPlayFile, 0, SEEK_SET); + else { + wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_playFileToMicInEventId ); + // call stop/start play menu item, should be thread safe + g_parent->GetEventHandler()->AddPendingEvent( event ); + } + } + } + g_mutexProtectingCallbackData.Unlock(); + + // Optional Speex pre-processor for acoustic noise reduction + + if (wxGetApp().m_speexpp_enable) { + speex_preprocess_run(g_speex_st, in8k_short); + } + + // Optional Mic In EQ Filtering, need mutex as filter can change at run time + + g_mutexProtectingCallbackData.Lock(); + if (cbData->micInEQEnable) { + sox_biquad_filter(cbData->sbqMicInBass, in8k_short, in8k_short, nout); + sox_biquad_filter(cbData->sbqMicInTreble, in8k_short, in8k_short, nout); + sox_biquad_filter(cbData->sbqMicInMid, in8k_short, in8k_short, nout); + } + g_mutexProtectingCallbackData.Unlock(); + + resample_for_plot(g_plotSpeechInFifo, in8k_short, nout, FS); + + n_samples = freedv_get_n_nom_modem_samples(g_pfreedv); + + if (g_analog) { + n_samples = freedv_get_n_speech_samples(g_pfreedv); + + // Boost the "from mic" -> "to radio" audio in analog + // mode. The need for the gain was found by + // experiment - analog SSB sounded too quiet compared + // to digital. With digital voice we generally drive + // the "to radio" (SSB radio mic input) at about 25% + // of the peak level for normal SSB voice. So we + // introduce 6dB gain to make analog SSB sound the + // same level as the digital. Watch out for clipping. + for(int i=0; i 32767) out = 32767.0; + if (out < -32767) out = -32767.0; + out8k_short[i] = out; + } + } + else { + COMP tx_fdm[freedv_get_n_nom_modem_samples(g_pfreedv)]; + COMP tx_fdm_offset[freedv_get_n_nom_modem_samples(g_pfreedv)]; + int i; + + if (g_mode == FREEDV_MODE_800XA) { + /* 800XA doesn't support complex output just yet */ + freedv_tx(g_pfreedv, out8k_short, in8k_short); + } + else { + freedv_comptx(g_pfreedv, tx_fdm, in8k_short); + + freq_shift_coh(tx_fdm_offset, tx_fdm, g_TxFreqOffsetHz, freedv_get_modem_sample_rate(g_pfreedv), &g_TxFreqOffsetPhaseRect, freedv_get_n_nom_modem_samples(g_pfreedv)); + for(i=0; ioutsrc1, out48k_short, out8k_short, g_soundCard1SampleRate, samplerate, N48*4, n_samples); + g_mutexProtectingCallbackData.Lock(); + ret = fifo_write(cbData->outfifo1, out48k_short, nout); + //fprintf(stderr,"nout: %d ret: %d N48*4: %d\n", nout, ret, N48*4); + + assert(ret != -1); + } + g_mutexProtectingCallbackData.Unlock(); + } + + //fprintf(g_logfile, " end infifo1: %5d outfifo2: %5d\n", fifo_used(cbData->infifo1), fifo_used(cbData->outfifo2)); + +} + +//---------------------------------------------------------------- +// per_frame_rx_processing() +//---------------------------------------------------------------- + +void per_frame_rx_processing( + FIFO *output_fifo, // decoded speech samples + FIFO *input_fifo + ) +{ + #ifdef OLDSPEC + float rx_spec[MODEM_STATS_NSPEC]; + #endif + int i; + + if (g_mode == -1) { + // PlugIn processing --------------------------------------------------- + + int nin = 160; // TODO: hard code for now - some sort of plugin supplied param in future + short input_buf[nin]; + + while (fifo_read(input_fifo, input_buf, nin) == 0) { + (wxGetApp().m_plugin_rx_samplesfp)(wxGetApp().m_plugInStates, input_buf, nin); + } + + #ifdef OLD_SPEC + COMP rx_fdm[nin]; + + for(i=0; isnr); + + // grab extended stats so we can plot spectrum, scatter diagram etc + + freedv_get_modem_extended_stats(g_pfreedv, &g_stats); + + #ifdef OLD_SPEC + // compute rx spectrum + + modem_stats_get_rx_spectrum(&g_stats, rx_spec, rx_fdm, nin_prev); + + // Average rx spectrum data using a simple IIR low pass filter + + for(i = 0; iinputChannels1) + indata[i] = rptr[0]; + if (fifo_write(cbData->infifo1, indata, framesPerBuffer)) { + //fprintf(g_logfile, "infifo1 full\n"); + } + //fifo_write(cbData->outfifo1, indata, framesPerBuffer); + } + + // OK now set up output samples for this callback + + if(wptr) { + //fprintf(g_logfile,"out %ld %d\n", framesPerBuffer, g_out++); + if (fifo_read(cbData->outfifo1, outdata, framesPerBuffer) == 0) { + + // write signal to both channels + + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + if (cbData->leftChannelVoxTone) { + cbData->voxTonePhase += 2.0*M_PI*VOX_TONE_FREQ/g_soundCard1SampleRate; + cbData->voxTonePhase -= 2.0*M_PI*floor(cbData->voxTonePhase/(2.0*M_PI)); + wptr[0] = VOX_TONE_AMP*cos(cbData->voxTonePhase); + //printf("%f %d\n", cbData->voxTonePhase, wptr[0]); + } + else + wptr[0] = outdata[i]; + + wptr[1] = outdata[i]; + } + } + else { + //fprintf(g_logfile, "outfifo1 empty\n"); + // zero output if no data available + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = 0; + wptr[1] = 0; + } + } + } + + return paContinue; +} + + +//------------------------------------------------------------------------- +// txCallback() +//------------------------------------------------------------------------- +int MainFrame::txCallback( + const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *outTime, + PaStreamCallbackFlags statusFlags, + void *userData + ) +{ + paCallBackData *cbData = (paCallBackData*)userData; + unsigned int i; + short *rptr = (short*)inputBuffer; + short *wptr = (short*)outputBuffer; + short indata[MAX_FPB]; + short outdata[MAX_FPB]; + + wxMutexLocker lock(g_mutexProtectingCallbackData); + + // if (statusFlags) + // printf("cb2 statusFlags: 0x%x\n", (int)statusFlags); + + // assemble a mono buffer and write to FIFO + + assert(framesPerBuffer < MAX_FPB); + + if(rptr) { + for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels2) + indata[i] = rptr[0]; + } + + //#define SC2_LOOPBACK +#ifdef SC2_LOOPBACK + //TODO: This doesn't work unless using the same soundcard! + + if(wptr) { + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = indata[i]; + wptr[1] = indata[i]; + } + } + +#else + if(rptr) { + if (fifo_write(cbData->infifo2, indata, framesPerBuffer)) { + //fprintf(g_logfile, "infifo2 full\n"); + } + } + + // OK now set up output samples for this callback + + if(wptr) { + if (fifo_read(cbData->outfifo2, outdata, framesPerBuffer) == 0) { + + // write signal to both channels */ + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = outdata[i]; + wptr[1] = outdata[i]; + } + } + else { + //fprintf(g_logfile, "outfifo2 empty\n"); + // zero output if no data available + for(i = 0; i < framesPerBuffer; i++, wptr += 2) { + wptr[0] = 0; + wptr[1] = 0; + } + } + } +#endif + return paContinue; +} + +// Callback from plot_spectrum & plot_waterfall. would be nice to +// work out a way to do this without globals. + +void fdmdv2_clickTune(float freq) { + + // The demod is hard-wired to expect a centre frequency of + // FDMDV_FCENTRE. So we want to take the signal centered on the + // click tune freq and re-centre it on FDMDV_FCENTRE. For example + // if the click tune freq is 1500Hz, and FDMDV_CENTRE is 1200 Hz, + // we need to shift the input signal centred on 1500Hz down to + // 1200Hz, an offset of -300Hz. + + // Bit of an "indent" as we are often trying to get it back + // exactly in the centre + + if (fabs(FDMDV_FCENTRE - freq) < 10.0) { + freq = FDMDV_FCENTRE; + fprintf(stderr, "indent!\n"); + } + + if (g_split) { + g_RxFreqOffsetHz = FDMDV_FCENTRE - freq; + } + else { + g_TxFreqOffsetHz = freq - FDMDV_FCENTRE; + g_RxFreqOffsetHz = FDMDV_FCENTRE - freq; + } +} + +//---------------------------------------------------------------- +// OpenSerialPort() +//---------------------------------------------------------------- + +void MainFrame::OpenSerialPort(void) +{ + Serialport *serialport = wxGetApp().m_serialport; + + if(!wxGetApp().m_strRigCtrlPort.IsEmpty()) { + serialport->openport(wxGetApp().m_strRigCtrlPort.c_str(), + wxGetApp().m_boolUseRTS, + wxGetApp().m_boolRTSPos, + wxGetApp().m_boolUseDTR, + wxGetApp().m_boolDTRPos); + if (serialport->isopen()) { + // always start PTT in Rx state + serialport->ptt(false); + } + else { + wxMessageBox("Couldn't open Serial Port", wxT("About"), wxOK | wxICON_ERROR, this); + } + } +} + + +//---------------------------------------------------------------- +// CloseSerialPort() +//---------------------------------------------------------------- + +void MainFrame::CloseSerialPort(void) +{ + Serialport *serialport = wxGetApp().m_serialport; + if (serialport->isopen()) { + // always end with PTT in rx state + + serialport->ptt(false); + serialport->closeport(); + } +} + + +#ifdef __UDP_SUPPORT__ + +//---------------------------------------------------------------- +// PollUDP() - see if any commands on UDP port +//---------------------------------------------------------------- + +// test this puppy with netcat: +// $ echo "hello" | nc -u -q1 localhost 3000 + +int MainFrame::PollUDP(void) +{ + // this will block until message received, so we put it in it's own thread + + char buf[1024]; + char reply[80]; + size_t n = m_udp_sock->RecvFrom(m_udp_addr, buf, sizeof(buf)).LastCount(); + + if (n) { + wxString bufstr = wxString::From8BitData(buf, n); + bufstr.Trim(); + wxString ipaddr = m_udp_addr.IPAddress(); + printf("Received: \"%s\" from %s:%u\n", + (const char *)bufstr.c_str(), + (const char *)ipaddr.c_str(), m_udp_addr.Service()); + + // for security only accept commands from local host + + sprintf(reply,"nope\n"); + if (ipaddr.Cmp(_("127.0.0.1")) == 0) { + + // process commands + + if (bufstr.Cmp(_("restore")) == 0) { + m_schedule_restore = true; // Make Restore happen in main thread to avoid crashing + sprintf(reply,"ok\n"); + } + + wxString itemToSet, val; + if (bufstr.StartsWith(_("set "), &itemToSet)) { + if (itemToSet.StartsWith("txtmsg ", &val)) { + // note: if options dialog is open this will get overwritten + wxGetApp().m_callSign = val; + } + sprintf(reply,"ok\n"); + } + if (bufstr.StartsWith(_("ptton"), &itemToSet)) { + // note: if options dialog is open this will get overwritten + m_btnTogPTT->SetValue(true); + togglePTT(); + sprintf(reply,"ok\n"); + } + if (bufstr.StartsWith(_("pttoff"), &itemToSet)) { + // note: if options dialog is open this will get overwritten + m_btnTogPTT->SetValue(false); + togglePTT(); + sprintf(reply,"ok\n"); + } + + } + else { + printf("We only accept messages from locahost!\n"); + } + + if ( m_udp_sock->SendTo(m_udp_addr, reply, strlen(reply)).LastCount() != strlen(reply)) { + printf("ERROR: failed to send data\n"); + } + } + + return n; +} + +void MainFrame::startUDPThread(void) { + fprintf(stderr, "starting UDP thread!\n"); + m_UDPThread = new UDPThread; + m_UDPThread->mf = this; + if (m_UDPThread->Create() != wxTHREAD_NO_ERROR ) { + wxLogError(wxT("Can't create thread!")); + } + if (m_UDPThread->Run() != wxTHREAD_NO_ERROR ) { + wxLogError(wxT("Can't start thread!")); + delete m_UDPThread; + } +} + +void MainFrame::stopUDPThread(void) { + printf("stopping UDP thread!\n"); + if ((m_UDPThread != NULL) && m_UDPThread->m_run) { + m_UDPThread->m_run = 0; + m_UDPThread->Wait(); + m_UDPThread = NULL; + } +} + +void *UDPThread::Entry() { + printf("UDP thread started!\n"); + while (m_run) { + if (wxGetApp().m_udp_enable) { + printf("m_udp_enable\n"); + mf->m_udp_addr.Service(wxGetApp().m_udp_port); + mf->m_udp_sock = new wxDatagramSocket(mf->m_udp_addr, wxSOCKET_NOWAIT); + + while (m_run && wxGetApp().m_udp_enable) { + if (mf->PollUDP() == 0) { + wxThread::Sleep(20); + } + } + + delete mf->m_udp_sock; + } + wxThread::Sleep(20); + } + return NULL; +} + +#endif + +char my_get_next_tx_char(void *callback_state) { + short ch = 0; + + fifo_read(g_txDataInFifo, &ch, 1); + //fprintf(stderr, "get_next_tx_char: %c\n", (char)ch); + return (char)ch; +} + +void my_put_next_rx_char(void *callback_state, char c) { + short ch = (short)c; + //fprintf(stderr, "put_next_rx_char: %c\n", (char)c); + fifo_write(g_rxDataOutFifo, &ch, 1); +} + +// Callback from FreeDv API to update error plots + +void my_freedv_put_error_pattern(void *state, short error_pattern[], int sz_error_pattern) { + fifo_write(g_error_pattern_fifo, error_pattern, sz_error_pattern); + //fprintf(stderr, "my_freedv_put_error_pattern: sz_error_pattern: %d ret: %d used: %d\n", + // sz_error_pattern, ret, fifo_used(g_error_pattern_fifo) ); +} + +void freq_shift_coh(COMP rx_fdm_fcorr[], COMP rx_fdm[], float foff, float Fs, COMP *foff_phase_rect, int nin) +{ + COMP foff_rect; + float mag; + int i; + + foff_rect.real = cosf(2.0*M_PI*foff/Fs); + foff_rect.imag = sinf(2.0*M_PI*foff/Fs); + for(i=0; ireal /= mag; + foff_phase_rect->imag /= mag; +} + +int plugin_get_persistant(char name[], char value[]) { + wxString n,v; + int i; + + for(i=0; i. +// +//========================================================================== +#ifndef __FDMDV2_MAIN__ +#define __FDMDV2_MAIN__ + +#include "version.h" +#ifndef _NO_AUTOTOOLS_ +#include "../config.h" +#endif +#include + +#include +#include +#include "wx/rawbmp.h" +#include "wx/file.h" +#include "wx/filename.h" +#include "wx/config.h" +#include +#include "wx/graphics.h" +#include "wx/mstream.h" +#include "wx/wfstream.h" +#include "wx/quantize.h" +#include "wx/scopedptr.h" +#include "wx/stopwatch.h" +#include "wx/versioninfo.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include "codec2.h" +#include "codec2_fifo.h" +#include "modem_stats.h" + +#include "topFrame.h" +#include "dlg_ptt.h" +#include "dlg_options.h" +#include "fdmdv2_plot.h" +#include "fdmdv2_plot_scalar.h" +#include "fdmdv2_plot_scatter.h" +#include "fdmdv2_plot_waterfall.h" +#include "fdmdv2_plot_spectrum.h" +#include "fdmdv2_pa_wrapper.h" +#include "sndfile.h" +#include "portaudio.h" +#include "dlg_audiooptions.h" +#include "dlg_filter.h" +#include "dlg_options.h" +#include "varicode.h" +#include "sox_biquad.h" +#include "comp_prim.h" +#include "dlg_plugin.h" +#include "hamlib.h" +#include "serialport.h" + +#define _USE_TIMER 1 +#define _USE_ONIDLE 1 +#define _DUMMY_DATA 1 +//#define _AUDIO_PASSTHROUGH 1 +#define _REFRESH_TIMER_PERIOD (DT*1000) +//#define _USE_ABOUT_DIALOG 1 + +enum { + ID_START = wxID_HIGHEST, + ID_TIMER_WATERFALL, + ID_TIMER_SPECTRUM, + ID_TIMER_SCATTER, + ID_TIMER_SCALAR + }; + +#define EXCHANGE_DATA_IN 0 +#define EXCHANGE_DATA_OUT 1 + + +extern int g_nSoundCards; +extern int g_soundCard1InDeviceNum; +extern int g_soundCard1OutDeviceNum; +extern int g_soundCard1SampleRate; +extern int g_soundCard2InDeviceNum; +extern int g_soundCard2OutDeviceNum; +extern int g_soundCard2SampleRate; + +// Voice Keyer Constants + +#define VK_SYNC_WAIT_TIME 5.0 + +// Voice Keyer States + +#define VK_IDLE 0 +#define VK_TX 1 +#define VK_RX 2 +#define VK_SYNC_WAIT 3 + +// Voice Keyer Events + +#define VK_START 0 +#define VK_SPACE_BAR 1 +#define VK_PLAY_FINISHED 2 +#define VK_DT 3 +#define VK_SYNC 4 + +class MainFrame; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class MainApp +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class MainApp : public wxApp +{ + public: + virtual bool OnInit(); + virtual int OnExit(); + + wxString m_strVendName; + wxString m_StrAppName; + + wxString m_textNumChOut; + wxString m_textNumChIn; + + wxString m_strRxInAudio; + wxString m_strRxOutAudio; + wxString m_textVoiceInput; + wxString m_textVoiceOutput; + wxString m_strSampleRate; + wxString m_strBitrate; + + // PTT ----------------------------------- + + bool m_boolHalfDuplex; + + wxString m_txtVoiceKeyerWaveFilePath; + wxString m_txtVoiceKeyerWaveFile; + int m_intVoiceKeyerRxPause; + int m_intVoiceKeyerRepeats; + + bool m_boolHamlibUseForPTT; + unsigned int m_intHamlibRig; + wxString m_strHamlibSerialPort; + unsigned int m_intHamlibSerialRate; + Hamlib *m_hamlib; + + bool m_boolUseSerialPTT; + wxString m_strRigCtrlPort; + bool m_boolUseRTS; + bool m_boolRTSPos; + bool m_boolUseDTR; + bool m_boolDTRPos; + Serialport *m_serialport; + + // Play/Rec files + + wxString m_playFileToMicInPath; + wxString m_recFileFromRadioPath; + unsigned int m_recFileFromRadioSecs; + wxString m_playFileFromRadioPath; + + // Options dialog + + wxString m_callSign; + bool m_events; + int m_events_spam_timer; + unsigned int m_textEncoding; + bool m_enable_checksum; + wxString m_events_regexp_match; + wxString m_events_regexp_replace; + + bool m_snrSlow; + + // LPC Post Filter + bool m_codec2LPCPostFilterEnable; + bool m_codec2LPCPostFilterBassBoost; + float m_codec2LPCPostFilterGamma; + float m_codec2LPCPostFilterBeta; + + // Speex Pre-Processor + bool m_speexpp_enable; + + // Mic In Equaliser + float m_MicInBassFreqHz; + float m_MicInBassGaindB; + float m_MicInTrebleFreqHz; + float m_MicInTrebleGaindB; + float m_MicInMidFreqHz; + float m_MicInMidGaindB; + float m_MicInMidQ; + bool m_MicInEQEnable; + + // Spk Out Equaliser + float m_SpkOutBassFreqHz; + float m_SpkOutBassGaindB; + float m_SpkOutTrebleFreqHz; + float m_SpkOutTrebleGaindB; + float m_SpkOutMidFreqHz; + float m_SpkOutMidGaindB; + float m_SpkOutMidQ; + bool m_SpkOutEQEnable; + + // Flags for displaying windows + int m_show_wf; + int m_show_spect; + int m_show_scatter; + int m_show_timing; + int m_show_freq; + int m_show_speech_in; + int m_show_speech_out; + int m_show_demod_in; + int m_show_test_frame_errors; + int m_show_test_frame_errors_hist; + + // optional vox trigger tone + bool m_leftChannelVoxTone; + + // UDP control port + bool m_udp_enable; + int m_udp_port; + + // notebook display after tx->rxtransition + int m_rxNbookCtrl; + + wxRect m_rTopWindow; + + int m_framesPerBuffer; + + bool loadConfig(); + bool saveConfig(); + + // Plugins ----------------------------------- + + wxString m_txtPlugInParam[PLUGIN_MAX_PARAMS]; + + // plugin details + + void *m_plugInHandle; + bool m_plugIn; + wxString m_plugInName; + int m_numPlugInParam; + wxString m_plugInParamName[PLUGIN_MAX_PARAMS]; + void *m_plugInStates; + void (*m_plugin_startfp)(void *); + void (*m_plugin_stopfp)(void *); + void (*m_plugin_rx_samplesfp)(void *, short *, int); + + // misc + + bool m_testFrames; + bool m_channel_noise; + float m_channel_snr_dB; + + int FilterEvent(wxEvent& event); + MainFrame *frame; + + // 700 options + + bool m_FreeDV700txClip; + bool m_FreeDV700Combine; + + // Noise simulation + + int m_noise_snr; + + // carrier attenuation + + bool m_attn_carrier_en; + int m_attn_carrier; + + // tone interferer simulation + + bool m_tone; + int m_tone_freq_hz; + int m_tone_amplitude; + + // Windows debug console + + bool m_debug_console; + protected: +}; + +// declare global static function wxGetApp() +DECLARE_APP(MainApp) + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// paCallBackData +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +typedef struct +{ + // libresample states for 48 to 8 kHz conversions + + SRC_STATE *insrc1; + SRC_STATE *outsrc1; + SRC_STATE *insrc2; + SRC_STATE *outsrc2; + SRC_STATE *insrcsf; + + // FIFOs attached to first sound card + + struct FIFO *infifo1; + struct FIFO *outfifo1; + + // FIFOs attached to second sound card + struct FIFO *infifo2; + struct FIFO *outfifo2; + + // FIFOs for rx process + struct FIFO *rxinfifo; + struct FIFO *rxoutfifo; + + int inputChannels1, inputChannels2; + + // EQ filter states + void *sbqMicInBass; + void *sbqMicInTreble; + void *sbqMicInMid; + void *sbqSpkOutBass; + void *sbqSpkOutTreble; + void *sbqSpkOutMid; + + bool micInEQEnable; + bool spkOutEQEnable; + + // optional loud tone on left channel to reliably trigger vox + bool leftChannelVoxTone; + float voxTonePhase; + +} paCallBackData; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// panel with custom loop checkbox for play file dialog +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class MyExtraPlayFilePanel : public wxPanel +{ +public: + MyExtraPlayFilePanel(wxWindow *parent); + void setLoopPlayFileToMicIn(bool checked) { m_cb->SetValue(checked); } + bool getLoopPlayFileToMicIn(void) { return m_cb->GetValue(); } +private: + wxCheckBox *m_cb; +}; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// panel with custom Seconds-to-record control for record file dialog +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class MyExtraRecFilePanel : public wxPanel +{ +public: + MyExtraRecFilePanel(wxWindow *parent); + ~MyExtraRecFilePanel() + { + wxLogDebug("Destructor\n"); + } + void setSecondsToRecord(wxString value) { m_secondsToRecord->SetValue(value); } + wxString getSecondsToRecord(void) + { + wxLogDebug("getSecondsToRecord: %s\n",m_secondsToRecord->GetValue()); + return m_secondsToRecord->GetValue(); + } +private: + wxTextCtrl *m_secondsToRecord; +}; + +class txRxThread; +class UDPThread; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class MainFrame +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class MainFrame : public TopFrame +{ + public: + MainFrame(wxString plugInName, wxWindow *parent); + virtual ~MainFrame(); + + PlotSpectrum* m_panelSpectrum; + PlotWaterfall* m_panelWaterfall; + PlotScatter* m_panelScatter; + PlotScalar* m_panelTimeOffset; + PlotScalar* m_panelFreqOffset; + PlotScalar* m_panelSpeechIn; + PlotScalar* m_panelSpeechOut; + PlotScalar* m_panelDemodIn; + PlotScalar* m_panelTestFrameErrors; + PlotScalar* m_panelTestFrameErrorsHist; + + bool m_RxRunning; + + PortAudioWrap *m_rxInPa; + PortAudioWrap *m_rxOutPa; + PortAudioWrap *m_txInPa; + PortAudioWrap *m_txOutPa; + + PaError m_rxErr; + PaError m_txErr; + + txRxThread* m_txRxThread; + + bool OpenHamlibRig(); + void OpenSerialPort(void); + void CloseSerialPort(void); + void SerialPTTRx(void); + + bool m_modal; + +#ifdef _USE_TIMER + wxTimer m_plotTimer; +#endif + + void destroy_fifos(void); + void destroy_src(void); + void autoDetectSoundCards(PortAudioWrap *pa); + + static int rxCallback( + const void *inBuffer, + void *outBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *outTime, + PaStreamCallbackFlags statusFlags, + void *userData + ); + + static int txCallback( + const void *inBuffer, + void *outBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *outTime, + PaStreamCallbackFlags statusFlags, + void *userData + ); + + + void initPortAudioDevice(PortAudioWrap *pa, int inDevice, int outDevice, + int soundCard, int sampleRate, int inputChannels); + + void togglePTT(void); + + wxIPV4address m_udp_addr; + wxDatagramSocket *m_udp_sock; + UDPThread *m_UDPThread; + void startUDPThread(void); + void stopUDPThread(void); + int PollUDP(); + bool m_schedule_restore; + + int vk_state; + void VoiceKeyerProcessEvent(int vk_event); + + protected: + + void setsnrBeta(bool snrSlow); + + // protected event handlers + virtual void OnCloseFrame(wxCloseEvent& event); + void OnExitClick(wxCommandEvent& event); + + void startTxStream(); + void startRxStream(); + void stopTxStream(); + void stopRxStream(); + void abortTxStream(); + void abortRxStream(); + + void OnTop(wxCommandEvent& event); + void OnExit( wxCommandEvent& event ); + + void OnToolsAudio( wxCommandEvent& event ); + void OnToolsAudioUI( wxUpdateUIEvent& event ); + void OnToolsComCfg( wxCommandEvent& event ); + void OnToolsComCfgUI( wxUpdateUIEvent& event ); + void OnToolsFilter( wxCommandEvent& event ); + void OnToolsOptions(wxCommandEvent& event); + void OnToolsOptionsUI(wxUpdateUIEvent& event); + + void OnToolsPlugInCfg( wxCommandEvent& event ); + void OnToolsPlugInCfgUI( wxUpdateUIEvent& event ); + + void OnPlayFileToMicIn( wxCommandEvent& event ); + void StopPlayFileToMicIn(void); + void OnRecFileFromRadio( wxCommandEvent& event ); + void OnPlayFileFromRadio( wxCommandEvent& event ); + + void OnHelpCheckUpdates( wxCommandEvent& event ); + void OnHelpCheckUpdatesUI( wxUpdateUIEvent& event ); + void OnHelpAbout( wxCommandEvent& event ); + void OnCmdSliderScroll( wxScrollEvent& event ); +// void OnSliderScrollBottom( wxScrollEvent& event ); +// void OnCmdSliderScrollChanged( wxScrollEvent& event ); +// void OnSliderScrollTop( wxScrollEvent& event ); + void OnCheckSQClick( wxCommandEvent& event ); + void OnCheckSNRClick( wxCommandEvent& event ); + + // Toggle Buttons + void OnTogBtnSplitClick(wxCommandEvent& event); + void OnTogBtnAnalogClick(wxCommandEvent& event); + void OnTogBtnRxID( wxCommandEvent& event ); + void OnTogBtnTxID( wxCommandEvent& event ); + void OnTogBtnPTT( wxCommandEvent& event ); + void OnTogBtnVoiceKeyerClick (wxCommandEvent& event); + void OnTogBtnOnOff( wxCommandEvent& event ); + + void OnCallSignReset( wxCommandEvent& event ); + void OnBerReset( wxCommandEvent& event ); + + //System Events + void OnPaint(wxPaintEvent& event); + void OnSize( wxSizeEvent& event ); + void OnUpdateUI( wxUpdateUIEvent& event ); + void OnDeleteConfig(wxCommandEvent&); +#ifdef _USE_TIMER + void OnTimer(wxTimerEvent &evt); +#endif +#ifdef _USE_ONIDLE + void OnIdle(wxIdleEvent &evt); +#endif + + int VoiceKeyerStartTx(void); + + private: + bool m_useMemory; + wxTextCtrl* m_tc; + int m_zoom; + float m_snrBeta; + + // Callsign/text messaging + char m_callsign[MAX_CALLSIGN]; + char *m_pcallsign; + unsigned int m_checksumGood; + unsigned int m_checksumBad; + + // Events + void processTxtEvent(char event[]); + class OptionsDlg *optionsDlg; + wxTimer spamTimer[MAX_EVENT_RULES]; + + // level Gauge + float m_maxLevel; + + // flags to indicate when new EQ filters need to be designed + + bool m_newMicInFilter; + bool m_newSpkOutFilter; + + void* designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q = 0.0); + void designEQFilters(paCallBackData *cb); + void deleteEQFilters(paCallBackData *cb); + + // Voice Keyer States + + int vk_rx_pause; + int vk_repeats, vk_repeat_counter; + float vk_rx_time; +}; + +void txRxProcessing(); + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// class txRxThread - experimental tx/rx processing thread +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class txRxThread : public wxThread +{ +public: + txRxThread(void) : wxThread(wxTHREAD_JOINABLE) { m_run = 1; } + + // thread execution starts here + void *Entry() + { + while (m_run) + { + txRxProcessing(); + wxThread::Sleep(20); + } + return NULL; + } + + // called when the thread exits - whether it terminates normally or is + // stopped with Delete() (but not when it is Kill()ed!) + void OnExit() { } + +public: + bool m_run; +}; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// class UDPThread - waits for UDP messages +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class UDPThread : public wxThread +{ +public: + UDPThread(void) : wxThread(wxTHREAD_JOINABLE) { m_run = 1; } + + // thread execution starts here + void *Entry(); + + // called when the thread exits - whether it terminates normally or is + // stopped with Delete() (but not when it is Kill()ed!) + void OnExit() { } + +public: + MainFrame *mf; + bool m_run; +}; + +void resample_for_plot(struct FIFO *plotFifo, short buf[], int length, int fs); + +int resample(SRC_STATE *src, + short output_short[], + short input_short[], + int output_sample_rate, + int input_sample_rate, + int length_output_short, // maximum output array length in samples + int length_input_short + ); +void txRxProcessing(); +void per_frame_rx_processing( + FIFO *output_fifo, // decoded speech samples + FIFO *input_fifo // modem samples input to demod + ); + +// FreeDv API calls this when there is a test frame that needs a-plottin' + +void my_freedv_put_error_pattern(void *state, short error_pattern[], int sz_error_pattern); + +// FreeDv API calls these puppies when it needs/receives a text char + +char my_get_next_tx_char(void *callback_state); +void my_put_next_rx_char(void *callback_state, char c); + +// helper complex freq shift function + +void freq_shift_coh(COMP rx_fdm_fcorr[], COMP rx_fdm[], float foff, float Fs, COMP *foff_phase_rect, int nin); + +// Helper function called by plugin + +int plugin_get_persistant(char name[], char value[]); + +#endif //__FDMDV2_MAIN__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.cpp b/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.cpp new file mode 100644 index 00000000..2f67ca26 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.cpp @@ -0,0 +1,324 @@ +//========================================================================== +// Name: fdmdv2_pa_wrapper.cpp +// Purpose: Implements a wrapper class around the PortAudio library. +// Created: August 12, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include "fdmdv2_pa_wrapper.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// PortAudioWrap() +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PortAudioWrap::PortAudioWrap() +{ + m_pStream = NULL; + m_pUserData = NULL; + m_samplerate = 0; + m_framesPerBuffer = 0; + m_statusFlags = 0; + m_pStreamCallback = NULL; + m_pStreamFinishedCallback = NULL; + m_pTimeInfo = 0; + m_newdata = false; + +// loadData(); +} + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// ~PortAudioWrap() +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PortAudioWrap::~PortAudioWrap() +{ +} + +//---------------------------------------------------------------- +// streamOpen() +//---------------------------------------------------------------- +PaError PortAudioWrap::streamOpen() +{ + return Pa_OpenStream( + &m_pStream, + m_inputBuffer.device == paNoDevice ? NULL : &m_inputBuffer, + m_outputBuffer.device == paNoDevice ? NULL : &m_outputBuffer, + m_samplerate, + m_framesPerBuffer, + m_statusFlags, + *m_pStreamCallback, + m_pUserData + ); +} + +//---------------------------------------------------------------- +// streamStart() +//---------------------------------------------------------------- +PaError PortAudioWrap::streamStart() +{ + return Pa_StartStream(m_pStream); +} + +//---------------------------------------------------------------- +// streamClose() +//---------------------------------------------------------------- +PaError PortAudioWrap::streamClose() +{ + if(isOpen()) + { + PaError rv = Pa_CloseStream(m_pStream); + return rv; + } + else + { + return paNoError; + } +} + +//---------------------------------------------------------------- +// terminate() +//---------------------------------------------------------------- +void PortAudioWrap::terminate() +{ + if(Pa_IsStreamStopped(m_pStream) != paNoError) + { + Pa_StopStream(m_pStream); + } + Pa_Terminate(); +} + +//---------------------------------------------------------------- +// stop() +//---------------------------------------------------------------- +void PortAudioWrap::stop() +{ + Pa_StopStream(m_pStream); +} + +//---------------------------------------------------------------- +// abort() +//---------------------------------------------------------------- +void PortAudioWrap::abort() +{ + Pa_AbortStream(m_pStream); +} + +//---------------------------------------------------------------- +// isStopped() +//---------------------------------------------------------------- +bool PortAudioWrap::isStopped() const +{ + PaError ret = Pa_IsStreamStopped(m_pStream); + return ret; +} + +//---------------------------------------------------------------- +// isActive() +//---------------------------------------------------------------- +bool PortAudioWrap::isActive() const +{ + PaError ret = Pa_IsStreamActive(m_pStream); + return ret; +} + +//---------------------------------------------------------------- +// isOpen() +//---------------------------------------------------------------- +bool PortAudioWrap::isOpen() const +{ + return (m_pStream != NULL); +} + +//---------------------------------------------------------------- +// getDefaultInputDevice() +//---------------------------------------------------------------- +PaDeviceIndex PortAudioWrap::getDefaultInputDevice() +{ + return Pa_GetDefaultInputDevice(); +} + +//---------------------------------------------------------------- +// getDefaultOutputDevice() +//---------------------------------------------------------------- +PaDeviceIndex PortAudioWrap::getDefaultOutputDevice() +{ + return Pa_GetDefaultOutputDevice(); +} + +//---------------------------------------------------------------- +// setInputChannelCount() +//---------------------------------------------------------------- +PaError PortAudioWrap::setInputChannelCount(int count) +{ + m_inputBuffer.channelCount = count; + return paNoError; +} + +//---------------------------------------------------------------- +// getInputChannelCount() +//---------------------------------------------------------------- +PaError PortAudioWrap::getInputChannelCount() +{ + return m_inputBuffer.channelCount; +} + +//---------------------------------------------------------------- +// setInputSampleFormat() +//---------------------------------------------------------------- +PaError PortAudioWrap::setInputSampleFormat(PaSampleFormat format) +{ + m_inputBuffer.sampleFormat = format; + return paNoError; +} + +//---------------------------------------------------------------- +// setInputLatency() +//---------------------------------------------------------------- +PaError PortAudioWrap::setInputLatency(PaTime latency) +{ + m_inputBuffer.suggestedLatency = latency; + return paNoError; +} + +//---------------------------------------------------------------- +// setInputHostApiStreamInfo() +//---------------------------------------------------------------- +void PortAudioWrap::setInputHostApiStreamInfo(void *info) +{ + m_inputBuffer.hostApiSpecificStreamInfo = info; +} + +//---------------------------------------------------------------- +// getInputDefaultLowLatency() +//---------------------------------------------------------------- +PaTime PortAudioWrap::getInputDefaultLowLatency() +{ + return Pa_GetDeviceInfo(m_inputBuffer.device)->defaultLowInputLatency; +} + +//---------------------------------------------------------------- +// setOutputChannelCount() +//---------------------------------------------------------------- +PaError PortAudioWrap::setOutputChannelCount(int count) +{ + m_outputBuffer.channelCount = count; + return paNoError; +} + +//---------------------------------------------------------------- +// getOutputChannelCount() +//---------------------------------------------------------------- +const int PortAudioWrap::getOutputChannelCount() +{ + return m_outputBuffer.channelCount; +} + +//---------------------------------------------------------------- +// getDeviceName() +//---------------------------------------------------------------- +const char *PortAudioWrap::getDeviceName(PaDeviceIndex dev) +{ + const PaDeviceInfo *info; + info = Pa_GetDeviceInfo(dev); + return info->name; +} + +//---------------------------------------------------------------- +// setOutputSampleFormat() +//---------------------------------------------------------------- +PaError PortAudioWrap::setOutputSampleFormat(PaSampleFormat format) +{ + m_outputBuffer.sampleFormat = format; + return paNoError; +} + +//---------------------------------------------------------------- +// setOutputLatency() +//---------------------------------------------------------------- +PaError PortAudioWrap::setOutputLatency(PaTime latency) +{ + m_outputBuffer.suggestedLatency = latency; + return paNoError; +} + +//---------------------------------------------------------------- +// setOutputHostApiStreamInfo() +//---------------------------------------------------------------- +void PortAudioWrap::setOutputHostApiStreamInfo(void *info) +{ + m_outputBuffer.hostApiSpecificStreamInfo = info; +} + +//---------------------------------------------------------------- +// getOutputDefaultLowLatency() +//---------------------------------------------------------------- +PaTime PortAudioWrap::getOutputDefaultLowLatency() +{ + return Pa_GetDeviceInfo(m_outputBuffer.device)->defaultLowOutputLatency; +} + +//---------------------------------------------------------------- +// setFramesPerBuffer() +//---------------------------------------------------------------- +PaError PortAudioWrap::setFramesPerBuffer(unsigned long size) +{ + m_framesPerBuffer = size; + return paNoError; +} + +//---------------------------------------------------------------- +// setSampleRate() +//---------------------------------------------------------------- +PaError PortAudioWrap::setSampleRate(unsigned long rate) +{ + m_samplerate = rate; + return paNoError; +} + +//---------------------------------------------------------------- +// setStreamFlags() +//---------------------------------------------------------------- +PaError PortAudioWrap::setStreamFlags(PaStreamFlags flags) +{ + m_statusFlags = flags; + return paNoError; +} + +//---------------------------------------------------------------- +// setInputDevice() +//---------------------------------------------------------------- +PaError PortAudioWrap::setInputDevice(PaDeviceIndex index) +{ + m_inputBuffer.device = index; + return paNoError; +} + +//---------------------------------------------------------------- +// setOutputDevice() +//---------------------------------------------------------------- +PaError PortAudioWrap::setOutputDevice(PaDeviceIndex index) +{ + m_outputBuffer.device = index; + return paNoError; +} + +//---------------------------------------------------------------- +// setCallback() +//---------------------------------------------------------------- +PaError PortAudioWrap::setCallback(PaStreamCallback *callback) +{ + m_pStreamCallback = callback; + return paNoError; +} + diff --git a/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.h b/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.h new file mode 100644 index 00000000..3d216c0d --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_pa_wrapper.h @@ -0,0 +1,115 @@ +//========================================================================== +// Name: fdmdv2_pa_wrapper.h +// Purpose: Defines a wrapper class around PortAudio +// Created: August 12, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include +#include "fdmdv2_defines.h" +#include "codec2_fdmdv.h" +#include "codec2.h" +#include "portaudio.h" + +#define PA_SAMPLE_TYPE paInt16 //paFloat32 +#define FRAMES_PER_BUFFER (64) + +typedef float SAMPLE; + +class PortAudioWrap +{ + public: + PortAudioWrap(); + ~PortAudioWrap(); + +// float m_av_mag[FDMDV_NSPEC]; + + private: + PaStream *m_pStream; + void *m_pUserData; + PaStreamCallback *m_pStreamCallback; + PaStreamFinishedCallback *m_pStreamFinishedCallback; + const PaStreamCallbackTimeInfo *m_pTimeInfo; + struct FDMDV *m_pFDMDV_state; + PaStreamParameters m_inputBuffer; + PaStreamParameters m_outputBuffer; + int m_samplerate; + unsigned long m_framesPerBuffer; + PaStreamCallbackFlags m_statusFlags; + bool m_newdata; + + public: + + void averageData(float mag_dB[]); + + int getDeviceCount() { return Pa_GetDeviceCount(); } + PaDeviceIndex getDefaultInputDevice(); + PaDeviceIndex getDefaultOutputDevice(); + PaStreamParameters *getDeviceInfo(PaDeviceIndex idx); + + PaError setFramesPerBuffer(unsigned long size); + PaError setSampleRate(unsigned long size); + + PaError setStreamFlags(PaStreamFlags flags); + PaError setCallback(PaStreamCallback *m_pStreamCallback); + PaError setStreamCallback(PaStream *stream, PaStreamCallback* callback) { m_pStreamCallback = callback; return 0;} + PaError setStreamFinishedCallback(PaStream *stream, PaStreamFinishedCallback* m_pStreamFinishedCallback); + + void setInputBuffer(const PaStreamParameters& inputBuffer) {this->m_inputBuffer = inputBuffer;} + PaError setInputDevice(PaDeviceIndex dev); + PaError setInputChannelCount(int count); + int getInputChannelCount(); + PaError setInputSampleFormat(PaSampleFormat format); + PaError setInputSampleRate(PaSampleFormat format); + PaError setInputLatency(PaTime latency); + void setInputHostApiStreamInfo(void *info = NULL); + PaTime getInputDefaultLowLatency(); + const char *getDeviceName(PaDeviceIndex dev); + + PaError setOutputDevice(PaDeviceIndex dev); + PaError setOutputChannelCount(int count); + const int getOutputChannelCount(); + PaError setOutputSampleFormat(PaSampleFormat format); + PaError setOutputLatency(PaTime latency); + void setOutputHostApiStreamInfo(void *info = NULL); + PaTime getOutputDefaultLowLatency(); + + void setFdmdvState(FDMDV* fdmdv_state) {this->m_pFDMDV_state = fdmdv_state;} + void setOutputBuffer(const PaStreamParameters& outputBuffer) {this->m_outputBuffer = outputBuffer;} + void setTimeInfo(PaStreamCallbackTimeInfo* timeInfo) {this->m_pTimeInfo = timeInfo;} + void setUserData(void* userData) {this->m_pUserData = userData;} + unsigned long getFramesPerBuffer() const {return m_framesPerBuffer;} + const PaStreamParameters& getInputBuffer() const {return m_inputBuffer;} + const PaStreamParameters& getOutputBuffer() const {return m_outputBuffer;} + const PaStreamCallbackFlags& getStatusFlags() const {return m_statusFlags;} + + FDMDV* getFdmdvState() {return m_pFDMDV_state;} + int getSamplerate() const {return m_samplerate;} + PaStream* getStream() {return m_pStream;} + void *getUserData() {return m_pUserData;} + bool getDataAvail() {return m_newdata;} + PaError streamStart(); + PaError streamClose(); + PaError streamOpen(); + void terminate(); + void stop(); + void abort(); + bool isOpen() const; + bool isStopped() const; + bool isActive() const; +// void loadData(); +}; diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot.cpp b/freedv/tags/1.2.2/src/fdmdv2_plot.cpp new file mode 100644 index 00000000..1b85cd90 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot.cpp @@ -0,0 +1,283 @@ +//========================================================================== +// Name: fdmdv2_plot.cpp +// Purpose: Implements simple wxWidgets application with GUI. +// Created: Apr. 9, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include "fdmdv2_plot.h" + +BEGIN_EVENT_TABLE(PlotPanel, wxPanel) + EVT_PAINT (PlotPanel::OnPaint) + EVT_MOTION (PlotPanel::OnMouseMove) + EVT_LEFT_DOWN (PlotPanel::OnMouseLeftDown) + EVT_LEFT_UP (PlotPanel::OnMouseLeftUp) + EVT_RIGHT_DOWN (PlotPanel::OnMouseRightDown) + EVT_MOUSEWHEEL (PlotPanel::OnMouseWheelMoved) + EVT_SIZE (PlotPanel::OnSize) + EVT_SHOW (PlotPanel::OnShow) +END_EVENT_TABLE() + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotPanel(wxFrame* parent) : wxPanel(parent) +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PlotPanel::PlotPanel(wxFrame* parent) : wxPanel(parent) +{ + m_pNoteBook = (wxAuiNotebook *) parent; + m_pTopFrame = (MainFrame *)m_pNoteBook->GetParent(); + m_zoomFactor = 1.0; + m_pBmp = NULL; + m_pPix = NULL; + m_firstPass = true; + m_line_color = 0; + m_newdata = false; + m_clip = false; + m_use_bitmap = true; + m_rubberBand = false; + m_mouseDown = false; + m_penShortDash = wxPen(wxColor(0xA0, 0xA0, 0xA0), 1, wxPENSTYLE_SHORT_DASH); + m_penDotDash = wxPen(wxColor(0xD0, 0xD0, 0xD0), 1, wxPENSTYLE_DOT_DASH); + m_penSolid = wxPen(wxColor(0x00, 0x00, 0x00), 1, wxPENSTYLE_SOLID); + SetBackgroundStyle(wxBG_STYLE_PAINT); + SetLabelSize(10.0); +} + +//------------------------------------------------------------------------- +// ~PlotPanel() +//------------------------------------------------------------------------- +PlotPanel::~PlotPanel() +{ + if(m_pBmp != NULL) + { + delete m_pBmp; + } +} + +//------------------------------------------------------------------------- +// GetLabelSize() +//------------------------------------------------------------------------- +double PlotPanel::GetLabelSize() +{ + return m_label_size; +} + +//------------------------------------------------------------------------- +// SetLabelSize() +//------------------------------------------------------------------------- +void PlotPanel::SetLabelSize(double size) +{ + m_label_size = size; +} + +//------------------------------------------------------------------------- +// OnShow() +//------------------------------------------------------------------------- +void PlotPanel::OnShow(wxShowEvent& event) +{ + this->Refresh(); +} + +//------------------------------------------------------------------------- +// OnErase() +//------------------------------------------------------------------------- +void PlotPanel::OnErase(wxEraseEvent& event) +{ + event.Skip(); +} + +//------------------------------------------------------------------------- +// OnSize() +//------------------------------------------------------------------------- +void PlotPanel::OnSize(wxSizeEvent& event) +{ + m_rCtrlPrev = m_rCtrl; + m_rCtrl = GetClientRect(); + if(m_use_bitmap) + { + if(!m_oImage.IsOk()) + { + m_oImage.Create(m_rCtrl.GetWidth(), m_rCtrl.GetHeight(), true); + } + else + { + m_oImage.Rescale(m_rCtrl.GetWidth(), m_rCtrl.GetHeight()); + } + m_pBmp = new wxBitmap(m_oImage, wxBITMAP_SCREEN_DEPTH); + m_firstPass = true; + } + this->Refresh(); +} + +//------------------------------------------------------------------------- +// OnMouseMove() +//------------------------------------------------------------------------- +void PlotPanel::OnMouseMove(wxMouseEvent& event) +{ +// if(m_mouseDown) +// { +// paintNow(); +// } +} + +//------------------------------------------------------------------------- +// OnMouseLeftDown() +//------------------------------------------------------------------------- +void PlotPanel::OnMouseLeftDown(wxMouseEvent& event) +{ +} + +//------------------------------------------------------------------------- +// OnMouseRightDown() +//------------------------------------------------------------------------- +void PlotPanel::OnMouseRightDown(wxMouseEvent& event) +{ +} + +//------------------------------------------------------------------------- +// OnMouseWheelMoved() +//------------------------------------------------------------------------- +void PlotPanel::OnMouseWheelMoved(wxMouseEvent& event) +{ +} + +//------------------------------------------------------------------------- +// OnMouseLeftUp() +//------------------------------------------------------------------------- +void PlotPanel::OnMouseLeftUp(wxMouseEvent& event) +{ + m_mouseDown = false; +} + +//------------------------------------------------------------------------- +// SetZoomFactor() +//------------------------------------------------------------------------- +double PlotPanel::SetZoomFactor(double zf) +{ + if((zf > 0) && (zf < 5.0)) + { + m_zoomFactor = zf; + } + return zf; +} + +//------------------------------------------------------------------------- +// GetZoomFactor() +//------------------------------------------------------------------------- +double PlotPanel::GetZoomFactor(double zf) +{ + return m_zoomFactor; +} + +//------------------------------------------------------------------------- +// draw() +//------------------------------------------------------------------------- +void PlotPanel::draw(wxAutoBufferedPaintDC& pDC) +{ + printf("PlotPanel::draw()"); + wxMemoryDC m_mDC; + m_mDC.SelectObject(*m_pBmp); + m_rCtrl = GetClientRect(); + m_rGrid = m_rCtrl; + + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + m_rGrid.Offset(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER); + + pDC.Clear(); + m_rPlot = wxRect(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + if(m_firstPass) + { + m_firstPass = false; + m_mDC.FloodFill(0, 0, VERY_LTGREY_COLOR); + + // Draw a filled rectangle with aborder + wxBrush ltGraphBkgBrush = wxBrush(DARK_BLUE_COLOR); + m_mDC.SetBrush(ltGraphBkgBrush); + m_mDC.SetPen(wxPen(BLACK_COLOR, 0)); + m_mDC.DrawRectangle(m_rPlot); + } + if(m_newdata) + { + m_newdata = false; + int t = m_rPlot.GetTop(); + int l = m_rPlot.GetLeft(); +// int r = m_rPlot.GetRight(); + int h = m_rPlot.GetHeight(); + int w = m_rPlot.GetWidth(); + pDC.Blit(l, t, w, h, &m_mDC, l, t); + } + drawGraticule(pDC); + m_mDC.SetBrush(wxNullBrush); + m_mDC.SelectObject(wxNullBitmap); +} + +//------------------------------------------------------------------------- +// drawGraticule() +//------------------------------------------------------------------------- +void PlotPanel::drawGraticule(wxAutoBufferedPaintDC& pDC) +{ + int p; + char buf[15]; + wxString s; + + // Vertical gridlines + pDC.SetPen(m_penShortDash); + for(p = (PLOT_BORDER + XLEFT_OFFSET + GRID_INCREMENT); p < ((m_rGrid.GetWidth() - XLEFT_OFFSET) + GRID_INCREMENT); p += GRID_INCREMENT) + { + pDC.DrawLine(p, (m_rGrid.GetHeight() + PLOT_BORDER), p, PLOT_BORDER); + } + // Horizontal gridlines + pDC.SetPen(m_penDotDash); + for(p = (m_rGrid.GetHeight() - GRID_INCREMENT); p > PLOT_BORDER; p -= GRID_INCREMENT) + { + pDC.DrawLine(PLOT_BORDER + XLEFT_OFFSET, (p + PLOT_BORDER), (m_rGrid.GetWidth() + PLOT_BORDER + XLEFT_OFFSET), (p + PLOT_BORDER)); + } + // Label the X-Axis + pDC.SetPen(wxPen(GREY_COLOR, 1)); + for(p = GRID_INCREMENT; p < (m_rGrid.GetWidth() - YBOTTOM_OFFSET); p += GRID_INCREMENT) + { + sprintf(buf, "%1.1f Hz",(double)(p / 10)); + pDC.DrawText(buf, p - PLOT_BORDER + XLEFT_OFFSET, m_rGrid.GetHeight() + YBOTTOM_OFFSET/2); + } + // Label the Y-Axis + //for(p = GRID_INCREMENT; p < (h - YBOTTOM_OFFSET); p += GRID_INCREMENT) + for(p = (m_rGrid.GetHeight() - GRID_INCREMENT); p > PLOT_BORDER; p -= GRID_INCREMENT) + { + sprintf(buf, "%1.0f", (double)((m_rGrid.GetHeight() - p) * -10)); + pDC.DrawText(buf, XLEFT_TEXT_OFFSET, p); + } +} + +//------------------------------------------------------------------------- +// paintEvent() +// +// Called by the system of by wxWidgets when the panel needs +// to be redrawn. You can also trigger this call by calling +// Refresh()/Update(). +//------------------------------------------------------------------------- +void PlotPanel::OnPaint(wxPaintEvent & evt) +{ + wxAutoBufferedPaintDC pdc(this); + draw(pdc); +} + diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot.h b/freedv/tags/1.2.2/src/fdmdv2_plot.h new file mode 100644 index 00000000..25309d3a --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot.h @@ -0,0 +1,150 @@ +//========================================================================== +// Name: fdmdv2_plot.h +// Purpose: Declares simple wxWidgets application with GUI +// Created: Apr. 10, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +//#include "fdmdv2_main.h" +#ifndef __FDMDV2_PLOT__ +#define __FDMDV2_PLOT__ +#include +#include +#include +#include +#include + +#define MAX_ZOOM 7 +#define MAX_BMP_X (400 * MAX_ZOOM) +#define MAX_BMP_Y (400 * MAX_ZOOM) +#define DATA_LINE_HEIGHT 10 +#define TEXT_BASELINE_OFFSET_Y -5 + + +#define wxUSE_FILEDLG 1 +#define wxUSE_LIBPNG 1 +#define wxUSE_LIBJPEG 1 +#define wxUSE_GIF 1 +#define wxUSE_PCX 1 +#define wxUSE_LIBTIFF 1 + +#define PLOT_BORDER 12 +#define XLEFT_OFFSET 40 +#define XLEFT_TEXT_OFFSET 6 +#define YBOTTOM_OFFSET 20 +#define YBOTTOM_TEXT_OFFSET 15 +#define GRID_INCREMENT 50 + +#define BLACK_COLOR wxColor(0x00, 0x00, 0x00) +#define GREY_COLOR wxColor(0x80, 0x80, 0x80) +#define DARK_GREY_COLOR wxColor(0x40, 0x40, 0x40) +#define MEDIUM_GREY_COLOR wxColor(0xC0, 0xC0, 0xC0) +#define LIGHT_GREY_COLOR wxColor(0xE0, 0xE0, 0xE0) +#define VERY_LTGREY_COLOR wxColor(0xF8, 0xF8, 0xF8) +#define WHITE_COLOR wxColor(0xFF, 0xFF, 0xFF) + +#define DARK_BLUE_COLOR wxColor(0x00, 0x00, 0x60) +#define BLUE_COLOR wxColor(0x00, 0x00, 0xFF) +#define LIGHT_BLUE_COLOR wxColor(0x80, 0x80, 0xFF) + +#define RED_COLOR wxColor(0xFF, 0x5E, 0x5E) +#define LIGHT_RED_COLOR wxColor(0xFF, 0xE0, 0xE0) +#define DARK_RED_COLOR wxColor(0xFF, 0x00, 0x00) +#define PINK_COLOR wxColor(0xFF, 0x80, 0xFF) + +#define LIGHT_GREEN_COLOR wxColor(0xE3, 0xFF, 0xE0) +#define GREEN_COLOR wxColor(0x95, 0xFF, 0x8A) +#define DARK_GREEN_COLOR wxColor(0x20, 0xFF, 0x08) +#define VERY_GREEN_COLOR wxColor(0x00, 0xFF, 0x00) + +#define YELLOW_COLOR wxColor(0xFF, 0xFF, 0x5E) +#define LIGHT_YELLOW_COLOR wxColor(0xFF, 0xFF, 0xB5) +#define DARK_YELLOW_COLOR wxColor(0xFF, 0xFF, 0x08) + +class MainFrame; + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotPanel +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlotPanel : public wxPanel +{ + public: + PlotPanel(wxFrame* parent); + ~PlotPanel(); + wxPen m_penShortDash; + wxPen m_penDotDash; + wxPen m_penSolid; + wxRect m_rCtrlPrev; + wxRect m_rCtrl; + wxRect m_rGrid; + wxRect m_rPlot; + MainFrame *m_pTopFrame; + wxAuiNotebook *m_pNoteBook; + double m_label_size; + wxSize m_Bufsz; + bool m_newdata; + wxImage m_oImage; + wxBitmap *m_pBmp; + wxNativePixelData *m_pPix; + + // some useful events + void OnMouseMove(wxMouseEvent& event); + virtual void OnMouseLeftDown(wxMouseEvent& event); + void OnMouseLeftUp(wxMouseEvent& event); + virtual void OnMouseRightDown(wxMouseEvent& event); + void OnMouseWheelMoved(wxMouseEvent& event); + void OnClose(wxCloseEvent& event ){ event.Skip(); } + void OnSize( wxSizeEvent& event ); + void OnErase(wxEraseEvent& event); + void OnPaint(wxPaintEvent& event); + //void OnUpdateUI( wxUpdateUIEvent& event ){ event.Skip(); } + + void paintEvent(wxPaintEvent & evt); + virtual void draw(wxAutoBufferedPaintDC& pdc); + virtual void drawGraticule(wxAutoBufferedPaintDC& pdc); + virtual double SetZoomFactor(double zf); + virtual double GetZoomFactor(double zf); + virtual void OnShow(wxShowEvent& event); + virtual double GetLabelSize(); + virtual void SetLabelSize(double size); + + protected: + int m_x; + int m_y; + int m_left; + int m_top; + int m_prev_w; + int m_prev_h; + int m_prev_x; + int m_prev_y; + bool m_use_bitmap; + bool m_clip; + bool m_rubberBand; + bool m_mouseDown; + bool m_firstPass; + double m_zoomFactor; + int m_greyscale; + int m_line_color; + DECLARE_EVENT_TABLE() +}; +#endif //__FDMDV2_PLOT__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_scalar.cpp b/freedv/tags/1.2.2/src/fdmdv2_plot_scalar.cpp new file mode 100644 index 00000000..9a794f53 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_scalar.cpp @@ -0,0 +1,344 @@ +//========================================================================== +// Name: fdmdv2_plot_scalar.cpp +// Purpose: Plots scalar amplitude against time +// Created: June 22, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include "wx/wx.h" +#include "fdmdv2_main.h" +#include "fdmdv2_plot_scalar.h" + +BEGIN_EVENT_TABLE(PlotScalar, PlotPanel) + EVT_PAINT (PlotScalar::OnPaint) + EVT_MOTION (PlotScalar::OnMouseMove) + EVT_MOUSEWHEEL (PlotScalar::OnMouseWheelMoved) + EVT_SIZE (PlotScalar::OnSize) + EVT_SHOW (PlotScalar::OnShow) +// EVT_ERASE_BACKGROUND(PlotScalar::OnErase) +END_EVENT_TABLE() + +//---------------------------------------------------------------- +// PlotScalar() +//---------------------------------------------------------------- +PlotScalar::PlotScalar(wxFrame* parent, + int channels, // number on channels to plot + float t_secs, // time covered by entire x axis in seconds + float sample_period_secs, // time between each sample in seconds + float a_min, // min ampltude of samples being plotted + float a_max, // max ampltude of samples being plotted + float graticule_t_step, // time step of x (time) axis graticule in seconds + float graticule_a_step, // step of amplitude axis graticule + const char a_fmt[], // printf format string for amplitude axis labels + int mini // true for mini-plot - don't draw graticule + ): PlotPanel(parent) +{ + int i; + + m_rCtrl = GetClientRect(); + + m_channels = channels; + m_t_secs = t_secs; + m_sample_period_secs = sample_period_secs; + m_a_min = a_min; + m_a_max = a_max; + m_graticule_t_step = graticule_t_step; + m_graticule_a_step = graticule_a_step; + assert(strlen(a_fmt) < 15); + strcpy(m_a_fmt, a_fmt); + m_mini = mini; + m_bar_graph = 0; + m_logy = 0; + + // work out number of samples we will store and allocate storage + + m_samples = m_t_secs/m_sample_period_secs; + m_mem = new float[m_samples*m_channels]; + + for(i = 0; i < m_samples*m_channels; i++) + { + m_mem[i] = 0.0; + } +} + +//---------------------------------------------------------------- +// ~PlotScalar() +//---------------------------------------------------------------- +PlotScalar::~PlotScalar() +{ + delete m_mem; +} + +//---------------------------------------------------------------- +// add_new_sample() +//---------------------------------------------------------------- +void PlotScalar::add_new_sample(int channel, float sample) +{ + int i; + int offset = channel*m_samples; + + assert(channel < m_channels); + + for(i = 0; i < m_samples-1; i++) + { + m_mem[offset+i] = m_mem[offset+i+1]; + } + m_mem[offset+m_samples-1] = sample; +} + +//---------------------------------------------------------------- +// add_new_samples() +//---------------------------------------------------------------- +void PlotScalar::add_new_samples(int channel, float samples[], int length) +{ + int i; + int offset = channel*m_samples; + + assert(channel < m_channels); + + for(i = 0; i < m_samples-length; i++) + m_mem[offset+i] = m_mem[offset+i+length]; + for(; i < m_samples; i++) + m_mem[offset+i] = *samples++; +} + +//---------------------------------------------------------------- +// add_new_short_samples() +//---------------------------------------------------------------- +void PlotScalar::add_new_short_samples(int channel, short samples[], int length, float scale_factor) +{ + int i; + int offset = channel*m_samples; + + assert(channel < m_channels); + + for(i = 0; i < m_samples-length; i++) + m_mem[offset+i] = m_mem[offset+i+length]; + for(; i < m_samples; i++) + m_mem[offset+i] = (float)*samples++/scale_factor; +} + +//---------------------------------------------------------------- +// draw() +//---------------------------------------------------------------- +void PlotScalar::draw(wxAutoBufferedPaintDC& dc) +{ + float index_to_px; + float a_to_py; + int i; + int prev_x, prev_y; + float a; + + m_rCtrl = GetClientRect(); + m_rGrid = m_rCtrl; + if (!m_mini) + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + + //printf("h %d w %d\n", m_rCtrl.GetWidth(), m_rCtrl.GetHeight()); + //printf("h %d w %d\n", m_rGrid.GetWidth(), m_rGrid.GetHeight()); + + // black background + + dc.Clear(); + if (m_mini) + m_rPlot = wxRect(0, 0, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + else + m_rPlot = wxRect(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + + wxBrush ltGraphBkgBrush = wxBrush(BLACK_COLOR); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 0)); + dc.DrawRectangle(m_rPlot); + + index_to_px = (float)m_rGrid.GetWidth()/m_samples; + a_to_py = (float)m_rGrid.GetHeight()/(m_a_max - m_a_min); + + wxPen pen; + pen.SetColour(DARK_GREEN_COLOR); + pen.SetWidth(1); + dc.SetPen(pen); + + // draw all samples + + prev_x = prev_y = 0; // stop warning + + // plot each channel + + int offset, x, y; + for(offset=0; offset m_a_max) a = m_a_max; + + // invert y axis and offset by minimum + + y = m_rGrid.GetHeight() - a_to_py * a + m_a_min*a_to_py; + + // regular point-point line graph + + x = index_to_px * i; + + // put inside plot window + + if (!m_mini) { + x += PLOT_BORDER + XLEFT_OFFSET; + y += PLOT_BORDER; + } + + if (m_bar_graph) { + + if (m_logy) { + + // can't take log(0) + + assert(m_a_min > 0.0); + assert(m_a_max > 0.0); + + float norm = (log10(a) - log10(m_a_min))/(log10(m_a_max) - log10(m_a_min)); + y = m_rGrid.GetHeight()*(1.0 - norm); + } else { + y = m_rGrid.GetHeight() - a_to_py * a + m_a_min*a_to_py; + } + + // use points to make a bar graph + + int x1, x2, y1; + + x1 = index_to_px * ((float)i - 0.5); + x2 = index_to_px * ((float)i + 0.5); + y1 = m_rGrid.GetHeight(); + x1 += PLOT_BORDER + XLEFT_OFFSET; x2 += PLOT_BORDER + XLEFT_OFFSET; + y1 += PLOT_BORDER; + dc.DrawLine(x1, y1, x1, y); dc.DrawLine(x1, y, x2, y); dc.DrawLine(x2, y, x2, y1); + } + else { + if (i) + dc.DrawLine(x, y, prev_x, prev_y); + prev_x = x; prev_y = y; + } + } + } + + drawGraticule(dc); +} + +//------------------------------------------------------------------------- +// drawGraticule() +//------------------------------------------------------------------------- +void PlotScalar::drawGraticule(wxAutoBufferedPaintDC& dc) +{ + float t, a; + int x, y, text_w, text_h; + char buf[15]; + wxString s; + float sec_to_px; + float a_to_py; + + wxBrush ltGraphBkgBrush; + ltGraphBkgBrush.SetStyle(wxBRUSHSTYLE_TRANSPARENT); + ltGraphBkgBrush.SetColour(*wxBLACK); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 1)); + + sec_to_px = (float)m_rGrid.GetWidth()/m_t_secs; + a_to_py = (float)m_rGrid.GetHeight()/(m_a_max - m_a_min); + + // upper LH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER) + // lower RH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET + m_rGrid.GetWidth(), + // PLOT_BORDER + m_rGrid.GetHeight()) + + // Vertical gridlines + + dc.SetPen(m_penShortDash); + for(t=0; t<=m_t_secs; t+=m_graticule_t_step) { + x = t*sec_to_px; + if (m_mini) { + dc.DrawLine(x, m_rGrid.GetHeight(), x, 0); + } + else { + x += PLOT_BORDER + XLEFT_OFFSET; + dc.DrawLine(x, m_rGrid.GetHeight() + PLOT_BORDER, x, PLOT_BORDER); + } + if (!m_mini) { + sprintf(buf, "%2.1fs", t); + GetTextExtent(buf, &text_w, &text_h); + dc.DrawText(buf, x - text_w/2, m_rGrid.GetHeight() + PLOT_BORDER + YBOTTOM_TEXT_OFFSET); + } + } + + // Horizontal gridlines + + dc.SetPen(m_penDotDash); + for(a=m_a_min; a. +// +//========================================================================== +#ifndef __FDMDV2_PLOT_SCALAR__ +#define __FDMDV2_PLOT_SCALAR__ + +#include "fdmdv2_plot.h" +#include "fdmdv2_defines.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotScalar +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlotScalar: public PlotPanel +{ + public: + + PlotScalar(wxFrame* parent, + int channels, + float t_secs, + float sample_period_secs, + float a_min, + float a_max, + float graticule_t_step, + float graticule_a_step, + const char a_fmt[], + int mini + ); + ~PlotScalar(); + void add_new_sample(int channel, float sample); + void add_new_samples(int channel, float samples[], int length); + void add_new_short_samples(int channel, short samples[], int length, float scale_factor); + void setBarGraph(int bar_graph) { m_bar_graph = bar_graph; } + void setLogY(int logy) { m_logy = logy; } + + protected: + + int m_channels; + float m_t_secs; + float m_sample_period_secs; + float m_a_min; + float m_a_max; + float m_graticule_t_step; + float m_graticule_a_step; + char m_a_fmt[15]; + int m_mini; + int m_samples; + float *m_mem; + int m_bar_graph; // non zero to plot bar graphs + int m_logy; // plot graph on log scale + + void draw(wxAutoBufferedPaintDC& dc); + void drawGraticule(wxAutoBufferedPaintDC& dc); + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + void OnShow(wxShowEvent& event); + + DECLARE_EVENT_TABLE() +}; + +#endif // __FDMDV2_PLOT_SCALAR__ + diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_scatter.cpp b/freedv/tags/1.2.2/src/fdmdv2_plot_scatter.cpp new file mode 100644 index 00000000..d7e88c70 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_scatter.cpp @@ -0,0 +1,289 @@ +//========================================================================== +// Name: fdmdv2_plot_scatter.cpp +// Purpose: A scatter plot derivative of fdmdv2_plot. +// Created: June 24, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include "wx/wx.h" +#include "fdmdv2_plot_scatter.h" + +BEGIN_EVENT_TABLE(PlotScatter, PlotPanel) + EVT_PAINT (PlotScatter::OnPaint) + EVT_MOTION (PlotScatter::OnMouseMove) + EVT_MOUSEWHEEL (PlotScatter::OnMouseWheelMoved) + EVT_SIZE (PlotScatter::OnSize) + EVT_SHOW (PlotScatter::OnShow) +// EVT_ERASE_BACKGROUND(PlotScatter::OnErase) +END_EVENT_TABLE() + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// PlotScatter +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PlotScatter::PlotScatter(wxFrame* parent) : PlotPanel(parent) +{ + int i; + + for(i=0; i < SCATTER_MEM_SYMS_MAX; i++) + { + m_mem[i].real = 0.0; + m_mem[i].imag = 0.0; + } + + m_filter_max_xy = m_filter_max_y = 0.1; + + // defaults so we start off with something sensible + + Nsym = 14+1; + scatterMemSyms = ((int)(SCATTER_MEM_SECS*(Nsym/DT))); + assert(scatterMemSyms <= SCATTER_MEM_SYMS_MAX); + + Ncol = 0; + memset(eye_mem, 0, sizeof(eye_mem)); + + mode = PLOT_SCATTER_MODE_SCATTER; +} + +// changing number of carriers changes number of symbols to plot +void PlotScatter::setNc(int Nc) { + Nsym = Nc; + assert(Nsym <= (MODEM_STATS_NC_MAX+1)); + scatterMemSyms = ((int)(SCATTER_MEM_SECS*(Nsym/DT))); + assert(scatterMemSyms <= SCATTER_MEM_SYMS_MAX); +} + +//---------------------------------------------------------------- +// draw() +//---------------------------------------------------------------- +void PlotScatter::draw(wxAutoBufferedPaintDC& dc) +{ + float x_scale; + float y_scale; + int i,j; + int x; + int y; + wxColour sym_to_colour[] = {wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255), + wxColor(255,255,0), + wxColor(255,255,255), + wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255), + wxColor(255,255,0), + wxColor(255,255,255), + wxColor(0,0,255), + wxColor(0,255,0), + wxColor(0,255,255), + wxColor(255,0,0), + wxColor(255,0,255) + }; + + m_rCtrl = GetClientRect(); + m_rGrid = m_rCtrl; + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + + // black background + + dc.Clear(); + m_rPlot = wxRect(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + wxBrush ltGraphBkgBrush = wxBrush(BLACK_COLOR); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 0)); + dc.DrawRectangle(m_rPlot); + + wxPen pen; + pen.SetWidth(1); // note this is ignored by DrawPoint + + if (mode == PLOT_SCATTER_MODE_SCATTER) { + + // automatically scale, first measure the maximum value + + float max_xy = 1E-12; + float real,imag; + for(i=0; i< scatterMemSyms; i++) { + real = fabs(m_mem[i].real); + imag = fabs(m_mem[i].imag); + if (real > max_xy) + max_xy = real; + if (imag > max_xy) + max_xy = imag; + } + + // smooth it out and set a lower limit to prevent divide by 0 issues + + m_filter_max_xy = BETA*m_filter_max_xy + (1 - BETA)*2.5*max_xy; + if (m_filter_max_xy < 0.001) + m_filter_max_xy = 0.001; + + // quantise to log steps to prevent scatter scaling bobbing about too + // much as scaling varies + + float quant_m_filter_max_xy = exp(floor(0.5+log(m_filter_max_xy))); + //printf("max_xy: %f m_filter_max_xy: %f quant_m_filter_max_xy: %f\n", max_xy, m_filter_max_xy, quant_m_filter_max_xy); + + x_scale = (float)m_rGrid.GetWidth()/quant_m_filter_max_xy; + y_scale = (float)m_rGrid.GetHeight()/quant_m_filter_max_xy; + + // draw all samples + + for(i = 0; i < scatterMemSyms; i++) { + x = x_scale * m_mem[i].real + m_rGrid.GetWidth()/2; + y = y_scale * m_mem[i].imag + m_rGrid.GetHeight()/2; + x += PLOT_BORDER + XLEFT_OFFSET; + y += PLOT_BORDER; + pen.SetColour(sym_to_colour[i%Nsym]); + dc.SetPen(pen); + dc.DrawPoint(x, y); + } + } + + if (mode == PLOT_SCATTER_MODE_EYE) { + + pen.SetColour(DARK_GREEN_COLOR); + pen.SetWidth(1); + dc.SetPen(pen); + + // automatically scale, first measure the maximum Y value + + float max_y = 1E-12; + float min_y = 1E+12; + for(i=0; i max_y) { + max_y = eye_mem[i][j]; + } + if (eye_mem[i][j] < min_y) { + min_y = eye_mem[i][j]; + } + } + } + + // smooth it out and set a lower limit to prevent divide by 0 issues + + m_filter_max_y = BETA*m_filter_max_y + (1 - BETA)*2.5*max_y; + if (m_filter_max_y < 0.001) + m_filter_max_y = 0.001; + + // quantise to log steps to prevent scatter scaling bobbing about too + // much as scaling varies + + float quant_m_filter_max_y = exp(floor(0.5+log(m_filter_max_y))); + //printf("min_y: %4.3f max_y: %4.3f quant_m_filter_max_y: %4.3f\n", min_y, max_y, quant_m_filter_max_y); + + x_scale = (float)m_rGrid.GetWidth()/Ncol; + y_scale = (float)m_rGrid.GetHeight()/quant_m_filter_max_y; + //printf("GetWidth(): %d GetHeight(): %d\n", m_rGrid.GetWidth(), m_rGrid.GetHeight()); + + // plot eye traces row by row + + int prev_x, prev_y; + prev_x = prev_y = 0; + for(i=0; i. +// +//========================================================================== +#ifndef __FDMDV2_PLOT_SCATTER__ +#define __FDMDV2_PLOT_SCATTER__ + +#include "comp.h" +#include "fdmdv2_plot.h" +#include "fdmdv2_defines.h" + +#define PLOT_SCATTER_MODE_SCATTER 0 +#define PLOT_SCATTER_MODE_EYE 1 +#define PLOT_SCATTER_EYE_MAX_SAMPLES_ROW 80 + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotScatter +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlotScatter : public PlotPanel +{ + public: + PlotScatter(wxFrame* parent); + ~PlotScatter(){}; + void add_new_samples_scatter(COMP samples[]); + void add_new_samples_eye(float samples[], int n); + void setNc(int Nc); + void setEyeScatter(int eye_mode) {mode = eye_mode;} + + protected: + int mode; + COMP m_mem[SCATTER_MEM_SYMS_MAX]; + COMP m_new_samples[MODEM_STATS_NC_MAX+1]; + float eye_mem[SCATTER_EYE_MEM_ROWS][PLOT_SCATTER_EYE_MAX_SAMPLES_ROW]; + + void draw(wxAutoBufferedPaintDC& dc); + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + void OnShow(wxShowEvent& event); + + DECLARE_EVENT_TABLE() + + private: + int Nsym; + int Ncol; + int scatterMemSyms; + float m_filter_max_xy, m_filter_max_y; +}; + +#endif //__FDMDV2_PLOT_SCATTER__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.cpp b/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.cpp new file mode 100644 index 00000000..1f5be59b --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.cpp @@ -0,0 +1,267 @@ +//========================================================================== +// Name: fdmdv2_plot_waterfall.cpp +// Purpose: Implements a waterfall plot derivative of fdmdv2_plot. +// Created: June 23, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include "wx/wx.h" + +#include "fdmdv2_main.h" + +extern float g_avmag[]; // average mag data passed to draw() +void fdmdv2_clickTune(float frequency); // callback to pass new click freq + +BEGIN_EVENT_TABLE(PlotSpectrum, PlotPanel) + EVT_MOTION (PlotSpectrum::OnMouseMove) + EVT_LEFT_DOWN (PlotSpectrum::OnMouseLeftDown) + EVT_LEFT_DCLICK (PlotSpectrum::OnMouseLeftDoubleClick) + EVT_LEFT_UP (PlotSpectrum::OnMouseLeftUp) + EVT_MOUSEWHEEL (PlotSpectrum::OnMouseWheelMoved) + EVT_PAINT (PlotSpectrum::OnPaint) + EVT_SHOW (PlotSpectrum::OnShow) +END_EVENT_TABLE() + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotSpectrum +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PlotSpectrum::PlotSpectrum(wxFrame* parent, float *magdB, int n_magdB, + float min_mag_db, float max_mag_db, bool clickTune): PlotPanel(parent) +{ + m_greyscale = 0; + m_Bufsz = GetMaxClientSize(); + m_newdata = false; + m_firstPass = true; + m_line_color = 0; + SetLabelSize(10.0); + + m_magdB = magdB; + m_n_magdB = n_magdB; // number of points in magdB that covers 0 ... MAX_F_HZ of spectrum + m_max_mag_db = max_mag_db; + m_min_mag_db = min_mag_db; + m_rxFreq = 0.0; + m_clickTune = clickTune; +} + +//---------------------------------------------------------------- +// ~PlotSpectrum() +//---------------------------------------------------------------- +PlotSpectrum::~PlotSpectrum() +{ +} + +//---------------------------------------------------------------- +// OnSize() +//---------------------------------------------------------------- +void PlotSpectrum::OnSize(wxSizeEvent& event) { +} + +//---------------------------------------------------------------- +// OnPaint() +//---------------------------------------------------------------- +void PlotSpectrum::OnPaint(wxPaintEvent& event) +{ + wxAutoBufferedPaintDC dc(this); + draw(dc); +} + +//---------------------------------------------------------------- +// OnShow() +//---------------------------------------------------------------- +void PlotSpectrum::OnShow(wxShowEvent& event) +{ +} + +//---------------------------------------------------------------- +// draw() +//---------------------------------------------------------------- +void PlotSpectrum::draw(wxAutoBufferedPaintDC& dc) +{ + m_rCtrl = GetClientRect(); + + // m_rGrid is coords of inner window we actually plot to. We deflate it a bit + // to leave room for axis labels. We need to work this out every time we draw + // as OnSize() may not be called before OnPaint(), for example when a new tab + // is selected + + m_rGrid = m_rCtrl; + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + + dc.Clear(); + + // black background + + m_rPlot = wxRect(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + wxBrush ltGraphBkgBrush = wxBrush(BLACK_COLOR); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 0)); + dc.DrawRectangle(m_rPlot); + + // draw spectrum + + int x, y, prev_x, prev_y, index; + float index_to_px, mag_dB_to_py, mag; + + m_newdata = false; + + wxPen pen; + pen.SetColour(DARK_GREEN_COLOR); + pen.SetWidth(1); + dc.SetPen(pen); + + index_to_px = (float)m_rGrid.GetWidth()/m_n_magdB; + mag_dB_to_py = (float)m_rGrid.GetHeight()/(m_max_mag_db - m_min_mag_db); + + prev_x = PLOT_BORDER + XLEFT_OFFSET; + prev_y = PLOT_BORDER; + for(index = 0; index < m_n_magdB; index++) + { + x = index*index_to_px; + mag = m_magdB[index]; + if (mag > m_max_mag_db) mag = m_max_mag_db; + if (mag < m_min_mag_db) mag = m_min_mag_db; + y = -(mag - m_max_mag_db) * mag_dB_to_py; + + x += PLOT_BORDER + XLEFT_OFFSET; + y += PLOT_BORDER; + + if (index) + dc.DrawLine(x, y, prev_x, prev_y); + prev_x = x; prev_y = y; + } + + // and finally draw Graticule + + drawGraticule(dc); + +} + +//------------------------------------------------------------------------- +// drawGraticule() +//------------------------------------------------------------------------- +void PlotSpectrum::drawGraticule(wxAutoBufferedPaintDC& dc) +{ + int x, y, text_w, text_h; + char buf[15]; + wxString s; + float f, mag, freq_hz_to_px, mag_dB_to_py; + + wxBrush ltGraphBkgBrush; + ltGraphBkgBrush.SetStyle(wxBRUSHSTYLE_TRANSPARENT); + ltGraphBkgBrush.SetColour(*wxBLACK); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 1)); + + freq_hz_to_px = (float)m_rGrid.GetWidth()/(MAX_F_HZ-MIN_F_HZ); + mag_dB_to_py = (float)m_rGrid.GetHeight()/(m_max_mag_db - m_min_mag_db); + + // upper LH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER) + // lower RH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET + m_rGrid.GetWidth(), + // PLOT_BORDER + m_rGrid.GetHeight()) + + // Check if small screen size means text will overlap + + int textXStep = STEP_F_HZ*freq_hz_to_px; + int textYStep = STEP_MAG_DB*mag_dB_to_py; + sprintf(buf, "%4.0fHz", (float)MAX_F_HZ - STEP_F_HZ); + GetTextExtent(buf, &text_w, &text_h); + int overlappedText = (text_w > textXStep) || (text_h > textYStep); + //printf("text_w: %d textXStep: %d text_h: %d textYStep: %d overlappedText: %d\n", text_w, textXStep, + // text_h, textYStep, overlappedText); + + // Vertical gridlines + + for(f=STEP_F_HZ; f= 0) && (pt.x <= m_rGrid.GetWidth()) && (pt.y >=0) && m_clickTune) { + float freq_hz_to_px = (float)m_rGrid.GetWidth()/(MAX_F_HZ-MIN_F_HZ); + float clickFreq = (float)pt.x/freq_hz_to_px; + + // see PlotWaterfall::OnMouseDown() + + fdmdv2_clickTune(clickFreq); + } +} diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.h b/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.h new file mode 100644 index 00000000..271eeb98 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_spectrum.h @@ -0,0 +1,58 @@ +//========================================================================== +// Name: fdmdv2_plot_spectrum.h +// Purpose: Defines a spectrum plot derived from fdmdv2_plot class. +// Created: June 22, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __FDMDV2_PLOT_SPECTRUM__ +#define __FDMDV2_PLOT_SPECTRUM__ + +#include "fdmdv2_plot.h" +#include "fdmdv2_defines.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class Waterfall +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlotSpectrum : public PlotPanel +{ + public: + PlotSpectrum(wxFrame* parent, float *magdB, int n_magdB, + float min_mag_db=MIN_MAG_DB, float max_mag_db=MAX_MAG_DB, bool clickTune=true); + ~PlotSpectrum(); + void setRxFreq(float rxFreq) { m_rxFreq = rxFreq; } + void setFreqScale(int n_magdB) { m_n_magdB = n_magdB; } + + protected: + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + void OnShow(wxShowEvent& event); + void drawGraticule(wxAutoBufferedPaintDC& dc); + void draw(wxAutoBufferedPaintDC& dc); + void OnMouseLeftDoubleClick(wxMouseEvent& event); + + private: + float m_rxFreq; + float m_max_mag_db; + float m_min_mag_db; + float *m_magdB; + int m_n_magdB; + bool m_clickTune; + + DECLARE_EVENT_TABLE() +}; + +#endif //__FDMDV2_PLOT_SPECTRUM__ diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.cpp b/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.cpp new file mode 100644 index 00000000..cdbe01e0 --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.cpp @@ -0,0 +1,483 @@ +//========================================================================== +// Name: fdmdv2_plot_waterfall.cpp +// Purpose: Implements a waterfall plot derivative of fdmdv2_plot. +// Created: June 22, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include +#include "wx/wx.h" +#include "fdmdv2_main.h" + +extern float g_avmag[]; // av mag spec passed in to draw() +void fdmdv2_clickTune(float frequency); // callback to pass new click freq + +BEGIN_EVENT_TABLE(PlotWaterfall, PlotPanel) + EVT_PAINT (PlotWaterfall::OnPaint) + EVT_MOTION (PlotWaterfall::OnMouseMove) + EVT_LEFT_DCLICK (PlotWaterfall::OnMouseLeftDoubleClick) + EVT_RIGHT_DOWN (PlotWaterfall::OnMouseRightDown) + EVT_LEFT_UP (PlotWaterfall::OnMouseLeftUp) + EVT_MOUSEWHEEL (PlotWaterfall::OnMouseWheelMoved) + EVT_SIZE (PlotWaterfall::OnSize) + EVT_SHOW (PlotWaterfall::OnShow) +END_EVENT_TABLE() + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class WaterfallPlot +// +// @class WaterfallPlot +// @author David Witten +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +PlotWaterfall::PlotWaterfall(wxFrame* parent, bool graticule, int colour): PlotPanel(parent) +{ + + for(int i = 0; i < 255; i++) + { + m_heatmap_lut[i] = heatmap((float)i, 0.0, 255.0); + } + m_graticule = graticule; + m_colour = colour; + m_Bufsz = GetMaxClientSize(); + m_newdata = false; + m_firstPass = true; + m_line_color = 0; + m_modem_stats_max_f_hz = MODEM_STATS_MAX_F_HZ; + + SetLabelSize(10.0); + + m_pBmp = NULL; + m_max_mag = MAX_MAG_DB; + m_min_mag = MIN_MAG_DB; +} + +// When the window size gets set we can work outthe size of the window +// we plot in and allocate a bit map of the correct size +void PlotWaterfall::OnSize(wxSizeEvent& event) +{ + // resize bit map + + delete m_pBmp; + + m_rCtrl = GetClientRect(); + + // m_rGrid is coords of inner window we actually plot to. We deflate it a bit + // to leave room for axis labels. + + m_rGrid = m_rCtrl; + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + + // we want a bit map the size of m_rGrid + + m_pBmp = new wxBitmap(m_rGrid.GetWidth(), m_rGrid.GetHeight(), 24); + + m_dT = DT; +} + +//---------------------------------------------------------------- +// paintEvent() +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +// Called by the system of by wxWidgets when the panel needs +// to be redrawn. You can also trigger this call by calling +// Refresh()/Update(). +//---------------------------------------------------------------- +void PlotWaterfall::OnPaint(wxPaintEvent & evt) +{ + wxAutoBufferedPaintDC dc(this); + draw(dc); +} + +//---------------------------------------------------------------- +// OnShow() +//---------------------------------------------------------------- +void PlotWaterfall::OnShow(wxShowEvent& event) +{ +} + +//---------------------------------------------------------------- +// ~PlotWaterfall() +//---------------------------------------------------------------- +PlotWaterfall::~PlotWaterfall() +{ +} + +//---------------------------------------------------------------- +// heatmap() +// map val to a rgb colour +// from http://eddiema.ca/2011/01/21/c-sharp-heatmaps/ +//---------------------------------------------------------------- +unsigned PlotWaterfall::heatmap(float val, float min, float max) +{ + unsigned r = 0; + unsigned g = 0; + unsigned b = 0; + + val = (val - min) / (max - min); + if(val <= 0.2) + { + b = (unsigned)((val / 0.2) * 255); + } + else if(val > 0.2 && val <= 0.7) + { + b = (unsigned)((1.0 - ((val - 0.2) / 0.5)) * 255); + } + if(val >= 0.2 && val <= 0.6) + { + g = (unsigned)(((val - 0.2) / 0.4) * 255); + } + else if(val > 0.6 && val <= 0.9) + { + g = (unsigned)((1.0 - ((val - 0.6) / 0.3)) * 255); + } + if(val >= 0.5) + { + r = (unsigned)(((val - 0.5) / 0.5) * 255); + } + //printf("%f %x %x %x\n", val, r, g, b); + return (b << 16) + (g << 8) + r; +} + +bool PlotWaterfall::checkDT(void) +{ + // Check dY is > 1 pixel before proceeding. For small screens + // and large WATERFALL_SECS_Y we might have less than one + // block per pixel. In this case increase m_dT and perform draw + // less often + + float px_per_sec = (float)m_rGrid.GetHeight() / WATERFALL_SECS_Y; + float dy = m_dT * px_per_sec; + + if (dy < 1.0) { + m_dT += DT; + return false; + } + else + return true; +} + +//---------------------------------------------------------------- +// draw() +//---------------------------------------------------------------- +void PlotWaterfall::draw(wxAutoBufferedPaintDC& dc) +{ + + m_rCtrl = GetClientRect(); + + // m_rGrid is coords of inner window we actually plot to. We deflate it a bit + // to leave room for axis labels. + + m_rGrid = m_rCtrl; + m_rGrid = m_rGrid.Deflate(PLOT_BORDER + (XLEFT_OFFSET/2), (PLOT_BORDER + (YBOTTOM_OFFSET/2))); + + if (m_pBmp == NULL) + { + // we want a bit map the size of m_rGrid + m_pBmp = new wxBitmap(m_rGrid.GetWidth(), m_rGrid.GetHeight(), 24); + } + + dc.Clear(); + + if(m_newdata) + { + m_newdata = false; + plotPixelData(); + dc.DrawBitmap(*m_pBmp, PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER); + m_dT = DT; + } + else + { + + // no data to plot so just erase to black. Blue looks nicer + // but is same colour as low amplitude signal + + // Bug on Linux: When Stop is pressed this code doesn't erase + // the lower 25% of the Waterfall Window + + m_rPlot = wxRect(PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER, m_rGrid.GetWidth(), m_rGrid.GetHeight()); + wxBrush ltGraphBkgBrush = wxBrush(BLACK_COLOR); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 0)); + dc.DrawRectangle(m_rPlot); + } + drawGraticule(dc); +} + +//------------------------------------------------------------------------- +// drawGraticule() +//------------------------------------------------------------------------- +void PlotWaterfall::drawGraticule(wxAutoBufferedPaintDC& dc) +{ + int x, y, text_w, text_h; + char buf[15]; + wxString s; + float f, time, freq_hz_to_px, time_s_to_py; + + wxBrush ltGraphBkgBrush; + ltGraphBkgBrush.SetStyle(wxBRUSHSTYLE_TRANSPARENT); + ltGraphBkgBrush.SetColour(*wxBLACK); + dc.SetBrush(ltGraphBkgBrush); + dc.SetPen(wxPen(BLACK_COLOR, 1)); + + freq_hz_to_px = (float)m_rGrid.GetWidth()/(MAX_F_HZ-MIN_F_HZ); + time_s_to_py = (float)m_rGrid.GetHeight()/WATERFALL_SECS_Y; + + // upper LH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET, PLOT_BORDER) + // lower RH coords of plot area are (PLOT_BORDER + XLEFT_OFFSET + m_rGrid.GetWidth(), + // PLOT_BORDER + m_rGrid.GetHeight()) + + // Check if small screen size means text will overlap + + int textXStep = STEP_F_HZ*freq_hz_to_px; + int textYStep = WATERFALL_SECS_STEP*time_s_to_py; + sprintf(buf, "%4.0fHz", (float)MAX_F_HZ - STEP_F_HZ); + GetTextExtent(buf, &text_w, &text_h); + int overlappedText = (text_w > textXStep) || (text_h > textYStep); + + // Major Vertical gridlines and legend + //dc.SetPen(m_penShortDash); + for(f=STEP_F_HZ; f max_mag) + { + max_mag = g_avmag[i]; + } + } + + m_max_mag = BETA*m_max_mag + (1 - BETA)*max_mag; + m_min_mag = max_mag - 20.0; + //printf("max_mag: %f m_max_mag: %f\n", max_mag, m_max_mag); + //intensity_per_dB = (float)256 /(MAX_MAG_DB - MIN_MAG_DB); + intensity_per_dB = (float)256 /(m_max_mag - m_min_mag); + spec_index_per_px = ((float)(MAX_F_HZ)/(float)m_modem_stats_max_f_hz)*(float)MODEM_STATS_NSPEC / (float) m_rGrid.GetWidth(); + + /* + printf("h %d w %d px_per_sec %d dy %d dy_blocks %d spec_index_per_px: %f\n", + m_rGrid.GetHeight(), m_rGrid.GetWidth(), px_per_sec, + dy, dy_blocks, spec_index_per_px); + */ + + // Shift previous bit map up one row of blocks ---------------------------- + wxNativePixelData data(*m_pBmp); + wxNativePixelData::Iterator bitMapStart(data); + wxNativePixelData::Iterator p = bitMapStart; + + for(b = 0; b < dy_blocks - 1; b++) + { + wxNativePixelData::Iterator psrc = bitMapStart; + wxNativePixelData::Iterator pdest = bitMapStart; + pdest.OffsetY(data, dy * b); + psrc.OffsetY(data, dy * (b+1)); + + // copy one line of blocks + + for(py = 0; py < dy; py++) + { + wxNativePixelData::Iterator pdestRowStart = pdest; + wxNativePixelData::Iterator psrcRowStart = psrc; + + for(px = 0; px < m_rGrid.GetWidth(); px++) + { + pdest.Red() = psrc.Red(); + pdest.Green() = psrc.Green(); + pdest.Blue() = psrc.Blue(); + pdest++; + psrc++; + } + pdest = pdestRowStart; + pdest.OffsetY(data, 1); + psrc = psrcRowStart; + psrc.OffsetY(data, 1); + } + } + + // Draw last line of blocks using latest amplitude data ------------------ + p = bitMapStart; + p.OffsetY(data, dy *(dy_blocks - 1)); + for(py = 0; py < dy; py++) + { + wxNativePixelData::Iterator rowStart = p; + + for(px = 0; px < m_rGrid.GetWidth(); px++) + { + index = px * spec_index_per_px; + assert(index < MODEM_STATS_NSPEC); + + intensity = intensity_per_dB * (g_avmag[index] - m_min_mag); + if(intensity > 255) intensity = 255; + if (intensity < 0) intensity = 0; + //printf("%d %f %d \n", index, g_avmag[index], intensity); + + switch (m_colour) { + case 0: + p.Red() = m_heatmap_lut[intensity] & 0xff; + p.Green() = (m_heatmap_lut[intensity] >> 8) & 0xff; + p.Blue() = (m_heatmap_lut[intensity] >> 16) & 0xff; + break; + case 1: + p.Red() = intensity; + p.Green() = intensity; + p.Blue() = intensity; + break; + case 2: + p.Red() = intensity; + p.Green() = intensity; + if (intensity < 127) + p.Blue() = intensity*2; + else + p.Blue() = 255; + + break; + } + ++p; + } + p = rowStart; + p.OffsetY(data, 1); + } + +} + +//------------------------------------------------------------------------- +// OnMouseLeftDown() +//------------------------------------------------------------------------- +void PlotWaterfall::OnMouseLeftDoubleClick(wxMouseEvent& event) +{ + m_mouseDown = true; + wxClientDC dc(this); + + wxPoint pt(event.GetLogicalPosition(dc)); + + // map x coord to edges of actual plot + pt.x -= PLOT_BORDER + XLEFT_OFFSET; + pt.y -= PLOT_BORDER; + + // valid click if inside of plot + if ((pt.x >= 0) && (pt.x <= m_rGrid.GetWidth()) && (pt.y >=0)) + { + float freq_hz_to_px = (float)m_rGrid.GetWidth()/(MAX_F_HZ-MIN_F_HZ); + float clickFreq = (float)pt.x/freq_hz_to_px; + + // communicate back to other threads + fdmdv2_clickTune(clickFreq); + } +} + +//------------------------------------------------------------------------- +// OnMouseRightDown() +//------------------------------------------------------------------------- +void PlotWaterfall::OnMouseRightDown(wxMouseEvent& event) +{ + m_colour++; + if (m_colour == 3) + m_colour = 0; +} + diff --git a/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.h b/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.h new file mode 100644 index 00000000..f4896c6b --- /dev/null +++ b/freedv/tags/1.2.2/src/fdmdv2_plot_waterfall.h @@ -0,0 +1,73 @@ +//========================================================================== +// Name: fdmdv2_plot_waterfall.h +// Purpose: Defines a waterfall plot derivative of fdmdv2_plot. +// Created: June 22, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __FDMDV2_PLOT_WATERFALL__ +#define __FDMDV2_PLOT_WATERFALL__ + +#include "fdmdv2_plot.h" +#include "fdmdv2_defines.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +// Class PlotWaterfall +// +// @class $(Name) +// @author $(User) +// @date $(Date) +// @file $(CurrentFileName).$(CurrentFileExt) +// @brief +// +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-= +class PlotWaterfall : public PlotPanel +{ + public: + PlotWaterfall(wxFrame* parent, bool graticule, int colour); + ~PlotWaterfall(); + bool checkDT(void); + void setGreyscale(bool greyscale) { m_greyscale = greyscale; } + void setRxFreq(float rxFreq) { m_rxFreq = rxFreq; } + void setFs(int fs) { m_modem_stats_max_f_hz = fs/2; } + + protected: + unsigned m_heatmap_lut[256]; + + unsigned heatmap(float val, float min, float max); + + void OnPaint(wxPaintEvent & evt); + void OnSize(wxSizeEvent& event); + void OnShow(wxShowEvent& event); + void drawGraticule(wxAutoBufferedPaintDC& dc); + void draw(wxAutoBufferedPaintDC& dc); + void plotPixelData(); + void OnMouseLeftDoubleClick(wxMouseEvent& event); + void OnMouseRightDown(wxMouseEvent& event); + + private: + float m_dT; + float m_rxFreq; + bool m_graticule; + float m_min_mag; + float m_max_mag; + int m_colour; + int m_modem_stats_max_f_hz; + + DECLARE_EVENT_TABLE() +}; + +#endif //__FDMDV2_PLOT_WATERFALL__ diff --git a/freedv/tags/1.2.2/src/freedv.icns b/freedv/tags/1.2.2/src/freedv.icns new file mode 100644 index 0000000000000000000000000000000000000000..5190e7951ffd9152576d6569f0c7ca64927649ec GIT binary patch literal 92136 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

-_`&2*Z5cR;kk!g) z*gVH`qzg?2CJr*N(AQ56S|hfFzWZPCS?iTqIC^xNIJ;-?_^UCVs-GnRhx-L~z}OBR3sMH@f$EoNUJjWSTo+D=+rv-ax&)QO)V$7@l zN(~6hAX#b-vu{JfpnbHv(D1meU|&cr;plU&2OGpmIQx&2#x{!Bj^M)*VQd%McAY_j zLOO9m2mx3-Ow}P8WAyO`y8ljIsjU)7EPGh4Jk?@L$#x6mz?FC}FR>L=Dcqn0K%6ZN zbBntIXy??3{=8+?nZZ=PwK?I~jXHhfqO3OK$;*>``<%yAB z1j&Qs1ANyD`o27csAM%g36ezeDw70yiOa+DX$!Ot?JT~R40Iuc73BVr(;%JWFTK0iv& z#TB*R)u7E$WO{HZpb`awk>Q6pzKit*i_+F1IJf1=oZa^5N&R?>4>KmFIyX z<{ZDm749wb^uljj{K$W|zF++)nsCUL{|B7%4~BX7-{7#n!2kk*v1@v<2ktu$ zGO_j40{i5>dh%w=;3gYoYAVV!h$=XERUXKMf9q5V;i~9&;-d;JlgCESm{XG@UzR79t>M9A*2<2n)W82R{HZ) zmxaIQFKu}H89NKIXa4;C^y%Zoq2Z=GsDFd4-V{(PSVjFTZ*#MS`H@yNO@DXjE=BDsdx$I;@dVj#DhtWzu7@=AJ(^eFKNJmd!`Vi+lwP|M}5T2O0WHb zp^S2Bb+IIqkn8=_!3Qpg;C0v>U-xPc)lb(e0i=+@RK`dJ6+s2W>Th^KI@i=EaMnCh z7Nust1=7;Iy7G1_yCU$716R1T^lJ~2j5bLJrAXJjI3vI6jS57XB{iu)-?^lv{_}RB z?R2kUz;72^WTdPtRsykIB2e9tNGQLhi6|*g-;-$+DN9me(dNOkDuwbWs?y@W)nfPa z;ZNW^a*xdpB`x2^pxraavpb;hT(xv~+B#)*@w7F>mXRv;yp2J=53YsI=!Uk3 z7}#(Dwd9--=6M#g#BF45*fuZ_bu3(9UC%L|UsMG2;=i-_`<}D92VSUfmi|)%mcs^-Iz51(v0)weylM%lEQ`c*KrfBAvj--5*M_Vu zG-t6kEZ!9tT1DDpsg1iWd;!0NW&9Ts_wwuj0WM~6@`@`kw-rGDwsVAK0`->K;W6@`GuuLAVOJnbvBAg1dTUIQWA(uGX^Q(PKwRS> zay-Mvq)o~T#12Ndl3(*WU77WETh7#2ejdVUkgb1KKdBGE&}7E-wuu6e0&| z<^YK-39(A_PFraE6m0_`s8V1g-H4zLTY!+yp}R7V%~=&iZiJXAuHia6j~MXc969t{ z>50A=$Ps~olpqbwXKOG?ua(tUSFv&|-CEzS`SFmrxQ`FGn>Zf&Z-IdstituhzBJW` z0gO?~`%*a+Rm4SZr|8bU`2_F9ao*(qo4Srg4y&QAk*gls*JqjW6 zbG;uF%pxO7t1bCa36p2xd!MnO?b#J>M~Ft%0iEX=C&K^;Sz=CBVs~XT;kxp z>;NLDn$A@=?vTOvLTX8FN=M&CU=m1*<=KpYvZ<<-MM0Mppn86&hi^a-vi&|1!1pq6 zx7dys;AGmDpC!U}kgE()0zAGlSnAI_dkY&?c3Vvsdi54YTgUs@81gV1dGI^Qw`sWE zSq8|&t9%@SMe(9M?>OlV2m8C0q%@swC)NoC3Z4ElAvk z4TISU>z%UnmcSOWZI-FSZqh8d8sa8sj9^pEk^%~dkg5L?CI!mYKZcaWZ`^+!T2(OW zErNG!80mtu4H_qGk~#N{IV1vKl3a|s`EUJ9+FpAk%!aMtb9xhzXrs;9T+|M|Zd-4D z!DK`dV zvj_@^Dkq`gkD9&nuW%d!iKY7wV|YS@whahE?0bkqIfftDSya)%)2tzB1@VaMi8u2%Zo+tyDBacTQ4Gzh0 z1D-gZjEEXQGM-^?VDy*LB7aR|mCI2Qk;4>JBB+dqo+p2O^zmMHOzrvS_CJ+nvkDN# zL5MBQ)!BJAISoP#Xw^lahaOjT-aL|ZUk=<}1|X1}Wkey#uz*8QLDO{-`Q^Xh_0R`A z=daqpU8h~_rVS(wl$uzv{NBZE-a(9gmtoSPHvam0f3yrZ4&wWCtLjk(wVf>Vs1uoV zt?%dHF4W4myiP%#t~|*iFG5s7WS*(iQX@+3?)C&t2U3$f@d{GUw6TcP1E~*H3}S^h zAP9kKd^&o0rNx>TCoR5zkud;b_kiHXHBbOJQv67h?b`XOC66ZTbOaIo)bA1AU{|2~ zhyWLnF0FG0#E?VcJX;|SF{^D}wq}fEE7LrK%M^9)0p1TIHh%&&V%owOlfu{na_9T_ zO}-kG5}y}|uQ_K0R?7Li*=CC~{ikPZTIk}On+y9Dh1%g{@pW5I^;*Zd8EcG>BXz*R z%7EI?y!Vt9dZPB~=p8m6C$F6hU``U^U?Fhg4oo7{;TltIe@eil{cA*7cfB%nn$ z?v3FV-GWd>njLUa-${r|1Y1XV(UuBLwsxrv^@(!b1@{!Po!SmQZ5n>sM?h)|QdMi; z6m&9UnHAbWei_$+#ok7{>8bNJbzeV*%}r??CGGfbjIVFl(a8HPeAnw1Ww)YG->MZ3 z|1tSOcoQHvZ5zg4VtJw6@*Cpz#|X2u9sQuPh%m@@F-HI7PSB5gZR$d=<;RfTKKOSW zH-WZsZloYF zNavnz68A$4d}pIv9DzhU#8&u=gA6R2-ypIFy&_E+x)2}5=_1k}#YqH;s(2iy<*U&U zpB*F#aG!^>bLWsa#jqcN^7VZIWfX-|7J)0#)W%^=tEnK4RNR)nEk*Q; z8`$8ys%IdTLvc1sGMgiFFNr5rTWcA%>#8_ZDUPNnsjJCxi}p^&Hj2(bO7qG&dzDXl z@E@vCe6RK{PqR8v=C=&Iq@@%DVan)|$2Hrh7^fQk-k=0P1A9Zm(n1?yt6$=dsXmKB z^|fr(MQozfZCL2e8CwckY+_3r%T;&s5bo`J$Nf6o_+#jc|BAuVQ$ zfOsu1(3AUlF-+OfiN!5VfieKXg+Pu_zH2Yyer4Em>o9`)L;f9b{JJGO5#MjA-7#z4 z!&2#n&k+M|DG0AxCdZDyAXq)+4{NqV$fgB4+7D4GfdFKS;U=6UPH5Lfn`gRhE@-4# zv30nwMKRh{f0_1t{LTmMrMg=!E3v!`!KbmgV$l1GhcL(v+s*B-+5X|zSs@MKb(x0^VKyEuPkW@6KD7%EpsWMOZ(ln1jbmuHP(+1aPiE+<@b;l++3W7dm zW~Ewz7P%3jIK;i?=$i5nb+=AAf(Y5ly0^H1I4hhm0tYR~lO_yeh?9gEIhr!Uuipo? z#z0PDBsXw$%}Zz5aK!iD^IdrPMpH}9y}4pc3XlYHk_Pll1KkmV-yB%#-YG&B=UQY@ z(pd|NLsD-7Klr|&DM{^a!{{w zVzlAr3&GCy<3G<`D%&` z(z4J+em$Yiw2cl%(NUrDxn1tOVym)z10bg?y-g9^S+iM?l>yNR0%^zC3m0nv;pBPPJYD4K((4DOSfs*fIkGeljpKua{<&LR zoiyv4I9~4*agZD!NKpn{9h-+lWtfunTC)KBE&_;LH)-u^Fwf@S|pJ& zobR@k95r_bOj=y}r@2S!!A%%nW&(8qgbYPj;C_ZSQ<~Qr;8_syWGk9T z-0CP#TjwnHKMZb*LI}zyVDH4H!W;{nai8UcpFpSVo}q2eQ$L!L`81CvOb8ErWuh|1U9W2K8zg_I;8?ZTX)M_zs#${A>psm^H#kP^bJD;9d5~gAxE$8xBC1xRO4r_2nEZ<_Nw-Kvb0I?ODA zmZCaH5^yn=kA)XPbYF19{53ke>;++@1V3(Eakcx71A~wMVOB9l$WJp61?_Gm!c+$8 zCmp#!IlRN=h$HUi9G-f(g+5^UDV0G4!WZeN%^lP~x-Wl21oGBwi|--qHjVOo+Ch?y+KC{y z-H7_srMqHkZskWBlGI3jF zP`xGpni^Uhe%SY*TMm-8vXv#bF-AiGS6F4xV8M52Vz0TaqjvK3o9y)G??+M;t)1~n~JaX7kzqmY@3CS)!A@rlEFP*Br=-3x8EoH89@V_ zvH}co&|beq97@Q_-Q_@;r)t@V19xYZF~?Fl%Zk{AsGIxG@1&k@v$Lnr&(DOcd4@JU zyM^}aMbeoeS&&Ej>I*<5O_^#7wGbY84QvS(ZDUDw3aP1bG7#Sy^{y;V8npOI!s=Kw z)X0oTG&{sQ2%mhp1KJq}F5z=MJ{=9sYz!hKmQk?m<*7|ypx+^W7a&RnEdh_}`w7B_ z!0GOyzXHx+#`10py+HHO$zl``;#`D{T$Xcabvuc_iP-Y1nt)cg5rL86gGB>g90ZVc zUK~$9%DSE`$v!y6c`+J_ONf&wu3UL#DDrt=28bAFUIT$9WqEEMKhbO$8-R57INyj% zIA`O&tjF_PE~P#&#Z9~*d>>I7AfS{?268~YIR01$JHGOOLxj>?9*S4u#9#nr+le^_~;JRt~91h{DW zGsJ#?*daV$pOO^<3t4F!wH@sy2~Sdrd?<>XTpb0J`&jqNLw*CYy|^w1QI?HIQPnS+ zIq}0kR0cpc9?~reC+Y{iuTCh{s5%S()=Mp8-Tn0Ic6{eS!uq_``H3+a1azjjvOvZm ztn&#tQ(_3tpmPC<%ZeMgImn)7!1$SdXWe*=GVgd{U9|`j37d@cS#3RfmS}OXC{l(tCK0EnTxp2FJPTx&*%UMer>tiSur~)C z-*m7$Z}_4$u-(^uzz)Jb_{p~dM?u1uY^4XEgWcE)>L2IL%cZwp;QB@-=9@PlFLsiU z_DvMB3t>dS99sAblJ~!E-3Lxst}bpzj~}$P6`s{mcH2%kM|i`cO_(dj=B!oQX~ik) z>bTjnT?jh7z)0u7)Mf$>U5`o?#_mzi*#Mc|Bw7w_>_$q(Qkp+V_&vhECwv_9z+PKA z-C~&~IC1FT@$3Krsig*PE)@+s$*K%}AZtA*Ek`U_-M9;ZE*$HnFCXI7 zcN0F(y9-4X&j|FXhU z)?FqPw=NHK>9%m)JmR@?YH@!zOu;?nZRYdoYQj=8I)?+1;X)DSm%>=)GaLi%99{^c zaUzbwV3$a$uQZ0714%!1mcvyKwV6elZxEHG|HlwJ!_LtVSzYQVcO3&TEB&5)NIS~% zl+(+j>y@Cq`Bt_O>=XfIuR&CnWFje~ zSWzxvjHu|}f%Iw3P)LFbflLXAi>5rw?MzfjH`DeHN&qXH`pF}z! zdQI1o_4bbYwq)y5t;OlKXnBN67iYyFmg?H2z*_QiXbljKyd;h?>;?&TSb-Ug6x9gj z@8!kq1j=SVM7W8d^hBYGV?69cfpWR31R_e_74dSomFRD$Y+LQ(7w)ztwjCeb&30bx zoR<(CB!=oiyVN&YvVP7M+FEU+zTP5RY2)^1;h?Y6#>`&0Yc5ei;6=6tyLf!ia!0pe zaPP5}9+Jyw96XaWNVi&W z7VTh$YVn>mAx#ik4qJjqssF1NjjFfgH2YhIdXVO>WeYX_b6ZB7fAQV}Hg!tk;|D;z z77&!dBam|tqJIr5zPfgg6p=vd^ckeapl1X!)){cJ1P?N}yGa*=P@?yeCE*a4rn?bH zdFNv1AqM>g(~)A@JaY;0e&Ur~95KZJlW|Hr0Nug>lOsy^VYBc2JZA$iB{dA^saxYY z+29VGW(sOaYw5d5%K>D|LM$k%gn%f-t+t+lTyYgvq^z|@#;uK&Z&M2_D4HO?O8^r6 zFB0BEAYY#iD3Sq==HPb+Btg3ZUkTp=BHsh?Ya%4Au`pxnBej->!$$gq5c&dSTRv#Z zuXozxW8Z8?PCjh8z0~J@i1Pa&Y>Twrnj{gRdBI{mGgc#KArNt4gablHni26Ks%4tv zN0`4@jPHL(ZKgYFcTK%$?fE4pS0o+K7lNrS+Goz}$3AZJcmaO#$ip`CSJd$oeQST= zQJmf(v4oNM7I1^k0B%5$zn!Pt<6!i}dEUPpWUnc!QKx_1tn7xz&? zlqTQkog2y!@xg@3Sp*Vq6&%P@y@;J4%QWK#({8>#9xHXx;;p#2Gqdy223GDcKJZce z5ON{%DL_0z;?{6F-^)PxmO<&OQssQg&vgY+R91=m7((+HKV#TRqB*8ag%6Ikq?slU zJON{z9axCQETpKy?eGR-vvK6qinN z{B~(dwg}2DCn7Zoo2|qX8@3Tf{qa82!|H_1JzkwPcq^sW*n5E;U2+SyyY-~?d?sb< zhdX)rhh|^gX7)$#DyE8_8fOiA3vR1vrsC|i9f<2(<;Wu+0QRr91`Q6Rhd6_|j)84^ zLHsngtDGK_ww&wn;)-xQEat{3KLvLAF{Y;C48R(>#A<)QRycj_G2%Ue=sC}3J*NnW zw4IBHw;{+v)bc9BiLFI~AraQ08%AAgA=HYqD+BiW=t0Z(Au({|ndhlEh~cTBw4G?* zXQ6?xb>B(a2}wC0LSXp_;Vk!*;~zYo^#TaYH}kaF6E~p(?6ECd&shg*RDPU#x^zL= zPe3SMJ7~kV9k-rCM`_zTU3|7D9%LHy&xrRW0-#ftz^ycf(BOrA`0f|1J+f#KsX$V_ z93+t_?Kh!iVF7#MJ$5P4g{lrQEyLPhlMn6k#d~ZCDXWP^I}P9TgoQdJC6ox77W;Hh z+WEVa_;b;^yFmJyA^L&u4k8WhpgqLmpSX>MU-WzWyQwphh5D1UTDDxYr#m0w(9_eT zS(MPq*;mu`#v}k51T2v)G%|OMuDa30z;Xu`2DHYQ41ofSTLD{u%QzL0?k5Yra}nN^ z4PGg@mN*V`(x%mG_|OOIaTp{AcH~(*&cJ4_ia#JV+)#RS2+O`dhI{5Tm8jsHMwv6v zGjJQ(1|%&|R82qn;iQxy#QA#OY5K;2GU-r^^%`amnQ{~7lxm`#cZ*^P`D5(F$d<^Z zF`Np?_$_gda99qSfy@VXag!y~r@A34&t@>*Yvg>Ql9$q%8Hf z+yq1PF>UK-AK>qx1xs<tWnV?NHz)xBnVZj9`2FY58tZLre?Ry+NVmPu zC3RbZ0|?N$v!8jumN59$X({fW^X$?K=M*v6I#EREmlgLZIK_b3<2$&21!-cNEuL?% z?MtUI7V&6~@9ek9V++zVW~tkcBSL0ul~aahPxDM+nsj{GFdS(7VLVcI5+6clC2G8w8e`Y`;?x)9kNsBmT#8|7bwb@Q<9-m{i z^El@mc0gnZSkiq13~hM13G_=I1;xhD^-6&2YGfjj+l~|>dwFK8B77I!P4~HIk1xo+UfJDxz#g#<>WCze{vAPWLQ3Lp|^XN4g zLUnSG!PO?pv6yOM4hTVCU_g;Bl0gAISFWUCBmsHAx%|5X$B~2}kW-I1k2s)g4&vBc zG=rxFH-4CuTf7TDUOpRNEU z=gONhN^)3B;P52XhkRwU(_$&NV-SapN8tVF|2Rd-QAj5PYnP!6#7@Q$q%a3@UnHOc zh%d81oaw!orU)hF$S!alP+cqjOqazWJqV$s6G;_m>>}F7ACo_^=y!o6I8?G+4O@!q znZ|<}*q`eib3 zh#N8>g*fa8oJM5LK@h|#!;R$AoFcAZANdZL4W6?sL@EwJY20-lT~>%A1Q+1W!<>E1 zNMLPKDe#suIS6qT4`ddUjElkpR^{_R2zsr-O}&Gccn$Gkq0wr%&ywpaa8rJ#Kg%~q z2oQpv!CKr z(JsidlTAphbpZ}ff5|miuF7*4kZSbqA>zX>M(Ka-INBL#nk~lii!&r9=24w+gAxD` zBgA14IR9v&PfikCQUkpmq~jvaF3{3h)(Y#qx~K+>IC7aY$a7DdNS2qs!nulg#NnGv z9pw>W;OAX zL)>dN%%$_B*_YHV7wGV?CE2d484tk8=l5Z8hck#kyt)GtRj z_u}$*Tf@P(*^Z90mcnOXKIpXZKY9+E!C~GBP7#iQrU2e#X3`|)5_OoP1+FDp8Bos)j*6PP(Gv5c+Iy!5q z{nWlo1VHt;HmtSQ*!E{8?Q~~@qR@vVhlmIa3LrgQ(<~a0Y)gntYgM8DqNB$b`boi%0US6zmcMxX~i0jK9AVZwE zzjpQ&cXvc$P-#jivchY0{PG!iS~E}vboW4loBJ2rS^2ur*U27IsFK{3H)80as}2Pqmi&LUxeIx$ z;~`nVS*t)9tP$x0g6=}kJOm=NZuz|-b|lAbTYVBahMIPUuB1(8#_6Me(wU-;N{_lq ziohNd!$rA<*^OX@KFpD3h0tDE!mt0|*CFp;{{{crN;t+K`81aOB|A`$p7=Z+`PYX? z@+ow5xb-V=dciE5^vUg(NN!kb??iEsK8qgt#oKY9>qQD+fE-s7zDW2B!v7$A3ggw6 zEP1ZOdQZb)Mi_|aJ9!RhuriI8V-7#dX+OEg@8DcIp2?>`NZSZ;ac{P5Jo_UwlHcd| zPvXGSZj z0cp{WOCeVe)%8YB2D*rZwu+Env1<^$_BGOnm>-|Fv)}(ioa!@RO?yvR_z=%)$XoE( zqS=4vOoOGcZ98?z>V~GVt2~6D_Cdb=PP0#b0$&l@Hvj)nC*ta?0c=5bZVOE|Wka{V zVcA!1;((XGA~8vU)d|t+EcVx0Y6ijj76^XAYA@ws>Jk@0K%bTx0x?F4L|t1$jhw-; zhZS`A*t_U|W(xzji#Q;5_9?#k34)}6-ywX7K-z0h;x;;DN${_}KpO5X$g7oB41U`z zhTa_CbmtM$Qdl?A2_(lX}kv7Q9TIVI!6?DU>7iq6`AuNQ%xh!qD2_l?$ zoX4(FaTYAUoBlEAv4(bzruMWr-VSx-^eTary0Axr3J^w>> zNkhmc`9tzW;09vj({LJaGBWw}6orvkE+p_dW%AT}*GpO9kD5?}_aU*|voOS7gILmv zNoop92b?mEnP*{AmYD{5A{LJ=D@Bp<3M>h;PF_-Vpxuq+7^CB`7{gjkcr^&^%i-#` zE~#h*ou)i7?1gc>$qH#ypE6)K;b>Ldq?(ZIDI;5@`EE)&YCB1Lg@xFFL~GSuQ=bi4 zONAP2L+XJxM3tMg>nB$uSp-+$^=5Wmq`AxH1W(TwBi+XB5NR-bQWIDzhuf~&vs_ip zu&_a^G`kEogCI@W^CG?w9o1V?zWl+;{)x!EaPoKOTvS##MhvyPPjn{ z0M#zkF?rKwb6Xnl6l0)myGRex$vs>&736@JI=orUS?hku=5b(2oocnlx+xH;%;|_C(e+Y` z?*tywOu>1OZ<3&yjKh>|76P#egd<^BAC%7~gx(YfF$g3<%;xl+Mx3JJ0~?mEAsubT zQHI-&*;?x|=Fgif-^n7W2VSCGj-zr=uRV7`Jhm5OiWsTB9i(??+|Ffgw(~2f7C1~< zrzjNdR9@&)PMd4P)P@~>B+1cWY&nJ<;!XGvx01;IgauZ3Aa>2z+)S76tAJsq7;Sqa zW$55wCr)r0;&9aq5R28f^X>-;w=xb6@t$%TeSZ5ZH1{NGU$;vuNgT5l+0`j{mTxjY zLL8n~DQp^>yyt@ zi4zM+c>yA`vYwzHlB5AA)!6{5LLot2#aYF5 zsb57!QQ0e?bk_;GUJEF@rZ@3ajNuHEhpT5Ly~w4x3=ke|sv2j(Rg42TbO?u2*gTR_ zinQ}eAD}JtRf5@-FdKKsoj72NHuqNcw)I$7{gH!FVhst&&tKU5ntaHUguI?=M!87) zxdmzffjc41q;zc0fQ@w|9cYV6_&4kNcjoQ!i`_Bjpq;9@`0u`Tad)?^;ns2z?csu%a@k|i+3Q-@s26hQ=B)jb>DeQ zzV8_bD0Om&DK1qAn?^|$mi{dlS=@7wL34mpW%9PsBIle0oFOOI%^U`uNg(*k0I7WNaJ20rkt)zR`aFwg8 zbY|)3+A-Kc-`DWFG@_AA{QQ6wHsB;)tQJo|%7Z&lDLz-Gs6y;&)!C~jSh^um3 z8|1)~sYNo<(&x*7HqqIKs2@=XNQwnJ90B6ePbJzSWu zW!j{c{t4=cCnE#nf(>MS7btCBnj#NLfj<4asjdCv>iMIQdjo>F)OX`C_it?mJe$0S z0Z@-v8vd8)_a|Vk3-MkzZ{l?Fi4ypv6BET`XHz z#*TolerX#s68#Kd(pdyiawFxKo30Feqa1E*WUxyzD2vbKSbNS`^x>y1vVYaij_$Ul zMF=aZImW=mb^_mOOEE1XYFo9I**S~L zMiY5~_z;UKg&ULrsJB*8`6evE0En(5NJJNiI4~sDUBCs=BK|=~J#!Zdbn-5vA;`c1mMG_tkiHaCGf~=ym0y=~Z;^ zECenrUxyaev5T;Sa2uhNuQ;h3s$^wfO}aZXHGtlfq#*9E1^F1uf#+D_%yeL(qGr(- zNy?>Dn?P8dr5G>Z1VuUhWS}rqSrCHwV)2h5jp0meg^ScDvxjFYo9azjL};}onf7`V zLB)XTOQ7*w)FNVcul96^Q>Ac&5`ZY(#Tu{#C&wVpN7;ZfQ4jakxpC_Z9$yzghp+!B zM&njXXb-@?X*!?gdTa{@W~7B*{ywwc|0cu-h=8V>lJ9_XX}~hD8XjT$`?QVi?Y4ju zX>A)painmE28)CObjt37FEdSzjya4pqUEV9mRUaAZ=~Q2)l~$@UF{phTl4yfdqehHy*($>(8UNk3lS@E?oy!+i(SbYTg<+ zjVR}BqKLHTHjucPHbON@V?5QaPCA7tFTaQ6HxyG#hydNfzJr{)EX4i<$iG0J+8+=; zOFy;JW{iO-+&=;_&_<*+^plu?PC91KfLg{(D9rmZ!Q-yl;07fCbgZV93O!iHnG0qO zcwUxXIq`@aNjDc1aDrJuw8Y(2J^&#dWhIF;0g_AquVp~lnIg9l?rT+%I09?mb>KuK zL)u~@1xW@X0;G+E;9HGz=ww2iUsGVRFfU2u7SbUdXf{AQ4{54STv2Ne*efrD?kS&s zKZCELn?uZM8R+<=FXtOEUUM3*oR2h6%b>EkaSyLP7Qp0XBhbPmNzGc4yIEZ*f8%9Q z8Y*zzuCeD$)?BZ1@cL zk!S4Xr`e7)ehUSiAXGNU7GqxaH@1;ys+H|Ls7qKIc{uQmR&&DfwY`|is9k{*TDYT4 z6Z0Ukz|Zuf2}nQ3Y(hZ-jT>{PxI#QNL!uL%G_5InOo6oIn;@?v43_ygvph%!p%7Bq z|8fLU<5d$N0<(xFTzG_K^l+)OwYGj@56>6{m*{-$)h_^_Z6kt6lp~7XCP0x4K9qMN*pmiS%02*V+7I;fusZD*7M*?){Bu! zo`Lpdxak`^cn_k8)1*guPFhd$obv#DgUee^S`1%+I5va)iinj6h5$s`nrG>8%#JM` zu;@r&UF&Cy+`SlqY9UcbrNCEzy-;sC7VlX5EZKqJ>`rXW8pKxu1Ws!s+{jfz=t>mw zl%+h?ZneyH`k9Fs9b;^RW&skC>==TcOYB#gvXv%I)ZI${LzGJ#+8!o6LfB3KqzY{T z@pfHcM&u}42}W&&6_o{*Thp@Ffx5Yr%?{(lLm8qFgCcKh?H%c!$Vx%M}WSx5UN z3v+5SyQLJP$uKH_Xr(%E1*?POyBDCwMhDGHI$GNxr!M(v{BL0(XD|WVFadMzAD^ z(a0dtn*BKj+RR-H44(TK0I9SZ4ks23lqd&iYJdBnP~vn#@H_*X!I@dDgPW6=Yzyp& zW36z&bIOo3g+RVUm0PV55I4eBAPas1y*is%#`FhrIFL67a^RS1by&P3Y|NR_>bml= zTZ+RVkNd7WtE@dhB$l+3L+VH~=yd>zHoSD{gnTNXp`o%|Ylwhs+V}&hD1-4XLWpYT zBB>?X`n)_Z&eWwm6Z1$3-lXED7~?REkSFz(q#}Szhv2TNy?h>+r@#+@zv?Ec_Z2~l zdn)?BVv;}2%0sj<7VMC~N}8j_IYHRhVw90p?ao?*5H^;zzwtlfoN;T3F2w63Lp@azX49!sYO2EY&H*qt@ZE@r= zs2vATGx#9KZ;wkID?l0$BD@(0S7IS~*3e$~!mpyLXbZ2tz6on+62hol5JJI)PMetR zFa?4TqA%R*@b~cYq)!K_qxON=Jq8gtOaDSJhQ=*|&1Q6Ai>>Q;^w_)jMrEr`xIqbk zINN=MyWwPPkdUv=Ec*dO5$$!< zlym$Z5KA)(EAt2_An!b7dU-#Cn`1f#CoIf*fb|R>i3CQTQUv(6E;VyM9`c2ql7{ZyXV(xRxaifB5Df z>FwG^3(2G%X{fJx#Ns;KWSa6Ah$Dblg_%N)t>Z1oa_2B2v$hGd1O%d%HY}?EYi*bz z*gjk{Bg5ksXP>`Iw|8)c2kE;?sF6gqRXv=p~2ii?A@uqA}iN)9o|#Sk1JJNJ^!wY!gl+NBK2L(g@+2FTdQ++&1= zQF5`}fY{`;w3!V{id2OQ7We|vl;-Orayrr)-z@EiHzJz^5F7bp7whz)|zn;l1=A0;XLmsug4)CyF8;q5U@9ha9}tL(I1ZXsS>l`t7?Qw^HyWw z-+Gx< zd%4!{W1xtu6F=9;AOZm}O3B(TojuQH5@~tfNqm()zvE)j*u}d5KZ3kw0%Ylye(b9Y z_me*HcTd7WdA1Xm(*tL%N5(q1?J_&tBwHm4_YeesZ}!Yf7W$KKwwBg0Ypk8I%wu=q z=rYW^QavC@LRMMT*`mYk1?;-PGlsC%1Qds|659l7BO#rxx_~AkQf1hHL$YCN?cVe3 zyFU*K*I8e-RO#c36dQRW@NA!JgV~OP{;X z@~B2NwR!8h#L;P5f-Rej1MO@-PWWL0$Y?*$S);eurN?mcnZ>S>_$bdXnhNAoAcq|g zIG~O#XBVtJKE)PZTX_jr7vNc`z5Q|!KONAbSwD;YJb(Tx;kQvs$X7-~5fjbOXr&-T zFULl*YK0Jf9+-|ftx1x{rgq_1H9?v5S5uk)5y~pWz$%=_5rplmbMo08?dRDHoz39U z9{&OviP#f9isKHxBoG7!u9gDJ!9{~YZoM)d;=JOZsV-Cq?J*x`YApn(%Z5lxyZQ>G z3TWC-L21Ych@s5!Plt^JLPpY8@$_6yMhw&tMzSdHwC{faB8r(7DUPm`x1ogv@8R!mDUa_!zKJM_6~qOkep))agYUF>2Yp*BP;ZRyQKbH^ePfj74Co9Ej|M~6>Hf1ZXw zTwoyL{KKY#K8M}O(%U+=3(q5hy4p~&0c@o_Takd7K3k}_g-pBU>V+T-UI)6khu@r6 zIQP;4TYjP6nw!p$KZsN_jWj}jIwj|;g7=fBkYpC}Q?O~4#-2XB&roeH=d@P6!^F$2NkWfBu(wM%scb$SR@>!bgxGYL?kg0WZyPn+QOOFv>=7=P?Yc ztF|(Z>hT)wn0^QEA0@mU35>Rp6BcTWAKM=6g0NCfNo&|ty8EYWHrH+$F^?*i02m!N zB8X!jU~R{stv7}((+q-WT1d_wetL}tEI{jo5S=B;hvZtTqq#I7B(_UG6yjIcmy~WQ-i*!275-dnWej$&sO=h z07>;JbIekT#S7m#x6~CVU5Y=JkYYe45J5Zs>J*n0llUGWMdJx$|QnSh+10M^N(s7P3SD*j??7e5OCFyzJ_w>p6 z=A7@Ootd58!R`W!On?wzfPzR65?P{CNmQvMe{z*2f3p42<+3f8%CarlVwq*Tl!~N8 z&f)*CIp=ow>3;j|_sMCH zdxv-~myZDPy@febYL+V@)|nGisYv*3F!MTnu34SV?K5Hwt2%?$`|0CF>zwHv0dV1UL`2z z3j5kty)%PjZNMtn63Q&vyb6hCH#k=SLd3_cNtd1$5q>d(dm=(2Nf!=SoMhuE$HAkp zNNnN&qa;W#@xRLJS3ryi%2ObUCMiOnAgzc*mr!s$;u}=P>~ML~igYjudeiE+j z)_EuN5pZ(>(Tt%|4XF5Q=sNp? zx#nw+0W9Do0*e`hY*km_UNp=&0!g*Vm4qWs6f6?N$hMkzOh4cli&`=JTH6w7)i^@B z>jQD4dmtH{JamWSER^ab3_5A@YDE=4AdyrcWRpQo1O&@@6GW~{s$em;%A8YVQHqOE z1=v}hUDO$b2}0Dy(yeYZa+SsST}9m60XHGBCyarzMLMW^KYQO(>x5ilz zGgZln;swAfQQ1pcbxhI~h!*4Ka))N&vdUm`&<9~)E>#SKF}8`fh#qr{a&0a2e1Fo9 z`DAY0wY86Thf7g?5rEUJbXs~ft3C2`U0pF zcoU+ob>LJE#;O1yy)SG9uEqQQ00C7Mgt!Ja%sy8Ntr>^1Ij;mCJqW=n>cOG^EJNmH zW5cZ-zlVA^YV+G#EWCIKy)D8Cu5}~;EXfW0^^LE9;KBofK)MxtLaXDsplv`t}k@2j(rc2Bb(kZ)Ppe_yzXuA9SYP_V}LJ_@3Olri?NKrB>Q zC?g^O0*Hc8Ftb5wYmuTAH40=VK{(a^KavRVfZtFKEHU!HDtB}CAi8KdBH)|~!TNUq z0Z9;Q8=;oKUU=A=Bm6cE!O1f&wFOU$@CYCrT@WH@dH9GT#B`fG{u+45JXM~h^Uj_v zJb%kJ=$LeT_dN6P2b}R8wqJn;3=j~xXKVC8jDHGo`8@MDyuf%F-wSZg_d#lK3x(Tk zp6fr)zU5KM1&q>VsBC*4etL?HKvCIr;w?6=;ee265I#FJ4ox9?u>Eg4`}Hw~n|7ag zjQ4rInqAM}DOlmXTo>i*yj!VsY7UCpCbh|hfY+uw$7h*}GDL}Yhni#0U*)%7N4QJa z$n=Ko-szA51`t=Nub|l|nbB)6f1iMLG#3cJ=6WJt3H` zFZF@cUiV#Aa&bm^7=-i$COMH@*O#czm?Ci3q_P&}qGTVkfX|uSHCgBdTx|3;NX0R@ zah-;eivs;s=Wx#^`>IG8iE5er2kxQb7BUvf4TzZT17q!i2#gR$pFn3y{6Cljb<81+ zx*dHfqlimSypt}1aYLZ?s z-ZhaDLeA>mxxaIbJUR%@!v<4Fs>15Qca1dwJs0PyDiVj1VX7g)wJoCYiO2$)UqpG2 zq#=#nbB0PC7-2)wkljQeDM0ZHjV11ZCxwElnoG@FMv;q(0_|Z4-arpO!oSPVwa^Dx zm-7^`AzmB;?l`=HV`Q{b(2YO_JL=l2b4^GFG$E?2_Ra&`8_s}LhcUR{SA!Yj8R2Qs5qI^&p#jEI8niIqA; zV2@l{5xN6KM5wfY9b8A6pxf}(Nt@2xgCieW>Bc-9TB2zO6s}7jMDHkWv-KtGUuMxB z@(leTkqE_lfcTFt2Tck{?hF?~CeG33;1fJE($7j3ji;a8_{5PiVoI0wKFJwp*g%@C zd2orMAn8LZ;AT6zJ_l*7g8>>XO17(_55+$GF}%y?whnb* zgPaIz0buZKQNg$AT66`+UIn?|;r_fE<}She?9b-$_aAUV=iMq!V13l$*llJ;*74j( zeYni_1-3W&EgxDGdIwU^ychkiGd;dd(a=W+CofrM4x8Tt?-diL8nMNxv zaGz|?dkB_e_^ROyGQxQ*HpIKfBmf~G0WVFsXzl80*2a0nRIu7f0W2_801mp-jPSQY zjH`8vXK0kMM+^b=<86}#Bap>kfk0FO@>obC&)=r6=LSO=gLuFN7x-R>yXe~DgjJRo zx_se{i+1O?zi#=P^F$_=?N}~Jc?}$}kjb{IQ&lIDRYH)An0Xi(#3TSA8ZiR1BiC$< zOvL=b1?~wlZ-a2JJkR*V^_~PFq7V*g;VIxZ@dc0o`w7EL!1)V|siNYVQT7|xxBr1q z>3a~!5qsv+8#ad9vGI?71GVWj8g6jy|Le2NgLG~IL{U1pT&t4A2w84aGnS^Zay$k> zb>-H1KV74Sek0nOVDEwT0Lb>=d576-Z@AQV}IV$s5$8a6$dnsL1)p)f)HIq(k zgn;n9N!Fb>lP8y)#8K^(*e~&4h9&9!SkYB+*rvBpM>s|5g+x^TxrjuG{4xY8Xhpty zkiLo3(bDf96`!e$8xry;^qBjFq-H3mApj%?k6@-^C}qWZ26SW5A{M*mrxLRRi6=PU zeQH45JFo#fc8swo`jOu$Z@)yeUmOA!AB2m_@aCEgKq!Zp=V-}FwH%@|;~NEuBOu`b z^R~!533(jg#AF`ANLijZF7xjp5!6ZGY09|jeD{(_G@geb7hnZd#SM9Xs&t_SV<=lMtSI$@eyCOt@SCWIxl{h#p&^qCs_!p*)KrHPp~NGF5R%C zYWUrnb1kQxnjkTL1H@N`pIlU0pF#@7<}<4`5UrlV$E;!?F3XSKE5dMT${7ILw`Z(= zZGrAwtL9M~AgL%d41-`LqA?f1xaP*8`--Ehbr$Co2$U(i(9~ejK^!*mgD*`(q$pk< zFJRaL`BhLe08aI?$MoaO*-mqmiew-X9eAwFvx=C=w(%h#(xvTj*x~_sF7g!$s3IG* zNy`wllY4HNeYZH3xwJ2$4e>n51IxF`rJ#0z(ISjarw}}d!Kbeyxqw_9hz#c~Nw{-J zm6v(mHYL(ZG*)QT?s5ji!s+Iz;B@yHf#4ofUje@eWa^1groKYZ7B7X0%Lhmo`Bpz! z&w5Fnc~ga_^UJt+%j8^5(QNrWj==!b5X6@JvC5dLu0?qyq}_L!h-2zA4pqSUG6It1An>4f>)+oQ!(kd z0_)=bjw7;=(+~sssU_-*BdTo}c84%UGO`1R#pv5EB-On+7GQiF8yI3R<5obB3%}L< z0)R_La0L#Ky{5#SvWshn9X25@Xg_{O9G(0q5(!bXJUR%Y!(Grmo~6K;ad)N&nNH9f zpiS<--yBrG194D=@IB)0r|7EU4rI5GG^DWKeV$DckpNvj#Ivh&xu<;?U1Md&&tp;n z!9S-PlhFB-4^;yWOTq#k)&z8aaxd;(okb7Qc!-K=%0`bocL#0&fq3hJkC4~rkpSQfZhgY7%lG*0GyL|G z{CtO=LtEDOfP8tIp~CDK`&n=jw-thVe%U;#`$Vw^G-$5bMk=VP&YS{^goM*<4;Eph zvwUY!Ug5g`>`NA>QqlDMZ5y0o9|bXeIM44s$->;Wy26A(+Jy2I0&BNU0`XGh2b>za zgTd3YoeE=;6i|kEFdS=4=`6rUCjYyTm=Ig8*O?7}xBH*x5P^BXbFEE~!*pQdjSY*C z(dUSxBOGwpIv7l1Pd0jJ5lJ<|zkU#3eF!!oYD(G>U>k{0BT@17VV*%{xchhT&TQfa z>~?}1qVip9GEf-=53 z_l@7fRZf(jbZDY@3fnBghA>yaf~~Qzi4r966=4{4KwQhWPrEb3#Z+N8LSv9fiJF44 zE69r>glB#GkwN?|D|{D^bU*|_pHBkg&~s9LJcO~aOwr6Tm6f;y`x&7(?QU;X)OWXo(;Mb_A(Cj7N!EVaL7<62$6{T8QQWkb{ti%Z>L;UhU9<~Fcst14|*$5z(a zDpJF*-*aKW<@I4Jyn7A=<~7hk$F>*v{okcM`Cs8v00FSSjAP-aX1_XRLj&(viV)S^ zE4ScmKX7M2DfkEC;Oa{g6W@nl<3QZKD?0NP;uT}zj~2IVdwqnYA+Ad?5{KaY9E!xo zb1PQf&5ia6`6;K(C$segyu;r&k!HwuxO~m>aqik0z$ec9wZF-+ciArS^CQc^b=4p> zIz~@KnuDqji3^7o_s5d%0Aptyv04F4hl<49ruq?c;?Z5{Aw?3B>&FjAqq5-Hqa&)H zWuH(1c%EnWAfTu{=TXgV>-kc0}H##SP`F|?mKqkwOb654ipHoiY?$^g5S)sNQS{T7FGmFKnAM_hAI5g z4YJfC2{?3G3m49>xO!4(B}uf^Kw)&UEb;*`7ncU!d*45yoix z@2MJXLf3*|IAB30@g|I793u31SVakKxPGmadr1KKAw$i9+yZ(eZvNB zx0W&?6;drpN{AJJauK9QbDqwZbfBur567YtA34ACJ9R)x@70JYlC4H#3P92a?*rl< zgG*m@e7I7beBsy#?p2~X3%i@Ul7`G8&e89V9zlfx41vGTE&<^uRz?I?@NYySO z6(kA24oM~WxedTgY47~!#833i$SM)B7@!dCT;8~{AQhgb}V zgmm^b!d~sovt~=r@ysu?sOMnTaDp^kQF>&04tr75%3}ok^n;8O;-(91APDWVJbd=e zxeY7H$frfN1~SX7cqD{F{Ip+Y`wZJx*#096_q}Z43qPee2~tWDeKyLuLG68U+M&}{ zd;Wpls9vz`)hX+rMR2L&{=>oMRrHwUcodtb7~_PCAiJh1c@gv1d5A(40sbqr+O1Hr3A4659>Gpr%< zKZekV)KTIsTy~xGbX2PlJrI4%et`Hw+~7N3fl`0c=HA@1G{)9O*CwJPNL6tVoM#@S zCxRN2*0^T7uJ_O(0GfNaw+CTr4>RuA633X!HpE<^% z>hk`BaT`wVT9ndnuF?~~>P667I)R`?fd0paN_NSR?1>ABtBDf_5&-&O8>H7lOv@6i z9CK%+0&!#q7}y6t-~i$_eAu!6gHs%1{cjMvAE#QbxPFX9?E+1>b`Flhq6>?Iu(%Xq zC@VO!J)1C(Mdze|Gkii+5b-1UoBQHapJ8|kxM`~YKp@KM#;5>cnsN>$b}U7ZO8F4! z#I@AwEcV?&mwucnP`Brp@?l$Ccb{|Y9{X>@?L+_=r$bsk0C+0@3t=8dIvs;0vO%cy z?9#6xpvO@<0b|<;yMiD(&U1onO{L_rsjIQGtw_h#B_%$)IoJ1wl_3y^*jnm}vXn}o zf+gD!FwOagK&(`^;t*lZypFIcJ9h!(mWPAuSFo8Vfy$+2yM`~F=aEN3p}^Q9Vw6jB zslW;q=4Vb7P;UXP18s^6QF(A|IB_I5C06MfIR8`eTY(fFlakRoJYOL-aYf35S9DtDJXp(zko<+~P;Cf>bR# zs?6HqP@g5RfZGs6LEK;XJxjM*0*Yevnjc07jVP%I4roJrNEf>Avux9Bf0gYMY=qg4 zAK9t#yN;8t+@6M;POyKBO#q@0RvW%LVlx9sD@Z8Eo2Plad5(2n@9KhiR~Bvj{vH*O zcdT`F0jc96$N!Dl?f(ad$lH8pd>x`96zQr&zBs_1N8uEIKmfu#Kldo-6SkG0_~}dc zEk3r$zVmEMDmDeRHCN4m8ajVl#7^S2qzuJg;DU9){(S8evul*CaJm)HyN-7EZK1m}^G8rIrJ0g`3F94=MUe&fw*dE@zr@oy~0-!~}0s+}t zi1`&Ts+o2*&T%Xq(_#6Dr4qQ>@hobWtis_miF}&ghH)Iyc7#=W}Vg)S$z^US%Q2uv+JoJK(sh z?j<;2P46JhAXU(UOK|{AS8x;QM3HR{6fs9@amFs2hv&Q!;`o{a5t0tU6I<&5Gr~6z z!hHsNgX7Ev0XR0q15JS(ic?tcvzW@3Nmq8n1nFR(l4u0ZM9{;tq*8|!t>~g1`Q0JU zjM35ob{CI%P-S4*U^JQ#mUT~g867`A6V2Yvd+~n-eZfSTgaF{ec{~t^u^sh;-~{bJ zfK-&!)dGi-i;0E8G>@cFB7k0aY3CK6okIU%ofa_NTxUVfdzkR{S= zM~_xm6d=G)&cfkYxW|$Pc=-hUSY(M~A_Dv^B!;v33$I~WEa9>KUG}3#O83pP_>o!b z6c?2tNej?b!tu1wgmhdx@ggPz7VtO=c@Xzi-vPlpj0@=_i9stQKe|a*6h|{rDP?_Z$#IaA*akml|I>U*)c_cRHvdDMFF`>zu*$jnBP3;_wNULHt+6;;`mc#4 zX66n_Cx3s+5{J}%+^0c_t3hzezku>An5Q=0nn?5*>FUeO?=<5Rrhkq{Wlq#_SmEC; zUXNx&k2A(r@iyP7BZE1THhv+kHv_X=P5`>97g4#0Y zdxvc4t&3KZC_MxhmcBm2Z{y6bKoZ0e1XZdRgfT?-r2@OEa}dr1M8gpv5m8Ab4?&%m z*YYz2U(luVta}iNx1X^lZNw%PY5SJE z$ZZ%;NCf9c7md+IZIqdhg~ zpM9|6v{;%OFQfmLsT@UXC^&@U(km=P5YBUgcpI>_Pt=d65j zdj$xoGBC*4*dDaP68A?W&YoZK?F#OO{6rE5G3xc03{oL1-jx3q=Xw+*UEtkl@Lc-p zsq&EmVPuHoanT+8v-PviPuou17D0k2J_TGvNj+IU(X&%IBI8CfR?aZyrmm&=1t^KG z=Amg2D5+NGlA6@DM|gY;Vm&%e5kd@#N_wiIK9wogEz#c9;&X#yUY+M-flrNT4%;eH zN4-jmE(KhPh)FU6RBKRn6%|5Ze8eb$_{^nLHWv;IGE0@h6Jyz`9o(N`*vs4tLQWGC zQ|U2v23F5Rsc8C*x5zcaR)HzBFifhHxLUBaRcAm%vq#~C5&*5hsKrYEsb%(#Y-3{9 z8u!GFRw%PSj(7@p5=hS{9JDZ;u%$UT#5HlZWfmCcse+I=gN%3zJ7uldNSQ@6!}^TcHpWThmmi+8P9ju2vy!j9ME+1Tgq z&z#st*?y5t1o{h>=6R^Tip)fge*LrT<8J{0Mb1DwhHNmoX>Djna}y#=wPwX=xZleS zN+qNA*s-m>^8z^;KjAiS^By%9f0-JA% z=N89OWgBrk&Ezr1WJZ=Gph!U7LrEkV5Zu+QpW?hrEGq7W7eKlry=E*Smsd3nVD9RYA&qu+dfW)yWSz8SPN{TSTUFoyTYj=P7ksb`Mg3!&QQ+XM+an6rG*wpz*?YRTVS)5s1RDnX+U&LKgyBcZA>~c{JLRcqD-|mpOs&ir!=XJ=C%Y z>Y9Zwvme5uqAePjsG#1z;u4fcEZYV#9Hum4RFwu^jWnu2Smy=L!7qCaPACE3rSY;o za>VuS2k!A-nMEb!b|teruTef;LPk9-(qS!95?jm`StKKb!7iK4Z#oG9Omgef4$pJ^ zBFMUr1A!OvQXkcFhR{JR$>KJ+jQY)wfGFpd zO`)n1V_j?lB5fVxVF@dAJ1GL7tpgdV3sa0Vb5P`X&eb^Z)sfEe61E76XXd6iz)vC+ z@4_iXtjZg>yWV9%L(ClbD_OH6;~y;BeL5pxU>|VjfvhnIlv1ZpZIZmW3*AB_riB_o z2%Mn}ps+`s5amKSWdle6St9A^whG|{(P-@H@-B3qR4Iw*T8siEiAYpGAa6jNO?qmUMNGMCS3pDy zmTEJ)mXiPwhecRIaGx**L0hV8r+D_l4EtHMGA<6WN&F$aZG^Qv4*E4Q#wYGIL7Y5Tp`{&x0*_o2Sz>ODwGDS=Tni6OmyFhQ*kTzF;2Bf zcL1?EyvsbT^B3dZ<$3Ybx1L|I5FMSp!z=!Noh#K%I7!4_3j=GmoF@^JYl~<(5s$+P z>E&K+75z}eUrpmd)<=-rY3OcO3l!y+F3p;bZnx|&psNZcKC#NV=741nA}M5cxBQaeul?6oK+ z*`DP`L+GYjASCpFfsRSooH*Y!wI`$W@1yH*f`pbbQ@r~E`#;SsS^V=L)km(9tg>Py ztlulIU$Xog7g%I0!T~r}`x2`Y0wld0v0bUBvGYqdz!qg;S4doG%gEIQ79^JcmXM7= zgBW&+gj5sNHRwXjM$3CPMRSX-dy7`OiBjy4?+%~8%{I>_#PtcEu11wzQ^?~JOAHf) z-F`zC@=w)!5mLT;hRo~?W*X`~v_N=5zoD$|98dNJ(y`kVHEG|O2f0O5--k#SkotI! z9CLv|D!#J8c%=r&TOon~p&C20g1G|~N0Ey%6!s`EOD7TZve%Fh@3JMa0kH6dY)a&j zQAfI{KnsbG-v;fpOVuprfFSA(P-NP~3A8pfE017txgmgoEfGdz}1t*hJj^0oQoTnx}WMpAux`{XYA+wm^hcVZD@E z3n8kn4a~vdLHNc92QRZh05*mQ1(~%F)tbChD@=z7z^525bF;(vuP#D_nSXgFAfVP} zj^e5a#F%g&?$ft1hWg-2#s8cDFn2u#;yis$&921~;35S~GKtVglof$ch%8b=oJJQR`R}`!!8sJ&td#n!GKJxc z64#yxQP+6JsYn=wSdsw7X^Y|pOwsb((jcKh05Xgv0daDE%tu98At+-FYyfWfB-zU#(7>iyGo>mk|l(Cf}J8Dj}n>` z7vZD{KDbvZwQt)OMv0oEx54B+rk&@X0l5ZrF9@iB;Ga5q7Zp|Pk)BP!QiPcNkRk>Z zxzq*b$&E`pKwOYBLCN-bibzNgqz{g*f}q!!7mTaLBK;qjw+vicW2Kc78*_^4qO$8p z6NDU>26^W<+5gA9U*6%w8mb@jBYj`*`Pvx>D)WP9$GbaiDqfN$#-NN*u(`pzP?e(| zg%e5uB<|v+Cjlf9DcIgHm3y#`JC3-|;!*s!05LL5gU%)%g4=)eSv!9Ftgl;2T@Xhh z4&G+@?y~ky^9e$^Mg5``=?HWkPr9r+*FtpUP!P8bKU&yAo(qbEq|ygu6t}HbBs?|AiI0kb|?aEzq^<)(9Wvwp!GhC&nSr^}5$&pO`lY(xeKI{OlnjCY_Jmg~fIxD0%}n zkXn?WM72lq_*Ci{}=%{#mOC_!2moS zeV@?dq9m*Kvm6t_5+I4~vb3oIP-cv(lQ=*|W+Xb!G0ia`uMeIT#d)-DJ%^Key063xh=%0E=14I$;)Z2%Jx-K1`CDTWa4+sB_n{|lZmVou zR}dQUrw8>aox;_}`>WyaZOc6xJdcPx_l*?s)JSqkYVJSF29Z}i1KY9Nd`azO0-04EwoOkHQHh09v5y7~2KKCf;_?vwGkgYSmfXfRPoSRSxC!`S~ zJ_<-nq6>8WZBszA)JpS+?2r{-epP1!0Eu?bTN%}+xlU*_!9jA5Is!Y*^F^8KAPxYJ zM-;-5RL~P%m>B9@ z){35r7m|I#&<@ZPq)GYf-OM1J%Lx{mXR)V5ur5H*Sip*M6zKL2`SroGxd0)?^%VSw zJrG0gQ)GS)$-7rAzvM9e6TQ0}W9*6soVZ~j3T8C>)aErwaLJJ@tPrl(6ZcJC;i#$#uE#0PislA!2DG9UPY-exxo- z1=3htD>q$9y8Yc5Yhh^ZAHzNn`!qWx$!UZ5(rVpF0fW4YA-&f%4nepHeLlQ3OQ=T7 z28e774|7eYl5t88!U-h+TGXTHuUf!;sIJs@k#0@{3mf7kyV6mdf+SD};X~-BQ!iad z)nmbK4r53@jSdguDWRo`@lP3kPW)Wv6MYg9Jy|@ofi)6&WuGv;Ov6ruq zokvxfpF9f_Kj*|zKkDs1k~zrp<}K*T>(>51{*tw?aQy58o1D63&x}D78n1JqZ|RBv z3l%k{fnPs1Sfif>Rd}UF$jTi6&(v#?_MAR#pXVo z2Vj<+JZC@^bdH06%>S=I7?XCG@3T!jB~5ho>^wnBtv5La0?D9y<_!y-UbFfT?lXvN ztPD5RxOO3`q|#DX+g(kBH%*A`aQ48GbL&>X5Lh7VE+h|$Y#<*3<#5_A%)VtKsSU== z*d5G6G$5#oqJ-ESRv|PBgGQ=Xx>wPZs z-1o=L=1~t$^E@8=6#R91!**Z0gj6$TGeguHTx1@9P5_v*o&sJZrM%;f=cu=_YMcGI z(cU}7q5*Na68GP5LNb=f`J5!>-d#7;p!kV+{%f#bS$#I7ZE1raZSVN z<$HdA!S9HUQz9JryPc2!>NOA!_hqwTMf~X1SqLF+JC_Tf;wMNh4)D1XieW|KB^r{j&|$K9+Kq3D z05Int0vg%S+ODOkhu|8#2In>2go(4XZ_bftaoUF7*t3a)bs{r)l0Y4kI&kKmZGWU? z+caQlsFV>c4IQDY_5KMiK zd}>kglA-F3AxQssk$`ZLoE`x#B)|x$wpH>syC4-Ay5yP@2h%23osd}_f)3yg;vhBl zVbltvUr?sPigX)ls2GLBa`ey!zp1WFIO)O}8-<7pFq#1=`)+a`95Q~I`#^*mEJn5V zRz@1-$(_+rVZuY$6~&0)hy{Zy=BPg5sY%Z>#w+_Mh@kQxFawj1VGtv4Y1# z{Qwm_qkeiax;l=_2V+rjjiRddu!Wds(rmEsGtNID(I#H2+S2d>4MM3%-1WulxtSwZ zN{p`r3N`w}6n){^7<{|eQ;-)noWv1Xn-%VZjlyh+?F!-t#6i)Zao#s`=G?-{B~b1H z$xLaCxpC$*i&r5OMS=@F3w-M}IH3{%FPHdtuf>7{Z>u@}i}Rud!YxM}_>k3JK`Io1 zJU|R=$$cmBUj#$ZVagj2N0VD{Pg9AgNZh;aPqHv7Ebo}@M~Tyym0GMebBsibID7-d zk#SLrP=rU={wjo(#G&9F7t+dy+--3p@d!CStaATd z+GRB%7%oRa#{qd7(7hFtpMt=&)Qen24pgE{!+t`~7TRkQ2%WU~5PbVGfBr4D2_6ds zKz|QIWZGQ2-ToE!zvQ1z9l|(Kh*9QouP|sOiPAzWd0KE;cHMxeI8sjz@rpF44H(4- zKR{o>$Sx9qRFw~bWTk9Z#xB{uH-sS=9M`S z5hfLKR_T@GH0vTKeA=jzW7XbT31@Xzlwu1G{KfBG{7;hB`Nv=30 zGdbN`2}2++ljxr(i@e6H=Rl^`) zcLADWa5+JkYeppr$R6;*Z6ttI_JSO#8NvaO3gj0DX(Ffq84x3;#mjhXl0mylM7jzK z6wx-d8Mcwg7WTj*nQll0*e|*O0SMz#6V9uaeVE1T;s~0%ZVe-Wwv)b1{xAr6TQa%P5%cVXc9F_qJ34(Q1GTWX-Cgkgir?Qz_>X* zaw|NkD&za8Otjv{!%!p5xGR}CZ$#9;Zs-44QF#$~7kDa_{`O&%kBdAPa|KZy8mA-D zxtwj5#{JANF@fyq70^?kKmb7ypJRO4iIv@+i{w5m>5FI^y>Yf#&8QV=vvXJK353=E-cSx-` za}0{=R^y6-I*v2TI)b3c2%$cAid55v$Q;4mpz>29L>^5k;_@Omot4vtp!D`@(r~0u z>$78$f|@fR4t4mtz*w7=ltuO_WIh?O)xe#2EKf z8Fv?{$SG32hCuFA!}1m8Ovju%_crf|=N&<$Qp)r$bL4QGc_M=pOR(?49d6j&txNQi z(D(x4!`B{z6G{O3c}?SV^!n!zfDrXDSv1qUXdy2hZv3X4e6ToFT+&1L778R)5JXOL zW%SATrwB5M?#}A1h448x7D(TQB{S=m#4vamn#F~8iv4gERRmHxunKm_!$2_!56&K* z18Io#^OJ@+t{C_?o+TClPWCEgz(JfO?z03%LHd93EyUguYc4>HWftg7@>&MyIF_Ij zFHvuC2wX+_Mh6atNLHM%QR3DIH>kF=!@diM)trL5sWqOtk>0gkvfWDivY}Li6?|KSlVpXI<5qW7LljszBeRCEWKcP8HR3a0iuww%gY( zVwgIz!9hY_ug`l>W+Ugxr0 z-$hn}gs3>`TolJn;q-b@qYr^vHf-Zp@NOJJu6Pt5AddDwqn7+DK9{Y2KWlrbQA_qy zI7C%`WoHmG5#l%fRLse7J+;}Js5Fll!m3RY!(L`;inCJ3B#B7|JrSp+3FeKtaMm?G)MON+s3WY`Pvcl=TOY9l-YQ6{ z@aqqO%x>B4e3JLlNAOb+Z7H=z@%;7z8~5p}p>@&u2QNWe!B^7n1r&O*Q}l<#PovWVDqa1m)ra*AvkZAWe*81t&7`h{TxvJvsv|Kmb&DvZr-0hwy;W^ z%afuxTt#vqc!$Y!r>Tmv6ZJK>uRmvFdvzNolhV6B3pegu%Rlq6|8pA!yY&U!i#$iP z*5T~(!nl;*=lSH>h{Q}E=Uq_{SHMNVT!OPJs4vthB=d3;!rojKq4@&af5`^%IJe9c z^PXf2xythv&oX9+G;taI1c7C!r=T=vJBCp7FahWaPCUg40u~xCyB)ertp1OvlLwO4 zMHJvTGLU)mKJ#Nysey-0kO@fSSRI30S&Lk8wy#e#+{p|CT}( zAV@)rhs7z3XrK1ygizMj0mwyUT0b227^Ycfvp3IE8Tbyr0SOfDD(+4v8&~N^x^5hs z0p+>Y8`E}hmxVN}`+Wo?+j3mdsQ@x4DzFC|fUEMY1*uSE(vU>JM#ScW0E1^AjZi~a-9YQK(+v+6U9Tt;}B!@I8fQI=sfr2oke+SH9pZR zQIE#`3kZ6Q6(UwpBWR!la1=!gr_i0xzb#nlVocku&6Oe`tb;nf}{sKW0<6oQOz(id_i$Dyzb`0jPk4<$F zNQ??&@xOmDWk=(j+`Pn zK5uVi7gfiz>KJwOG$yG4GGb114uwa2c$5GLQM5cd2qPn%HV2w32tfayMb9r2s8p~u z>KPtW2-VpI0u_J^pPgxXD)iT>q4$8_b1edbEX{T09u3NKDV`v~amsre$Q25g?B0v? z1(@aU=I1zXicRA{a_K_NV)O;9V0cunK}d5N09Alo4+jX}5E*}Mh+H1OT~js&CvI`# zCRAx@ngFD@jgC|0$5mkBxJnzzYeUixb<~j{P$foruh0TR^jA(s-`qMiR1&s-Ymsv6 zyt7B!umxl2kS$zoxYNMlO zNZ5%T9omt&i1bp4`tlw)oY2CQtf&p{j5-ia<9E(CNzrAY?F_M>MK41iC9)jh&~FKU zZ6HasFzeKxYEqWJJM92t!ae+rjzDDqn1Bc|nucX?52(oVo+;a-a zBzEr&;p0RD8$ww5g*j-_ga4gwA;0<`9WL0G>T-ab22&ztYW9Y4To<( z9)b{1_Nt`PTJY*fVSZRfB6t8HL7xwWpvo(>pvT7mpX9 zuMp9o@CxU97+Sq&Z}HP*tL_b2YYn5OB%|1mxDbTHQ6SCFLnuK2&epEmsJG<{E5RZ4 zbW~YyLaECTzb4s_b=-d~H7{+##RVb&xN}7S)~OrN*gz#|E_2{MQ_Hi>aa=`Wq?X8) zTm325WBk}sR2w2s{z8I)rjUr*qtb`2-9vCf2>>sxaKr2;XDJp_wxM-IV%HUR6^;Wr z-xLP07^$}FeB5x8177&9D#WNCfb>Nf@mwGfoXw%}KjQP7Y=yWCHUi-oCM?h9#=$-^ImK>a5y>c7d~FHkIa83I6f>=D(A zcgRg}VYHv-m;hwUoMT<@gQ^(MHW2Z{EL2rC3V@h|Mz~^kn&(JV9Jci0G7gV*7B=TS z3o;IZxB{gKJLK0NA~FyO_o=@H9Cs4IclhLYbTlsRkms-ddlWBCW|=SV-*D`0HaKYs z^#q(%oI?e!=?I{@CC4Eot~)U-+JVFXPE?9DbK}v{yD|l7Mw;=0vSFPwAN@$R3U>9u z=WQ!L2a$S&IP5deBq-@xDfD}lD@&y*S0)=poA)6Q*CymT%&Y9fK7uiZV=uAu1&AZ> zLE+iz8>o_)JOV4caHeNuAF(-OdAP(OHRP1L@15g(IF(CmamGV9r~NM5ciC8A8*f_p zhaa`cm72}H3zBt7#bTlrwMDHv&2-< z28iA>#jv+Q#3G+{h=ITn02a8A!&$cb_=GrhK?FbpeH~iBHAq@2-=44poO%qqNz?ZQHS~jPh{zs6{A%Lt0iGD1QAZ zzz5|F62`kZ?V|2ILvHC0(brtKcVxs}52j2VV2`W6cnf6@^uS_H9 zLd^1<_a=mb9QUUI7KhZKEPs9TLC?|oD*|#RL1~I<^i3RFly1m1S5;@8b0A8Z(>?ey zgh;N*G~$1RYDF#@lGI!)y~R08Yywm*)FkCb*zbSd`l$i1a0CH(ljfw@13nZWCh_T2 z)Gg*Pua@|K?s{5zZ#WF8o(ozBu^nxpTmf^2|iu*T(a5Psu?c?6Q_|X@Fd{UtIr?WE_ha$0cmob zbBQEpB+`h(@e&jDYad4H<28acOT$QLAUZf2c z+>#o5oaBxl95eOiUEXZx-oUrMn~vKyf$bD`;3Q7NGGv){}Z1- z4$ka;Qlp3OAeQ^=?tb{C@huqr8n`<`hp-Kh3^n7&ALAK>5W4qESo8;oo2c6{1yGX5 zAxq9ftZ*IL^ljpzAU>ff|9ZqHgb2NjoMnE#_?qp#f7&*vGnjamHfKWa4}mtS)c^rm zxd>93WO4@(pHap>i&TS-U!$YbUSo`RP5pl&7BQ6grzs%|+U-FQq>lNPJk9G_Htwma z%90oa!~47hh$M9kl#ii^#wyjMixHZ;D1Jca_9&cC0swa)e770-8nx$JmXH{%iolKl zun2+*ISL#ZCAtp7Ph7rWD%&XXtgZO;wNb=V7P}V2laM@s6Blid^<9=PJ0Ji>xbMyH zTI@9KtRMhMUE>>Y{BMIS-(%sfUgt`0gWzyVL{nFQggs%JjEDeaDNCIZauX-dNu9fL zjRd}S!g3=O;^*l{5SHkne1iZeXO$ScWz)lVthUr|N9&B)S-f9mQU7hudLuZ~eF~M@ z;^4ApIFaA&e*C}Y{|kqN02HRESiWNE18fj~{0jv1NSc61*)M|VSo9kh0k533G;^F@ zTtR9=)tbG5#ebYRN5mGOXGa#-EQV_3eeNwgqzdvjQH8OyoSOmpJ_Op-_#4C!my3~W z5Cr%rM0tv_pIJtYT1LwtCA&6GKp^jG6v>TjNe6nzA^@lkQ9Lm&qT4+$gjlXzsW2V{ zSdxV#z9^iZjH3wIn~DOhU{E}U05oMRRO6N&g%e5uv1*F3b)U2Hjj zLfJC^W`B1758l?F-4Xu(GBQa3Eh_a~C(DnQN$P3559If@SYP+Jb?qynCwASukzqnE%~Jd z^w5J1vWfFB4i)s2#32_Hpsqwx15`M(pyD0))*m9~I*gk3VYmTv3GxNTi!?H2TqRvE z01|PUyOr?m9`dJ|440RX`W-wlP|b#|;+ z$#V!PqToP_Rv-fq7X0MsCY`Kegav~*G6G3LP+?2e)mN{3mm2##3;N@HXTe5B={1Pn zU1<^-%c55o9LG(=7^Bcf;UaCt{!g;VUbum-yv1VEn5NiRm~!KR@=nFzn-?YQm1W#G zPFP`m1jZ@}MpA=-#YwVJd*TS`faIg~oJB|Pb2}EQi%Z}mObhMNZ3}a1J4uG4vhQ3G z6LEPsSdhQZHvuG$O=7%KUORCBI`9~P?3;}Bd(T_(_5wwTjx8JFIn=qx0bE{*HHgq5 z+-V8YYCUn!D$gcMyrcouM-<54uQ}J;ssJaEj!}xL)jj8iHB2HQldf{BK5;<4BH4fD{W&{^p{I{Ke!*vq_d2mm?%Y2Lj@A3ui_ z62G-L1hUeT50DvuV98D1{q1?y2m9~_&{O~%WRa0;&PwAT$lv|~-!HJRsR~TX>M*qi zB2{sJIFjS0NB}j8W2hG&wfH>+qkQZt+y-Q#8Ky7lvPeC+ln8{1hXP)Ng$yD#^GE;$ z@l;*t8AQkdh?at|;P=lL`-#fS5t_RJ0nl$p{ZnvuCq{RmLx|yNssk&0)h${O(jaOd zz+rs?Aa1F7`5@rf-Zli_A_-6DEPhx*7hmJP+}i=qtn5bR8-RdRGB8h}xOFB{O<3h) z4|HdRMa#>gz$#@7f?&zUF#%1;Jx%7@k)+OLANdHQVyGym2!Jv*eK_k+bqoT=eUsBv zbi$U?Kv8H9G0)Ks0dQ&s<;1B!7^hf>Cvo3}CqsY~TTws9xm;6q8kEFTpX0i`OU*sR zO3hBoWNNm&*Eqj-2|zGwoyj{P9}2!9WVKcPO=8Th+tzbbl?kUT-~JSf8pOfq=Rk-; z(~dmlwA3Kf#gnF3SVCO+t*2SggDi3t;UxFp+V|OyG|{i(m~b69U^4*%6iDaTdYy>UmBj-88JQK(KEJR()2k~Wz z*I)#jKG`I$2O_(q6Ic5Cb07ur_B2MxGMMI35`wtMF#!Z(17t#9&XGBpBQIimmA{Xt zAiN-(3b06>5zsdw5=S2=Mxy?)_lb8%+uHM^Fkz$z2u4#uM;`!n+>pt=GI<7w@*><0 zn7bXmn5zi+dxZ;9r4U zarDoA7UO`pHNO{ZOQ(@s zPk`_Jmr5jZ0O&Cv1Bj)41}=%N9>o$*0}by0Ze1SloW*@mS8QX5nj>^0ij$6f8{K-B z;~{P;S882z4p2KVf|QeB-oqb#pJ(2%`yUxc8|X(w-{pkhS>4&){~Ga#Wwt5WN<`qp z&Ompei6A6RNi=rZTO!0?xHRAm?pptv)Fl~-)DldRz{RapF zZ(4PE*827iExpPL+}AlQAe#p#XR$L~V$&^@g$WOI2#<7P#TQ6AIJbg+ zzl|;of~%IFD(kA7uOtx%NDsS5mV9s6Ci81n8w%Ub?q&KHNHw|YpMof?LYq;d78@+) zwwhjuNT@?_Kc_r}-1-^FjOrqfgChpgeYH8@8RTwrUW*z5PZ-DetFFST*%`jgvAx6G z-GIxI+)FdiBLHnclD}$_H!iI6?95pb#8+$ez~&OCcLMI%8WD?4>Mu(4o>*V9&@#zV zHO_bT2tJ;G5JtxR*+F8F685k!fKK8)+-5%S%uxkt!m{)R?58z)sKmSL8tnc8=lvHv z`zUSP*!|8Kkp3&|`z)If{*(T9HBk}$(X%r?=bj~cGpl#Q#IeSHzJ;Y3{WPx@VTj|PhZA$9_X#jW*)Rbv94jg4hrkl2|EtQcBmjkpI_?e^Kl;bu z!z=vght}TsF&%EEt$&w#f)7|Y`w#)u3{Zm4_yUano)hiETr;X3b&4hwH&kwcfHL1M zZFrvFJb#PQg*#Ry3$US3*{j@J1VHz8umUF|uHc;s@@s_94cpy%4x%6gyXBvP$04HT zBE$xWQ?@`#*XSa_xVg0gT%HCI0C7Kk0(yilFa}o!4MOD+W?N<~JZ6oEKuezE*jcu> z_F;XE(8tf=y~j;(}*77bOp)g{ss9$=Y(cG5!6A>(*W&3KEs(!w7(f z9VI`}M`52RukFzRz77FE<+?R%jR(AkyLo~tapPh`dFZnHI@kTL`E%SaV|Znr_waG{ zeV$DO`(?I|y7BwPSs+9u2!OIVu~}s3>EMv3B|_U`>rFVB1VF~81RRy*n3}?L<5=re zm{P?&vLF|UBMxNvp&P6dWtICc@n2kDEds+8k>m26u|Qje!B z9s{YTCB_O^DDsUf$Oj@5!j=%%H5FW<>#AnHU>`33%9o%ee;#S$1@mx`Me#w~0tob} zD=bv*vvt}(SNEIe8K^A0aX4a|hm^rqt1%}{fM^27+oHAgy^V{uwJ~q$ktOt4?kW4k zMV?s}@TWioM029gC*dkPHsJ0faL0r_ppHVFNWUz8am&2Qv0H2qJBoxDc7Zhh_hDrY z-p(RcGhV4at`!%ANPw!<)GoOJaj!-(0@AOUDn}r3U?=d;eC&U5{g&()P^X8HfI0<2 znd8~c61F|LX4?xRb}))GBP)K;7$hztlq5pYvm(Q+Pojc#hT{@~?9*I>cOv8PB6Adf zk+BT&F1IW~IQSkl7LVwu(2kzvep&;)2_HoId-F;DG^2JcqUc+f_(dBkf5UdihODw7 z#2C5*8yt|{ez7q+ z1SisVKsl<8e!($_h&t4gU6??H$WrIOsE}g0zto0$f0%QCr8mufi_g+7G~<`7F@>f5 ztFKyi;g;ntT(|YVw@X0E4R@-$XWtD%RFAAwO54W%q{Y>Xumyq!)@jDvjFa}deSs|5 zq7BAwVh@7=s9xbSEbiqz&(gUZAB>O6i`O+qd?4uGd9goyfB(T$(^18zOyol7OsKA5DjSV&N|JDIR!m8SiVs zUay|ELP2@}@5lQbW8+=E%zMq<;a#x*0?)a@mSya8el~Iz>aq{baE}3q&ChKBZc2OS zKi-=}?nGjOrV{%E_2E9-gPW-$(7iF%4+~jP;H6XbCC-cob$X+8$j`7)4kKLzJo~BDmgjAMzaMG6Y%>#E1k-Wc^&!;REM(;vY>yyWV+_OXR$Ap+6eEGM z=mZuv?njmE=GF{ZQRCIL$qj+FKo$L{) z4NDTke(_;YqXFm;1SL&M_v6D{W*m%rW0XFLlu@Hjg8BzK!W7ss&MaZ`B_D(E>Wx+E z`fc$ZT(zK!ocD2Jx>gV*wm|S>2s(4wMx`0PkNQ%x1~nn$s8UIYs^?OKna3W5r{8>! zKFFVH*y=&Y+OAXiwJ;c`He{8^E~vXL!`0yMiWn29V8s~{N?4plWq`>zClK`~8q8Jp zA;`u@wD0iwJ8a(r`QEY0gHtq@E!fyPh|sOM$3jw0Ivk|9G-U@6kjNwm{0xij{4$HH zb6=;JbA1PQEuOn+BjXU22={`ywUl=2&d~`xNIZu+QXtw7u8OP9^$>KS#qI*W-pcqB zcr-F_{`vz5gvPJH9BccNR}!7?7XJ0r3Q)scb} z9M!(%Uq!td6!0a#zoH5$Z|1(^)>O-}!5aMUFIs980+8CbHS7|#^;vM}n;g5(1|%s9K2B3m zN~Rwk4p5_m=TD+?6hJ81A7soFAXQ2KuPe+JWQ%8Cvn>925AT5^vi?~g_`hT5j~M`U z+9`w(2>DlkXfeum?LbsqvlW7_Se~1XtEwhzd~5~p%wa*u196psHnh^gL_;9 zq+@Y;aG{pMjkU;RWIUFtpq2Loz-PKX1AG;*}fZWKdQOFG5K;g|y=q;PlvqQ8o9#2d_oQk>Nsq`d2J%r+n}fjF)N zAZ)7*=-Bv-_|`xnK69xGq_IYZ1~)=WvP_xsk%_$ z@{}hC&q_T@(O2v%zPEvWl6)j#$;~U!m?j_q(O=_vA^rqn+1VxACJcDDIZO>kO`u+4 zZ^FqW05U*TSbvB2t&vOGGuc~~P@cL9V=S;hCE^RByB1!g)9=DMZZ|65Dl1F@KnlkB zLkvYX=Da;M5IlSQClg+ ziYrj5?;H|^l6zuYZyYXr7VZbttJ$X2R9A;AHiW?=$T5DGUkYpPcg#AO_g>Be>7%&XO2G#~F z!m*n1|Zm%rh!_uTUf=NDIO>(+=>Fk7~a&NG&X=+FpaQ&DUIoHDoH1R(*rTZC+VOai}qfQ*(x z{kgqgp69+>03rD#&((*EPP^@fa6x$&j?B^T0KbVq0oxSOK>J=n``+7)8L%IzMBNV1 zPU+Vp#F0~z4$V9H47Pgk(Pvz#B7Yym`K!a*L7abcq=l?u%$Ml!0sIN?>$myqA5cJv z6l#|`ZL~ClEW;evtoKYGNDiS4Xc$pS591d8Z`4+C>&82p(yk)0>vS_TGde zPL+Zi6%ha}fcbuOVCUH`u-MzQsE`T*rm~H{MGRQ9$}=zb++C{N*cybw19P~(Z3z58 z5+L_uAc7j|TX^05Op~92$ygSa~(qbnYPzq~8i^O$B!Lbf; zs^_0)SY%`1H$;Lw1tzy$R}kVY$GDZiBGt+Fk`x>;M{Z4ErRcEO$QB_0^On3xKiiTz zcpd>twkn6b9aBvu?z_X6eHHhk?tb5Vo7nYpS1+UAvk`Ocljj0J-{&u!Xwcr zX{#qE(Rg_WdL}6w^}aZEC>j!{ux!k?fcKjxF8h!}og9A1NBQr2c)Ul6qSfjoE*xai z-6>fC=M$&oGW*CeN5H&%g2|xG*#WqsX zNhHO^xlcR&XM`P^l%IAvhjqslfKstz)^eYg@DhNk_pp})OS(cdlfJ}gG+qgmhBm#i9UfX=m$ly)9 z0Po57Je;)epC2GFF>Mt|WWgD}9IYKm#-RbQU2~QoA>K2{Dm%YG+~?SGZA$EenqTnKIsYY(^m#EIL&dR&hy0<~R#Wg(-Z$u6-y) zG~Em}!Gbz{okf_kG^V>I%29cUGWSgU;1NqsuGRL;w1%ocP)Ka5XuDAL)kXH2m<^`qNp4GOv8$(9{16rUCVC9G@7&{j*7HjY`Dx%S=H>!NjH1d}&s3Bk z`9_lGscs4xL`*m^m+fCYLDrn2_2fvZvW(QCIZ|n`TeQp7368uVrG*zUq{d-Op5P~R z6r~~l_MoAAkEvy6*Iw^=%$AaakS^Zoy#NzrGh<5Wm1@i54qKdF8!&tRIE7N1tZ$Nf zgr9hw=efxb-{)2FuKws0ZFo=DIc6E3!k?%44p-&OQ}5Zp7A?tY_|D5v#;h#Ds^UFsI*AUDCW2qgi?9xXtNoh4j%=)??LUAQQ=|FR=5$SyZv z{u^~#t;OyKLJ8f;La(HiQjS2H5KcKkXD!dneYGk@lJAB>U;O^eok1LZX2PN*54?1FKS!VjV;bAG>Pp@hM86 zAIDgtaYAhG1C<16!Q%zyhi;|Vy%&)0KS??-=|IC4NloI|fT98+x>a;HCA|Yun_2Q4 zKQNzUB$k4c1Oc^ez%G5qegUF+3ZkF1kv)5{jEm#-ZCQp^eP_ZNfjr2KUpB$YovQJh~UE4_1ZtukCzL}Q-s z7R5ndwaUzx6^OrYL+)&qn`#{KQ4T^19X>OSVsG9>%L04MU1b;|EZ7i5Z2)9f5|8zl z;gH;itw(8k(2BHEQi}wZg%VCl1?^u8^)bqBy@d&Iz5`FeEz2Oigc}`fD>QPX6#-j_ z3Q8>!b5W&-F|%K<*}0EFkXQmFFs+HkcPqWO-gK|DrNq5 z*JucUa06$k9QgvJ40Pq6je*pRPgR$qAR2kxv9)m`fbulX*6P-7KV=%3QxV7eK%Rh} zW0fgL<`%Eu&au}o!h#YfCnNb%TeD%KEXA#X;8S&tTpJ<{ItVX1Rzgdvg;K4?6%I* zF6FDQbM=oA`9T^Mp@TRBL;&6e7v}h&3^phO5a}rlDmyZn zWb!c(PElkALd&*=e!=Ssu;)#(@H5hk3yBm^_(q&|ux9DP$#{5prLOb@BrN)s;I;yr-Afbus z9CW~ix0s6_#@$w*=MLso`T?j z5$Yh|rEl~3zhgUqqtfMqjf~FN_Iu~q-hGX0MBp0}4!`lne1e~4)O7+-u2S6<(^?xP zrZSz&^mMUi29$|N0Mg4_&p%2@C9{wSfDlu90P51$97~4zOe;^sIl;mv*(6*yL_{qf zDC`DUEfWWk1xXd( z#zO$gg-(liR_X*m$jum|eX*RF*iPh0JJv}=Ta%PpZoXnX{Evqd{|F8*2R%1Pa@%)m zBNzv<`nfNq%CKNmaA-=QS4p=*0OI^6<)WmIzMB^7rD#VpA-9+cOXwl+RN{VYmyoKA ztVs@1s*2NVZt?#w@3&ZI%d%y2{H+>&_kjWgpl7Orvk}VC!$Wr`slUTjzs2zT?E3j0 z41A|h<{ly@^?Z-PUcx#sOU5^NKMMa=7;t%k`O$cIsYTYr8VFu_neTs}@u7H}!vxr| zYWq_YWM_gvC^E7^p}rw%(9!>_!X`8BR|WceCv#joc;qVEk6n~6hUDD?Vn&V zaqUBEevcB#B`OB$e|3vk^pqpI1@b(9!V1jB94sN zj;hCR4$=v*i)^F9XC|FMhs|1?-#U+w+$X5VIALR(J6>c1vTebHW zv6Fwo&0!LEG+EaaUWC_ar{JmkKoj`@7amAhj>k~(ln(&HrS$GSjPZdhR>7gDy>-X7 zA>0S8PLk{R4g<9KY_4s^7&nm;Eya#VaeV_oL`n$K#N*-(;ylPMSFe` zkN!1~zZC)j(b0B7QptweK^!`GEs7GNLdwQL)>wVir{0^n;lfFao6go@f7s%=&?k@r` zJwzW();$}dl&3wJknRU*%(ok(NLd-ojBbyQTGD?D*LE|ngcnx`kxrn*tu6NYGeX#n z_=qHKzd0PM!rb?{Sj6r)2hY$s%X`tg<()K%!a&@lpMl^P+hH7D<(S`N5cR*%0Wb%R z1mZ$P5=jIX2}w^{(`;ay?p-Hr;DxiVtpO2x>~B?&BdbL!S5?`iCC1CCMIL&tr5>aC zFLHp)+Bb*KSgwsevHz#5NCrc7?to1?#@;)D$R9_kmr{XIM+y-J7VMTp=Rp=d{fi)p zGb&adKmH!P5u1?$O5_i4QTo6|mh1RG--lHJWm_C?H*sTkAEq}4qVT#~7OlT`(bi|j zEdMs+t(@g25akw#Tzrnty&OXN8eN0r@k!4EQr-oRF|NmvPEfM1LsaeHd1F2h1H?Ir(nnAULA;F-)o!)Z1#?Aq0mwR%MZJ#OIks zEEUj|h4A3Li2EWE3q+&*fAamy2x#~{kV&Y()+7)ij`Q6?WF{XQ3~EHN!3Ow4!MJBnTQif#IHjIO@9ea)vSHGBm)rAomRAXwej?N0wNp}99qyU zAS%q1(&WPHq-1x6MTU*s0kc;e2_lYuKu8+XB20B+ewPM3tUgR^vc#q2Qt@q*-k+Ikpk`*h=pEt<3?d zfkWV8Bl_Cv*~#ry$pBVsp&Q9`W2T5)V0QqSuuuUbQ8{A=n&cpIqw5ZS)xSj zg|hz$Zx3LR%a~iZet?$0h-8sqoN?)GWC9jYA;|`*r$}zX614$pQGy~wx+l-q#k=ia zi{FoJ} ze3QZZu;*FATG!y(r5m*_wJ1pnntYG!MmW$kQB)*cgot=_W4*~O%4}omYF{W&rtcOy z3Z7+VjvxP5qe%#W>eZh{iu zU!&2NIY1mj`ZnjgtO>K@(BqfztD@~I=W2QoNiZU|0!*+0p94u)!%Mc1>a;y1viKP9 zLB!%dkZVs_@6|X;K*Fj?PksQ2zXBoZf_Rd+lRh(Sx%QB}N|G=nRSCYqyZXJewpnVn z0rV}2*1yb`|BNyH-P-w^jQ?8?u~m71zr`Fxh#-1}J}jFM9sIJ>m@We%G5uNjw-MoJ z5&$i<&K3y3$87_SoC9ZDl?+q?E{Z~tXfB{#g3prpHx}Uzvi}P^#GR$M)1jamM|=?h zaZDCV_z0z{C`z$SvXKI9lw7h7A*QepC!#dmyhV#!olD%8AA+X@12I=w_^+MDD8Zq` zBY5(KAV>zH(>6&-vpp7a1tQ?Ud7UamH+oR%tuX>@;$*C0wby^=B*KL_-nve~&COlJ zSO~?!<1=H4oMiZ(j~O~q%^}iQiY%nj!)s2&wW?k zrODeIAb5}P302T;1zZAU%+>Aq|@|M4W;0o{h)#ErRWvqDs zsUz_eU;dCW{T^rLKB$IGO}N zqNnr#6E`d!$`g=0dde9uZYtkyxP=2qnx|CNF7@I1m1$R8OTS5ZC1qd^q%E$|^g3(g zIEzc%VjMF#x$W=*+#PJUxhDr%KD2k=eUgCS->!XWNJLph-fz)108zB2n1jnCS!jg! z+C%T7KOkudM5(}5VGC2T64j=Phh@Fp_s(a5XU+Z zGx~S)A$`w1Xywbte3vv>nzN?s++&yd!`;`?%x5)6TR__|d=uK(8Y!1S3P&L98Tju1 zWW-8Qk6^b)X~95pA_EUV^$AHurnF}%wrK-o1!|&iCq5qZrIyT*eb-8CJ9_7EXgUVb zN*%u6^NjBT2mPYYsbzKI{SIu|TB6muGi&%hKoo*W`a!a3P&k?dKzn!P#_z*kU#Y-N zDZHs{y-EqCQqRiLBfvCOCRKM1Q&hH>J)=y8@&}N}?g+r&RdG%s3Y<+*e1>=p!;O&` zwAh)ptr2SVIXC4eeWIE6=iwIl?OSo$moD=0K8^L?WiM6sM?%(tIUE>;w?0RS47cfYX}paM?2Q zmQ?&i7xU~s5BXYC2x^MGdJDAM zOxZzf1742RVh_JX&&xnw5?^392{xsL|Rah z2}I25k6yCs`%hT=>6dKaA_#W<1Y$m1WFN6V_s?nbB8!yOe38ZWBG;|#V`kg3(RNak zgCGGBb}7{kl-HsI+%9v&%IzSGa^Vx*SL_mDVLTBanpbZ1lb9%Ptt%iO-fy*ffV8Q; z#DS#Yd)&jzK?DH;;o@)9(V?^IC9pNg;%h(HyhPqp%978HYOh2i-5aE!AZhaK4NQau zTTAv^ZbAspJ-iUgF+SJlS=o`%53FMntw=|()n(nw^f^TzKfwQZqe%S5dzB_L`VqLFo+KHBFOP>Rw6}jmEp(gw{g*lSq%g_)Jk`xEm$=qkpaGU|} z`Ti3~0O9=F8k zP3w@!2@uE5$PeE21f8UxdV>2EtTeGffyq7=JBYT%A__ow7sDYU6&BnoNIB2_R?6rL zQqmqA$eouX0;}F21oAGI z7wsW>{@g2^AqrAx>GnY2zwh!Xx{rT-=pXJ2(uT}WK9<3ciQIgWqN?Jaf)HvF=nASV z-jLK(nH)|f+HXn$s4avDfs}Y^yT@XG9R!|z9Rl*am7f2!?aZN-Zy|3!TZ-Ln5L_OD zvM^{r&b~6mezTH;Q!5$B^dJk2)z_kSNCSy;NEM&q{3PG?kv@DK{+cClsm(Um#MvL< zc$!17fV6<}?B)PlgI&vlSP7J0HY-f$SsaLX!=YszAN#%*6k?PScLenaCWxNzZ)+_- z_*awwB=rA2`1asa2M7TY1myc_$11Ti-$rON5=Sd@7=e^{%wZD}QWc9p5tnP1KJd)* zy}fQBkYt4<;T4yVsIn;D+7QX(O2>@?_I^+ZQr3^zkWeRdbx~((4FPCI%IMfbUlOAp zK@;W4Ajq=X)Ucd-(d$Zg^vc$v zH0VeOiXJplksapVdvN69K4!B7S$i^i5Sc9X`nkUba~Ft6GEQgBcl}sHFzo-DlWZ|Z z*)KK$+Crsn)*C}8(Vqk;OI2A(sm!Vh0WLGYno!MObX$2@>OOc}D&b}ZF57~YCVopu zCqi~_P^IG*%9Bxa><%P^C?M@;*59p6j96L{xJ7R+_u@Mt!ms<( zw_@_qko&ikkZz4&>^!V;EBe^c$AoG9#sv6v_a5Vi{adYoBM7&6=I`(#6YxGtJBkG zChW>znI#p21e$M=V1ZY7spChqbnJ z27@C?0C@RI(S?F31!qccV(t?Gh_Mi~CL^&^E1x}Xft>agxTfzjCH|ehSz91-EfvAQ zCDzLC()bbwoTd1N#YxiHyp4m-9u7fDZ>{)za@~oWB;e3ZY2pDy0K}EV0THLIg#{|H zJj0@csds<$yZ=0#;Jwg#Ntl2)S9YW9e!f5yOaPYzYF@9moX#{GU}*35={|LMHB+e z;d#E$*fhsP-?7CiqG{jSAw#ePiXO(BMCw3)a3DGpQXFF*6(b=J%N%|h+i7$e%2&wy zslfy%ZGR%b{~+9BKTalOME4lUISwH6I74S1@vTt8j|5vUc>;#gJGgR`meMakd=*6) zNGTc{aq4bIrHJ z-XRdIN>B&B0A$`($g10tw6|x`I1mn;!~v)u&QXyMxE6x;*WY@&hMZdbN)qW}^n)|9LVG4|;SCMmL?cjcFUQ#`!HV(BKx+-r}| zqvx9eIr+TW<1n+;d&(kpv`Qegoz_aAl055D0sgFd=IP*VRB0%_zr5IK*{7&XkRC zKajsl*mn33-|^gn639>4ZxObTu-zO~E`yX5-{PG|c>}cdQb;iGqYP4`1>{1uV(5B! zsSKY1cvK~4DQf*X2*EB{n0Z(2xQI8iR)YK{?d#9Mr$z&2_$UGk#Qvovn-VrOhBkMx z%Wla5McH4z0atm4Me4wQ9nzn{(dSkhNPlgJHPXpGc9sPKq1a@xxZRtDArZ0qC|7Ap z&snjV@Lco;t!x8K#X$seA4Uq;6#`HfpySLmc|_V#A%G~#6HuXlZB3?r*X*CN(8=*< z8!XDxEV9#!*4{%lATk24Y`;jmSJfi`5oy9&p?G}q{q5so97=H|jW;;p0HIP^?vU+A z93q!YlU%d{*ORHR8$?*sq8CKZtR$uj&*8CvSJoowX)7J9oFWY$RhxU5)5ekq# z&+m)1HkA0bndhjf*l9ygy=wD4f6pq{r}zy$gfaua20>SvHA#aXVXnW--`YA2uK9Lx z3TLpP=Pp?6EbopqVU0=yBJx_Blq2)6sP>OhJdxjx2(cou>Uqp)9tUu#AMVLfhZqPG zz&ZEQi0jJGqhuE19O7o$NBXK}5d)K7XBdZ>UFrSI8D7 zkO)nmd0cH>p{YG=1aR6I{tgvPb`>lMF&uXgdAO8kl6NBn?rvbLZYI?EPSCR=sfc4P zH(P?EmAr!KTOb{YLFt?WM9W;qiiv{~++CG_Jt2r{1THYY+VY6w3-lXYwt_Ce6Rn~% zs8X~w(Do+<5p>|X*eLUSjK9uv?!j*hf(1bhu^3c64^ zmt)Q?O!H%%DnfLOtx}nKfvqF+7H@$lIq~M*DmC03eJ#>P%Ej?{&ODc-2BaxD4eneY zLH$EAR+^w3;-AKZBT4{3cKZ^UfX!}PonTGzK`&Y2^hpqIc&7F?$)1zl%Gv1D98xk1 zO6EW9SG9QFfP-8{1l{K>sQ!oi^FMHW1Cjmzu-LngTIbrHbzI>q1;9A4awG*%u{Qs$ zXKV+e)A7(Xi*dNUxl~8Q;AvWaiQ~7p-^Wo`aQ)3LD^f>rANN;_o#VGpc#o`f#)USz z++?Z2ybbp^(u6*1BpY6|4x9wIk%+UUBGwo zc98QF^F>CW>YU|J?(K|#z)Hm78ca@m)CIuHF+-ibc9NOKDj?O~yAaT~K`549v^A36 z1+MSefCy!7rWQj-JBYZ;bA*4Dj(RxgSK4|BSn+YT4MbHU-iNp%S0SLJZs7+JB|s_1 zB(33lknc`e)2bA&5GOATT1QrtjW#)tu%EY`m2NBVK+F{G>)VS@b4%tK{|g_Zyfw@H zJTKRhsi{gG$6$;UacpgB*@XXs0BG792*hb0VL@OgZo+4OgvCPw4@-gwXRrE8_J^U; zZt1akTkGtxz1aaoR~EdY%FbP@J*(1oJ)(u9#r21rS=h$)+h4Ze<_VkwapPTQamblO zpgpe?U}U~6LqN*A&8Q1-lIe!)k1sI>`cp#7;om^twTe!WYsXl8ivELZ=TVTA3)CGP z8H5Nt;a$4My*fk&BCHiXX=}zANZE#Ob7@9zu=ArwEu5*?vFbI;%7s})N%?Fm5WDT= zdS8c6?0r!*l7oojPT=|>kW2+pQRZbk7Hx5P)K=R-nvO0SLm+k4{0ZA6Q&5&Tfsu(R z{O+0Kb%<97=Uq6ul&S)`UrVYYMVFnrX%)hFw+8UOBzVa}QBV;h=0KHtkz|a_N4N>P zVzC9&VN#iW$|6)ew?K+U<@T?>YU`9f>%GV&1==XNhi_dtNs?P^v$?a)#1cq?qHlR# zy1xcgM(NWvPgqrCO|S~m$AfHnkh04E-zTjbQ(!Xk3jGNIOLX|52FUPH1TATN+~6Ei z4&@|D%A-qVx7i=NGEmckw_a&v&o(+b-Jb2a03}+VoP|Rmn#w0&K*+g?@NQNC)K=Tw&pYEr>lyS2pT;p4AsT#OI!HxRb*T7*7jh zu7Y#~E+x>|6>$VA$?GPj!PO-mJm+LNkuo$*#7!W?+&jtCgUqrYO7SVse8?2amPkaP z!1sGeq6<|)D7ZuejZ^c*AtEX_Y%UMk(xeC@mWO6la{0kS)5f{|G-VrhnL?b3RC+DO zyNjd*DfVV5yWT}W4~o-xmN7}naCJWfSp*6*pA#M5PVr2=ymRF)U~7?pm?Y8=1}O@q zJZ?}pq6C1&z{?hrmbWu7b$kKj+8SI(amCAwL{r>;_9TWEtmD8`gQI;te>@DTz*gKJbJE)?jq2V`-8lVQ@!^;B%OIA+0oKzV=}II57Q4sFyb zeBdgQb3px)QL0_4a zn(h>;t_=- zTEOCBaKaXd(Kw51m$s|)jV8JZQdCf!h2$r2rIQp=t2Ec9n6FCO$`IU=*?{mU;HC-W zi2x;^h={e2`w$LY;JIAsC}dD+GlF?)AS{eP56o{KA{%`dov{umdai{&F)!|PQKFo& z$I~sm4^%sf1yjQ8wizG0m{QrK@!bkrf>n|_bQr`Fe z&K5xk1E*Fk0BNQ@%MATApQ4h~ycd{V8wH78VbKV*NKqcyp0_?I|4&^Jo2I15!7bpjTCK1Ls;2tNRl zSTwhjq8yuij78f{{QiD?65M8?3&(dJWTA`0N<^-q5v4r%#8}-IK;H@=90DZ*QJOO% z<>IP!NOqwT&OQO+c_6~1y)nX)_CTuH*R{89?GyqDR9E4>6E4-%!g4`qfody=U!)(u&?m_bq*d#?!KDmsc|Hyb;|sO zBus_q;tW+)R$x7bwp~aWa=uI+hoFd>cxLCn0&(MG0qI5}zw{C%h(hH}t{LZA5h2RS z6XM-nnYF|QwFM)+0+%%!98m%Qg4t;n=r|>{u6SmiP%>(j7cKw)$UKEhaCmJ3+c{Zf z%B<+7X%Oo>wRfCLT(5M#Vv4jpg#~z{Two8OZmsca_AfZHxbA^SDup)ftWZx zi0?%|6da>5$gC;Aqc6nasi}ZJzUB$7xu;x5ad;a6kSSDBfQTV(&nYrA$u}vs0a1{- z(1B|l!8Ohc9A|l7=ta7I(zD(=x*=iN->B;14iuIOHKpo0xeOECH0rDJ^i1&%Lq{LdV}#ldxsMUnvY zI}0L4?*8^Nn(P#5!9bd3?MDHLxZiug?5m%!&WGQz6BAc0cM~1JG>4F9UtH)C3cxAc zfZ;eFgX=wZ!*a)OqGcbk?K#FJok0L%hzkqYZis+{%Cdu!usOu;u2A63NJ5|uQE{0P zg+q%4mOm-Zq?b@(3nfMoQIXx;=r_`$^QlGrL=5<=2`NXo^!7$bSti$^Xg z)W_zbQ}wwM!E&+UEzWOqTxTSC=p0E&wG9e3`A%&gL4N`v(Ee*=$+BecDfDJ-H{Why zH~nQlO&gwp7y_AN%c>CExy0Q4XMC@|zs2zmz6$NM8L2&sEJJA|CJ254-A(YsJNDMp z$8DcE4$jPSE$^T~;fP8AdSRsdW`CbW3`}gWM{NbE+^rUBADO54CWP7(5kQg;77b;! z(7th~?Nc1rID{Ueqe3E(&_!nA#<3aaTM&VFz!Y(t2G~ zN*I#BowZDo4y8SGsYBwd99`lXx1NnY4?z8bnErgxm%@6!ms6B}BD--F&q%*Y&G`^u zBX=o4m!P3f;ZvVWQb+@?>>=09JSXFHj(_-m((J2fDLDaiX!}dJ0S7&B-$)vB9RSzL zQOey1k!ZNSj6MZT4d00%YW{}lgU^~Do?8)@u@-U%=6M%Q5Qu~b9fRFPN+2k!+J$qJ z5|fl69IjNE;XKG|WRh@5^83rhIf;~nUqPJMh6;pbV`bcmln)PZbVA|7HH`_Hxg!WH zED$x3e~g^+>=DQLQ)aa?RDP3llopJ1VO1Q2HrD%WNsHl}MB?88|9^wp5m#XTAf$bU zLkq8)<3Wz6S;$lbN|7#Vw}gL7KiotBdq9N0A~ukRNx&g)T zOA9VoV*$Q=4&A|7+*Jo0xpQO$E-Oai+zrOcI9Rg|)ddjp6};INEwNd(sp3gnILqQ4 zyIF6*2`Q$OzR=cF2TDoMd`G`1z~=Dq>Shs7sB`~buSnxLUDBq3-g(FG;wBU)yFSlV6$4!-Y3~OX<^TL($PYx#n-fNY;WRV8D z+WU2AF@of=Gkj(tXNAph9LGj20{z|R+$SCoe1i92PWkC~=vNS#7ZGdY1h6FbAuy_b zD}tj17wHQ2AtpB=9CAcKLQrr_83GiF8=}ba`zgxMA;&n0_s1R`6sxvN=9+yHgx0bP z)thjV7QV`!zXBI4OWzRVIYkk~DPgb z@v_N>D@X@=CIQ7W5?EDQ$yXSopNcZ>Am9)T!80FPdit!n(H2Pul1?Z~3D%FAq@d39 z+~WRQ`U9Ayiyb5k@(wbls%K}`_lqDT*J-REc1!OqTcbuoPJSMm-zsAeNVg#5rebjh z>qOv5R5fl90iID~a{UASEb$&n5IYEEs30pmK|3fC+Csolu%_mkm&OF1@+d+t5B;}U zTxap1gW;U#h4+iQL+O>K>*P#rQj&<6aOZtoX_b*Tt>mAA5#GA&-6y8uzCw~oj>kBj z;$UG_P7nx$*B;8a{@FPcfA#CM6;SX^P^R2m8-;?&;-fOzDna$wB}7*^ykPK~X20?y zvrohIP^tyQAS5su#Fl6~wOKBT$u-O~hT*L@ukO!)_ul zsbg~u!UwV0Wh^20o~6%Cjc7-h-oZ4SV@!y!dih0oJu8 z5ND5+iO|A5>KrbWah!n|W|AOpdKTizL7|X?t&wgZ2OlXALA=EHf+a?`$ZdecAZg`3 zP=#=|;x*_Qfg)3@J;&HaI2bFsmFYLn@$Po4NeMuPpgx%w=}q&)!Mh8Xv$U)iqr6j% z&3IqZH;`P#&4BZ)m2SRk`Wr|uyyITR{i)Y%`rTu8vF(f%JCH2yBA$HJAaFzpfELvx zi-eaeFSb^=99mu92Zu-k%{#T%2e(w{XAwJa0>ze!U^pmIg;xs}(a?ey=LacWKfuBY z6_Ll(vIaBzw2|m8(B4-fEeKkI0~bi<>6vV#G?R5enTZ8i79y#|$wf|Icr3yl& zN@5c2@8ug2aZ#QzPM}plcyXw}qDX4Ga|rycSpPIJ`|RQm{W{DwP<3&82;Uaod;u1Q zq?;~v|M^;5Nj>NY?N89aIff(6mNG@8Ybd@WUZk^?O5Ldx0O5cS^F3z4` zF0@rjVHwFPd`}u_&*clS#{v|>!(FSG3rlZLQq~);{|Ms~hda8{(#m1=q_w>?WPQ{G*hc%k|I$g;`H%SN?{oYY9NH31vW{8sGF7U3|4!9^ zrEI$sbS>k~uvH;@?>d%~G|Im=Dc?$O^4nDo#y&?3!j(xKsO=$YVeKNQrzFMh4DTS%vkR1p9j`-e`4Vhj^w<_Y%s;$i1KGD3pF9zT@Sg36 z{uQyQt)NP}u^yTsWO2Da-(!aOvLWDT5&)T?o}9DN6YtyH?u4zibP^WK%T*vwe9CMb z)_AUq4 zU2LN?*ch9OKI97&ZUZ3Z1Vo^16y&-DVjZRHUk_%$H15$M&P6E`2RG|l7s`hTP#Y|1H!>rq}|_3G_mDjyQ3th zZ6dA^(r|_IZmn15As$c$9l{oqC!vzCrm>LV4(#*btspMGMbW#_n6;(hzyv4fCgu01 zF$W72E1LiSKmbWZK~#NWI<>8K=7=p3TL=Y6X`2$r{#juR0&*zY1cZ!j$@a{!g*Wy7 z#QoWNbuU1#^**?ll#+5%LW%0iTFpP)KS9PI^;^ZN(#W9MJbEDHS?se6h%!O`y$X?I z+v`H8ITj6EWK+oSU9bgF6cIe5{j@|sV%jl0!z@`0fu>H2K?|=RZo_vA$N4-2m&xFE zzh%SaSsZleZ&ht)=#Rxhy}-mynqU|F<`oAZP>_*q{DX`9ae{6i;|wBckBkD+iDm{P9I%d|alWIO>%j7o^fe8rV zILD?V=u)-cw)PRhsi$W{5Ra_J!1V&mfO*92ZJsla*|Qm6fb`&&l{a{HY3dyi*QMaI z-{ANg9RCT^({FqDaD*r{W7UuIUA;fK3&&RZn`l9ZO47}lJcS|v@tJUV^E9a&K4w)+ zv^~$fX2DbZPJKi70ob6QH&4~{EkgK>F5bWTgt%-%SYCaYrGUgEpAWY_us#|T^d^rU zXp!S=5+}|(OXQmK5NVC|LX~bxMLM*{Wp-qWQh5wF3n_&a*=KAI#FP?F2bW zg+sa&Ah%RyB9gQsg`xxFWsCuwuu4^r(zi!Er{WiPur$E*bejK2*#MC6b(9=PxUMM37?kd zp%6;Zh)4wodwS-f3a8dpp}2Y$-3XGF!hH_{v!oaCtUr%%KF{$KjJeecDz{F$;aC;I zDapbCT>AK&Ez6hG|6L{3&9#e9RbNeXn7`uSX#z#nCuwa#MB^S8+85Qw?NcjdL3h@Xm+ zE|7ajB-L+H0!lNDn{f=zU&480R~%O%vf5q&gG?u zO!sYp%sAYje5(P7iU^ySMv+NmOB}m+;{9Xj;po#n<~Yf9m%4>7K5@!U3``+toUwT= zc4^;J|AYp=&)6>3x;yw6;^UriJl|a)thm6~FX0e{w;y!}b_O~q)eX`p$%u1Ye(Qb3 zI?qv85i7_NWy16Fc0^inS8E}eV;3hRn%p+ZJhC2FfB2YnpL^eixJ63o*+`mFgV#A} zjEvPKjNiklvb;=t#L;8$nW;z0m1Id|ljFl88X>Kz(1)9}X3s zJ%x@Xj)PNGN=%m_^b>@mc^SgHVri12?h#OFTN;^4Hk zER}2zkwY|;v-!_|q z%gUF(xJo<^X~A}_`m>LAHL0Uv>!?9;?C}CSnS@{9Y1LJh~ni|$W z*G@%TuG^QXg~+^zWD))VUPe#D{JcMD({G(2yuV~AOu2#HUF5cVh4KC9JOroJh99_M z@ez&X7dXBMho3|M9LJh4!rb#-r}$jF&cVAxcODSLMflKmb5ef?O)SSQS7-e3z(z1A?_`aCMNxqlJ&jDu#P*%(pw-Cq_RE`2%Sp zxLZ5GHC(AaJsRFmE}mD;XrM z5YImqdUYbMMtLYqkt#|^7>Rb~O3qQPH06vQh3qiAbLK~8J~;)sg@-=%9Q2b32_jQP zDAy}!5*rg5cjQ0Md`lrTOe7v@!S~w9K*Of6rGJqgS(sTCgY-y|;OaiUUB!2#e9iV6e)?SUmekFcPM);+KY&QSv(4p+PJEZ={UMZ|sa z!EKJiiP%!e6etN`4g`8}7!Jlb-olG*o5U5T=B*dobN3))Dsn$h zKEg?R@hb$x{P7t(x&1EUaL!hre+n)3Y5iCOOrEi^MRW%V%dL~EpbsfUISrNZ<|KzQ z9UlUgIU>3C-f(6|=0iXFM-W?L_6Bowh5hq>t97odS&QbTJj8Fk99OvfRa_b7t!<4l z%NJjXIyd8*_rP=!$jt z03clWAK0HhY~`E%%-O1)9w!;9(tt_&5X`^AJbw#a$}TlRmq{6R83O(+KS01lecqzK zZ4UWpTxHz2G1oOt>%T-SNr;6c{&lVq6KG60ngl@mYJya%DB-GX2V_T%r$As4 z0Jy&I`?NnT^EY<-08u@KlS)bsA^SzXpFyl=aY{5-rd#bB)n^+Vodu-8mnOKfD&mqh zfzVe-f~{;GT>}LplK7rHCuYTgAsh{snDWvKOh0TciUhKwaa{S7qe z=zrE>Yfs#_M$FE-EjEwX?DPo9L6Dv0t(Rb7G_g4CQ=CC0R$`UJ7bS>AkOuLB36S`g zaY(zwF$2S=y-Z7EESRDsfS0W_4AWnoup6m1Z-AK?(t_x8({B(I(Fn#MP9 z$XV(G!L*p`fEXKMX^5eC>r8B77K3A9YV-ZIGWtl&6-YP2A;uijw#Mu6-7Xz?JNyc` z0nhv+hWjcNjxO89T_3WQ-8e`@w!!#zx+v@Yi+~X)YoZgGe_|@h;%ha zCAwr7{9O?^5dY-&i{w1q~zYMYnO+BRjgj@<&; zkG)ApAPYzpi@!rL%dD-=^piJ`2)}19fahx<4ma~E!YsJS@8aw7s@Z3!&3^L-RvcNk zEyUT?cP4G_$7gJJXA+T_1?q5;`#;8Z{uZeLp)TElwGS-A5k)}ifEljk^t6pl-ms;X zL7QqGg|KlyWf&p_mut4wRJsCLC^#t!kRLec^DfZ$a9Esrf_^gYfTMBTqci;=h;zmI z1Th=TsI+=NF1mYrdVP&J%P!fvVo|R^uxz06XLZ$mq(#}D$4TwZ! z!>@Or-746%kCG>`hf-qhldm7vAuQZO`Up&REtK-gKz!0Bed6CH6|QQ z0#Fyd17d-*6qJyeujFpHykJG3v36j`h3&AL1w#*PTlTo;{Z)57jhxsX0GI&4} zwTGmUbV~|-w$Uyv$)Y|+eb3g#k&6sPs&617bf6>PV{II_FV`e{|E)pXjN!ymZq|Ul zb|=?z7&oV_G8H8R{jqmBF||1M+Kx?5be@Y2h8#bvE`4o|Rd5YKL+qytfv#!D*x zGzl$+Z&|YxN3tU;tXFa63finBvPJ-Z66${43S~ z?^*LBBDqhy0LfrwjPKF4Ak9=-D|kZScC8?xh>#uJzi~bsO#&eHqLf^_aB>!Svc$p? zN0c&0F22r9p!Ni~#NZ@SBHbq1N@*p9mFNU6u3kl!)Yp`klCp}m0_hs@ z`p`4@WP1xAnf@t~aXGW60xvwX$7V!7L7?PJm0v&tDN#T~2+rK(AX+lr3B<`YuZ$sq zJ8uw5BH~YpX*^7AE8c33UE_4AsGtw31oJ}$9JVVag7Knn;VHSF^u$g)&8 zJjTEM_0P3a$HJzqGG_r{jwzW~BS@VfbDQ4Duh>X6X-m)b*lMO3Y5#M4*H>$$fGQ-a zBm|+*LO@Z1cc5*JvKj{uBkA!j83fr+b0DHgGo{$!buYG!&vqE+S0V%FfrfL80qM#NB<~T0}B^$c_*51_@GM`;U zg~O3vT&bp#1#uSjQ}2%rW2$=}-*^bnW*Y(q(=Ta4COajp91>SN&H-X_Z&xgq$UI9_ zVjOp3)%>f%ApE1FV-)^!E? z41nZvbB=4j4RH+_e-)3%K*+C0ae%lSm_BRYB$(eqGg*k(CIm7CMi4NMxJ=8clR{5X z0hue55VML1EMpY-UY>g}7jg0d2C8qf~+BtHaI zq>AWyb!mgap;gjgBmcRE3-=-nIR4Nn+*C_8b^g5BKb@qzZe-Yc&lHa~2^ z2qNDZ76kiKyLt>F!QxWP`5qi8cA2&i2>VMMk8p@H9!HD(Rom_%A3ljKL|%UMt8Awq1w-5xAkgejDx&H~l&YCK0&ukhPrex3O^91~8A!v`*U0 z+W*A@eu=gM+8vv>@{@D6QW~+1{XPp$PxIZanqWc;1>3~N)~siL14p1{TT}*QdB1HT z#d!KNhzaye#Nnp{bRd{JZTlF;iw?^!?OS{Gmd%lw$V%_>+l^W`WaUZIf0+Uul{5%6 zItzk<$VHwj{c)b{0dVJ%BWsB*K?tD@vE3{FLJ~!6GtG1Id=5Y4-$PuUzX~mBo0!Ie z^Pv1WG{y@c1xWCt8X~g+Z$uG3^;D0~kG~?u#~s0g&_iwR_Ndw&!mwYM;ZL}n(jth0M&#bzXMvyn$!p3&r=g1CfxxW zf!g~&bpHnW7|m`24f)^4Q2Th%ze=(WL_^FuDN=ab$P67XU>j!fjA}6es>Ao%S!*IS zR2PfIZ9d8is6`>nUh2jrc#?*22muO;5Fw8&tT#dFwoAzC#&nyF^v+r*_3{jz0G>H;U_@UhJZf9(Q~$=? zafyn$>~EtxsYMN*-ZwSeX`b!Tcq%2jDgH$nJyKN z;N~{QuTG<_k;Hxwy&_LRf`2N+=`gA}n0@VI6nFY{+g}~9@n3k}lB1H~#vni!Isa=41)rsj zq!h;73$cq`g{ai570PB@R0*>+0rkehp{fYa9TQUbbHqd@{1Vt2j~(Fc{v4A|>Sz zMwFn!#eIH7I8-@WrV06f=y}+h#IPj!T8Ic4zcd;gS|yD(^q*tALP$zlhah1-1F}K@ zvV_s*#1S!-85(ta9mM3LoM%prTqRk>h^-P{YAYbAtN`*0aF<&lD_kJ_ZO&IX)}21M?EU_B+kTZTf$Gk+e99^(sx~i=LaN``@p+^G`cqO6%81$b-bM$JrR`TZ zzRK|imWE%l@bf=pC>gu9F-a2Jg!S~Ev^g^3nEme@ClPf=EkpS4+SL(En<0Mh(`LgU z&>}wzsK=K*JwxvAB8t+E=8kX=qpp5yzHpNEa z+A&#fRMeFJ?ZF#7`x}-?@7Us%Q}|ANlP~Eb_E{eO%jgFf=RWt=_!KT3{B@AM-Kxqa zEW$)Zp}ls}zl)%0!WtBK14j_VttE)| z>`^^zRc1Je*{ZH1$)S3QvoJ2t=MAd<;02h&kAM={p4dYeMmg~+-f1Nih$^S;weZAI zv?vvw*ADTJ{=nnuCpfqHq6@kRWMP#RF+QtqULwf?KQM`bOlitvBUKb~aQXENx&jug z(w9Xb4LgdlBo>Ym*Ncatme)T#Jg0kdS^c1~=zJ&WTArT}+6o%E9 zl%z$Hmn1m4)hl&-y(}&f5z}X+xfq14g!Q6Y7SW=QY6oYYPr4mn2mC1^2uq6xFDV?@ zB?(SS@%9r46jy!df$2GdAoEfmPCBFOwtz49EZ%ylw2HDe3B(QUF%A~5P4hcB1TcR|$CXg1t%6(x1g+Sg^5q7) z1Az*CYAiz_{>(5^2mNP|6vp_ibDV$vCg)MxT~FHeg%h@pNSaRVkce2E`6P$n>!>cC zx9p#uu#p5r=$UQXSbG@HM0HXJ)@gnti-u#+lHFy5AI7fjh60{_}ri)8F`v^*nUV&OY`gaa0hW2;LdCYt7Ht9te4I>=LiBEF1C5rZO($5xQO zcyD^!H|fNrK3NQ#X+=I`KHBa?V;(PlBhXCY3aT(y$lBdwc&Me!2(GA4gI?+)R z@T^Q8fURvJ0^xX>Bt!`pT`!<9D1?pL*0{ZJL?O*J`B&#VLC6ARs4;M2hBW`vu6tOx z_K@u@T;vCcr~p^#JZaGuQ^VBj#HZpU1ZvR@ ztOm*>cpoU@A$8_ETT~%u-Uz^|f%=31J^=#%N8hxrGw<0O4?kjW?BFc*Ht#I*dX$xb zEube;RZ&*w>izuAIPCL`9fIG%JD1K!J`+{uyn+`cl2srFEsD8Rnz3G}!jP|!+T9!N zUp?*715x!niX-K;4{+LHzgyvN-$QEuDCG`2ONo&I+$F(AQ06Sp(kl^C+W=*k$>qTj z5Xq4RM30@~Gu#5ra}3RXVGZ*WrZNu@l8djiAFk74=X!OOLP97>)D-t~MHRof^u9@Z z7I7Z$IJldaCgYo=#C1y@K|GJ|rX}jZt&0!?!jNN(h$nEoh9oF3(<* zxdo!2^4xG@CjjoG0tc4)P?dIy|0$pUOaA=tZSSSeSQlZrBjXSR`uDz3;5lNd27n^= z;MzqHwlD)n)FEyo#L`=deT;uT2d5#9J+o!E;O+&ly@#^0+{bSa90v#sxd+0fUaNEz zEtMeZE=M1}9jP}#(rCt&xMyg|=68o}slWnx2y|N_6djnO!)hV z&}XQW^g4d=DA~|4q?O>Kh-mFfz!Y1Y6ShInQHWyoMAv0&Uxl+v>4{QOwE#TwQul%D zs`g_@tHx-IV!eIGP~Fv+JMv!XH*H$en+juG{F z@Ni^jHu5}Fc^4VIqpYN#A)a6F^J|Fs5G2(ASYf-S?OBzvPy<2ETOmnCN6%leLL21t z7Vkm?p+Vtj5`a2m3&{tVCu%HP%weg6JBjO~)Pm(6yv`7$%k8r$bd5Nc5Let)W;Smo zWaDqe19;dU#G$ByARX*4%dU6>1NfbOrIB(mmotbK2;s%}B=Slr=-jD}fOxKPGxnGG zsl%~LNDNYRiG$0rhQ=HMKC8Yt++Ep?WDW~F7yqGGt8o*MBNL|-iDmgLh_mS#)D14Z zwFiv)RKJ2d|E_&o`xgR~X55JBF(j814mW|qgUg8HJ~4V$+-9-;%V8&}pdguBMKl%I zq%6B?FK}ll6tPl>GAAJtQia1M8IAKk5R;ey$Z^QI@j*MDPe@6>F^TAUTycA!-`X`U z#WC*zYQsut)DP zd{E)e>=To}$aS3UlN`Un!EddfM{#t+61#Ax9WD0yG!ynJt{;hzd5UW0a9C+Qe;rTa zmp^bo67HXa8=p;rV3faRE7F6~O6k0&R!6Y8pP|8n90G__J0UwgIH{15EKUSsh(CZS z-XH=}cJ=J-+VS893%+c59As<-t{;x!EQ7eb{3tEqaw>`<0>%HJ42MdC%l>dDWcgy8 z39MM>#Eh*ikJxKp{Q{ErtYs#?#BZPF;JN4Vgxnmqu^-;FZj>y=MdnUnx^J8}o6;5s z&MUymQ9+SbB+w=lA;hau{tU5YIIy-__km=tY-RTTD*7S9SOfYe-=m$q0sTZOA;o($ zTa-=SUwLo|>&Rfe83dnl@B1Tp+xyWcvEKdx-+zqz{TgTfCeHwwTWiRzEt9MONE=Q# z$t^fFGepgZ(BfEoYTE}cYe1;4%my6!&ogB0+gRVRM7!^;3r#FCk#ivvNLCB*;ZG1D z#$Gax<4-vf^N>4?v$mLARsv_=_RFzloO{b`+k)+2O>TTo~*`-PmqA&H!hmoQbW=+oCZw)&N53u_baak7i_V%rri%MWI?0n;KS-k_b8Cv`gzMyO zhypE5>8=F%6|1TRAOa&KCz*#633aC}8PDeNJe*wtpxfT>*186eq~lEewtYC`()1`^ zLk?UZsT7|TIJbvX^Rt7-fnGoYiI*XF2cVnWlf&J6*`nzF>f3b{I@dqbw}=%z{F!4^ zlMgfalKC(X@Xuvz%KEbb2P*wH2m)#Az9DB7Ey!s!)NpC#f*0`O%W(buwN9i$Bm+qR z{Tndf>qPCMFTh+ywoV$df3|X55Q5wAm9eUpE_!|0%t7ad4iwfgqoB#e}wsvjAHrG&S?W&y!j^GDe z_y3o7o>^W~QPkW#DdCywnVz-rJCR|jHn{K(j5~mWuU>zs#@Qy=Dn$TC2EE9LQm->9i zxm{cS8X@1Jz92via{hyjxTrJj^G<++a0?>r3$Wa)>+WYogM9|)aQ`*2k8f%dK(-CB z6`O2AcE*Q)qKdYY%dRfkt_eMaxDJ8rAcR=iA*nO@O0XZO5?d}mi8AZm24o57R+><1 z7kfM$B3kXRa}iomS&ZRK1se=M6jk1R7f%uj41qbe;bdjCjib^obfdNi`&{jR^m%j} zv_0kUU7$ns|N4I7hW!U3Z{9P2;Ul7o#KQpdZd1yi@7KbWI}b!QG-%MW!=%C%%d}JY zJe;TzwdajCsAQzI*E_`%HIUXG_R^Jh(dHIyadqVv-zD=ysl_B{kdjdVl66HkrDt&M zG64jlGWsUt70H?j27i&LI^l3fmgBqJfyrYU+WMSH_Q?@ruW-GqPt9<#qB?5yn+3j7DuhTew=9iP+q8qJk$#4@M75W205#g zGD!z>%8RZhueR}`skQ}CvQ{o8t@Us*An-|-#bvf^T^w1+cYr_>{47-Ug-%N!>9fYY zcStH>)4fn84J5I~LI66JdK|7oyY{d+lmaV_*>D9MLz#{l!?jmHx|Qf*@;C{LKjEkT z#Fw*3AEY5ATLc|AzG&~WvEpX-H#J+jvzH|@A*?;<9k|Ib$0@y#Km@AKZ7!8P!eG(2 z8$6tG>Ns-lfGw~t-u&1(D>_QsCBDtKA9Edto7zj8joMrzN0)KXMo}4vrsO3E|5?m^ zN5B(dyJ#{>xhjV%?9y9if@N_H9@rv~)HbkCSH?W%YzIt>SKQJt47k8VS07zs+x2+D zb`A7dG0TNENs*M^f+EF1E=5pN*3ENh*|RDK+=pwv<&SDZ2LRGw^HD5`4uHuZPA0DPc~R8}$Rmb+ zVU0Zy8O{le7lYuuj}b!`JW8AwEV9YNl8 zE&1Ve$`VVBa+6lP0>YFWOB2m_*a4n(U#YDaAXlamo~EpR*u z{6cE85)R$?n=l0o>h+xmSy8r^mYKZ4@c(?0ccT~p9^I6m8C@l_8QYh= zWEI^*Eai7$uKN&j>OsQnF}qbCv|a2UF?hep;Qi114Mnd1x3={3EB40zhwXgZUQ5*+`w_D*f0|7|dtpUktHnXg{1rs>kl9!MiP@izTiL)N z>q1_wYvwRZ$?38GOSu2WCAf-SVa$@R?Y8!Gk5yDo+x$=`PtY2J^=We!GT?S5v z%{i-=NP7eh#=Z~J(#!Kjo+;a1^1db>1Kek)W@A1)${%mSiG0(VyCRaEIS0m zrqW>$^}!n`VaIq2(zXB1Fle>aJM1znhZqP64Yq+mUIyn*qN}N~g+Kd@9bCK3Q4p9C z>+~Yrjuk5GOa$NuIXuoPK(Q6GiDs>0*)m;BehrRh27(lU0_6PZwk10^eauoir|(TB zzbMrsOqgGPjzck+1Wjikgnwc71cUpiSjDUrePqDOVIO;kyRbk@;58RyVgG_}P)B^w z&!RXQ>u+Jd4jKsA06hK{Nn|Ayy}Sy4luHK#|Mxe#|>+Mt1Y zl1spg>M^YGW;sz8yM{P>|HZPnmOlt#7%}D8M^G|9hXWvV3Odq!oyjFio$2G5$tc7l zXQ&$Rnv7Zz@eN0%`eO^gnb>b2`LIoyz4qg8JY-9xU%Hn`R@ut8eO#3HDxA!C6Ne9B z&WxUAUFRL9Y2%TDTt8ysTnN(bAr9`&T@2C=NwzGHG!@(O36O5ZToC3SzYVvh?7}mp zoYuY?dPEKb3AsG~u&0-9oWs0-o>dAK3fF~j5H$dI#)=hY4CgR_7NAbf{LP*CxYrO-?cgvHvJ+qzAUpV^ z1k~~AF%S@y;i3(7losrHEEIuqqV#HO9TTQqf?=Ip!5XpQ5)&wE20cwq{uvd18%SVb zxQx5CT$-Ba`vjD(MY}a&-($3m&J2)0f}nO@dES$c`cmop4nLJyZA_oCYtxFtvT7_y zobY8|QV;<^oWcN_-lQG$t$^WPy<5Q5d6z|ZC7`tQM;MlD6gvWx zc|~ZZcXbSyd+^bqdif|UHSF`b{btwO8RRtKA`|irChK$)1G^KS|GPMxHfF6*v$CRu zjbB9DZvTr3BMt*Z4CQ!?ed!}M%|@QruRX#^ zT9sDcG{*T9qRzSmAfhqkYYbD$XD~cssV}1?PJ7$G`cG`ZM%X9tO7>A3EpN5d$(tA| zfshhkgq?VTAfochJAXK+4Msr==ree_8{m92S?clQ#}2;?>~uAD_to{d$clf1Z?MY_ zv@4c!DLm*(hngFL^Oiiq*IEEI`E*Q0s?J1 zVMIYfb5cJDBVXa@90GBKpyD74Inj`k5KdtggPL`53&hX)5}K&Cfb?7;Nrpg7GfmVt zpDpE;6lL-Xn9;JOEZQ*7bt>-ETF;7?5Bi&M?|82;nz-brxe3LVk&Tw_l1i7&RRJ>L zrU}cEq}YEaqFUyY{{)r=?+!;$;(H0GU)+_2swa8qa!^@Rue-t5|F^ytC#80#kKBn5 zj$gIwFF+-U0TE zj|9|PT6aYmJHQy(kEbDy|C&u(6&rKPkU_1gmye{a$%oH7tDLh{HkHib{icY+E6dxV zSZMLxW3C-@;%X~*7{Gn*J0L@OmTxyOZ*$hpVWZ_${iH`6hjHZReoOYYTRp|ey=RASM*auAm}=xI=qMG3f9Ce&Mk4AiaR`QFEpHb&P90-+?r{C@ZHj*(YDLT+57I zZ{dK_*B--sn|I~EO{h9LAFNDXvj1|Ia6Z8`nN>IXcOEWGY`?^iH@f*7u1}&1OP&=n zaOxaJ^1?c^ACW$uTqKmO2J& zic&9!oGtfLGWJP0l~-5+aWfbIk)90d;u?>5s|`2~58!3=lXs*)ARles3H%gNCnX~? z`9Pon&t%X(d8ne~Y2k)!2C9waU%(-7BcCK^23m2G2loi97XamQ?S;!x_fV%Tu8X<-+5g|s4|#*q0mEczOoopPLv6s*_NzM|v` z@7x=AM{LS0>L(gS$Vc0NMe%X}<$bQc2O&AfDC7~gJrN5QRO_7w8X(NSSZU_t$aEHvmaCr&>7s+F+)bs4Wfv$xtvivcbpAN%iIySR=s*ggU= z{S~Y1J!5-5cG*s3&RYE|D>lA(p6`Fd6++9XWsdjQ)$WwtK16v+kMZGC(O1$gu?m!g z)o>fA*PvSM*!WB`sNaI35o{@>tU`j6&vD>pY@UMm{=wf<5Bmz`yNvt&CEM3<6~6}o z+$AI0?3(=gGid%bNtZm7d>!iWQ!N3m4Re&nIAzmTX8Byue2!d#VU)DSVNta>_UB@Il-_q$dy} z@brk!+};4Fg}94=y68h*IMR=40}$VRbwOsv%F`)3thF1fa8r@iu%gSfTY{Z;=`0R5 z*nPI{+l#|VMi4xkFgM|3@EWuZwKb5BBOHAbAj|3uKY10#`8L6sq>>W!<5ea92L@PW z*o`E5k~E%*v*eMRV`9sxsu!G_+K4)eQs-Fq&`Nh#P~Jp!10*t|H+T=?;&dhdpbT`WAi{XA*I+!j zk4KP$;`85EDB=(WZTdol(t9~)(W(M!OJb|7qcZYXU_guSK#+~@@&so<{1KaWTCDBX zyrn9kh?1L;8RbaAAwGbNFRgUH)uKrrpGG#63@=5XsJ5uTcJXN-X-P>Ida~h1n?2Xt zYJ)dgtSUWgRXU|cw)R*Z+7JUq0};wnF3tJDKBv(3>JhUZ4JK)$Kj_&9KL?m#$MMhG zXW6=#E$rrWTAY+_!U?mkM{MP(x9!HzQOoqyThk|Bha(x;4AM1di35E$i>KVdu})j; z9y8nD%Xix07-jDiBBU-71*ppge%wsq9#2n0?SGj=mLIZ-SdG;*BNHEmGpRgE*w!FC zZ1@@qzv-G9%T*;T(F9eM&p>I?<%&#}g97$BRKTt`u|kqOaSgAyQZk$4+u3*-cWw}PRpV( zL>C&73*^vohdbrN6xjP#{W=KcU;7E~85_NAHo!_yd-s5~rl%krUk%>#TZphT5r8l@ z8)I^~@XjIYrEme}W~%CVQ4dilfzGdkI>aQ)@+=EApX{PkqN1+VCYIsb5-LYN2?R;I^URsyu55Uc_y4+cI(*0>Kq6XD$g)G!nHOH}1=x&9Dp`8mrSDkbKc-Ae7T3!FPxn)ndk zh~w}G1N9fIzrDh)9qO>g^;=fIimY4>1&aVmTTwk~)rnafV{Lm{XB(v8W~_~3F;TL5 zrv9N0U}G*t@!xTWLy4avZpx-F?6yg~A5~!wP}|)8 z0(g55PFrzDhM8`|^ayGiRu&}pbJyH5*#uVx71v zpfg2!pj-}msb{Df!x9Ru=HjKVqw`)=M6$d5(7V+e>577{RyeiOpp=DL)~T_+0h04I zsEL=;He<<}k!^vfqpu>=aIfVOYtCTyoqbR`CPjwl*j;u2lsQ+7Wg*lbrCMT*%_^)4 zcsza`L|sW+Wy!E{CngRi;t*6Ur`o)G*Fd?o35vGL(k7C#d9;cNva^}JIQLF1S z`vH^;s^@FeO`=fWW56|V|F2vxLg_wlRo{Koj$h<7p4ajEbE%v6fFlgDWnDjx&-o0i z0huAV3LR5b0f&$f723mpbP&ht&_e$!bP$hOZud3bbNtp+wXKY$QF1@b-=EF+#y|@8bDlV^7S-NT2_8q%s)#cMvRcVly7zLfcBZZ)lkq`Kd znAw>%kN@Mk=6FatWr);g)AHz z;iS+n$SwLKm)Y{2wwvpm0uA0tXeVOsR|o~AZ)cM0T!6Ag8y>*X=W-YC`JPW^?y}YceYoXfaEx|>7}{N@=So0{46+}0?J>$HX_I$d zv$F0tEw#9A{f)4iFCJ8CeB#?h*xjE$i4t@G037j2L_t)1(6Zlt!itAc1nR7T&wrPJ z2U5DRA0&a8FhUtFwUI0cfx|CW@lHFIYp_)9qIDoTq*pGG*jZ1~>|3@r_+3lC&|!z- z=WMll-3EV%h;XGV{LXE<+(uq{#Fj3W+U_L23fW@JSz55`DCHn%Huok!h%!>7iNfjK zec>-C+czNOl#hcnI_d|ku6D%MZ&J@uP3o6}*bQSGtUCn9k;OR=`K=;iX}4r{@8(2Q zNHqUB46L#et1zEl62dJ`+gdqs?xOBNve9qbg$euG|LC&UPIPc~0`2BN{&$FX{Fd1> zuUIWg#j#tR*8A+Ewt5woe(eW@8eu}y7l?Yxjck*?1TvicJKq03{r@Ep@5f>H84H^5 zvi6io@Y*WKkv@`#qH4Shb1Ly{3^(jTDz3Nj%nR;klUFOaqQ9R4GeBhYfoGimTP~jmZ=za&%8!3miaSkYDf8U(5dnmdXiq=b!+jOPHOQw!jtMP`O^F1`UkDNXgr_f2 zfV5QJBr7JacvOFS1-KO;FtTb$cB+b+YLJW41WHgLfYvk;0(Va z9DD3N{hB3)@mkvt<-Y{W-p7RdpTBALjVGZtV*i+M_RqNZW>AyF?{SLMgvrrx*&3{9 zJC_54erXq!zJ(|&whn8Vk!aBXOMdDaA$p-qud+FXf!IbIO^6e{oT`(uE6+b-16Oym z*1n1gVLxG?@D$M~lF3uH+BIPlse0J^Icq&}73!t|9RloeyoO`WS~B>^qfnx%?wEso zku=;1N)(p6hBVWLUm+UZHEFki+UUql%E%;|D1z%AvPzJUW&WCgA$LOCWkG71Go@N) zt)uSt1uoji+l&tai5@p;+p};7&z`mlkYMW!_3SGLkuh<%0U7^P_0CuW%X&$zd^pgy z+AS$BOPDFwv*D^pyEi$vz+J4#s7HwP?R{p?eGs!`+W**Fh%>*$``_Z?{XD$=q2QFK@W6;jN{0a0a#g;gMQg=iAAbNoTJ+>1;16l?^P$(2N(e*k%mtNqOFuebeiE;p?YF5@lNA|Pl)AgEn9|1 zc>isdqByyRb^d0y4OiAq!lX9ZbmMMu98Ozl27@M zmeXV?BI*^32^Z3lvJr@=1os?rtREjOZouVK1_+4Xsh{l6-M>BWv^1yXKv57~Vk-!z zr8%c4A!E&mW`OaH6#|6}xf61oKvP6`JuWoHa zAU-Lu!UuWk0TAWGu*JRe@>Pt5HafYVJI?N-Vjk@7A0-xDcYpD}oGl?CZTCgDkc5+o zsoaVZ0o2o-foK^}RN32=$>nU8CXMQ>8v^6CF$_Nu&N)O#}%9542J3Pa*NA(igpuj2h7tL#zStA=g36?U!$EzsSYdz+ltqj z+{MW8%WQ~Rzf7Mks$NWP`3$_UhqFttW^5vS>J;yZV}DJb@ONJ;4qsG%>b^_giDO-G zDfB(2!24N^Ny05;wT1bvcH_?if_tI<0xJIdDJoTWt-A&{l->uqI$5J?bHKYyElQwQ zq6VVW+NP_munfMyA%yr(D1j!k1Mi_aGE@SYh+PEWkWIC)9XM}v< z%0$XS%5xVeeZ7nF$^RIBlZKpYJ%za~)Yw-(Br8Xh-RrC3iAqPvMHF*uUV246WOPMa zdz0zsXMox_sf|G1$WdVOQQg&^+U6aCpZnnD2`RR84idfePW>jA-wq%n6;+g8s|MuZ zm+Li%qwvyGP+pm5Z}edpyU=c%EmUYFe_!PVYgb;g#HTsRsyJ(7RgISIIYx?~Vj~Gy z|Mni9p~SGZvC&E^=J@QAsu0u5QxCrNtraRN<vu0m7PM5umMZB52?1L8;#aI z-)9xgoqUldUceytb$iN(MWGLHe}BK9Hp*K^ossD=h2DMrGCOX~3Aa1a97=}XBejl4GhU>LQ6Bh%!*J5cfnK?Onc zf17WQ*;h|LWj(bm98<`|bs)|-6LTI)kJsf=tT9`w2iXceg2!_DX9W&H@Ys2?&pk^W z#>~F+3G5eCt`RPD82g6~D(XDrN`b-~${{x08cTk-z4%|3e0>QV!C>4hh7%BaTG}S_Wl{x}M46s{x9X52WjL&3_j?tDqnbMG+;GY^-8C^%o^& zayt+pdhglk4^0-eBM=lMc-F5HiSBwwnFVpo9LVODc{xit~lZD<3LBIVm~6?L0IG{_d>fzqoB~r z{XWYJW}o>ZD|+*k?MYs~IR36A_;R>(#bgzs9lmY0er%Mo&J(cj~iYPr#!@iH!+2$BO|jQQ)nU z@uzSzKZKj;ixSUjc9*()lW@d4+sWba(4ns}hT6U3_ zsHZ?WfM`iktT(K=jyhkLV$~;Y5Nzx3VzShZmz{$Lf}@g16@ic}TdfTTD9NH_Gz}?M zM^K__Q%}6#L&bFFK+7b|2M9JnUE`nNJsgOv0T;S0i97O``dGw4o|JPR;|0QWMe1+%TT zbiLbpR=R9{x{T#JCT2H-UduqIFEW^+(AP9*Uchh!_VGGMBm;7JfiwKZTCFwHZ#5S= zf_n8DVZMhr0HT-%QGD;iC=F*(5TfW;A=MP)Q*c~g+%-Z}#CgK!aM5JUZ!Ah_b_vQ^nHdfMz zQywPA9`ch}Zt6dB{e}_(L^>nTb9C{ji5O96A%*_GC;3+_@e~N@*qq&{>$K^##17bd znH6hC;N;_+xmkgMQZ4J|47CdK&7J1xZqj8*)7gGPx(jb9Sl`~y*$W3i2J2SJ=}I<5 zJ!M_E4WyTFgo0VKsBy3XDhzmlK579OW5S)}h_O<19djVw^=re_rHshX^ z*xbTCyLP?HZVz+b0K_Pj*voq;slzoW?SLhE+N}m+R6LZmm38W1uZQ2c!NxY_hO8ws zj9|mW94C*Nr}@CO1~;?T!Lvjeo;N^4L&!n*trJo2o+RDfh(<~3xZg@pgw?mfjkKw4 z_i)jk)mN-EHg9X=oYBW5O3IURC21|wjxrmLkf#n$)tLZNs_KNG+Bqv{CtmDYThQjD zb}v{fNptq-9MXeaqUB)%i34g+mQH0@Sw1vpb>%}C{lHz278-YZf#0h}-1Bw-Oyr?^a#tmu+!?O%vM0vDW z6l_9Kf+1BBg&{wWtu(`OC`6DcWphPZF?X32LtU9Hk$G_tMqve%SsVZcDbAc|Nlce> z4j#aNR2J>c59-7#-E<-tB>+ z(&lswH5>>&>zCb$ zGbQErTEi(@I#^=04YLgDGko|F7uXIkPO+W|wJa z`ztOujX(X2*_XaxxvL#^vg}o>>`6=>;o_LkXSK5q7Ra9K;o(FvhXC??Gnj%t_w zr**<+W-9Rf%mFaDZsMs83ls8MtuQO_-b*d&qzjC$@tjYQ@}Hv&2=-{uoZDNNXK&9Lm#!D=d^*XY6S!v9!?qcf!s?5F1M69D*kpCNDW$ z-{EMKL_SMQ+TtE0r;Bp=CpXMd{RK)jESLWs{dj?*KepUyv`}vM(|J686kcfs<@PpH zOx4e`P#Ar?M}B&@aRtE?^*uqD{zf*Qzw!+0EIo{jfp{-80CeOT%T>$Qc4v zP18^UD1cmg>zqIhj7o76Q0}63sD-rOuUQ6pa(cB2cY7S{S^| z_Gc*awr2w>Ko|dUmHzk{%N|{_Q9J}KYi~rlUmt;eP}2|D5L9%9CDKxS>AUi?io6c> zQeWD6202@xla|D0%d8f&-7=QomJ!ugdfUKKWy+eb#H?f*hy^%hQsOszCY|#D0k_%UTI)r(e^5Lw?eoFsV!VE(B3a|CP`dxh}p3;$Wid zj*U}hoehxirA*qpKw)dM6=*fyW3h$pnJgj>-s$T2tq@W!r@i<1yh%9qQ3FBLWD96Q zMrn(x=x=`)h70ou3ZnSv6WlO`NB2xjaKbx%_pv4_7EM;Y$V>lTWmI0`G!&BjeWgHJ za+OIWAipdMv;je!jX&>tAg=Oq+X0mz$~cO{-_*-Za1}55?9YUWg6Z$*+1nuq7;q{2 zcz@Wo=wY7Og5XW3e;?CnnkR3XB5rNzb+2+On%jDN|v2ztxGhoyxU|3+l50=dC>UP1}vJ zOY7_q)DE2kMlGB07_#$ei?v_3%t)Q3FU!aNEz%m>3^Lka#aAnAS5=?2KYYW=q#=Lv z7~gNB<>od3=i?fGO@Ij02;zjI#)#fWcW4LPOXJ~QYsTa7)Ihb(v99iv+=I{-O$;xL zlt$oeX2nuV|ItjjbiU$&K6EHhS3z&5Rnrc;PF=L(Pd|@;Mm!wH+im$I92MNac96s) zQ5gGttja8*1{>-;7e4_ zAjmz^@>i1odDx(|dM1hd`Y6c*t8;iTXJzN0h+3N?*v95_uY7$-=U0t8t-2;n(0j^#F3<@_?)^JdYsa8TlS*93G*P$|;jJq<@ zWCMd;tc@?(?x{W~;TydF4jcj$MjXT);@xVuN;rrX*%#oj!VjXD5nC^AAs;5$ZFief z1*%x{PFnj_s8U(P{wv%fd8aJVUu(%El(if#gY|EhtD|IEMl5@H)b8BgXVYwuDaH{- zpqFTaBcZy?_6(&2!$M%Cb>MbUSjpR+L?}xv@&jAtGG^ z98VEt;B;bZp`Yz37I|o+C1HzWR#USCe}>NC+yS^Z+8d{%n{^KJFQD5FMF3$BMO##b z*dvPcuBgH`J*;$`er^38P>#FadRX2>`QG*MK7T~IC4Aqc z`W_{IxAnYV`TZLSZKeG8$Zq>rB(jmm#=~Zs`E9vPg zcm;}bb-(3i;2y@H5-Cx2sN+C~*>k5^+lE@P_CGsXYeP49Cy%wbuCfcQgxd;xu|0ZR zzJZWcv8Zd}Ku%Gf4M1kC%d%WOVGGNhoExy)79WH1BAagjrxdZ3z#6Nt7-SR7oZMW? zsflbDv@$UxDocR7E|+(#i_Sj%r|8nU9~3VWd_i)3O4xT;C&#p*^W zPC485_oG;c%3g)j;G6=J=i-R!!b-286Pd$_X9lMoNmfo0oZ~m8EzS9>8Cyy*+gxg$ zXR}s3i$CNI-;4@G+5_bC!;?18azf3in^xN~MD0|Qa1!quz<%Nao2V9T2A#qBIr7Rp z&Kf`cy8?%_>jspcb}S~`KRgY`_wR`NHM{WBV>UjCeS}slP7v;TJM|&I0tNNfJ^Qyn z>+=A`)KBXMX>mym{cZ-lpN4gHU2z51i)D bcU%5HHl?3v;lGWz00000NkvXXu0mjfpGTzJ literal 0 HcmV?d00001 diff --git a/freedv/tags/1.2.2/src/hamlib.cpp b/freedv/tags/1.2.2/src/hamlib.cpp new file mode 100644 index 00000000..ff80b24a --- /dev/null +++ b/freedv/tags/1.2.2/src/hamlib.cpp @@ -0,0 +1,160 @@ +//========================================================================== +// Name: hamlib.cpp +// +// Purpose: Hamlib integration for FreeDV +// Created: May 2013 +// Authors: Joel Stanley +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include + +#include +#include + +using namespace std; + +typedef std::vector riglist_t; + +static bool rig_cmp(const struct rig_caps *rig1, const struct rig_caps *rig2); +static int build_list(const struct rig_caps *rig, rig_ptr_t); + +Hamlib::Hamlib() : m_rig(NULL) { + /* Stop hamlib from spewing info to stderr. */ + rig_set_debug(RIG_DEBUG_NONE); + + /* Create sorted list of rigs. */ + rig_load_all_backends(); + rig_list_foreach(build_list, &m_rigList); + sort(m_rigList.begin(), m_rigList.end(), rig_cmp); + + /* Reset debug output. */ + rig_set_debug(RIG_DEBUG_VERBOSE); + + m_rig = NULL; +} + +Hamlib::~Hamlib() { + if(m_rig) + close(); +} + +static int build_list(const struct rig_caps *rig, rig_ptr_t rigList) { + ((riglist_t *)rigList)->push_back(rig); + return 1; +} + +static bool rig_cmp(const struct rig_caps *rig1, const struct rig_caps *rig2) { + /* Compare manufacturer. */ + int r = strcasecmp(rig1->mfg_name, rig2->mfg_name); + if (r != 0) + return r < 0; + + /* Compare model. */ + r = strcasecmp(rig1->model_name, rig2->model_name); + if (r != 0) + return r < 0; + + /* Compare rig ID. */ + return rig1->rig_model < rig2->rig_model; +} + +void Hamlib::populateComboBox(wxComboBox *cb) { + + riglist_t::const_iterator rig = m_rigList.begin(); + for (; rig !=m_rigList.end(); rig++) { + char name[128]; + snprintf(name, 128, "%s %s", (*rig)->mfg_name, (*rig)->model_name); + cb->Append(name); + } +} + +bool Hamlib::connect(unsigned int rig_index, const char *serial_port, const int serial_rate) { + /* Look up model from index. */ + if (rig_index >= m_rigList.size()) { + return false; + } + fprintf(stderr, "rig: %s %s (%d)\n", m_rigList[rig_index]->mfg_name, + m_rigList[rig_index]->model_name, m_rigList[rig_index]->rig_model); + + if(m_rig) { + printf("Closing old hamlib instance!\n"); + close(); + } + + /* Initialise, configure and open. */ + + m_rig = rig_init(m_rigList[rig_index]->rig_model); + + if (!m_rig) + return false; + + /* TODO we may also need civaddr for Icom */ + + strncpy(m_rig->state.rigport.pathname, serial_port, FILPATHLEN - 1); + if (serial_rate) { + m_rig->state.rigport.parm.serial.rate = serial_rate; + } + fprintf(stderr, "hamlib: setting serial rate: %d\n", m_rig->state.rigport.parm.serial.rate); + + if (rig_open(m_rig) == RIG_OK) { + return true; + } + + return false; +} + +int Hamlib::get_serial_rate(void) { + return m_rig->state.rigport.parm.serial.rate; +} + +int Hamlib::get_data_bits(void) { + return m_rig->state.rigport.parm.serial.data_bits; +} + +int Hamlib::get_stop_bits(void) { + return m_rig->state.rigport.parm.serial.stop_bits; +} + +bool Hamlib::ptt(bool press, wxString &hamlibError) { + fprintf(stderr,"Hamlib::ptt: %d\n", press); + hamlibError = ""; + + if(!m_rig) + return false; + + /* TODO(Joel): make ON_DATA and ON configurable. */ + + ptt_t on = press ? RIG_PTT_ON : RIG_PTT_OFF; + + /* TODO(Joel): what should the VFO option be? */ + + int retcode = rig_set_ptt(m_rig, RIG_VFO_CURR, on); + fprintf(stderr,"Hamlib::ptt: rig_set_ptt returned: %d\n", retcode); + if (retcode != RIG_OK ) { + fprintf(stderr, "rig_set_ptt: error = %s \n", rigerror(retcode)); + hamlibError = rigerror(retcode); + } + + return retcode == RIG_OK; +} + +void Hamlib::close(void) { + if(m_rig) { + rig_close(m_rig); + rig_cleanup(m_rig); + m_rig = NULL; + } +} diff --git a/freedv/tags/1.2.2/src/hamlib.h b/freedv/tags/1.2.2/src/hamlib.h new file mode 100644 index 00000000..65af2d46 --- /dev/null +++ b/freedv/tags/1.2.2/src/hamlib.h @@ -0,0 +1,31 @@ +#ifndef HAMLIB_H +#define HAMLIB_H + +extern "C" { +#include +} +#include +#include + +class Hamlib { + + public: + Hamlib(); + ~Hamlib(); + void populateComboBox(wxComboBox *cb); + bool connect(unsigned int rig_index, const char *serial_port, const int serial_rate); + bool ptt(bool press, wxString &hamlibError); + void close(void); + int get_serial_rate(void); + int get_data_bits(void); + int get_stop_bits(void); + + typedef std::vector riglist_t; + + private: + RIG *m_rig; + /* Sorted list of rigs. */ + riglist_t m_rigList; +}; + +#endif /*HAMLIB_H*/ diff --git a/freedv/tags/1.2.2/src/info.plist b/freedv/tags/1.2.2/src/info.plist new file mode 100644 index 00000000..8f0d4c34 --- /dev/null +++ b/freedv/tags/1.2.2/src/info.plist @@ -0,0 +1,104 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + freedv + CFBundleIconFile + + CFBundleIdentifier + org.freedv.freedv + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FreeDV + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.5 + NSHumanReadableCopyright + Copyright © 2012 FreeDV. All rights reserved. + + CFBundleIconFile + freedv + NSPrincipalClass + NSApplication + + + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + freedv + CFBundleIconFile + + CFBundleIdentifier + org.freedv.freedv + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FreeDV + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.5 + NSHumanReadableCopyright + Copyright © 2012 FreeDV. All rights reserved. + + NSPrincipalClass + NSApplication + + + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + freedv + CFBundleIconFile + + CFBundleIdentifier + org.freedv.freedv + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FreeDV + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.5 + NSHumanReadableCopyright + Copyright © 2012 FreeDV. All rights reserved. + + NSPrincipalClass + NSApplication + + \ No newline at end of file diff --git a/freedv/tags/1.2.2/src/serialport.cpp b/freedv/tags/1.2.2/src/serialport.cpp new file mode 100644 index 00000000..59dd0c93 --- /dev/null +++ b/freedv/tags/1.2.2/src/serialport.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include "serialport.h" + +Serialport::Serialport() { + com_handle = COM_HANDLE_INVALID; +} + +Serialport::~Serialport() { + if (isopen()) { + closeport(); + } +} + +// returns true if comm port opened OK, false if there was a problem + +bool Serialport::openport(const char name[], bool useRTS, bool RTSPos, bool useDTR, bool DTRPos) +{ + if (com_handle != COM_HANDLE_INVALID) { + closeport(); + } + + m_useRTS = useRTS; + m_RTSPos = RTSPos; + m_useDTR = useDTR; + m_DTRPos = DTRPos; + +#ifdef _WIN32 + { + COMMCONFIG CC; + DWORD CCsize=sizeof(CC); + COMMTIMEOUTS timeouts; + DCB dcb; + + if(GetDefaultCommConfigA(name, &CC, &CCsize)) { + CC.dcb.fOutxCtsFlow = FALSE; + CC.dcb.fOutxDsrFlow = FALSE; + CC.dcb.fDtrControl = DTR_CONTROL_DISABLE; + CC.dcb.fDsrSensitivity = FALSE; + CC.dcb.fRtsControl = RTS_CONTROL_DISABLE; + SetDefaultCommConfigA(name, &CC, CCsize); + } + + if((com_handle=CreateFileA(name + ,GENERIC_READ|GENERIC_WRITE /* Access */ + ,0 /* Share mode */ + ,NULL /* Security attributes */ + ,OPEN_EXISTING /* Create access */ + ,FILE_ATTRIBUTE_NORMAL /* File attributes */ + ,NULL /* Template */ + ))==INVALID_HANDLE_VALUE) + return false; + + if(GetCommTimeouts(com_handle, &timeouts)) { + timeouts.ReadIntervalTimeout=MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier=0; + timeouts.ReadTotalTimeoutConstant=0; // No-wait read timeout + timeouts.WriteTotalTimeoutMultiplier=0; + timeouts.WriteTotalTimeoutConstant=5000; // 5 seconds + SetCommTimeouts(com_handle,&timeouts); + } + + /* Force N-8-1 mode: */ + if(GetCommState(com_handle, &dcb)==TRUE) { + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.DCBlength = sizeof(DCB); + dcb.fBinary = TRUE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fTXContinueOnXoff= TRUE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + dcb.fAbortOnError = FALSE; + SetCommState(com_handle, &dcb); + } + } +#else + { + struct termios t; + + if((com_handle=open(name, O_NONBLOCK|O_RDWR))== COM_HANDLE_INVALID) + return false; + + if(tcgetattr(com_handle, &t)==-1) { + close(com_handle); + com_handle = COM_HANDLE_INVALID; + return false; + } + + t.c_iflag = ( + IGNBRK /* ignore BREAK condition */ + | IGNPAR /* ignore (discard) parity errors */ + ); + t.c_oflag = 0; /* No output processing */ + t.c_cflag = ( + CS8 /* 8 bits */ + | CREAD /* enable receiver */ + + /* + Fun snippet from the FreeBSD manpage: + + If CREAD is set, the receiver is enabled. Otherwise, no character is + received. Not all hardware supports this bit. In fact, this flag is + pretty silly and if it were not part of the termios specification it + would be omitted. + */ + | CLOCAL /* ignore modem status lines */ + ); + + t.c_lflag = 0; /* No local modes */ + if(tcsetattr(com_handle, TCSANOW, &t)==-1) { + close(com_handle); + com_handle = COM_HANDLE_INVALID; + return false; + } + + } +#endif + return true; +} + + +// fixme: this takes about one second to close under Linux + +void Serialport::closeport() +{ +#ifdef _WIN32 + CloseHandle(com_handle); +#else + close(com_handle); +#endif + com_handle = COM_HANDLE_INVALID; +} + +//---------------------------------------------------------------- +// (raise|lower)(RTS|DTR)() +// +// Raises/lowers the specified signal +//---------------------------------------------------------------- + +void Serialport::raiseDTR(void) +{ + if(com_handle == COM_HANDLE_INVALID) + return; +#ifdef _WIN32 + EscapeCommFunction(com_handle, SETDTR); +#else + { // For C89 happiness + int flags = TIOCM_DTR; + ioctl(com_handle, TIOCMBIS, &flags); + } +#endif +} + +void Serialport::raiseRTS(void) +{ + if(com_handle == COM_HANDLE_INVALID) + return; +#ifdef _WIN32 + EscapeCommFunction(com_handle, SETRTS); +#else + { // For C89 happiness + int flags = TIOCM_RTS; + ioctl(com_handle, TIOCMBIS, &flags); + } +#endif +} + +void Serialport::lowerDTR(void) +{ + if(com_handle == COM_HANDLE_INVALID) + return; +#ifdef _WIN32 + EscapeCommFunction(com_handle, CLRDTR); +#else + { // For C89 happiness + int flags = TIOCM_DTR; + ioctl(com_handle, TIOCMBIC, &flags); + } +#endif +} + +void Serialport::lowerRTS(void) +{ + if(com_handle == COM_HANDLE_INVALID) + return; +#ifdef _WIN32 + EscapeCommFunction(com_handle, CLRRTS); +#else + { // For C89 happiness + int flags = TIOCM_RTS; + ioctl(com_handle, TIOCMBIC, &flags); + } +#endif +} + +void Serialport::ptt(bool tx) { + + /* Truth table: + + g_tx RTSPos RTS + ------------------- + 0 1 0 + 1 1 1 + 0 0 1 + 1 0 0 + + exclusive NOR + */ + + if (com_handle != COM_HANDLE_INVALID) { + if (m_useRTS) { + //fprintf(stderr, "g_tx: %d m_boolRTSPos: %d serialLine: %d\n", g_tx, wxGetApp().m_boolRTSPos, g_tx == wxGetApp().m_boolRTSPos); + if (tx == m_RTSPos) + raiseRTS(); + else + lowerRTS(); + } + if (m_useDTR) { + //fprintf(stderr, "g_tx: %d m_boolDTRPos: %d serialLine: %d\n", g_tx, wxGetApp().m_boolDTRPos, g_tx == wxGetApp().m_boolDTRPos); + if (tx == m_DTRPos) + raiseDTR(); + else + lowerDTR(); + } + + } +} diff --git a/freedv/tags/1.2.2/src/serialport.h b/freedv/tags/1.2.2/src/serialport.h new file mode 100644 index 00000000..e5db10b4 --- /dev/null +++ b/freedv/tags/1.2.2/src/serialport.h @@ -0,0 +1,42 @@ +#ifndef SERIALPORT_H +#define SERIALPORT_H + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +// Serial ports called com port for historic reasons, especially on Windows machines + +#ifdef _WIN32 +#define COM_HANDLE_INVALID INVALID_HANDLE_VALUE +typedef HANDLE com_handle_t; +#else +#define COM_HANDLE_INVALID -1 +typedef int com_handle_t; +#endif + +class Serialport { + + public: + Serialport(); + ~Serialport(); + bool openport(const char port[], bool useRTS, bool RTSPos, bool useDTR, bool DTRPos); + bool isopen() {return (com_handle != COM_HANDLE_INVALID);} + void closeport(); + void ptt(bool tx); + + private: + com_handle_t com_handle; + bool m_useRTS, m_RTSPos, m_useDTR, m_DTRPos; + + void raiseDTR(void); + void lowerDTR(void); + void raiseRTS(void); + void lowerRTS(void); +}; + +#endif /* SERIALPORT_H */ diff --git a/freedv/tags/1.2.2/src/sox/band.h b/freedv/tags/1.2.2/src/sox/band.h new file mode 100644 index 00000000..5398ff45 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/band.h @@ -0,0 +1,47 @@ +/* libSoX Bandpass effect file. July 5, 1991 + * Copyright 1991 Lance Norskog And Sundry Contributors + * + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Lance Norskog And Sundry Contributors are not responsible for + * the consequences of using this software. + * + * Algorithm: 2nd order recursive filter. + * Formula stolen from MUSIC56K, a toolkit of 56000 assembler stuff. + * Quote: + * This is a 2nd order recursive band pass filter of the form. + * y(n)= a * x(n) - b * y(n-1) - c * y(n-2) + * where : + * x(n) = "IN" + * "OUT" = y(n) + * c = EXP(-2*pi*cBW/S_RATE) + * b = -4*c/(1+c)*COS(2*pi*cCF/S_RATE) + * if cSCL=2 (i.e. noise input) + * a = SQT(((1+c)*(1+c)-b*b)*(1-c)/(1+c)) + * else + * a = SQT(1-b*b/(4*c))*(1-c) + * endif + * note : cCF is the center frequency in Hertz + * cBW is the band width in Hertz + * cSCL is a scale factor, use 1 for pitched sounds + * use 2 for noise. + * + * + * July 1, 1999 - Jan Paul Schmidt + * + * This looks like the resonator band pass in SPKit. It's a + * second order all-pole (IIR) band-pass filter described + * at the pages 186 - 189 in + * Dodge, Charles & Jerse, Thomas A. 1985: + * Computer Music -- Synthesis, Composition and Performance. + * New York: Schirmer Books. + * Reference from the SPKit manual. + */ + + p->a2 = exp(-2 * M_PI * bw_Hz / effp->in_signal.rate); + p->a1 = -4 * p->a2 / (1 + p->a2) * cos(2 * M_PI * p->fc / effp->in_signal.rate); + p->b0 = sqrt(1 - p->a1 * p->a1 / (4 * p->a2)) * (1 - p->a2); + if (p->filter_type == filter_BPF_SPK_N) { + mult = sqrt(((1+p->a2) * (1+p->a2) - p->a1*p->a1) * (1-p->a2) / (1+p->a2)) / p->b0; + p->b0 *= mult; + } diff --git a/freedv/tags/1.2.2/src/sox/biquad.c b/freedv/tags/1.2.2/src/sox/biquad.c new file mode 100644 index 00000000..c57f1902 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/biquad.c @@ -0,0 +1,178 @@ +/* libSoX Biquad filter common functions (c) 2006-7 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "biquad.h" +#include + +typedef biquad_t priv_t; + +static char const * const width_str[] = { + "band-width(Hz)", + "band-width(kHz)", + "band-width(Hz, no warp)", /* deprecated */ + "band-width(octaves)", + "Q", + "slope", +}; +static char const all_width_types[] = "hkboqs"; + + +int lsx_biquad_getopts(sox_effect_t * effp, int argc, char **argv, + int min_args, int max_args, int fc_pos, int width_pos, int gain_pos, + char const * allowed_width_types, filter_t filter_type) +{ + priv_t * p = (priv_t *)effp->priv; + char width_type = *allowed_width_types; + char dummy, * dummy_p; /* To check for extraneous chars. */ + --argc, ++argv; + + p->filter_type = filter_type; + if (argc < min_args || argc > max_args || + (argc > fc_pos && ((p->fc = lsx_parse_frequency(argv[fc_pos], &dummy_p)) <= 0 || *dummy_p)) || + (argc > width_pos && ((unsigned)(sscanf(argv[width_pos], "%lf%c %c", &p->width, &width_type, &dummy)-1) > 1 || p->width <= 0)) || + (argc > gain_pos && sscanf(argv[gain_pos], "%lf %c", &p->gain, &dummy) != 1) || + !strchr(allowed_width_types, width_type) || (width_type == 's' && p->width > 1)) + return lsx_usage(effp); + p->width_type = strchr(all_width_types, width_type) - all_width_types; + if ((size_t)p->width_type >= strlen(all_width_types)) + p->width_type = 0; + if (p->width_type == width_bw_kHz) { + p->width *= 1000; + p->width_type = width_bw_Hz; + } + return SOX_SUCCESS; +} + + +static int start(sox_effect_t * effp) +{ + priv_t * p = (priv_t *)effp->priv; + /* Simplify: */ + p->b2 /= p->a0; + p->b1 /= p->a0; + p->b0 /= p->a0; + p->a2 /= p->a0; + p->a1 /= p->a0; + + p->o2 = p->o1 = p->i2 = p->i1 = 0; + return SOX_SUCCESS; +} + + +int lsx_biquad_start(sox_effect_t * effp) +{ + priv_t * p = (priv_t *)effp->priv; + + start(effp); + + if (effp->global_info->plot == sox_plot_octave) { + printf( + "%% GNU Octave file (may also work with MATLAB(R) )\n" + "Fs=%g;minF=10;maxF=Fs/2;\n" + "sweepF=logspace(log10(minF),log10(maxF),200);\n" + "[h,w]=freqz([%.15e %.15e %.15e],[1 %.15e %.15e],sweepF,Fs);\n" + "semilogx(w,20*log10(h))\n" + "title('SoX effect: %s gain=%g frequency=%g %s=%g (rate=%g)')\n" + "xlabel('Frequency (Hz)')\n" + "ylabel('Amplitude Response (dB)')\n" + "axis([minF maxF -35 25])\n" + "grid on\n" + "disp('Hit return to continue')\n" + "pause\n" + , effp->in_signal.rate, p->b0, p->b1, p->b2, p->a1, p->a2 + , effp->handler.name, p->gain, p->fc, width_str[p->width_type], p->width + , effp->in_signal.rate); + return SOX_EOF; + } + if (effp->global_info->plot == sox_plot_gnuplot) { + printf( + "# gnuplot file\n" + "set title 'SoX effect: %s gain=%g frequency=%g %s=%g (rate=%g)'\n" + "set xlabel 'Frequency (Hz)'\n" + "set ylabel 'Amplitude Response (dB)'\n" + "Fs=%g\n" + "b0=%.15e; b1=%.15e; b2=%.15e; a1=%.15e; a2=%.15e\n" + "o=2*pi/Fs\n" + "H(f)=sqrt((b0*b0+b1*b1+b2*b2+2.*(b0*b1+b1*b2)*cos(f*o)+2.*(b0*b2)*cos(2.*f*o))/(1.+a1*a1+a2*a2+2.*(a1+a1*a2)*cos(f*o)+2.*a2*cos(2.*f*o)))\n" + "set logscale x\n" + "set samples 250\n" + "set grid xtics ytics\n" + "set key off\n" + "plot [f=10:Fs/2] [-35:25] 20*log10(H(f))\n" + "pause -1 'Hit return to continue'\n" + , effp->handler.name, p->gain, p->fc, width_str[p->width_type], p->width + , effp->in_signal.rate, effp->in_signal.rate + , p->b0, p->b1, p->b2, p->a1, p->a2); + return SOX_EOF; + } + if (effp->global_info->plot == sox_plot_data) { + printf("# SoX effect: %s gain=%g frequency=%g %s=%g (rate=%g)\n" + "# IIR filter\n" + "# rate: %g\n" + "# name: b\n" + "# type: matrix\n" + "# rows: 3\n" + "# columns: 1\n" + "%24.16e\n%24.16e\n%24.16e\n" + "# name: a\n" + "# type: matrix\n" + "# rows: 3\n" + "# columns: 1\n" + "%24.16e\n%24.16e\n%24.16e\n" + , effp->handler.name, p->gain, p->fc, width_str[p->width_type], p->width + , effp->in_signal.rate, effp->in_signal.rate + , p->b0, p->b1, p->b2, 1. /* a0 */, p->a1, p->a2); + return SOX_EOF; + } + return SOX_SUCCESS; +} + + +int lsx_biquad_flow(sox_effect_t * effp, const sox_sample_t *ibuf, + sox_sample_t *obuf, size_t *isamp, size_t *osamp) +{ + priv_t * p = (priv_t *)effp->priv; + size_t len = *isamp = *osamp = min(*isamp, *osamp); + while (len--) { + double o0 = *ibuf*p->b0 + p->i1*p->b1 + p->i2*p->b2 - p->o1*p->a1 - p->o2*p->a2; + p->i2 = p->i1, p->i1 = *ibuf++; + p->o2 = p->o1, p->o1 = o0; + *obuf++ = SOX_ROUND_CLIP_COUNT(o0, effp->clips); + } + return SOX_SUCCESS; +} + +static int create(sox_effect_t * effp, int argc, char * * argv) +{ + priv_t * p = (priv_t *)effp->priv; + double * d = &p->b0; + char c; + + --argc, ++argv; + if (argc == 6) + for (; argc && sscanf(*argv, "%lf%c", d, &c) == 1; --argc, ++argv, ++d); + return argc? lsx_usage(effp) : SOX_SUCCESS; +} + +sox_effect_handler_t const * lsx_biquad_effect_fn(void) +{ + static sox_effect_handler_t handler = { + "biquad", "b0 b1 b2 a0 a1 a2", 0, + create, lsx_biquad_start, lsx_biquad_flow, NULL, NULL, NULL, sizeof(priv_t) + }; + return &handler; +} diff --git a/freedv/tags/1.2.2/src/sox/biquad.h b/freedv/tags/1.2.2/src/sox/biquad.h new file mode 100644 index 00000000..8786ac83 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/biquad.h @@ -0,0 +1,78 @@ +/* libSoX Biquad filter common definitions (c) 2006-7 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef biquad_included +#define biquad_included + +#define LSX_EFF_ALIAS +#include "sox_i.h" + +typedef enum { + filter_LPF, + filter_HPF, + filter_BPF_CSG, + filter_BPF, + filter_notch, + filter_APF, + filter_peakingEQ, + filter_lowShelf, + filter_highShelf, + filter_LPF_1, + filter_HPF_1, + filter_BPF_SPK, + filter_BPF_SPK_N, + filter_AP1, + filter_AP2, + filter_deemph, + filter_riaa +} filter_t; + +typedef enum { + width_bw_Hz, + width_bw_kHz, + /* The old, non-RBJ, non-freq-warped band-pass/reject response; + * leaving here for now just in case anybody misses it: */ + width_bw_old, + width_bw_oct, + width_Q, + width_slope +} width_t; + +/* Private data for the biquad filter effects */ +typedef struct { + double gain; /* For EQ filters */ + double fc; /* Centre/corner/cutoff frequency */ + double width; /* Filter width; interpreted as per width_type */ + width_t width_type; + + filter_t filter_type; + + double b0, b1, b2; /* Filter coefficients */ + double a0, a1, a2; /* Filter coefficients */ + + sox_sample_t i1, i2; /* Filter memory */ + double o1, o2; /* Filter memory */ +} biquad_t; + +int lsx_biquad_getopts(sox_effect_t * effp, int n, char **argv, + int min_args, int max_args, int fc_pos, int width_pos, int gain_pos, + char const * allowed_width_types, filter_t filter_type); +int lsx_biquad_start(sox_effect_t * effp); +int lsx_biquad_flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf, + size_t *isamp, size_t *osamp); + +#endif diff --git a/freedv/tags/1.2.2/src/sox/biquads.c b/freedv/tags/1.2.2/src/sox/biquads.c new file mode 100644 index 00000000..19793a6d --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/biquads.c @@ -0,0 +1,400 @@ +/* libSoX Biquad filter effects (c) 2006-8 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * + * 2-pole filters designed by Robert Bristow-Johnson + * see http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + * + * 1-pole filters based on code (c) 2000 Chris Bagwell + * Algorithms: Recursive single pole low/high pass filter + * Reference: The Scientist and Engineer's Guide to Digital Signal Processing + * + * low-pass: output[N] = input[N] * A + output[N-1] * B + * X = exp(-2.0 * pi * Fc) + * A = 1 - X + * B = X + * Fc = cutoff freq / sample rate + * + * Mimics an RC low-pass filter: + * + * ---/\/\/\/\-----------> + * | + * --- C + * --- + * | + * | + * V + * + * high-pass: output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1] + * X = exp(-2.0 * pi * Fc) + * A0 = (1 + X) / 2 + * A1 = -(1 + X) / 2 + * B1 = X + * Fc = cutoff freq / sample rate + * + * Mimics an RC high-pass filter: + * + * || C + * ----||---------> + * || | + * < + * > R + * < + * | + * V + */ + + +#include "biquad.h" +#include +#include + +typedef biquad_t priv_t; + + +static int hilo1_getopts(sox_effect_t * effp, int argc, char **argv) { + return lsx_biquad_getopts(effp, argc, argv, 1, 1, 0, 1, 2, "", + *effp->handler.name == 'l'? filter_LPF_1 : filter_HPF_1); +} + + +static int hilo2_getopts(sox_effect_t * effp, int argc, char **argv) { + priv_t * p = (priv_t *)effp->priv; + if (argc > 1 && strcmp(argv[1], "-1") == 0) + return hilo1_getopts(effp, argc - 1, argv + 1); + if (argc > 1 && strcmp(argv[1], "-2") == 0) + ++argv, --argc; + p->width = sqrt(0.5); /* Default to Butterworth */ + return lsx_biquad_getopts(effp, argc, argv, 1, 2, 0, 1, 2, "qohk", + *effp->handler.name == 'l'? filter_LPF : filter_HPF); +} + + +static int bandpass_getopts(sox_effect_t * effp, int argc, char **argv) { + filter_t type = filter_BPF; + if (argc > 1 && strcmp(argv[1], "-c") == 0) + ++argv, --argc, type = filter_BPF_CSG; + return lsx_biquad_getopts(effp, argc, argv, 2, 2, 0, 1, 2, "hkqob", type); +} + + +static int bandrej_getopts(sox_effect_t * effp, int argc, char **argv) { + return lsx_biquad_getopts(effp, argc, argv, 2, 2, 0, 1, 2, "hkqob", filter_notch); +} + + +static int allpass_getopts(sox_effect_t * effp, int argc, char **argv) { + filter_t type = filter_APF; + int m; + if (argc > 1 && strcmp(argv[1], "-1") == 0) + ++argv, --argc, type = filter_AP1; + else if (argc > 1 && strcmp(argv[1], "-2") == 0) + ++argv, --argc, type = filter_AP2; + m = 1 + (type == filter_APF); + return lsx_biquad_getopts(effp, argc, argv, m, m, 0, 1, 2, "hkqo", type); +} + + +static int tone_getopts(sox_effect_t * effp, int argc, char **argv) { + priv_t * p = (priv_t *)effp->priv; + p->width = 0.5; + p->fc = *effp->handler.name == 'b'? 100 : 3000; + return lsx_biquad_getopts(effp, argc, argv, 1, 3, 1, 2, 0, "shkqo", + *effp->handler.name == 'b'? filter_lowShelf: filter_highShelf); +} + + +static int equalizer_getopts(sox_effect_t * effp, int argc, char **argv) { + return lsx_biquad_getopts(effp, argc, argv, 3, 3, 0, 1, 2, "qohk", filter_peakingEQ); +} + + +static int band_getopts(sox_effect_t * effp, int argc, char **argv) { + filter_t type = filter_BPF_SPK; + if (argc > 1 && strcmp(argv[1], "-n") == 0) + ++argv, --argc, type = filter_BPF_SPK_N; + return lsx_biquad_getopts(effp, argc, argv, 1, 2, 0, 1, 2, "hkqo", type); +} + + +static int deemph_getopts(sox_effect_t * effp, int argc, char **argv) { + priv_t * p = (priv_t *)effp->priv; + p->fc = 5283; + p->width = 0.4845; + p->gain = -9.477; + return lsx_biquad_getopts(effp, argc, argv, 0, 0, 0, 1, 2, "s", filter_deemph); +} + + +static int riaa_getopts(sox_effect_t * effp, int argc, char **argv) { + priv_t * p = (priv_t *)effp->priv; + p->filter_type = filter_riaa; + (void)argv; + return --argc? lsx_usage(effp) : SOX_SUCCESS; +} + + +static void make_poly_from_roots( + double const * roots, size_t num_roots, double * poly) +{ + size_t i, j; + poly[0] = 1; + poly[1] = -roots[0]; + memset(poly + 2, 0, (num_roots + 1 - 2) * sizeof(*poly)); + for (i = 1; i < num_roots; ++i) + for (j = num_roots; j > 0; --j) + poly[j] -= poly[j - 1] * roots[i]; +} + +static int start(sox_effect_t * effp) +{ + priv_t * p = (priv_t *)effp->priv; + double w0 = 2 * M_PI * p->fc / effp->in_signal.rate; + double A = exp(p->gain / 40 * log(10.)); + double alpha = 0, mult = dB_to_linear(max(p->gain, 0)); + + if (w0 > M_PI) { + lsx_fail("frequency must be less than half the sample-rate (Nyquist rate)"); + return SOX_EOF; + } + + /* Set defaults: */ + p->b0 = p->b1 = p->b2 = p->a1 = p->a2 = 0; + p->a0 = 1; + + if (p->width) switch (p->width_type) { + case width_slope: + alpha = sin(w0)/2 * sqrt((A + 1/A)*(1/p->width - 1) + 2); + break; + + case width_Q: + alpha = sin(w0)/(2*p->width); + break; + + case width_bw_oct: + alpha = sin(w0)*sinh(log(2.)/2 * p->width * w0/sin(w0)); + break; + + case width_bw_Hz: + alpha = sin(w0)/(2*p->fc/p->width); + break; + + case width_bw_kHz: assert(0); /* Shouldn't get here */ + + case width_bw_old: + alpha = tan(M_PI * p->width / effp->in_signal.rate); + break; + } + switch (p->filter_type) { + case filter_LPF: /* H(s) = 1 / (s^2 + s/Q + 1) */ + p->b0 = (1 - cos(w0))/2; + p->b1 = 1 - cos(w0); + p->b2 = (1 - cos(w0))/2; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_HPF: /* H(s) = s^2 / (s^2 + s/Q + 1) */ + p->b0 = (1 + cos(w0))/2; + p->b1 = -(1 + cos(w0)); + p->b2 = (1 + cos(w0))/2; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_BPF_CSG: /* H(s) = s / (s^2 + s/Q + 1) (constant skirt gain, peak gain = Q) */ + p->b0 = sin(w0)/2; + p->b1 = 0; + p->b2 = -sin(w0)/2; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_BPF: /* H(s) = (s/Q) / (s^2 + s/Q + 1) (constant 0 dB peak gain) */ + p->b0 = alpha; + p->b1 = 0; + p->b2 = -alpha; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_notch: /* H(s) = (s^2 + 1) / (s^2 + s/Q + 1) */ + p->b0 = 1; + p->b1 = -2*cos(w0); + p->b2 = 1; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_APF: /* H(s) = (s^2 - s/Q + 1) / (s^2 + s/Q + 1) */ + p->b0 = 1 - alpha; + p->b1 = -2*cos(w0); + p->b2 = 1 + alpha; + p->a0 = 1 + alpha; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha; + break; + + case filter_peakingEQ: /* H(s) = (s^2 + s*(A/Q) + 1) / (s^2 + s/(A*Q) + 1) */ + if (A == 1) + return SOX_EFF_NULL; + p->b0 = 1 + alpha*A; + p->b1 = -2*cos(w0); + p->b2 = 1 - alpha*A; + p->a0 = 1 + alpha/A; + p->a1 = -2*cos(w0); + p->a2 = 1 - alpha/A; + break; + + case filter_lowShelf: /* H(s) = A * (s^2 + (sqrt(A)/Q)*s + A)/(A*s^2 + (sqrt(A)/Q)*s + 1) */ + if (A == 1) + return SOX_EFF_NULL; + p->b0 = A*( (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha ); + p->b1 = 2*A*( (A-1) - (A+1)*cos(w0) ); + p->b2 = A*( (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha ); + p->a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha; + p->a1 = -2*( (A-1) + (A+1)*cos(w0) ); + p->a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha; + break; + + case filter_deemph: /* See deemph.plt for documentation */ + if (effp->in_signal.rate != 44100) { + lsx_fail("Sample rate must be 44100 (audio-CD)"); + return SOX_EOF; + } + /* Falls through... */ + + case filter_highShelf: /* H(s) = A * (A*s^2 + (sqrt(A)/Q)*s + 1)/(s^2 + (sqrt(A)/Q)*s + A) */ + if (!A) + return SOX_EFF_NULL; + p->b0 = A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ); + p->b1 = -2*A*( (A-1) + (A+1)*cos(w0) ); + p->b2 = A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ); + p->a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha; + p->a1 = 2*( (A-1) - (A+1)*cos(w0) ); + p->a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha; + break; + + case filter_LPF_1: /* single-pole */ + p->a1 = -exp(-w0); + p->b0 = 1 + p->a1; + break; + + case filter_HPF_1: /* single-pole */ + p->a1 = -exp(-w0); + p->b0 = (1 - p->a1)/2; + p->b1 = -p->b0; + break; + + case filter_BPF_SPK: case filter_BPF_SPK_N: { + double bw_Hz; + if (!p->width) + p->width = p->fc / 2; + bw_Hz = p->width_type == width_Q? p->fc / p->width : + p->width_type == width_bw_Hz? p->width : + p->fc * (pow(2., p->width) - 1) * pow(2., -0.5 * p->width); /* bw_oct */ + #include "band.h" /* Has different licence */ + break; + } + + case filter_AP1: /* Experimental 1-pole all-pass from Tom Erbe @ UCSD */ + p->b0 = exp(-w0); + p->b1 = -1; + p->a1 = -exp(-w0); + break; + + case filter_AP2: /* Experimental 2-pole all-pass from Tom Erbe @ UCSD */ + p->b0 = 1 - sin(w0); + p->b1 = -2 * cos(w0); + p->b2 = 1 + sin(w0); + p->a0 = 1 + sin(w0); + p->a1 = -2 * cos(w0); + p->a2 = 1 - sin(w0); + break; + + case filter_riaa: /* http://www.dsprelated.com/showmessage/73300/3.php */ + if (effp->in_signal.rate == 44100) { + static const double zeros[] = {-0.2014898, 0.9233820}; + static const double poles[] = {0.7083149, 0.9924091}; + make_poly_from_roots(zeros, (size_t)2, &p->b0); + make_poly_from_roots(poles, (size_t)2, &p->a0); + } + else if (effp->in_signal.rate == 48000) { + static const double zeros[] = {-0.1766069, 0.9321590}; + static const double poles[] = {0.7396325, 0.9931330}; + make_poly_from_roots(zeros, (size_t)2, &p->b0); + make_poly_from_roots(poles, (size_t)2, &p->a0); + } + else if (effp->in_signal.rate == 88200) { + static const double zeros[] = {-0.1168735, 0.9648312}; + static const double poles[] = {0.8590646, 0.9964002}; + make_poly_from_roots(zeros, (size_t)2, &p->b0); + make_poly_from_roots(poles, (size_t)2, &p->a0); + } + else if (effp->in_signal.rate == 96000) { + static const double zeros[] = {-0.1141486, 0.9676817}; + static const double poles[] = {0.8699137, 0.9966946}; + make_poly_from_roots(zeros, (size_t)2, &p->b0); + make_poly_from_roots(poles, (size_t)2, &p->a0); + } + else { + lsx_fail("Sample rate must be 44.1k, 48k, 88.2k, or 96k"); + return SOX_EOF; + } + { /* Normalise to 0dB at 1kHz (Thanks to Glenn Davis) */ + double y = 2 * M_PI * 1000 / effp->in_signal.rate; + double b_re = p->b0 + p->b1 * cos(-y) + p->b2 * cos(-2 * y); + double a_re = p->a0 + p->a1 * cos(-y) + p->a2 * cos(-2 * y); + double b_im = p->b1 * sin(-y) + p->b2 * sin(-2 * y); + double a_im = p->a1 * sin(-y) + p->a2 * sin(-2 * y); + double g = 1 / sqrt((sqr(b_re) + sqr(b_im)) / (sqr(a_re) + sqr(a_im))); + p->b0 *= g; p->b1 *= g; p->b2 *= g; + } + mult = (p->b0 + p->b1 + p->b2) / (p->a0 + p->a1 + p->a2); + lsx_debug("gain=%f", linear_to_dB(mult)); + break; + } + if (effp->in_signal.mult) + *effp->in_signal.mult /= mult; + return lsx_biquad_start(effp); +} + + +#define BIQUAD_EFFECT(name,group,usage,flags) \ +sox_effect_handler_t const * lsx_##name##_effect_fn(void) { \ + static sox_effect_handler_t handler = { \ + #name, usage, flags, \ + group##_getopts, start, lsx_biquad_flow, 0, 0, 0, sizeof(biquad_t)\ + }; \ + return &handler; \ +} + +BIQUAD_EFFECT(highpass, hilo2, "[-1|-2] frequency [width[q|o|h|k](0.707q)]", 0) +BIQUAD_EFFECT(lowpass, hilo2, "[-1|-2] frequency [width[q|o|h|k]](0.707q)", 0) +BIQUAD_EFFECT(bandpass, bandpass, "[-c] frequency width[h|k|q|o]", 0) +BIQUAD_EFFECT(bandreject,bandrej, "frequency width[h|k|q|o]", 0) +BIQUAD_EFFECT(allpass, allpass, "frequency width[h|k|q|o]", 0) +BIQUAD_EFFECT(bass, tone, "gain [frequency(100) [width[s|h|k|q|o]](0.5s)]", 0) +BIQUAD_EFFECT(treble, tone, "gain [frequency(3000) [width[s|h|k|q|o]](0.5s)]", 0) +BIQUAD_EFFECT(equalizer, equalizer,"frequency width[q|o|h|k] gain", 0) +BIQUAD_EFFECT(band, band, "[-n] center [width[h|k|q|o]]", 0) +BIQUAD_EFFECT(deemph, deemph, NULL, 0) +BIQUAD_EFFECT(riaa, riaa, NULL, 0) diff --git a/freedv/tags/1.2.2/src/sox/effects.c b/freedv/tags/1.2.2/src/sox/effects.c new file mode 100644 index 00000000..435412fa --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/effects.c @@ -0,0 +1,544 @@ +/* SoX Effects chain (c) 2007 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define LSX_EFF_ALIAS +#include "sox_i.h" +#include +#include +#ifdef HAVE_STRINGS_H + #include +#endif + +#define DEBUG_EFFECTS_CHAIN 0 + +/* Default effect handler functions for do-nothing situations: */ + +static int default_function(sox_effect_t * effp UNUSED) +{ + return SOX_SUCCESS; +} + +/* Pass through samples verbatim */ +int lsx_flow_copy(sox_effect_t * effp UNUSED, const sox_sample_t * ibuf, + sox_sample_t * obuf, size_t * isamp, size_t * osamp) +{ + *isamp = *osamp = min(*isamp, *osamp); + memcpy(obuf, ibuf, *isamp * sizeof(*obuf)); + return SOX_SUCCESS; +} + +/* Inform no more samples to drain */ +static int default_drain(sox_effect_t * effp UNUSED, sox_sample_t *obuf UNUSED, size_t *osamp) +{ + *osamp = 0; + return SOX_EOF; +} + +/* Check that no parameters have been given */ +static int default_getopts(sox_effect_t * effp, int argc, char **argv UNUSED) +{ + return --argc? lsx_usage(effp) : SOX_SUCCESS; +} + +/* Partially initialise the effect structure; signal info will come later */ +sox_effect_t * sox_create_effect(sox_effect_handler_t const * eh) +{ + sox_effect_t * effp = lsx_calloc(1, sizeof(*effp)); + effp->obuf = NULL; + + effp->global_info = sox_get_effects_globals(); + effp->handler = *eh; + if (!effp->handler.getopts) effp->handler.getopts = default_getopts; + if (!effp->handler.start ) effp->handler.start = default_function; + if (!effp->handler.flow ) effp->handler.flow = lsx_flow_copy; + if (!effp->handler.drain ) effp->handler.drain = default_drain; + if (!effp->handler.stop ) effp->handler.stop = default_function; + if (!effp->handler.kill ) effp->handler.kill = default_function; + + effp->priv = lsx_calloc(1, effp->handler.priv_size); + + return effp; +} /* sox_create_effect */ + +int sox_effect_options(sox_effect_t *effp, int argc, char * const argv[]) +{ + int result; + + char * * argv2 = lsx_malloc((argc + 1) * sizeof(*argv2)); + argv2[0] = (char *)effp->handler.name; + memcpy(argv2 + 1, argv, argc * sizeof(*argv2)); + result = effp->handler.getopts(effp, argc + 1, argv2); + free(argv2); + return result; +} /* sox_effect_options */ + +/* Effects chain: */ + +sox_effects_chain_t * sox_create_effects_chain( + sox_encodinginfo_t const * in_enc, sox_encodinginfo_t const * out_enc) +{ + sox_effects_chain_t * result = lsx_calloc(1, sizeof(sox_effects_chain_t)); + result->global_info = *sox_get_effects_globals(); + result->in_enc = in_enc; + result->out_enc = out_enc; + return result; +} /* sox_create_effects_chain */ + +void sox_delete_effects_chain(sox_effects_chain_t *ecp) +{ + if (ecp && ecp->length) + sox_delete_effects(ecp); + free(ecp->effects); + free(ecp); +} /* sox_delete_effects_chain */ + +/* Effect can call in start() or flow() to set minimum input size to flow() */ +int lsx_effect_set_imin(sox_effect_t * effp, size_t imin) +{ + if (imin > sox_globals.bufsiz / effp->flows) { + lsx_fail("sox_bufsiz not big enough"); + return SOX_EOF; + } + + effp->imin = imin; + return SOX_SUCCESS; +} + +/* Effects table to be extended in steps of EFF_TABLE_STEP */ +#define EFF_TABLE_STEP 8 + +/* Add an effect to the chain. *in is the input signal for this effect. *out is + * a suggestion as to what the output signal should be, but depending on its + * given options and *in, the effect can choose to do differently. Whatever + * output rate and channels the effect does produce are written back to *in, + * ready for the next effect in the chain. + */ +int sox_add_effect(sox_effects_chain_t * chain, sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t const * out) +{ + int ret, (*start)(sox_effect_t * effp) = effp->handler.start; + unsigned f; + sox_effect_t eff0; /* Copy of effect for flow 0 before calling start */ + + effp->global_info = &chain->global_info; + effp->in_signal = *in; + effp->out_signal = *out; + effp->in_encoding = chain->in_enc; + effp->out_encoding = chain->out_enc; + if (!(effp->handler.flags & SOX_EFF_CHAN)) + effp->out_signal.channels = in->channels; + if (!(effp->handler.flags & SOX_EFF_RATE)) + effp->out_signal.rate = in->rate; + if (!(effp->handler.flags & SOX_EFF_PREC)) + effp->out_signal.precision = (effp->handler.flags & SOX_EFF_MODIFY)? + in->precision : SOX_SAMPLE_PRECISION; + if (!(effp->handler.flags & SOX_EFF_GAIN)) + effp->out_signal.mult = in->mult; + + effp->flows = + (effp->handler.flags & SOX_EFF_MCHAN)? 1 : effp->in_signal.channels; + effp->clips = 0; + effp->imin = 0; + eff0 = *effp, eff0.priv = lsx_memdup(eff0.priv, eff0.handler.priv_size); + eff0.in_signal.mult = NULL; /* Only used in channel 0 */ + ret = start(effp); + if (ret == SOX_EFF_NULL) { + lsx_report("has no effect in this configuration"); + free(eff0.priv); + free(effp->priv); + effp->priv = NULL; + return SOX_SUCCESS; + } + if (ret != SOX_SUCCESS) { + free(eff0.priv); + return SOX_EOF; + } + if (in->mult) + lsx_debug("mult=%g", *in->mult); + + if (!(effp->handler.flags & SOX_EFF_LENGTH)) { + effp->out_signal.length = in->length; + if (effp->out_signal.length != SOX_UNKNOWN_LEN) { + if (effp->handler.flags & SOX_EFF_CHAN) + effp->out_signal.length = + effp->out_signal.length / in->channels * effp->out_signal.channels; + if (effp->handler.flags & SOX_EFF_RATE) + effp->out_signal.length = + effp->out_signal.length / in->rate * effp->out_signal.rate + .5; + } + } + + *in = effp->out_signal; + + if (chain->length == chain->table_size) { + chain->table_size += EFF_TABLE_STEP; + lsx_debug_more("sox_add_effect: extending effects table, " + "new size = %lu", (unsigned long)chain->table_size); + lsx_revalloc(chain->effects, chain->table_size); + } + + chain->effects[chain->length] = + lsx_calloc(effp->flows, sizeof(chain->effects[chain->length][0])); + chain->effects[chain->length][0] = *effp; + + for (f = 1; f < effp->flows; ++f) { + chain->effects[chain->length][f] = eff0; + chain->effects[chain->length][f].flow = f; + chain->effects[chain->length][f].priv = lsx_memdup(eff0.priv, eff0.handler.priv_size); + if (start(&chain->effects[chain->length][f]) != SOX_SUCCESS) { + free(eff0.priv); + return SOX_EOF; + } + } + + ++chain->length; + free(eff0.priv); + return SOX_SUCCESS; +} + +static int flow_effect(sox_effects_chain_t * chain, size_t n) +{ + sox_effect_t * effp1 = &chain->effects[n - 1][0]; + sox_effect_t * effp = &chain->effects[n][0]; + int effstatus = SOX_SUCCESS, f = 0; + size_t i; + const sox_sample_t *ibuf; + size_t idone = effp1->oend - effp1->obeg; + size_t obeg = sox_globals.bufsiz - effp->oend; +#if DEBUG_EFFECTS_CHAIN + size_t pre_idone = idone; + size_t pre_odone = obeg; +#endif + + if (effp->flows == 1) { /* Run effect on all channels at once */ + idone -= idone % effp->in_signal.channels; + effstatus = effp->handler.flow(effp, &effp1->obuf[effp1->obeg], + &effp->obuf[effp->oend], &idone, &obeg); + if (obeg % effp->out_signal.channels != 0) { + lsx_fail("multi-channel effect flowed asymmetrically!"); + effstatus = SOX_EOF; + } + } else { /* Run effect on each channel individually */ + sox_sample_t *obuf = &effp->obuf[effp->oend]; + size_t idone_last = 0, odone_last = 0; /* Initialised to prevent warning */ + + ibuf = &effp1->obuf[effp1->obeg]; + for (i = 0; i < idone; i += effp->flows) + for (f = 0; f < (int)effp->flows; ++f) + chain->ibufc[f][i / effp->flows] = *ibuf++; + +#ifdef HAVE_OPENMP + if (sox_globals.use_threads && effp->flows > 1) + { + #pragma omp parallel for + for (f = 0; f < (int)effp->flows; ++f) { + size_t idonec = idone / effp->flows; + size_t odonec = obeg / effp->flows; + int eff_status_c = effp->handler.flow(&chain->effects[n][f], + chain->ibufc[f], chain->obufc[f], &idonec, &odonec); + if (!f) { + idone_last = idonec; + odone_last = odonec; + } + + if (eff_status_c != SOX_SUCCESS) + effstatus = SOX_EOF; + } + } + else /* sox_globals.use_threads */ +#endif + { + for (f = 0; f < (int)effp->flows; ++f) { + size_t idonec = idone / effp->flows; + size_t odonec = obeg / effp->flows; + int eff_status_c = effp->handler.flow(&chain->effects[n][f], + chain->ibufc[f], chain->obufc[f], &idonec, &odonec); + if (f && (idonec != idone_last || odonec != odone_last)) { + lsx_fail("flowed asymmetrically!"); + effstatus = SOX_EOF; + } + idone_last = idonec; + odone_last = odonec; + + if (eff_status_c != SOX_SUCCESS) + effstatus = SOX_EOF; + } + } + + for (i = 0; i < odone_last; ++i) + for (f = 0; f < (int)effp->flows; ++f) + *obuf++ = chain->obufc[f][i]; + + idone = effp->flows * idone_last; + obeg = effp->flows * odone_last; + } +#if DEBUG_EFFECTS_CHAIN + lsx_report("flow: %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR, + pre_idone, pre_odone, idone, obeg); +#endif + effp1->obeg += idone; + if (effp1->obeg == effp1->oend) + effp1->obeg = effp1->oend = 0; + else if (effp1->oend - effp1->obeg < effp->imin ) { /* Need to refill? */ + memmove(effp1->obuf, &effp1->obuf[effp1->obeg], (effp1->oend - effp1->obeg) * sizeof(*effp1->obuf)); + effp1->oend -= effp1->obeg; + effp1->obeg = 0; + } + + effp->oend += obeg; + + return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF; +} + +/* The same as flow_effect but with no input */ +static int drain_effect(sox_effects_chain_t * chain, size_t n) +{ + sox_effect_t * effp = &chain->effects[n][0]; + int effstatus = SOX_SUCCESS; + size_t i, f; + size_t obeg = sox_globals.bufsiz - effp->oend; +#if DEBUG_EFFECTS_CHAIN + size_t pre_odone = obeg; +#endif + + if (effp->flows == 1) { /* Run effect on all channels at once */ + effstatus = effp->handler.drain(effp, &effp->obuf[effp->oend], &obeg); + if (obeg % effp->out_signal.channels != 0) { + lsx_fail("multi-channel effect drained asymmetrically!"); + effstatus = SOX_EOF; + } + } else { /* Run effect on each channel individually */ + sox_sample_t *obuf = &effp->obuf[effp->oend]; + size_t odone_last = 0; /* Initialised to prevent warning */ + + for (f = 0; f < effp->flows; ++f) { + size_t odonec = obeg / effp->flows; + int eff_status_c = effp->handler.drain(&chain->effects[n][f], chain->obufc[f], &odonec); + if (f && (odonec != odone_last)) { + lsx_fail("drained asymmetrically!"); + effstatus = SOX_EOF; + } + odone_last = odonec; + + if (eff_status_c != SOX_SUCCESS) + effstatus = SOX_EOF; + } + + for (i = 0; i < odone_last; ++i) + for (f = 0; f < effp->flows; ++f) + *obuf++ = chain->obufc[f][i]; + obeg = f * odone_last; + } +#if DEBUG_EFFECTS_CHAIN + lsx_report("drain: %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR, + (size_t)0, pre_odone, (size_t)0, obeg); +#endif + if (!obeg) /* This is the only thing that drain has and flow hasn't */ + effstatus = SOX_EOF; + + effp->oend += obeg; + + return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF; +} + +/* Flow data through the effects chain until an effect or callback gives EOF */ +int sox_flow_effects(sox_effects_chain_t * chain, int (* callback)(sox_bool all_done, void * client_data), void * client_data) +{ + int flow_status = SOX_SUCCESS; + size_t e, source_e = 0; /* effect indices */ + size_t f, max_flows = 0; + sox_bool draining = sox_true; + + for (e = 0; e < chain->length; ++e) { + chain->effects[e][0].obuf = lsx_realloc(chain->effects[e][0].obuf, + sox_globals.bufsiz * sizeof(chain->effects[e][0].obuf[0])); + /* Possibly there is already a buffer, if this is a used effect; + it may still contain samples in that case. */ + /* Memory will be freed by sox_delete_effect() later. */ + max_flows = max(max_flows, chain->effects[e][0].flows); + } + if (max_flows == 1) /* don't need interleave buffers */ + max_flows = 0; + chain->ibufc = lsx_calloc(max_flows, sizeof(*chain->ibufc)); + chain->obufc = lsx_calloc(max_flows, sizeof(*chain->obufc)); + for (f = 0; f < max_flows; ++f) { + chain->ibufc[f] = lsx_calloc(sox_globals.bufsiz / 2, sizeof(chain->ibufc[f][0])); + chain->obufc[f] = lsx_calloc(sox_globals.bufsiz / 2, sizeof(chain->obufc[f][0])); + } + + e = chain->length - 1; + while (source_e < chain->length) { +#define have_imin (e > 0 && e < chain->length && chain->effects[e - 1][0].oend - chain->effects[e - 1][0].obeg >= chain->effects[e][0].imin) + size_t osize = chain->effects[e][0].oend - chain->effects[e][0].obeg; + if (e == source_e && (draining || !have_imin)) { + if (drain_effect(chain, e) == SOX_EOF) { + ++source_e; + draining = sox_false; + } + } else if (have_imin && flow_effect(chain, e) == SOX_EOF) { + flow_status = SOX_EOF; + if (e == chain->length - 1) + break; + source_e = e; + draining = sox_true; + } + if (e < chain->length && chain->effects[e][0].oend - chain->effects[e][0].obeg > osize) /* False for output */ + ++e; + else if (e == source_e) + draining = sox_true; + else if ((int)--e < (int)source_e) + e = source_e; + + if (callback && callback(source_e == chain->length, client_data) != SOX_SUCCESS) { + flow_status = SOX_EOF; /* Client has requested to stop the flow. */ + break; + } + } + + for (f = 0; f < max_flows; ++f) { + free(chain->ibufc[f]); + free(chain->obufc[f]); + } + free(chain->obufc); + free(chain->ibufc); + + return flow_status; +} + +sox_uint64_t sox_effects_clips(sox_effects_chain_t * chain) +{ + unsigned i, f; + uint64_t clips = 0; + for (i = 1; i < chain->length - 1; ++i) + for (f = 0; f < chain->effects[i][0].flows; ++f) + clips += chain->effects[i][f].clips; + return clips; +} + +sox_uint64_t sox_stop_effect(sox_effect_t *effp) +{ + unsigned f; + uint64_t clips = 0; + + for (f = 0; f < effp->flows; ++f) { + effp[f].handler.stop(&effp[f]); + clips += effp[f].clips; + } + return clips; +} + +void sox_push_effect_last(sox_effects_chain_t *chain, sox_effect_t *effp) +{ + if (chain->length == chain->table_size) { + chain->table_size += EFF_TABLE_STEP; + lsx_debug_more("sox_push_effect_last: extending effects table, " + "new size = %lu", (unsigned long)chain->table_size); + lsx_revalloc(chain->effects, chain->table_size); + } + + chain->effects[chain->length++] = effp; +} /* sox_push_effect_last */ + +sox_effect_t *sox_pop_effect_last(sox_effects_chain_t *chain) +{ + if (chain->length > 0) + { + sox_effect_t *effp; + chain->length--; + effp = chain->effects[chain->length]; + chain->effects[chain->length] = NULL; + return effp; + } + else + return NULL; +} /* sox_pop_effect_last */ + +/* Free resources related to effect. + * Note: This currently closes down the effect which might + * not be obvious from name. + */ +void sox_delete_effect(sox_effect_t *effp) +{ + uint64_t clips; + unsigned f; + + if ((clips = sox_stop_effect(effp)) != 0) + lsx_warn("%s clipped %" PRIu64 " samples; decrease volume?", + effp->handler.name, clips); + if (effp->obeg != effp->oend) + lsx_debug("output buffer still held %" PRIuPTR " samples; dropped.", + (effp->oend - effp->obeg)/effp->out_signal.channels); + /* May or may not indicate a problem; it is normal if the user aborted + processing, or if an effect like "trim" stopped early. */ + effp->handler.kill(effp); /* N.B. only one kill; not one per flow */ + for (f = 0; f < effp->flows; ++f) + free(effp[f].priv); + free(effp->obuf); + free(effp); +} + +void sox_delete_effect_last(sox_effects_chain_t *chain) +{ + if (chain->length > 0) + { + chain->length--; + sox_delete_effect(chain->effects[chain->length]); + chain->effects[chain->length] = NULL; + } +} /* sox_delete_effect_last */ + +/* Remove all effects from the chain. + * Note: This currently closes down the effect which might + * not be obvious from name. + */ +void sox_delete_effects(sox_effects_chain_t * chain) +{ + size_t e; + + for (e = 0; e < chain->length; ++e) { + sox_delete_effect(chain->effects[e]); + chain->effects[e] = NULL; + } + chain->length = 0; +} + +/*----------------------------- Effects library ------------------------------*/ + +static sox_effect_fn_t s_sox_effect_fns[] = { +#define EFFECT(f) lsx_##f##_effect_fn, +#include "effects.h" +#undef EFFECT + NULL +}; + +const sox_effect_fn_t* +sox_get_effect_fns(void) +{ + return s_sox_effect_fns; +} + +/* Find a named effect in the effects library */ +sox_effect_handler_t const * sox_find_effect(char const * name) +{ + int e; + sox_effect_fn_t const * fns = sox_get_effect_fns(); + for (e = 0; fns[e]; ++e) { + const sox_effect_handler_t *eh = fns[e] (); + if (eh && eh->name && strcasecmp(eh->name, name) == 0) + return eh; /* Found it. */ + } + return NULL; +} diff --git a/freedv/tags/1.2.2/src/sox/effects.h b/freedv/tags/1.2.2/src/sox/effects.h new file mode 100644 index 00000000..8d7025c8 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/effects.h @@ -0,0 +1,22 @@ +/* This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Manually edited for FreeDV to contain just the effects we need */ + + EFFECT(bass) + EFFECT(highpass) + EFFECT(treble) + EFFECT(equalizer) + diff --git a/freedv/tags/1.2.2/src/sox/effects_i.c b/freedv/tags/1.2.2/src/sox/effects_i.c new file mode 100644 index 00000000..e5770a94 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/effects_i.c @@ -0,0 +1,379 @@ +/* Implements a libSoX internal interface for implementing effects. + * All public functions & data are prefixed with lsx_ . + * + * Copyright (c) 2005-8 Chris Bagwell and SoX contributors + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define LSX_EFF_ALIAS +#include "sox_i.h" +#include +#include + +int lsx_usage(sox_effect_t * effp) +{ + if (effp->handler.usage) + lsx_fail("usage: %s", effp->handler.usage); + else + lsx_fail("this effect takes no parameters"); + return SOX_EOF; +} + +char * lsx_usage_lines(char * * usage, char const * const * lines, size_t n) +{ + if (!*usage) { + size_t i, len; + for (len = i = 0; i < n; len += strlen(lines[i++]) + 1); + *usage = lsx_malloc(len); /* FIXME: this memory will never be freed */ + strcpy(*usage, lines[0]); + for (i = 1; i < n; ++i) { + strcat(*usage, "\n"); + strcat(*usage, lines[i]); + } + } + return *usage; +} + +static lsx_enum_item const s_lsx_wave_enum[] = { + LSX_ENUM_ITEM(SOX_WAVE_,SINE) + LSX_ENUM_ITEM(SOX_WAVE_,TRIANGLE) + {0, 0}}; + +lsx_enum_item const * lsx_get_wave_enum(void) +{ + return s_lsx_wave_enum; +} + +void lsx_generate_wave_table( + lsx_wave_t wave_type, + sox_data_t data_type, + void *table, + size_t table_size, + double min, + double max, + double phase) +{ + uint32_t t; + uint32_t phase_offset = phase / M_PI / 2 * table_size + 0.5; + + for (t = 0; t < table_size; t++) + { + uint32_t point = (t + phase_offset) % table_size; + double d; + switch (wave_type) + { + case SOX_WAVE_SINE: + d = (sin((double)point / table_size * 2 * M_PI) + 1) / 2; + break; + + case SOX_WAVE_TRIANGLE: + d = (double)point * 2 / table_size; + switch (4 * point / table_size) + { + case 0: d = d + 0.5; break; + case 1: case 2: d = 1.5 - d; break; + case 3: d = d - 1.5; break; + } + break; + + default: /* Oops! FIXME */ + d = 0.0; /* Make sure we have a value */ + break; + } + d = d * (max - min) + min; + switch (data_type) + { + case SOX_FLOAT: + { + float *fp = (float *)table; + *fp++ = (float)d; + table = fp; + continue; + } + case SOX_DOUBLE: + { + double *dp = (double *)table; + *dp++ = d; + table = dp; + continue; + } + default: break; + } + d += d < 0? -0.5 : +0.5; + switch (data_type) + { + case SOX_SHORT: + { + short *sp = table; + *sp++ = (short)d; + table = sp; + continue; + } + case SOX_INT: + { + int *ip = table; + *ip++ = (int)d; + table = ip; + continue; + } + default: break; + } + } +} + +/* + * lsx_parsesamples + * + * Parse a string for # of samples. If string ends with a 's' + * then the string is interpreted as a user calculated # of samples. + * If string contains ':' or '.' or if it ends with a 't' then its + * treated as an amount of time. This is converted into seconds and + * fraction of seconds and then use the sample rate to calculate + * # of samples. + * Returns NULL on error, pointer to next char to parse otherwise. + */ +char const * lsx_parsesamples(sox_rate_t rate, const char *str0, uint64_t *samples, int def) +{ + int i, found_samples = 0, found_time = 0; + char const * end; + char const * pos; + sox_bool found_colon, found_dot; + char * str = (char *)str0; + + for (;*str == ' '; ++str); + for (end = str; *end && strchr("0123456789:.ets", *end); ++end); + if (end == str) + return NULL; + + pos = strchr(str, ':'); + found_colon = pos && pos < end; + + pos = strchr(str, '.'); + found_dot = pos && pos < end; + + if (found_colon || found_dot || *(end-1) == 't') + found_time = 1; + else if (*(end-1) == 's') + found_samples = 1; + + if (found_time || (def == 't' && !found_samples)) { + for (*samples = 0, i = 0; *str != '.' && i < 3; ++i) { + char * last_str = str; + long part = strtol(str, &str, 10); + if (!i && str == last_str) + return NULL; + *samples += rate * part; + if (i < 2) { + if (*str != ':') + break; + ++str; + *samples *= 60; + } + } + if (*str == '.') { + char * last_str = str; + double part = strtod(str, &str); + if (str == last_str) + return NULL; + *samples += rate * part + .5; + } + return *str == 't'? str + 1 : str; + } + { + char * last_str = str; + double part = strtod(str, &str); + if (str == last_str) + return NULL; + *samples = part + .5; + return *str == 's'? str + 1 : str; + } +} + +#if 0 + +#include + +#define TEST(st, samp, len) \ + str = st; \ + next = lsx_parsesamples(10000, str, &samples, 't'); \ + assert(samples == samp && next == str + len); + +int main(int argc, char * * argv) +{ + char const * str, * next; + uint64_t samples; + + TEST("0" , 0, 1) + TEST("1" , 10000, 1) + + TEST("0s" , 0, 2) + TEST("0s,", 0, 2) + TEST("0s/", 0, 2) + TEST("0s@", 0, 2) + + TEST("0t" , 0, 2) + TEST("0t,", 0, 2) + TEST("0t/", 0, 2) + TEST("0t@", 0, 2) + + TEST("1s" , 1, 2) + TEST("1s,", 1, 2) + TEST("1s/", 1, 2) + TEST("1s@", 1, 2) + TEST(" 01s" , 1, 4) + TEST("1e6s" , 1000000, 4) + + TEST("1t" , 10000, 2) + TEST("1t,", 10000, 2) + TEST("1t/", 10000, 2) + TEST("1t@", 10000, 2) + TEST("1.1t" , 11000, 4) + TEST("1.1t,", 11000, 4) + TEST("1.1t/", 11000, 4) + TEST("1.1t@", 11000, 4) + TEST("1e6t" , 10000, 1) + + TEST(".0", 0, 2) + TEST("0.0", 0, 3) + TEST("0:0.0", 0, 5) + TEST("0:0:0.0", 0, 7) + + TEST(".1", 1000, 2) + TEST(".10", 1000, 3) + TEST("0.1", 1000, 3) + TEST("1.1", 11000, 3) + TEST("1:1.1", 611000, 5) + TEST("1:1:1.1", 36611000, 7) + TEST("1:1", 610000, 3) + TEST("1:01", 610000, 4) + TEST("1:1:1", 36610000, 5) + TEST("1:", 600000, 2) + TEST("1::", 36000000, 3) + + TEST("0.444444", 4444, 8) + TEST("0.555555", 5556, 8) + + assert(!lsx_parsesamples(10000, "x", &samples, 't')); + return 0; +} +#endif + +/* a note is given as an int, + * 0 => 440 Hz = A + * >0 => number of half notes 'up', + * <0 => number of half notes down, + * example 12 => A of next octave, 880Hz + * + * calculated by freq = 440Hz * 2**(note/12) + */ +static double calc_note_freq(double note, int key) +{ + if (key != INT_MAX) { /* Just intonation. */ + static const int n[] = {16, 9, 6, 5, 4, 7}; /* Numerator. */ + static const int d[] = {15, 8, 5, 4, 3, 5}; /* Denominator. */ + static double j[13]; /* Just semitones */ + int i, m = floor(note); + + if (!j[1]) for (i = 1; i <= 12; ++i) + j[i] = i <= 6? log((double)n[i - 1] / d[i - 1]) / log(2.) : 1 - j[12 - i]; + note -= m; + m -= key = m - ((INT_MAX / 2 - ((INT_MAX / 2) % 12) + m - key) % 12); + return 440 * pow(2., key / 12. + j[m] + (j[m + 1] - j[m]) * note); + } + return 440 * pow(2., note / 12); +} + +int lsx_parse_note(char const * text, char * * end_ptr) +{ + int result = INT_MAX; + + if (*text >= 'A' && *text <= 'G') { + result = (int)(5/3. * (*text++ - 'A') + 9.5) % 12 - 9; + if (*text == 'b') {--result; ++text;} + else if (*text == '#') {++result; ++text;} + if (isdigit((unsigned char)*text)) + result += 12 * (*text++ - '4'); + } + *end_ptr = (char *)text; + return result; +} + +/* Read string 'text' and convert to frequency. + * 'text' can be a positive number which is the frequency in Hz. + * If 'text' starts with a '%' and a following number the corresponding + * note is calculated. + * Return -1 on error. + */ +double lsx_parse_frequency_k(char const * text, char * * end_ptr, int key) +{ + double result; + + if (*text == '%') { + result = strtod(text + 1, end_ptr); + if (*end_ptr == text + 1) + return -1; + return calc_note_freq(result, key); + } + if (*text >= 'A' && *text <= 'G') { + int result2 = lsx_parse_note(text, end_ptr); + return result2 == INT_MAX? - 1 : calc_note_freq((double)result2, key); + } + result = strtod(text, end_ptr); + if (end_ptr) { + if (*end_ptr == text) + return -1; + if (**end_ptr == 'k') { + result *= 1000; + ++*end_ptr; + } + } + return result < 0 ? -1 : result; +} + +FILE * lsx_open_input_file(sox_effect_t * effp, char const * filename) +{ + FILE * file; + + if (!filename || !strcmp(filename, "-")) { + if (effp->global_info->global_info->stdin_in_use_by) { + lsx_fail("stdin already in use by `%s'", effp->global_info->global_info->stdin_in_use_by); + return NULL; + } + effp->global_info->global_info->stdin_in_use_by = effp->handler.name; + file = stdin; + } + else if (!(file = fopen(filename, "r"))) { + lsx_fail("couldn't open file %s: %s", filename, strerror(errno)); + return NULL; + } + return file; +} + +int lsx_effects_init(void) +{ + #ifndef __FREEDV__ + init_fft_cache(); + #endif + return SOX_SUCCESS; +} + +int lsx_effects_quit(void) +{ + #ifndef __FREEDV__ + clear_fft_cache(); + #endif + return SOX_SUCCESS; +} diff --git a/freedv/tags/1.2.2/src/sox/formats_i.c b/freedv/tags/1.2.2/src/sox/formats_i.c new file mode 100644 index 00000000..17c40615 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/formats_i.c @@ -0,0 +1,487 @@ +/* Implements a libSoX internal interface for use in implementing file formats. + * All public functions & data are prefixed with lsx_ . + * + * (c) 2005-8 Chris Bagwell and SoX contributors + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sox_i.h" +#include +#include +#include + +void lsx_fail_errno(sox_format_t * ft, int sox_errno, const char *fmt, ...) +{ + va_list args; + + ft->sox_errno = sox_errno; + + va_start(args, fmt); +#ifdef HAVE_VSNPRINTF + vsnprintf(ft->sox_errstr, sizeof(ft->sox_errstr), fmt, args); +#else + vsprintf(ft->sox_errstr, fmt, args); +#endif + va_end(args); + ft->sox_errstr[255] = '\0'; +} + +void lsx_set_signal_defaults(sox_format_t * ft) +{ + if (!ft->signal.rate ) ft->signal.rate = SOX_DEFAULT_RATE; + if (!ft->signal.precision) ft->signal.precision = SOX_DEFAULT_PRECISION; + if (!ft->signal.channels ) ft->signal.channels = SOX_DEFAULT_CHANNELS; + + if (!ft->encoding.bits_per_sample) + ft->encoding.bits_per_sample = ft->signal.precision; + if (ft->encoding.encoding == SOX_ENCODING_UNKNOWN) + ft->encoding.encoding = SOX_ENCODING_SIGN2; +} + +#ifndef __FREEDV__ +int lsx_check_read_params(sox_format_t * ft, unsigned channels, + sox_rate_t rate, sox_encoding_t encoding, unsigned bits_per_sample, + uint64_t num_samples, sox_bool check_length) +{ + ft->signal.length = ft->signal.length == SOX_IGNORE_LENGTH? SOX_UNSPEC : num_samples; + + if (ft->seekable) + ft->data_start = lsx_tell(ft); + + if (channels && ft->signal.channels && ft->signal.channels != channels) + lsx_warn("`%s': overriding number of channels", ft->filename); + else ft->signal.channels = channels; + + if (rate && ft->signal.rate && ft->signal.rate != rate) + lsx_warn("`%s': overriding sample rate", ft->filename); + else ft->signal.rate = rate; + + if (encoding && ft->encoding.encoding && ft->encoding.encoding != encoding) + lsx_warn("`%s': overriding encoding type", ft->filename); + else ft->encoding.encoding = encoding; + + if (bits_per_sample && ft->encoding.bits_per_sample && ft->encoding.bits_per_sample != bits_per_sample) + lsx_warn("`%s': overriding encoding size", ft->filename); + ft->encoding.bits_per_sample = bits_per_sample; + + if (check_length && ft->encoding.bits_per_sample && lsx_filelength(ft)) { + uint64_t calculated_length = div_bits(lsx_filelength(ft) - ft->data_start, ft->encoding.bits_per_sample); + if (!ft->signal.length) + ft->signal.length = calculated_length; + else if (num_samples != calculated_length) + lsx_warn("`%s': file header gives the total number of samples as %" PRIu64 " but file length indicates the number is in fact %" PRIu64, ft->filename, num_samples, calculated_length); + } + + if (sox_precision(ft->encoding.encoding, ft->encoding.bits_per_sample)) + return SOX_SUCCESS; + lsx_fail_errno(ft, EINVAL, "invalid format for this file type"); + return SOX_EOF; +} +#endif + +/* Read in a buffer of data of length len bytes. + * Returns number of bytes read. + */ +size_t lsx_readbuf(sox_format_t * ft, void *buf, size_t len) +{ + size_t ret = fread(buf, (size_t) 1, len, (FILE*)ft->fp); + if (ret != len && ferror((FILE*)ft->fp)) + lsx_fail_errno(ft, errno, "lsx_readbuf"); + ft->tell_off += ret; + return ret; +} + +/* Skip input without seeking. */ +int lsx_skipbytes(sox_format_t * ft, size_t n) +{ + unsigned char trash; + + while (n--) + if (lsx_readb(ft, &trash) == SOX_EOF) + return (SOX_EOF); + + return (SOX_SUCCESS); +} + +/* Pad output. */ +int lsx_padbytes(sox_format_t * ft, size_t n) +{ + while (n--) + if (lsx_writeb(ft, '\0') == SOX_EOF) + return (SOX_EOF); + + return (SOX_SUCCESS); +} + +/* Write a buffer of data of length bytes. + * Returns number of bytes written. + */ +size_t lsx_writebuf(sox_format_t * ft, void const * buf, size_t len) +{ + size_t ret = fwrite(buf, (size_t) 1, len, (FILE*)ft->fp); + if (ret != len) { + lsx_fail_errno(ft, errno, "error writing output file"); + clearerr((FILE*)ft->fp); /* Allows us to seek back to write header */ + } + ft->tell_off += ret; + return ret; +} + +uint64_t lsx_filelength(sox_format_t * ft) +{ + struct stat st; + int ret = fstat(fileno((FILE*)ft->fp), &st); + + return (!ret && (st.st_mode & S_IFREG))? (uint64_t)st.st_size : 0; +} + +int lsx_flush(sox_format_t * ft) +{ + return fflush((FILE*)ft->fp); +} + +off_t lsx_tell(sox_format_t * ft) +{ + return ft->seekable? (off_t)ftello((FILE*)ft->fp) : (off_t)ft->tell_off; +} + +int lsx_eof(sox_format_t * ft) +{ + return feof((FILE*)ft->fp); +} + +int lsx_error(sox_format_t * ft) +{ + return ferror((FILE*)ft->fp); +} + +void lsx_rewind(sox_format_t * ft) +{ + rewind((FILE*)ft->fp); + ft->tell_off = 0; +} + +void lsx_clearerr(sox_format_t * ft) +{ + clearerr((FILE*)ft->fp); + ft->sox_errno = 0; +} + +int lsx_unreadb(sox_format_t * ft, unsigned b) +{ + return ungetc((int)b, ft->fp); +} + +/* Implements traditional fseek() behavior. Meant to abstract out + * file operations so that they could one day also work on memory + * buffers. + * + * N.B. Can only seek forwards on non-seekable streams! + */ +int lsx_seeki(sox_format_t * ft, off_t offset, int whence) +{ + if (ft->seekable == 0) { + /* If a stream peel off chars else EPERM */ + if (whence == SEEK_CUR) { + while (offset > 0 && !feof((FILE*)ft->fp)) { + getc((FILE*)ft->fp); + offset--; + ++ft->tell_off; + } + if (offset) + lsx_fail_errno(ft,SOX_EOF, "offset past EOF"); + else + ft->sox_errno = SOX_SUCCESS; + } else + lsx_fail_errno(ft,SOX_EPERM, "file not seekable"); + } else { + if (fseeko((FILE*)ft->fp, offset, whence) == -1) + lsx_fail_errno(ft,errno, "%s", strerror(errno)); + else + ft->sox_errno = SOX_SUCCESS; + } + return ft->sox_errno; +} + +int lsx_offset_seek(sox_format_t * ft, off_t byte_offset, off_t to_sample) +{ + double wide_sample = to_sample - (to_sample % ft->signal.channels); + double to_d = wide_sample * ft->encoding.bits_per_sample / 8; + off_t to = to_d; + return (to != to_d)? SOX_EOF : lsx_seeki(ft, (byte_offset + to), SEEK_SET); +} + +/* Read and write known datatypes in "machine format". Swap if indicated. + * They all return SOX_EOF on error and SOX_SUCCESS on success. + */ +/* Read n-char string (and possibly null-terminating). + * Stop reading and null-terminate string if either a 0 or \n is reached. + */ +int lsx_reads(sox_format_t * ft, char *c, size_t len) +{ + char *sc; + char in; + + sc = c; + do + { + if (lsx_readbuf(ft, &in, (size_t)1) != 1) + { + *sc = 0; + return (SOX_EOF); + } + if (in == 0 || in == '\n') + break; + + *sc = in; + sc++; + } while (sc - c < (ptrdiff_t)len); + *sc = 0; + return(SOX_SUCCESS); +} + +/* Write null-terminated string (without \0). */ +int lsx_writes(sox_format_t * ft, char const * c) +{ + if (lsx_writebuf(ft, c, strlen(c)) != strlen(c)) + return(SOX_EOF); + return(SOX_SUCCESS); +} + +/* return swapped 32-bit float */ +static void lsx_swapf(float * f) +{ + union { + uint32_t dw; + float f; + } u; + + u.f= *f; + u.dw= (u.dw>>24) | ((u.dw>>8)&0xff00) | ((u.dw<<8)&0xff0000) | (u.dw<<24); + *f = u.f; +} + +static void swap(void * data, size_t len) +{ + uint8_t * bytes = (uint8_t *)data; + size_t i; + + for (i = 0; i < len / 2; ++i) { + char tmp = bytes[i]; + bytes[i] = bytes[len - 1 - i]; + bytes[len - 1 - i] = tmp; + } +} + +static double lsx_swapdf(double data) +{ + swap(&data, sizeof(data)); + return data; +} + +static uint64_t lsx_swapqw(uint64_t data) +{ + swap(&data, sizeof(data)); + return data; +} + +/* Lookup table to reverse the bit order of a byte. ie MSB become LSB */ +static uint8_t const cswap[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, + 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, + 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, + 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, + 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, + 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, + 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, + 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, + 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, + 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, + 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, + 0x3F, 0xBF, 0x7F, 0xFF +}; + +/* Utilities to byte-swap values, use libc optimized macros if possible */ +#define TWIDDLE_BYTE(ub, type) \ + do { \ + if (ft->encoding.reverse_bits) \ + ub = cswap[ub]; \ + if (ft->encoding.reverse_nibbles) \ + ub = ((ub & 15) << 4) | (ub >> 4); \ + } while (0); + +#define TWIDDLE_WORD(uw, type) \ + if (ft->encoding.reverse_bytes) \ + uw = lsx_swap ## type(uw); + +#define TWIDDLE_FLOAT(f, type) \ + if (ft->encoding.reverse_bytes) \ + lsx_swapf(&f); + +/* N.B. This macro doesn't work for unaligned types (e.g. 3-byte + types). */ +#define READ_FUNC(type, size, ctype, twiddle) \ + size_t lsx_read_ ## type ## _buf( \ + sox_format_t * ft, ctype *buf, size_t len) \ + { \ + size_t n, nread; \ + nread = lsx_readbuf(ft, buf, len * size) / size; \ + for (n = 0; n < nread; n++) \ + twiddle(buf[n], type); \ + return nread; \ + } + +/* Unpack a 3-byte value from a uint8_t * */ +#define sox_unpack3(p) (ft->encoding.reverse_bytes == MACHINE_IS_BIGENDIAN? \ + ((p)[0] | ((p)[1] << 8) | ((p)[2] << 16)) : \ + ((p)[2] | ((p)[1] << 8) | ((p)[0] << 16))) + +/* This (slower) macro works for unaligned types (e.g. 3-byte types) + that need to be unpacked. */ +#define READ_FUNC_UNPACK(type, size, ctype, twiddle) \ + size_t lsx_read_ ## type ## _buf( \ + sox_format_t * ft, ctype *buf, size_t len) \ + { \ + size_t n, nread; \ + uint8_t *data = lsx_malloc(size * len); \ + nread = lsx_readbuf(ft, data, len * size) / size; \ + for (n = 0; n < nread; n++) \ + buf[n] = sox_unpack ## size(data + n * size); \ + free(data); \ + return n; \ + } + +READ_FUNC(b, 1, uint8_t, TWIDDLE_BYTE) +READ_FUNC(w, 2, uint16_t, TWIDDLE_WORD) +READ_FUNC_UNPACK(3, 3, sox_uint24_t, TWIDDLE_WORD) +READ_FUNC(dw, 4, uint32_t, TWIDDLE_WORD) +READ_FUNC(qw, 8, uint64_t, TWIDDLE_WORD) +READ_FUNC(f, sizeof(float), float, TWIDDLE_FLOAT) +READ_FUNC(df, sizeof(double), double, TWIDDLE_WORD) + +#define READ1_FUNC(type, ctype) \ +int lsx_read ## type(sox_format_t * ft, ctype * datum) { \ + if (lsx_read_ ## type ## _buf(ft, datum, (size_t)1) == 1) \ + return SOX_SUCCESS; \ + if (!lsx_error(ft)) \ + lsx_fail_errno(ft, errno, premature_eof); \ + return SOX_EOF; \ +} + +static char const premature_eof[] = "premature EOF"; + +READ1_FUNC(b, uint8_t) +READ1_FUNC(w, uint16_t) +READ1_FUNC(3, sox_uint24_t) +READ1_FUNC(dw, uint32_t) +READ1_FUNC(qw, uint64_t) +READ1_FUNC(f, float) +READ1_FUNC(df, double) + +int lsx_readchars(sox_format_t * ft, char * chars, size_t len) +{ + size_t ret = lsx_readbuf(ft, chars, len); + if (ret == len) + return SOX_SUCCESS; + if (!lsx_error(ft)) + lsx_fail_errno(ft, errno, premature_eof); + return SOX_EOF; +} + +/* N.B. This macro doesn't work for unaligned types (e.g. 3-byte + types). */ +#define WRITE_FUNC(type, size, ctype, twiddle) \ + size_t lsx_write_ ## type ## _buf( \ + sox_format_t * ft, ctype *buf, size_t len) \ + { \ + size_t n, nwritten; \ + for (n = 0; n < len; n++) \ + twiddle(buf[n], type); \ + nwritten = lsx_writebuf(ft, buf, len * size); \ + return nwritten / size; \ + } + +/* Pack a 3-byte value to a uint8_t * */ +#define sox_pack3(p, v) do {if (ft->encoding.reverse_bytes == MACHINE_IS_BIGENDIAN)\ +{(p)[0] = v & 0xff; (p)[1] = (v >> 8) & 0xff; (p)[2] = (v >> 16) & 0xff;} else \ +{(p)[2] = v & 0xff; (p)[1] = (v >> 8) & 0xff; (p)[0] = (v >> 16) & 0xff;} \ +} while (0) + +/* This (slower) macro works for unaligned types (e.g. 3-byte types) + that need to be packed. */ +#define WRITE_FUNC_PACK(type, size, ctype, twiddle) \ + size_t lsx_write_ ## type ## _buf( \ + sox_format_t * ft, ctype *buf, size_t len) \ + { \ + size_t n, nwritten; \ + uint8_t *data = lsx_malloc(size * len); \ + for (n = 0; n < len; n++) \ + sox_pack ## size(data + n * size, buf[n]); \ + nwritten = lsx_writebuf(ft, data, len * size); \ + free(data); \ + return nwritten / size; \ + } + +WRITE_FUNC(b, 1, uint8_t, TWIDDLE_BYTE) +WRITE_FUNC(w, 2, uint16_t, TWIDDLE_WORD) +WRITE_FUNC_PACK(3, 3, sox_uint24_t, TWIDDLE_WORD) +WRITE_FUNC(dw, 4, uint32_t, TWIDDLE_WORD) +WRITE_FUNC(qw, 8, uint64_t, TWIDDLE_WORD) +WRITE_FUNC(f, sizeof(float), float, TWIDDLE_FLOAT) +WRITE_FUNC(df, sizeof(double), double, TWIDDLE_WORD) + +#define WRITE1U_FUNC(type, ctype) \ + int lsx_write ## type(sox_format_t * ft, unsigned d) \ + { ctype datum = (ctype)d; \ + return lsx_write_ ## type ## _buf(ft, &datum, (size_t)1) == 1 ? SOX_SUCCESS : SOX_EOF; \ + } + +#define WRITE1S_FUNC(type, ctype) \ + int lsx_writes ## type(sox_format_t * ft, signed d) \ + { ctype datum = (ctype)d; \ + return lsx_write_ ## type ## _buf(ft, &datum, (size_t)1) == 1 ? SOX_SUCCESS : SOX_EOF; \ + } + +#define WRITE1_FUNC(type, ctype) \ + int lsx_write ## type(sox_format_t * ft, ctype datum) \ + { \ + return lsx_write_ ## type ## _buf(ft, &datum, (size_t)1) == 1 ? SOX_SUCCESS : SOX_EOF; \ + } + +WRITE1U_FUNC(b, uint8_t) +WRITE1U_FUNC(w, uint16_t) +WRITE1U_FUNC(3, sox_uint24_t) +WRITE1U_FUNC(dw, uint32_t) +WRITE1_FUNC(qw, uint64_t) +WRITE1S_FUNC(b, uint8_t) +WRITE1S_FUNC(w, uint16_t) +WRITE1_FUNC(df, double) + +int lsx_writef(sox_format_t * ft, double datum) +{ + float f = datum; + return lsx_write_f_buf(ft, &f, (size_t) 1) == 1 ? SOX_SUCCESS : SOX_EOF; +} diff --git a/freedv/tags/1.2.2/src/sox/libsox.c b/freedv/tags/1.2.2/src/sox/libsox.c new file mode 100644 index 00000000..43620250 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/libsox.c @@ -0,0 +1,225 @@ +/* Implements the public API for libSoX general functions + * All public functions & data are prefixed with sox_ . + * + * (c) 2006-8 Chris Bagwell and SoX contributors + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sox_i.h" +#include + +const char *sox_version(void) +{ + static char versionstr[20]; + + sprintf(versionstr, "%d.%d.%d", + (SOX_LIB_VERSION_CODE & 0xff0000) >> 16, + (SOX_LIB_VERSION_CODE & 0x00ff00) >> 8, + (SOX_LIB_VERSION_CODE & 0x0000ff)); + return(versionstr); +} + +sox_version_info_t const * sox_version_info(void) +{ +#define STRINGIZE1(x) #x +#define STRINGIZE(x) STRINGIZE1(x) + static char arch[30]; + static sox_version_info_t info = { + /* size */ + sizeof(sox_version_info_t), + /* flags */ + (sox_version_flags_t)( +#if HAVE_POPEN + sox_version_have_popen + +#endif +#if HAVE_MAGIC + sox_version_have_magic + +#endif +#if HAVE_OPENMP + sox_version_have_threads + +#endif +#ifdef HAVE_FMEMOPEN + sox_version_have_memopen + +#endif + sox_version_none), + /* version_code */ + SOX_LIB_VERSION_CODE, + /* version */ + NULL, + /* sox_version_extra */ +#ifdef PACKAGE_EXTRA + PACKAGE_EXTRA, +#else + NULL, +#endif + /* sox_time */ + __DATE__ " " __TIME__, + /* sox_distro */ +#ifdef DISTRO + DISTRO, +#else + NULL, +#endif + /* sox_compiler */ +#if defined __GNUC__ + "gcc " __VERSION__, +#elif defined _MSC_VER + "msvc " STRINGIZE(_MSC_FULL_VER), +#elif defined __SUNPRO_C + fprintf(file, "sun c " STRINGIZE(__SUNPRO_C), +#else + NULL, +#endif + /* sox_arch */ + NULL + }; + + if (!info.version) + { + info.version = sox_version(); + } + + if (!info.arch) + { + snprintf(arch, sizeof(arch), + "%" PRIuPTR "%" PRIuPTR "%" PRIuPTR "%" PRIuPTR + " %" PRIuPTR "%" PRIuPTR " %" PRIuPTR "%" PRIuPTR " %c %s", + sizeof(char), sizeof(short), sizeof(long), sizeof(off_t), + sizeof(float), sizeof(double), sizeof(int *), sizeof(int (*)(void)), + MACHINE_IS_BIGENDIAN ? 'B' : 'L', + (info.flags & sox_version_have_threads) ? "OMP" : ""); + arch[sizeof(arch) - 1] = 0; + info.arch = arch; + } + + return &info; +} + +/* Default routine to output messages; can be overridden */ +static void output_message( + unsigned level, const char *filename, const char *fmt, va_list ap) +{ + if (sox_globals.verbosity >= level) { + char base_name[128]; + sox_basename(base_name, sizeof(base_name), filename); + fprintf(stderr, "%s: ", base_name); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } +} + +static sox_globals_t s_sox_globals = { + 2, /* unsigned verbosity */ + output_message, /* sox_output_message_handler */ + sox_false, /* sox_bool repeatable */ + 8192, /* size_t bufsiz */ + 0, /* size_t input_bufsiz */ + 0, /* int32_t ranqd1 */ + NULL, /* char const * stdin_in_use_by */ + NULL, /* char const * stdout_in_use_by */ + NULL, /* char const * subsystem */ + NULL, /* char * tmp_path */ + sox_false, /* sox_bool use_magic */ + sox_false /* sox_bool use_threads */ +}; + +sox_globals_t * sox_get_globals(void) +{ + return &s_sox_globals; +} + +/* FIXME: Not thread safe using globals */ +static sox_effects_globals_t s_sox_effects_globals = + {sox_plot_off, &s_sox_globals}; + +sox_effects_globals_t * +sox_get_effects_globals(void) +{ + return &s_sox_effects_globals; +} + +char const * sox_strerror(int sox_errno) +{ + static char const * const errors[] = { + "Invalid Audio Header", + "Unsupported data format", + "Can't allocate memory", + "Operation not permitted", + "Operation not supported", + "Invalid argument", + }; + if (sox_errno < SOX_EHDR) + return strerror(sox_errno); + sox_errno -= SOX_EHDR; + if (sox_errno < 0 || (size_t)sox_errno >= array_length(errors)) + return "Unknown error"; + return errors[sox_errno]; +} + +size_t sox_basename(char * base_buffer, size_t base_buffer_len, const char * filename) +{ + if (!base_buffer || !base_buffer_len) + { + return 0; + } + else + { + char const * slash_pos = LAST_SLASH(filename); + char const * base_name = slash_pos ? slash_pos + 1 : filename; + char const * dot_pos = strrchr(base_name, '.'); + size_t i, len; + dot_pos = dot_pos ? dot_pos : base_name + strlen(base_name); + len = dot_pos - base_name; + len = min(len, base_buffer_len - 1); + for (i = 0; i < len; i++) + { + base_buffer[i] = base_name[i]; + } + base_buffer[i] = 0; + return i; + } +} + +#define SOX_MESSAGE_FUNCTION(name,level) \ +void name(char const * fmt, ...) { \ + va_list ap; \ + va_start(ap, fmt); \ + if (sox_globals.output_message_handler) \ + (*sox_globals.output_message_handler)(level,sox_globals.subsystem,fmt,ap); \ + va_end(ap); \ +} + +SOX_MESSAGE_FUNCTION(lsx_fail_impl , 1) +SOX_MESSAGE_FUNCTION(lsx_warn_impl , 2) +SOX_MESSAGE_FUNCTION(lsx_report_impl, 3) +SOX_MESSAGE_FUNCTION(lsx_debug_impl , 4) +SOX_MESSAGE_FUNCTION(lsx_debug_more_impl , 5) +SOX_MESSAGE_FUNCTION(lsx_debug_most_impl , 6) + +#undef SOX_MESSAGE_FUNCTION + +int sox_init(void) +{ + return lsx_effects_init(); +} + +int sox_quit(void) +{ + #ifndef __FREEDV__ + sox_format_quit(); + #endif + return lsx_effects_quit(); +} diff --git a/freedv/tags/1.2.2/src/sox/sox.h b/freedv/tags/1.2.2/src/sox/sox.h new file mode 100644 index 00000000..05372558 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/sox.h @@ -0,0 +1,2608 @@ +/* libSoX Library Public Interface + * + * Copyright 1999-2011 Chris Bagwell and SoX Contributors. + * + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Chris Bagwell And SoX Contributors are not responsible for + * the consequences of using this software. + */ + +/** @file +Contains the interface exposed to clients of the libSoX library. +Symbols starting with "sox_" or "SOX_" are part of the public interface for +libSoX clients (applications that consume libSoX). Symbols starting with +"lsx_" or "LSX_" are internal use by libSoX and plugins. +LSX_ and lsx_ symbols should not be used by libSoX-based applications. +*/ + +#ifndef SOX_H +#define SOX_H /**< Client API: This macro is defined if sox.h has been included. */ + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Suppress warnings from use of type long long. */ +#if defined __GNUC__ +#pragma GCC system_header +#endif + +/***************************************************************************** +API decoration macros: +Mostly for documentation purposes. For some compilers, decorations also affect +code generation, influence compiler warnings or activate compiler +optimizations. +*****************************************************************************/ + +/** +Plugins API: +Attribute required on all functions exported by libSoX and on all function +pointer types used by the libSoX API. +*/ +#ifdef __GNUC__ +#define LSX_API __attribute__ ((cdecl)) /* libSoX function */ +#elif _MSC_VER +#define LSX_API __cdecl /* libSoX function */ +#else +#define LSX_API /* libSoX function */ +#endif + +/** +Plugins API: +Attribute applied to a parameter or local variable to suppress warnings about +the variable being unused (especially in macro-generated code). +*/ +#ifdef __GNUC__ +#define LSX_UNUSED __attribute__ ((unused)) /* Parameter or local variable is intentionally unused. */ +#else +#define LSX_UNUSED /* Parameter or local variable is intentionally unused. */ +#endif + +/** +Plugins API: +LSX_PRINTF12: Attribute applied to a function to indicate that it requires +a printf-style format string for arg1 and that printf parameters start at +arg2. +*/ +#ifdef __GNUC__ +#define LSX_PRINTF12 __attribute__ ((format (printf, 1, 2))) /* Function has printf-style arguments. */ +#else +#define LSX_PRINTF12 /* Function has printf-style arguments. */ +#endif + +/** +Plugins API: +Attribute applied to a function to indicate that it has no side effects and +depends only its input parameters and global memory. If called repeatedly, it +returns the same result each time. +*/ +#ifdef __GNUC__ +#define LSX_RETURN_PURE __attribute__ ((pure)) /* Function is pure. */ +#else +#define LSX_RETURN_PURE /* Function is pure. */ +#endif + +/** +Plugins API: +Attribute applied to a function to indicate that the +return value is always a pointer to a valid object (never NULL). +*/ +#ifdef _Ret_ +#define LSX_RETURN_VALID _Ret_ /* Function always returns a valid object (never NULL). */ +#else +#define LSX_RETURN_VALID /* Function always returns a valid object (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a function to indicate that the return value is always a +pointer to a valid array (never NULL). +*/ +#ifdef _Ret_valid_ +#define LSX_RETURN_ARRAY _Ret_valid_ /* Function always returns a valid array (never NULL). */ +#else +#define LSX_RETURN_ARRAY /* Function always returns a valid array (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a function to indicate that the return value is always a +pointer to a valid 0-terminated array (never NULL). +*/ +#ifdef _Ret_z_ +#define LSX_RETURN_VALID_Z _Ret_z_ /* Function always returns a 0-terminated array (never NULL). */ +#else +#define LSX_RETURN_VALID_Z /* Function always returns a 0-terminated array (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a function to indicate that the returned pointer may be +null. +*/ +#ifdef _Ret_opt_ +#define LSX_RETURN_OPT _Ret_opt_ /* Function may return NULL. */ +#else +#define LSX_RETURN_OPT /* Function may return NULL. */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to one const element of the pointed-to type (never NULL). +*/ +#ifdef _In_ +#define LSX_PARAM_IN _In_ /* Required const pointer to a valid object (never NULL). */ +#else +#define LSX_PARAM_IN /* Required const pointer to a valid object (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to a const 0-terminated string (never NULL). +*/ +#ifdef _In_z_ +#define LSX_PARAM_IN_Z _In_z_ /* Required const pointer to 0-terminated string (never NULL). */ +#else +#define LSX_PARAM_IN_Z /* Required const pointer to 0-terminated string (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a const +pointer to a 0-terminated printf format string. +*/ +#ifdef _Printf_format_string_ +#define LSX_PARAM_IN_PRINTF _Printf_format_string_ /* Required const pointer to 0-terminated printf format string (never NULL). */ +#else +#define LSX_PARAM_IN_PRINTF /* Required const pointer to 0-terminated printf format string (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to (len) const initialized elements of the pointed-to type, where +(len) is the name of another parameter. +@param len The parameter that contains the number of elements in the array. +*/ +#ifdef _In_count_ +#define LSX_PARAM_IN_COUNT(len) _In_count_(len) /* Required const pointer to (len) valid objects (never NULL). */ +#else +#define LSX_PARAM_IN_COUNT(len) /* Required const pointer to (len) valid objects (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to (len) const bytes of initialized data, where (len) is the name of +another parameter. +@param len The parameter that contains the number of bytes in the array. +*/ +#ifdef _In_bytecount_ +#define LSX_PARAM_IN_BYTECOUNT(len) _In_bytecount_(len) /* Required const pointer to (len) bytes of data (never NULL). */ +#else +#define LSX_PARAM_IN_BYTECOUNT(len) /* Required const pointer to (len) bytes of data (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is either NULL +or a valid pointer to one const element of the pointed-to type. +*/ +#ifdef _In_opt_ +#define LSX_PARAM_IN_OPT _In_opt_ /* Optional const pointer to a valid object (may be NULL). */ +#else +#define LSX_PARAM_IN_OPT /* Optional const pointer to a valid object (may be NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is either NULL +or a valid pointer to a const 0-terminated string. +*/ +#ifdef _In_opt_z_ +#define LSX_PARAM_IN_OPT_Z _In_opt_z_ /* Optional const pointer to 0-terminated string (may be NULL). */ +#else +#define LSX_PARAM_IN_OPT_Z /* Optional const pointer to 0-terminated string (may be NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to one initialized element of the pointed-to type (never NULL). The +function may modify the element. +*/ +#ifdef _Inout_ +#define LSX_PARAM_INOUT _Inout_ /* Required pointer to a valid object (never NULL). */ +#else +#define LSX_PARAM_INOUT /* Required pointer to a valid object (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to (len) initialized elements of the pointed-to type (never NULL). The +function may modify the elements. +@param len The parameter that contains the number of elements in the array. +*/ +#ifdef _Inout_count_x_ +#define LSX_PARAM_INOUT_COUNT(len) _Inout_count_x_(len) /* Required pointer to (len) valid objects (never NULL). */ +#else +#define LSX_PARAM_INOUT_COUNT(len) /* Required pointer to (len) valid objects (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to memory sufficient for one element of the pointed-to type (never +NULL). The function will initialize the element. +*/ +#ifdef _Out_ +#define LSX_PARAM_OUT _Out_ /* Required pointer to an object to be initialized (never NULL). */ +#else +#define LSX_PARAM_OUT /* Required pointer to an object to be initialized (never NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to memory sufficient for (len) bytes of data (never NULL), where (len) +is the name of another parameter. The function may write up to len bytes of +data to this memory. +@param len The parameter that contains the number of bytes in the array. +*/ +#ifdef _Out_bytecap_ +#define LSX_PARAM_OUT_BYTECAP(len) _Out_bytecap_(len) /* Required pointer to writable buffer with room for len bytes. */ +#else +#define LSX_PARAM_OUT_BYTECAP(len) /* Required pointer to writable buffer with room for len bytes. */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to memory sufficient for (len) elements of the pointed-to type (never +NULL), where (len) is the name of another parameter. On return, (filled) +elements will have been initialized, where (filled) is either the dereference +of another pointer parameter (for example "*written") or the "return" +parameter (indicating that the function returns the number of elements +written). +@param len The parameter that contains the number of elements in the array. +@param filled The dereference of the parameter that receives the number of elements written to the array, or "return" if the value is returned. +*/ +#ifdef _Out_cap_post_count_ +#define LSX_PARAM_OUT_CAP_POST_COUNT(len,filled) _Out_cap_post_count_(len,filled) /* Required pointer to buffer for (len) elements (never NULL); on return, (filled) elements will have been initialized. */ +#else +#define LSX_PARAM_OUT_CAP_POST_COUNT(len,filled) /* Required pointer to buffer for (len) elements (never NULL); on return, (filled) elements will have been initialized. */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer to memory sufficient for (len) elements of the pointed-to type (never +NULL), where (len) is the name of another parameter. On return, (filled+1) +elements will have been initialized, with the last element having been +initialized to 0, where (filled) is either the dereference of another pointer +parameter (for example, "*written") or the "return" parameter (indicating that +the function returns the number of elements written). +@param len The parameter that contains the number of elements in the array. +@param filled The dereference of the parameter that receives the number of elements written to the array (not counting the terminating null), or "return" if the value is returned. +*/ +#ifdef _Out_z_cap_post_count_ +#define LSX_PARAM_OUT_Z_CAP_POST_COUNT(len,filled) _Out_z_cap_post_count_(len,filled) /* Required pointer to buffer for (len) elements (never NULL); on return, (filled+1) elements will have been initialized, and the array will be 0-terminated. */ +#else +#define LSX_PARAM_OUT_Z_CAP_POST_COUNT(len,filled) /* Required pointer to buffer for (len) elements (never NULL); on return, (filled+1) elements will have been initialized, and the array will be 0-terminated. */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is either NULL +or a valid pointer to memory sufficient for one element of the pointed-to +type. The function will initialize the element. +*/ +#ifdef _Out_opt_ +#define LSX_PARAM_OUT_OPT _Out_opt_ /* Optional pointer to an object to be initialized (may be NULL). */ +#else +#define LSX_PARAM_OUT_OPT /* Optional pointer to an object to be initialized (may be NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer (never NULL) to another pointer which may be NULL when the function is +invoked. +*/ +#ifdef _Deref_pre_maybenull_ +#define LSX_PARAM_DEREF_PRE_MAYBENULL _Deref_pre_maybenull_ /* Required pointer (never NULL) to another pointer (may be NULL). */ +#else +#define LSX_PARAM_DEREF_PRE_MAYBENULL /* Required pointer (never NULL) to another pointer (may be NULL). */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer (never NULL) to another pointer which will be NULL when the function +returns. +*/ +#ifdef _Deref_post_null_ +#define LSX_PARAM_DEREF_POST_NULL _Deref_post_null_ /* Required pointer (never NULL) to another pointer, which will be NULL on exit. */ +#else +#define LSX_PARAM_DEREF_POST_NULL /* Required pointer (never NULL) to another pointer, which will be NULL on exit. */ +#endif + +/** +Plugins API: +Attribute applied to a parameter to indicate that the parameter is a valid +pointer (never NULL) to another pointer which will be non-NULL when the +function returns. +*/ +#ifdef _Deref_post_notnull_ +#define LSX_PARAM_DEREF_POST_NOTNULL _Deref_post_notnull_ /* Required pointer (never NULL) to another pointer, which will be valid (not NULL) on exit. */ +#else +#define LSX_PARAM_DEREF_POST_NOTNULL /* Required pointer (never NULL) to another pointer, which will be valid (not NULL) on exit. */ +#endif + +/** +Plugins API: +Expression that "uses" a potentially-unused variable to avoid compiler +warnings (especially in macro-generated code). +*/ +#ifdef _PREFAST_ +#define LSX_USE_VAR(x) ((void)(x=0)) /* During static analysis, initialize unused variables to 0. */ +#else +#define LSX_USE_VAR(x) ((void)(x)) /* Parameter or variable is intentionally unused. */ +#endif + +/** +Plugins API: +Compile-time assertion. Causes a compile error if the expression is false. +@param e The expression to test. If expression is false, compilation will fail. +@param f A unique identifier for the test, for example foo_must_not_be_zero. +*/ +#define lsx_static_assert(e,f) enum {lsx_static_assert_##f = 1/((e) ? 1 : 0)} + +/***************************************************************************** +Basic typedefs: +*****************************************************************************/ + +/** +Client API: +Signed twos-complement 8-bit type. Typically defined as signed char. +*/ +#if SCHAR_MAX==127 && SCHAR_MIN==(-128) +typedef signed char sox_int8_t; +#elif CHAR_MAX==127 && CHAR_MIN==(-128) +typedef char sox_int8_t; +#else +#error Unable to determine an appropriate definition for sox_int8_t. +#endif + +/** +Client API: +Unsigned 8-bit type. Typically defined as unsigned char. +*/ +#if UCHAR_MAX==0xff +typedef unsigned char sox_uint8_t; +#elif CHAR_MAX==0xff && CHAR_MIN==0 +typedef char sox_uint8_t; +#else +#error Unable to determine an appropriate definition for sox_uint8_t. +#endif + +/** +Client API: +Signed twos-complement 16-bit type. Typically defined as short. +*/ +#if SHRT_MAX==32767 && SHRT_MIN==(-32768) +typedef short sox_int16_t; +#elif INT_MAX==32767 && INT_MIN==(-32768) +typedef int sox_int16_t; +#else +#error Unable to determine an appropriate definition for sox_int16_t. +#endif + +/** +Client API: +Unsigned 16-bit type. Typically defined as unsigned short. +*/ +#if USHRT_MAX==0xffff +typedef unsigned short sox_uint16_t; +#elif UINT_MAX==0xffff +typedef unsigned int sox_uint16_t; +#else +#error Unable to determine an appropriate definition for sox_uint16_t. +#endif + +/** +Client API: +Signed twos-complement 32-bit type. Typically defined as int. +*/ +#if INT_MAX==2147483647 && INT_MIN==(-2147483647-1) +typedef int sox_int32_t; +#elif LONG_MAX==2147483647 && LONG_MIN==(-2147483647-1) +typedef long sox_int32_t; +#else +#error Unable to determine an appropriate definition for sox_int32_t. +#endif + +/** +Client API: +Unsigned 32-bit type. Typically defined as unsigned int. +*/ +#if UINT_MAX==0xffffffff +typedef unsigned int sox_uint32_t; +#elif ULONG_MAX==0xffffffff +typedef unsigned long sox_uint32_t; +#else +#error Unable to determine an appropriate definition for sox_uint32_t. +#endif + +/** +Client API: +Signed twos-complement 64-bit type. Typically defined as long or long long. +*/ +#if LONG_MAX==9223372036854775807 && LONG_MIN==(-9223372036854775807-1) +typedef long sox_int64_t; +#elif defined(_MSC_VER) +typedef __int64 sox_int64_t; +#else +typedef long long sox_int64_t; +#endif + +/** +Client API: +Unsigned 64-bit type. Typically defined as unsigned long or unsigned long long. +*/ +#if ULONG_MAX==0xffffffffffffffff +typedef unsigned long sox_uint64_t; +#elif defined(_MSC_VER) +typedef unsigned __int64 sox_uint64_t; +#else +typedef unsigned long long sox_uint64_t; +#endif + +#ifndef _DOXYGEN_ +lsx_static_assert(sizeof(sox_int8_t)==1, sox_int8_size); +lsx_static_assert(sizeof(sox_uint8_t)==1, sox_uint8_size); +lsx_static_assert(sizeof(sox_int16_t)==2, sox_int16_size); +lsx_static_assert(sizeof(sox_uint16_t)==2, sox_uint16_size); +lsx_static_assert(sizeof(sox_int32_t)==4, sox_int32_size); +lsx_static_assert(sizeof(sox_uint32_t)==4, sox_uint32_size); +lsx_static_assert(sizeof(sox_int64_t)==8, sox_int64_size); +lsx_static_assert(sizeof(sox_uint64_t)==8, sox_uint64_size); +#endif + +/** +Client API: +Alias for sox_int32_t (beware of the extra byte). +*/ +typedef sox_int32_t sox_int24_t; + +/** +Client API: +Alias for sox_uint32_t (beware of the extra byte). +*/ +typedef sox_uint32_t sox_uint24_t; + +/** +Client API: +Native SoX audio sample type (alias for sox_int32_t). +*/ +typedef sox_int32_t sox_sample_t; + +/** +Client API: +Samples per second is stored as a double. +*/ +typedef double sox_rate_t; + +/** +Client API: +File's metadata, access via sox_*_comments functions. +*/ +typedef char * * sox_comments_t; + +/***************************************************************************** +Enumerations: +*****************************************************************************/ + +/** +Client API: +Boolean type, assignment (but not necessarily binary) compatible with C++ bool. +*/ +typedef enum sox_bool { + sox_false, /**< False = 0. */ + sox_true /**< True = 1. */ +} sox_bool; + +/** +Client API: +no, yes, or default (default usually implies some kind of auto-detect logic). +*/ +typedef enum sox_option_t { + sox_option_no, /**< Option specified as no = 0. */ + sox_option_yes, /**< Option specified as yes = 1. */ + sox_option_default /**< Option unspecified = 2. */ +} sox_option_t; + +/** +Client API: +The libSoX-specific error codes. +libSoX functions may return these codes or others that map from errno codes. +*/ +enum sox_error_t { + SOX_SUCCESS = 0, /**< Function succeeded = 0 */ + SOX_EOF = -1, /**< End Of File or other error = -1 */ + SOX_EHDR = 2000, /**< Invalid Audio Header = 2000 */ + SOX_EFMT, /**< Unsupported data format = 2001 */ + SOX_ENOMEM, /**< Can't alloc memory = 2002 */ + SOX_EPERM, /**< Operation not permitted = 2003 */ + SOX_ENOTSUP, /**< Operation not supported = 2004 */ + SOX_EINVAL /**< Invalid argument = 2005 */ +}; + +/** +Client API: +Flags indicating whether optional features are present in this build of libSoX. +*/ +typedef enum sox_version_flags_t { + sox_version_none = 0, /**< No special features = 0. */ + sox_version_have_popen = 1, /**< popen = 1. */ + sox_version_have_magic = 2, /**< magic = 2. */ + sox_version_have_threads = 4, /**< threads = 4. */ + sox_version_have_memopen = 8 /**< memopen = 8. */ +} sox_version_flags_t; + +/** +Client API: +Format of sample data. +*/ +typedef enum sox_encoding_t { + SOX_ENCODING_UNKNOWN , /**< encoding has not yet been determined */ + + SOX_ENCODING_SIGN2 , /**< signed linear 2's comp: Mac */ + SOX_ENCODING_UNSIGNED , /**< unsigned linear: Sound Blaster */ + SOX_ENCODING_FLOAT , /**< floating point (binary format) */ + SOX_ENCODING_FLOAT_TEXT, /**< floating point (text format) */ + SOX_ENCODING_FLAC , /**< FLAC compression */ + SOX_ENCODING_HCOM , /**< Mac FSSD files with Huffman compression */ + SOX_ENCODING_WAVPACK , /**< WavPack with integer samples */ + SOX_ENCODING_WAVPACKF , /**< WavPack with float samples */ + SOX_ENCODING_ULAW , /**< u-law signed logs: US telephony, SPARC */ + SOX_ENCODING_ALAW , /**< A-law signed logs: non-US telephony, Psion */ + SOX_ENCODING_G721 , /**< G.721 4-bit ADPCM */ + SOX_ENCODING_G723 , /**< G.723 3 or 5 bit ADPCM */ + SOX_ENCODING_CL_ADPCM , /**< Creative Labs 8 --> 2,3,4 bit Compressed PCM */ + SOX_ENCODING_CL_ADPCM16, /**< Creative Labs 16 --> 4 bit Compressed PCM */ + SOX_ENCODING_MS_ADPCM , /**< Microsoft Compressed PCM */ + SOX_ENCODING_IMA_ADPCM , /**< IMA Compressed PCM */ + SOX_ENCODING_OKI_ADPCM , /**< Dialogic/OKI Compressed PCM */ + SOX_ENCODING_DPCM , /**< Differential PCM: Fasttracker 2 (xi) */ + SOX_ENCODING_DWVW , /**< Delta Width Variable Word */ + SOX_ENCODING_DWVWN , /**< Delta Width Variable Word N-bit */ + SOX_ENCODING_GSM , /**< GSM 6.10 33byte frame lossy compression */ + SOX_ENCODING_MP3 , /**< MP3 compression */ + SOX_ENCODING_VORBIS , /**< Vorbis compression */ + SOX_ENCODING_AMR_WB , /**< AMR-WB compression */ + SOX_ENCODING_AMR_NB , /**< AMR-NB compression */ + SOX_ENCODING_CVSD , /**< Continuously Variable Slope Delta modulation */ + SOX_ENCODING_LPC10 , /**< Linear Predictive Coding */ + + SOX_ENCODINGS /**< End of list marker */ +} sox_encoding_t; + +/** +Client API: +Flags for sox_encodings_info_t: lossless/lossy1/lossy2. +*/ +typedef enum sox_encodings_flags_t { + sox_encodings_none = 0, /**< no flags specified (implies lossless encoding) = 0. */ + sox_encodings_lossy1 = 1, /**< encode, decode: lossy once = 1. */ + sox_encodings_lossy2 = 2 /**< encode, decode, encode, decode: lossy twice = 2. */ +} sox_encodings_flags_t; + +/** +Client API: +Type of plot. +*/ +typedef enum sox_plot_t { + sox_plot_off, /**< No plot = 0. */ + sox_plot_octave, /**< Octave plot = 1. */ + sox_plot_gnuplot, /**< Gnuplot plot = 2. */ + sox_plot_data /**< Plot data = 3. */ +} sox_plot_t; + +/** +Client API: +Loop modes: upper 4 bits mask the loop blass, lower 4 bits describe +the loop behaviour, for example single shot, bidirectional etc. +*/ +enum sox_loop_flags_t { + sox_loop_none = 0, /**< single-shot = 0 */ + sox_loop_forward = 1, /**< forward loop = 1 */ + sox_loop_forward_back = 2, /**< forward/back loop = 2 */ + sox_loop_8 = 32, /**< 8 loops (??) = 32 */ + sox_loop_sustain_decay = 64 /**< AIFF style, one sustain & one decay loop = 64 */ +}; + +/** +Plugins API: +Is file a real file, a pipe, or a url? +*/ +typedef enum lsx_io_type +{ + lsx_io_file, /**< File is a real file = 0. */ + lsx_io_pipe, /**< File is a pipe (no seeking) = 1. */ + lsx_io_url /**< File is a URL (no seeking) = 2. */ +} lsx_io_type; + +/***************************************************************************** +Macros: +*****************************************************************************/ + +/** +Client API: +Compute a 32-bit integer API version from three 8-bit parts. +@param a Major version. +@param b Minor version. +@param c Revision or build number. +@returns 32-bit integer API version 0x000a0b0c. +*/ +#define SOX_LIB_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) + +/** +Client API: +The API version of the sox.h file. It is not meant to follow the version +number of SoX but it has historically. Please do not count on +SOX_LIB_VERSION_CODE staying in sync with the libSoX version. +*/ +#define SOX_LIB_VERSION_CODE SOX_LIB_VERSION(14, 4, 1) + +/** +Client API: +Returns the smallest (negative) value storable in a twos-complement signed +integer with the specified number of bits, cast to an unsigned integer; +for example, SOX_INT_MIN(8) = 0x80, SOX_INT_MIN(16) = 0x8000, etc. +@param bits Size of value for which to calculate minimum. +@returns the smallest (negative) value storable in a twos-complement signed +integer with the specified number of bits, cast to an unsigned integer. +*/ +#define SOX_INT_MIN(bits) (1 <<((bits)-1)) + +/** +Client API: +Returns the largest (positive) value storable in a twos-complement signed +integer with the specified number of bits, cast to an unsigned integer; +for example, SOX_INT_MAX(8) = 0x7F, SOX_INT_MAX(16) = 0x7FFF, etc. +@param bits Size of value for which to calculate maximum. +@returns the largest (positive) value storable in a twos-complement signed +integer with the specified number of bits, cast to an unsigned integer. +*/ +#define SOX_INT_MAX(bits) (((unsigned)-1)>>(33-(bits))) + +/** +Client API: +Returns the largest value storable in an unsigned integer with the specified +number of bits; for example, SOX_UINT_MAX(8) = 0xFF, +SOX_UINT_MAX(16) = 0xFFFF, etc. +@param bits Size of value for which to calculate maximum. +@returns the largest value storable in an unsigned integer with the specified +number of bits. +*/ +#define SOX_UINT_MAX(bits) (SOX_INT_MIN(bits)|SOX_INT_MAX(bits)) + +/** +Client API: +Returns 0x7F. +*/ +#define SOX_INT8_MAX SOX_INT_MAX(8) + +/** +Client API: +Returns 0x7FFF. +*/ +#define SOX_INT16_MAX SOX_INT_MAX(16) + +/** +Client API: +Returns 0x7FFFFF. +*/ +#define SOX_INT24_MAX SOX_INT_MAX(24) + +/** +Client API: +Returns 0x7FFFFFFF. +*/ +#define SOX_INT32_MAX SOX_INT_MAX(32) + +/** +Client API: +Bits in a sox_sample_t = 32. +*/ +#define SOX_SAMPLE_PRECISION 32 + +/** +Client API: +Max value for sox_sample_t = 0x7FFFFFFF. +*/ +#define SOX_SAMPLE_MAX (sox_sample_t)SOX_INT_MAX(32) + +/** +Client API: +Min value for sox_sample_t = 0x80000000. +*/ +#define SOX_SAMPLE_MIN (sox_sample_t)SOX_INT_MIN(32) + + +/* Conversions: Linear PCM <--> sox_sample_t + * + * I/O Input sox_sample_t Clips? Input sox_sample_t Clips? + * Format Minimum Minimum I O Maximum Maximum I O + * ------ --------- ------------ -- -- -------- ------------ -- -- + * Float -inf -1 y n inf 1 - 5e-10 y n + * Int8 -128 -128 n n 127 127.9999999 n y + * Int16 -32768 -32768 n n 32767 32767.99998 n y + * Int24 -8388608 -8388608 n n 8388607 8388607.996 n y + * Int32 -2147483648 -2147483648 n n 2147483647 2147483647 n n + * + * Conversions are as accurate as possible (with rounding). + * + * Rounding: halves toward +inf, all others to nearest integer. + * + * Clips? shows whether on not there is the possibility of a conversion + * clipping to the minimum or maximum value when inputing from or outputing + * to a given type. + * + * Unsigned integers are converted to and from signed integers by flipping + * the upper-most bit then treating them as signed integers. + */ + +/** +Client API: +Declares the temporary local variables that are required when using SOX +conversion macros. +*/ +#define SOX_SAMPLE_LOCALS sox_sample_t sox_macro_temp_sample LSX_UNUSED; \ + double sox_macro_temp_double LSX_UNUSED + +/** +Client API: +Sign bit for sox_sample_t = 0x80000000. +*/ +#define SOX_SAMPLE_NEG SOX_INT_MIN(32) + +/** +Client API: +Converts sox_sample_t to an unsigned integer of width (bits). +@param bits Width of resulting sample (1 through 32). +@param d Input sample to be converted. +@param clips Variable that is incremented if the result is too big. +@returns Unsigned integer of width (bits). +*/ +#define SOX_SAMPLE_TO_UNSIGNED(bits,d,clips) \ + (sox_uint##bits##_t)(SOX_SAMPLE_TO_SIGNED(bits,d,clips)^SOX_INT_MIN(bits)) + +/** +Client API: +Converts sox_sample_t to a signed integer of width (bits). +@param bits Width of resulting sample (1 through 32). +@param d Input sample to be converted. +@param clips Variable that is incremented if the result is too big. +@returns Signed integer of width (bits). +*/ +#define SOX_SAMPLE_TO_SIGNED(bits,d,clips) \ + (sox_int##bits##_t)(LSX_USE_VAR(sox_macro_temp_double),sox_macro_temp_sample=(d),sox_macro_temp_sample>SOX_SAMPLE_MAX-(1<<(31-bits))?++(clips),SOX_INT_MAX(bits):((sox_uint32_t)(sox_macro_temp_sample+(1<<(31-bits))))>>(32-bits)) + +/** +Client API: +Converts signed integer of width (bits) to sox_sample_t. +@param bits Width of input sample (1 through 32). +@param d Input sample to be converted. +@returns SoX native sample value. +*/ +#define SOX_SIGNED_TO_SAMPLE(bits,d)((sox_sample_t)(d)<<(32-bits)) + +/** +Client API: +Converts unsigned integer of width (bits) to sox_sample_t. +@param bits Width of input sample (1 through 32). +@param d Input sample to be converted. +@returns SoX native sample value. +*/ +#define SOX_UNSIGNED_TO_SAMPLE(bits,d)(SOX_SIGNED_TO_SAMPLE(bits,d)^SOX_SAMPLE_NEG) + +/** +Client API: +Converts unsigned 8-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_UNSIGNED_8BIT_TO_SAMPLE(d,clips) SOX_UNSIGNED_TO_SAMPLE(8,d) + +/** +Client API: +Converts signed 8-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_SIGNED_8BIT_TO_SAMPLE(d,clips) SOX_SIGNED_TO_SAMPLE(8,d) + +/** +Client API: +Converts unsigned 16-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_UNSIGNED_16BIT_TO_SAMPLE(d,clips) SOX_UNSIGNED_TO_SAMPLE(16,d) + +/** +Client API: +Converts signed 16-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_SIGNED_16BIT_TO_SAMPLE(d,clips) SOX_SIGNED_TO_SAMPLE(16,d) + +/** +Client API: +Converts unsigned 24-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_UNSIGNED_24BIT_TO_SAMPLE(d,clips) SOX_UNSIGNED_TO_SAMPLE(24,d) + +/** +Client API: +Converts signed 24-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_SIGNED_24BIT_TO_SAMPLE(d,clips) SOX_SIGNED_TO_SAMPLE(24,d) + +/** +Client API: +Converts unsigned 32-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_UNSIGNED_32BIT_TO_SAMPLE(d,clips) ((sox_sample_t)(d)^SOX_SAMPLE_NEG) + +/** +Client API: +Converts signed 32-bit integer to sox_sample_t. +@param d Input sample to be converted. +@param clips The parameter is not used. +@returns SoX native sample value. +*/ +#define SOX_SIGNED_32BIT_TO_SAMPLE(d,clips) (sox_sample_t)(d) + +/** +Client API: +Converts 32-bit float to sox_sample_t. +@param d Input sample to be converted, range [-1, 1). +@param clips Variable to increment if the input sample is too large or too small. +@returns SoX native sample value. +*/ +#define SOX_FLOAT_32BIT_TO_SAMPLE(d,clips) (sox_sample_t)(LSX_USE_VAR(sox_macro_temp_sample),sox_macro_temp_double=(d)*(SOX_SAMPLE_MAX+1.),sox_macro_temp_double=SOX_SAMPLE_MAX+1.?sox_macro_temp_double>SOX_SAMPLE_MAX+1.?++(clips),SOX_SAMPLE_MAX:SOX_SAMPLE_MAX:sox_macro_temp_double) + +/** +Client API: +Converts 64-bit float to sox_sample_t. +@param d Input sample to be converted, range [-1, 1). +@param clips Variable to increment if the input sample is too large or too small. +@returns SoX native sample value. +*/ +#define SOX_FLOAT_64BIT_TO_SAMPLE(d,clips) (sox_sample_t)(LSX_USE_VAR(sox_macro_temp_sample),sox_macro_temp_double=(d)*(SOX_SAMPLE_MAX+1.),sox_macro_temp_double<0?sox_macro_temp_double<=SOX_SAMPLE_MIN-.5?++(clips),SOX_SAMPLE_MIN:sox_macro_temp_double-.5:sox_macro_temp_double>=SOX_SAMPLE_MAX+.5?sox_macro_temp_double>SOX_SAMPLE_MAX+1.?++(clips),SOX_SAMPLE_MAX:SOX_SAMPLE_MAX:sox_macro_temp_double+.5) + +/** +Client API: +Converts SoX native sample to an unsigned 8-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_UNSIGNED_8BIT(d,clips) SOX_SAMPLE_TO_UNSIGNED(8,d,clips) + +/** +Client API: +Converts SoX native sample to an signed 8-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_SIGNED_8BIT(d,clips) SOX_SAMPLE_TO_SIGNED(8,d,clips) + +/** +Client API: +Converts SoX native sample to an unsigned 16-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_UNSIGNED_16BIT(d,clips) SOX_SAMPLE_TO_UNSIGNED(16,d,clips) + +/** +Client API: +Converts SoX native sample to a signed 16-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_SIGNED_16BIT(d,clips) SOX_SAMPLE_TO_SIGNED(16,d,clips) + +/** +Client API: +Converts SoX native sample to an unsigned 24-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_UNSIGNED_24BIT(d,clips) SOX_SAMPLE_TO_UNSIGNED(24,d,clips) + +/** +Client API: +Converts SoX native sample to a signed 24-bit integer. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_SIGNED_24BIT(d,clips) SOX_SAMPLE_TO_SIGNED(24,d,clips) + +/** +Client API: +Converts SoX native sample to an unsigned 32-bit integer. +@param d Input sample to be converted. +@param clips The parameter is not used. +*/ +#define SOX_SAMPLE_TO_UNSIGNED_32BIT(d,clips) (sox_uint32_t)((d)^SOX_SAMPLE_NEG) + +/** +Client API: +Converts SoX native sample to a signed 32-bit integer. +@param d Input sample to be converted. +@param clips The parameter is not used. +*/ +#define SOX_SAMPLE_TO_SIGNED_32BIT(d,clips) (sox_int32_t)(d) + +/** +Client API: +Converts SoX native sample to a 32-bit float. +@param d Input sample to be converted. +@param clips Variable to increment if input sample is too large. +*/ +#define SOX_SAMPLE_TO_FLOAT_32BIT(d,clips) (LSX_USE_VAR(sox_macro_temp_double),sox_macro_temp_sample=(d),sox_macro_temp_sample>SOX_SAMPLE_MAX-128?++(clips),1:(((sox_macro_temp_sample+128)&~255)*(1./(SOX_SAMPLE_MAX+1.)))) + +/** +Client API: +Converts SoX native sample to a 64-bit float. +@param d Input sample to be converted. +@param clips The parameter is not used. +*/ +#define SOX_SAMPLE_TO_FLOAT_64BIT(d,clips) ((d)*(1./(SOX_SAMPLE_MAX+1.))) + +/** +Client API: +Clips a value of a type that is larger then sox_sample_t (for example, int64) +to sox_sample_t's limits and increment a counter if clipping occurs. +@param samp Value (lvalue) to be clipped, updated as necessary. +@param clips Value (lvalue) that is incremented if clipping is needed. +*/ +#define SOX_SAMPLE_CLIP_COUNT(samp, clips) \ + do { \ + if (samp > SOX_SAMPLE_MAX) \ + { samp = SOX_SAMPLE_MAX; clips++; } \ + else if (samp < SOX_SAMPLE_MIN) \ + { samp = SOX_SAMPLE_MIN; clips++; } \ + } while (0) + +/** +Client API: +Clips a value of a type that is larger then sox_sample_t (for example, int64) +to sox_sample_t's limits and increment a counter if clipping occurs. +@param d Value (rvalue) to be clipped. +@param clips Value (lvalue) that is incremented if clipping is needed. +@returns Clipped value. +*/ +#define SOX_ROUND_CLIP_COUNT(d, clips) \ + ((d) < 0? (d) <= SOX_SAMPLE_MIN - 0.5? ++(clips), SOX_SAMPLE_MIN: (d) - 0.5 \ + : (d) >= SOX_SAMPLE_MAX + 0.5? ++(clips), SOX_SAMPLE_MAX: (d) + 0.5) + +/** +Client API: +Clips a value to the limits of a signed integer of the specified width +and increment a counter if clipping occurs. +@param bits Width (in bits) of target integer type. +@param i Value (rvalue) to be clipped. +@param clips Value (lvalue) that is incremented if clipping is needed. +@returns Clipped value. +*/ +#define SOX_INTEGER_CLIP_COUNT(bits,i,clips) ( \ + (i) >(1 << ((bits)-1))- 1? ++(clips),(1 << ((bits)-1))- 1 : \ + (i) <-1 << ((bits)-1) ? ++(clips),-1 << ((bits)-1) : (i)) + +/** +Client API: +Clips a value to the limits of a 16-bit signed integer and increment a counter +if clipping occurs. +@param i Value (rvalue) to be clipped. +@param clips Value (lvalue) that is incremented if clipping is needed. +@returns Clipped value. +*/ +#define SOX_16BIT_CLIP_COUNT(i,clips) SOX_INTEGER_CLIP_COUNT(16,i,clips) + +/** +Client API: +Clips a value to the limits of a 24-bit signed integer and increment a counter +if clipping occurs. +@param i Value (rvalue) to be clipped. +@param clips Value (lvalue) that is incremented if clipping is needed. +@returns Clipped value. +*/ +#define SOX_24BIT_CLIP_COUNT(i,clips) SOX_INTEGER_CLIP_COUNT(24,i,clips) + +#define SOX_SIZE_MAX ((size_t)(-1)) /**< Client API: Maximum value of size_t. */ + +#define SOX_UNSPEC 0 /**< Client API: Members of sox_signalinfo_t are set to SOX_UNSPEC (= 0) if the actual value is not yet known. */ +#define SOX_UNKNOWN_LEN (sox_uint64_t)(-1) /**< Client API: sox_signalinfo_t.length is set to SOX_UNKNOWN_LEN (= -1) within the effects chain if the actual length is not known. Format handlers currently use SOX_UNSPEC instead. */ +#define SOX_IGNORE_LENGTH (sox_uint64_t)(-2) /**< Client API: sox_signalinfo_t.length is set to SOX_IGNORE_LENGTH (= -2) to indicate that a format handler should ignore length information in file headers. */ + +#define SOX_DEFAULT_CHANNELS 2 /**< Client API: Default channel count is 2 (stereo). */ +#define SOX_DEFAULT_RATE 48000 /**< Client API: Default rate is 48000Hz. */ +#define SOX_DEFAULT_PRECISION 16 /**< Client API: Default precision is 16 bits per sample. */ +#define SOX_DEFAULT_ENCODING SOX_ENCODING_SIGN2 /**< Client API: Default encoding is SIGN2 (linear 2's complement PCM). */ + +#define SOX_LOOP_NONE ((unsigned char)sox_loop_none) /**< Client API: single-shot = 0 */ +#define SOX_LOOP_8 ((unsigned char)sox_loop_8) /**< Client API: 8 loops = 32 */ +#define SOX_LOOP_SUSTAIN_DECAY ((unsigned char)sox_loop_sustain_decay) /**< Client API: AIFF style, one sustain & one decay loop = 64 */ + +#define SOX_MAX_NLOOPS 8 /**< Client API: Maximum number of loops supported by sox_oob_t = 8. */ + +#define SOX_FILE_NOSTDIO 0x0001 /**< Client API: Does not use stdio routines */ +#define SOX_FILE_DEVICE 0x0002 /**< Client API: File is an audio device */ +#define SOX_FILE_PHONY 0x0004 /**< Client API: Phony file/device (for example /dev/null) */ +#define SOX_FILE_REWIND 0x0008 /**< Client API: File should be rewound to write header */ +#define SOX_FILE_BIT_REV 0x0010 /**< Client API: Is file bit-reversed? */ +#define SOX_FILE_NIB_REV 0x0020 /**< Client API: Is file nibble-reversed? */ +#define SOX_FILE_ENDIAN 0x0040 /**< Client API: Is file format endian? */ +#define SOX_FILE_ENDBIG 0x0080 /**< Client API: For endian file format, is it big endian? */ +#define SOX_FILE_MONO 0x0100 /**< Client API: Do channel restrictions allow mono? */ +#define SOX_FILE_STEREO 0x0200 /**< Client API: Do channel restrictions allow stereo? */ +#define SOX_FILE_QUAD 0x0400 /**< Client API: Do channel restrictions allow quad? */ + +#define SOX_FILE_CHANS (SOX_FILE_MONO | SOX_FILE_STEREO | SOX_FILE_QUAD) /**< Client API: No channel restrictions */ +#define SOX_FILE_LIT_END (SOX_FILE_ENDIAN | 0) /**< Client API: File is little-endian */ +#define SOX_FILE_BIG_END (SOX_FILE_ENDIAN | SOX_FILE_ENDBIG) /**< Client API: File is big-endian */ + +#define SOX_EFF_CHAN 1 /**< Client API: Effect might alter the number of channels */ +#define SOX_EFF_RATE 2 /**< Client API: Effect might alter sample rate */ +#define SOX_EFF_PREC 4 /**< Client API: Effect does its own calculation of output sample precision (otherwise a default value is taken, depending on the presence of SOX_EFF_MODIFY) */ +#define SOX_EFF_LENGTH 8 /**< Client API: Effect might alter audio length (as measured in time units, not necessarily in samples) */ +#define SOX_EFF_MCHAN 16 /**< Client API: Effect handles multiple channels internally */ +#define SOX_EFF_NULL 32 /**< Client API: Effect does nothing (can be optimized out of chain) */ +#define SOX_EFF_DEPRECATED 64 /**< Client API: Effect will soon be removed from SoX */ +#define SOX_EFF_GAIN 128 /**< Client API: Effect does not support gain -r */ +#define SOX_EFF_MODIFY 256 /**< Client API: Effect does not modify sample values (but might remove or duplicate samples or insert zeros) */ +#define SOX_EFF_ALPHA 512 /**< Client API: Effect is experimental/incomplete */ +#define SOX_EFF_INTERNAL 1024 /**< Client API: Effect present in libSoX but not valid for use by SoX command-line tools */ + +/** +Client API: +When used as the "whence" parameter of sox_seek, indicates that the specified +offset is relative to the beginning of the file. +*/ +#define SOX_SEEK_SET 0 + +/***************************************************************************** +Forward declarations: +*****************************************************************************/ + +typedef struct sox_format_t sox_format_t; +typedef struct sox_effect_t sox_effect_t; +typedef struct sox_effect_handler_t sox_effect_handler_t; +typedef struct sox_format_handler_t sox_format_handler_t; + +/***************************************************************************** +Function pointers: +*****************************************************************************/ + +/** +Client API: +Callback to write a message to an output device (console or log file), +used by sox_globals_t.output_message_handler. +*/ +typedef void (LSX_API * sox_output_message_handler_t)( + unsigned level, /* 1 = FAIL, 2 = WARN, 3 = INFO, 4 = DEBUG, 5 = DEBUG_MORE, 6 = DEBUG_MOST. */ + LSX_PARAM_IN_Z char const * filename, /* Source code __FILENAME__ from which message originates. */ + LSX_PARAM_IN_PRINTF char const * fmt, /* Message format string. */ + LSX_PARAM_IN va_list ap /* Message format parameters. */ + ); + +/** +Client API: +Callback to retrieve information about a format handler, +used by sox_format_tab_t.fn. +@returns format handler information. +*/ +typedef sox_format_handler_t const * (LSX_API * sox_format_fn_t)(void); + +/** +Client API: +Callback to get information about an effect handler, +used by the table returned from sox_get_effect_fns(void). +@returns Pointer to information about an effect handler. +*/ +typedef sox_effect_handler_t const * (LSX_API *sox_effect_fn_t)(void); + +/** +Client API: +Callback to initialize reader (decoder), used by +sox_format_handler.startread. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_format_handler_startread)( + LSX_PARAM_INOUT sox_format_t * ft /**< Format pointer. */ + ); + +/** +Client API: +Callback to read (decode) a block of samples, +used by sox_format_handler.read. +@returns number of samples read, or 0 if unsuccessful. +*/ +typedef size_t (LSX_API * sox_format_handler_read)( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + LSX_PARAM_OUT_CAP_POST_COUNT(len,return) sox_sample_t *buf, /**< Buffer from which to read samples. */ + size_t len /**< Number of samples available in buf. */ + ); + +/** +Client API: +Callback to close reader (decoder), +used by sox_format_handler.stopread. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_format_handler_stopread)( + LSX_PARAM_INOUT sox_format_t * ft /**< Format pointer. */ + ); + +/** +Client API: +Callback to initialize writer (encoder), +used by sox_format_handler.startwrite. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_format_handler_startwrite)( + LSX_PARAM_INOUT sox_format_t * ft /**< Format pointer. */ + ); + +/** +Client API: +Callback to write (encode) a block of samples, +used by sox_format_handler.write. +@returns number of samples written, or 0 if unsuccessful. +*/ +typedef size_t (LSX_API * sox_format_handler_write)( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + LSX_PARAM_IN_COUNT(len) sox_sample_t const * buf, /**< Buffer to which samples are written. */ + size_t len /**< Capacity of buf, measured in samples. */ + ); + +/** +Client API: +Callback to close writer (decoder), +used by sox_format_handler.stopwrite. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_format_handler_stopwrite)( + LSX_PARAM_INOUT sox_format_t * ft /**< Format pointer. */ + ); + +/** +Client API: +Callback to reposition reader, +used by sox_format_handler.seek. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_format_handler_seek)( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + sox_uint64_t offset /**< Sample offset to which reader should be positioned. */ + ); + +/** +Client API: +Callback to parse command-line arguments (called once per effect), +used by sox_effect_handler.getopts. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_getopts)( + LSX_PARAM_INOUT sox_effect_t * effp, /**< Effect pointer. */ + int argc, /**< Number of arguments in argv. */ + LSX_PARAM_IN_COUNT(argc) char *argv[] /**< Array of command-line arguments. */ + ); + +/** +Client API: +Callback to initialize effect (called once per flow), +used by sox_effect_handler.start. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_start)( + LSX_PARAM_INOUT sox_effect_t * effp /**< Effect pointer. */ + ); + +/** +Client API: +Callback to process samples, +used by sox_effect_handler.flow. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_flow)( + LSX_PARAM_INOUT sox_effect_t * effp, /**< Effect pointer. */ + LSX_PARAM_IN_COUNT(*isamp) sox_sample_t const * ibuf, /**< Buffer from which to read samples. */ + LSX_PARAM_OUT_CAP_POST_COUNT(*osamp,*osamp) sox_sample_t * obuf, /**< Buffer to which samples are written. */ + LSX_PARAM_INOUT size_t *isamp, /**< On entry, contains capacity of ibuf; on exit, contains number of samples consumed. */ + LSX_PARAM_INOUT size_t *osamp /**< On entry, contains capacity of obuf; on exit, contains number of samples written. */ + ); + +/** +Client API: +Callback to finish getting output after input is complete, +used by sox_effect_handler.drain. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_drain)( + LSX_PARAM_INOUT sox_effect_t * effp, /**< Effect pointer. */ + LSX_PARAM_OUT_CAP_POST_COUNT(*osamp,*osamp) sox_sample_t *obuf, /**< Buffer to which samples are written. */ + LSX_PARAM_INOUT size_t *osamp /**< On entry, contains capacity of obuf; on exit, contains number of samples written. */ + ); + +/** +Client API: +Callback to shut down effect (called once per flow), +used by sox_effect_handler.stop. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_stop)( + LSX_PARAM_INOUT sox_effect_t * effp /**< Effect pointer. */ + ); + +/** +Client API: +Callback to shut down effect (called once per effect), +used by sox_effect_handler.kill. +@returns SOX_SUCCESS if successful. +*/ +typedef int (LSX_API * sox_effect_handler_kill)( + LSX_PARAM_INOUT sox_effect_t * effp /**< Effect pointer. */ + ); + +/** +Client API: +Callback called while flow is running (called once per buffer), +used by sox_flow_effects.callback. +@returns SOX_SUCCESS to continue, other value to abort flow. +*/ +typedef int (LSX_API * sox_flow_effects_callback)( + sox_bool all_done, + void * client_data + ); + +/** +Client API: +Callback for enumerating the contents of a playlist, +used by the sox_parse_playlist function. +@returns SOX_SUCCESS if successful, any other value to abort playlist enumeration. +*/ +typedef int (LSX_API * sox_playlist_callback_t)( + void * callback_data, + LSX_PARAM_IN_Z char const * filename + ); + +/***************************************************************************** +Structures: +*****************************************************************************/ + +/** +Client API: +Information about a build of libSoX, returned from the sox_version_info +function. +*/ +typedef struct sox_version_info_t { + size_t size; /**< structure size = sizeof(sox_version_info_t) */ + sox_version_flags_t flags; /**< feature flags = popen | magic | threads | memopen */ + sox_uint32_t version_code; /**< version number = 0x140400 */ + char const * version; /**< version string = sox_version(), for example, "14.4.0" */ + char const * version_extra;/**< version extra info or null = "PACKAGE_EXTRA", for example, "beta" */ + char const * time; /**< build time = "__DATE__ __TIME__", for example, "Jan 7 2010 03:31:50" */ + char const * distro; /**< distro or null = "DISTRO", for example, "Debian" */ + char const * compiler; /**< compiler info or null, for example, "msvc 160040219" */ + char const * arch; /**< arch, for example, "1248 48 44 L OMP" */ + /* new info should be added at the end for version backwards-compatibility. */ +} sox_version_info_t; + +/** +Client API: +Global parameters (for effects & formats), returned from the sox_get_globals +function. +*/ +typedef struct sox_globals_t { +/* public: */ + unsigned verbosity; /**< messages are only written if globals.verbosity >= message.level */ + sox_output_message_handler_t output_message_handler; /**< client-specified message output callback */ + sox_bool repeatable; /**< true to use pre-determined timestamps and PRNG seed */ + + /** + Default size (in bytes) used by libSoX for blocks of sample data. + Plugins should use similarly-sized buffers to get best performance. + */ + size_t bufsiz; + + /** + Default size (in bytes) used by libSoX for blocks of input sample data. + Plugins should use similarly-sized buffers to get best performance. + */ + size_t input_bufsiz; + + sox_int32_t ranqd1; /**< Can be used to re-seed libSoX's PRNG */ + + char const * stdin_in_use_by; /**< Private: tracks the name of the handler currently using stdin */ + char const * stdout_in_use_by; /**< Private: tracks the name of the handler currently using stdout */ + char const * subsystem; /**< Private: tracks the name of the handler currently writing an output message */ + char * tmp_path; /**< Private: client-configured path to use for temporary files */ + sox_bool use_magic; /**< Private: true if client has requested use of 'magic' file-type detection */ + sox_bool use_threads; /**< Private: true if client has requested parallel effects processing */ +} sox_globals_t; + +/** +Client API: +Signal parameters; members should be set to SOX_UNSPEC (= 0) if unknown. +*/ +typedef struct sox_signalinfo_t { + sox_rate_t rate; /**< samples per second, 0 if unknown */ + unsigned channels; /**< number of sound channels, 0 if unknown */ + unsigned precision; /**< bits per sample, 0 if unknown */ + sox_uint64_t length; /**< samples * chans in file, 0 if unknown, -1 if unspecified */ + double * mult; /**< Effects headroom multiplier; may be null */ +} sox_signalinfo_t; + +/** +Client API: +Basic information about an encoding. +*/ +typedef struct sox_encodings_info_t { + sox_encodings_flags_t flags; /**< lossy once (lossy1), lossy twice (lossy2), or lossless (none). */ + char const * name; /**< encoding name. */ + char const * desc; /**< encoding description. */ +} sox_encodings_info_t; + +/** +Client API: +Encoding parameters. +*/ +typedef struct sox_encodinginfo_t { + sox_encoding_t encoding; /**< format of sample numbers */ + unsigned bits_per_sample;/**< 0 if unknown or variable; uncompressed value if lossless; compressed value if lossy */ + double compression; /**< compression factor (where applicable) */ + + /** + Should bytes be reversed? If this is default during sox_open_read or + sox_open_write, libSoX will set them to either no or yes according to the + machine or format default. + */ + sox_option_t reverse_bytes; + + /** + Should nibbles be reversed? If this is default during sox_open_read or + sox_open_write, libSoX will set them to either no or yes according to the + machine or format default. + */ + sox_option_t reverse_nibbles; + + /** + Should bits be reversed? If this is default during sox_open_read or + sox_open_write, libSoX will set them to either no or yes according to the + machine or format default. + */ + sox_option_t reverse_bits; + + /** + If set to true, the format should reverse its default endianness. + */ + sox_bool opposite_endian; +} sox_encodinginfo_t; + +/** +Client API: +Looping parameters (out-of-band data). +*/ +typedef struct sox_loopinfo_t { + sox_uint64_t start; /**< first sample */ + sox_uint64_t length; /**< length */ + unsigned count; /**< number of repeats, 0=forever */ + unsigned char type; /**< 0=no, 1=forward, 2=forward/back (see sox_loop_* for valid values). */ +} sox_loopinfo_t; + +/** +Client API: +Instrument information. +*/ +typedef struct sox_instrinfo_t{ + signed char MIDInote; /**< for unity pitch playback */ + signed char MIDIlow; /**< MIDI pitch-bend low range */ + signed char MIDIhi; /**< MIDI pitch-bend high range */ + unsigned char loopmode; /**< 0=no, 1=forward, 2=forward/back (see sox_loop_* values) */ + unsigned nloops; /**< number of active loops (max SOX_MAX_NLOOPS). */ +} sox_instrinfo_t; + +/** +Client API: +File buffer info. Holds info so that data can be read in blocks. +*/ +typedef struct sox_fileinfo_t { + char *buf; /**< Pointer to data buffer */ + size_t size; /**< Size of buffer in bytes */ + size_t count; /**< Count read into buffer */ + size_t pos; /**< Position in buffer */ +} sox_fileinfo_t; + +/** +Client API: +Handler structure defined by each format. +*/ +struct sox_format_handler_t { + unsigned sox_lib_version_code; /**< Checked on load; must be 1st in struct*/ + char const * description; /**< short description of format */ + char const * const * names; /**< null-terminated array of filename extensions that are handled by this format */ + unsigned int flags; /**< File flags (SOX_FILE_* values). */ + sox_format_handler_startread startread; /**< called to initialize reader (decoder) */ + sox_format_handler_read read; /**< called to read (decode) a block of samples */ + sox_format_handler_stopread stopread; /**< called to close reader (decoder); may be null if no closing necessary */ + sox_format_handler_startwrite startwrite; /**< called to initialize writer (encoder) */ + sox_format_handler_write write; /**< called to write (encode) a block of samples */ + sox_format_handler_stopwrite stopwrite; /**< called to close writer (decoder); may be null if no closing necessary */ + sox_format_handler_seek seek; /**< called to reposition reader; may be null if not supported */ + + /** + Array of values indicating the encodings and precisions supported for + writing (encoding). Precisions specified with default precision first. + Encoding, precision, precision, ..., 0, repeat. End with one more 0. + Example: + unsigned const * formats = { + SOX_ENCODING_SIGN2, 16, 24, 0, // Support SIGN2 at 16 and 24 bits, default to 16 bits. + SOX_ENCODING_UNSIGNED, 8, 0, // Support UNSIGNED at 8 bits, default to 8 bits. + 0 // No more supported encodings. + }; + */ + unsigned const * write_formats; + + /** + Array of sample rates (samples per second) supported for writing (encoding). + NULL if all (or almost all) rates are supported. End with 0. + */ + sox_rate_t const * write_rates; + + /** + SoX will automatically allocate a buffer in which the handler can store data. + Specify the size of the buffer needed here. Usually this will be sizeof(your_struct). + The buffer will be allocated and zeroed before the call to startread/startwrite. + The buffer will be freed after the call to stopread/stopwrite. + The buffer will be provided via format.priv in each call to the handler. + */ + size_t priv_size; +}; + +/** +Client API: +Comments, instrument info, loop info (out-of-band data). +*/ +typedef struct sox_oob_t{ + /* Decoded: */ + sox_comments_t comments; /**< Comment strings in id=value format. */ + sox_instrinfo_t instr; /**< Instrument specification */ + sox_loopinfo_t loops[SOX_MAX_NLOOPS]; /**< Looping specification */ + + /* TBD: Non-decoded chunks, etc: */ +} sox_oob_t; + +/** +Client API: +Data passed to/from the format handler +*/ +struct sox_format_t { + char * filename; /**< File name */ + + /** + Signal specifications for reader (decoder) or writer (encoder): + sample rate, number of channels, precision, length, headroom multiplier. + Any info specified by the user is here on entry to startread or + startwrite. Info will be SOX_UNSPEC if the user provided no info. + At exit from startread, should be completely filled in, using + either data from the file's headers (if available) or whatever + the format is guessing/assuming (if header data is not available). + At exit from startwrite, should be completely filled in, using + either the data that was specified, or values chosen by the format + based on the format's defaults or capabilities. + */ + sox_signalinfo_t signal; + + /** + Encoding specifications for reader (decoder) or writer (encoder): + encoding (sample format), bits per sample, compression rate, endianness. + Should be filled in by startread. Values specified should be used + by startwrite when it is configuring the encoding parameters. + */ + sox_encodinginfo_t encoding; + + char * filetype; /**< Type of file, as determined by header inspection or libmagic. */ + sox_oob_t oob; /**< comments, instrument info, loop info (out-of-band data) */ + sox_bool seekable; /**< Can seek on this file */ + char mode; /**< Read or write mode ('r' or 'w') */ + sox_uint64_t olength; /**< Samples * chans written to file */ + sox_uint64_t clips; /**< Incremented if clipping occurs */ + int sox_errno; /**< Failure error code */ + char sox_errstr[256]; /**< Failure error text */ + void * fp; /**< File stream pointer */ + lsx_io_type io_type; /**< Stores whether this is a file, pipe or URL */ + sox_uint64_t tell_off; /**< Current offset within file */ + sox_uint64_t data_start; /**< Offset at which headers end and sound data begins (set by lsx_check_read_params) */ + sox_format_handler_t handler; /**< Format handler for this file */ + void * priv; /**< Format handler's private data area */ +}; + +/** +Client API: +Information about a loaded format handler, including the format name and a +function pointer that can be invoked to get additional information about the +format. +*/ +typedef struct sox_format_tab_t { + char *name; /**< Name of format handler */ + sox_format_fn_t fn; /**< Function to call to get format handler's information */ +} sox_format_tab_t; + +/** +Client API: +Global parameters for effects. +*/ +typedef struct sox_effects_globals_t { + sox_plot_t plot; /**< To help the user choose effect & options */ + sox_globals_t * global_info; /**< Pointer to associated SoX globals */ +} sox_effects_globals_t; + +/** +Client API: +Effect handler information. +*/ +struct sox_effect_handler_t { + char const * name; /**< Effect name */ + char const * usage; /**< Short explanation of parameters accepted by effect */ + unsigned int flags; /**< Combination of SOX_EFF_* flags */ + sox_effect_handler_getopts getopts; /**< Called to parse command-line arguments (called once per effect). */ + sox_effect_handler_start start; /**< Called to initialize effect (called once per flow). */ + sox_effect_handler_flow flow; /**< Called to process samples. */ + sox_effect_handler_drain drain; /**< Called to finish getting output after input is complete. */ + sox_effect_handler_stop stop; /**< Called to shut down effect (called once per flow). */ + sox_effect_handler_kill kill; /**< Called to shut down effect (called once per effect). */ + size_t priv_size; /**< Size of private data SoX should pre-allocate for effect */ +}; + +/** +Client API: +Effect information. +*/ +struct sox_effect_t { + sox_effects_globals_t * global_info; /**< global effect parameters */ + sox_signalinfo_t in_signal; /**< Information about the incoming data stream */ + sox_signalinfo_t out_signal; /**< Information about the outgoing data stream */ + sox_encodinginfo_t const * in_encoding; /**< Information about the incoming data encoding */ + sox_encodinginfo_t const * out_encoding; /**< Information about the outgoing data encoding */ + sox_effect_handler_t handler; /**< The handler for this effect */ + sox_sample_t * obuf; /**< output buffer */ + size_t obeg; /**< output buffer: start of valid data section */ + size_t oend; /**< output buffer: one past valid data section (oend-obeg is length of current content) */ + size_t imin; /**< minimum input buffer content required for calling this effect's flow function; set via lsx_effect_set_imin() */ + sox_uint64_t clips; /**< increment if clipping occurs */ + size_t flows; /**< 1 if MCHAN, number of chans otherwise */ + size_t flow; /**< flow number */ + void * priv; /**< Effect's private data area (each flow has a separate copy) */ +}; + +/** +Client API: +Chain of effects to be applied to a stream. +*/ +typedef struct sox_effects_chain_t { + sox_effect_t **effects; /**< Table of effects to be applied to a stream */ + unsigned table_size; /**< Number of entries in effects table */ + unsigned length; /**< Number of effects to be applied */ + sox_sample_t **ibufc; /**< Channel interleave buffer */ + sox_sample_t **obufc; /**< Channel interleave buffer */ + sox_effects_globals_t global_info; /**< Copy of global effects settings */ + sox_encodinginfo_t const * in_enc; /**< Input encoding */ + sox_encodinginfo_t const * out_enc; /**< Output encoding */ +} sox_effects_chain_t; + +/***************************************************************************** +Functions: +*****************************************************************************/ + +/** +Client API: +Returns version number string of libSoX, for example, "14.4.0". +@returns The version number string of libSoX, for example, "14.4.0". +*/ +LSX_RETURN_VALID_Z LSX_RETURN_PURE +char const * +LSX_API +sox_version(void); + +/** +Client API: +Returns information about this build of libsox. +@returns Pointer to a version information structure. +*/ +LSX_RETURN_VALID LSX_RETURN_PURE +sox_version_info_t const * +LSX_API +sox_version_info(void); + +/** +Client API: +Returns a pointer to the structure with libSoX's global settings. +@returns a pointer to the structure with libSoX's global settings. +*/ +LSX_RETURN_VALID LSX_RETURN_PURE +sox_globals_t * +LSX_API +sox_get_globals(void); + +/** +Client API: +Deprecated macro that returns the structure with libSoX's global settings +as an lvalue. +*/ +#define sox_globals (*sox_get_globals()) + +/** +Client API: +Returns a pointer to the list of available encodings. +End of list indicated by name == NULL. +@returns pointer to the list of available encodings. +*/ +LSX_RETURN_ARRAY LSX_RETURN_PURE +sox_encodings_info_t const * +LSX_API +sox_get_encodings_info(void); + +/** +Client API: +Deprecated macro that returns the list of available encodings. +End of list indicated by name == NULL. +*/ +#define sox_encodings_info (sox_get_encodings_info()) + +/** +Client API: +Fills in an encodinginfo with default values. +*/ +void +LSX_API +sox_init_encodinginfo( + LSX_PARAM_OUT sox_encodinginfo_t * e /**< Pointer to uninitialized encoding info structure to be initialized. */ + ); + +/** +Client API: +Given an encoding (for example, SIGN2) and the encoded bits_per_sample (for +example, 16), returns the number of useful bits per sample in the decoded data +(for example, 16), or returns 0 to indicate that the value returned by the +format handler should be used instead of a pre-determined precision. +@returns the number of useful bits per sample in the decoded data (for example +16), or returns 0 to indicate that the value returned by the format handler +should be used instead of a pre-determined precision. +*/ +LSX_RETURN_PURE +unsigned +LSX_API +sox_precision( + sox_encoding_t encoding, /**< Encoding for which to lookup precision information. */ + unsigned bits_per_sample /**< The number of encoded bits per sample. */ + ); + +/** +Client API: +Returns the number of items in the metadata block. +@returns the number of items in the metadata block. +*/ +size_t +LSX_API +sox_num_comments( + LSX_PARAM_IN_OPT sox_comments_t comments /**< Metadata block. */ + ); + +/** +Client API: +Adds an "id=value" item to the metadata block. +*/ +void +LSX_API +sox_append_comment( + LSX_PARAM_DEREF_PRE_MAYBENULL LSX_PARAM_DEREF_POST_NOTNULL sox_comments_t * comments, /**< Metadata block. */ + LSX_PARAM_IN_Z char const * item /**< Item to be added in "id=value" format. */ + ); + +/** +Client API: +Adds a newline-delimited list of "id=value" items to the metadata block. +*/ +void +LSX_API +sox_append_comments( + LSX_PARAM_DEREF_PRE_MAYBENULL LSX_PARAM_DEREF_POST_NOTNULL sox_comments_t * comments, /**< Metadata block. */ + LSX_PARAM_IN_Z char const * items /**< Newline-separated list of items to be added, for example "id1=value1\\nid2=value2". */ + ); + +/** +Client API: +Duplicates the metadata block. +@returns the copied metadata block. +*/ +LSX_RETURN_OPT +sox_comments_t +LSX_API +sox_copy_comments( + LSX_PARAM_IN_OPT sox_comments_t comments /**< Metadata block to copy. */ + ); + +/** +Client API: +Frees the metadata block. +*/ +void +LSX_API +sox_delete_comments( + LSX_PARAM_DEREF_PRE_MAYBENULL LSX_PARAM_DEREF_POST_NULL sox_comments_t * comments /**< Metadata block. */ + ); + +/** +Client API: +If "id=value" is found, return value, else return null. +@returns value, or null if value not found. +*/ +LSX_RETURN_OPT +char const * +LSX_API +sox_find_comment( + LSX_PARAM_IN_OPT sox_comments_t comments, /**< Metadata block in which to search. */ + LSX_PARAM_IN_Z char const * id /**< Id for which to search */ + ); + +/** +Client API: +Find and load format handler plugins. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_format_init(void); + +/** +Client API: +Unload format handler plugins. +*/ +void +LSX_API +sox_format_quit(void); + +/** +Client API: +Initialize effects library. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_init(void); + +/** +Client API: +Close effects library and unload format handler plugins. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_quit(void); + +/** +Client API: +Returns the table of format handler names and functions. +@returns the table of format handler names and functions. +*/ +LSX_RETURN_ARRAY LSX_RETURN_PURE +sox_format_tab_t const * +LSX_API +sox_get_format_fns(void); + +/** +Client API: +Deprecated macro that returns the table of format handler names and functions. +*/ +#define sox_format_fns (sox_get_format_fns()) + +/** +Client API: +Opens a decoding session for a file. Returned handle must be closed with sox_close(). +@returns The handle for the new session, or null on failure. +*/ +LSX_RETURN_OPT +sox_format_t * +LSX_API +sox_open_read( + LSX_PARAM_IN_Z char const * path, /**< Path to file to be opened (required). */ + LSX_PARAM_IN_OPT sox_signalinfo_t const * signal, /**< Information already known about audio stream, or NULL if none. */ + LSX_PARAM_IN_OPT sox_encodinginfo_t const * encoding, /**< Information already known about sample encoding, or NULL if none. */ + LSX_PARAM_IN_OPT_Z char const * filetype /**< Previously-determined file type, or NULL to auto-detect. */ + ); + +/** +Client API: +Opens a decoding session for a memory buffer. Returned handle must be closed with sox_close(). +@returns The handle for the new session, or null on failure. +*/ +LSX_RETURN_OPT +sox_format_t * +LSX_API +sox_open_mem_read( + LSX_PARAM_IN_BYTECOUNT(buffer_size) void * buffer, /**< Pointer to audio data buffer (required). */ + size_t buffer_size,/**< Number of bytes to read from audio data buffer. */ + LSX_PARAM_IN_OPT sox_signalinfo_t const * signal, /**< Information already known about audio stream, or NULL if none. */ + LSX_PARAM_IN_OPT sox_encodinginfo_t const * encoding, /**< Information already known about sample encoding, or NULL if none. */ + LSX_PARAM_IN_OPT_Z char const * filetype /**< Previously-determined file type, or NULL to auto-detect. */ + ); + +/** +Client API: +Returns true if the format handler for the specified file type supports the specified encoding. +@returns true if the format handler for the specified file type supports the specified encoding. +*/ +sox_bool +LSX_API +sox_format_supports_encoding( + LSX_PARAM_IN_OPT_Z char const * path, /**< Path to file to be examined (required if filetype is NULL). */ + LSX_PARAM_IN_OPT_Z char const * filetype, /**< Previously-determined file type, or NULL to use extension from path. */ + LSX_PARAM_IN sox_encodinginfo_t const * encoding /**< Encoding for which format handler should be queried. */ + ); + +/** +Client API: +Gets the format handler for a specified file type. +@returns The found format handler, or null if not found. +*/ +LSX_RETURN_OPT +sox_format_handler_t const * +LSX_API +sox_write_handler( + LSX_PARAM_IN_OPT_Z char const * path, /**< Path to file (required if filetype is NULL). */ + LSX_PARAM_IN_OPT_Z char const * filetype, /**< Filetype for which handler is needed, or NULL to use extension from path. */ + LSX_PARAM_OUT_OPT char const * * filetype1 /**< Receives the filetype that was detected. Pass NULL if not needed. */ + ); + +/** +Client API: +Opens an encoding session for a file. Returned handle must be closed with sox_close(). +@returns The new session handle, or null on failure. +*/ +LSX_RETURN_OPT +sox_format_t * +LSX_API +sox_open_write( + LSX_PARAM_IN_Z char const * path, /**< Path to file to be written (required). */ + LSX_PARAM_IN sox_signalinfo_t const * signal, /**< Information about desired audio stream (required). */ + LSX_PARAM_IN_OPT sox_encodinginfo_t const * encoding, /**< Information about desired sample encoding, or NULL to use defaults. */ + LSX_PARAM_IN_OPT_Z char const * filetype, /**< Previously-determined file type, or NULL to auto-detect. */ + LSX_PARAM_IN_OPT sox_oob_t const * oob, /**< Out-of-band data to add to file, or NULL if none. */ + LSX_PARAM_IN_OPT sox_bool (LSX_API * overwrite_permitted)(LSX_PARAM_IN_Z char const * filename) /**< Called if file exists to determine whether overwrite is ok. */ + ); + +/** +Client API: +Opens an encoding session for a memory buffer. Returned handle must be closed with sox_close(). +@returns The new session handle, or null on failure. +*/ +LSX_RETURN_OPT +sox_format_t * +LSX_API +sox_open_mem_write( + LSX_PARAM_OUT_BYTECAP(buffer_size) void * buffer, /**< Pointer to audio data buffer that receives data (required). */ + LSX_PARAM_IN size_t buffer_size, /**< Maximum number of bytes to write to audio data buffer. */ + LSX_PARAM_IN sox_signalinfo_t const * signal, /**< Information about desired audio stream (required). */ + LSX_PARAM_IN_OPT sox_encodinginfo_t const * encoding, /**< Information about desired sample encoding, or NULL to use defaults. */ + LSX_PARAM_IN_OPT_Z char const * filetype, /**< Previously-determined file type, or NULL to auto-detect. */ + LSX_PARAM_IN_OPT sox_oob_t const * oob /**< Out-of-band data to add to file, or NULL if none. */ + ); + +/** +Client API: +Opens an encoding session for a memstream buffer. Returned handle must be closed with sox_close(). +@returns The new session handle, or null on failure. +*/ +LSX_RETURN_OPT +sox_format_t * +LSX_API +sox_open_memstream_write( + LSX_PARAM_OUT char * * buffer_ptr, /**< Receives pointer to audio data buffer that receives data (required). */ + LSX_PARAM_OUT size_t * buffer_size_ptr, /**< Receives size of data written to audio data buffer (required). */ + LSX_PARAM_IN sox_signalinfo_t const * signal, /**< Information about desired audio stream (required). */ + LSX_PARAM_IN_OPT sox_encodinginfo_t const * encoding, /**< Information about desired sample encoding, or NULL to use defaults. */ + LSX_PARAM_IN_OPT_Z char const * filetype, /**< Previously-determined file type, or NULL to auto-detect. */ + LSX_PARAM_IN_OPT sox_oob_t const * oob /**< Out-of-band data to add to file, or NULL if none. */ + ); + +/** +Client API: +Reads samples from a decoding session into a sample buffer. +@returns Number of samples decoded, or 0 for EOF. +*/ +size_t +LSX_API +sox_read( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + LSX_PARAM_OUT_CAP_POST_COUNT(len,return) sox_sample_t *buf, /**< Buffer from which to read samples. */ + size_t len /**< Number of samples available in buf. */ + ); + +/** +Client API: +Writes samples to an encoding session from a sample buffer. +@returns Number of samples encoded. +*/ +size_t +LSX_API +sox_write( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + LSX_PARAM_IN_COUNT(len) sox_sample_t const * buf, /**< Buffer from which to read samples. */ + size_t len /**< Number of samples available in buf. */ + ); + +/** +Client API: +Closes an encoding or decoding session. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_close( + LSX_PARAM_INOUT sox_format_t * ft /**< Format pointer. */ + ); + +/** +Client API: +Sets the location at which next samples will be decoded. Returns SOX_SUCCESS if successful. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_seek( + LSX_PARAM_INOUT sox_format_t * ft, /**< Format pointer. */ + sox_uint64_t offset, /**< Sample offset at which to position reader. */ + int whence /**< Set to SOX_SEEK_SET. */ + ); + +/** +Client API: +Finds a format handler by name. +@returns Format handler data, or null if not found. +*/ +LSX_RETURN_OPT +sox_format_handler_t const * +LSX_API +sox_find_format( + LSX_PARAM_IN_Z char const * name, /**< Name of format handler to find. */ + sox_bool ignore_devices /**< Set to true to ignore device names. */ + ); + +/** +Client API: +Returns global parameters for effects +@returns global parameters for effects. +*/ +LSX_RETURN_VALID LSX_RETURN_PURE +sox_effects_globals_t * +LSX_API +sox_get_effects_globals(void); + +/** +Client API: +Deprecated macro that returns global parameters for effects. +*/ +#define sox_effects_globals (*sox_get_effects_globals()) + +/** +Client API: +Finds the effect handler with the given name. +@returns Effect pointer, or null if not found. +*/ +LSX_RETURN_OPT LSX_RETURN_PURE +sox_effect_handler_t const * +LSX_API +sox_find_effect( + LSX_PARAM_IN_Z char const * name /**< Name of effect to find. */ + ); + +/** +Client API: +Creates an effect using the given handler. +@returns The new effect, or null if not found. +*/ +LSX_RETURN_OPT +sox_effect_t * +LSX_API +sox_create_effect( + LSX_PARAM_IN sox_effect_handler_t const * eh /**< Handler to use for effect. */ + ); + +/** +Client API: +Applies the command-line options to the effect. +@returns the number of arguments consumed. +*/ +int +LSX_API +sox_effect_options( + LSX_PARAM_IN sox_effect_t *effp, /**< Effect pointer on which to set options. */ + int argc, /**< Number of arguments in argv. */ + LSX_PARAM_IN_COUNT(argc) char * const argv[] /**< Array of command-line options. */ + ); + +/** +Client API: +Returns an array containing the known effect handlers. +@returns An array containing the known effect handlers. +*/ +LSX_RETURN_VALID_Z LSX_RETURN_PURE +sox_effect_fn_t const * +LSX_API +sox_get_effect_fns(void); + +/** +Client API: +Deprecated macro that returns an array containing the known effect handlers. +*/ +#define sox_effect_fns (sox_get_effect_fns()) + +/** +Client API: +Initializes an effects chain. Returned handle must be closed with sox_delete_effects_chain(). +@returns Handle, or null on failure. +*/ +LSX_RETURN_OPT +sox_effects_chain_t * +LSX_API +sox_create_effects_chain( + LSX_PARAM_IN sox_encodinginfo_t const * in_enc, /**< Input encoding. */ + LSX_PARAM_IN sox_encodinginfo_t const * out_enc /**< Output encoding. */ + ); + +/** +Client API: +Closes an effects chain. +*/ +void +LSX_API +sox_delete_effects_chain( + LSX_PARAM_INOUT sox_effects_chain_t *ecp /**< Effects chain pointer. */ + ); + +/** +Client API: +Adds an effect to the effects chain, returns SOX_SUCCESS if successful. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_add_effect( + LSX_PARAM_INOUT sox_effects_chain_t * chain, /**< Effects chain to which effect should be added . */ + LSX_PARAM_INOUT sox_effect_t * effp, /**< Effect to be added. */ + LSX_PARAM_INOUT sox_signalinfo_t * in, /**< Input format. */ + LSX_PARAM_IN sox_signalinfo_t const * out /**< Output format. */ + ); + +/** +Client API: +Runs the effects chain, returns SOX_SUCCESS if successful. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_flow_effects( + LSX_PARAM_INOUT sox_effects_chain_t * chain, /**< Effects chain to run. */ + LSX_PARAM_IN_OPT sox_flow_effects_callback callback, /**< Callback for monitoring flow progress. */ + LSX_PARAM_IN_OPT void * client_data /**< Data to pass into callback. */ + ); + +/** +Client API: +Gets the number of clips that occurred while running an effects chain. +@returns the number of clips that occurred while running an effects chain. +*/ +sox_uint64_t +LSX_API +sox_effects_clips( + LSX_PARAM_IN sox_effects_chain_t * chain /**< Effects chain from which to read clip information. */ + ); + +/** +Client API: +Shuts down an effect (calls stop on each of its flows). +@returns the number of clips from all flows. +*/ +sox_uint64_t +LSX_API +sox_stop_effect( + LSX_PARAM_INOUT_COUNT(effp->flows) sox_effect_t * effp /**< Effect to stop. */ + ); + +/** +Client API: +Adds an already-initialized effect to the end of the chain. +*/ +void +LSX_API +sox_push_effect_last( + LSX_PARAM_INOUT sox_effects_chain_t * chain, /**< Effects chain to which effect should be added. */ + LSX_PARAM_INOUT sox_effect_t * effp /**< Effect to be added. */ + ); + +/** +Client API: +Removes and returns an effect from the end of the chain. +@returns the removed effect, or null if no effects. +*/ +LSX_RETURN_OPT +sox_effect_t * +LSX_API +sox_pop_effect_last( + LSX_PARAM_INOUT sox_effects_chain_t *chain /**< Effects chain from which to remove an effect. */ + ); + +/** +Client API: +Shut down and delete an effect. +*/ +void +LSX_API +sox_delete_effect( + LSX_PARAM_INOUT_COUNT(effp->flows) sox_effect_t *effp /**< Effect to be deleted. */ + ); + +/** +Client API: +Shut down and delete the last effect in the chain. +*/ +void +LSX_API +sox_delete_effect_last( + LSX_PARAM_INOUT sox_effects_chain_t *chain /**< Effects chain from which to remove the last effect. */ + ); + +/** +Client API: +Shut down and delete all effects in the chain. +*/ +void +LSX_API +sox_delete_effects( + LSX_PARAM_INOUT sox_effects_chain_t *chain /**< Effects chain from which to delete effects. */ + ); + +/** +Client API: +Gets the sample offset of the start of the trim, useful for efficiently +skipping the part that will be trimmed anyway (get trim start, seek, then +clear trim start). +@returns the sample offset of the start of the trim. +*/ +sox_uint64_t +LSX_API +sox_trim_get_start( + LSX_PARAM_IN sox_effect_t * effp /**< Trim effect. */ + ); + +/** +Client API: +Clears the start of the trim to 0. +*/ +void +LSX_API +sox_trim_clear_start( + LSX_PARAM_INOUT sox_effect_t * effp /**< Trim effect. */ + ); + +/** +Client API: +Returns true if the specified file is a known playlist file type. +@returns true if the specified file is a known playlist file type. +*/ +sox_bool +LSX_API +sox_is_playlist( + LSX_PARAM_IN_Z char const * filename /**< Name of file to examine. */ + ); + +/** +Client API: +Parses the specified playlist file. +@returns SOX_SUCCESS if successful. +*/ +int +LSX_API +sox_parse_playlist( + LSX_PARAM_IN sox_playlist_callback_t callback, /**< Callback to call for each item in the playlist. */ + void * p, /**< Data to pass to callback. */ + LSX_PARAM_IN char const * const listname /**< Filename of playlist file. */ + ); + +/** +Client API: +Converts a SoX error code into an error string. +@returns error string corresponding to the specified error code, +or a generic message if the error code is not recognized. +*/ +LSX_RETURN_VALID_Z LSX_RETURN_PURE +char const * +LSX_API +sox_strerror( + int sox_errno /**< Error code to look up. */ + ); + +/** +Client API: +Gets the basename of the specified file; for example, the basename of +"/a/b/c.d" would be "c". +@returns the number of characters written to base_buffer, excluding the null, +or 0 on failure. +*/ +size_t +LSX_API +sox_basename( + LSX_PARAM_OUT_Z_CAP_POST_COUNT(base_buffer_len,return) char * base_buffer, /**< Buffer into which basename should be written. */ + size_t base_buffer_len, /**< Size of base_buffer, in bytes. */ + LSX_PARAM_IN_Z char const * filename /**< Filename from which to extract basename. */ + ); + +/***************************************************************************** +Internal API: +WARNING - The items in this section are subject to instability. They only +exist in the public header because sox (the application) currently uses them. +These may be changed or removed in future versions of libSoX. +*****************************************************************************/ + +/** +Plugins API: +Print a fatal error in libSoX. +*/ +void +LSX_API +lsx_fail_impl( + LSX_PARAM_IN_PRINTF char const * fmt, /**< printf-style format string. */ + ...) + LSX_PRINTF12; + +/** +Plugins API: +Print a warning in libSoX. +*/ +void +LSX_API +lsx_warn_impl( + LSX_PARAM_IN_PRINTF char const * fmt, /**< printf-style format string. */ + ...) + LSX_PRINTF12; + +/** +Plugins API: +Print an informational message in libSoX. +*/ +void +LSX_API +lsx_report_impl( + LSX_PARAM_IN_PRINTF char const * fmt, /**< printf-style format string. */ + ...) + LSX_PRINTF12; + +/** +Plugins API: +Print a debug message in libSoX. +*/ +void +LSX_API +lsx_debug_impl( + LSX_PARAM_IN_PRINTF char const * fmt, /**< printf-style format string. */ + ...) + LSX_PRINTF12; + +/** +Plugins API: +Report a fatal error in libSoX; printf-style arguments must follow. +*/ +#define lsx_fail sox_get_globals()->subsystem=__FILE__,lsx_fail_impl + +/** +Plugins API: +Report a warning in libSoX; printf-style arguments must follow. +*/ +#define lsx_warn sox_get_globals()->subsystem=__FILE__,lsx_warn_impl + +/** +Plugins API: +Report an informational message in libSoX; printf-style arguments must follow. +*/ +#define lsx_report sox_get_globals()->subsystem=__FILE__,lsx_report_impl + +/** +Plugins API: +Report a debug message in libSoX; printf-style arguments must follow. +*/ +#define lsx_debug sox_get_globals()->subsystem=__FILE__,lsx_debug_impl + +/** +Plugins API: +String name and integer values for enumerated types (type metadata), for use +with LSX_ENUM_ITEM, lsx_find_enum_text, and lsx_find_enum_value. +*/ +typedef struct lsx_enum_item { + char const *text; /**< String name of enumeration. */ + unsigned value; /**< Integer value of enumeration. */ +} lsx_enum_item; + +/** +Plugins API: +Declares a static instance of an lsx_enum_item structure in format +{ "item", prefixitem }, for use in declaring lsx_enum_item[] arrays. +@param prefix The prefix to prepend to the item in the enumeration symbolic name. +@param item The user-visible text name of the item (must also be a valid C symbol name). +*/ +#define LSX_ENUM_ITEM(prefix, item) {#item, prefix##item}, + +/** +Plugins API: +Flags for use with lsx_find_enum_item. +*/ +enum +{ + lsx_find_enum_item_none = 0, /**< Default parameters (case-insensitive). */ + lsx_find_enum_item_case_sensitive = 1 /**< Enable case-sensitive search. */ +}; + +/** +Plugins API: +Looks up an enumeration by name in an array of lsx_enum_items. +@returns the corresponding item, or null if not found. +*/ +LSX_RETURN_OPT LSX_RETURN_PURE +lsx_enum_item const * +LSX_API +lsx_find_enum_text( + LSX_PARAM_IN_Z char const * text, /**< Name of enumeration to find. */ + LSX_PARAM_IN lsx_enum_item const * lsx_enum_items, /**< Array of items to search, with text == NULL for last item. */ + int flags /**< Search flags: 0 (case-insensitive) or lsx_find_enum_item_case_sensitive (case-sensitive). */ + ); + +/** +Plugins API: +Looks up an enumeration by value in an array of lsx_enum_items. +@returns the corresponding item, or null if not found. +*/ +LSX_RETURN_OPT LSX_RETURN_PURE +lsx_enum_item const * +LSX_API +lsx_find_enum_value( + unsigned value, /**< Enumeration value to find. */ + LSX_PARAM_IN lsx_enum_item const * lsx_enum_items /**< Array of items to search, with text == NULL for last item. */ + ); + +/** +Plugins API: +Looks up a command-line argument in a set of enumeration names, showing an +error message if the argument is not found in the set of names. +@returns The enumeration value corresponding to the matching enumeration, or +INT_MAX if the argument does not match any enumeration name. +*/ +LSX_RETURN_PURE +int +LSX_API +lsx_enum_option( + int c, /**< Option character to which arg is associated, for example with -a, c would be 'a'. */ + LSX_PARAM_IN_Z char const * arg, /**< Argument to find in enumeration list. */ + LSX_PARAM_IN lsx_enum_item const * items /**< Array of items to search, with text == NULL for last item. */ + ); + +/** +Plugins API: +Determines whether the specified string ends with the specified suffix (case-sensitive). +@returns true if the specified string ends with the specified suffix. +*/ +LSX_RETURN_PURE +sox_bool +LSX_API +lsx_strends( + LSX_PARAM_IN_Z char const * str, /**< String to search. */ + LSX_PARAM_IN_Z char const * end /**< Suffix to search for. */ + ); + +/** +Plugins API: +Finds the file extension for a filename. +@returns the file extension, not including the '.', or null if filename does +not have an extension. +*/ +LSX_RETURN_VALID_Z LSX_RETURN_PURE +char const * +LSX_API +lsx_find_file_extension( + LSX_PARAM_IN_Z char const * pathname /**< Filename to search for extension. */ + ); + +/** +Plugins API: +Formats the specified number with up to three significant figures and adds a +metric suffix in place of the exponent, such as 1.23G. +@returns A static buffer with the formatted number, valid until the next time +this function is called (note: not thread safe). +*/ +LSX_RETURN_VALID_Z +char const * +LSX_API +lsx_sigfigs3( + double number /**< Number to be formatted. */ + ); + +/** +Plugins API: +Formats the specified number as a percentage, showing up to three significant +figures. +@returns A static buffer with the formatted number, valid until the next time +this function is called (note: not thread safe). +*/ +LSX_RETURN_VALID_Z +char const * +LSX_API +lsx_sigfigs3p( + double percentage /**< Number to be formatted. */ + ); + +/** +Plugins API: +Allocates, deallocates, or resizes; like C's realloc, except that this version +terminates the running application if unable to allocate the requested memory. +@returns New buffer, or null if buffer was freed. +*/ +LSX_RETURN_OPT +void * +LSX_API +lsx_realloc( + LSX_PARAM_IN_OPT void *ptr, /**< Pointer to be freed or resized, or null if allocating a new buffer. */ + size_t newsize /**< New size for buffer, or 0 to free the buffer. */ + ); + +/** +Plugins API: +Like strcmp, except that the characters are compared without regard to case. +@returns 0 (s1 == s2), negative (s1 < s2), or positive (s1 > s2). +*/ +LSX_RETURN_PURE +int +LSX_API +lsx_strcasecmp( + LSX_PARAM_IN_Z char const * s1, /**< First string. */ + LSX_PARAM_IN_Z char const * s2 /**< Second string. */ + ); + + +/** +Plugins API: +Like strncmp, except that the characters are compared without regard to case. +@returns 0 (s1 == s2), negative (s1 < s2), or positive (s1 > s2). +*/ +LSX_RETURN_PURE +int +LSX_API +lsx_strncasecmp( + LSX_PARAM_IN_Z char const * s1, /**< First string. */ + LSX_PARAM_IN_Z char const * s2, /**< Second string. */ + size_t n /**< Maximum number of characters to examine. */ + ); + +/** +Plugins API: +Is option argument unsupported, required, or optional. +*/ +typedef enum lsx_option_arg_t { + lsx_option_arg_none, /**< Option does not have an argument. */ + lsx_option_arg_required, /**< Option requires an argument. */ + lsx_option_arg_optional /**< Option can optionally be followed by an argument. */ +} lsx_option_arg_t; + +/** +Plugins API: +lsx_getopt_init options. +*/ +typedef enum lsx_getopt_flags_t { + lsx_getopt_flag_none = 0, /**< no flags (no output, not long-only) */ + lsx_getopt_flag_opterr = 1, /**< if set, invalid options trigger lsx_warn output */ + lsx_getopt_flag_longonly = 2 /**< if set, recognize -option as a long option */ +} lsx_getopt_flags_t; + +/** +Plugins API: +lsx_getopt long option descriptor. +*/ +typedef struct lsx_option_t { + char const * name; /**< Name of the long option. */ + lsx_option_arg_t has_arg; /**< Whether the long option supports an argument and, if so, whether the argument is required or optional. */ + int * flag; /**< Flag to set if argument is present. */ + int val; /**< Value to put in flag if argument is present. */ +} lsx_option_t; + +/** +Plugins API: +lsx_getopt session information (initialization data and state). +*/ +typedef struct lsx_getopt_t { + int argc; /**< IN argc: Number of arguments in argv */ + char * const * argv; /**< IN argv: Array of arguments */ + char const * shortopts;/**< IN shortopts: Short option characters */ + lsx_option_t const * longopts; /**< IN longopts: Array of long option descriptors */ + lsx_getopt_flags_t flags; /**< IN flags: Flags for longonly and opterr */ + char const * curpos; /**< INOUT curpos: Maintains state between calls to lsx_getopt */ + int ind; /**< INOUT optind: Maintains the index of next element to be processed */ + int opt; /**< OUT optopt: Receives the option character that caused error */ + char const * arg; /**< OUT optarg: Receives the value of the option's argument */ + int lngind; /**< OUT lngind: Receives the index of the matched long option or -1 if not a long option */ +} lsx_getopt_t; + +/** +Plugins API: +Initializes an lsx_getopt_t structure for use with lsx_getopt. +*/ +void +LSX_API +lsx_getopt_init( + LSX_PARAM_IN int argc, /**< Number of arguments in argv */ + LSX_PARAM_IN_COUNT(argc) char * const * argv, /**< Array of arguments */ + LSX_PARAM_IN_Z char const * shortopts, /**< Short options, for example ":abc:def::ghi" (+/- not supported) */ + LSX_PARAM_IN_OPT lsx_option_t const * longopts, /**< Array of long option descriptors */ + LSX_PARAM_IN lsx_getopt_flags_t flags, /**< Flags for longonly and opterr */ + LSX_PARAM_IN int first, /**< First argv to check (usually 1) */ + LSX_PARAM_OUT lsx_getopt_t * state /**< State object to be initialized */ + ); + +/** +Plugins API: +Gets the next option. Options are parameters that start with "-" or "--". +If no more options, returns -1. If unrecognized short option, returns '?'. +If a recognized short option is missing a required argument, +return (shortopts[0]==':' ? ':' : '?'). If successfully recognized short +option, return the recognized character. If successfully recognized long +option, returns (option.flag ? 0 : option.val). +Note: lsx_getopt does not permute the non-option arguments. +@returns option character (short), val or 0 (long), or -1 (no more). +*/ +int +LSX_API +lsx_getopt( + LSX_PARAM_INOUT lsx_getopt_t * state /**< The getopt state pointer. */ + ); + +/* WARNING END */ + +#if defined(__cplusplus) +} +#endif + +#endif /* SOX_H */ diff --git a/freedv/tags/1.2.2/src/sox/sox_i.h b/freedv/tags/1.2.2/src/sox/sox_i.h new file mode 100644 index 00000000..9d533263 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/sox_i.h @@ -0,0 +1,417 @@ +/* libSoX Internal header + * + * This file is meant for libSoX internal use only + * + * Copyright 2001-2008 Chris Bagwell and SoX Contributors + * + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Chris Bagwell And SoX Contributors are not responsible for + * the consequences of using this software. + */ + +#ifndef SOX_I_H +#define SOX_I_H + +#include "soxomp.h" /* Note: soxomp.h includes soxconfig.h */ +#include "sox.h" + +#define __FREEDV__ + +#if defined HAVE_FMEMOPEN +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#include "util.h" + +#if defined(LSX_EFF_ALIAS) +#undef lsx_debug +#undef lsx_fail +#undef lsx_report +#undef lsx_warn +#define lsx_debug sox_globals.subsystem=effp->handler.name,lsx_debug_impl +#define lsx_fail sox_globals.subsystem=effp->handler.name,lsx_fail_impl +#define lsx_report sox_globals.subsystem=effp->handler.name,lsx_report_impl +#define lsx_warn sox_globals.subsystem=effp->handler.name,lsx_warn_impl +#endif + +#define RANQD1 ranqd1(sox_globals.ranqd1) +#define DRANQD1 dranqd1(sox_globals.ranqd1) + +typedef enum {SOX_SHORT, SOX_INT, SOX_FLOAT, SOX_DOUBLE} sox_data_t; +typedef enum {SOX_WAVE_SINE, SOX_WAVE_TRIANGLE} lsx_wave_t; +lsx_enum_item const * lsx_get_wave_enum(void); + +/* Define fseeko and ftello for platforms lacking them */ +#ifndef HAVE_FSEEKO +#define fseeko fseek +#define ftello ftell +#endif + +#ifdef _FILE_OFFSET_BITS +assert_static(sizeof(off_t) == _FILE_OFFSET_BITS >> 3, OFF_T_BUILD_PROBLEM); +#endif + +FILE * lsx_tmpfile(void); + +void lsx_debug_more_impl(char const * fmt, ...) LSX_PRINTF12; +void lsx_debug_most_impl(char const * fmt, ...) LSX_PRINTF12; + +#define lsx_debug_more sox_get_globals()->subsystem=__FILE__,lsx_debug_more_impl +#define lsx_debug_most sox_get_globals()->subsystem=__FILE__,lsx_debug_most_impl + +/* Digitise one cycle of a wave and store it as + * a table of samples of a specified data-type. + */ +void lsx_generate_wave_table( + lsx_wave_t wave_type, + sox_data_t data_type, + void * table, /* Really of type indicated by data_type. */ + size_t table_size, /* Number of points on the x-axis. */ + double min, /* Minimum value on the y-axis. (e.g. -1) */ + double max, /* Maximum value on the y-axis. (e.g. +1) */ + double phase); /* Phase at 1st point; 0..2pi. (e.g. pi/2 for cosine) */ +char const * lsx_parsesamples(sox_rate_t rate, const char *str, uint64_t *samples, int def); +int lsx_parse_note(char const * text, char * * end_ptr); +double lsx_parse_frequency_k(char const * text, char * * end_ptr, int key); +#define lsx_parse_frequency(a, b) lsx_parse_frequency_k(a, b, INT_MAX) +FILE * lsx_open_input_file(sox_effect_t * effp, char const * filename); + +void lsx_prepare_spline3(double const * x, double const * y, int n, + double start_1d, double end_1d, double * y_2d); +double lsx_spline3(double const * x, double const * y, double const * y_2d, + int n, double x1); + +double lsx_bessel_I_0(double x); +int lsx_set_dft_length(int num_taps); +void init_fft_cache(void); +void clear_fft_cache(void); +void lsx_safe_rdft(int len, int type, double * d); +void lsx_safe_cdft(int len, int type, double * d); +void lsx_power_spectrum(int n, double const * in, double * out); +void lsx_power_spectrum_f(int n, float const * in, float * out); +void lsx_apply_hann_f(float h[], const int num_points); +void lsx_apply_hann(double h[], const int num_points); +void lsx_apply_hamming(double h[], const int num_points); +void lsx_apply_bartlett(double h[], const int num_points); +void lsx_apply_blackman(double h[], const int num_points, double alpha); +void lsx_apply_blackman_nutall(double h[], const int num_points); +double lsx_kaiser_beta(double att); +void lsx_apply_kaiser(double h[], const int num_points, double beta); +double * lsx_make_lpf(int num_taps, double Fc, double beta, double scale, sox_bool dc_norm); +int lsx_lpf_num_taps(double att, double tr_bw, int k); +double * lsx_design_lpf( + double Fp, /* End of pass-band; ~= 0.01dB point */ + double Fc, /* Start of stop-band */ + double Fn, /* Nyquist freq; e.g. 0.5, 1, PI */ + sox_bool allow_aliasing, + double att, /* Stop-band attenuation in dB */ + int * num_taps, /* (Single phase.) 0: value will be estimated */ + int k); /* Number of phases; 0 for single-phase */ +void lsx_fir_to_phase(double * * h, int * len, + int * post_len, double phase0); +#define LSX_TO_6dB .5869 +#define LSX_TO_3dB ((2/3.) * (.5 + LSX_TO_6dB)) +#define LSX_MAX_TBW0 36. +#define LSX_MAX_TBW0A (LSX_MAX_TBW0 / (1 + LSX_TO_3dB)) +#define LSX_MAX_TBW3 floor(LSX_MAX_TBW0 * LSX_TO_3dB) +#define LSX_MAX_TBW3A floor(LSX_MAX_TBW0A * LSX_TO_3dB) +void lsx_plot_fir(double * h, int num_points, sox_rate_t rate, sox_plot_t type, char const * title, double y1, double y2); + +#ifdef HAVE_BYTESWAP_H +#include +#define lsx_swapw(x) bswap_16(x) +#define lsx_swapdw(x) bswap_32(x) +#elif defined(_MSC_VER) +#define lsx_swapw(x) _byteswap_ushort(x) +#define lsx_swapdw(x) _byteswap_ulong(x) +#else +#define lsx_swapw(uw) (((uw >> 8) | (uw << 8)) & 0xffff) +#define lsx_swapdw(udw) ((udw >> 24) | ((udw >> 8) & 0xff00) | ((udw << 8) & 0xff0000) | (udw << 24)) +#endif + + + +/*------------------------ Implemented in libsoxio.c -------------------------*/ + +/* Read and write basic data types from "ft" stream. */ +size_t lsx_readbuf(sox_format_t * ft, void *buf, size_t len); +int lsx_skipbytes(sox_format_t * ft, size_t n); +int lsx_padbytes(sox_format_t * ft, size_t n); +size_t lsx_writebuf(sox_format_t * ft, void const *buf, size_t len); +int lsx_reads(sox_format_t * ft, char *c, size_t len); +int lsx_writes(sox_format_t * ft, char const * c); +void lsx_set_signal_defaults(sox_format_t * ft); +#define lsx_writechars(ft, chars, len) (lsx_writebuf(ft, chars, len) == len? SOX_SUCCESS : SOX_EOF) + +size_t lsx_read_3_buf(sox_format_t * ft, sox_uint24_t *buf, size_t len); +size_t lsx_read_b_buf(sox_format_t * ft, uint8_t *buf, size_t len); +size_t lsx_read_df_buf(sox_format_t * ft, double *buf, size_t len); +size_t lsx_read_dw_buf(sox_format_t * ft, uint32_t *buf, size_t len); +size_t lsx_read_qw_buf(sox_format_t * ft, uint64_t *buf, size_t len); +size_t lsx_read_f_buf(sox_format_t * ft, float *buf, size_t len); +size_t lsx_read_w_buf(sox_format_t * ft, uint16_t *buf, size_t len); + +size_t lsx_write_3_buf(sox_format_t * ft, sox_uint24_t *buf, size_t len); +size_t lsx_write_b_buf(sox_format_t * ft, uint8_t *buf, size_t len); +size_t lsx_write_df_buf(sox_format_t * ft, double *buf, size_t len); +size_t lsx_write_dw_buf(sox_format_t * ft, uint32_t *buf, size_t len); +size_t lsx_write_qw_buf(sox_format_t * ft, uint64_t *buf, size_t len); +size_t lsx_write_f_buf(sox_format_t * ft, float *buf, size_t len); +size_t lsx_write_w_buf(sox_format_t * ft, uint16_t *buf, size_t len); + +int lsx_read3(sox_format_t * ft, sox_uint24_t * u3); +int lsx_readb(sox_format_t * ft, uint8_t * ub); +int lsx_readchars(sox_format_t * ft, char * chars, size_t len); +int lsx_readdf(sox_format_t * ft, double * d); +int lsx_readdw(sox_format_t * ft, uint32_t * udw); +int lsx_readqw(sox_format_t * ft, uint64_t * udw); +int lsx_readf(sox_format_t * ft, float * f); +int lsx_readw(sox_format_t * ft, uint16_t * uw); + +#if 1 /* FIXME: use defines */ +UNUSED static int lsx_readsb(sox_format_t * ft, int8_t * sb) +{return lsx_readb(ft, (uint8_t *)sb);} +UNUSED static int lsx_readsw(sox_format_t * ft, int16_t * sw) +{return lsx_readw(ft, (uint16_t *)sw);} +#else +#define lsx_readsb(ft, sb) lsx_readb(ft, (uint8_t *)sb) +#define lsx_readsw(ft, sw) lsx_readb(ft, (uint16_t *)sw) +#endif + +int lsx_write3(sox_format_t * ft, unsigned u3); +int lsx_writeb(sox_format_t * ft, unsigned ub); +int lsx_writedf(sox_format_t * ft, double d); +int lsx_writedw(sox_format_t * ft, unsigned udw); +int lsx_writeqw(sox_format_t * ft, uint64_t uqw); +int lsx_writef(sox_format_t * ft, double f); +int lsx_writew(sox_format_t * ft, unsigned uw); + +int lsx_writesb(sox_format_t * ft, signed); +int lsx_writesw(sox_format_t * ft, signed); + +int lsx_eof(sox_format_t * ft); +int lsx_error(sox_format_t * ft); +int lsx_flush(sox_format_t * ft); +int lsx_seeki(sox_format_t * ft, off_t offset, int whence); +int lsx_unreadb(sox_format_t * ft, unsigned ub); +uint64_t lsx_filelength(sox_format_t * ft); +off_t lsx_tell(sox_format_t * ft); +void lsx_clearerr(sox_format_t * ft); +void lsx_rewind(sox_format_t * ft); + +int lsx_offset_seek(sox_format_t * ft, off_t byte_offset, off_t to_sample); + +void lsx_fail_errno(sox_format_t *, int, const char *, ...) +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 4))); +#else +; +#endif + +typedef struct sox_formats_globals { /* Global parameters (for formats) */ + sox_globals_t * global_info; +} sox_formats_globals; + + + +/*------------------------------ File Handlers -------------------------------*/ + +int lsx_check_read_params(sox_format_t * ft, unsigned channels, + sox_rate_t rate, sox_encoding_t encoding, unsigned bits_per_sample, + uint64_t num_samples, sox_bool check_length); +#define LSX_FORMAT_HANDLER(name) \ +sox_format_handler_t const * lsx_##name##_format_fn(void); \ +sox_format_handler_t const * lsx_##name##_format_fn(void) +#define div_bits(size, bits) ((uint64_t)(size) * 8 / bits) + +/* Raw I/O */ +int lsx_rawstartread(sox_format_t * ft); +size_t lsx_rawread(sox_format_t * ft, sox_sample_t *buf, size_t nsamp); +int lsx_rawstopread(sox_format_t * ft); +int lsx_rawstartwrite(sox_format_t * ft); +size_t lsx_rawwrite(sox_format_t * ft, const sox_sample_t *buf, size_t nsamp); +int lsx_rawseek(sox_format_t * ft, uint64_t offset); +int lsx_rawstart(sox_format_t * ft, sox_bool default_rate, sox_bool default_channels, sox_bool default_length, sox_encoding_t encoding, unsigned bits_per_sample); +#define lsx_rawstartread(ft) lsx_rawstart(ft, sox_false, sox_false, sox_false, SOX_ENCODING_UNKNOWN, 0) +#define lsx_rawstartwrite lsx_rawstartread +#define lsx_rawstopread NULL +#define lsx_rawstopwrite NULL + +extern sox_format_handler_t const * lsx_sndfile_format_fn(void); + +char * lsx_cat_comments(sox_comments_t comments); + +/*--------------------------------- Effects ----------------------------------*/ + +int lsx_flow_copy(sox_effect_t * effp, const sox_sample_t * ibuf, + sox_sample_t * obuf, size_t * isamp, size_t * osamp); +int lsx_usage(sox_effect_t * effp); +char * lsx_usage_lines(char * * usage, char const * const * lines, size_t n); +#define EFFECT(f) extern sox_effect_handler_t const * lsx_##f##_effect_fn(void); +#include "effects.h" +#undef EFFECT + +#define NUMERIC_PARAMETER(name, min, max) { \ + char * end_ptr; \ + double d; \ + if (argc == 0) break; \ + d = strtod(*argv, &end_ptr); \ + if (end_ptr != *argv) { \ + if (d < min || d > max || *end_ptr != '\0') {\ + lsx_fail("parameter `%s' must be between %g and %g", #name, (double)min, (double)max); \ + return lsx_usage(effp); \ + } \ + p->name = d; \ + --argc, ++argv; \ + } \ +} + +#define TEXTUAL_PARAMETER(name, enum_table) { \ + lsx_enum_item const * e; \ + if (argc == 0) break; \ + e = lsx_find_enum_text(*argv, enum_table, 0); \ + if (e != NULL) { \ + p->name = e->value; \ + --argc, ++argv; \ + } \ +} + +#define GETOPT_NUMERIC(state, ch, name, min, max) case ch:{ \ + char * end_ptr; \ + double d = strtod(state.arg, &end_ptr); \ + if (end_ptr == state.arg || d < min || d > max || *end_ptr != '\0') {\ + lsx_fail("parameter `%s' must be between %g and %g", #name, (double)min, (double)max); \ + return lsx_usage(effp); \ + } \ + p->name = d; \ + break; \ +} + +int lsx_effect_set_imin(sox_effect_t * effp, size_t imin); + +int lsx_effects_init(void); +int lsx_effects_quit(void); + +/*--------------------------------- Dynamic Library ----------------------------------*/ + +#if defined(HAVE_WIN32_LTDL_H) + #include "win32-ltdl.h" + #define HAVE_LIBLTDL 1 + typedef lt_dlhandle lsx_dlhandle; +#elif defined(HAVE_LIBLTDL) + #include + typedef lt_dlhandle lsx_dlhandle; +#else + struct lsx_dlhandle_tag; + typedef struct lsx_dlhandle_tag *lsx_dlhandle; +#endif + +typedef void (*lsx_dlptr)(void); + +typedef struct lsx_dlfunction_info +{ + const char* name; + lsx_dlptr static_func; + lsx_dlptr stub_func; +} lsx_dlfunction_info; + +int lsx_open_dllibrary( + int show_error_on_failure, + const char* library_description, + const char * const library_names[], + const lsx_dlfunction_info func_infos[], + lsx_dlptr selected_funcs[], + lsx_dlhandle* pdl); + +void lsx_close_dllibrary( + lsx_dlhandle dl); + +#define LSX_DLENTRIES_APPLY__(entries, f, x) entries(f, x) + +#define LSX_DLENTRY_TO_PTR__(unused, func_return, func_name, func_args, static_func, stub_func, func_ptr) \ + func_return (*func_ptr) func_args; + +#define LSX_DLENTRIES_TO_FUNCTIONS__(unused, func_return, func_name, func_args, static_func, stub_func, func_ptr) \ + func_return func_name func_args; + +/* LSX_DLENTRIES_TO_PTRS: Given an ENTRIES macro and the name of the dlhandle + variable, declares the corresponding function pointer variables and the + dlhandle variable. */ +#define LSX_DLENTRIES_TO_PTRS(entries, dlhandle) \ + LSX_DLENTRIES_APPLY__(entries, LSX_DLENTRY_TO_PTR__, 0) \ + lsx_dlhandle dlhandle + +/* LSX_DLENTRIES_TO_FUNCTIONS: Given an ENTRIES macro, declares the corresponding + functions. */ +#define LSX_DLENTRIES_TO_FUNCTIONS(entries) \ + LSX_DLENTRIES_APPLY__(entries, LSX_DLENTRIES_TO_FUNCTIONS__, 0) + +#define LSX_DLLIBRARY_OPEN1__(unused, func_return, func_name, func_args, static_func, stub_func, func_ptr) \ + { #func_name, (lsx_dlptr)(static_func), (lsx_dlptr)(stub_func) }, + +#define LSX_DLLIBRARY_OPEN2__(ptr_container, func_return, func_name, func_args, static_func, stub_func, func_ptr) \ + (ptr_container)->func_ptr = (func_return (*)func_args)lsx_dlfunction_open_library_funcs[lsx_dlfunction_open_library_index++]; + +/* LSX_DLLIBRARY_OPEN: Input an ENTRIES macro, the library's description, + a null-terminated list of library names (i.e. { "libmp3-0", "libmp3", NULL }), + the name of the dlhandle variable, the name of the structure that contains + the function pointer and dlhandle variables, and the name of the variable in + which the result of the lsx_open_dllibrary call should be stored. This will + call lsx_open_dllibrary and copy the resulting function pointers into the + structure members. If the library cannot be opened, show a failure message. */ +#define LSX_DLLIBRARY_OPEN(ptr_container, dlhandle, entries, library_description, library_names, return_var) \ + LSX_DLLIBRARY_TRYOPEN(1, ptr_container, dlhandle, entries, library_description, library_names, return_var) + +/* LSX_DLLIBRARY_TRYOPEN: Input an ENTRIES macro, the library's description, + a null-terminated list of library names (i.e. { "libmp3-0", "libmp3", NULL }), + the name of the dlhandle variable, the name of the structure that contains + the function pointer and dlhandle variables, and the name of the variable in + which the result of the lsx_open_dllibrary call should be stored. This will + call lsx_open_dllibrary and copy the resulting function pointers into the + structure members. If the library cannot be opened, show a report or a failure + message, depending on whether error_on_failure is non-zero. */ +#define LSX_DLLIBRARY_TRYOPEN(error_on_failure, ptr_container, dlhandle, entries, library_description, library_names, return_var) \ + do { \ + lsx_dlfunction_info lsx_dlfunction_open_library_infos[] = { \ + LSX_DLENTRIES_APPLY__(entries, LSX_DLLIBRARY_OPEN1__, 0) \ + {NULL,NULL,NULL} }; \ + int lsx_dlfunction_open_library_index = 0; \ + lsx_dlptr lsx_dlfunction_open_library_funcs[sizeof(lsx_dlfunction_open_library_infos)/sizeof(lsx_dlfunction_open_library_infos[0])]; \ + (return_var) = lsx_open_dllibrary((error_on_failure), (library_description), (library_names), lsx_dlfunction_open_library_infos, lsx_dlfunction_open_library_funcs, &(ptr_container)->dlhandle); \ + LSX_DLENTRIES_APPLY__(entries, LSX_DLLIBRARY_OPEN2__, ptr_container) \ + } while(0) + +#define LSX_DLLIBRARY_CLOSE(ptr_container, dlhandle) \ + lsx_close_dllibrary((ptr_container)->dlhandle) + + /* LSX_DLENTRY_STATIC: For use in creating an ENTRIES macro. func is + expected to be available at link time. If not present, link will fail. */ +#define LSX_DLENTRY_STATIC(f,x, ret, func, args) f(x, ret, func, args, func, NULL, func) + + /* LSX_DLENTRY_DYNAMIC: For use in creating an ENTRIES macro. func need + not be available at link time (and if present, the link time version will + not be used). func will be loaded via dlsym. If this function is not + found in the shared library, the shared library will not be used. */ +#define LSX_DLENTRY_DYNAMIC(f,x, ret, func, args) f(x, ret, func, args, NULL, NULL, func) + + /* LSX_DLENTRY_STUB: For use in creating an ENTRIES macro. func need not + be available at link time (and if present, the link time version will not + be used). If using DL_LAME, the func may be loaded via dlopen/dlsym, but + if not found, the shared library will still be used if all of the + non-stub functions are found. If the function is not found via dlsym (or + if we are not loading any shared libraries), the stub will be used. This + assumes that the name of the stub function is the name of the function + + "_stub". */ +#define LSX_DLENTRY_STUB(f,x, ret, func, args) f(x, ret, func, args, NULL, func##_stub, func) + + /* LSX_DLFUNC_IS_STUB: returns true if the named function is a do-nothing + stub. Assumes that the name of the stub function is the name of the + function + "_stub". */ +#define LSX_DLFUNC_IS_STUB(ptr_container, func) ((ptr_container)->func == func##_stub) + +#endif diff --git a/freedv/tags/1.2.2/src/sox/soxomp.h b/freedv/tags/1.2.2/src/sox/soxomp.h new file mode 100644 index 00000000..6fce07d9 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/soxomp.h @@ -0,0 +1,38 @@ +#include "soxconfig.h" + +#ifdef HAVE_OPENMP + #include +#else + +typedef int omp_lock_t; +typedef int omp_nest_lock_t; + +#define omp_set_num_threads(int) (void)0 +#define omp_get_num_threads() 1 +#define omp_get_max_threads() 1 +#define omp_get_thread_num() 0 +#define omp_get_num_procs() 1 +#define omp_in_parallel() 1 + +#define omp_set_dynamic(int) (void)0 +#define omp_get_dynamic() 0 + +#define omp_set_nested(int) (void)0 +#define omp_get_nested() 0 + +#define omp_init_lock(omp_lock_t) (void)0 +#define omp_destroy_lock(omp_lock_t) (void)0 +#define omp_set_lock(omp_lock_t) (void)0 +#define omp_unset_lock(omp_lock_t) (void)0 +#define omp_test_lock(omp_lock_t) 0 + +#define omp_init_nest_lock(omp_nest_lock_t) (void)0 +#define omp_destroy_nest_lock(omp_nest_lock_t) (void)0 +#define omp_set_nest_lock(omp_nest_lock_t) (void)0 +#define omp_unset_nest_lock(omp_nest_lock_t) (void)0 +#define omp_test_nest_lock(omp_nest_lock_t) 0 + +#define omp_get_wtime() 0 +#define omp_get_wtick() 0 + +#endif diff --git a/freedv/tags/1.2.2/src/sox/util.h b/freedv/tags/1.2.2/src/sox/util.h new file mode 100644 index 00000000..89bbe752 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/util.h @@ -0,0 +1,231 @@ +/* General purpose, i.e. non SoX specific, utility functions and macros. + * + * (c) 2006-8 Chris Bagwell and SoX contributors + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "soxconfig.h" + +#ifdef HAVE_SYS_TYPES_H +#include /* For off_t not found in stdio.h */ +#endif + +#ifdef HAVE_SYS_STAT_H +#include /* Needs to be included before we redefine off_t. */ +#endif + +#include "xmalloc.h" + +/*---------------------------- Portability stuff -----------------------------*/ + +#if defined(HAVE_INTTYPES_H) + #include +#elif defined(HAVE_STDINT_H) + #include +#else + typedef sox_int8_t int8_t; + typedef sox_uint8_t uint8_t; + typedef sox_int16_t int16_t; + typedef sox_uint16_t uint16_t; + typedef sox_int32_t int32_t; + typedef sox_uint32_t uint32_t; + typedef sox_int64_t int64_t; + typedef sox_uint64_t uint64_t; +#endif + +/* Define the format specifier to use for int64_t values. + * Example: printf("You may have already won $ %" PRId64 " !!!", n64); */ +#ifndef PRId64 /* Maybe already defined this. */ +#if defined(_MSC_VER) || defined(__MINGW32__) /* Older versions of msvcrt.dll don't recognize %lld. */ +#define PRId64 "I64d" +#elif LONG_MAX==9223372036854775807 +#define PRId64 "ld" +#else +#define PRId64 "lld" +#endif +#endif /* PRId64 */ + +/* Define the format specifier to use for uint64_t values. */ +#ifndef PRIu64 /* Maybe already defined this. */ +#if defined(_MSC_VER) || defined(__MINGW32__) /* Older versions of msvcrt.dll don't recognize %llu. */ +#define PRIu64 "I64u" +#elif ULONG_MAX==0xffffffffffffffff +#define PRIu64 "lu" +#else +#define PRIu64 "llu" +#endif +#endif /* PRIu64 */ + +/* Define the format specifier to use for size_t values. + * Example: printf("Sizeof(x) = %" PRIuPTR " bytes", sizeof(x)); */ +#ifndef PRIuPTR /* Maybe already defined this. */ +#if defined(_MSC_VER) || defined(__MINGW32__) /* Older versions of msvcrt.dll don't recognize %zu. */ +#define PRIuPTR "Iu" +#else +#define PRIuPTR "zu" +#endif +#endif /* PRIuPTR */ + +#ifdef __GNUC__ +#define NORET __attribute__((noreturn)) +#define UNUSED __attribute__ ((unused)) +#else +#define NORET +#define UNUSED +#endif + +#ifdef _MSC_VER + +#define __STDC__ 1 +#define O_BINARY _O_BINARY +#define O_CREAT _O_CREAT +#define O_RDWR _O_RDWR +#define O_TRUNC _O_TRUNC +#define S_IFMT _S_IFMT +#define S_IFREG _S_IFREG +#define S_IREAD _S_IREAD +#define S_IWRITE _S_IWRITE +#define close _close +#define dup _dup +#define fdopen _fdopen +#define fileno _fileno + +#ifdef _fstati64 +#define fstat _fstati64 +#else +#define fstat _fstat +#endif + +#define ftime _ftime +#define inline __inline +#define isatty _isatty +#define kbhit _kbhit +#define mktemp _mktemp +#define off_t _off_t +#define open _open +#define pclose _pclose +#define popen _popen +#define setmode _setmode +#define snprintf _snprintf + +#ifdef _stati64 +#define stat _stati64 +#else +#define stat _stat +#endif + +#define strdup _strdup +#define timeb _timeb +#define unlink _unlink + +#if defined(HAVE__FSEEKI64) && !defined(HAVE_FSEEKO) +#undef off_t +#define fseeko _fseeki64 +#define ftello _ftelli64 +#define off_t __int64 +#define HAVE_FSEEKO 1 +#endif + +#elif defined(__MINGW32__) + +#if !defined(HAVE_FSEEKO) +#undef off_t +#define fseeko fseeko64 +#define fstat _fstati64 +#define ftello ftello64 +#define off_t off64_t +#define stat _stati64 +#define HAVE_FSEEKO 1 +#endif + +#endif + +#if defined(DOS) || defined(WIN32) || defined(__NT__) || defined(__DJGPP__) || defined(__OS2__) + #define LAST_SLASH(path) max(strrchr(path, '/'), strrchr(path, '\\')) + #define IS_ABSOLUTE(path) ((path)[0] == '/' || (path)[0] == '\\' || (path)[1] == ':') + #define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) + #define POPEN_MODE "rb" +#else + #define LAST_SLASH(path) strrchr(path, '/') + #define IS_ABSOLUTE(path) ((path)[0] == '/') + #define SET_BINARY_MODE(file) +#endif + +#ifdef WORDS_BIGENDIAN + #define MACHINE_IS_BIGENDIAN 1 + #define MACHINE_IS_LITTLEENDIAN 0 +#else + #define MACHINE_IS_BIGENDIAN 0 + #define MACHINE_IS_LITTLEENDIAN 1 +#endif + +/*--------------------------- Language extensions ----------------------------*/ + +/* Compile-time ("static") assertion */ +/* e.g. assert_static(sizeof(int) >= 4, int_type_too_small) */ +#define assert_static(e,f) enum {assert_static__##f = 1/(e)} +#define array_length(a) (sizeof(a)/sizeof(a[0])) +#define field_offset(type, field) ((size_t)&(((type *)0)->field)) +#define unless(x) if (!(x)) + +/*------------------------------- Maths stuff --------------------------------*/ + +#include + +#ifdef min +#undef min +#endif +#define min(a, b) ((a) <= (b) ? (a) : (b)) + +#ifdef max +#undef max +#endif +#define max(a, b) ((a) >= (b) ? (a) : (b)) + +#define range_limit(x, lower, upper) (min(max(x, lower), upper)) + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 /* natural log of 10 */ +#endif +#ifndef M_SQRT2 +#define M_SQRT2 sqrt(2.) +#endif + +#define sqr(a) ((a) * (a)) +#define sign(x) ((x) < 0? -1 : 1) + +/* Numerical Recipes in C, p. 284 */ +#define ranqd1(x) ((x) = 1664525L * (x) + 1013904223L) /* int32_t x */ +#define dranqd1(x) (ranqd1(x) * (1. / (65536. * 32768.))) /* [-1,1) */ + +#define dB_to_linear(x) exp((x) * M_LN10 * 0.05) +#define linear_to_dB(x) (log10(x) * 20) + +extern int lsx_strcasecmp(const char *s1, const char *st); +extern int lsx_strncasecmp(char const *s1, char const *s2, size_t n); + +#ifndef HAVE_STRCASECMP +#define strcasecmp(s1, s2) lsx_strcasecmp((s1), (s2)) +#define strncasecmp(s1, s2, n) lsx_strncasecmp((s1), (s2), (n)) +#endif diff --git a/freedv/tags/1.2.2/src/sox/xmalloc.c b/freedv/tags/1.2.2/src/sox/xmalloc.c new file mode 100644 index 00000000..9bf15969 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/xmalloc.c @@ -0,0 +1,43 @@ +/* SoX Memory allocation functions + * + * Copyright (c) 2005-2006 Reuben Thomas. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sox_i.h" +#include + +/* Resize an allocated memory area; abort if not possible. + * + * For malloc, `If the size of the space requested is zero, the behavior is + * implementation defined: either a null pointer is returned, or the + * behavior is as if the size were some nonzero value, except that the + * returned pointer shall not be used to access an object' + */ +void *lsx_realloc(void *ptr, size_t newsize) +{ + if (ptr && newsize == 0) { + free(ptr); + return NULL; + } + + if ((ptr = realloc(ptr, newsize)) == NULL) { + lsx_fail("out of memory"); + exit(2); + } + + return ptr; +} diff --git a/freedv/tags/1.2.2/src/sox/xmalloc.h b/freedv/tags/1.2.2/src/sox/xmalloc.h new file mode 100644 index 00000000..9ee77f63 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox/xmalloc.h @@ -0,0 +1,34 @@ +/* libSoX Memory allocation functions + * + * Copyright (c) 2005-2006 Reuben Thomas. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LSX_MALLOC_H +#define LSX_MALLOC_H + +#include +#include + +#define lsx_malloc(size) lsx_realloc(NULL, (size)) +#define lsx_calloc(n,s) (((n)*(s))? memset(lsx_malloc((n)*(s)),0,(n)*(s)) : NULL) +#define lsx_Calloc(v,n) v = lsx_calloc(n,sizeof(*(v))) +#define lsx_strdup(p) ((p)? strcpy((char *)lsx_malloc(strlen(p) + 1), p) : NULL) +#define lsx_memdup(p,s) ((p)? memcpy(lsx_malloc(s), p, s) : NULL) +#define lsx_valloc(v,n) v = lsx_malloc((n)*sizeof(*(v))) +#define lsx_revalloc(v,n) v = lsx_realloc(v, (n)*sizeof(*(v))) + +#endif diff --git a/freedv/tags/1.2.2/src/sox_biquad.c b/freedv/tags/1.2.2/src/sox_biquad.c new file mode 100644 index 00000000..548f4249 --- /dev/null +++ b/freedv/tags/1.2.2/src/sox_biquad.c @@ -0,0 +1,134 @@ +//========================================================================== +// Name: sox_biquad.h +// Purpose: Interface into Sox Biquad filters +// Created: Dec 1, 2012 +// Authors: David Rowe +// +// To test: +/* + $ gcc sox_biquad.c sox/effects_i.c sox/effects.c sox/formats_i.c \ + sox/biquad.c sox/biquads.c sox/xmalloc.c sox/libsox.c \ + -o sox_biquad -DSOX_BIQUAD_UNITTEST -D__FREEDV__ \ + -Wall -lm -lsndfile -g + $ ./sox_biquad +*/ +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== + +#include +#include +#include +#include "sox/sox.h" + +#include "sox_biquad.h" + + +#define N_MAX 1024 + +int lsx_biquad_flow(sox_effect_t * effp, const sox_sample_t *ibuf, + sox_sample_t *obuf, size_t *isamp, size_t *osamp); + +void sox_biquad_start(void) +{ + int r = sox_init(); + assert(r == SOX_SUCCESS); +} + +void sox_biquad_finish(void) +{ + sox_quit(); +} + +/* + Effect must be implemented by biquads.c in sox, arguments are just + like sox command line, for example: + + char *argv[10]; + argv[0] = "highpass"; argv[1]="1000"; argc=1; +*/ + +void *sox_biquad_create(int argc, const char *argv[]) +{ + int ret; + sox_effect_t *e; + int (*start)(sox_effect_t *); /* function pointer to effect start func */ + + e = sox_create_effect(sox_find_effect(argv[0])); assert(e != NULL); + ret = sox_effect_options(e, argc, (char * const*)&argv[1]); + assert(ret == SOX_SUCCESS); + + start = e->handler.start; + e->in_signal.rate = 8000; /* locked at FS=8000 Hz */ + ret = start(e); assert(ret == SOX_SUCCESS); + + return (void *)e; +} + +void sox_biquad_destroy(void *sbq) { + sox_effect_t *e = (sox_effect_t *)sbq; + free(e); +} + +void sox_biquad_filter(void *sbq, short out[], short in[], int n) +{ + sox_effect_t *e = (sox_effect_t *)sbq; + sox_sample_t ibuf[N_MAX]; + sox_sample_t obuf[N_MAX]; + size_t isamp, osamp; + unsigned int clips; + SOX_SAMPLE_LOCALS; + int i; + + assert(n <= N_MAX); + + clips = 0; + for(i=0; i. +// +//========================================================================== + +#ifndef __SOX_BIQUAD__ +#define __SOX_BIQUAD__ + +#ifdef __cplusplus +extern "C" { + +#endif + +void sox_biquad_start(void); +void sox_biquad_finish(void); +void *sox_biquad_create(int argc, const char *argv[]); +void sox_biquad_destroy(void *sbq); +void sox_biquad_filter(void *sbq, short out[], short in[], int n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/freedv/tags/1.2.2/src/topFrame.cpp b/freedv/tags/1.2.2/src/topFrame.cpp new file mode 100644 index 00000000..a5b574a1 --- /dev/null +++ b/freedv/tags/1.2.2/src/topFrame.cpp @@ -0,0 +1,595 @@ +//========================================================================== +// Name: topFrame.cpp +// +// Purpose: Implements simple wxWidgets application with GUI. +// Created: Apr. 9, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================== +#include "topFrame.h" + +extern int g_playFileToMicInEventId; +extern int g_recFileFromRadioEventId; +extern int g_playFileFromRadioEventId; + +//========================================================================= +// Code that lays out the main application window +//========================================================================= +TopFrame::TopFrame(wxString plugInName, wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(parent, id, title, pos, size, style) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT)); + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT)); + //===================================================== + // Menubar Setup + m_menubarMain = new wxMenuBar(wxMB_DOCKABLE); + file = new wxMenu(); + + wxMenuItem* m_menuItemOnTop; + m_menuItemOnTop = new wxMenuItem(file, wxID_ANY, wxString(_("On Top")) , _("Always Top Window"), wxITEM_NORMAL); + file->Append(m_menuItemOnTop); + + wxMenuItem* m_menuItemExit; + m_menuItemExit = new wxMenuItem(file, ID_EXIT, wxString(_("E&xit")) , _("Exit Program"), wxITEM_NORMAL); + file->Append(m_menuItemExit); + + m_menubarMain->Append(file, _("&File")); + + tools = new wxMenu(); + wxMenuItem* m_menuItemAudio; + m_menuItemAudio = new wxMenuItem(tools, wxID_ANY, wxString(_("&Audio Config")) , wxEmptyString, wxITEM_NORMAL); + tools->Append(m_menuItemAudio); + + wxMenuItem* m_menuItemRigCtrlCfg; + m_menuItemRigCtrlCfg = new wxMenuItem(tools, wxID_ANY, wxString(_("&PTT Config")) , wxEmptyString, wxITEM_NORMAL); + tools->Append(m_menuItemRigCtrlCfg); + + wxMenuItem* m_menuItemOptions; + m_menuItemOptions = new wxMenuItem(tools, wxID_ANY, wxString(_("Options")) , wxEmptyString, wxITEM_NORMAL); + tools->Append(m_menuItemOptions); + + wxMenuItem* m_menuItemFilter; + m_menuItemFilter = new wxMenuItem(tools, wxID_ANY, wxString(_("&Filter")) , wxEmptyString, wxITEM_NORMAL); + tools->Append(m_menuItemFilter); + + wxMenuItem* m_menuItemPlugIn; + if (!wxIsEmpty(plugInName)) { + m_menuItemPlugIn = new wxMenuItem(tools, wxID_ANY, plugInName + wxString(_(" Config")) , wxEmptyString, wxITEM_NORMAL); + tools->Append(m_menuItemPlugIn); + } + + wxMenuItem* m_menuItemPlayFileToMicIn; + m_menuItemPlayFileToMicIn = new wxMenuItem(tools, wxID_ANY, wxString(_("Start/Stop Play File - Mic In")) , wxEmptyString, wxITEM_NORMAL); + g_playFileToMicInEventId = m_menuItemPlayFileToMicIn->GetId(); + tools->Append(m_menuItemPlayFileToMicIn); + + wxMenuItem* m_menuItemRecFileFromRadio; + m_menuItemRecFileFromRadio = new wxMenuItem(tools, wxID_ANY, wxString(_("Start/Stop Record File - From Radio")) , wxEmptyString, wxITEM_NORMAL); + g_recFileFromRadioEventId = m_menuItemRecFileFromRadio->GetId(); + tools->Append(m_menuItemRecFileFromRadio); + + wxMenuItem* m_menuItemPlayFileFromRadio; + m_menuItemPlayFileFromRadio = new wxMenuItem(tools, wxID_ANY, wxString(_("Start/Stop Play File - From Radio")) , wxEmptyString, wxITEM_NORMAL); + g_playFileFromRadioEventId = m_menuItemPlayFileFromRadio->GetId(); + tools->Append(m_menuItemPlayFileFromRadio); + m_menubarMain->Append(tools, _("&Tools")); + + help = new wxMenu(); + wxMenuItem* m_menuItemHelpUpdates; + m_menuItemHelpUpdates = new wxMenuItem(help, wxID_ANY, wxString(_("Check for Updates")) , wxEmptyString, wxITEM_NORMAL); + help->Append(m_menuItemHelpUpdates); + m_menuItemHelpUpdates->Enable(false); + + wxMenuItem* m_menuItemAbout; + m_menuItemAbout = new wxMenuItem(help, ID_ABOUT, wxString(_("&About")) , _("About this program"), wxITEM_NORMAL); + help->Append(m_menuItemAbout); + + m_menubarMain->Append(help, _("&Help")); + + this->SetMenuBar(m_menubarMain); + + wxBoxSizer* bSizer1; + bSizer1 = new wxBoxSizer(wxHORIZONTAL); + + //===================================================== + // Left side + //===================================================== + wxBoxSizer* leftSizer; + leftSizer = new wxBoxSizer(wxVERTICAL); + + wxStaticBoxSizer* snrSizer; + snrSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("SNR")), wxVERTICAL); + + //------------------------------ + // S/N ratio Guage (vert. bargraph) + //------------------------------ + m_gaugeSNR = new wxGauge(this, wxID_ANY, 25, wxDefaultPosition, wxSize(15,135), wxGA_SMOOTH|wxGA_VERTICAL); + m_gaugeSNR->SetToolTip(_("Displays signal to noise ratio in dB.")); + snrSizer->Add(m_gaugeSNR, 1, wxALIGN_CENTER_HORIZONTAL|wxALL, 10); + + //------------------------------ + // Box for S/N ratio (Numeric) + //------------------------------ + m_textSNR = new wxStaticText(this, wxID_ANY, wxT(" 0.0"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE); + snrSizer->Add(m_textSNR, 0, wxALIGN_CENTER_HORIZONTAL, 1); + + //------------------------------ + // S/N ratio slow Checkbox + //------------------------------ + m_ckboxSNR = new wxCheckBox(this, wxID_ANY, _("Slow"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + m_ckboxSNR->SetToolTip(_("Smooth but slow SNR estimation")); + snrSizer->Add(m_ckboxSNR, 0, wxALIGN_CENTER_HORIZONTAL, 5); + + leftSizer->Add(snrSizer, 2, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxALL, 1); + + //------------------------------ + // Sync Indicator box + //------------------------------ + wxStaticBoxSizer* sbSizer3_33; + sbSizer3_33 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Sync")), wxVERTICAL); + + m_rbSync = new wxRadioButton( this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + m_rbSync->SetForegroundColour( wxColour( 255, 0, 0 ) ); + sbSizer3_33->Add(m_rbSync, 0, wxALIGN_CENTER|wxALL, 1); + leftSizer->Add(sbSizer3_33,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // BER Frames box + //------------------------------ + + wxStaticBoxSizer* sbSizer_ber; + sbSizer_ber = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Bit Error Rate")), wxVERTICAL); + + m_BtnBerReset = new wxButton(this, wxID_ANY, _("Reset"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_ber->Add(m_BtnBerReset, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + + m_textBits = new wxStaticText(this, wxID_ANY, wxT("Bits: 0"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + sbSizer_ber->Add(m_textBits, 0, wxALIGN_LEFT, 1); + m_textErrors = new wxStaticText(this, wxID_ANY, wxT("Errs: 0"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + sbSizer_ber->Add(m_textErrors, 0, wxALIGN_LEFT, 1); + m_textBER = new wxStaticText(this, wxID_ANY, wxT("BER: 0.0"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + sbSizer_ber->Add(m_textBER, 0, wxALIGN_LEFT, 1); + + m_textResyncs = new wxStaticText(this, wxID_ANY, wxT("Resyncs: 0"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + sbSizer_ber->Add(m_textResyncs, 0, wxALIGN_LEFT, 1); + + leftSizer->Add(sbSizer_ber,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + //------------------------------ + // Signal Level(vert. bargraph) + //------------------------------ + wxStaticBoxSizer* levelSizer; + levelSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Level")), wxVERTICAL); + + m_textLevel = new wxStaticText(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(60,-1), wxALIGN_CENTRE); + m_textLevel->SetForegroundColour(wxColour(255,0,0)); + levelSizer->Add(m_textLevel, 0, wxALIGN_LEFT, 1); + + m_gaugeLevel = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(15,135), wxGA_SMOOTH|wxGA_VERTICAL); + m_gaugeLevel->SetToolTip(_("Peak of From Radio in Rx, or peak of From Mic in Tx mode. If Red you should reduce your levels")); + levelSizer->Add(m_gaugeLevel, 1, wxALIGN_CENTER_HORIZONTAL|wxALL, 10); + + leftSizer->Add(levelSizer, 2, wxALIGN_CENTER|wxALL|wxEXPAND, 1); + + bSizer1->Add(leftSizer, 0, wxALL|wxEXPAND, 5); + + //===================================================== + // Center Section + //===================================================== + wxBoxSizer* centerSizer; + centerSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer* upperSizer; + upperSizer = new wxBoxSizer(wxVERTICAL); + + //===================================================== + // Tabbed Notebook control containing display graphs + //===================================================== + //m_auiNbookCtrl = new wxAuiNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_NB_BOTTOM|wxAUI_NB_DEFAULT_STYLE); + //long style = wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_CLOSE_ON_ACTIVE_TAB | wxAUI_NB_MIDDLE_CLICK_CLOSE; + long nb_style = wxAUI_NB_BOTTOM | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS; + m_auiNbookCtrl = new wxAuiNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, nb_style); + // This line sets the fontsize for the tabs on the notebook control + m_auiNbookCtrl->SetFont(wxFont(8, 70, 90, 90, false, wxEmptyString)); + + upperSizer->Add(m_auiNbookCtrl, 1, wxALIGN_TOP|wxEXPAND, 1); + centerSizer->Add(upperSizer, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALIGN_TOP|wxEXPAND, 0); + + // lower middle used for user ID + + wxBoxSizer* lowerSizer; + lowerSizer = new wxBoxSizer(wxHORIZONTAL); + + m_BtnCallSignReset = new wxButton(this, wxID_ANY, _("Clear"), wxDefaultPosition, wxDefaultSize, 0); + lowerSizer->Add(m_BtnCallSignReset, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + + wxBoxSizer* bSizer15; + bSizer15 = new wxBoxSizer(wxVERTICAL); + m_txtCtrlCallSign = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); + m_txtCtrlCallSign->SetToolTip(_("Call Sign of transmitting station will appear here")); + bSizer15->Add(m_txtCtrlCallSign, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5); + lowerSizer->Add(bSizer15, 1, wxEXPAND, 5); + +#ifdef __EXPERIMENTAL_UDP__ + wxStaticBoxSizer* sbSizer_Checksum = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Checksums")), wxHORIZONTAL); + + wxStaticText *goodLabel = new wxStaticText(this, wxID_ANY, wxT("Good: "), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE); + sbSizer_Checksum->Add(goodLabel, 0, 0, 2); + m_txtChecksumGood = new wxStaticText(this, wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(30,-1), wxALIGN_CENTRE); + sbSizer_Checksum->Add(m_txtChecksumGood, 0, 0, 2); + + wxStaticText *badLabel = new wxStaticText(this, wxID_ANY, wxT("Bad: "), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE); + sbSizer_Checksum->Add(badLabel, 0, 0, 1); + m_txtChecksumBad = new wxStaticText(this, wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(30,-1), wxALIGN_CENTRE); + sbSizer_Checksum->Add(m_txtChecksumBad, 0, 0, 1); + + lowerSizer->Add(sbSizer_Checksum, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); +#endif + + //===================================================== + // These are the buttons that autosend the userid (?) + //===================================================== + + // DR 4 Dec - taken off for screen for Beta release to avoid questions on their use until + // we implement this feature + #ifdef UNIMPLEMENTED + wxBoxSizer* bSizer141; + bSizer141 = new wxBoxSizer(wxHORIZONTAL); + + // TxID + //--------- + m_togTxID = new wxToggleButton(this, wxID_ANY, _("TxID"), wxDefaultPosition, wxDefaultSize, 0); + m_togTxID->SetToolTip(_("Send Tx ID information")); + bSizer141->Add(m_togTxID, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5); + + // RxID + //--------- + m_togRxID = new wxToggleButton(this, wxID_ANY, _("RxID"), wxDefaultPosition, wxDefaultSize, 0); + m_togRxID->SetToolTip(_("Enable reception of ID information")); + bSizer141->Add(m_togRxID, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxALL|wxFIXED_MINSIZE, 5); + + lowerSizer->Add(bSizer141, 0, wxALIGN_RIGHT, 5); +#endif + + centerSizer->Add(lowerSizer, 0, wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxEXPAND, 2); + bSizer1->Add(centerSizer, 4, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 1); + + //===================================================== + // Right side + //===================================================== + wxBoxSizer* rightSizer; + rightSizer = new wxBoxSizer(wxVERTICAL); + + //===================================================== + // Squelch Slider Control + //===================================================== + wxStaticBoxSizer* sbSizer3; + sbSizer3 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Squelch")), wxVERTICAL); + + m_sliderSQ = new wxSlider(this, wxID_ANY, 0, 0, 40, wxDefaultPosition, wxSize(-1,80), wxSL_AUTOTICKS|wxSL_INVERSE|wxSL_VERTICAL); + m_sliderSQ->SetToolTip(_("Set Squelch level in dB.")); + + sbSizer3->Add(m_sliderSQ, 1, wxALIGN_CENTER_HORIZONTAL, 0); + + //------------------------------ + // Squelch Level static text box + //------------------------------ + m_textSQ = new wxStaticText(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE); + + sbSizer3->Add(m_textSQ, 0, wxALIGN_CENTER_HORIZONTAL, 0); + + //------------------------------ + // Squelch Toggle Checkbox + //------------------------------ + m_ckboxSQ = new wxCheckBox(this, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + + sbSizer3->Add(m_ckboxSQ, 0, wxALIGN_CENTER_HORIZONTAL, 0); + rightSizer->Add(sbSizer3, 2, wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 0); + + //rightSizer->Add(sbSizer3_33,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + /* new --- */ + + //------------------------------ + // Mode box + //------------------------------ + wxStaticBoxSizer* sbSizer_mode; + sbSizer_mode = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Mode")), wxVERTICAL); + +#ifdef DISABLED_FEATURE + m_rb1400old = new wxRadioButton( this, wxID_ANY, wxT("1400 V0.91"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + sbSizer_mode->Add(m_rb1400old, 0, wxALIGN_LEFT|wxALL, 1); + m_rb1400 = new wxRadioButton( this, wxID_ANY, wxT("1400"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rb1400, 0, wxALIGN_LEFT|wxALL, 1); + m_rb700 = new wxRadioButton( this, wxID_ANY, wxT("700"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + sbSizer_mode->Add(m_rb700, 0, wxALIGN_LEFT|wxALL, 1); + m_rb700b = new wxRadioButton( this, wxID_ANY, wxT("700B"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + sbSizer_mode->Add(m_rb700b, 0, wxALIGN_LEFT|wxALL, 1); +#endif + m_rb700c = new wxRadioButton( this, wxID_ANY, wxT("700C"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + sbSizer_mode->Add(m_rb700c, 0, wxALIGN_LEFT|wxALL, 1); + m_rb800xa = new wxRadioButton( this, wxID_ANY, wxT("800XA"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rb800xa, 0, wxALIGN_LEFT|wxALL, 1); + m_rb1600 = new wxRadioButton( this, wxID_ANY, wxT("1600"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rb1600, 0, wxALIGN_LEFT|wxALL, 1); + m_rb1600->SetValue(true); + + m_rbPlugIn = NULL; + if (!wxIsEmpty(plugInName)) { + // Optional plug in + + m_rbPlugIn = new wxRadioButton( this, wxID_ANY, plugInName, wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rbPlugIn, 0, wxALIGN_LEFT|wxALL, 1); + } + +#ifdef DISABLED_FEATURE + m_rb1600Wide = new wxRadioButton( this, wxID_ANY, wxT("1600 Wide"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rb1600Wide, 0, wxALIGN_LEFT|wxALL, 1); + m_rb2000 = new wxRadioButton( this, wxID_ANY, wxT("2000"), wxDefaultPosition, wxDefaultSize, 0); + sbSizer_mode->Add(m_rb2000, 0, wxALIGN_LEFT|wxALL, 1); +#endif + + rightSizer->Add(sbSizer_mode,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + + #ifdef MOVED_TO_OPTIONS_DIALOG + /* new --- */ + + //------------------------------ + // Test Frames box + //------------------------------ + + wxStaticBoxSizer* sbSizer_testFrames; + sbSizer_testFrames = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Test Frames")), wxVERTICAL); + + m_ckboxTestFrame = new wxCheckBox(this, wxID_ANY, _("Enable"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE); + sbSizer_testFrames->Add(m_ckboxTestFrame, 0, wxALIGN_LEFT, 0); + + rightSizer->Add(sbSizer_testFrames,0, wxALIGN_CENTER_HORIZONTAL|wxALL|wxEXPAND, 3); + #endif + + //===================================================== + // Control Toggles box + //===================================================== + wxStaticBoxSizer* sbSizer5; + sbSizer5 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Control")), wxVERTICAL); + wxBoxSizer* bSizer1511; + bSizer1511 = new wxBoxSizer(wxVERTICAL); + + //------------------------------- + // Stop/Stop signal processing (rx and tx) + //------------------------------- + m_togBtnOnOff = new wxToggleButton(this, wxID_ANY, _("Start"), wxDefaultPosition, wxDefaultSize, 0); + m_togBtnOnOff->SetToolTip(_("Begin/End receiving data.")); + bSizer1511->Add(m_togBtnOnOff, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + sbSizer5->Add(bSizer1511, 0, wxEXPAND, 1); + +#ifdef UNIMPLEMENTED + //------------------------------ + // Toggle Loopback button for RX + //------------------------------ + wxBoxSizer* bSizer15113; + bSizer15113 = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* bSizer15111; + bSizer15111 = new wxBoxSizer(wxVERTICAL); + wxSize wxSz = wxSize(44, 30); + m_togBtnLoopRx = new wxToggleButton(this, wxID_ANY, _("Loop\nRX"), wxDefaultPosition, wxSz, 0); + m_togBtnLoopRx->SetFont(wxFont(6, 70, 90, 90, false, wxEmptyString)); + m_togBtnLoopRx->SetToolTip(_("Loopback Receive audio data.")); + + bSizer15111->Add(m_togBtnLoopRx, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 0); + + //sbSizer5->Add(bSizer15111, 0, wxEXPAND, 1); + bSizer15113->Add(bSizer15111, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 0); + + //------------------------------ + // Toggle Loopback button for Tx + //------------------------------ + wxBoxSizer* bSizer15112; + bSizer15112 = new wxBoxSizer(wxVERTICAL); + m_togBtnLoopTx = new wxToggleButton(this, wxID_ANY, _("Loop\nTX"), wxDefaultPosition, wxSz, 0); + m_togBtnLoopTx->SetFont(wxFont(6, 70, 90, 90, false, wxEmptyString)); + m_togBtnLoopTx->SetToolTip(_("Loopback Transmit audio data.")); + + bSizer15112->Add(m_togBtnLoopTx, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 0); + bSizer15113->Add(bSizer15112, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 0); + + sbSizer5->Add(bSizer15113, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); +#endif + + //------------------------------ + // Split Frequency Mode Toggle + //------------------------------ + wxBoxSizer* bSizer151; + bSizer151 = new wxBoxSizer(wxVERTICAL); + + m_togBtnSplit = new wxToggleButton(this, wxID_ANY, _("Split"), wxDefaultPosition, wxDefaultSize, 0); + m_togBtnSplit->SetToolTip(_("Toggle split frequency mode.")); + + bSizer151->Add(m_togBtnSplit, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + sbSizer5->Add(bSizer151, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 1); + wxBoxSizer* bSizer13; + bSizer13 = new wxBoxSizer(wxVERTICAL); + + //------------------------------ + // Analog Passthrough Toggle + //------------------------------ + m_togBtnAnalog = new wxToggleButton(this, wxID_ANY, _("Analog"), wxDefaultPosition, wxDefaultSize, 0); + m_togBtnAnalog->SetToolTip(_("Toggle analog/digital operation.")); + bSizer13->Add(m_togBtnAnalog, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + sbSizer5->Add(bSizer13, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + + //------------------------------ + // Voice Keyer Toggle + //------------------------------ + m_togBtnVoiceKeyer = new wxToggleButton(this, wxID_ANY, _("Voice Keyer"), wxDefaultPosition, wxDefaultSize, 0); + m_togBtnVoiceKeyer->SetToolTip(_("Toggle Voice Keyer")); + wxBoxSizer* bSizer13a = new wxBoxSizer(wxVERTICAL); + bSizer13a->Add(m_togBtnVoiceKeyer, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + sbSizer5->Add(bSizer13a, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + + // not implemented on fdmdv2 +#ifdef ALC + //------------------------------ + // Toggle for ALC + //------------------------------ + wxBoxSizer* bSizer14; + bSizer14 = new wxBoxSizer(wxVERTICAL); + m_togBtnALC = new wxToggleButton(this, wxID_ANY, _("ALC"), wxDefaultPosition, wxDefaultSize, 0); + m_togBtnALC->SetToolTip(_("Toggle automatic level control mode.")); + + bSizer14->Add(m_togBtnALC, 0, wxALL, 1); + sbSizer5->Add(bSizer14, 0, wxALIGN_CENTER|wxALIGN_CENTER_HORIZONTAL|wxALL, 1); +#endif + + //------------------------------ + // PTT button: Toggle Transmit/Receive mode + //------------------------------ + wxBoxSizer* bSizer11; + bSizer11 = new wxBoxSizer(wxVERTICAL); + m_btnTogPTT = new wxToggleButton(this, wxID_ANY, _("PTT"), wxDefaultPosition, wxDefaultSize, 0); + m_btnTogPTT->SetToolTip(_("Push to Talk - Switch between Receive and Transmit - you can also use the space bar ")); + bSizer11->Add(m_btnTogPTT, 1, wxALIGN_CENTER|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1); + sbSizer5->Add(bSizer11, 2, wxEXPAND, 1); + rightSizer->Add(sbSizer5, 2, wxALIGN_CENTER|wxALL|wxEXPAND, 3); + bSizer1->Add(rightSizer, 0, wxALL|wxEXPAND, 3); + this->SetSizer(bSizer1); + this->Layout(); + m_statusBar1 = this->CreateStatusBar(3, wxST_SIZEGRIP, wxID_ANY); + + //===================================================== + // End of layout + //===================================================== + + //------------------- + // Connect Events + //------------------- + this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(TopFrame::topFrame_OnClose)); + this->Connect(wxEVT_PAINT, wxPaintEventHandler(TopFrame::topFrame_OnPaint)); + this->Connect(wxEVT_SIZE, wxSizeEventHandler(TopFrame::topFrame_OnSize)); + this->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::topFrame_OnUpdateUI)); + + this->Connect(m_menuItemExit->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnExit)); + this->Connect(m_menuItemOnTop->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnTop)); + + this->Connect(m_menuItemAudio->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsAudio)); + this->Connect(m_menuItemAudio->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsAudioUI)); + this->Connect(m_menuItemFilter->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsFilter)); + this->Connect(m_menuItemFilter->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsFilterUI)); + this->Connect(m_menuItemRigCtrlCfg->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsComCfg)); + this->Connect(m_menuItemRigCtrlCfg->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsComCfgUI)); + this->Connect(m_menuItemOptions->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsOptions)); + this->Connect(m_menuItemOptions->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsOptionsUI)); + + if (!wxIsEmpty(plugInName)) { + this->Connect(m_menuItemPlugIn->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsPlugInCfg)); + this->Connect(m_menuItemPlugIn->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsPlugInCfgUI)); + } + + this->Connect(m_menuItemPlayFileToMicIn->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnPlayFileToMicIn)); + this->Connect(m_menuItemRecFileFromRadio->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnRecFileFromRadio)); + this->Connect(m_menuItemPlayFileFromRadio->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnPlayFileFromRadio)); + + this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpCheckUpdates)); + this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI)); + this->Connect(m_menuItemAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpAbout)); + //m_togRxID->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnRxID), NULL, this); + //m_togTxID->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnTxID), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_TOP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_BOTTOM, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_LINEUP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_LINEDOWN, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_PAGEUP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_PAGEDOWN, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_THUMBRELEASE, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_BOTTOM, wxScrollEventHandler(TopFrame::OnSliderScrollBottom), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(TopFrame::OnCmdSliderScrollChanged), NULL, this); + m_sliderSQ->Connect(wxEVT_SCROLL_TOP, wxScrollEventHandler(TopFrame::OnSliderScrollTop), NULL, this); + m_ckboxSQ->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(TopFrame::OnCheckSQClick), NULL, this); + + m_ckboxSNR->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(TopFrame::OnCheckSNRClick), NULL, this); + + m_togBtnOnOff->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnOnOff), NULL, this); + m_togBtnSplit->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnSplitClick), NULL, this); + m_togBtnAnalog->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnAnalogClick), NULL, this); + m_togBtnVoiceKeyer->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnVoiceKeyerClick), NULL, this); +#ifdef ALC + m_togBtnALC->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnALCClick), NULL, this); +#endif + m_btnTogPTT->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this); + + m_BtnCallSignReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnCallSignReset), NULL, this); + m_BtnBerReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnBerReset), NULL, this); +} + +TopFrame::~TopFrame() +{ + //------------------- + // Disconnect Events + //------------------- + this->Disconnect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(TopFrame::topFrame_OnClose)); + this->Disconnect(wxEVT_PAINT, wxPaintEventHandler(TopFrame::topFrame_OnPaint)); + this->Disconnect(wxEVT_SIZE, wxSizeEventHandler(TopFrame::topFrame_OnSize)); + this->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::topFrame_OnUpdateUI)); + this->Disconnect(ID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnExit)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsAudio)); + this->Disconnect(wxID_ANY, wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsAudioUI)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsFilter)); + this->Disconnect(wxID_ANY, wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsFilterUI)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsComCfg)); + this->Disconnect(wxID_ANY, wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsComCfgUI)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsOptions)); + this->Disconnect(wxID_ANY, wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnToolsOptionsUI)); + + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnToolsPlugInCfg)); + + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnPlayFileToMicIn)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnRecFileFromRadio)); + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnPlayFileFromRadio)); + + this->Disconnect(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpCheckUpdates)); + this->Disconnect(wxID_ANY, wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI)); + this->Disconnect(ID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TopFrame::OnHelpAbout)); + //m_togRxID->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnRxID), NULL, this); + //m_togTxID->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnTxID), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_TOP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_BOTTOM, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_LINEUP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_LINEDOWN, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_PAGEUP, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_PAGEDOWN, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_THUMBRELEASE, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(TopFrame::OnCmdSliderScroll), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_BOTTOM, wxScrollEventHandler(TopFrame::OnSliderScrollBottom), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(TopFrame::OnCmdSliderScrollChanged), NULL, this); + m_sliderSQ->Disconnect(wxEVT_SCROLL_TOP, wxScrollEventHandler(TopFrame::OnSliderScrollTop), NULL, this); + m_ckboxSQ->Disconnect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(TopFrame::OnCheckSQClick), NULL, this); + + m_togBtnOnOff->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnOnOff), NULL, this); + m_togBtnSplit->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnSplitClick), NULL, this); + m_togBtnAnalog->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnAnalogClick), NULL, this); + m_togBtnVoiceKeyer->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnVoiceKeyerClick), NULL, this); +#ifdef ALC + m_togBtnALC->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnALCClick), NULL, this); +#endif + m_btnTogPTT->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this); + +} + diff --git a/freedv/tags/1.2.2/src/topFrame.h b/freedv/tags/1.2.2/src/topFrame.h new file mode 100644 index 00000000..37950a99 --- /dev/null +++ b/freedv/tags/1.2.2/src/topFrame.h @@ -0,0 +1,194 @@ +//========================================================================== +// Name: topFrame.h +// +// Purpose: Implements simple wxWidgets application with GUI. +// Created: Apr. 9, 2012 +// Authors: David Rowe, David Witten +// +// License: +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program; if not, see . +// +//========================================================================== +#ifndef __TOPFRAME_H__ +#define __TOPFRAME_H__ + +#include "version.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/////////////////////////////////////////////////////////////////////////// + +#define ID_OPEN 1000 +#define ID_SAVE 1001 +#define ID_CLOSE 1002 +#define ID_EXIT 1003 +#define ID_COPY 1004 +#define ID_CUT 1005 +#define ID_PASTE 1006 +#define ID_OPTIONS 1007 +#define ID_ABOUT 1008 + +/////////////////////////////////////////////////////////////////////////////// +/// Class TopFrame +/////////////////////////////////////////////////////////////////////////////// +class TopFrame : public wxFrame +{ + private: + + protected: + wxMenuBar* m_menubarMain; + wxMenu* file; + wxMenu* edit; + wxMenu* tools; + wxMenu* help; + wxGauge* m_gaugeSNR; + wxStaticText* m_textSNR; + wxCheckBox* m_ckboxSNR; + wxGauge* m_gaugeLevel; + wxStaticText* m_textLevel; + + wxButton* m_BtnCallSignReset; + wxTextCtrl* m_txtCtrlCallSign; + wxStaticText* m_txtChecksumGood; + wxStaticText* m_txtChecksumBad; + + wxSlider* m_sliderSQ; + wxCheckBox* m_ckboxSQ; + wxStaticText* m_textSQ; + wxStatusBar* m_statusBar1; + + wxButton* m_BtnBerReset; + wxStaticText *m_textBits; + wxStaticText *m_textErrors; + wxStaticText *m_textBER; + wxStaticText *m_textResyncs; + + wxRadioButton *m_rbSync; + wxRadioButton *m_rb1400old; + wxRadioButton *m_rb1400; + wxRadioButton *m_rb700; + wxRadioButton *m_rb700b; + wxRadioButton *m_rb700c; + wxRadioButton *m_rb800xa; + wxRadioButton *m_rb1600; + wxRadioButton *m_rb2000; + wxRadioButton *m_rb1600Wide; + wxRadioButton *m_rbPlugIn; + + // Virtual event handlers, overide them in your derived class + virtual void topFrame_OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void topFrame_OnPaint( wxPaintEvent& event ) { event.Skip(); } + virtual void topFrame_OnSize( wxSizeEvent& event ) { event.Skip(); } + virtual void topFrame_OnUpdateUI( wxUpdateUIEvent& event ) { event.Skip(); } + + virtual void OnExit( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTop( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsAudio( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsAudioUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnToolsFilter( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsFilterUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnToolsOptions( wxCommandEvent& event ) { event.Skip(); } + + virtual void OnToolsPlugInCfg( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsPlugInCfgUI( wxUpdateUIEvent& event ) { event.Skip(); } + + virtual void OnToolsUDP( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsOptionsUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnToolsComCfg( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToolsComCfgUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnPlayFileToMicIn( wxCommandEvent& event ) { event.Skip(); } + virtual void OnRecFileFromRadio( wxCommandEvent& event ) { event.Skip(); } + virtual void OnPlayFileFromRadio( wxCommandEvent& event ) { event.Skip(); } + + virtual void OnHelpCheckUpdates( wxCommandEvent& event ) { event.Skip(); } + virtual void OnHelpCheckUpdatesUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnHelpAbout( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnRxID( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnTxID( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCmdSliderScroll( wxScrollEvent& event ) { event.Skip(); } + virtual void OnSliderScrollBottom( wxScrollEvent& event ) { event.Skip(); } + virtual void OnCmdSliderScrollChanged( wxScrollEvent& event ) { event.Skip(); } + virtual void OnSliderScrollTop( wxScrollEvent& event ) { event.Skip(); } + virtual void OnCheckSQClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCheckSNRClick( wxCommandEvent& event ) { event.Skip(); } + + virtual void OnTogBtnLoopRx( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnLoopTx( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnOnOff( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnSplitClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnAnalogClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnVoiceKeyerClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnALCClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTogBtnPTT( wxCommandEvent& event ) { event.Skip(); } + + virtual void OnTogBtnSplitClickUI(wxUpdateUIEvent& event) { event.Skip(); } + virtual void OnTogBtnAnalogClickUI(wxUpdateUIEvent& event) { event.Skip(); } + virtual void OnTogBtnALCClickUI(wxUpdateUIEvent& event) { event.Skip(); } + virtual void OnTogBtnRxIDUI(wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnTogBtnTxIDUI(wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnTogBtnPTT_UI(wxUpdateUIEvent& event ) { event.Skip(); } + virtual void OnTogBtnOnOffUI(wxUpdateUIEvent& event ) { event.Skip(); } + + virtual void OnCallSignReset( wxCommandEvent& event ) { event.Skip(); } + virtual void OnBerReset( wxCommandEvent& event ) { event.Skip(); } + + public: + wxToggleButton* m_togRxID; + wxToggleButton* m_togTxID; + wxToggleButton* m_togBtnOnOff; + wxToggleButton* m_togBtnSplit; + wxToggleButton* m_togBtnAnalog; + wxToggleButton* m_togBtnVoiceKeyer; + wxToggleButton* m_togBtnALC; + wxToggleButton* m_btnTogPTT; + wxToggleButton* m_togBtnLoopRx; + wxToggleButton* m_togBtnLoopTx; + wxAuiNotebook* m_auiNbookCtrl; + + TopFrame( wxString plugInName, wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("FreeDV ") + _(FREEDV_VERSION), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(561,300 ), long style = wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER|wxTAB_TRAVERSAL ); + + ~TopFrame(); +}; + +#endif //__TOPFRAME_H__ -- 2.25.1