├── .gitmodules ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── cmake ├── FindStaticFLTK.cmake ├── GNUWarnings.cmake ├── PdExternal.cmake ├── Require.cmake └── StaticLinking.cmake ├── docs ├── patch.png ├── screen-ogvisu~.png ├── screen-sgvisu~.png └── screen-wfvisu~.png ├── objects.txt ├── ogvisu~-help.pd ├── scripts ├── deken-package.sh └── run-jack-visu~.sh ├── sgvisu~-help.pd ├── src ├── .clang_complete ├── gui │ ├── fl_util.h │ ├── fl_widgets_ex.cc │ ├── fl_widgets_ex.h │ ├── s_math.h │ ├── s_smem.h │ ├── w_dft_spectrogram.cc │ ├── w_dft_spectrogram.h │ ├── w_dft_transfer.cc │ ├── w_dft_transfer.h │ ├── w_dft_visu.h │ ├── w_dft_waterfall.cc │ ├── w_dft_waterfall.h │ ├── w_ts_oscillogram.cc │ ├── w_ts_oscillogram.h │ ├── w_ts_visu.h │ └── w_visu.h ├── jack-main.cc ├── ogvisu~.cc ├── ogvisu~.h ├── sgvisu~.cc ├── sgvisu~.h ├── tfvisu~.cc ├── tfvisu~.h ├── util │ ├── scope_guard.h │ ├── self_path.cc │ ├── self_path.h │ ├── unit_format.cc │ ├── unit_format.h │ ├── unix.h │ ├── unix.tcc │ ├── unix_fd.h │ ├── unix_fd.tcc │ ├── unix_sock.h │ ├── unix_sock.tcc │ ├── win32_argv.cc │ ├── win32_argv.h │ ├── win32_socketpair.cc │ └── win32_socketpair.h ├── visu~-common.h ├── visu~-gui.cc ├── visu~-remote.cc ├── visu~-remote.h ├── visu~.cc ├── visu~.h ├── wfvisu~.cc └── wfvisu~.h ├── thirdparty └── Fl_Knob │ ├── Fl_Knob │ ├── Fl_Knob.H │ ├── Fl_Knob.cxx │ └── Fl_Knob.fld │ ├── Makefile │ ├── README.txt │ ├── Test.cxx │ ├── Test.fld │ └── Test.h ├── visu~-help.pd └── wfvisu~-help.pd /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/color"] 2 | path = thirdparty/color 3 | url = https://github.com/dmilos/color.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | project(pd-visualization VERSION 0.1 LANGUAGES C CXX) 4 | 5 | set(STATIC_LINK_DEFAULT OFF) 6 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 7 | set(STATIC_LINK_DEFAULT ON) 8 | endif() 9 | 10 | option(USE_FFTW "Use FFTW" ON) 11 | option(USE_STATIC_FLTK "Use static FLTK" "${STATIC_LINK_DEFAULT}") 12 | option(USE_STATIC_FFTW "Use static FFTW" "${STATIC_LINK_DEFAULT}") 13 | option(LINK_STATICALLY "Use static linking" "${STATIC_LINK_DEFAULT}") 14 | option(USE_LIBCXX "Use libc++" OFF) 15 | 16 | if(USE_LIBCXX) 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 18 | endif() 19 | 20 | include(FindPkgConfig) 21 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 22 | include(GNUWarnings) 23 | include(PdExternal) 24 | include(Require) 25 | include(StaticLinking) 26 | 27 | set_gnu_warning("all") 28 | set_gnu_warning("no-unused-local-typedefs") 29 | 30 | set(FLTK_SKIP_OPENGL TRUE) 31 | set(FLTK_SKIP_FORMS TRUE) 32 | set(FLTK_SKIP_IMAGES TRUE) 33 | set(FLTK_SKIP_FLUID TRUE) 34 | 35 | find_package(Threads REQUIRED) 36 | find_package(FLTK REQUIRED) 37 | 38 | if(USE_STATIC_FLTK OR LINK_STATICALLY) 39 | find_package(StaticFLTK REQUIRED) 40 | set(PROJECT_FLTK_LIBRARIES "${StaticFLTK_LIBRARIES}") 41 | else() 42 | set(PROJECT_FLTK_LIBRARIES "${FLTK_LIBRARIES}") 43 | endif() 44 | 45 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 46 | find_package(X11 REQUIRED) 47 | require_library(FONTCONFIG_LIBRARY "fontconfig") 48 | set(PROJECT_FLTK_LIBRARIES "${PROJECT_FLTK_LIBRARIES}" 49 | ${X11_Xrender_LIB} ${X11_Xcursor_LIB} ${X11_Xfixes_LIB} ${X11_Xext_LIB} ${X11_Xft_LIB} ${X11_Xinerama_LIB} 50 | ${FONTCONFIG_LIBRARY} 51 | dl) 52 | endif() 53 | link_directories(${FLTK_LIBRARY_DIRS}) 54 | 55 | if(USE_FFTW) 56 | require_path(FFTW3F_INCLUDE_DIR "fftw3.h") 57 | require_library(FFTW3F_LIBRARY "fftw3f") 58 | if(USE_STATIC_FFTW OR LINK_STATICALLY) 59 | require_static_library(StaticFFTW3F_LIBRARY "fftw3f") 60 | set(PROJECT_FFTW3F_LIBRARIES "${StaticFFTW3F_LIBRARY}") 61 | else() 62 | set(PROJECT_FFTW3F_LIBRARIES "${FFTW3F_LIBRARY}") 63 | endif() 64 | endif() 65 | 66 | set(CMAKE_CXX_STANDARD "14") 67 | add_definitions("-D__STDC_FORMAT_MACROS=1") 68 | 69 | set(visu_names "wfvisu~" "ogvisu~" "sgvisu~" "tfvisu~") 70 | 71 | set(visu_common_SOURCES 72 | src/visu~.cc 73 | src/visu~-remote.cc 74 | src/util/self_path.cc) 75 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 76 | set(visu_common_SOURCES ${visu_common_SOURCES} 77 | src/util/win32_argv.cc 78 | src/util/win32_socketpair.cc) 79 | endif() 80 | 81 | add_library(visu_common STATIC ${visu_common_SOURCES}) 82 | target_include_directories(visu_common 83 | PUBLIC src 84 | PUBLIC "${PD_INCLUDE_DIR}") 85 | target_link_libraries(visu_common 86 | PUBLIC ${PD_LIBRARIES} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) 87 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 88 | target_link_libraries(visu_common 89 | PUBLIC dl) 90 | endif() 91 | set_target_properties(visu_common PROPERTIES 92 | POSITION_INDEPENDENT_CODE TRUE) 93 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 94 | target_link_libraries(visu_common PUBLIC ws2_32 shlwapi) 95 | endif() 96 | 97 | add_pd_external(wfvisu_tilde "wfvisu~" src/wfvisu~.cc) 98 | target_link_libraries(wfvisu_tilde visu_common) 99 | 100 | add_pd_external(ogvisu_tilde "ogvisu~" src/ogvisu~.cc) 101 | target_link_libraries(ogvisu_tilde visu_common) 102 | 103 | add_pd_external(sgvisu_tilde "sgvisu~" src/sgvisu~.cc) 104 | target_link_libraries(sgvisu_tilde visu_common) 105 | 106 | add_pd_external(tfvisu_tilde "tfvisu~" src/tfvisu~.cc) 107 | target_link_libraries(tfvisu_tilde visu_common) 108 | 109 | ### 110 | 111 | add_library(Fl_Knob STATIC 112 | thirdparty/Fl_Knob/Fl_Knob/Fl_Knob.cxx) 113 | target_include_directories(Fl_Knob 114 | PUBLIC thirdparty/Fl_Knob 115 | PUBLIC "${FLTK_INCLUDE_DIR}") 116 | target_link_libraries(Fl_Knob ${PROJECT_FLTK_LIBRARIES}) 117 | 118 | set(visu_gui_SOURCES 119 | src/visu~-gui.cc 120 | src/gui/w_dft_spectrogram.cc 121 | src/gui/w_dft_waterfall.cc 122 | src/gui/w_dft_transfer.cc 123 | src/gui/w_ts_oscillogram.cc 124 | src/gui/fl_widgets_ex.cc 125 | src/util/unit_format.cc) 126 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 127 | set(visu_gui_SOURCES ${visu_gui_SOURCES} 128 | src/util/win32_argv.cc 129 | src/util/win32_socketpair.cc) 130 | endif() 131 | 132 | add_executable(visu_gui ${visu_gui_SOURCES}) 133 | set_target_properties(visu_gui PROPERTIES 134 | OUTPUT_NAME "visu~-gui" 135 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}") 136 | target_compile_definitions(visu_gui 137 | PRIVATE COLOR_USE_PP2FILE=1) 138 | target_include_directories(visu_gui 139 | PRIVATE src 140 | PRIVATE thirdparty/color/src 141 | PRIVATE "${FLTK_INCLUDE_DIR}") 142 | target_link_libraries(visu_gui Fl_Knob ${PROJECT_FLTK_LIBRARIES}) 143 | if(LINK_STATICALLY) 144 | target_static_link(visu_gui) 145 | endif() 146 | 147 | if(USE_FFTW) 148 | target_compile_definitions(visu_gui 149 | PRIVATE "USE_FFTW=1") 150 | target_include_directories(visu_gui 151 | PRIVATE "${FFTW3F_INCLUDE_DIR}") 152 | target_link_libraries(visu_gui 153 | "${PROJECT_FFTW3F_LIBRARIES}") 154 | else() 155 | target_include_directories(visu_gui 156 | PRIVATE thirdparty/kfr/include) 157 | endif() 158 | 159 | pkg_check_modules(JACK jack) 160 | if(JACK_FOUND) 161 | link_directories(${JACK_LIBRARY_DIRS}) 162 | include_directories(${JACK_INCLUDE_DIRS}) 163 | add_executable(visu_jack 164 | src/jack-main.cc) 165 | target_link_libraries(visu_jack 166 | PRIVATE visu_common PRIVATE ${JACK_LIBRARIES}) 167 | set_target_properties(visu_jack PROPERTIES 168 | OUTPUT_NAME "visu~-jack" 169 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}") 170 | install(TARGETS visu_jack DESTINATION bin) 171 | install(TARGETS visu_gui DESTINATION bin) 172 | foreach(visu_name ${visu_names}) 173 | install(FILES "scripts/run-jack-visu~.sh" 174 | PERMISSIONS 175 | OWNER_READ OWNER_WRITE OWNER_EXECUTE 176 | GROUP_READ GROUP_EXECUTE 177 | WORLD_READ WORLD_EXECUTE 178 | DESTINATION bin 179 | RENAME "${visu_name}") 180 | endforeach() 181 | endif() 182 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pd-visualization 2 | 3 | This pd-visualization package provides Pure Data externals for visualizing signals. 4 | 5 | These visualizers run in external processes and do not disturb real-time signal processing. 6 | 7 | A standalone version for the Jack Audio Connection Kit is also part of the package. 8 | 9 | ![patch](docs/patch.png) 10 | 11 | The bang message is used to show or hide the visualizer window. By default the visualizer is not started. 12 | When started and in the hidden state, the visualizers do not consume any CPU time. 13 | 14 | Platform compatibility: 15 | - Linux 16 | - Windows 17 | - probably Darwin, testing is needed 18 | 19 | To build the project, follow the usual CMake command line steps. The built puredata externals are located in the project directory, and the standalone versions are installed on the system. To use this in Pure Data, add the directory of this project to the search paths in preferences. 20 | 21 | ``` 22 | mkdir build ; cd build 23 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. 24 | make 25 | sudo make install 26 | ``` 27 | 28 | ## wfvisu~ 29 | 30 | This visualization shows a waterfall view of the DFT of the input. 31 | 32 | ![wfvisu~](docs/screen-wfvisu~.png) 33 | 34 | ## sgvisu~ 35 | 36 | This visualization shows a spectrogram of the DFT of the input. 37 | 38 | ![sgvisu~](docs/screen-sgvisu~.png) 39 | 40 | ## ogvisu~ 41 | 42 | This visualization shows an oscillogram of the input. 43 | 44 | Features: 45 | - collapsible UI controls 46 | - control of position and scale 47 | - trigger 48 | 49 | ![ogvisu~](docs/screen-ogvisu~.png) 50 | -------------------------------------------------------------------------------- /cmake/FindStaticFLTK.cmake: -------------------------------------------------------------------------------- 1 | 2 | find_package(FLTK) 3 | include(FindPackageHandleStandardArgs) 4 | include(StaticLinking) 5 | 6 | if(NOT FLTK_FOUND) 7 | set(StaticFLTK_FOUND "StaticFLTK-NOTFOUND") 8 | else() 9 | find_static_library_of_shared_library(StaticFLTK_BASE_LIBRARY "${FLTK_BASE_LIBRARY}") 10 | if(NOT StaticFLTK_BASE_LIBRARY) 11 | set(StaticFLTK_FOUND "StaticFLTK-NOTFOUND") 12 | else() 13 | set(StaticFLTK_FOUND "StaticFLTK-FOUND") 14 | message(STATUS "Found static FLTK: ${StaticFLTK_BASE_LIBRARY}") 15 | if(NOT FLTK_SKIP_OPENGL) 16 | find_static_library_of_shared_library(StaticFLTK_GL_LIBRARY "${FLTK_GL_LIBRARY}") 17 | message(STATUS "Found static FLTK GL: ${StaticFLTK_GL_LIBRARY}") 18 | endif() 19 | if(NOT FLTK_SKIP_FORMS) 20 | find_static_library_of_shared_library(StaticFLTK_FORMS_LIBRARY "${FLTK_FORMS_LIBRARY}") 21 | message(STATUS "Found static FLTK forms: ${StaticFLTK_FORMS_LIBRARY}") 22 | endif() 23 | if(NOT FLTK_SKIP_IMAGES) 24 | find_static_library_of_shared_library(StaticFLTK_IMAGES_LIBRARY "${FLTK_IMAGES_LIBRARY}") 25 | message(STATUS "Found static FLTK images: ${StaticFLTK_IMAGES_LIBRARY}") 26 | endif() 27 | endif() 28 | set(StaticFLTK_LIBRARIES) 29 | foreach(_lib ${FLTK_LIBRARIES}) 30 | if(_lib STREQUAL "${FLTK_BASE_LIBRARY}") 31 | list(APPEND StaticFLTK_LIBRARIES "${StaticFLTK_BASE_LIBRARY}") 32 | elseif(_lib STREQUAL "${FLTK_GL_LIBRARY}") 33 | list(APPEND StaticFLTK_LIBRARIES "${StaticFLTK_GL_LIBRARY}") 34 | elseif(_lib STREQUAL "${FLTK_FORMS_LIBRARY}") 35 | list(APPEND StaticFLTK_LIBRARIES "${StaticFLTK_FORMS_LIBRARY}") 36 | elseif(_lib STREQUAL "${FLTK_IMAGES_LIBRARY}") 37 | list(APPEND StaticFLTK_LIBRARIES "${StaticFLTK_IMAGES_LIBRARY}") 38 | else() 39 | list(APPEND StaticFLTK_LIBRARIES "${_lib}") 40 | endif() 41 | endforeach() 42 | endif() 43 | 44 | find_package_handle_standard_args(StaticFLTK 45 | REQUIRED_VARS StaticFLTK_FOUND) 46 | -------------------------------------------------------------------------------- /cmake/GNUWarnings.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Enable all warnings 3 | # 4 | 5 | include(CheckCCompilerFlag) 6 | include(CheckCXXCompilerFlag) 7 | 8 | macro(set_gnu_warning id) 9 | if ("${CMAKE_C_COMPILER_ID}" MATCHES GNU OR 10 | "${CMAKE_C_COMPILER_ID}" MATCHES Clang) 11 | check_c_compiler_flag("-W${id}" _test) 12 | if(_test) 13 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W${id}") 14 | endif() 15 | endif() 16 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES GNU OR 17 | "${CMAKE_CXX_COMPILER_ID}" MATCHES Clang) 18 | check_cxx_compiler_flag("-W${id}" _test) 19 | if(_test) 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W${id}") 21 | endif() 22 | endif() 23 | unset(_test) 24 | endmacro() 25 | -------------------------------------------------------------------------------- /cmake/PdExternal.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Puredata externals 3 | # 4 | 5 | include(StaticLinking) 6 | 7 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 8 | if(NOT PD_WINDOWS_PROGRAM_DIR) 9 | message(FATAL_ERROR "Please set PD_WINDOWS_PROGRAM_DIR on the Windows platform") 10 | endif() 11 | set(PD_INCLUDE_DIR "${PD_WINDOWS_PROGRAM_DIR}/src") 12 | set(PD_LIBRARIES "${PD_WINDOWS_PROGRAM_DIR}/bin/pd.lib") 13 | else() 14 | find_path(PD_INCLUDE_DIR "m_pd.h") 15 | set(PD_LIBRARIES) 16 | endif() 17 | 18 | if(NOT PD_INCLUDE_DIR OR NOT EXISTS "${PD_INCLUDE_DIR}/m_pd.h") 19 | message(FATAL_ERROR "Cannot find the Puredata headers") 20 | endif() 21 | 22 | message(STATUS "Found Puredata headers: ${PD_INCLUDE_DIR}") 23 | 24 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 25 | set(PD_EXTERNAL_SUFFIX ".pd_linux") 26 | elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") 27 | set(PD_EXTERNAL_SUFFIX ".pd_darwin") 28 | elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") 29 | set(PD_EXTERNAL_SUFFIX ".dll") 30 | else() 31 | message(FATAL_ERROR "Unrecognized platform") 32 | endif() 33 | 34 | macro(add_pd_external TARGET NAME) 35 | add_library(${TARGET} MODULE ${ARGN}) 36 | target_include_directories(${TARGET} 37 | PRIVATE "${PD_INCLUDE_DIR}") 38 | target_compile_definitions(${TARGET} 39 | PRIVATE "PD") 40 | target_link_libraries(${TARGET} 41 | ${PD_LIBRARIES}) 42 | set_target_properties(${TARGET} PROPERTIES 43 | OUTPUT_NAME "${NAME}" 44 | LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}" 45 | PREFIX "" 46 | SUFFIX "${PD_EXTERNAL_SUFFIX}") 47 | if(CMAKE_SYSTEM_NAME MATCHES Darwin) 48 | set_target_properties(${TARGET} PROPERTIES 49 | LINK_FLAGS "-Wl,-undefined,suppress,-flat_namespace,-bundle") 50 | endif() 51 | if(LINK_STATICALLY) 52 | target_static_link(${TARGET}) 53 | endif() 54 | endmacro() 55 | -------------------------------------------------------------------------------- /cmake/Require.cmake: -------------------------------------------------------------------------------- 1 | 2 | macro(require_path VAR NAME1) 3 | find_path(${VAR} "${NAME1}" ${ARGN}) 4 | if(NOT ${VAR}) 5 | message(FATAL_ERROR "cannot find path of ${NAME1}") 6 | else() 7 | message(STATUS "Found ${NAME1} in path: ${${VAR}}") 8 | endif() 9 | endmacro() 10 | 11 | macro(require_file VAR NAME1) 12 | find_file(${VAR} "${NAME1}" ${ARGN}) 13 | if(NOT ${VAR}) 14 | message(FATAL_ERROR "cannot find file ${NAME1}") 15 | else() 16 | message(STATUS "Found file ${NAME1}: ${${VAR}}") 17 | endif() 18 | endmacro() 19 | 20 | macro(require_library VAR NAME1) 21 | find_library(${VAR} "${NAME1}" ${ARGN}) 22 | if(NOT ${VAR}) 23 | message(FATAL_ERROR "cannot find library ${NAME1}") 24 | else() 25 | message(STATUS "Found library ${NAME1}: ${${VAR}}") 26 | endif() 27 | endmacro() 28 | 29 | macro(require_static_library VAR NAME1) 30 | find_static_library(${VAR} "${NAME1}" ${ARGN}) 31 | if(NOT ${VAR}) 32 | message(FATAL_ERROR "cannot find static library ${NAME1}") 33 | else() 34 | message(STATUS "Found static library ${NAME1}: ${${VAR}}") 35 | endif() 36 | endmacro() 37 | -------------------------------------------------------------------------------- /cmake/StaticLinking.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Find static library 3 | # 4 | 5 | macro(target_static_link TARGET) 6 | if ("${CMAKE_C_COMPILER_ID}" MATCHES GNU OR 7 | "${CMAKE_C_COMPILER_ID}" MATCHES Clang) 8 | set_property(TARGET ${TARGET} 9 | APPEND_STRING 10 | PROPERTY LINK_FLAGS " -static") 11 | else() 12 | message(WARNING "Static linking not supported on this compiler") 13 | endif() 14 | endmacro() 15 | 16 | macro(find_static_library) 17 | set(_orig_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") 18 | if (WIN32) 19 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".a") 20 | else() 21 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") 22 | endif() 23 | find_library(${ARGN}) 24 | set(CMAKE_FIND_LIBRARY_SUFFIXES "${_orig_CMAKE_FIND_LIBRARY_SUFFIXES}") 25 | unset(_orig_CMAKE_FIND_LIBRARY_SUFFIXES) 26 | endmacro() 27 | 28 | macro(find_static_library_of_shared_library VAR LIBRARY) 29 | set(_lib "${LIBRARY}") 30 | if(NOT _lib) 31 | set(${VAR} "${VAR}-NOTFOUND") 32 | elseif(NOT IS_ABSOLUTE "${_lib}") 33 | find_static_library(${VAR} "${_lib}") 34 | else() 35 | get_filename_component(_dir "${_lib}" DIRECTORY) 36 | get_filename_component(_name "${_lib}" NAME_WE) 37 | set(_staticlib "${_dir}/${_name}${CMAKE_STATIC_LIBRARY_SUFFIX}") 38 | if(NOT EXISTS "${_lib}") 39 | set(${VAR} "${VAR}-NOTFOUND") 40 | else() 41 | set(${VAR} "${_staticlib}") 42 | endif() 43 | endif() 44 | unset(_lib) 45 | unset(_dir) 46 | unset(_name) 47 | unset(_staticlib) 48 | endmacro() 49 | -------------------------------------------------------------------------------- /docs/patch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpcima/pd-visualization/b303c457298be2642c4a131315109996b3da8d8d/docs/patch.png -------------------------------------------------------------------------------- /docs/screen-ogvisu~.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpcima/pd-visualization/b303c457298be2642c4a131315109996b3da8d8d/docs/screen-ogvisu~.png -------------------------------------------------------------------------------- /docs/screen-sgvisu~.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpcima/pd-visualization/b303c457298be2642c4a131315109996b3da8d8d/docs/screen-sgvisu~.png -------------------------------------------------------------------------------- /docs/screen-wfvisu~.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpcima/pd-visualization/b303c457298be2642c4a131315109996b3da8d8d/docs/screen-wfvisu~.png -------------------------------------------------------------------------------- /objects.txt: -------------------------------------------------------------------------------- 1 | wfvisu~ 2 | sgvisu~ 3 | ogvisu~ 4 | -------------------------------------------------------------------------------- /ogvisu~-help.pd: -------------------------------------------------------------------------------- 1 | visu~-help.pd -------------------------------------------------------------------------------- /scripts/deken-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | pkgname=jpcvisu 3 | dekformat=1 # 0=old (.zip/.tar.gz) 1=new (.dek) 4 | 5 | case "$#" in 6 | 1) pkgos=$1; pkgver=$(git describe) ;; 7 | 2) pkgos=$1; pkgver=$2 ;; 8 | *) exit 1 ;; 9 | esac 10 | 11 | scriptdir=`dirname "$0"` 12 | cd "$scriptdir/.." 13 | 14 | if test "${pkgver:0:1}" = v; then 15 | pkgver="${pkgver:1}" 16 | fi 17 | 18 | mkdir -p deken-pkg 19 | 20 | rm -rf deken-tmp 21 | mkdir -p "deken-tmp/$pkgname" 22 | cp -va *.md src cmake CMakeLists.txt "deken-tmp/$pkgname/" 23 | cp -va *.pd "deken-tmp/$pkgname/" 24 | 25 | case "$pkgos" in 26 | linux) cp -va *.pd_linux visu~-gui "deken-tmp/$pkgname/" ;; 27 | windows) cp -va *.dll visu~-gui.exe "deken-tmp/$pkgname/" ;; 28 | mac) cp -va *.pd_darwin visu~-gui "deken-tmp/$pkgname/" ;; 29 | esac 30 | 31 | mkdir -p "deken-tmp/$pkgname/thirdparty" 32 | find thirdparty -type f | while read f; do 33 | case "$f" in 34 | thirdparty/color/.git*) ;; 35 | thirdparty/color/doc/*) ;; 36 | thirdparty/color/tmp/*) ;; 37 | thirdparty/color/example/*) ;; 38 | thirdparty/color/favicon.*) ;; 39 | thirdparty/color/src/color/*/*) ;; 40 | thirdparty/color/src/color/color.body.hpp) ;; 41 | thirdparty/Fl_Knob/Makefile) ;; 42 | thirdparty/Fl_Knob/Test.*) ;; 43 | *) install -v -D -m 644 "$f" "deken-tmp/$pkgname/$f" ;; 44 | esac 45 | done 46 | 47 | cd deken-tmp 48 | deken package --dekformat "$dekformat" --version "$pkgver" "$pkgname" 49 | if test "$dekformat" -lt 1; then 50 | case "$pkgos" in 51 | windows) mv -f *.zip *.zip.* ../deken-pkg/ ;; 52 | *) mv -f *.tar.gz *.tar.gz.* ../deken-pkg/ ;; 53 | esac 54 | else 55 | mv -f *.dek *.dek.* ../deken-pkg/ 56 | fi 57 | cp -f ../objects.txt "../deken-pkg/$pkgname-v$pkgver-objects.txt" 58 | -------------------------------------------------------------------------------- /scripts/run-jack-visu~.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | exec "`dirname "$0"`/visu~-jack" -t "`basename "$0"`" "$@" 3 | -------------------------------------------------------------------------------- /sgvisu~-help.pd: -------------------------------------------------------------------------------- 1 | visu~-help.pd -------------------------------------------------------------------------------- /src/.clang_complete: -------------------------------------------------------------------------------- 1 | -std=gnu++14 2 | -DPD 3 | -DUSE_FFTW=1 4 | -I. 5 | -I../thirdparty/color/src 6 | -I../thirdparty/kfr/include 7 | -I../thirdparty/Fl_Knob 8 | -------------------------------------------------------------------------------- /src/gui/fl_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define VALUE_CALLBACK(v, r, f) \ 5 | (v)->callback([](Fl_Widget *w, void *p) { \ 6 | static_cast(p)->f( \ 7 | static_cast(w)->value()); \ 8 | }, (r)); 9 | 10 | #define TRIGGER_CALLBACK(v, r, f) \ 11 | (v)->callback([](Fl_Widget *w, void *p) { \ 12 | static_cast(p)->f(); \ 13 | }, (r)); 14 | -------------------------------------------------------------------------------- /src/gui/fl_widgets_ex.cc: -------------------------------------------------------------------------------- 1 | #include "fl_widgets_ex.h" 2 | #include 3 | 4 | int Fl_KnobEx::handle(int event) { 5 | if (event == FL_MOUSEWHEEL) { 6 | int wx = this->x(); 7 | int wy = this->y(); 8 | int ww = this->w(); 9 | int wh = this->h(); 10 | int mx = Fl::event_x(); 11 | int my = Fl::event_y(); 12 | if (mx >= wx && mx < wx + ww && my >= wy && my < wy + wh) { 13 | int dx = Fl::event_dx(); 14 | int dy = Fl::event_dy(); 15 | double val = this->value(); 16 | double range = this->maximum() - this->minimum(); 17 | val += range * this->wheelstep * (dx - dy); 18 | if (this->value(this->clamp(val))) { 19 | this->set_changed(); 20 | this->do_callback(); 21 | } 22 | return true; 23 | } 24 | } 25 | return Fl_Knob::handle(event); 26 | } 27 | -------------------------------------------------------------------------------- /src/gui/fl_widgets_ex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class Fl_KnobEx : public Fl_Knob { 5 | public: 6 | Fl_KnobEx(int x, int y, int w, int h, const char *l = nullptr) 7 | : Fl_Knob(x, y, w, h, l) {} 8 | int handle(int event) override; 9 | private: 10 | double wheelstep = 1.0 / 20; 11 | }; 12 | -------------------------------------------------------------------------------- /src/gui/s_math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | template 5 | R clamp(R x, R xmin, R xmax) { 6 | return (x < xmin) ? xmin : (x > xmax) ? xmax : x; 7 | } 8 | 9 | template 10 | R interp_linear(const R y[], R mu) { 11 | return y[0] * (1 - mu) + y[1] * mu; 12 | } 13 | 14 | template 15 | R interp_catmull(const R y[], R mu) { 16 | R mu2 = mu * mu; 17 | R mu3 = mu2 * mu; 18 | R a[] = {- R(0.5) * y[0] + R(1.5) * y[1] - R(1.5) * y[2] + R(0.5) * y[3], 19 | y[0] - R(2.5) * y[1] + R(2) * y[2] - R(0.5) * y[3], 20 | - R(0.5) * y[0] + R(0.5) * y[2], 21 | y[1]}; 22 | return a[0] * mu3 + a[1] * mu2 + a[2] * mu + a[3]; 23 | } 24 | 25 | template 26 | R window_nutall(R r) { 27 | R a[] = {0.355768, -0.487396, 0.144232, -0.012604}; 28 | R p = r * 2 * R(M_PI); 29 | R w = a[0]; 30 | for (unsigned i = 1; i < 4; ++i) 31 | w += a[i] * std::cos(i * p); 32 | return w; 33 | } 34 | -------------------------------------------------------------------------------- /src/gui/s_smem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template 6 | struct frame { 7 | float samples[ChannelMax] = {}; 8 | }; 9 | 10 | template 11 | class sample_memory { 12 | public: 13 | typedef frame frame_type; 14 | 15 | sample_memory() {} 16 | explicit sample_memory(unsigned size) { this->resize(size); } 17 | 18 | unsigned size() const { return bufsize_; } 19 | 20 | void resize(unsigned size) { 21 | buf_.reset(new frame_type[2 * size]()); 22 | bufsize_ = size; 23 | writeindex_ = 0; 24 | } 25 | 26 | void append(frame_type frame) { 27 | buf_[writeindex_] = buf_[writeindex_ + bufsize_] = frame; 28 | writeindex_ = (writeindex_ + 1) % bufsize_; 29 | } 30 | 31 | void append(const float samples[], unsigned nsamples) { 32 | frame_type frame; 33 | std::copy_n(samples, std::min(nsamples, ChannelMax), frame.samples); 34 | append(frame); 35 | } 36 | 37 | const frame_type *data() const { 38 | unsigned readindex = writeindex_; 39 | return buf_.get() + readindex; 40 | } 41 | 42 | private: 43 | std::unique_ptr buf_; 44 | unsigned bufsize_ = 0; 45 | unsigned writeindex_ = 0; 46 | }; 47 | -------------------------------------------------------------------------------- /src/gui/w_dft_spectrogram.cc: -------------------------------------------------------------------------------- 1 | #include "w_dft_spectrogram.h" 2 | #include "gui/fl_util.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | typedef std::complex cfloat; 16 | 17 | struct W_DftSpectrogram::Impl { 18 | W_DftSpectrogram *Q = nullptr; 19 | 20 | static constexpr float fsref = 44100; 21 | float fs = 44100; 22 | 23 | float frequency_of_x(float x) const; 24 | float frequency_of_r(float r) const; 25 | float r_of_frequency(float f) const; 26 | float x_of_frequency(float f) const; 27 | 28 | float nth_frequency_mark(unsigned i) const; 29 | int height_of_mark(unsigned i) const; 30 | 31 | enum { 32 | Domain_Linear, 33 | Domain_Logarithmic, 34 | }; 35 | int fdomain = Domain_Logarithmic; 36 | 37 | int mx = -1, my = -1; 38 | 39 | std::vector spec; 40 | unsigned channels = 0; 41 | 42 | Fl_Box *rulertop = nullptr; 43 | Fl_Box *rulerbtm = nullptr; 44 | Fl_Box *screen = nullptr; 45 | 46 | float dbmin = -140; 47 | float dbmax = +20; 48 | 49 | // data 50 | void draw_rulers(); 51 | void draw_back(); 52 | void draw_data(); 53 | void draw_pointer(int x, int y); 54 | 55 | // controls 56 | Fl_Group *grpctl = nullptr; 57 | Fl_Button *btnexpand = nullptr; 58 | int grpw = 0; 59 | int grph = 0; 60 | 61 | void create_controls(bool expanded); 62 | void reposition_controls(); 63 | void on_expand_controls(); 64 | void on_unexpand_controls(); 65 | 66 | void changed_fdomain(int val); 67 | }; 68 | 69 | // margins 70 | static constexpr int /*mw = 20,*/ mh = 20; 71 | 72 | W_DftSpectrogram::W_DftSpectrogram(int x, int y, int w, int h) 73 | : W_DftVisu(x, y, w, h), 74 | P(new Impl) { 75 | P->Q = this; 76 | 77 | int sx = x; 78 | int sy = y; 79 | int sw = w; 80 | int sh = h; 81 | 82 | bool b_rulertop = true; 83 | bool b_rulerbtm = true; 84 | 85 | if (b_rulertop) { 86 | P->rulertop = new Fl_Box(x, y, w, mh, ""); 87 | P->rulertop->box(FL_UP_BOX); 88 | sy += mh; 89 | sh -= std::min(sh, mh); 90 | } 91 | if (b_rulerbtm) { 92 | P->rulerbtm = new Fl_Box(x, y+h-mh, w, mh, ""); 93 | P->rulerbtm->box(FL_UP_BOX); 94 | sh -= std::min(sh, mh); 95 | } 96 | 97 | P->screen = new Fl_Box(sx, sy, sw, sh); 98 | this->resizable(P->screen); 99 | 100 | P->create_controls(false); 101 | P->reposition_controls(); 102 | 103 | this->end(); 104 | } 105 | 106 | W_DftSpectrogram::~W_DftSpectrogram() { 107 | } 108 | 109 | void W_DftSpectrogram::update_dft_data( 110 | const cfloat *spec[], unsigned n, float fs, unsigned nch) { 111 | P->spec.clear(); 112 | P->spec.reserve(nch * n); 113 | for (unsigned c = 0; c < nch; ++c) 114 | P->spec.insert(P->spec.end(), spec[c], spec[c] + n); 115 | P->fs = fs; 116 | P->channels = nch; 117 | } 118 | 119 | VisuDftResolution W_DftSpectrogram::desired_resolution() const { 120 | return (P->fdomain == Impl::Domain_Logarithmic) ? 121 | VisuDftResolution::High : VisuDftResolution::Medium; 122 | } 123 | 124 | void W_DftSpectrogram::reset_data() { 125 | P->spec.clear(); 126 | P->channels = 0; 127 | } 128 | 129 | void W_DftSpectrogram::draw() { 130 | // 131 | draw_child(*P->rulertop); 132 | draw_child(*P->rulerbtm); 133 | P->draw_rulers(); 134 | 135 | // 136 | int sx = P->screen->x(); 137 | int sy = P->screen->y(); 138 | int sw = P->screen->w(); 139 | int sh = P->screen->h(); 140 | if (sw > 0 && sh > 0) { 141 | fl_push_clip(sx, sy, sw, sh); 142 | P->draw_back(); 143 | P->draw_data(); 144 | // 145 | int mx = P->mx; 146 | int my = P->my; 147 | if (mx >= sx && mx < sx+sw && my >= sy && my < sy+sh) 148 | P->draw_pointer(mx, my); 149 | fl_pop_clip(); 150 | } 151 | 152 | // 153 | draw_child(*P->grpctl); 154 | } 155 | 156 | int W_DftSpectrogram::handle(int event) { 157 | if (event == FL_ENTER) { 158 | fl_cursor(FL_CURSOR_CROSS); 159 | return 1; 160 | } 161 | 162 | if (event == FL_LEAVE) { 163 | P->mx = -1; 164 | P->my = -1; 165 | fl_cursor(FL_CURSOR_DEFAULT); 166 | return 1; 167 | } 168 | 169 | if (event == FL_MOVE) { 170 | P->mx = Fl::event_x(); 171 | P->my = Fl::event_y(); 172 | return 1; 173 | } 174 | 175 | return Fl_Group::handle(event); 176 | } 177 | 178 | void W_DftSpectrogram::resize(int x, int y, int w, int h) { 179 | W_DftVisu::resize(x, y, w, h); 180 | P->reposition_controls(); 181 | } 182 | 183 | float W_DftSpectrogram::Impl::frequency_of_x(float x) const { 184 | return frequency_of_r((x - Q->x()) / Q->w()); 185 | } 186 | 187 | float W_DftSpectrogram::Impl::frequency_of_r(float r) const { 188 | float fmax = 0.5f * fsref; 189 | switch (fdomain) { 190 | default: 191 | case Domain_Linear: 192 | return r * fmax; 193 | case Domain_Logarithmic: 194 | float fmin = 10; 195 | float lfmin = std::log10(fmin); 196 | float lfmax = std::log10(fmax); 197 | float lf = lfmin + (lfmax - lfmin) * r; 198 | return std::pow(10.0f, lf); 199 | } 200 | } 201 | 202 | float W_DftSpectrogram::Impl::r_of_frequency(float f) const { 203 | float fmax = 0.5f * fsref; 204 | switch (fdomain) { 205 | default: 206 | case Domain_Linear: 207 | return f / fmax; 208 | case Domain_Logarithmic: 209 | float lf = std::log10(f); 210 | float fmin = 10; 211 | float lfmin = std::log10(fmin); 212 | float lfmax = std::log10(fmax); 213 | return (lf - lfmin) / (lfmax - lfmin); 214 | } 215 | } 216 | 217 | float W_DftSpectrogram::Impl::x_of_frequency(float f) const { 218 | return Q->x() + r_of_frequency(f) * Q->w(); 219 | } 220 | 221 | float W_DftSpectrogram::Impl::nth_frequency_mark(unsigned i) const 222 | { 223 | switch (fdomain) { 224 | default: 225 | case Domain_Linear: 226 | return 250 * i; 227 | case Domain_Logarithmic: { 228 | const float m[] = {1.0, 2.5, 5.0}; 229 | const unsigned n = 3; 230 | return m[i % n] * std::pow(10, (i + n) / n); 231 | } 232 | } 233 | } 234 | 235 | int W_DftSpectrogram::Impl::height_of_mark(unsigned i) const { 236 | switch (fdomain) { 237 | default: 238 | case Domain_Linear: { 239 | const int gradh[] = {16, 6, 8, 6}; 240 | return gradh[i%4]; 241 | } 242 | case Domain_Logarithmic: { 243 | const int gradh[] = {16, 6, 8}; 244 | return gradh[i%3]; 245 | } 246 | } 247 | } 248 | 249 | void W_DftSpectrogram::Impl::draw_rulers() { 250 | int x = Q->x(); 251 | int y = Q->y(); 252 | int w = Q->w(); 253 | int h = Q->h(); 254 | 255 | fl_color(0, 0, 0); 256 | 257 | float f_nyq = 0.5f * this->fsref; 258 | for (unsigned i = 0; ; ++i) { 259 | float f = nth_frequency_mark(i); 260 | unsigned g = (fdomain == Domain_Logarithmic) ? 1 : 4; 261 | if (f > f_nyq) 262 | break; 263 | 264 | int xf = x_of_frequency(f); 265 | 266 | int l = height_of_mark(i); 267 | if (this->rulertop) 268 | fl_line(xf, y+mh-l, xf, y+mh-1); 269 | if (this->rulerbtm) 270 | fl_line(xf, y+h-mh, xf, y+h-mh+l); 271 | 272 | char textbuf[32]; 273 | if (f < 1000) 274 | snprintf(textbuf, sizeof(textbuf), "%g", f); 275 | else 276 | snprintf(textbuf, sizeof(textbuf), "%gk", f/1000); 277 | textbuf[sizeof(textbuf)-1] = 0; 278 | 279 | fl_font(FL_COURIER, 10); 280 | if (i%g == 0) { 281 | if (this->rulertop) 282 | fl_draw(textbuf, xf+4, y+2, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 283 | if (this->rulerbtm) 284 | fl_draw(textbuf, xf+4, y+h-1, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_BOTTOM, nullptr, 0); 285 | } 286 | } 287 | } 288 | 289 | void W_DftSpectrogram::Impl::draw_back() { 290 | int sx = this->screen->x(); 291 | int sy = this->screen->y(); 292 | int sw = this->screen->w(); 293 | int sh = this->screen->h(); 294 | 295 | fl_color(0, 0, 0); 296 | fl_rectf(sx, sy, sw, sh); 297 | 298 | float f_nyq = this->fsref / 2; 299 | //float f_interval = 1000; 300 | 301 | for (unsigned i = 1; ; ++i) { 302 | // float f = f_interval * i; 303 | float f = nth_frequency_mark(i); 304 | unsigned g = (fdomain == Domain_Logarithmic) ? 1 : 4; 305 | if (f > f_nyq) 306 | break; 307 | 308 | if (i % g == 0) { 309 | int xf = x_of_frequency(f); 310 | fl_color(50, 50, 50); 311 | fl_line(xf, sy, xf, sy+sh-1); 312 | } 313 | } 314 | 315 | float dbmin = this->dbmin; 316 | float dbmax = this->dbmax; 317 | float ginterval = 20; 318 | 319 | for (unsigned i = 0; ; ++i) { 320 | float g = dbmin + ginterval * i; 321 | if (g > dbmax) 322 | break; 323 | float dv = (g - dbmin) / (dbmax - dbmin); 324 | int yg = sy + (1-dv) * (sh-1); 325 | fl_color(50, 50, 50); 326 | fl_line(sx, yg, sx+sw-1, yg); 327 | 328 | char textbuf[16]; 329 | snprintf(textbuf, sizeof(textbuf), "%g dB", g); 330 | textbuf[sizeof(textbuf)-1] = 0; 331 | 332 | fl_font(FL_COURIER, 10); 333 | fl_color(200, 200, 200); 334 | fl_draw(textbuf, sx+2, yg, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_BOTTOM, nullptr, 0); 335 | fl_draw(textbuf, sx+sw-2, yg, 0, 0, FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM, nullptr, 0); 336 | } 337 | } 338 | 339 | void W_DftSpectrogram::Impl::draw_data() { 340 | const unsigned channels = this->channels; 341 | if (channels == 0) 342 | return; 343 | 344 | const unsigned specn = this->spec.size() / channels; 345 | if (specn == 0) 346 | return; 347 | const unsigned dftsize = (specn - 1) * 2; 348 | 349 | const float fs = this->fs; 350 | const float fsref = this->fsref; 351 | 352 | const int sx = this->screen->x(); 353 | const int sy = this->screen->y(); 354 | const int sw = this->screen->w(); 355 | const int sh = this->screen->h(); 356 | 357 | for (unsigned c = channels; c-- > 0;) { 358 | const cfloat *spec = &this->spec[c * specn]; 359 | 360 | const unsigned hue = (170 + c * 130) % 360; 361 | color::hsv col_hsv{(float)hue, 75, 80}; 362 | color::rgb col_rgb(col_hsv); 363 | 364 | fl_color( 365 | color::get::red(col_rgb), 366 | color::get::green(col_rgb), 367 | color::get::blue(col_rgb)); 368 | 369 | int lasty {}; 370 | for (int i = 0; i < sw; ++i) { 371 | float f = frequency_of_x(sx + i); 372 | float binnum = f * dftsize / fs; 373 | unsigned binidx = (unsigned)binnum; 374 | float mu = binnum - binidx; 375 | 376 | constexpr unsigned itp = 4; 377 | float a[itp] = {}; 378 | 379 | for (unsigned j = 0; j < itp; ++j) { 380 | cfloat bin = 0.0f; 381 | if (binidx + j < specn) 382 | bin = spec[binidx + j]; 383 | a[j] = std::abs(bin); 384 | } 385 | 386 | float g = dbmin; 387 | bool gvalid = true; 388 | for (unsigned j = 0; gvalid && j < itp; ++j) 389 | gvalid = a[j] > 0; 390 | if (gvalid) { 391 | float y[itp]; 392 | for (unsigned j = 0; j < itp; ++j) 393 | y[j] = 20 * std::log10(a[j]); 394 | 395 | switch (itp) { 396 | case 1: 397 | g = y[0]; 398 | break; 399 | case 2: 400 | g = y[0] * (1 - mu) + y[1] * mu; 401 | break; 402 | case 4: { 403 | float c[4]; 404 | if (0) { // Cubic 405 | c[0] = y[3] - y[2] - y[0] + y[1]; 406 | c[1] = y[0] - y[1] - c[0]; 407 | c[2] = y[2] - y[0]; 408 | c[3] = y[1]; 409 | } 410 | else { // Hermite 411 | c[0] = -0.5f * y[0] + 1.5f * y[1] - 1.5f * y[2] + 0.5f * y[3]; 412 | c[1] = y[0] - 2.5f * y[1] + 2 * y[2] - 0.5f * y[3]; 413 | c[2] = -0.5f * y[0] + 0.5f * y[2]; 414 | c[3] = y[1]; 415 | } 416 | g = c[0] * (mu * mu * mu) + c[1] * (mu * mu) + c[2] * mu + c[3]; 417 | break; 418 | } 419 | } 420 | } 421 | 422 | float dv = (g - dbmin) / (dbmax - dbmin); 423 | int newy = sy+(1-dv)*(sh-1); 424 | if (i > 0) 425 | fl_line(sx+i-1, lasty, sx+i, newy); 426 | lasty = newy; 427 | } 428 | } 429 | } 430 | 431 | void W_DftSpectrogram::Impl::draw_pointer(int mx, int my) { 432 | int sx = this->screen->x(); 433 | int sy = this->screen->y(); 434 | int sw = this->screen->w(); 435 | int sh = this->screen->h(); 436 | 437 | float f = frequency_of_x(mx); 438 | 439 | float dbmin = this->dbmin; 440 | float dbmax = this->dbmax; 441 | float dv = 1-float(my-sy)/(sh-1); 442 | float g = dbmin + dv * (dbmax - dbmin); 443 | 444 | fl_color(150, 150, 150); 445 | fl_line(mx, sy, mx, sy+sh-1); 446 | fl_line(sx, my, sx+sw-1, my); 447 | 448 | char textbuf[64]; 449 | snprintf(textbuf, sizeof(textbuf), "%.2f Hz %.2f dB", f, g); 450 | textbuf[sizeof(textbuf)-1] = 0; 451 | 452 | fl_font(FL_COURIER, 12); 453 | int tw = 8+std::ceil(fl_width("00000.00 Hz -000.00 dB")); 454 | int th = 16; 455 | int tx = sx+sw-1-tw-4; 456 | int ty = sy+4; 457 | int tal = FL_ALIGN_LEFT|FL_ALIGN_CENTER; 458 | fl_color(50, 50, 50); 459 | fl_rectf(tx, ty, tw, th); 460 | fl_color(200, 200, 200); 461 | fl_rect(tx, ty, tw, th); 462 | fl_draw(textbuf, tx+4, ty, tw, th, tal, nullptr, 0); 463 | } 464 | 465 | void W_DftSpectrogram::Impl::create_controls(bool expanded) { 466 | int mh = 60; 467 | int bh = 20; 468 | 469 | bool btnfocus = this->btnexpand == Fl::focus(); 470 | 471 | delete this->grpctl; 472 | this->grpctl = nullptr; 473 | this->btnexpand = nullptr; 474 | 475 | if (!expanded) { 476 | int grpw = this->grpw = bh; 477 | int grph = this->grph = bh; 478 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, grpw, grph); 479 | grpctl->begin(); 480 | grpctl->resizable(nullptr); 481 | Fl_Button *btn = this->btnexpand = new Fl_Button(0, 0, grpw, grph, "+"); 482 | if (btnfocus) btn->take_focus(); 483 | btn->labelfont(FL_COURIER|FL_BOLD); 484 | btn->labelsize(16); 485 | TRIGGER_CALLBACK(btn, this, on_expand_controls); 486 | grpctl->end(); 487 | return; 488 | } 489 | 490 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, 1, mh); 491 | grpctl->begin(); 492 | grpctl->resizable(nullptr); 493 | 494 | int interx = 8; 495 | int curx = 0; 496 | 497 | Fl_Group *box {}; 498 | Fl_Button *btn {}; 499 | Fl_Choice *choice {}; 500 | 501 | int knobw = 42; 502 | int knobh = 42; 503 | 504 | box = new Fl_Group(curx, 0, 1, mh); 505 | box->begin(); 506 | box->resizable(nullptr); 507 | box->color(fl_rgb_color(191, 218, 255)); 508 | box->box(FL_ENGRAVED_BOX); 509 | curx += interx; 510 | 511 | choice = new Fl_Choice(curx, 15, 120, knobh, "F. domain"); 512 | choice->labelsize(10); 513 | choice->align(FL_ALIGN_TOP); 514 | choice->textfont(FL_COURIER); 515 | choice->textsize(12); 516 | choice->add("Linear"); 517 | choice->add("Logarithmic"); 518 | choice->value(this->fdomain); 519 | VALUE_CALLBACK(choice, this, changed_fdomain); 520 | curx += choice->w() + interx; 521 | 522 | box->size(curx - box->x(), mh); 523 | box->end(); 524 | 525 | box = new Fl_Group(curx, 0, mh, mh); 526 | box->begin(); 527 | // box->color(fl_rgb_color(191, 218, 255)); 528 | box->box(FL_ENGRAVED_BOX); 529 | btn = this->btnexpand = new Fl_Button(curx, mh-bh, bh, bh, "-"); 530 | if (btnfocus) btn->take_focus(); 531 | btn->labelfont(FL_COURIER|FL_BOLD); 532 | btn->labelsize(16); 533 | TRIGGER_CALLBACK(btn, this, on_unexpand_controls); 534 | curx += btn->w(); 535 | box->end(); 536 | 537 | int grpw = this->grpw = curx; 538 | int grph = this->grph = mh; 539 | grpctl->size(grpw, grph); 540 | 541 | grpctl->end(); 542 | } 543 | 544 | void W_DftSpectrogram::Impl::reposition_controls() { 545 | int x = Q->x(); 546 | int y = Q->y(); 547 | int w = Q->w(); 548 | int h = Q->h(); 549 | Fl_Group *grpctl = this->grpctl; 550 | grpctl->resize(x+w-this->grpw, y+h-this->grph, this->grpw, this->grph); 551 | 552 | int wrbtm = this->rulertop->w() - this->grpw; 553 | if (this->rulerbtm->w() != wrbtm) { 554 | this->rulerbtm->size(wrbtm, this->rulerbtm->h()); 555 | Q->redraw(); 556 | } 557 | } 558 | 559 | void W_DftSpectrogram::Impl::on_expand_controls() { 560 | Q->begin(); 561 | this->create_controls(true); 562 | this->reposition_controls(); 563 | Q->end(); 564 | } 565 | 566 | void W_DftSpectrogram::Impl::on_unexpand_controls() { 567 | Q->begin(); 568 | this->create_controls(false); 569 | this->reposition_controls(); 570 | Q->end(); 571 | } 572 | 573 | void W_DftSpectrogram::Impl::changed_fdomain(int val) { 574 | this->fdomain = val; 575 | Q->redraw(); 576 | } 577 | -------------------------------------------------------------------------------- /src/gui/w_dft_spectrogram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_dft_visu.h" 3 | #include 4 | 5 | class W_DftSpectrogram : public W_DftVisu { 6 | public: 7 | W_DftSpectrogram(int x, int y, int w, int h); 8 | ~W_DftSpectrogram(); 9 | 10 | void update_dft_data( 11 | const std::complex *spec[], unsigned n, float fs, unsigned nch) override; 12 | VisuDftResolution desired_resolution() const override; 13 | void reset_data() override; 14 | 15 | void draw() override; 16 | int handle(int event) override; 17 | void resize(int x, int y, int w, int h) override; 18 | 19 | private: 20 | struct Impl; 21 | std::unique_ptr P; 22 | }; 23 | -------------------------------------------------------------------------------- /src/gui/w_dft_transfer.cc: -------------------------------------------------------------------------------- 1 | #include "w_dft_transfer.h" 2 | #include "gui/fl_util.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | typedef std::complex cfloat; 16 | 17 | struct W_DftTransfer::Impl { 18 | W_DftTransfer *Q = nullptr; 19 | 20 | static constexpr float fsref = 44100; 21 | float fs = 44100; 22 | 23 | float frequency_of_x(float x) const; 24 | float frequency_of_r(float r) const; 25 | float r_of_frequency(float f) const; 26 | float x_of_frequency(float f) const; 27 | 28 | float nth_frequency_mark(unsigned i) const; 29 | int height_of_mark(unsigned i) const; 30 | 31 | enum { 32 | Domain_Linear, 33 | Domain_Logarithmic, 34 | }; 35 | int fdomain = Domain_Logarithmic; 36 | 37 | int mx = -1, my = -1; 38 | 39 | std::vector spec; 40 | unsigned channels = 0; 41 | 42 | Fl_Box *rulertop = nullptr; 43 | Fl_Box *rulerbtm = nullptr; 44 | Fl_Box *screen = nullptr; 45 | 46 | float dbmin = -80; 47 | float dbmax = +20; 48 | 49 | // data 50 | void draw_rulers(); 51 | void draw_back(); 52 | void draw_data(); 53 | void draw_pointer(int x, int y); 54 | 55 | // controls 56 | Fl_Group *grpctl = nullptr; 57 | Fl_Button *btnexpand = nullptr; 58 | int grpw = 0; 59 | int grph = 0; 60 | 61 | void create_controls(bool expanded); 62 | void reposition_controls(); 63 | void on_expand_controls(); 64 | void on_unexpand_controls(); 65 | 66 | void changed_fdomain(int val); 67 | }; 68 | 69 | // margins 70 | static constexpr int /*mw = 20,*/ mh = 20; 71 | 72 | W_DftTransfer::W_DftTransfer(int x, int y, int w, int h) 73 | : W_DftVisu(x, y, w, h), 74 | P(new Impl) { 75 | P->Q = this; 76 | 77 | int sx = x; 78 | int sy = y; 79 | int sw = w; 80 | int sh = h; 81 | 82 | bool b_rulertop = true; 83 | bool b_rulerbtm = true; 84 | 85 | if (b_rulertop) { 86 | P->rulertop = new Fl_Box(x, y, w, mh, ""); 87 | P->rulertop->box(FL_UP_BOX); 88 | sy += mh; 89 | sh -= std::min(sh, mh); 90 | } 91 | if (b_rulerbtm) { 92 | P->rulerbtm = new Fl_Box(x, y+h-mh, w, mh, ""); 93 | P->rulerbtm->box(FL_UP_BOX); 94 | sh -= std::min(sh, mh); 95 | } 96 | 97 | P->screen = new Fl_Box(sx, sy, sw, sh); 98 | this->resizable(P->screen); 99 | 100 | P->create_controls(false); 101 | P->reposition_controls(); 102 | 103 | this->end(); 104 | } 105 | 106 | W_DftTransfer::~W_DftTransfer() { 107 | } 108 | 109 | void W_DftTransfer::update_dft_data( 110 | const cfloat *spec[], unsigned n, float fs, unsigned nch) { 111 | P->spec.clear(); 112 | P->spec.reserve(nch * n); 113 | for (unsigned c = 0; c < nch; ++c) 114 | P->spec.insert(P->spec.end(), spec[c], spec[c] + n); 115 | P->fs = fs; 116 | P->channels = nch; 117 | } 118 | 119 | VisuDftResolution W_DftTransfer::desired_resolution() const { 120 | return (P->fdomain == Impl::Domain_Logarithmic) ? 121 | VisuDftResolution::High : VisuDftResolution::Medium; 122 | } 123 | 124 | void W_DftTransfer::reset_data() { 125 | P->spec.clear(); 126 | P->channels = 0; 127 | } 128 | 129 | void W_DftTransfer::draw() { 130 | // 131 | draw_child(*P->rulertop); 132 | draw_child(*P->rulerbtm); 133 | P->draw_rulers(); 134 | 135 | // 136 | int sx = P->screen->x(); 137 | int sy = P->screen->y(); 138 | int sw = P->screen->w(); 139 | int sh = P->screen->h(); 140 | if (sw > 0 && sh > 0) { 141 | fl_push_clip(sx, sy, sw, sh); 142 | P->draw_back(); 143 | P->draw_data(); 144 | // 145 | int mx = P->mx; 146 | int my = P->my; 147 | if (mx >= sx && mx < sx+sw && my >= sy && my < sy+sh) 148 | P->draw_pointer(mx, my); 149 | fl_pop_clip(); 150 | } 151 | 152 | // 153 | draw_child(*P->grpctl); 154 | } 155 | 156 | int W_DftTransfer::handle(int event) { 157 | if (event == FL_ENTER) { 158 | fl_cursor(FL_CURSOR_CROSS); 159 | return 1; 160 | } 161 | 162 | if (event == FL_LEAVE) { 163 | P->mx = -1; 164 | P->my = -1; 165 | fl_cursor(FL_CURSOR_DEFAULT); 166 | return 1; 167 | } 168 | 169 | if (event == FL_MOVE) { 170 | P->mx = Fl::event_x(); 171 | P->my = Fl::event_y(); 172 | return 1; 173 | } 174 | 175 | return Fl_Group::handle(event); 176 | } 177 | 178 | void W_DftTransfer::resize(int x, int y, int w, int h) { 179 | W_DftVisu::resize(x, y, w, h); 180 | P->reposition_controls(); 181 | } 182 | 183 | float W_DftTransfer::Impl::frequency_of_x(float x) const { 184 | return frequency_of_r((x - Q->x()) / Q->w()); 185 | } 186 | 187 | float W_DftTransfer::Impl::frequency_of_r(float r) const { 188 | float fmax = 0.5f * fsref; 189 | switch (fdomain) { 190 | default: 191 | case Domain_Linear: 192 | return r * fmax; 193 | case Domain_Logarithmic: 194 | float fmin = 10; 195 | float lfmin = std::log10(fmin); 196 | float lfmax = std::log10(fmax); 197 | float lf = lfmin + (lfmax - lfmin) * r; 198 | return std::pow(10.0f, lf); 199 | } 200 | } 201 | 202 | float W_DftTransfer::Impl::r_of_frequency(float f) const { 203 | float fmax = 0.5f * fsref; 204 | switch (fdomain) { 205 | default: 206 | case Domain_Linear: 207 | return f / fmax; 208 | case Domain_Logarithmic: 209 | float lf = std::log10(f); 210 | float fmin = 10; 211 | float lfmin = std::log10(fmin); 212 | float lfmax = std::log10(fmax); 213 | return (lf - lfmin) / (lfmax - lfmin); 214 | } 215 | } 216 | 217 | float W_DftTransfer::Impl::x_of_frequency(float f) const { 218 | return Q->x() + r_of_frequency(f) * Q->w(); 219 | } 220 | 221 | float W_DftTransfer::Impl::nth_frequency_mark(unsigned i) const 222 | { 223 | switch (fdomain) { 224 | default: 225 | case Domain_Linear: 226 | return 250 * i; 227 | case Domain_Logarithmic: { 228 | const float m[] = {1.0, 2.5, 5.0}; 229 | const unsigned n = 3; 230 | return m[i % n] * std::pow(10, (i + n) / n); 231 | } 232 | } 233 | } 234 | 235 | int W_DftTransfer::Impl::height_of_mark(unsigned i) const { 236 | switch (fdomain) { 237 | default: 238 | case Domain_Linear: { 239 | const int gradh[] = {16, 6, 8, 6}; 240 | return gradh[i%4]; 241 | } 242 | case Domain_Logarithmic: { 243 | const int gradh[] = {16, 6, 8}; 244 | return gradh[i%3]; 245 | } 246 | } 247 | } 248 | 249 | void W_DftTransfer::Impl::draw_rulers() { 250 | int x = Q->x(); 251 | int y = Q->y(); 252 | int w = Q->w(); 253 | int h = Q->h(); 254 | 255 | fl_color(0, 0, 0); 256 | 257 | float f_nyq = 0.5f * this->fsref; 258 | for (unsigned i = 0; ; ++i) { 259 | float f = nth_frequency_mark(i); 260 | unsigned g = (fdomain == Domain_Logarithmic) ? 1 : 4; 261 | if (f > f_nyq) 262 | break; 263 | 264 | int xf = x_of_frequency(f); 265 | 266 | int l = height_of_mark(i); 267 | if (this->rulertop) 268 | fl_line(xf, y+mh-l, xf, y+mh-1); 269 | if (this->rulerbtm) 270 | fl_line(xf, y+h-mh, xf, y+h-mh+l); 271 | 272 | char textbuf[32]; 273 | if (f < 1000) 274 | snprintf(textbuf, sizeof(textbuf), "%g", f); 275 | else 276 | snprintf(textbuf, sizeof(textbuf), "%gk", f/1000); 277 | textbuf[sizeof(textbuf)-1] = 0; 278 | 279 | fl_font(FL_COURIER, 10); 280 | if (i%g == 0) { 281 | if (this->rulertop) 282 | fl_draw(textbuf, xf+4, y+2, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 283 | if (this->rulerbtm) 284 | fl_draw(textbuf, xf+4, y+h-1, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_BOTTOM, nullptr, 0); 285 | } 286 | } 287 | } 288 | 289 | void W_DftTransfer::Impl::draw_back() { 290 | int sx = this->screen->x(); 291 | int sy = this->screen->y(); 292 | int sw = this->screen->w(); 293 | int sh = this->screen->h(); 294 | 295 | fl_color(0, 0, 0); 296 | fl_rectf(sx, sy, sw, sh); 297 | 298 | float f_nyq = this->fsref / 2; 299 | //float f_interval = 1000; 300 | 301 | for (unsigned i = 1; ; ++i) { 302 | // float f = f_interval * i; 303 | float f = nth_frequency_mark(i); 304 | unsigned g = (fdomain == Domain_Logarithmic) ? 1 : 4; 305 | if (f > f_nyq) 306 | break; 307 | 308 | if (i % g == 0) { 309 | int xf = x_of_frequency(f); 310 | fl_color(50, 50, 50); 311 | fl_line(xf, sy, xf, sy+sh-1); 312 | } 313 | } 314 | 315 | float dbmin = this->dbmin; 316 | float dbmax = this->dbmax; 317 | float ginterval = 20; 318 | 319 | for (unsigned i = 0; ; ++i) { 320 | float g = dbmin + ginterval * i; 321 | if (g > dbmax) 322 | break; 323 | float dv = (g - dbmin) / (dbmax - dbmin); 324 | int yg = sy + (1-dv) * (sh-1); 325 | fl_color(50, 50, 50); 326 | fl_line(sx, yg, sx+sw-1, yg); 327 | 328 | char textbuf[16]; 329 | snprintf(textbuf, sizeof(textbuf), "%g dB", g); 330 | textbuf[sizeof(textbuf)-1] = 0; 331 | 332 | fl_font(FL_COURIER, 10); 333 | fl_color(200, 200, 200); 334 | fl_draw(textbuf, sx+2, yg, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_BOTTOM, nullptr, 0); 335 | fl_draw(textbuf, sx+sw-2, yg, 0, 0, FL_ALIGN_RIGHT|FL_ALIGN_BOTTOM, nullptr, 0); 336 | } 337 | } 338 | 339 | void W_DftTransfer::Impl::draw_data() { 340 | const unsigned channels = this->channels; 341 | if (channels == 0) 342 | return; 343 | 344 | const unsigned specn = this->spec.size() / channels; 345 | if (specn == 0) 346 | return; 347 | const unsigned dftsize = (specn - 1) * 2; 348 | 349 | const float fs = this->fs; 350 | const float fsref = this->fsref; 351 | 352 | const int sx = this->screen->x(); 353 | const int sy = this->screen->y(); 354 | const int sw = this->screen->w(); 355 | const int sh = this->screen->h(); 356 | 357 | for (unsigned c = channels; c-- > 1;) { 358 | const cfloat *ref = &this->spec[0]; 359 | const cfloat *spec = &this->spec[c * specn]; 360 | 361 | const unsigned hue = (170 + (c - 1) * 130) % 360; 362 | color::hsv col_hsv{(float)hue, 75, 80}; 363 | color::rgb col_rgb(col_hsv); 364 | 365 | fl_color( 366 | color::get::red(col_rgb), 367 | color::get::green(col_rgb), 368 | color::get::blue(col_rgb)); 369 | 370 | int lasty {}; 371 | for (int i = 0; i < sw; ++i) { 372 | float f = frequency_of_x(sx + i); 373 | float binnum = f * dftsize / fs; 374 | unsigned binidx = (unsigned)binnum; 375 | float mu = binnum - binidx; 376 | 377 | constexpr unsigned itp = 4; 378 | float a[itp] = {}; 379 | 380 | for (unsigned j = 0; j < itp; ++j) { 381 | cfloat bin = 0.0f; 382 | if (binidx + j < specn) 383 | bin = spec[binidx + j] / ref[binidx + j]; 384 | a[j] = std::abs(bin); 385 | } 386 | 387 | float g = dbmin; 388 | bool gvalid = true; 389 | for (unsigned j = 0; gvalid && j < itp; ++j) 390 | gvalid = a[j] > 0; 391 | if (gvalid) { 392 | float y[itp]; 393 | for (unsigned j = 0; j < itp; ++j) 394 | y[j] = 20 * std::log10(a[j]); 395 | 396 | switch (itp) { 397 | case 1: 398 | g = y[0]; 399 | break; 400 | case 2: 401 | g = y[0] * (1 - mu) + y[1] * mu; 402 | break; 403 | case 4: { 404 | float c[4]; 405 | if (0) { // Cubic 406 | c[0] = y[3] - y[2] - y[0] + y[1]; 407 | c[1] = y[0] - y[1] - c[0]; 408 | c[2] = y[2] - y[0]; 409 | c[3] = y[1]; 410 | } 411 | else { // Hermite 412 | c[0] = -0.5f * y[0] + 1.5f * y[1] - 1.5f * y[2] + 0.5f * y[3]; 413 | c[1] = y[0] - 2.5f * y[1] + 2 * y[2] - 0.5f * y[3]; 414 | c[2] = -0.5f * y[0] + 0.5f * y[2]; 415 | c[3] = y[1]; 416 | } 417 | g = c[0] * (mu * mu * mu) + c[1] * (mu * mu) + c[2] * mu + c[3]; 418 | break; 419 | } 420 | } 421 | } 422 | 423 | float dv = (g - dbmin) / (dbmax - dbmin); 424 | int newy = sy+(1-dv)*(sh-1); 425 | if (i > 0) 426 | fl_line(sx+i-1, lasty, sx+i, newy); 427 | lasty = newy; 428 | } 429 | } 430 | } 431 | 432 | void W_DftTransfer::Impl::draw_pointer(int mx, int my) { 433 | int sx = this->screen->x(); 434 | int sy = this->screen->y(); 435 | int sw = this->screen->w(); 436 | int sh = this->screen->h(); 437 | 438 | float f = frequency_of_x(mx); 439 | 440 | float dbmin = this->dbmin; 441 | float dbmax = this->dbmax; 442 | float dv = 1-float(my-sy)/(sh-1); 443 | float g = dbmin + dv * (dbmax - dbmin); 444 | 445 | fl_color(150, 150, 150); 446 | fl_line(mx, sy, mx, sy+sh-1); 447 | fl_line(sx, my, sx+sw-1, my); 448 | 449 | char textbuf[64]; 450 | snprintf(textbuf, sizeof(textbuf), "%.2f Hz %.2f dB", f, g); 451 | textbuf[sizeof(textbuf)-1] = 0; 452 | 453 | fl_font(FL_COURIER, 12); 454 | int tw = 8+std::ceil(fl_width("00000.00 Hz -000.00 dB")); 455 | int th = 16; 456 | int tx = sx+sw-1-tw-4; 457 | int ty = sy+4; 458 | int tal = FL_ALIGN_LEFT|FL_ALIGN_CENTER; 459 | fl_color(50, 50, 50); 460 | fl_rectf(tx, ty, tw, th); 461 | fl_color(200, 200, 200); 462 | fl_rect(tx, ty, tw, th); 463 | fl_draw(textbuf, tx+4, ty, tw, th, tal, nullptr, 0); 464 | } 465 | 466 | void W_DftTransfer::Impl::create_controls(bool expanded) { 467 | int mh = 60; 468 | int bh = 20; 469 | 470 | bool btnfocus = this->btnexpand == Fl::focus(); 471 | 472 | delete this->grpctl; 473 | this->grpctl = nullptr; 474 | this->btnexpand = nullptr; 475 | 476 | if (!expanded) { 477 | int grpw = this->grpw = bh; 478 | int grph = this->grph = bh; 479 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, grpw, grph); 480 | grpctl->begin(); 481 | grpctl->resizable(nullptr); 482 | Fl_Button *btn = this->btnexpand = new Fl_Button(0, 0, grpw, grph, "+"); 483 | if (btnfocus) btn->take_focus(); 484 | btn->labelfont(FL_COURIER|FL_BOLD); 485 | btn->labelsize(16); 486 | TRIGGER_CALLBACK(btn, this, on_expand_controls); 487 | grpctl->end(); 488 | return; 489 | } 490 | 491 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, 1, mh); 492 | grpctl->begin(); 493 | grpctl->resizable(nullptr); 494 | 495 | int interx = 8; 496 | int curx = 0; 497 | 498 | Fl_Group *box {}; 499 | Fl_Button *btn {}; 500 | Fl_Choice *choice {}; 501 | 502 | int knobw = 42; 503 | int knobh = 42; 504 | 505 | box = new Fl_Group(curx, 0, 1, mh); 506 | box->begin(); 507 | box->resizable(nullptr); 508 | box->color(fl_rgb_color(191, 218, 255)); 509 | box->box(FL_ENGRAVED_BOX); 510 | curx += interx; 511 | 512 | choice = new Fl_Choice(curx, 15, 120, knobh, "F. domain"); 513 | choice->labelsize(10); 514 | choice->align(FL_ALIGN_TOP); 515 | choice->textfont(FL_COURIER); 516 | choice->textsize(12); 517 | choice->add("Linear"); 518 | choice->add("Logarithmic"); 519 | choice->value(this->fdomain); 520 | VALUE_CALLBACK(choice, this, changed_fdomain); 521 | curx += choice->w() + interx; 522 | 523 | box->size(curx - box->x(), mh); 524 | box->end(); 525 | 526 | box = new Fl_Group(curx, 0, mh, mh); 527 | box->begin(); 528 | // box->color(fl_rgb_color(191, 218, 255)); 529 | box->box(FL_ENGRAVED_BOX); 530 | btn = this->btnexpand = new Fl_Button(curx, mh-bh, bh, bh, "-"); 531 | if (btnfocus) btn->take_focus(); 532 | btn->labelfont(FL_COURIER|FL_BOLD); 533 | btn->labelsize(16); 534 | TRIGGER_CALLBACK(btn, this, on_unexpand_controls); 535 | curx += btn->w(); 536 | box->end(); 537 | 538 | int grpw = this->grpw = curx; 539 | int grph = this->grph = mh; 540 | grpctl->size(grpw, grph); 541 | 542 | grpctl->end(); 543 | } 544 | 545 | void W_DftTransfer::Impl::reposition_controls() { 546 | int x = Q->x(); 547 | int y = Q->y(); 548 | int w = Q->w(); 549 | int h = Q->h(); 550 | Fl_Group *grpctl = this->grpctl; 551 | grpctl->resize(x+w-this->grpw, y+h-this->grph, this->grpw, this->grph); 552 | 553 | int wrbtm = this->rulertop->w() - this->grpw; 554 | if (this->rulerbtm->w() != wrbtm) { 555 | this->rulerbtm->size(wrbtm, this->rulerbtm->h()); 556 | Q->redraw(); 557 | } 558 | } 559 | 560 | void W_DftTransfer::Impl::on_expand_controls() { 561 | Q->begin(); 562 | this->create_controls(true); 563 | this->reposition_controls(); 564 | Q->end(); 565 | } 566 | 567 | void W_DftTransfer::Impl::on_unexpand_controls() { 568 | Q->begin(); 569 | this->create_controls(false); 570 | this->reposition_controls(); 571 | Q->end(); 572 | } 573 | 574 | void W_DftTransfer::Impl::changed_fdomain(int val) { 575 | this->fdomain = val; 576 | Q->redraw(); 577 | } 578 | -------------------------------------------------------------------------------- /src/gui/w_dft_transfer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_dft_visu.h" 3 | #include 4 | 5 | class W_DftTransfer : public W_DftVisu { 6 | public: 7 | W_DftTransfer(int x, int y, int w, int h); 8 | ~W_DftTransfer(); 9 | 10 | void update_dft_data( 11 | const std::complex *spec[], unsigned n, float fs, unsigned nch) override; 12 | VisuDftResolution desired_resolution() const override; 13 | void reset_data() override; 14 | 15 | void draw() override; 16 | int handle(int event) override; 17 | void resize(int x, int y, int w, int h) override; 18 | 19 | private: 20 | struct Impl; 21 | std::unique_ptr P; 22 | }; 23 | -------------------------------------------------------------------------------- /src/gui/w_dft_visu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_visu.h" 3 | #include "../visu~-common.h" 4 | #include 5 | 6 | class W_DftVisu : public W_Visu { 7 | public: 8 | W_DftVisu(int x, int y, int w, int h) 9 | : W_Visu(x, y, w, h) {} 10 | virtual ~W_DftVisu() {} 11 | 12 | virtual void update_dft_data( 13 | const std::complex *spec[], unsigned n, float fs, unsigned nch) = 0; 14 | virtual VisuDftResolution desired_resolution() const = 0; 15 | }; 16 | -------------------------------------------------------------------------------- /src/gui/w_dft_waterfall.cc: -------------------------------------------------------------------------------- 1 | #include "w_dft_waterfall.h" 2 | #include "gui/fl_util.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | typedef std::complex cfloat; 16 | 17 | struct W_DftWaterfall::Impl { 18 | W_DftWaterfall *Q = nullptr; 19 | 20 | float fsref = 44100; 21 | 22 | float frequency_of_x(float x) const; 23 | float frequency_of_r(float r) const; 24 | float r_of_frequency(float f) const; 25 | float x_of_frequency(float f) const; 26 | 27 | float nth_frequency_mark(unsigned i) const; 28 | int height_of_mark(unsigned i) const; 29 | 30 | enum { 31 | Domain_Linear, 32 | Domain_Logarithmic, 33 | }; 34 | int fdomain = Domain_Linear; 35 | 36 | int mx = -1, my = -1; 37 | 38 | static constexpr unsigned pixsize = 4; 39 | unsigned stride {}; 40 | 41 | Fl_Box *rulertop = nullptr; 42 | Fl_Box *rulerbtm = nullptr; 43 | Fl_Box *screen = nullptr; 44 | 45 | std::unique_ptr imagebuf; 46 | unsigned imagew = 0; 47 | unsigned imageh = 0; 48 | unsigned imagerow = 0; 49 | 50 | float dbmin = -200; 51 | float dbmax = +0; 52 | 53 | // data 54 | void draw_rulers(); 55 | void draw_data(); 56 | void draw_pointer(int x, int y); 57 | 58 | uint8_t *row(unsigned i); 59 | uint8_t *current_top_row(); 60 | uint8_t *current_btm_row(bool mirror = false); 61 | void advance_row(unsigned n); 62 | 63 | bool imagebuf_valid() const; 64 | void adapt_imagebuf(); 65 | 66 | // controls 67 | Fl_Group *grpctl = nullptr; 68 | Fl_Button *btnexpand = nullptr; 69 | int grpw = 0; 70 | int grph = 0; 71 | 72 | void create_controls(bool expanded); 73 | void reposition_controls(); 74 | void on_expand_controls(); 75 | void on_unexpand_controls(); 76 | 77 | void changed_fdomain(int val); 78 | }; 79 | 80 | // margins 81 | static constexpr int /*mw = 20,*/ mh = 20; 82 | 83 | W_DftWaterfall::W_DftWaterfall(int x, int y, int w, int h) 84 | : W_DftVisu(x, y, w, h), 85 | P(new Impl) { 86 | P->Q = this; 87 | 88 | int sx = x; 89 | int sy = y; 90 | int sw = w; 91 | int sh = h; 92 | 93 | bool b_rulertop = true; 94 | bool b_rulerbtm = true; 95 | 96 | if (b_rulertop) { 97 | P->rulertop = new Fl_Box(x, y, w, mh, ""); 98 | P->rulertop->box(FL_UP_BOX); 99 | sy += mh; 100 | sh -= std::min(sh, mh); 101 | } 102 | if (b_rulerbtm) { 103 | P->rulerbtm = new Fl_Box(x, y+h-mh, w, mh, ""); 104 | P->rulerbtm->box(FL_UP_BOX); 105 | sh -= std::min(sh, mh); 106 | } 107 | 108 | P->screen = new Fl_Box(sx, sy, sw, sh); 109 | this->resizable(P->screen); 110 | 111 | P->create_controls(false); 112 | P->reposition_controls(); 113 | 114 | this->end(); 115 | 116 | P->adapt_imagebuf(); 117 | } 118 | 119 | W_DftWaterfall::~W_DftWaterfall() { 120 | } 121 | 122 | void W_DftWaterfall::update_dft_data( 123 | const cfloat *allspec[], unsigned n, float fs, unsigned nch) { 124 | if (n == 0 || nch == 0) 125 | return; 126 | 127 | const unsigned dftsize = (n - 1) * 2; 128 | 129 | const unsigned sx = P->screen->x(); 130 | const unsigned sw = P->screen->w(); 131 | const unsigned sh = P->screen->h(); 132 | if (sw == 0 || sh == 0) 133 | return; 134 | 135 | if (!P->imagebuf_valid()) 136 | return; 137 | 138 | const float dbmin = P->dbmin; 139 | const float dbmax = P->dbmax; 140 | const float fsref = P->fsref; 141 | 142 | const unsigned pixsize = P->pixsize; 143 | 144 | P->advance_row(1); 145 | uint8_t *row = P->current_btm_row(); 146 | 147 | for (unsigned i = 0; i < sw; ++i) { 148 | unsigned red = 0; 149 | unsigned green = 0; 150 | unsigned blue = 0; 151 | 152 | for (unsigned c = 0; c < nch; ++c) { 153 | const cfloat *spec = allspec[c]; 154 | 155 | float f = P->frequency_of_x(sx + i); 156 | float binnum = f * dftsize / fs; 157 | unsigned binidx = (unsigned)binnum; 158 | float mu = binnum - binidx; 159 | 160 | constexpr unsigned itp = 2; 161 | float a[itp] = {}; 162 | 163 | for (unsigned j = 0; j < itp; ++j) { 164 | cfloat bin = 0.0f; 165 | if (binidx + j < n) 166 | bin = spec[binidx + j]; 167 | a[j] = std::abs(bin); 168 | } 169 | 170 | float g = dbmin; 171 | bool gvalid = true; 172 | for (unsigned j = 0; gvalid && j < itp; ++j) 173 | gvalid = a[j] > 0; 174 | if (gvalid) { 175 | float y[itp]; 176 | for (unsigned j = 0; j < itp; ++j) 177 | y[j] = 20 * std::log10(a[j]); 178 | 179 | switch (itp) { 180 | case 1: 181 | g = y[0]; 182 | break; 183 | case 2: 184 | g = y[0] * (1 - mu) + y[1] * mu; 185 | break; 186 | case 4: { 187 | float c[4]; 188 | if (0) { // Cubic 189 | c[0] = y[3] - y[2] - y[0] + y[1]; 190 | c[1] = y[0] - y[1] - c[0]; 191 | c[2] = y[2] - y[0]; 192 | c[3] = y[1]; 193 | } 194 | else { // Hermite 195 | c[0] = -0.5f * y[0] + 1.5f * y[1] - 1.5f * y[2] + 0.5f * y[3]; 196 | c[1] = y[0] - 2.5f * y[1] + 2 * y[2] - 0.5f * y[3]; 197 | c[2] = -0.5f * y[0] + 0.5f * y[2]; 198 | c[3] = y[1]; 199 | } 200 | g = c[0] * (mu * mu * mu) + c[1] * (mu * mu) + c[2] * mu + c[3]; 201 | break; 202 | } 203 | } 204 | } 205 | 206 | // value display 207 | float dv = (g - dbmin) / (dbmax - dbmin); 208 | dv = std::min(dv, 1.0f); 209 | dv = std::max(dv, 0.0f); 210 | 211 | const float logt = 50; 212 | float logdv = (std::pow(logt, dv) - 1) / (logt - 1); 213 | 214 | const unsigned hue = (170 + c * 130) % 360; 215 | color::hsl hcol{(float)hue, 50, logdv * 100}; 216 | color::rgb col(hcol); 217 | 218 | red += color::get::red(col); 219 | green += color::get::green(col); 220 | blue += color::get::blue(col); 221 | } 222 | 223 | uint8_t *pix = &row[i*pixsize]; 224 | pix[0] = std::min(255u, red); 225 | pix[1] = std::min(255u, green); 226 | pix[2] = std::min(255u, blue); 227 | pix[3] = 0; 228 | } 229 | memcpy(P->current_btm_row(true), row, P->stride); 230 | } 231 | 232 | VisuDftResolution W_DftWaterfall::desired_resolution() const { 233 | return VisuDftResolution::Medium; 234 | } 235 | 236 | void W_DftWaterfall::reset_data() { 237 | if (!P->imagebuf_valid()) 238 | return; 239 | 240 | unsigned sh = P->screen->h(); 241 | memset(P->imagebuf.get(), 0, sh*2*P->stride); 242 | } 243 | 244 | void W_DftWaterfall::draw() { 245 | draw_child(*P->rulertop); 246 | draw_child(*P->rulerbtm); 247 | P->draw_rulers(); 248 | 249 | // 250 | int sx = P->screen->x(); 251 | int sy = P->screen->y(); 252 | int sw = P->screen->w(); 253 | int sh = P->screen->h(); 254 | if (sw > 0 && sh > 0) { 255 | fl_push_clip(sx, sy, sw, sh); 256 | P->draw_data(); 257 | // 258 | int mx = P->mx; 259 | int my = P->my; 260 | if (mx >= sx && mx < sx+sw && my >= sy && my < sy+sh) 261 | P->draw_pointer(mx, my); 262 | fl_pop_clip(); 263 | } 264 | 265 | // 266 | draw_child(*P->grpctl); 267 | } 268 | 269 | int W_DftWaterfall::handle(int event) { 270 | if (event == FL_ENTER) { 271 | fl_cursor(FL_CURSOR_CROSS); 272 | return 1; 273 | } 274 | 275 | if (event == FL_LEAVE) { 276 | P->mx = -1; 277 | P->my = -1; 278 | fl_cursor(FL_CURSOR_DEFAULT); 279 | return 1; 280 | } 281 | 282 | if (event == FL_MOVE) { 283 | P->mx = Fl::event_x(); 284 | P->my = Fl::event_y(); 285 | return 1; 286 | } 287 | 288 | return Fl_Group::handle(event); 289 | } 290 | 291 | void W_DftWaterfall::resize(int x, int y, int w, int h) { 292 | W_DftVisu::resize(x, y, w, h); 293 | P->adapt_imagebuf(); 294 | P->reposition_controls(); 295 | } 296 | 297 | float W_DftWaterfall::Impl::frequency_of_x(float x) const { 298 | return frequency_of_r((x - Q->x()) / Q->w()); 299 | } 300 | 301 | float W_DftWaterfall::Impl::frequency_of_r(float r) const { 302 | float fmax = 0.5f * fsref; 303 | switch (fdomain) { 304 | default: 305 | case Domain_Linear: 306 | return r * fmax; 307 | case Domain_Logarithmic: 308 | float fmin = 10; 309 | float lfmin = std::log10(fmin); 310 | float lfmax = std::log10(fmax); 311 | float lf = lfmin + (lfmax - lfmin) * r; 312 | return std::pow(10.0f, lf); 313 | } 314 | } 315 | 316 | float W_DftWaterfall::Impl::r_of_frequency(float f) const { 317 | float fmax = 0.5f * fsref; 318 | switch (fdomain) { 319 | default: 320 | case Domain_Linear: 321 | return f / fmax; 322 | case Domain_Logarithmic: 323 | float lf = std::log10(f); 324 | float fmin = 10; 325 | float lfmin = std::log10(fmin); 326 | float lfmax = std::log10(fmax); 327 | return (lf - lfmin) / (lfmax - lfmin); 328 | } 329 | } 330 | 331 | float W_DftWaterfall::Impl::x_of_frequency(float f) const { 332 | return Q->x() + r_of_frequency(f) * Q->w(); 333 | } 334 | 335 | float W_DftWaterfall::Impl::nth_frequency_mark(unsigned i) const 336 | { 337 | switch (fdomain) { 338 | default: 339 | case Domain_Linear: 340 | return 250 * i; 341 | case Domain_Logarithmic: { 342 | const float m[] = {1.0, 2.5, 5.0}; 343 | const unsigned n = 3; 344 | return m[i % n] * std::pow(10, (i + n) / n); 345 | } 346 | } 347 | } 348 | 349 | int W_DftWaterfall::Impl::height_of_mark(unsigned i) const { 350 | switch (fdomain) { 351 | default: 352 | case Domain_Linear: { 353 | const int gradh[] = {16, 6, 8, 6}; 354 | return gradh[i%4]; 355 | } 356 | case Domain_Logarithmic: { 357 | const int gradh[] = {16, 6, 8}; 358 | return gradh[i%3]; 359 | } 360 | } 361 | } 362 | 363 | void W_DftWaterfall::Impl::draw_rulers() { 364 | int x = Q->x(); 365 | int y = Q->y(); 366 | int w = Q->w(); 367 | int h = Q->h(); 368 | 369 | fl_color(0, 0, 0); 370 | 371 | float f_nyq = 0.5f * this->fsref; 372 | for (unsigned i = 0; ; ++i) { 373 | float f = nth_frequency_mark(i); 374 | unsigned g = (fdomain == Domain_Logarithmic) ? 1 : 4; 375 | if (f > f_nyq) 376 | break; 377 | 378 | int xf = x_of_frequency(f); 379 | 380 | int l = height_of_mark(i); 381 | if (this->rulertop) 382 | fl_line(xf, y+mh-l, xf, y+mh-1); 383 | if (this->rulerbtm) 384 | fl_line(xf, y+h-mh, xf, y+h-mh+l); 385 | 386 | char textbuf[32]; 387 | if (f < 1000) 388 | snprintf(textbuf, sizeof(textbuf), "%g", f); 389 | else 390 | snprintf(textbuf, sizeof(textbuf), "%gk", f/1000); 391 | textbuf[sizeof(textbuf)-1] = 0; 392 | 393 | fl_font(FL_COURIER, 10); 394 | if (i%g == 0) { 395 | if (this->rulertop) 396 | fl_draw(textbuf, xf+4, y+2, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 397 | if (this->rulerbtm) 398 | fl_draw(textbuf, xf+4, y+h-1, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_BOTTOM, nullptr, 0); 399 | } 400 | } 401 | } 402 | 403 | void W_DftWaterfall::Impl::draw_data() { 404 | int sx = this->screen->x(); 405 | int sy = this->screen->y(); 406 | int sw = this->screen->w(); 407 | int sh = this->screen->h(); 408 | 409 | if (!this->imagebuf_valid()) { 410 | fl_color(0, 0, 0); 411 | fl_rectf(sx, sy, sw, sh); 412 | return; 413 | } 414 | 415 | unsigned pixsize = this->pixsize; 416 | unsigned stride = this->stride; 417 | 418 | uint8_t *top = this->current_top_row(); 419 | fl_draw_image(top, sx, sy, sw, sh, pixsize, stride); 420 | } 421 | 422 | void W_DftWaterfall::Impl::draw_pointer(int mx, int my) { 423 | int sx = this->screen->x(); 424 | int sy = this->screen->y(); 425 | int sw = this->screen->w(); 426 | int sh = this->screen->h(); 427 | 428 | float f = frequency_of_x(mx); 429 | 430 | fl_color(150, 150, 150); 431 | fl_line(mx, sy, mx, sy+sh-1); 432 | 433 | char textbuf[32]; 434 | snprintf(textbuf, sizeof(textbuf), "%.2f Hz", f); 435 | textbuf[sizeof(textbuf)-1] = 0; 436 | 437 | fl_font(FL_COURIER, 12); 438 | int tw = 8+std::ceil(fl_width("00000.00 Hz")); 439 | int th = 16; 440 | int tx = sx+sw-1-tw-4; 441 | int ty = sy+4; 442 | int tal = FL_ALIGN_LEFT|FL_ALIGN_CENTER; 443 | fl_color(50, 50, 50); 444 | fl_rectf(tx, ty, tw, th); 445 | fl_color(200, 200, 200); 446 | fl_rect(tx, ty, tw, th); 447 | fl_draw(textbuf, tx+4, ty, tw, th, tal, nullptr, 0); 448 | } 449 | 450 | uint8_t *W_DftWaterfall::Impl::row(unsigned i) { 451 | return &this->imagebuf[i*this->stride]; 452 | } 453 | 454 | uint8_t *W_DftWaterfall::Impl::current_top_row() { 455 | return this->row(this->imagerow); 456 | } 457 | 458 | uint8_t *W_DftWaterfall::Impl::current_btm_row(bool mirror) { 459 | unsigned sh = this->screen->h(); 460 | unsigned rowindex = this->imagerow+sh-1; 461 | if (mirror) { 462 | rowindex += sh; 463 | if (rowindex >= 2 * sh) 464 | rowindex -= 2 * sh; 465 | } 466 | return this->row(rowindex); 467 | } 468 | 469 | void W_DftWaterfall::Impl::advance_row(unsigned n) { 470 | unsigned sh = this->screen->h(); 471 | unsigned imagerow = this->imagerow + n; 472 | if (imagerow >= sh) 473 | imagerow -= sh; 474 | this->imagerow = imagerow; 475 | } 476 | 477 | bool W_DftWaterfall::Impl::imagebuf_valid() const { 478 | unsigned sw = this->screen->w(); 479 | unsigned sh = this->screen->h(); 480 | return this->imagew == sw && this->imageh == sh; 481 | } 482 | 483 | void W_DftWaterfall::Impl::adapt_imagebuf() { 484 | if (this->imagebuf_valid()) 485 | return; 486 | unsigned sw = this->screen->w(); 487 | unsigned sh = this->screen->h(); 488 | unsigned stride = sw * this->pixsize; 489 | this->imagebuf.reset(new uint8_t[sh * 2 * stride]()); 490 | this->stride = stride; 491 | this->imagew = sw; 492 | this->imageh = sh; 493 | this->imagerow = 0; 494 | } 495 | 496 | void W_DftWaterfall::Impl::create_controls(bool expanded) { 497 | int mh = 60; 498 | int bh = 20; 499 | 500 | bool btnfocus = this->btnexpand == Fl::focus(); 501 | 502 | delete this->grpctl; 503 | this->grpctl = nullptr; 504 | this->btnexpand = nullptr; 505 | 506 | if (!expanded) { 507 | int grpw = this->grpw = bh; 508 | int grph = this->grph = bh; 509 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, grpw, grph); 510 | grpctl->begin(); 511 | grpctl->resizable(nullptr); 512 | Fl_Button *btn = this->btnexpand = new Fl_Button(0, 0, grpw, grph, "+"); 513 | if (btnfocus) btn->take_focus(); 514 | btn->labelfont(FL_COURIER|FL_BOLD); 515 | btn->labelsize(16); 516 | TRIGGER_CALLBACK(btn, this, on_expand_controls); 517 | grpctl->end(); 518 | return; 519 | } 520 | 521 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, 1, mh); 522 | grpctl->begin(); 523 | grpctl->resizable(nullptr); 524 | 525 | int interx = 8; 526 | int curx = 0; 527 | 528 | Fl_Group *box {}; 529 | Fl_Button *btn {}; 530 | Fl_Choice *choice {}; 531 | 532 | int knobw = 42; 533 | int knobh = 42; 534 | 535 | box = new Fl_Group(curx, 0, 1, mh); 536 | box->begin(); 537 | box->resizable(nullptr); 538 | box->color(fl_rgb_color(191, 218, 255)); 539 | box->box(FL_ENGRAVED_BOX); 540 | curx += interx; 541 | 542 | choice = new Fl_Choice(curx, 15, 120, knobh, "F. domain"); 543 | choice->labelsize(10); 544 | choice->align(FL_ALIGN_TOP); 545 | choice->textfont(FL_COURIER); 546 | choice->textsize(12); 547 | choice->add("Linear"); 548 | choice->add("Logarithmic"); 549 | choice->value(this->fdomain); 550 | VALUE_CALLBACK(choice, this, changed_fdomain); 551 | curx += choice->w() + interx; 552 | 553 | box->size(curx - box->x(), mh); 554 | box->end(); 555 | 556 | box = new Fl_Group(curx, 0, mh, mh); 557 | box->begin(); 558 | // box->color(fl_rgb_color(191, 218, 255)); 559 | box->box(FL_ENGRAVED_BOX); 560 | btn = this->btnexpand = new Fl_Button(curx, mh-bh, bh, bh, "-"); 561 | if (btnfocus) btn->take_focus(); 562 | btn->labelfont(FL_COURIER|FL_BOLD); 563 | btn->labelsize(16); 564 | TRIGGER_CALLBACK(btn, this, on_unexpand_controls); 565 | curx += btn->w(); 566 | box->end(); 567 | 568 | int grpw = this->grpw = curx; 569 | int grph = this->grph = mh; 570 | grpctl->size(grpw, grph); 571 | 572 | grpctl->end(); 573 | } 574 | 575 | void W_DftWaterfall::Impl::reposition_controls() { 576 | int x = Q->x(); 577 | int y = Q->y(); 578 | int w = Q->w(); 579 | int h = Q->h(); 580 | Fl_Group *grpctl = this->grpctl; 581 | grpctl->resize(x+w-this->grpw, y+h-this->grph, this->grpw, this->grph); 582 | 583 | int wrbtm = this->rulertop->w() - this->grpw; 584 | if (this->rulerbtm->w() != wrbtm) { 585 | this->rulerbtm->size(wrbtm, this->rulerbtm->h()); 586 | Q->redraw(); 587 | } 588 | } 589 | 590 | void W_DftWaterfall::Impl::on_expand_controls() { 591 | Q->begin(); 592 | this->create_controls(true); 593 | this->reposition_controls(); 594 | Q->end(); 595 | } 596 | 597 | void W_DftWaterfall::Impl::on_unexpand_controls() { 598 | Q->begin(); 599 | this->create_controls(false); 600 | this->reposition_controls(); 601 | Q->end(); 602 | } 603 | 604 | void W_DftWaterfall::Impl::changed_fdomain(int val) { 605 | this->fdomain = val; 606 | Q->redraw(); 607 | } 608 | -------------------------------------------------------------------------------- /src/gui/w_dft_waterfall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_dft_visu.h" 3 | #include 4 | 5 | class W_DftWaterfall : public W_DftVisu { 6 | public: 7 | W_DftWaterfall(int x, int y, int w, int h); 8 | ~W_DftWaterfall(); 9 | 10 | void update_dft_data( 11 | const std::complex *spec[], unsigned n, float fs, unsigned nch) override; 12 | VisuDftResolution desired_resolution() const override; 13 | void reset_data() override; 14 | 15 | void draw() override; 16 | int handle(int event) override; 17 | void resize(int x, int y, int w, int h) override; 18 | 19 | private: 20 | struct Impl; 21 | std::unique_ptr P; 22 | }; 23 | -------------------------------------------------------------------------------- /src/gui/w_ts_oscillogram.cc: -------------------------------------------------------------------------------- 1 | #include "w_ts_oscillogram.h" 2 | #include "fl_widgets_ex.h" 3 | #include "fl_util.h" 4 | #include "s_math.h" 5 | #include "visu~-common.h" 6 | #include "util/unit_format.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | struct W_TsOscillogram::Impl { 18 | W_TsOscillogram *Q = nullptr; 19 | 20 | int mx = -1, my = -1; 21 | 22 | float samplerate = 44100; 23 | float period = 100e-3; 24 | float scale = 1; 25 | float yoff = 0; // -1..1 26 | float xtrig = 0; // 0..1 27 | float ytrig = 0; // -1..1 28 | int trigmode = 0; 29 | unsigned trigchannel = 0; 30 | 31 | // static constexpr float periodmin = 1e-2; 32 | // static constexpr float periodmax = 1; 33 | 34 | static constexpr float scalemin = 0.4; 35 | static constexpr float scalemax = 4; 36 | 37 | std::vector> timedata; 38 | unsigned channels = 0; 39 | 40 | float detect_best_xdiv(float divlimit, int minw); 41 | float detect_best_ydiv(float divlimit, int minh); 42 | 43 | Fl_Group *grpctl = nullptr; 44 | Fl_Button *btnexpand = nullptr; 45 | int grpw = 0; 46 | int grph = 0; 47 | 48 | void create_controls(bool expanded); 49 | void reposition_controls(); 50 | void on_expand_controls(); 51 | void on_unexpand_controls(); 52 | void changed_tb(float val); 53 | void changed_sc(float val); 54 | void changed_yoff(float val); 55 | void changed_xtrig(float val); 56 | void changed_ytrig(float val); 57 | void changed_trigmode(int val); 58 | void changed_trigchannel(float val); 59 | 60 | class Screen; 61 | Screen *screen = nullptr; 62 | }; 63 | 64 | constexpr float W_TsOscillogram::Impl::scalemin; 65 | constexpr float W_TsOscillogram::Impl::scalemax; 66 | 67 | class W_TsOscillogram::Impl::Screen : public Fl_Widget { 68 | public: 69 | Screen(Impl *P, int x, int y, int w, int h) 70 | : Fl_Widget(x, y, w, h), P(P) {} 71 | void draw() override; 72 | int handle(int event) override; 73 | private: 74 | void draw_back(); 75 | void draw_data(); 76 | void draw_pointer(int x, int y); 77 | Impl *P = nullptr; 78 | }; 79 | 80 | W_TsOscillogram::W_TsOscillogram(int x, int y, int w, int h) 81 | : W_TsVisu(x, y, w, h), 82 | P(new Impl) { 83 | P->Q = this; 84 | 85 | int sx = x; 86 | int sy = y; 87 | int sw = w; 88 | int sh = h; 89 | 90 | P->screen = new Impl::Screen(P.get(), sx, sy, sw, sh); 91 | this->resizable(P->screen); 92 | 93 | P->create_controls(false); 94 | P->reposition_controls(); 95 | 96 | this->end(); 97 | } 98 | 99 | W_TsOscillogram::~W_TsOscillogram() { 100 | } 101 | 102 | void W_TsOscillogram::update_ts_data( 103 | float sr, const frame<> data[], unsigned len, unsigned nch) { 104 | P->samplerate = sr; 105 | P->timedata.assign(data, data + len); 106 | P->channels = nch; 107 | } 108 | 109 | void W_TsOscillogram::reset_data() { 110 | P->timedata.clear(); 111 | } 112 | 113 | int W_TsOscillogram::Impl::Screen::handle(int event) { 114 | if (event == FL_ENTER) { 115 | fl_cursor(FL_CURSOR_CROSS); 116 | return 1; 117 | } 118 | 119 | if (event == FL_LEAVE) { 120 | P->mx = -1; 121 | P->my = -1; 122 | fl_cursor(FL_CURSOR_DEFAULT); 123 | return 1; 124 | } 125 | 126 | if (event == FL_MOVE) { 127 | P->mx = Fl::event_x(); 128 | P->my = Fl::event_y(); 129 | return 1; 130 | } 131 | 132 | return Fl_Widget::handle(event); 133 | } 134 | 135 | void W_TsOscillogram::Impl::Screen::draw() { 136 | int sx = P->screen->x(); 137 | int sy = P->screen->y(); 138 | int sw = P->screen->w(); 139 | int sh = P->screen->h(); 140 | if (sw > 0 && sh > 0) { 141 | fl_push_clip(sx, sy, sw, sh); 142 | // 143 | this->draw_back(); 144 | this->draw_data(); 145 | // 146 | int mx = P->mx; 147 | int my = P->my; 148 | if (mx >= sx && mx < sx+sw && my >= sy && my < sy+sh) 149 | this->draw_pointer(mx, my); 150 | fl_pop_clip(); 151 | } 152 | } 153 | 154 | void W_TsOscillogram::Impl::Screen::draw_back() { 155 | int sx = this->x(); 156 | int sy = this->y(); 157 | int sw = this->w(); 158 | int sh = this->h(); 159 | 160 | float tb = P->period; 161 | float sc = P->scale; 162 | float yoff = P->yoff; 163 | int trigmode = P->trigmode; 164 | 165 | const float dvmin = (-1 - yoff) / sc; 166 | const float dvmax = (1 - yoff) / sc; 167 | float xdiv = P->detect_best_xdiv(20.0f, 60); 168 | float ydiv = P->detect_best_ydiv(10.0f, 30); 169 | 170 | fl_color(0, 0, 0); 171 | fl_rectf(sx, sy, sw, sh); 172 | 173 | for (unsigned i = 0; ; ++i) { 174 | float dv = ydiv * i; 175 | float dv2 = -dv; 176 | if (dv > dvmax && dv2 < dvmin) 177 | break; 178 | int yv = sy + (sh-1) * (- yoff - dv * sc + 1) / 2; 179 | int yv2 = sy + (sh-1) * (- yoff - dv2 * sc + 1) / 2; 180 | if (i == 0) { 181 | fl_color(100, 100, 100); 182 | fl_line(sx, yv, sx+sw-1, yv); 183 | } else { 184 | fl_color(50, 50, 50); 185 | fl_line(sx, yv, sx+sw-1, yv); 186 | fl_line(sx, yv2, sx+sw-1, yv2); 187 | } 188 | } 189 | 190 | for (unsigned i = 1; ; ++i) { 191 | float r = 1 - i * xdiv / tb; 192 | if (r < 0) 193 | break; 194 | int xi = sx + sw * r; 195 | fl_color(50, 50, 50); 196 | fl_line(xi, sy, xi, sy+sh-1); 197 | } 198 | 199 | if (trigmode != 0) { 200 | fl_color(0, 150, 0); 201 | float xtrig = P->xtrig; 202 | int xt = sx + (sw-1) * xtrig; 203 | fl_line(xt, sy, xt, sy+sh-1); 204 | float ytrig = P->ytrig; 205 | int yt = sy + (sh-1) * (-ytrig + 1) / 2; 206 | fl_line(sx, yt, sx+sw-1, yt); 207 | } 208 | 209 | std::string X_str = unit_format("%.2f", tb); 210 | std::string Y_str = unit_format("%.2f", 2 / sc); 211 | std::string dX_str = unit_format("%.2f", xdiv); 212 | std::string dY_str = unit_format("%.2f", ydiv); 213 | 214 | fl_font(FL_COURIER, 12); 215 | fl_color(200, 200, 200); 216 | char textbuf[64]; 217 | int tx = sx+2; 218 | int ty = sy+2; 219 | 220 | snprintf(textbuf, sizeof(textbuf), "X = %ss", X_str.c_str()); 221 | textbuf[sizeof(textbuf)-1] = 0; 222 | fl_draw(textbuf, tx, ty, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 223 | 224 | snprintf(textbuf, sizeof(textbuf), "Y = %sV", Y_str.c_str()); 225 | textbuf[sizeof(textbuf)-1] = 0; 226 | fl_draw(textbuf, tx, ty+14, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 227 | 228 | tx += std::ceil(fl_width("X = 000.00 ms")); 229 | tx += 24; 230 | 231 | snprintf(textbuf, sizeof(textbuf), "dX = %ss/div", dX_str.c_str()); 232 | textbuf[sizeof(textbuf)-1] = 0; 233 | fl_draw(textbuf, tx, ty, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 234 | 235 | snprintf(textbuf, sizeof(textbuf), "dY = %sV/div", dY_str.c_str()); 236 | textbuf[sizeof(textbuf)-1] = 0; 237 | fl_draw(textbuf, tx, ty+14, 0, 0, FL_ALIGN_LEFT|FL_ALIGN_TOP, nullptr, 0); 238 | } 239 | 240 | void W_TsOscillogram::Impl::Screen::draw_data() { 241 | const unsigned channels = P->channels; 242 | if (channels == 0) 243 | return; 244 | 245 | const frame<> *p = P->timedata.data(); 246 | unsigned n = P->timedata.size(); 247 | 248 | if (n == 0) 249 | return; 250 | 251 | int sx = this->x(); 252 | int sy = this->y(); 253 | int sw = this->w(); 254 | int sh = this->h(); 255 | 256 | auto safe_data = [p, n](int idx, unsigned ch) { 257 | // return (idx < 0 || unsigned(idx) >= n) ? 0 : p[idx]; 258 | return p[clamp(idx, 0, int(n-1))].samples[ch]; 259 | }; 260 | 261 | float sr = P->samplerate; 262 | float tb = P->period; 263 | float sc = P->scale; 264 | float yoff = P->yoff; 265 | float xtrig = P->xtrig; 266 | float ytrig = P->ytrig; 267 | 268 | int trigmode = P->trigmode; 269 | bool trigup = trigmode & 1; 270 | bool trigdown = trigmode & 2; 271 | 272 | int xi {}; 273 | int yi {}; 274 | float idf0 = n - 1 - tb * sr; 275 | 276 | bool trigd = false; 277 | const unsigned trigchannel = P->trigchannel; 278 | 279 | if (trigmode != 0 && trigchannel < channels) { 280 | float ylevel = (ytrig - yoff) / sc; 281 | float xdelta = xtrig * tb * sr; 282 | int idt = idf0 + xdelta; 283 | float y1 = safe_data(idt + 1, trigchannel); 284 | float y2 {}; 285 | for (; idt >= 0 && !trigd; --idt) { 286 | y2 = y1; 287 | y1 = safe_data(idt, trigchannel); 288 | if ((trigup && y1 <= ylevel && y2 >= ylevel) || 289 | (trigdown && y1 >= ylevel && y2 <= ylevel)) { 290 | float frac = (ylevel - y1) / (y2 - y1); 291 | idf0 = idt - xdelta + frac; 292 | trigd = true; 293 | } 294 | } 295 | } 296 | 297 | for (unsigned c = channels; c-- > 0;) { 298 | const unsigned hue = (170 + c * 130) % 360; 299 | color::hsv col_hsv{(float)hue, 75, 80}; 300 | color::rgb col_rgb(col_hsv); 301 | 302 | fl_color( 303 | color::get::red(col_rgb), 304 | color::get::green(col_rgb), 305 | color::get::blue(col_rgb)); 306 | 307 | for (int i = 0; i < sw; ++i) { 308 | float r = i / float(sw-1); 309 | float idf = idf0 + r * tb * sr; 310 | 311 | int idx = idf; 312 | float mu = idf - idx; 313 | 314 | constexpr int nsmp = 4; 315 | float ismp[nsmp]; 316 | for (int i = 0; i < nsmp; ++i) 317 | ismp[i] = safe_data(idx + i, c); 318 | 319 | // float smp = interp_linear(ismp, mu); 320 | float smp = interp_catmull(ismp, mu); 321 | 322 | int oldxi = xi; 323 | int oldyi = yi; 324 | xi = sx + i; 325 | yi = sy + (sh-1) * (- yoff - smp * sc + 1) / 2; 326 | 327 | if (i > 0) 328 | fl_line(oldxi, oldyi, xi, yi); 329 | } 330 | } 331 | 332 | if (trigmode != 0) { 333 | const char textbuf[] = "Trig"; 334 | fl_font(FL_COURIER, 12); 335 | int tw = 8+std::ceil(fl_width(textbuf)); 336 | int th = 16; 337 | int tx = sx+sw-1-tw-150; 338 | int ty = sy+4; 339 | int tal = FL_ALIGN_LEFT|FL_ALIGN_CENTER; 340 | fl_color(50, 50, 50); 341 | fl_rectf(tx, ty, tw, th); 342 | fl_color(200, 200, 200); 343 | fl_rect(tx, ty, tw, th); 344 | if (trigd) 345 | fl_color(0, 250, 0); 346 | else 347 | fl_color(0, 150, 0); 348 | fl_draw(textbuf, tx + 4, ty, tw, th, tal, nullptr, 0); 349 | } 350 | } 351 | 352 | void W_TsOscillogram::Impl::Screen::draw_pointer(int mx, int my) { 353 | int sx = this->x(); 354 | int sy = this->y(); 355 | int sw = this->w(); 356 | int sh = this->h(); 357 | 358 | float tb = P->period; 359 | float sc = P->scale; 360 | float yoff = P->yoff; 361 | 362 | float t = tb * (mx-sx) / (sw-1); 363 | float v = (1 - yoff - 2*float(my-sy)/(sh-1)) / sc; 364 | 365 | fl_color(150, 150, 150); 366 | fl_line(mx, sy, mx, sy+sh-1); 367 | fl_line(sx, my, sx+sw-1, my); 368 | 369 | char textbuf[64]; 370 | if (t < 1) 371 | snprintf(textbuf, sizeof(textbuf), "%.2f ms %.2f V", t * 1000, v); 372 | else 373 | snprintf(textbuf, sizeof(textbuf), "%.2f s %.2f V", t, v); 374 | textbuf[sizeof(textbuf)-1] = 0; 375 | 376 | fl_font(FL_COURIER, 12); 377 | int tw = 8+std::ceil(fl_width("000.00 ms -0.00 V")); 378 | int th = 16; 379 | int tx = sx+sw-1-tw-4; 380 | int ty = sy+4; 381 | int tal = FL_ALIGN_LEFT|FL_ALIGN_CENTER; 382 | fl_color(50, 50, 50); 383 | fl_rectf(tx, ty, tw, th); 384 | fl_color(200, 200, 200); 385 | fl_rect(tx, ty, tw, th); 386 | fl_draw(textbuf, tx+4, ty, tw, th, tal, nullptr, 0); 387 | } 388 | 389 | void W_TsOscillogram::resize(int x, int y, int w, int h) { 390 | W_TsVisu::resize(x, y, w, h); 391 | P->reposition_controls(); 392 | } 393 | 394 | float W_TsOscillogram::Impl::detect_best_xdiv(float divlimit, int minw) { 395 | float tb = this->period; 396 | int sw = Q->w(); 397 | 398 | auto nthdiv = [](unsigned i) -> float { 399 | const float v[] = { 50, 25, 10 }; 400 | return v[i % 3] * std::pow(10.0f, -float(i / 3)); 401 | }; 402 | 403 | float divs[2] = {nthdiv(0)}; 404 | for (unsigned i = 1; ; ++i) { 405 | divs[1] = divs[0]; 406 | divs[0] = nthdiv(i); 407 | if ((sw-1) * divs[0] / tb < minw) 408 | return divs[1]; 409 | if (tb / divs[0] > divlimit) 410 | return divs[0]; 411 | } 412 | } 413 | 414 | float W_TsOscillogram::Impl::detect_best_ydiv(float divlimit, int minh) { 415 | float yr = 2 / this->scale; 416 | int sh = Q->h(); 417 | 418 | auto nthdiv = [](unsigned i) -> float { 419 | const float v[] = { 50, 25, 10 }; 420 | return v[i % 3] * std::pow(10.0f, -float(i / 3)); 421 | }; 422 | 423 | float divs[2] = {nthdiv(0)}; 424 | for (unsigned i = 1; ; ++i) { 425 | divs[1] = divs[0]; 426 | divs[0] = nthdiv(i); 427 | if ((sh-1) * divs[0] / yr < minh) 428 | return divs[1]; 429 | if (yr / divs[0] > divlimit) 430 | return divs[0]; 431 | } 432 | } 433 | 434 | void W_TsOscillogram::Impl::create_controls(bool expanded) { 435 | int mh = 60; 436 | int bh = 20; 437 | 438 | bool btnfocus = this->btnexpand == Fl::focus(); 439 | 440 | delete this->grpctl; 441 | this->grpctl = nullptr; 442 | this->btnexpand = nullptr; 443 | 444 | if (!expanded) { 445 | int grpw = this->grpw = bh; 446 | int grph = this->grph = bh; 447 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, grpw, grph); 448 | grpctl->begin(); 449 | grpctl->resizable(nullptr); 450 | Fl_Button *btn = this->btnexpand = new Fl_Button(0, 0, grpw, grph, "+"); 451 | if (btnfocus) btn->take_focus(); 452 | btn->labelfont(FL_COURIER|FL_BOLD); 453 | btn->labelsize(16); 454 | TRIGGER_CALLBACK(btn, this, on_expand_controls); 455 | grpctl->end(); 456 | return; 457 | } 458 | 459 | Fl_Group *grpctl = this->grpctl = new Fl_Group(0, 0, 1, mh); 460 | grpctl->begin(); 461 | grpctl->resizable(nullptr); 462 | 463 | int interx = 8; 464 | int curx = 0; 465 | 466 | Fl_Group *box {}; 467 | Fl_Button *btn {}; 468 | Fl_KnobEx *knob {}; 469 | Fl_Choice *choice {}; 470 | Fl_Counter *counter {}; 471 | 472 | int knobw = 42; 473 | int knobh = 42; 474 | 475 | box = new Fl_Group(curx, 0, mh, mh); 476 | box->begin(); 477 | // box->color(fl_rgb_color(191, 218, 255)); 478 | box->box(FL_ENGRAVED_BOX); 479 | btn = this->btnexpand = new Fl_Button(curx, mh-bh, bh, bh, "-"); 480 | if (btnfocus) btn->take_focus(); 481 | btn->labelfont(FL_COURIER|FL_BOLD); 482 | btn->labelsize(16); 483 | TRIGGER_CALLBACK(btn, this, on_unexpand_controls); 484 | curx += btn->w(); 485 | box->end(); 486 | 487 | box = new Fl_Group(curx, 0, 1, mh); 488 | box->begin(); 489 | box->resizable(nullptr); 490 | box->color(fl_rgb_color(191, 218, 255)); 491 | box->box(FL_ENGRAVED_BOX); 492 | curx += interx; 493 | 494 | knob = new Fl_KnobEx(curx, 15, knobw, knobh, "X. period"); 495 | // knob->type(Fl_KnobEx::LINELOG_3); 496 | knob->type(Fl_KnobEx::LINELOG_2); 497 | knob->labelsize(10); 498 | knob->align(FL_ALIGN_TOP); 499 | // knob->value(this->period / this->periodmax); 500 | // knob->value((std::log10(this->period) + 2) / 2); 501 | knob->value((std::log10(this->period) + 3) / 3); 502 | VALUE_CALLBACK(knob, this, changed_tb); 503 | curx += knob->w() + interx; 504 | 505 | knob = new Fl_KnobEx(curx, 15, knobw, knobh, "Y. scale"); 506 | knob->type(Fl_KnobEx::LINELIN); 507 | knob->labelsize(10); 508 | knob->align(FL_ALIGN_TOP); 509 | knob->value(this->scale / this->scalemax); 510 | VALUE_CALLBACK(knob, this, changed_sc); 511 | curx += knob->w() + interx; 512 | 513 | knob = new Fl_KnobEx(curx, 15, knobw, knobh, "Y. offset"); 514 | knob->type(Fl_KnobEx::LINELIN); 515 | knob->labelsize(10); 516 | knob->align(FL_ALIGN_TOP); 517 | knob->range(-1, +1); 518 | knob->value(this->yoff); 519 | VALUE_CALLBACK(knob, this, changed_yoff); 520 | curx += knob->w() + interx; 521 | 522 | box->size(curx - box->x(), mh); 523 | box->end(); 524 | 525 | box = new Fl_Group(curx, 0, 1, mh); 526 | box->begin(); 527 | box->resizable(nullptr); 528 | box->color(fl_rgb_color(226, 189, 255)); 529 | box->box(FL_ENGRAVED_BOX); 530 | curx += interx; 531 | 532 | knob = new Fl_KnobEx(curx, 15, knobw, knobh, "X. trig"); 533 | knob->type(Fl_KnobEx::LINELIN); 534 | knob->labelsize(10); 535 | knob->align(FL_ALIGN_TOP); 536 | knob->value(this->xtrig); 537 | VALUE_CALLBACK(knob, this, changed_xtrig); 538 | curx += knob->w() + interx; 539 | 540 | knob = new Fl_KnobEx(curx, 15, knobw, knobh, "Y. trig"); 541 | knob->type(Fl_KnobEx::LINELIN); 542 | knob->labelsize(10); 543 | knob->align(FL_ALIGN_TOP); 544 | knob->range(-1, +1); 545 | knob->value(this->ytrig); 546 | VALUE_CALLBACK(knob, this, changed_ytrig); 547 | curx += knob->w() + interx; 548 | 549 | choice = new Fl_Choice(curx, 15, 65, knobh, "Trig. mode"); 550 | choice->labelsize(10); 551 | choice->align(FL_ALIGN_TOP); 552 | choice->textfont(FL_COURIER); 553 | choice->textsize(12); 554 | choice->add("None"); 555 | choice->add("Up"); 556 | choice->add("Down"); 557 | choice->add("Both"); 558 | choice->value(this->trigmode); 559 | VALUE_CALLBACK(choice, this, changed_trigmode); 560 | curx += choice->w() + interx; 561 | 562 | counter = new Fl_Counter(curx, 15, 65, knobh, "Trig. channel"); 563 | counter->labelsize(10); 564 | counter->align(FL_ALIGN_TOP); 565 | counter->textfont(FL_COURIER); 566 | counter->textsize(12); 567 | counter->type(FL_SIMPLE_COUNTER); 568 | counter->step(1); 569 | counter->bounds(1, channelmax); 570 | counter->value(1 + this->trigchannel); 571 | VALUE_CALLBACK(counter, this, changed_trigchannel); 572 | curx += counter->w() + interx; 573 | 574 | box->size(curx - box->x(), mh); 575 | box->end(); 576 | 577 | int grpw = this->grpw = curx; 578 | int grph = this->grph = mh; 579 | grpctl->size(grpw, grph); 580 | 581 | grpctl->end(); 582 | } 583 | 584 | void W_TsOscillogram::Impl::reposition_controls() { 585 | int x = Q->x(); 586 | int y = Q->y(); 587 | int h = Q->h(); 588 | Fl_Group *grpctl = this->grpctl; 589 | grpctl->resize(x, y+h-this->grph, this->grpw, this->grph); 590 | } 591 | 592 | void W_TsOscillogram::Impl::on_expand_controls() { 593 | Q->begin(); 594 | this->create_controls(true); 595 | this->reposition_controls(); 596 | Q->end(); 597 | } 598 | 599 | void W_TsOscillogram::Impl::on_unexpand_controls() { 600 | Q->begin(); 601 | this->create_controls(false); 602 | this->reposition_controls(); 603 | Q->end(); 604 | } 605 | 606 | void W_TsOscillogram::Impl::changed_tb(float val) { 607 | // float tb = clamp(val * this->periodmax, this->periodmin, this->periodmax); 608 | // float tb = std::pow(10.0f, -2 + 2 * val); 609 | float tb = std::pow(10.0f, -3 + 3 * val); 610 | this->period = tb; 611 | Q->redraw(); 612 | } 613 | 614 | void W_TsOscillogram::Impl::changed_sc(float val) { 615 | float sc = clamp(val * this->scalemax, this->scalemin, this->scalemax); 616 | this->scale = sc; 617 | Q->redraw(); 618 | } 619 | 620 | void W_TsOscillogram::Impl::changed_yoff(float val) { 621 | this->yoff = val; 622 | Q->redraw(); 623 | } 624 | 625 | void W_TsOscillogram::Impl::changed_xtrig(float val) { 626 | this->xtrig = val; 627 | Q->redraw(); 628 | } 629 | 630 | void W_TsOscillogram::Impl::changed_ytrig(float val) { 631 | this->ytrig = val; 632 | Q->redraw(); 633 | } 634 | 635 | void W_TsOscillogram::Impl::changed_trigmode(int val) { 636 | this->trigmode = val; 637 | Q->redraw(); 638 | } 639 | 640 | void W_TsOscillogram::Impl::changed_trigchannel(float val) 641 | { 642 | unsigned channel = (unsigned)val - 1; 643 | this->trigchannel = channel; 644 | Q->redraw(); 645 | } 646 | -------------------------------------------------------------------------------- /src/gui/w_ts_oscillogram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_ts_visu.h" 3 | #include 4 | 5 | class W_TsOscillogram : public W_TsVisu { 6 | public: 7 | W_TsOscillogram(int x, int y, int w, int h); 8 | ~W_TsOscillogram(); 9 | 10 | void update_ts_data( 11 | float sr, const frame<> data[], unsigned len, unsigned nch) override; 12 | void reset_data() override; 13 | 14 | void resize(int x, int y, int w, int h) override; 15 | 16 | private: 17 | struct Impl; 18 | std::unique_ptr P; 19 | }; 20 | -------------------------------------------------------------------------------- /src/gui/w_ts_visu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "w_visu.h" 3 | #include "s_smem.h" 4 | 5 | class W_TsVisu : public W_Visu { 6 | public: 7 | W_TsVisu(int x, int y, int w, int h) 8 | : W_Visu(x, y, w, h) {} 9 | virtual ~W_TsVisu() {} 10 | 11 | virtual void update_ts_data( 12 | float sr, const frame<> data[], unsigned len, unsigned nch) = 0; 13 | }; 14 | -------------------------------------------------------------------------------- /src/gui/w_visu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class W_Visu : public Fl_Group { 5 | public: 6 | W_Visu(int x, int y, int w, int h) 7 | : Fl_Group(x, y, w, h) {} 8 | virtual ~W_Visu() {} 9 | 10 | virtual void reset_data() = 0; 11 | }; 12 | -------------------------------------------------------------------------------- /src/jack-main.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "visu~.h" 3 | #include "visu~-remote.h" 4 | #include "util/scope_guard.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static jack_client_t *g_client = nullptr; 14 | static jack_port_t *g_input[channelmax] = {}; 15 | static std::unique_ptr g_visu; 16 | static float g_samplerate = 44100; 17 | static unsigned g_channels = 2; 18 | 19 | // substitutions of pd standard routines 20 | extern "C" float sys_getsr() { return g_samplerate; } 21 | extern "C" void dsp_addv(t_perfroutine, int, t_int *) { /* don't need */ } 22 | extern "C" t_inlet *inlet_new(t_object *, t_pd *, t_symbol *, t_symbol *) { return nullptr; /* don't need */ } 23 | extern "C" t_outlet *outlet_new(t_object *owner, t_symbol *s) { return nullptr; /* don't need */ } 24 | extern "C" t_symbol *gensym(const char *) { return nullptr; /* don't need */ } 25 | extern "C" void outlet_bang(t_outlet *x) { /* don't need */ } 26 | 27 | //------------------------------------------------------------------------------ 28 | static int cb_jack_process(jack_nframes_t nframes, void *) { 29 | t_int w[3 + channelmax]; 30 | t_int *wp = w; 31 | *wp++ = (t_int)&visu_perform; 32 | *wp++ = (t_int)g_visu.get(); 33 | for (unsigned i = 0, n = g_channels; i < n; ++i) 34 | *wp++ = (t_int)jack_port_get_buffer(g_input[i], nframes); 35 | *wp++ = (t_int)nframes; 36 | visu_perform(w); 37 | return 0; 38 | } 39 | 40 | //------------------------------------------------------------------------------ 41 | int main(int argc, char *argv[]) { 42 | const char *visuname = nullptr; 43 | for (int c; (c = getopt(argc, argv, "t:c:h")) != -1;) { 44 | switch (c) { 45 | case 't': 46 | visuname = optarg; break; 47 | case 'c': { 48 | unsigned nch = ::atoi(optarg); 49 | if (nch < 1 || nch > channelmax) { 50 | fprintf(stderr, "invalid number of channels (1-%u)\n", channelmax); 51 | return 1; 52 | } 53 | g_channels = nch; break; 54 | } 55 | case 'h': 56 | default: 57 | fprintf(stderr, "Usage: %s [-t type] [-c channels] [title]\n", argv[0]); 58 | return 1; 59 | } 60 | } 61 | 62 | if (!visuname) { 63 | const char *progname = argv[0]; 64 | size_t pos; 65 | for (pos = strlen(progname); pos > 0; --pos) { 66 | if (progname[pos - 1] == '/') break; 67 | #ifdef _WIN32 68 | if (progname[pos - 1] == '\\') break; 69 | #endif 70 | } 71 | visuname = progname + pos; 72 | } 73 | 74 | // 75 | static const char *visu_names[] = 76 | {"wfvisu~", "sgvisu~", "ogvisu~", "tfvisu~"}; 77 | static const VisuType visu_types[] = { 78 | Visu_Waterfall, Visu_Spectrogram, Visu_Oscillogram, Visu_Transfer}; 79 | 80 | // 81 | const char *name; 82 | if (optind == argc) 83 | name = "untitled"; 84 | else if (optind == argc - 1) 85 | name = argv[optind]; 86 | else 87 | return 1; 88 | 89 | // 90 | unsigned visu_index = (unsigned)-1; 91 | for (unsigned i = 0; i < Visu_Count && visu_index == (unsigned)-1; ++i) 92 | if (!std::strcmp(visuname, visu_names[i])) 93 | visu_index = i; 94 | if (visu_index == (unsigned)-1) 95 | return 1; 96 | 97 | // 98 | { 99 | sigset_t mask; 100 | sigemptyset(&mask); 101 | sigaddset(&mask, SIGINT); 102 | sigaddset(&mask, SIGTERM); 103 | sigaddset(&mask, SIGCHLD); 104 | pthread_sigmask(SIG_BLOCK, &mask, nullptr); 105 | } 106 | 107 | // 108 | VisuType type = visu_types[visu_index]; 109 | t_visu *visu = new t_visu; 110 | g_visu.reset(visu); 111 | visu_init(visu, type); 112 | visu->x_title = name; 113 | visu->x_channels = g_channels; 114 | 115 | // 116 | char *client_name; 117 | if (asprintf(&client_name, "%s %s", visuname, name) == -1) 118 | throw std::bad_alloc(); 119 | scope(exit) { free(client_name); }; 120 | 121 | g_client = jack_client_open(client_name, JackNoStartServer, nullptr); 122 | if (!g_client) 123 | throw std::runtime_error("cannot open jack client"); 124 | scope(exit) { jack_client_close(g_client); }; 125 | 126 | // 127 | for (unsigned i = 0, n = g_channels; i < n; ++i) { 128 | char portname[32]; 129 | sprintf(portname, "channel %u", i + 1); 130 | jack_port_t *port = jack_port_register( 131 | g_client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0); 132 | if (!port) 133 | throw std::runtime_error("cannot register jack ports"); 134 | g_input[i] = port; 135 | } 136 | 137 | // 138 | g_samplerate = jack_get_sample_rate(g_client); 139 | jack_set_process_callback(g_client, &cb_jack_process, nullptr); 140 | if (jack_activate(g_client)) 141 | throw std::runtime_error("cannot activate jack client"); 142 | 143 | // 144 | visu_bang(visu); 145 | 146 | // 147 | bool signaled = false; 148 | while (!signaled) { 149 | sigset_t mask; 150 | sigemptyset(&mask); 151 | sigaddset(&mask, SIGINT); 152 | sigaddset(&mask, SIGTERM); 153 | sigaddset(&mask, SIGCHLD); 154 | int sig; 155 | if (!sigwait(&mask, &sig)) { 156 | if (sig != SIGCHLD) { 157 | signaled = true; 158 | } else { 159 | int status; 160 | pid_t pid = waitpid(-1, &status, WNOHANG); 161 | signaled = pid != -1 && (WIFEXITED(status) || WIFSIGNALED(status)); 162 | } 163 | } 164 | if (signaled) { 165 | jack_client_close(g_client); 166 | _exit(1); 167 | } 168 | } 169 | 170 | // 171 | return 0; 172 | } 173 | -------------------------------------------------------------------------------- /src/ogvisu~.cc: -------------------------------------------------------------------------------- 1 | #include "ogvisu~.h" 2 | #include "util/scope_guard.h" 3 | 4 | PD_LAYOUT_CHECK(t_ogvisu); 5 | 6 | static t_class *ogvisu_class; 7 | 8 | void ogvisu_tilde_setup() { 9 | ogvisu_class = class_new( 10 | gensym("ogvisu~"), 11 | (t_newmethod)&ogvisu_new, (t_method)&ogvisu_free, sizeof(t_ogvisu), 12 | CLASS_DEFAULT, A_GIMME, A_NULL); 13 | CLASS_MAINSIGNALIN( 14 | ogvisu_class, t_ogvisu, x_signalin); 15 | class_addbang( 16 | ogvisu_class, &ogvisu_bang); 17 | class_addmethod( 18 | ogvisu_class, (t_method)&ogvisu_dsp, gensym("dsp"), A_CANT, A_NULL); 19 | visu_setup_generic_methods(ogvisu_class); 20 | } 21 | 22 | void *ogvisu_new(t_symbol *s, int argc, t_atom *argv) { 23 | t_ogvisu *x = (t_ogvisu *)pd_new(ogvisu_class); 24 | if (!x) 25 | return nullptr; 26 | x->x_cleanup = nullptr; 27 | 28 | bool success = false; 29 | scope(exit) { if (!success) pd_free((t_pd *)x); }; 30 | 31 | /// 32 | try { 33 | new (x) t_ogvisu; 34 | x->x_cleanup = [](t_visu *b) { 35 | static_cast(b)->~t_ogvisu(); }; 36 | } catch (std::exception &ex) { 37 | error("%s", ex.what()); 38 | return nullptr; 39 | } 40 | 41 | /// 42 | try { 43 | if (!ogvisu_opts(x, argc, argv)) 44 | return nullptr; 45 | visu_init(x, Visu_Oscillogram); 46 | } catch (std::exception &ex) { 47 | error("%s", ex.what()); 48 | return nullptr; 49 | } 50 | 51 | success = true; 52 | return x; 53 | } 54 | 55 | void ogvisu_free(t_ogvisu *x) { 56 | visu_free(x); 57 | } 58 | 59 | bool ogvisu_opts(t_ogvisu *x, int argc, t_atom *argv) { 60 | std::string title; 61 | unsigned channels = 2; 62 | switch (argc) { 63 | case 2: { 64 | channels = (int)atom_getfloat(&argv[1]); 65 | if ((int)channels <= 0 || channels > channelmax) 66 | return false; 67 | } 68 | case 1: { 69 | char buf[128]; 70 | atom_string(&argv[0], buf, sizeof(buf)); 71 | title.assign(buf); 72 | } 73 | case 0: 74 | break; 75 | default: 76 | return false; 77 | } 78 | x->x_title = std::move(title); 79 | x->x_channels = channels; 80 | return true; 81 | } 82 | 83 | void ogvisu_bang(t_ogvisu *x) { 84 | visu_bang(x); 85 | } 86 | 87 | void ogvisu_dsp(t_ogvisu *x, t_signal **sp) { 88 | visu_dsp(x, sp); 89 | } 90 | -------------------------------------------------------------------------------- /src/ogvisu~.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~.h" 3 | 4 | #ifdef WIN32 5 | # define EXPORT __declspec(dllexport) 6 | #else 7 | # define EXPORT [[gnu::visibility("default")]] 8 | #endif 9 | 10 | extern "C" { 11 | EXPORT void ogvisu_tilde_setup(); 12 | } 13 | 14 | /// 15 | struct t_ogvisu : t_visu {}; 16 | 17 | void *ogvisu_new(t_symbol *s, int argc, t_atom *argv); 18 | void ogvisu_free(t_ogvisu *x); 19 | bool ogvisu_opts(t_ogvisu *x, int argc, t_atom *argv); 20 | void ogvisu_bang(t_ogvisu *x); 21 | void ogvisu_dsp(t_ogvisu *x, t_signal **sp); 22 | -------------------------------------------------------------------------------- /src/sgvisu~.cc: -------------------------------------------------------------------------------- 1 | #include "sgvisu~.h" 2 | #include "util/scope_guard.h" 3 | 4 | PD_LAYOUT_CHECK(t_sgvisu); 5 | 6 | static t_class *sgvisu_class; 7 | 8 | void sgvisu_tilde_setup() { 9 | sgvisu_class = class_new( 10 | gensym("sgvisu~"), 11 | (t_newmethod)&sgvisu_new, (t_method)&sgvisu_free, sizeof(t_sgvisu), 12 | CLASS_DEFAULT, A_GIMME, A_NULL); 13 | CLASS_MAINSIGNALIN( 14 | sgvisu_class, t_sgvisu, x_signalin); 15 | class_addbang( 16 | sgvisu_class, &sgvisu_bang); 17 | class_addmethod( 18 | sgvisu_class, (t_method)&sgvisu_dsp, gensym("dsp"), A_CANT, A_NULL); 19 | visu_setup_generic_methods(sgvisu_class); 20 | } 21 | 22 | void *sgvisu_new(t_symbol *s, int argc, t_atom *argv) { 23 | t_sgvisu *x = (t_sgvisu *)pd_new(sgvisu_class); 24 | if (!x) 25 | return nullptr; 26 | x->x_cleanup = nullptr; 27 | 28 | bool success = false; 29 | scope(exit) { if (!success) pd_free((t_pd *)x); }; 30 | 31 | /// 32 | try { 33 | new (x) t_sgvisu; 34 | x->x_cleanup = [](t_visu *b) { 35 | static_cast(b)->~t_sgvisu(); }; 36 | } catch (std::exception &ex) { 37 | error("%s", ex.what()); 38 | return nullptr; 39 | } 40 | 41 | /// 42 | try { 43 | if (!sgvisu_opts(x, argc, argv)) 44 | return nullptr; 45 | visu_init(x, Visu_Spectrogram); 46 | } catch (std::exception &ex) { 47 | error("%s", ex.what()); 48 | return nullptr; 49 | } 50 | 51 | success = true; 52 | return x; 53 | } 54 | 55 | void sgvisu_free(t_sgvisu *x) { 56 | visu_free(x); 57 | } 58 | 59 | bool sgvisu_opts(t_sgvisu *x, int argc, t_atom *argv) { 60 | std::string title; 61 | unsigned channels = 2; 62 | switch (argc) { 63 | case 2: { 64 | channels = (int)atom_getfloat(&argv[1]); 65 | if ((int)channels <= 0 || channels > channelmax) 66 | return false; 67 | } 68 | case 1: { 69 | char buf[128]; 70 | atom_string(&argv[0], buf, sizeof(buf)); 71 | title.assign(buf); 72 | } 73 | case 0: 74 | break; 75 | default: 76 | return false; 77 | } 78 | x->x_title = std::move(title); 79 | x->x_channels = channels; 80 | return true; 81 | } 82 | 83 | void sgvisu_bang(t_sgvisu *x) { 84 | visu_bang(x); 85 | } 86 | 87 | void sgvisu_dsp(t_sgvisu *x, t_signal **sp) { 88 | visu_dsp(x, sp); 89 | } 90 | -------------------------------------------------------------------------------- /src/sgvisu~.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~.h" 3 | 4 | #ifdef WIN32 5 | # define EXPORT __declspec(dllexport) 6 | #else 7 | # define EXPORT [[gnu::visibility("default")]] 8 | #endif 9 | 10 | extern "C" { 11 | EXPORT void sgvisu_tilde_setup(); 12 | } 13 | 14 | /// 15 | struct t_sgvisu : t_visu {}; 16 | 17 | void *sgvisu_new(t_symbol *s, int argc, t_atom *argv); 18 | void sgvisu_free(t_sgvisu *x); 19 | bool sgvisu_opts(t_sgvisu *x, int argc, t_atom *argv); 20 | void sgvisu_bang(t_sgvisu *x); 21 | void sgvisu_dsp(t_sgvisu *x, t_signal **sp); 22 | -------------------------------------------------------------------------------- /src/tfvisu~.cc: -------------------------------------------------------------------------------- 1 | #include "tfvisu~.h" 2 | #include "util/scope_guard.h" 3 | 4 | PD_LAYOUT_CHECK(t_tfvisu); 5 | 6 | static t_class *tfvisu_class; 7 | 8 | void tfvisu_tilde_setup() { 9 | tfvisu_class = class_new( 10 | gensym("tfvisu~"), 11 | (t_newmethod)&tfvisu_new, (t_method)&tfvisu_free, sizeof(t_tfvisu), 12 | CLASS_DEFAULT, A_GIMME, A_NULL); 13 | CLASS_MAINSIGNALIN( 14 | tfvisu_class, t_tfvisu, x_signalin); 15 | class_addbang( 16 | tfvisu_class, &tfvisu_bang); 17 | class_addmethod( 18 | tfvisu_class, (t_method)&tfvisu_dsp, gensym("dsp"), A_CANT, A_NULL); 19 | visu_setup_generic_methods(tfvisu_class); 20 | } 21 | 22 | void *tfvisu_new(t_symbol *s, int argc, t_atom *argv) { 23 | t_tfvisu *x = (t_tfvisu *)pd_new(tfvisu_class); 24 | if (!x) 25 | return nullptr; 26 | x->x_cleanup = nullptr; 27 | 28 | bool success = false; 29 | scope(exit) { if (!success) pd_free((t_pd *)x); }; 30 | 31 | /// 32 | try { 33 | new (x) t_tfvisu; 34 | x->x_cleanup = [](t_visu *b) { 35 | static_cast(b)->~t_tfvisu(); }; 36 | } catch (std::exception &ex) { 37 | error("%s", ex.what()); 38 | return nullptr; 39 | } 40 | 41 | /// 42 | try { 43 | if (!tfvisu_opts(x, argc, argv)) 44 | return nullptr; 45 | visu_init(x, Visu_Transfer); 46 | } catch (std::exception &ex) { 47 | error("%s", ex.what()); 48 | return nullptr; 49 | } 50 | 51 | success = true; 52 | return x; 53 | } 54 | 55 | void tfvisu_free(t_tfvisu *x) { 56 | visu_free(x); 57 | } 58 | 59 | bool tfvisu_opts(t_tfvisu *x, int argc, t_atom *argv) { 60 | std::string title; 61 | unsigned channels = 2; 62 | switch (argc) { 63 | case 2: { 64 | channels = (int)atom_getfloat(&argv[1]); 65 | if ((int)channels <= 0 || channels > channelmax) 66 | return false; 67 | } 68 | case 1: { 69 | char buf[128]; 70 | atom_string(&argv[0], buf, sizeof(buf)); 71 | title.assign(buf); 72 | } 73 | case 0: 74 | break; 75 | default: 76 | return false; 77 | } 78 | x->x_title = std::move(title); 79 | x->x_channels = channels; 80 | return true; 81 | } 82 | 83 | void tfvisu_bang(t_tfvisu *x) { 84 | visu_bang(x); 85 | } 86 | 87 | void tfvisu_dsp(t_tfvisu *x, t_signal **sp) { 88 | visu_dsp(x, sp); 89 | } 90 | -------------------------------------------------------------------------------- /src/tfvisu~.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~.h" 3 | 4 | #ifdef WIN32 5 | # define EXPORT __declspec(dllexport) 6 | #else 7 | # define EXPORT [[gnu::visibility("default")]] 8 | #endif 9 | 10 | extern "C" { 11 | EXPORT void tfvisu_tilde_setup(); 12 | } 13 | 14 | /// 15 | struct t_tfvisu : t_visu {}; 16 | 17 | void *tfvisu_new(t_symbol *s, int argc, t_atom *argv); 18 | void tfvisu_free(t_tfvisu *x); 19 | bool tfvisu_opts(t_tfvisu *x, int argc, t_atom *argv); 20 | void tfvisu_bang(t_tfvisu *x); 21 | void tfvisu_dsp(t_tfvisu *x, t_signal **sp); 22 | -------------------------------------------------------------------------------- /src/util/scope_guard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define scope(kw) SCOPE_GUARD_##kw() 5 | 6 | template class scope_guard { 7 | F f_; 8 | public: 9 | inline scope_guard(scope_guard &&o): f_(std::move(o.f_)) {} 10 | inline scope_guard(F f): f_(std::move(f)) {} 11 | inline ~scope_guard() { f_(); } 12 | }; 13 | 14 | #define SCOPE_GUARD_PP_CAT_I(x, y) x##y 15 | #define SCOPE_GUARD_PP_CAT(x, y) SCOPE_GUARD_PP_CAT_I(x, y) 16 | #define SCOPE_GUARD_PP_UNIQUE(x) SCOPE_GUARD_PP_CAT(x, __LINE__) 17 | #define SCOPE_GUARD_exit() \ 18 | auto SCOPE_GUARD_PP_UNIQUE(_scope_exit_local) = \ 19 | ::scope_guard_detail::scope_guard_helper() << [&] 20 | 21 | namespace scope_guard_detail { 22 | 23 | struct scope_guard_helper { 24 | template inline scope_guard operator<<(F &&f) { 25 | return scope_guard(std::forward(f)); 26 | } 27 | }; 28 | 29 | } // namespace scope_guard_detail 30 | -------------------------------------------------------------------------------- /src/util/self_path.cc: -------------------------------------------------------------------------------- 1 | #include "self_path.h" 2 | #include 3 | #include 4 | #include 5 | #ifdef _WIN32 6 | # include 7 | # include 8 | #else 9 | # include 10 | #endif 11 | 12 | static char library_data; 13 | 14 | std::string self_directory() { 15 | #ifdef _WIN32 16 | HMODULE hmodule = nullptr; 17 | if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 18 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 19 | &library_data, &hmodule)) 20 | return nullptr; 21 | 22 | char loc[MAX_PATH]; 23 | GetModuleFileNameA(hmodule, loc, sizeof(loc)); 24 | PathRemoveFileSpecA(loc); 25 | strcat(loc, "\\"); 26 | return loc; 27 | #else 28 | const char *loc = nullptr; 29 | 30 | Dl_info info {}; 31 | int ret = dladdr(&library_data, &info); 32 | if (ret != 0) 33 | loc = info.dli_fname; 34 | 35 | if (!loc) 36 | return nullptr; 37 | 38 | std::size_t dirlen = strrchr(loc, '/') - loc; 39 | return std::string(loc, dirlen); 40 | #endif 41 | } 42 | 43 | std::string self_relative(const char *path) { 44 | std::string libdir = self_directory(); 45 | return self_directory() + '/' + path; 46 | } 47 | -------------------------------------------------------------------------------- /src/util/self_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | std::string self_directory(); 5 | std::string self_relative(const char *path); 6 | -------------------------------------------------------------------------------- /src/util/unit_format.cc: -------------------------------------------------------------------------------- 1 | #include "unit_format.h" 2 | #include 3 | 4 | std::string unit_format(const char *fmt, double val) { 5 | struct unit_mapping { 6 | const char prefix; 7 | double factor; 8 | }; 9 | 10 | const unit_mapping mappings[] = { 11 | {'u', 1e-6}, 12 | {'m', 1e-3}, 13 | {0, 1}, 14 | {'k', 1e3}, 15 | {'M', 1e6}, 16 | }; 17 | const unsigned nmappings = sizeof(mappings) / sizeof(mappings[0]); 18 | 19 | unsigned index = 0; 20 | while (index + 1 < nmappings && val >= mappings[index + 1].factor) 21 | ++index; 22 | 23 | char prefix = mappings[index].prefix; 24 | float factor = mappings[index].factor; 25 | 26 | char textbuf[64]; 27 | unsigned len = std::sprintf(textbuf, fmt, val / factor); 28 | textbuf[len++] = ' '; 29 | textbuf[len++] = prefix; 30 | textbuf[len++] = 0; 31 | 32 | return std::string(textbuf, len); 33 | } 34 | -------------------------------------------------------------------------------- /src/util/unit_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | std::string unit_format(const char *fmt, double val); 5 | -------------------------------------------------------------------------------- /src/util/unix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "util/unix_fd.h" 3 | #include "util/unix_sock.h" 4 | 5 | #if !defined(_WIN32) || _WIN32_WINNT >= 0x600 6 | int poll1(SOCKET fd, int timeout, int events); 7 | #endif 8 | int select1(SOCKET readfd, SOCKET writefd, SOCKET exceptfd, struct timeval *timeout); 9 | int socksetblocking(SOCKET fd, bool block); 10 | template auto posix_retry(const F &f, A &&... args); 11 | template auto socket_retry(const F &f, A &&... args); 12 | 13 | #include "unix.tcc" 14 | -------------------------------------------------------------------------------- /src/util/unix.tcc: -------------------------------------------------------------------------------- 1 | #include "unix.h" 2 | #include 3 | #include 4 | #include 5 | #ifdef _WIN32 6 | # include 7 | #else 8 | # include 9 | #endif 10 | 11 | #if !defined(_WIN32) || _WIN32_WINNT >= 0x600 12 | inline int poll1(SOCKET fd, int timeout, int events) { 13 | #ifdef _WIN32 14 | WSAPOLLFD pfd; 15 | #else 16 | pollfd pfd; 17 | #endif 18 | pfd.fd = fd; 19 | pfd.events = events; 20 | pfd.revents = 0; 21 | #ifdef _WIN32 22 | int ret = WSAPoll(&pfd, 1, timeout); 23 | #else 24 | int ret = poll(&pfd, 1, timeout); 25 | #endif 26 | if (ret > 0) 27 | ret = pfd.revents; 28 | return ret; 29 | } 30 | #endif 31 | 32 | inline int select1(SOCKET readfd, SOCKET writefd, SOCKET exceptfd, struct timeval *timeout) { 33 | SOCKET nfds = 0; 34 | SOCKET fds[3] { readfd, writefd, exceptfd }; 35 | fd_set sets[3]; 36 | fd_set *setp[3] {}; 37 | for (unsigned i = 0; i < 3; ++i) { 38 | SOCKET fd = fds[i]; 39 | if (fd != INVALID_SOCKET) { 40 | FD_ZERO(&sets[i]); 41 | FD_SET(fd, &sets[i]); 42 | setp[i] = &sets[i]; 43 | nfds = (fd + 1 > nfds) ? (fd + 1) : nfds; 44 | } 45 | } 46 | return select(nfds, setp[0], setp[1], setp[2], timeout); 47 | } 48 | 49 | inline int socksetblocking(SOCKET fd, bool block) { 50 | #ifdef _WIN32 51 | u_long wbio = !block; 52 | bool fail = ioctlsocket(fd, FIONBIO, &wbio) == SOCKET_ERROR; 53 | #else 54 | int flags = fcntl(fd, F_GETFL); 55 | bool fail = flags == -1; 56 | if (!fail) { 57 | int newflags = block ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK); 58 | if (flags != newflags) 59 | fail = fcntl(fd, F_SETFL, newflags) == -1; 60 | } 61 | #endif 62 | return fail ? -1 : 0; 63 | } 64 | 65 | template 66 | auto posix_retry(const F &f, A &&... args) { 67 | auto ret = f(std::forward(args)...); 68 | while (ret == -1 && errno == EINTR) 69 | ret = f(std::forward(args)...); 70 | return ret; 71 | } 72 | 73 | template 74 | auto socket_retry(const F &f, A &&... args) { 75 | auto ret = f(std::forward(args)...); 76 | while (ret == -1 && errno == SOCK_ERR(EINTR)) 77 | ret = f(std::forward(args)...); 78 | return ret; 79 | } 80 | -------------------------------------------------------------------------------- /src/util/unix_fd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class unix_fd { 4 | public: 5 | constexpr unix_fd() noexcept; 6 | explicit constexpr unix_fd(int fd) noexcept; 7 | ~unix_fd(); 8 | 9 | unix_fd(const unix_fd &) = delete; 10 | unix_fd &operator=(const unix_fd &) = delete; 11 | 12 | unix_fd(unix_fd &&o) noexcept; 13 | unix_fd &operator=(unix_fd &&o) noexcept; 14 | 15 | void reset(int fd = -1) noexcept; 16 | int get() const noexcept; 17 | int operator*() const noexcept; 18 | explicit operator bool() const noexcept; 19 | 20 | private: 21 | int fd_ = -1; 22 | }; 23 | 24 | void unix_pipe(unix_fd p[2]); 25 | 26 | #include "unix_fd.tcc" 27 | -------------------------------------------------------------------------------- /src/util/unix_fd.tcc: -------------------------------------------------------------------------------- 1 | #include "unix_fd.h" 2 | #include 3 | #include 4 | #include 5 | 6 | inline constexpr unix_fd::unix_fd() noexcept { 7 | } 8 | 9 | inline unix_fd::~unix_fd() { 10 | if (fd_ != -1) 11 | close(fd_); 12 | } 13 | 14 | inline constexpr unix_fd::unix_fd(int fd) noexcept 15 | : fd_(fd) { 16 | } 17 | 18 | inline unix_fd::unix_fd(unix_fd &&o) noexcept 19 | : fd_(o.fd_) { 20 | o.fd_ = -1; 21 | } 22 | 23 | inline unix_fd &unix_fd::operator=(unix_fd &&o) noexcept { 24 | reset(o.fd_); 25 | o.fd_ = -1; 26 | return *this; 27 | } 28 | 29 | inline void unix_fd::reset(int fd) noexcept { 30 | if (fd != fd_) { 31 | if (fd_ != -1) 32 | close(fd_); 33 | fd_ = fd; 34 | } 35 | } 36 | 37 | inline int unix_fd::get() const noexcept { 38 | return fd_; 39 | } 40 | 41 | inline int unix_fd::operator*() const noexcept { 42 | return fd_; 43 | } 44 | 45 | inline unix_fd::operator bool() const noexcept { 46 | return fd_ != -1; 47 | } 48 | 49 | inline void unix_pipe(unix_fd p[2]) { 50 | int pa[2]; 51 | #ifdef _WIN32 52 | if (_pipe(pa, 64 * 1024, _O_BINARY) == -1) 53 | #else 54 | if (pipe(pa) == -1) 55 | #endif 56 | throw std::system_error(errno, std::generic_category(), "pipe"); 57 | p[0].reset(pa[0]); 58 | p[1].reset(pa[1]); 59 | } 60 | -------------------------------------------------------------------------------- /src/util/unix_sock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #ifdef _WIN32 4 | # include 5 | # include 6 | #endif 7 | 8 | #ifdef _WIN32 9 | # define PRIdSOCKET PRIdPTR 10 | # define SCNdSOCKET SCNdPTR 11 | # define SOCK_ERR(x) WSA##x 12 | #else 13 | # define PRIdSOCKET "d" 14 | # define SCNdSOCKET "d" 15 | # define SOCK_ERR(x) x 16 | typedef int SOCKET; 17 | static constexpr int INVALID_SOCKET = -1; 18 | #endif 19 | 20 | class unix_sock { 21 | public: 22 | constexpr unix_sock() noexcept; 23 | explicit constexpr unix_sock(SOCKET sock) noexcept; 24 | ~unix_sock(); 25 | 26 | unix_sock(const unix_sock &) = delete; 27 | unix_sock &operator=(const unix_sock &) = delete; 28 | 29 | unix_sock(unix_sock &&o) noexcept; 30 | unix_sock &operator=(unix_sock &&o) noexcept; 31 | 32 | void reset(SOCKET sock = INVALID_SOCKET) noexcept; 33 | SOCKET get() const noexcept; 34 | SOCKET operator*() const noexcept; 35 | explicit operator bool() const noexcept; 36 | 37 | private: 38 | SOCKET sock_ = INVALID_SOCKET; 39 | }; 40 | 41 | const std::error_category &socket_category() noexcept; 42 | int socket_errno() noexcept; 43 | 44 | void unix_socketpair(int domain, int type, int protocol, unix_sock s[2]); 45 | 46 | #include "unix_sock.tcc" 47 | -------------------------------------------------------------------------------- /src/util/unix_sock.tcc: -------------------------------------------------------------------------------- 1 | #include "unix_sock.h" 2 | #include "util/scope_guard.h" 3 | #include 4 | #include 5 | #ifdef _WIN32 6 | # include "win32_socketpair.h" 7 | #else 8 | # include 9 | #endif 10 | 11 | inline constexpr unix_sock::unix_sock() noexcept { 12 | } 13 | 14 | inline unix_sock::~unix_sock() { 15 | if (sock_ != INVALID_SOCKET) { 16 | #ifdef _WIN32 17 | closesocket(sock_); 18 | #else 19 | close(sock_); 20 | #endif 21 | } 22 | } 23 | 24 | inline constexpr unix_sock::unix_sock(SOCKET sock) noexcept 25 | : sock_(sock) { 26 | } 27 | 28 | inline unix_sock::unix_sock(unix_sock &&o) noexcept 29 | : sock_(o.sock_) { 30 | o.sock_ = INVALID_SOCKET; 31 | } 32 | 33 | inline unix_sock &unix_sock::operator=(unix_sock &&o) noexcept { 34 | reset(o.sock_); 35 | o.sock_ = INVALID_SOCKET; 36 | return *this; 37 | } 38 | 39 | inline void unix_sock::reset(SOCKET sock) noexcept { 40 | if (sock != sock_) { 41 | if (sock_ != INVALID_SOCKET) { 42 | #ifdef _WIN32 43 | closesocket(sock_); 44 | #else 45 | close(sock_); 46 | #endif 47 | } 48 | sock_ = sock; 49 | } 50 | } 51 | 52 | inline SOCKET unix_sock::get() const noexcept { 53 | return sock_; 54 | } 55 | 56 | inline SOCKET unix_sock::operator*() const noexcept { 57 | return sock_; 58 | } 59 | 60 | inline unix_sock::operator bool() const noexcept { 61 | return sock_ != INVALID_SOCKET; 62 | } 63 | 64 | #ifdef _WIN32 65 | inline const std::error_category &socket_category() noexcept { 66 | return std::system_category(); 67 | } 68 | inline int socket_errno() noexcept { 69 | return WSAGetLastError(); 70 | } 71 | #else 72 | inline const std::error_category &socket_category() noexcept { 73 | return std::generic_category(); 74 | } 75 | inline int socket_errno() noexcept { 76 | return errno; 77 | } 78 | #endif 79 | 80 | inline void unix_socketpair(int domain, int type, int protocol, unix_sock s[2]) { 81 | SOCKET sa[2]; 82 | if (socketpair(domain, type, protocol, sa) == -1) 83 | throw std::system_error(errno, socket_category(), "socketpair"); 84 | s[0].reset(sa[0]); 85 | s[1].reset(sa[1]); 86 | } 87 | -------------------------------------------------------------------------------- /src/util/win32_argv.cc: -------------------------------------------------------------------------------- 1 | #include "win32_argv.h" 2 | 3 | template 4 | std::basic_string argv_to_commandG(const C *const argv[]) { 5 | std::basic_string cmd; 6 | cmd.reserve(1024); 7 | for (const C *arg; (arg = *argv); ++argv) { 8 | if (!cmd.empty()) 9 | cmd.push_back(' '); 10 | cmd.push_back('"'); 11 | for (C c;; ++arg) { 12 | unsigned numbackslash = 0; 13 | while ((c = *arg) == '\\') { 14 | ++arg; 15 | ++numbackslash; 16 | } 17 | if (!c) { 18 | cmd.append(numbackslash * 2, '\\'); 19 | break; 20 | } 21 | if (c == '"') 22 | cmd.append(numbackslash * 2 + 1, '\\'); 23 | else 24 | cmd.append(numbackslash, '\\'); 25 | cmd.push_back(c); 26 | } 27 | cmd.push_back('"'); 28 | } 29 | return cmd; 30 | } 31 | 32 | std::string argv_to_commandA(const char *const argv[]) { 33 | return argv_to_commandG(argv); 34 | } 35 | 36 | std::wstring argv_to_commandW(const wchar_t *const argv[]) { 37 | return argv_to_commandG(argv); 38 | } 39 | 40 | std::basic_string argv_to_command(const TCHAR *const argv[]) { 41 | return argv_to_commandG(argv); 42 | } 43 | -------------------------------------------------------------------------------- /src/util/win32_argv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | std::string argv_to_commandA(const char *const argv[]); 6 | std::wstring argv_to_commandW(const wchar_t *const argv[]); 7 | std::basic_string argv_to_command(const TCHAR *const argv[]); 8 | -------------------------------------------------------------------------------- /src/util/win32_socketpair.cc: -------------------------------------------------------------------------------- 1 | #include "win32_socketpair.h" 2 | #include "util/scope_guard.h" 3 | 4 | // based on libnix's implementation by Diego Casorran 5 | 6 | #ifndef AF_LOCAL 7 | # define AF_LOCAL AF_UNIX 8 | #endif 9 | #ifndef PF_LOCAL 10 | # define PF_LOCAL PF_UNIX 11 | #endif 12 | 13 | extern "C" int socketpair(int family, int type, int protocol, SOCKET sv[2]) { 14 | SOCKET insock = INVALID_SOCKET; 15 | SOCKET outsock = INVALID_SOCKET; 16 | SOCKET newsock = INVALID_SOCKET; 17 | sockaddr_in sock_in, sock_out; 18 | int len; 19 | 20 | bool success = false; 21 | scope(exit) { 22 | if (newsock != INVALID_SOCKET) closesocket(newsock); 23 | if (!success) { 24 | if (insock != INVALID_SOCKET) closesocket(insock); 25 | if (outsock != INVALID_SOCKET) closesocket(outsock); 26 | } 27 | }; 28 | 29 | /* windowz only has AF_INET (we use that for AF_LOCAL too) */ 30 | if (family != AF_LOCAL && family != AF_INET) 31 | return -1; 32 | 33 | /* STRAM and DGRAM sockets only */ 34 | if (type != SOCK_STREAM && type != SOCK_DGRAM) 35 | return -1; 36 | 37 | /* yes, we all love windoze */ 38 | if ((family == AF_LOCAL && protocol != PF_UNSPEC && protocol != PF_LOCAL) || 39 | (family == AF_INET && protocol != PF_UNSPEC && protocol != PF_INET)) 40 | return -1; 41 | 42 | /* create the first socket */ 43 | newsock = socket(AF_INET, type, 0); 44 | if (newsock == INVALID_SOCKET) 45 | return -1; 46 | 47 | /* bind the socket to any unused port */ 48 | sock_in.sin_family = AF_INET; 49 | sock_in.sin_port = 0; 50 | sock_in.sin_addr.s_addr = INADDR_ANY; 51 | if (bind(newsock, (sockaddr *)&sock_in, sizeof(sock_in)) < 0) 52 | return -1; 53 | len = sizeof(sock_in); 54 | if (getsockname(newsock, (sockaddr *)&sock_in, &len) < 0) 55 | return -1; 56 | 57 | /* For stream sockets, create a listener */ 58 | if (type == SOCK_STREAM) 59 | listen(newsock, 2); 60 | 61 | /* create a connecting socket */ 62 | outsock = socket(AF_INET, type, 0); 63 | if (outsock == INVALID_SOCKET) 64 | return -1; 65 | 66 | /* For datagram sockets, bind the 2nd socket to an unused address, too */ 67 | if (type == SOCK_DGRAM) { 68 | sock_out.sin_family = AF_INET; 69 | sock_out.sin_port = 0; 70 | sock_out.sin_addr.s_addr = INADDR_ANY; 71 | if (bind(outsock, (sockaddr *)&sock_out, sizeof(sock_out)) < 0) 72 | return -1; 73 | len = sizeof(sock_out); 74 | if (getsockname(outsock, (sockaddr *)&sock_out, &len) < 0) 75 | return -1; 76 | } 77 | 78 | /* Force IP address to loopback */ 79 | sock_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 80 | if (type == SOCK_DGRAM) 81 | sock_out.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 82 | 83 | /* Do a connect */ 84 | if (connect(outsock, (sockaddr *)&sock_in, sizeof(sock_in)) < 0) 85 | return -1; 86 | 87 | if (type == SOCK_STREAM) { 88 | /* For stream sockets, accept the connection and close the listener */ 89 | len = sizeof(sock_in); 90 | insock = accept(newsock, (sockaddr *)&sock_in, &len); 91 | if (insock == INVALID_SOCKET) 92 | return -1; 93 | closesocket(newsock); 94 | newsock = INVALID_SOCKET; 95 | } else { 96 | /* For datagram sockets, connect the 2nd socket */ 97 | if (connect(newsock, (sockaddr *)&sock_out, sizeof(sock_out)) < 0) 98 | return -1; 99 | insock = newsock; 100 | newsock = INVALID_SOCKET; 101 | } 102 | 103 | /* set the descriptors */ 104 | sv[0] = insock; 105 | sv[1] = outsock; 106 | 107 | /* we've done it */ 108 | success = true; 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /src/util/win32_socketpair.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int socketpair(int family, int type, int protocol, SOCKET sv[2]); 9 | 10 | #ifdef __cplusplus 11 | } // extern "C" 12 | #endif 13 | -------------------------------------------------------------------------------- /src/visu~-common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | static constexpr unsigned msgmax = 512; 7 | static constexpr unsigned channelmax = 4; 8 | static constexpr unsigned sockbuf = 256 * 1024; 9 | 10 | enum MessageTag { 11 | MessageTag_SampleRate, 12 | MessageTag_Frames, 13 | MessageTag_Toggle, 14 | MessageTag_Position, 15 | MessageTag_Size, 16 | MessageTag_Border, 17 | }; 18 | 19 | struct MessageHeader { 20 | MessageTag tag; 21 | unsigned len; 22 | union { 23 | char c[0]; 24 | int32_t i[0]; 25 | uint32_t u[0]; 26 | float f[0]; 27 | }; 28 | }; 29 | 30 | enum VisuType { 31 | Visu_Waterfall, 32 | Visu_Spectrogram, 33 | Visu_Oscillogram, 34 | Visu_Transfer, 35 | Visu_Count, 36 | Visu_Default = Visu_Waterfall, 37 | }; 38 | 39 | enum class VisuDftResolution { 40 | Medium, 41 | High, 42 | }; 43 | 44 | #define PD_LAYOUT_CHECK(t) \ 45 | static_assert(std::is_standard_layout() && !offsetof(t, x_obj), \ 46 | "object layout check failed"); 47 | 48 | -------------------------------------------------------------------------------- /src/visu~-gui.cc: -------------------------------------------------------------------------------- 1 | #include "visu~-common.h" 2 | #include "gui/w_dft_waterfall.h" 3 | #include "gui/w_dft_spectrogram.h" 4 | #include "gui/w_dft_transfer.h" 5 | #include "gui/w_ts_oscillogram.h" 6 | #include "gui/s_smem.h" 7 | #include "gui/s_math.h" 8 | #include "util/unix.h" 9 | #include 10 | #include 11 | #include 12 | #ifdef USE_FFTW 13 | # include 14 | #else 15 | # include 16 | #endif 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #ifdef _WIN32 28 | # include 29 | #endif 30 | 31 | namespace { 32 | 33 | SOCKET arg_fd = INVALID_SOCKET; 34 | #ifdef _WIN32 35 | DWORD arg_ppid = -1; 36 | HANDLE hparentprocess = nullptr; 37 | #else 38 | pid_t arg_ppid = -1; 39 | #endif 40 | VisuType arg_visu = Visu_Default; 41 | std::string arg_title; 42 | 43 | std::unique_ptr recvbuf(new uint8_t[msgmax]); 44 | 45 | float samplerate = 44100; 46 | 47 | Fl_Double_Window *window = nullptr; 48 | W_Visu *visu = nullptr; 49 | 50 | constexpr double redraw_interval = 1 / 24.0; 51 | 52 | VisuDftResolution fftres = (VisuDftResolution)-1; 53 | unsigned fftsize = 2 * 1024; 54 | #ifdef USE_FFTW 55 | std::unique_ptr fftplan{ 56 | nullptr, &fftwf_destroy_plan}; 57 | #else 58 | std::unique_ptr> fftplan; 59 | std::unique_ptr ffttemp; 60 | #endif 61 | std::unique_ptr fftin; 62 | std::unique_ptr[]> fftout[channelmax]; 63 | std::unique_ptr fftwindow; 64 | 65 | constexpr float updatedelta = 10e-3f; 66 | 67 | sample_memory<> smem; 68 | float smemtime = 0; 69 | unsigned schannels = 1; 70 | 71 | bool enabled = false; 72 | bool visucandraw = false; 73 | void (*initfn)() = nullptr; 74 | void (*updatefn)() = nullptr; 75 | 76 | } // namespace 77 | 78 | static void on_redraw_timeout(void *); 79 | static void on_fd_input(FL_SOCKET, void *); 80 | 81 | /// 82 | static void enable() { 83 | if (!::enabled) { 84 | Fl::add_timeout(redraw_interval, &on_redraw_timeout); 85 | if (arg_fd != INVALID_SOCKET) 86 | Fl::add_fd(arg_fd, FL_READ, &on_fd_input); 87 | ::enabled = true; 88 | } 89 | } 90 | 91 | static void disable() { 92 | if (::enabled) { 93 | Fl::remove_timeout(&on_redraw_timeout); 94 | if (arg_fd != INVALID_SOCKET) 95 | Fl::remove_fd(arg_fd); 96 | ::enabled = false; 97 | } 98 | } 99 | 100 | static bool check_alive_parent_process() { 101 | #ifdef _WIN32 102 | if (!hparentprocess) 103 | return true; 104 | DWORD ec = 0; 105 | return GetExitCodeProcess(hparentprocess, &ec) && ec == STILL_ACTIVE; 106 | #else 107 | if (arg_ppid == -1) 108 | return true; 109 | return getppid() == arg_ppid; 110 | #endif 111 | } 112 | 113 | static int receive_from_fd(SOCKET rfd, MessageHeader **pmsg) { 114 | if (!check_alive_parent_process()) 115 | exit(1); 116 | 117 | uint8_t *recvbuf = ::recvbuf.get(); 118 | MessageHeader *msg = (MessageHeader *)recvbuf; 119 | 120 | size_t nread = socket_retry(recv, rfd, (char *)msg, msgmax, 0); 121 | if ((ssize_t)nread == -1) { 122 | if (socket_errno() == SOCK_ERR(EWOULDBLOCK)) 123 | return 0; 124 | return -1; 125 | } 126 | if (nread == 0) 127 | return 0; 128 | if (nread < sizeof(MessageHeader) || 129 | nread != sizeof(MessageHeader) + msg->len) 130 | return -1; 131 | 132 | if (pmsg) *pmsg = msg; 133 | return 1; 134 | } 135 | 136 | static bool handle_message(const MessageHeader *msg) { 137 | switch (msg->tag) { 138 | case MessageTag_SampleRate: { 139 | float sr = msg->f[0]; 140 | if (!std::isfinite(sr) || sr <= 0) 141 | exit(1); 142 | ::samplerate = sr; 143 | break; 144 | } 145 | 146 | case MessageTag_Toggle: 147 | if (window->visible()) 148 | window->hide(); 149 | else 150 | window->show(); 151 | break; 152 | 153 | case MessageTag_Position: { 154 | const float *data = &msg->f[0]; 155 | unsigned datalen = msg->len; 156 | 157 | if (datalen != 2 * sizeof(float)) 158 | exit(1); 159 | 160 | int x = (int)data[0]; 161 | int y = (int)data[1]; 162 | 163 | window->position(x, y); 164 | 165 | break; 166 | } 167 | 168 | case MessageTag_Size: { 169 | const float *data = &msg->f[0]; 170 | unsigned datalen = msg->len; 171 | 172 | if (datalen != 2 * sizeof(float)) 173 | exit(1); 174 | 175 | int x = (int)data[0]; 176 | int y = (int)data[1]; 177 | 178 | if (x > 0 && y > 0) 179 | window->size(x, y); 180 | 181 | break; 182 | } 183 | 184 | case MessageTag_Border: { 185 | if (msg->len != sizeof(int)) 186 | exit(1); 187 | 188 | window->border(msg->i[0]); 189 | 190 | break; 191 | } 192 | 193 | case MessageTag_Frames: { 194 | const float *data = &msg->f[0]; 195 | unsigned datalen = msg->len; 196 | 197 | if (datalen < sizeof(uint32_t)) 198 | exit(1); 199 | uint32_t nchannels = *(uint32_t *)data++; 200 | datalen -= sizeof(*data); 201 | if (nchannels == 0 || nchannels > channelmax) 202 | exit(1); 203 | uint32_t nframes = datalen / (nchannels * sizeof(float)); 204 | 205 | sample_memory<> &smem = ::smem; 206 | const float sr = ::samplerate; 207 | float t = ::smemtime; 208 | ::schannels = nchannels; 209 | 210 | for (unsigned i = 0; i < nframes; ++i) { 211 | smem.append(&data[i * nchannels], nchannels); 212 | t += 1 / sr; 213 | if (t >= updatedelta) { 214 | if (::enabled && ::updatefn) 215 | ::updatefn(); 216 | t -= updatedelta; 217 | } 218 | } 219 | ::smemtime = t; 220 | break; 221 | } 222 | 223 | default: 224 | return false; 225 | } 226 | return true; 227 | } 228 | 229 | static void on_fd_input(FL_SOCKET, void *) { 230 | SOCKET rfd = ::arg_fd; 231 | MessageHeader *msg = nullptr; 232 | for (int ret; (ret = receive_from_fd(rfd, &msg)) != 0;) { 233 | if (ret == -1) 234 | exit(1); 235 | if (!handle_message(msg)) 236 | exit(1); 237 | } 238 | } 239 | 240 | static void dft_prepare() { 241 | const unsigned fftsize = ::fftsize; 242 | smem.resize(fftsize); 243 | 244 | fftin.reset(new float[fftsize]()); 245 | for (unsigned c = 0; c < channelmax; ++c) 246 | fftout[c].reset(new std::complex[fftsize/2+1]()); 247 | 248 | #ifdef USE_FFTW 249 | fftplan.reset(fftwf_plan_dft_r2c_1d( 250 | fftsize, fftin.get(), (fftwf_complex *)fftout[0].get(), FFTW_MEASURE)); 251 | #else 252 | fftplan.reset(new kfr::dft_plan_real(fftsize)); 253 | ffttemp.reset(new kfr::u8[fftplan->temp_size]()); 254 | #endif 255 | 256 | fftwindow.reset(new float[fftsize]()); 257 | for (unsigned i = 0; i < fftsize; ++i) 258 | fftwindow[i] = window_nutall(i / float(fftsize-1)); 259 | } 260 | 261 | static void dft_initres(VisuDftResolution res) { 262 | if (res == ::fftres) 263 | return; 264 | 265 | ::fftres = res; 266 | switch (res) { 267 | case VisuDftResolution::Medium: 268 | ::fftsize = 2048; break; 269 | case VisuDftResolution::High: 270 | ::fftsize = 8192; break; 271 | } 272 | dft_prepare(); 273 | } 274 | 275 | static void dft_init() { 276 | dft_initres(VisuDftResolution::Medium); 277 | } 278 | 279 | static void dft_update() { 280 | W_DftVisu *dftvisu = static_cast(::visu); 281 | dft_initres(dftvisu->desired_resolution()); 282 | 283 | float *fftin = ::fftin.get(); 284 | const unsigned fftsize = ::fftsize; 285 | const unsigned nchannels = ::schannels; 286 | const frame<> *smem = ::smem.data(); 287 | 288 | for (unsigned c = 0; c < nchannels; ++c) { 289 | std::complex *fftout = ::fftout[c].get(); 290 | for (unsigned i = 0; i < fftsize; ++i) 291 | fftin[i] = fftwindow[i] * smem[i].samples[c]; 292 | #ifdef USE_FFTW 293 | fftwf_execute_dft_r2c(fftplan.get(), fftin, (fftwf_complex *)fftout); 294 | #else 295 | fftplan->execute((kfr::complex *)fftout, fftin, ::ffttemp.get()); 296 | #endif 297 | for (unsigned i = 0; i < fftsize/2+1; ++i) 298 | fftout[i] /= fftsize; 299 | } 300 | 301 | const std::complex *spec[channelmax]; 302 | for (unsigned c = 0; c < nchannels; ++c) 303 | spec[c] = ::fftout[c].get(); 304 | 305 | dftvisu->update_dft_data(spec, fftsize/2+1, samplerate, nchannels); 306 | visucandraw = true; 307 | } 308 | 309 | static void ts_init() { 310 | constexpr float sampleratemax = 192000; 311 | constexpr float timebasemax = 1.0; 312 | unsigned memsize = std::ceil(timebasemax * sampleratemax); 313 | smem.resize(memsize); 314 | } 315 | 316 | static void ts_update() { 317 | const sample_memory<> &smem = ::smem; 318 | const unsigned nchannels = ::schannels; 319 | W_TsVisu *tsvisu = static_cast(::visu); 320 | tsvisu->update_ts_data(samplerate, smem.data(), smem.size(), nchannels); 321 | visucandraw = true; 322 | } 323 | 324 | static void on_redraw_timeout(void *) { 325 | if (!check_alive_parent_process()) 326 | exit(1); 327 | 328 | if (visucandraw) { 329 | visu->redraw(); 330 | visucandraw = false; 331 | } 332 | 333 | Fl::repeat_timeout(redraw_interval, &on_redraw_timeout); 334 | } 335 | 336 | static bool handle_cmdline(int argc, char *argv[]) { 337 | const struct option opts[] = { 338 | {"fd", required_argument, nullptr, 256 + 'f'}, 339 | {"ppid", required_argument, nullptr, 256 + 'p'}, 340 | {"visu", required_argument, nullptr, 256 + 'v'}, 341 | {"title", required_argument, nullptr, 256 + 't'}, 342 | {}, 343 | }; 344 | 345 | for (int c; (c = getopt_long(argc, argv, "", opts, nullptr)) != -1;) { 346 | switch (c) { 347 | case 256 + 'f': { 348 | unsigned len = 0; 349 | if (sscanf(optarg, "%" SCNdSOCKET "%n", &::arg_fd, &len) != 1 || 350 | len != strlen(optarg)) 351 | return false; 352 | break; 353 | } 354 | case 256 + 'p': { 355 | unsigned len = 0; 356 | #ifdef _WIN32 357 | unsigned count = sscanf(optarg, "%lu%n", &::arg_ppid, &len); 358 | #else 359 | unsigned count = sscanf(optarg, "%d%n", &::arg_ppid, &len); 360 | #endif 361 | if (count != 1 || len != strlen(optarg)) 362 | return false; 363 | break; 364 | } 365 | case 256 + 'v': { 366 | unsigned len = 0; 367 | int v = 0; 368 | if (sscanf(optarg, "%d%n", &v, &len) != 1 || len != strlen(optarg)) 369 | return false; 370 | ::arg_visu = (VisuType)v; 371 | break; 372 | } 373 | case 256 + 't': { 374 | ::arg_title.assign(optarg); 375 | break; 376 | } 377 | default: 378 | return false; 379 | } 380 | } 381 | 382 | if (argc != optind) 383 | return false; 384 | 385 | return true; 386 | } 387 | 388 | class W_MainWindow : public Fl_Double_Window { 389 | public: 390 | W_MainWindow(int w, int h, const char *l = nullptr) 391 | : Fl_Double_Window(w, h, l) {} 392 | // note: superclass destructor is non-virtual 393 | int handle(int event) override; 394 | }; 395 | 396 | int W_MainWindow::handle(int event) { 397 | if (event == FL_SHOW) { 398 | enable(); 399 | } else if (event == FL_HIDE) { 400 | disable(); 401 | ::visu->reset_data(); 402 | } 403 | return Fl_Double_Window::handle(event); 404 | } 405 | 406 | int main(int argc, char *argv[]) { 407 | if (!handle_cmdline(argc, argv)) 408 | return 1; 409 | 410 | #ifdef _WIN32 411 | if (arg_ppid != (DWORD)-1) { 412 | hparentprocess = OpenProcess(PROCESS_QUERY_INFORMATION, false, arg_ppid); 413 | if (!hparentprocess) 414 | throw std::system_error(GetLastError(), std::system_category(), "OpenProcess"); 415 | } 416 | #endif 417 | 418 | #ifdef _WIN32 419 | WSADATA wsadata {}; 420 | if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) 421 | throw std::system_error(socket_errno(), socket_category(), "WSAStartup"); 422 | #endif 423 | 424 | Fl::visual(FL_RGB); 425 | 426 | int w = 1, h = 1; 427 | 428 | Fl_Double_Window *window = new W_MainWindow(w, h, ::arg_title.c_str()); 429 | ::window = window; 430 | 431 | switch (::arg_visu) { 432 | case Visu_Waterfall: { 433 | w = 1000; 434 | h = 400; 435 | window->size(w, h); 436 | ::visu = new W_DftWaterfall(0, 0, w, h); 437 | ::initfn = &dft_init; 438 | ::updatefn = &dft_update; 439 | break; 440 | } 441 | case Visu_Spectrogram: { 442 | w = 1000; 443 | h = 250; 444 | window->size(w, h); 445 | ::visu = new W_DftSpectrogram(0, 0, w, h); 446 | ::initfn = &dft_init; 447 | ::updatefn = &dft_update; 448 | break; 449 | } 450 | case Visu_Oscillogram: { 451 | w = 1000; 452 | h = 300; 453 | window->size(w, h); 454 | ::visu = new W_TsOscillogram(0, 0, w, h); 455 | ::initfn = &ts_init; 456 | ::updatefn = &ts_update; 457 | break; 458 | } 459 | case Visu_Transfer: { 460 | w = 1000; 461 | h = 400; 462 | window->size(w, h); 463 | ::visu = new W_DftTransfer(0, 0, w, h); 464 | ::initfn = &dft_init; 465 | ::updatefn = &dft_update; 466 | break; 467 | } 468 | default: 469 | return 1; 470 | } 471 | 472 | window->resizable(::visu); 473 | window->size_range(w / 10, h / 10); 474 | 475 | if (::initfn) 476 | ::initfn(); 477 | 478 | window->end(); 479 | 480 | window->show(); 481 | window->wait_for_expose(); 482 | 483 | if (arg_fd != INVALID_SOCKET) { 484 | // ready 485 | if (socket_retry(send, arg_fd, "!", 1, 0) == -1) 486 | throw std::system_error(socket_errno(), socket_category(), "send"); 487 | if (socksetblocking(arg_fd, false) == -1) 488 | throw std::system_error(socket_errno(), socket_category(), "socksetblocking"); 489 | } 490 | 491 | return Fl::run(); 492 | } 493 | -------------------------------------------------------------------------------- /src/visu~-remote.cc: -------------------------------------------------------------------------------- 1 | #include "visu~-remote.h" 2 | #include "visu~-common.h" 3 | #include "util/scope_guard.h" 4 | #include "util/unix.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #ifdef _WIN32 15 | # include "util/win32_argv.h" 16 | # include 17 | #else 18 | # include 19 | # include 20 | # include 21 | #endif 22 | 23 | extern "C" { 24 | extern char **environ; 25 | } 26 | 27 | struct RemoteVisu::Impl { 28 | #ifdef _WIN32 29 | HANDLE hprocess = nullptr; 30 | #else 31 | pid_t pid = -1; 32 | #endif 33 | unix_sock sock; 34 | std::unique_ptr msg{nullptr, &::free}; 35 | bool send_message(const MessageHeader *m); 36 | 37 | std::mutex sock_mutex; 38 | SOCKET acquire_socket(std::unique_lock &lock); 39 | SOCKET try_acquire_socket(std::unique_lock &lock); 40 | }; 41 | 42 | RemoteVisu::RemoteVisu() 43 | : P(new Impl) { 44 | P->msg.reset((MessageHeader *)malloc(msgmax)); 45 | if (!P->msg) 46 | throw std::bad_alloc(); 47 | } 48 | 49 | RemoteVisu::~RemoteVisu() { 50 | stop(); 51 | } 52 | 53 | bool RemoteVisu::is_running() const { 54 | #ifdef _WIN32 55 | HANDLE hprocess = P->hprocess; 56 | if (!hprocess) 57 | return false; 58 | DWORD exitcode = 0; 59 | if (!(GetExitCodeProcess(hprocess, &exitcode) && exitcode == STILL_ACTIVE)) 60 | return false; 61 | #else 62 | pid_t pid = P->pid; 63 | if (pid == -1) 64 | return false; 65 | int wstate = 0; 66 | if (posix_retry(waitpid, pid, &wstate, WNOHANG) > 0 && 67 | (WIFEXITED(wstate) || WIFSIGNALED(wstate))) { 68 | P->pid = -1; 69 | return false; 70 | } 71 | #endif 72 | return true; 73 | } 74 | 75 | void RemoteVisu::start(const char *pgm, VisuType type, const char *title) { 76 | if (is_running()) 77 | return; 78 | 79 | std::unique_lock lock; 80 | P->acquire_socket(lock); 81 | 82 | bool success = false; 83 | 84 | unix_sock sockpair[2]; 85 | #if !defined(__APPLE__) 86 | unix_socketpair(AF_UNIX, SOCK_DGRAM, PF_UNIX, sockpair); 87 | #else 88 | unix_socketpair(AF_UNIX, SOCK_DGRAM, PF_UNSPEC, sockpair); 89 | #endif 90 | 91 | SOCKET rfd = sockpair[0].get(); 92 | SOCKET wfd = sockpair[1].get(); 93 | 94 | if (socksetblocking(wfd, false) == -1) 95 | throw std::system_error(socket_errno(), socket_category(), "socksetblocking"); 96 | 97 | #ifndef _WIN32 98 | int wfdflags = fcntl(wfd, F_GETFD); 99 | if (wfdflags == -1 || fcntl(wfd, F_SETFD, wfdflags|FD_CLOEXEC) == -1) 100 | throw std::system_error(socket_errno(), socket_category(), "fcntl"); 101 | #endif 102 | if (setsockopt( 103 | wfd, SOL_SOCKET, SO_SNDBUF, (const char *)&sockbuf, sizeof(sockbuf)) == -1) 104 | throw std::system_error(socket_errno(), socket_category(), "setsockopt"); 105 | 106 | char rfd_str[16]; 107 | sprintf(rfd_str, "%" PRIdSOCKET, rfd); 108 | 109 | #ifdef _WIN32 110 | char ppid_str[24]; 111 | sprintf(ppid_str, "%lu", GetCurrentProcessId()); 112 | #else 113 | char ppid_str[16]; 114 | sprintf(ppid_str, "%d", getpid()); 115 | #endif 116 | 117 | char visu_str[16]; 118 | sprintf(visu_str, "%d", (int)type); 119 | 120 | char *ps_argv[16]; 121 | unsigned ps_argc = 0; 122 | ps_argv[ps_argc++] = (char *)pgm; 123 | ps_argv[ps_argc++] = (char *)"--fd"; 124 | ps_argv[ps_argc++] = rfd_str; 125 | ps_argv[ps_argc++] = (char *)"--ppid"; 126 | ps_argv[ps_argc++] = ppid_str; 127 | ps_argv[ps_argc++] = (char *)"--visu"; 128 | ps_argv[ps_argc++] = visu_str; 129 | if (title) { 130 | ps_argv[ps_argc++] = (char *)"--title"; 131 | ps_argv[ps_argc++] = (char *)title; 132 | } 133 | ps_argv[ps_argc++] = nullptr; 134 | 135 | /// 136 | #ifdef _WIN32 137 | STARTUPINFOA sinfo {}; 138 | sinfo.cb = sizeof(sinfo); 139 | sinfo.dwFlags = STARTF_USESTDHANDLES; 140 | sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 141 | sinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 142 | sinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 143 | PROCESS_INFORMATION pinfo; 144 | 145 | char *cmdline = strdup(argv_to_commandA(ps_argv).c_str()); 146 | if (!cmdline) 147 | throw std::bad_alloc(); 148 | scope(exit) { free(cmdline); }; 149 | 150 | if (!CreateProcessA( 151 | nullptr, cmdline, 152 | nullptr, nullptr, 153 | true, CREATE_NO_WINDOW, 154 | nullptr, nullptr, 155 | &sinfo, &pinfo)) 156 | throw std::system_error(GetLastError(), std::system_category(), "CreateProcess"); 157 | 158 | HANDLE hprocess = pinfo.hProcess; 159 | scope(exit) { if (hprocess) CloseHandle(hprocess); }; 160 | CloseHandle(pinfo.hThread); 161 | #else 162 | posix_spawn_file_actions_t ps_fact; 163 | if (posix_spawn_file_actions_init(&ps_fact) == -1) 164 | throw std::bad_alloc(); 165 | scope(exit) { posix_spawn_file_actions_destroy(&ps_fact); }; 166 | /// 167 | posix_spawnattr_t ps_attr; 168 | if (posix_spawnattr_init(&ps_attr) == -1) 169 | throw std::bad_alloc(); 170 | scope(exit) { posix_spawnattr_destroy(&ps_attr); }; 171 | 172 | /// 173 | pid_t pid; 174 | int ps_err = posix_spawn(&pid, pgm, &ps_fact, &ps_attr, ps_argv, environ); 175 | if (ps_err != 0) 176 | throw std::system_error(ps_err, std::generic_category(), "posix_spawn"); 177 | #endif 178 | 179 | scope(exit) { 180 | if (!success) { 181 | #ifdef _WIN32 182 | TerminateProcess(hprocess, 1); 183 | #else 184 | kill(pid, SIGTERM); 185 | posix_retry(waitpid, pid, nullptr, 0); 186 | #endif 187 | } 188 | }; 189 | 190 | // wait until ready to prevent startup hiccups 191 | bool ready = false; 192 | const unsigned ready_timeout = 10; 193 | typedef std::chrono::steady_clock clock; 194 | clock::time_point t1 = clock::now(); 195 | while (!ready) { 196 | timeval tv { ready_timeout, 0 }; 197 | socket_retry(select1, wfd, INVALID_SOCKET, INVALID_SOCKET, &tv); 198 | char bytebuf {}; 199 | size_t n = socket_retry(recv, wfd, &bytebuf, 1, 0); 200 | if ((ssize_t)n == -1) { 201 | int err = socket_errno(); 202 | if (err != SOCK_ERR(EWOULDBLOCK)) 203 | throw std::system_error(err, socket_category(), "recv"); 204 | } else if (n == 1) { 205 | if (bytebuf != '!') 206 | throw std::runtime_error("error in communication protocol"); 207 | ready = true; 208 | } 209 | if (clock::now() - t1 > std::chrono::seconds(ready_timeout)) 210 | break; 211 | } 212 | 213 | if (!ready) 214 | throw std::runtime_error("timeout waiting for message from child process"); 215 | 216 | #ifdef _WIN32 217 | P->hprocess = hprocess; 218 | hprocess = nullptr; 219 | #else 220 | P->pid = pid; 221 | #endif 222 | P->sock = std::move(sockpair[1]); 223 | success = true; 224 | } 225 | 226 | void RemoteVisu::stop() { 227 | #ifdef _WIN32 228 | HANDLE hprocess = P->hprocess; 229 | if (!hprocess) 230 | return; 231 | #else 232 | pid_t pid = P->pid; 233 | if (pid == -1) 234 | return; 235 | #endif 236 | 237 | { std::unique_lock lock; 238 | P->acquire_socket(lock); 239 | P->sock.reset(); } 240 | 241 | #ifdef _WIN32 242 | TerminateProcess(hprocess, 1); 243 | WaitForSingleObject(hprocess, INFINITE); 244 | CloseHandle(hprocess); 245 | P->hprocess = nullptr; 246 | #else 247 | kill(pid, SIGTERM); 248 | waitpid(pid, nullptr, 0); 249 | P->pid = -1; 250 | #endif 251 | } 252 | 253 | bool RemoteVisu::toggle_visibility() { 254 | MessageHeader *msg = P->msg.get(); 255 | msg->tag = MessageTag_Toggle; 256 | msg->len = 0; 257 | return P->send_message(msg); 258 | } 259 | 260 | bool RemoteVisu::set_position(float x, float y) 261 | { 262 | MessageHeader *msg = P->msg.get(); 263 | msg->tag = MessageTag_Position; 264 | msg->len = 2 * sizeof(float); 265 | msg->f[0] = x; 266 | msg->f[1] = y; 267 | return P->send_message(msg); 268 | } 269 | 270 | bool RemoteVisu::set_size(float w, float h) 271 | { 272 | MessageHeader *msg = P->msg.get(); 273 | msg->tag = MessageTag_Size; 274 | msg->len = 2 * sizeof(float); 275 | msg->f[0] = w; 276 | msg->f[1] = h; 277 | return P->send_message(msg); 278 | } 279 | 280 | bool RemoteVisu::set_border(bool b) 281 | { 282 | MessageHeader *msg = P->msg.get(); 283 | msg->tag = MessageTag_Border; 284 | msg->len = sizeof(int); 285 | msg->i[0] = b; 286 | return P->send_message(msg); 287 | } 288 | 289 | bool RemoteVisu::send_frames( 290 | float fs, const float *data[], unsigned nframes, unsigned nchannels) { 291 | MessageHeader *msg = P->msg.get(); 292 | msg->tag = MessageTag_SampleRate; 293 | msg->len = sizeof(float); 294 | msg->f[0] = fs; 295 | if (!P->send_message(msg)) 296 | return false; 297 | 298 | constexpr unsigned maxdatalen = msgmax - sizeof(MessageHeader); 299 | constexpr unsigned headerlen = sizeof(uint32_t); 300 | constexpr unsigned maxsamples = (maxdatalen - headerlen) / sizeof(float); 301 | const unsigned maxframes = maxsamples / nchannels; 302 | 303 | msg->tag = MessageTag_Frames; 304 | for (unsigned i = 0; i < nframes;) { 305 | unsigned nsend = std::min(nframes - i, maxframes); 306 | msg->len = headerlen + nsend * nchannels * sizeof(float); 307 | float *buf = &msg->f[0]; 308 | // header 309 | *(uint32_t *)buf++ = nchannels; 310 | // frames 311 | for (unsigned j = i + nsend; i < j; ++i) { 312 | for (unsigned c = 0; c < nchannels; ++c) 313 | *buf++ = data[c][i]; 314 | } 315 | if (!P->send_message(msg)) 316 | return false; 317 | } 318 | 319 | return true; 320 | } 321 | 322 | bool RemoteVisu::Impl::send_message(const MessageHeader *m) { 323 | std::unique_lock lock; 324 | SOCKET wfd = this->try_acquire_socket(lock); 325 | if (wfd == INVALID_SOCKET) 326 | return -1; 327 | return socket_retry( 328 | send, wfd, (const char *)m, sizeof(MessageHeader) + m->len, 0) != -1; 329 | } 330 | 331 | SOCKET RemoteVisu::Impl::acquire_socket(std::unique_lock &lock) { 332 | lock = std::unique_lock(this->sock_mutex); 333 | return this->sock.get(); 334 | } 335 | 336 | SOCKET RemoteVisu::Impl::try_acquire_socket(std::unique_lock &lock) { 337 | lock = std::unique_lock(this->sock_mutex, std::try_to_lock); 338 | if (!lock.owns_lock()) 339 | return INVALID_SOCKET; 340 | return this->sock.get(); 341 | } 342 | -------------------------------------------------------------------------------- /src/visu~-remote.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~-common.h" 3 | #include 4 | 5 | class RemoteVisu { 6 | public: 7 | RemoteVisu(); 8 | ~RemoteVisu(); 9 | 10 | bool is_running() const; 11 | 12 | void start( 13 | const char *pgm, VisuType type = Visu_Default, 14 | const char *title = nullptr); 15 | void stop(); 16 | 17 | bool toggle_visibility(); 18 | bool set_position(float x, float y); 19 | bool set_size(float w, float h); 20 | bool set_border(bool b); 21 | bool send_frames( 22 | float fs, const float *data[], unsigned nframes, unsigned nchannels); 23 | 24 | private: 25 | struct Impl; 26 | std::unique_ptr P; 27 | }; 28 | -------------------------------------------------------------------------------- /src/visu~.cc: -------------------------------------------------------------------------------- 1 | #include "visu~.h" 2 | #include "visu~-remote.h" 3 | #include "util/self_path.h" 4 | 5 | t_visu::t_visu() { 6 | } 7 | 8 | t_visu::~t_visu() { 9 | if (this->x_commander.joinable()) { 10 | SOCKET fd = this->x_comm[1].get(); 11 | char msg = 0; 12 | for (size_t n; (n = socket_retry(send, fd, &msg, 1, 0)) == 0 || 13 | ((ssize_t)n == -1 && socket_errno() == SOCK_ERR(EWOULDBLOCK));) 14 | select1(INVALID_SOCKET, fd, INVALID_SOCKET, nullptr); 15 | try { this->x_commander.join(); } 16 | catch (std::system_error &) { /* must not throw in destructor */ } 17 | } 18 | } 19 | 20 | void visu_init(t_visu *x, VisuType t) { 21 | x->x_visutype = t; 22 | for (unsigned i = 1, n = x->x_channels; i < n; ++i) 23 | inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("signal"), gensym("signal")); 24 | x->x_out_ready = outlet_new(&x->x_obj, gensym("bang")); 25 | x->x_remote.reset(new RemoteVisu); 26 | unix_socketpair(AF_UNIX, SOCK_DGRAM, 0, x->x_comm); 27 | if (socksetblocking(x->x_comm[1].get(), false) == -1) 28 | throw std::system_error(socket_errno(), socket_category(), "socksetblocking"); 29 | x->x_commander = std::thread(&t_visu::commander_thread_routine, x); 30 | } 31 | 32 | void visu_free(t_visu *x) { 33 | if (x->x_cleanup) 34 | x->x_cleanup(x); 35 | } 36 | 37 | void visu_bang(t_visu *x) { 38 | SOCKET fd = x->x_comm[1].get(); 39 | char msg = 1; 40 | send(fd, &msg, 1, 0); 41 | } 42 | 43 | void visu_dsp(t_visu *x, t_signal **sp) { 44 | t_int elts[2 + channelmax]; 45 | t_int *eltp = elts; 46 | *eltp++ = (t_int)x; 47 | for (unsigned i = 0, n = x->x_channels; i < n; ++i) 48 | *eltp++ = (t_int)sp[i]->s_vec; 49 | *eltp++ = sp[0]->s_n; 50 | dsp_addv(visu_perform, eltp - elts, elts); 51 | } 52 | 53 | t_int *visu_perform(t_int *w) { 54 | ++w; 55 | t_visu *x = (t_visu *)(*w++); 56 | unsigned channels = x->x_channels; 57 | const t_sample *in[channelmax]; 58 | for (unsigned c = 0; c < channels; ++c) 59 | in[c] = (t_sample *)(*w++); 60 | unsigned n = (uintptr_t)(*w++); 61 | 62 | RemoteVisu &remote = *x->x_remote; 63 | bool sendok = remote.send_frames(sys_getsr(), in, n, channels); 64 | 65 | #if 0 // not RT safe 66 | if (!sendok && remote.is_running()) 67 | error("error writing to socket, is buffer full?"); 68 | #else 69 | (void)sendok; 70 | #endif 71 | 72 | if (x->x_ready.exchange(false)) 73 | outlet_bang(x->x_out_ready); 74 | 75 | return w; 76 | } 77 | 78 | /// 79 | void visu_position(t_visu *x, t_float xpos, t_float ypos) { 80 | RemoteVisu &remote = *x->x_remote; 81 | remote.set_position(xpos, ypos); 82 | } 83 | 84 | void visu_size(t_visu *x, t_float w, t_float h) { 85 | RemoteVisu &remote = *x->x_remote; 86 | remote.set_size(w, h); 87 | } 88 | 89 | void visu_border(t_visu *x, t_float b) { 90 | RemoteVisu &remote = *x->x_remote; 91 | remote.set_border(b != 0); 92 | } 93 | 94 | /// 95 | void t_visu::commander_thread_routine() { 96 | SOCKET fd = this->x_comm[0].get(); 97 | RemoteVisu &remote = *this->x_remote; 98 | 99 | for (;;) { 100 | char msg {}; 101 | size_t n = socket_retry(recv, fd, &msg, 1, 0); 102 | 103 | if ((ssize_t)n == -1) 104 | throw std::system_error(socket_errno(), socket_category(), "recv"); 105 | 106 | if (n != 1) 107 | throw std::runtime_error("error in communication protocol"); 108 | 109 | if (msg == 0) { // quit 110 | remote.stop(); 111 | break; 112 | } 113 | 114 | if (msg == 1) { // bang 115 | if (remote.is_running()) { 116 | remote.toggle_visibility(); 117 | } else { 118 | std::string pgm = self_relative("visu~-gui"); 119 | remote.start(pgm.c_str(), this->x_visutype, this->x_title.c_str()); 120 | this->x_ready.store(true); 121 | } 122 | } 123 | } 124 | } 125 | 126 | #ifdef _WIN32 127 | # include 128 | # include 129 | 130 | static bool wsainit = false; 131 | 132 | extern "C" __declspec(dllexport) 133 | BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID) { 134 | if (dwReason == DLL_PROCESS_ATTACH) { 135 | WSADATA wsadata {}; 136 | if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) 137 | return false; 138 | wsainit = true; 139 | } 140 | 141 | if (dwReason == DLL_PROCESS_DETACH) { 142 | if (wsainit) 143 | WSACleanup(); 144 | } 145 | 146 | return true; 147 | } 148 | #endif 149 | -------------------------------------------------------------------------------- /src/visu~.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~-common.h" 3 | #include "util/unix.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /// 11 | class RemoteVisu; 12 | 13 | /// 14 | struct t_visu { 15 | t_visu(); 16 | ~t_visu(); 17 | t_object x_obj; 18 | float x_signalin = 0; 19 | VisuType x_visutype = Visu_Default; 20 | std::string x_title; 21 | unsigned x_channels = 2; 22 | std::unique_ptr x_remote; 23 | std::thread x_commander; 24 | unix_sock x_comm[2]; 25 | std::atomic_bool x_ready{false}; 26 | t_outlet *x_out_ready = nullptr; 27 | void (*x_cleanup)(t_visu *) = nullptr; 28 | void commander_thread_routine(); 29 | }; 30 | 31 | void visu_init(t_visu *x, VisuType t); 32 | void visu_free(t_visu *x); 33 | void visu_bang(t_visu *x); 34 | void visu_dsp(t_visu *x, t_signal **sp); 35 | t_int *visu_perform(t_int *w); 36 | 37 | /// 38 | void visu_position(t_visu *x, t_float xpos, t_float ypos); 39 | void visu_size(t_visu *x, t_float w, t_float h); 40 | void visu_border(t_visu *x, t_float b); 41 | 42 | /// 43 | template 44 | void visu_setup_generic_methods(t_class *c) 45 | { 46 | class_addmethod( 47 | c, (t_method)+[](t_visu *x, t_float xpos, t_float ypos) { visu_position(x, xpos, ypos); }, 48 | gensym("position"), A_FLOAT, A_FLOAT, A_NULL); 49 | class_addmethod( 50 | c, (t_method)+[](t_visu *x, t_float w, t_float h) { visu_size(x, w, h); }, 51 | gensym("size"), A_FLOAT, A_FLOAT, A_NULL); 52 | class_addmethod( 53 | c, (t_method)+[](t_visu *x, t_float b) { visu_border(x, b); }, 54 | gensym("border"), A_FLOAT, A_NULL); 55 | } 56 | -------------------------------------------------------------------------------- /src/wfvisu~.cc: -------------------------------------------------------------------------------- 1 | #include "wfvisu~.h" 2 | #include "util/scope_guard.h" 3 | 4 | PD_LAYOUT_CHECK(t_wfvisu); 5 | 6 | static t_class *wfvisu_class; 7 | 8 | void wfvisu_tilde_setup() { 9 | wfvisu_class = class_new( 10 | gensym("wfvisu~"), 11 | (t_newmethod)&wfvisu_new, (t_method)&wfvisu_free, sizeof(t_wfvisu), 12 | CLASS_DEFAULT, A_GIMME, A_NULL); 13 | CLASS_MAINSIGNALIN( 14 | wfvisu_class, t_wfvisu, x_signalin); 15 | class_addbang( 16 | wfvisu_class, &wfvisu_bang); 17 | class_addmethod( 18 | wfvisu_class, (t_method)&wfvisu_dsp, gensym("dsp"), A_CANT, A_NULL); 19 | visu_setup_generic_methods(wfvisu_class); 20 | } 21 | 22 | void *wfvisu_new(t_symbol *s, int argc, t_atom *argv) { 23 | t_wfvisu *x = (t_wfvisu *)pd_new(wfvisu_class); 24 | if (!x) 25 | return nullptr; 26 | x->x_cleanup = nullptr; 27 | 28 | bool success = false; 29 | scope(exit) { if (!success) pd_free((t_pd *)x); }; 30 | 31 | /// 32 | try { 33 | new (x) t_wfvisu; 34 | x->x_cleanup = [](t_visu *b) { 35 | static_cast(b)->~t_wfvisu(); }; 36 | } catch (std::exception &ex) { 37 | error("%s", ex.what()); 38 | return nullptr; 39 | } 40 | 41 | /// 42 | try { 43 | if (!wfvisu_opts(x, argc, argv)) 44 | return nullptr; 45 | visu_init(x, Visu_Waterfall); 46 | } catch (std::exception &ex) { 47 | error("%s", ex.what()); 48 | return nullptr; 49 | } 50 | 51 | success = true; 52 | return x; 53 | } 54 | 55 | void wfvisu_free(t_wfvisu *x) { 56 | visu_free(x); 57 | } 58 | 59 | bool wfvisu_opts(t_wfvisu *x, int argc, t_atom *argv) { 60 | std::string title; 61 | unsigned channels = 2; 62 | switch (argc) { 63 | case 2: { 64 | channels = (int)atom_getfloat(&argv[1]); 65 | if ((int)channels <= 0 || channels > channelmax) 66 | return false; 67 | } 68 | case 1: { 69 | char buf[128]; 70 | atom_string(&argv[0], buf, sizeof(buf)); 71 | title.assign(buf); 72 | } 73 | case 0: 74 | break; 75 | default: 76 | return false; 77 | } 78 | x->x_title = std::move(title); 79 | x->x_channels = channels; 80 | return true; 81 | } 82 | 83 | void wfvisu_bang(t_wfvisu *x) { 84 | visu_bang(x); 85 | } 86 | 87 | void wfvisu_dsp(t_wfvisu *x, t_signal **sp) { 88 | visu_dsp(x, sp); 89 | } 90 | -------------------------------------------------------------------------------- /src/wfvisu~.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "visu~.h" 3 | 4 | #ifdef WIN32 5 | # define EXPORT __declspec(dllexport) 6 | #else 7 | # define EXPORT [[gnu::visibility("default")]] 8 | #endif 9 | 10 | extern "C" { 11 | EXPORT void wfvisu_tilde_setup(); 12 | } 13 | 14 | /// 15 | struct t_wfvisu : t_visu {}; 16 | 17 | void *wfvisu_new(t_symbol *s, int argc, t_atom *argv); 18 | void wfvisu_free(t_wfvisu *x); 19 | bool wfvisu_opts(t_wfvisu *x, int argc, t_atom *argv); 20 | void wfvisu_bang(t_wfvisu *x); 21 | void wfvisu_dsp(t_wfvisu *x, t_signal **sp); 22 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Fl_Knob/Fl_Knob.H: -------------------------------------------------------------------------------- 1 | // generated by Fast Light User Interface Designer (fluid) version 1.0304 2 | 3 | #ifndef Fl_Knob_H 4 | #define Fl_Knob_H 5 | #include 6 | #include 7 | 8 | class Fl_Knob : public Fl_Valuator { 9 | public: 10 | enum Fl_Knobtype {DOTLIN=0,DOTLOG_1,DOTLOG_2,DOTLOG_3,LINELIN,LINELOG_1,LINELOG_2,LINELOG_3}; 11 | private: 12 | int _type; 13 | float _percent; 14 | int _scaleticks; 15 | short a1,a2; 16 | public: 17 | Fl_Knob(int xx,int yy,int ww,int hh,const char *l); 18 | virtual ~Fl_Knob() {}; 19 | protected: 20 | void draw(); 21 | int handle(int event); 22 | public: 23 | void type(int ty); 24 | private: 25 | void shadow(const int offs,const uchar r,uchar g,uchar b); 26 | void draw_scale(const int ox,const int oy,const int side); 27 | void draw_cursor(const int ox,const int oy,const int side); 28 | public: 29 | void cursor(const int pc); 30 | void scaleticks(const int tck); 31 | }; 32 | #endif 33 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Fl_Knob/Fl_Knob.cxx: -------------------------------------------------------------------------------- 1 | // generated by Fast Light User Interface Designer (fluid) version 1.0304 2 | 3 | #include "Fl_Knob.H" 4 | #include 5 | #include 6 | 7 | Fl_Knob::Fl_Knob(int xx,int yy,int ww,int hh,const char *l): Fl_Valuator(xx,yy,ww,hh,l) { 8 | a1 = 35; 9 | a2 = 325; 10 | _type = DOTLIN; 11 | _percent = 0.3; 12 | _scaleticks = 10; 13 | } 14 | 15 | void Fl_Knob::draw() { 16 | int ox,oy,ww,hh,side; 17 | unsigned char rr,gg,bb; 18 | 19 | ox = x(); 20 | oy = y(); 21 | ww = w(); 22 | hh = h(); 23 | draw_label(); 24 | fl_clip(ox,oy,ww,hh); 25 | if (ww > hh) 26 | { 27 | side = hh; 28 | ox = ox + (ww - side) / 2; 29 | } 30 | else 31 | { 32 | side = ww; 33 | oy = oy + (hh - side) / 2; 34 | } 35 | side = w() > h () ? hh:ww; 36 | int dam = damage(); 37 | if (dam & FL_DAMAGE_ALL) 38 | { 39 | int col = parent()->color(); 40 | fl_color(col); 41 | fl_rectf(ox,oy,side,side); 42 | Fl::get_color((Fl_Color)col,rr,gg,bb); 43 | shadow(-60,rr,gg,bb); 44 | fl_pie(ox+9,oy+9,side-12,side-12,0,360); 45 | draw_scale(ox,oy,side); 46 | col = color(); 47 | Fl::get_color((Fl_Color)col,rr,gg,bb); 48 | 49 | shadow(7,rr,gg,bb); 50 | fl_pie(ox+6,oy+6,side-12,side-12,40,50); 51 | fl_pie(ox+6,oy+6,side-12,side-12,260,270); 52 | 53 | shadow(15,rr,gg,bb); 54 | fl_pie(ox+6,oy+6,side-12,side-12,50,70); 55 | fl_pie(ox+6,oy+6,side-12,side-12,230,260); 56 | 57 | shadow(25,rr,gg,bb); 58 | fl_pie(ox+6,oy+6,side-12,side-12,70,80); 59 | fl_pie(ox+6,oy+6,side-12,side-12,220,230); 60 | 61 | shadow(30,rr,gg,bb); 62 | fl_pie(ox+6,oy+6,side-12,side-12,80,220); 63 | 64 | shadow(-7,rr,gg,bb); 65 | fl_pie(ox+6,oy+6,side-12,side-12,30,40); 66 | fl_pie(ox+6,oy+6,side-12,side-12,270,280); 67 | 68 | shadow(-15,rr,gg,bb); 69 | fl_pie(ox+6,oy+6,side-12,side-12,280,400); 70 | shadow(-25,rr,gg,bb); 71 | fl_pie(ox+6,oy+6,side-12,side-12,290,390); 72 | 73 | 74 | fl_color(FL_BLACK); 75 | fl_arc(ox+6,oy+6,side-11,side-11,0,360); 76 | fl_color(col); 77 | fl_pie(ox+10,oy+10,side-20,side-20,0,360); 78 | } 79 | else 80 | { 81 | fl_color(color()); 82 | fl_pie(ox+10,oy+10,side-20,side-20,0,360); 83 | } 84 | Fl::get_color((Fl_Color)color(),rr,gg,bb); 85 | shadow(10,rr,gg,bb); 86 | fl_pie(ox+10,oy+10,side-20,side-20,110,150); 87 | fl_pie(ox+10,oy+10,side-20,side-20,290,330); 88 | shadow(17,rr,gg,bb); 89 | fl_pie(ox+10,oy+10,side-20,side-20,120,140); 90 | fl_pie(ox+10,oy+10,side-20,side-20,300,320); 91 | shadow(25,rr,gg,bb); 92 | fl_pie(ox+10,oy+10,side-20,side-20,127,133); 93 | fl_pie(ox+10,oy+10,side-20,side-20,307,313); 94 | draw_cursor(ox,oy,side); 95 | fl_pop_clip(); 96 | } 97 | 98 | int Fl_Knob::handle(int event) { 99 | int ox,oy,ww,hh; 100 | 101 | ox = x() + 10; oy = y() + 10; 102 | ww = w() - 20; 103 | hh = h()-20; 104 | switch (event) 105 | { 106 | case FL_PUSH: 107 | handle_push(); 108 | case FL_DRAG: 109 | { 110 | int mx = Fl::event_x()-ox-ww/2; 111 | int my = Fl::event_y()-oy-hh/2; 112 | if (!mx && !my) return 1; 113 | double angle = 270-atan2((float)-my, (float)mx)*180/M_PI; 114 | double oldangle = (a2-a1)*(value()-minimum())/(maximum()-minimum()) + a1; 115 | while (angle < oldangle-180) angle += 360; 116 | while (angle > oldangle+180) angle -= 360; 117 | double val; 118 | if ((a1= a1)) 119 | { 120 | val = minimum(); 121 | } 122 | else 123 | if ((a1= a2) : (angle <= a2)) 124 | { 125 | val = maximum(); 126 | } 127 | else 128 | { 129 | val = minimum() + (maximum()-minimum())*(angle-a1)/(a2-a1); 130 | } 131 | handle_drag(clamp(round(val))); 132 | } 133 | return 1; 134 | case FL_RELEASE: 135 | handle_release(); 136 | return 1; 137 | default: 138 | return 0; 139 | } 140 | return 0; 141 | } 142 | 143 | void Fl_Knob::type(int ty) { 144 | _type = ty; 145 | } 146 | 147 | void Fl_Knob::shadow(const int offs,const uchar r,uchar g,uchar b) { 148 | int rr,gg,bb; 149 | 150 | rr = r + offs; 151 | rr = rr > 255 ? 255:rr; 152 | rr = rr < 0 ? 0:rr; 153 | gg = g + offs; 154 | gg = gg > 255 ? 255:gg; 155 | gg = gg < 0 ? 0:gg; 156 | bb = b + offs; 157 | bb = bb > 255 ? 255:bb; 158 | bb = bb < 0 ? 0:bb; 159 | fl_color((uchar)rr,(uchar)gg,(uchar)bb); 160 | } 161 | 162 | void Fl_Knob::draw_scale(const int ox,const int oy,const int side) { 163 | float x1,y1,x2,y2,rds,cx,cy,ca,sa; 164 | 165 | rds = side / 2; 166 | cx = ox + side / 2; 167 | cy = oy + side / 2; 168 | if (!(_type & DOTLOG_3)) 169 | { 170 | if (_scaleticks == 0) return; 171 | double a_step = (10.0*3.14159/6.0) / _scaleticks; 172 | double a_orig = -(3.14159/3.0); 173 | for (int a = 0; a <= _scaleticks; a++) 174 | { 175 | double na = a_orig + a * a_step; 176 | ca = cos(na); 177 | sa = sin(na); 178 | x1 = cx + rds * ca; 179 | y1 = cy - rds * sa; 180 | x2 = cx + (rds-6) * ca; 181 | y2 = cy - (rds-6) * sa; 182 | fl_color(FL_BLACK); 183 | fl_line(x1,y1,x2,y2); 184 | fl_color(FL_WHITE); 185 | if (sa*ca >=0) 186 | fl_line(x1+1,y1+1,x2+1,y2+1); 187 | else 188 | fl_line(x1+1,y1-1,x2+1,y2-1); 189 | } 190 | } 191 | else 192 | { 193 | int nb_dec = (_type & DOTLOG_3); 194 | for (int k = 0; k < nb_dec; k++) 195 | { 196 | double a_step = (10.0*3.14159/6.0) / nb_dec; 197 | double a_orig = -(3.14159/3.0) + k * a_step; 198 | for (int a = (k) ? 2:1; a <= 10; ) 199 | { 200 | double na = a_orig + log10((double)a) * a_step; 201 | ca = cos(na); 202 | sa = sin(na); 203 | x1 = cx - rds * ca; 204 | y1 = cy - rds * sa; 205 | x2 = cx - (rds-6) * ca; 206 | y2 = cy - (rds-6) * sa; 207 | fl_color(FL_BLACK); 208 | fl_line(x1,y1,x2,y2); 209 | fl_color(FL_WHITE); 210 | if (sa*ca <0) 211 | fl_line(x1+1,y1+1,x2+1,y2+1); 212 | else 213 | fl_line(x1+1,y1-1,x2+1,y2-1); 214 | if ((a == 1) || (nb_dec == 1)) 215 | a += 1; 216 | else 217 | a += 2; 218 | } 219 | } 220 | } 221 | } 222 | 223 | void Fl_Knob::draw_cursor(const int ox,const int oy,const int side) { 224 | float rds,cur,cx,cy; 225 | double angle; 226 | 227 | rds = (side - 20) / 2.0; 228 | cur = _percent * rds / 2; 229 | cx = ox + side / 2; 230 | cy = oy + side / 2; 231 | angle = (a2-a1)*(value()-minimum())/(maximum()-minimum()) + a1; 232 | fl_push_matrix(); 233 | fl_scale(1,1); 234 | fl_translate(cx,cy); 235 | fl_rotate(-angle); 236 | fl_translate(0,rds-cur-2.0); 237 | if (_type 1.0) _percent = 1.0; 273 | if (visible()) damage(FL_DAMAGE_CHILD); 274 | } 275 | 276 | void Fl_Knob::scaleticks(const int tck) { 277 | _scaleticks = tck; 278 | if (_scaleticks < 0) _scaleticks = 0; 279 | if (_scaleticks > 31) _scaleticks = 31; 280 | if (visible()) damage(FL_DAMAGE_ALL); 281 | } 282 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Fl_Knob/Fl_Knob.fld: -------------------------------------------------------------------------------- 1 | # data file for the Fltk User Interface Designer (fluid) 2 | version 1.0304 3 | header_name {.H} 4 | code_name {.cxx} 5 | decl {\#include } {public local 6 | } 7 | 8 | decl {\#include } {private local 9 | } 10 | 11 | decl {\#include } {private local 12 | } 13 | 14 | class Fl_Knob {open : {public Fl_Valuator} 15 | } { 16 | decl {enum Fl_Knobtype {DOTLIN=0,DOTLOG_1,DOTLOG_2,DOTLOG_3,LINELIN,LINELOG_1,LINELOG_2,LINELOG_3};} {public local 17 | } 18 | decl {int _type;} {private local 19 | } 20 | decl {float _percent;} {private local 21 | } 22 | decl {int _scaleticks;} {private local 23 | } 24 | decl {short a1,a2;} {private local 25 | } 26 | Function {Fl_Knob(int xx,int yy,int ww,int hh,const char *l): Fl_Valuator(xx,yy,ww,hh,l)} {} { 27 | code {a1 = 35; 28 | a2 = 325; 29 | _type = DOTLIN; 30 | _percent = 0.3; 31 | _scaleticks = 10;} {} 32 | } 33 | decl {virtual ~Fl_Knob() {}} {selected public local 34 | } 35 | Function {draw()} {open protected 36 | } { 37 | code {int ox,oy,ww,hh,side; 38 | unsigned char rr,gg,bb; 39 | 40 | ox = x(); 41 | oy = y(); 42 | ww = w(); 43 | hh = h(); 44 | draw_label(); 45 | fl_clip(ox,oy,ww,hh); 46 | if (ww > hh) 47 | { 48 | side = hh; 49 | ox = ox + (ww - side) / 2; 50 | } 51 | else 52 | { 53 | side = ww; 54 | oy = oy + (hh - side) / 2; 55 | } 56 | side = w() > h () ? hh:ww; 57 | int dam = damage(); 58 | if (dam & FL_DAMAGE_ALL) 59 | { 60 | int col = parent()->color(); 61 | fl_color(col); 62 | fl_rectf(ox,oy,side,side); 63 | Fl::get_color((Fl_Color)col,rr,gg,bb); 64 | shadow(-60,rr,gg,bb); 65 | fl_pie(ox+9,oy+9,side-12,side-12,0,360); 66 | draw_scale(ox,oy,side); 67 | col = color(); 68 | Fl::get_color((Fl_Color)col,rr,gg,bb); 69 | 70 | shadow(7,rr,gg,bb); 71 | fl_pie(ox+6,oy+6,side-12,side-12,40,50); 72 | fl_pie(ox+6,oy+6,side-12,side-12,260,270); 73 | 74 | shadow(15,rr,gg,bb); 75 | fl_pie(ox+6,oy+6,side-12,side-12,50,70); 76 | fl_pie(ox+6,oy+6,side-12,side-12,230,260); 77 | 78 | shadow(25,rr,gg,bb); 79 | fl_pie(ox+6,oy+6,side-12,side-12,70,80); 80 | fl_pie(ox+6,oy+6,side-12,side-12,220,230); 81 | 82 | shadow(30,rr,gg,bb); 83 | fl_pie(ox+6,oy+6,side-12,side-12,80,220); 84 | 85 | shadow(-7,rr,gg,bb); 86 | fl_pie(ox+6,oy+6,side-12,side-12,30,40); 87 | fl_pie(ox+6,oy+6,side-12,side-12,270,280); 88 | 89 | shadow(-15,rr,gg,bb); 90 | fl_pie(ox+6,oy+6,side-12,side-12,280,400); 91 | shadow(-25,rr,gg,bb); 92 | fl_pie(ox+6,oy+6,side-12,side-12,290,390); 93 | 94 | 95 | fl_color(FL_BLACK); 96 | fl_arc(ox+6,oy+6,side-11,side-11,0,360); 97 | fl_color(col); 98 | fl_pie(ox+10,oy+10,side-20,side-20,0,360); 99 | } 100 | else 101 | { 102 | fl_color(color()); 103 | fl_pie(ox+10,oy+10,side-20,side-20,0,360); 104 | } 105 | Fl::get_color((Fl_Color)color(),rr,gg,bb); 106 | shadow(10,rr,gg,bb); 107 | fl_pie(ox+10,oy+10,side-20,side-20,110,150); 108 | fl_pie(ox+10,oy+10,side-20,side-20,290,330); 109 | shadow(17,rr,gg,bb); 110 | fl_pie(ox+10,oy+10,side-20,side-20,120,140); 111 | fl_pie(ox+10,oy+10,side-20,side-20,300,320); 112 | shadow(25,rr,gg,bb); 113 | fl_pie(ox+10,oy+10,side-20,side-20,127,133); 114 | fl_pie(ox+10,oy+10,side-20,side-20,307,313); 115 | draw_cursor(ox,oy,side); 116 | fl_pop_clip();} {} 117 | } 118 | Function {handle(int event)} {protected return_type int 119 | } { 120 | code {int ox,oy,ww,hh; 121 | 122 | ox = x() + 10; oy = y() + 10; 123 | ww = w() - 20; 124 | hh = h()-20; 125 | switch (event) 126 | { 127 | case FL_PUSH: 128 | handle_push(); 129 | case FL_DRAG: 130 | { 131 | int mx = Fl::event_x()-ox-ww/2; 132 | int my = Fl::event_y()-oy-hh/2; 133 | if (!mx && !my) return 1; 134 | double angle = 270-atan2((float)-my, (float)mx)*180/M_PI; 135 | double oldangle = (a2-a1)*(value()-minimum())/(maximum()-minimum()) + a1; 136 | while (angle < oldangle-180) angle += 360; 137 | while (angle > oldangle+180) angle -= 360; 138 | double val; 139 | if ((a1= a1)) 140 | { 141 | val = minimum(); 142 | } 143 | else 144 | if ((a1= a2) : (angle <= a2)) 145 | { 146 | val = maximum(); 147 | } 148 | else 149 | { 150 | val = minimum() + (maximum()-minimum())*(angle-a1)/(a2-a1); 151 | } 152 | handle_drag(clamp(round(val))); 153 | } 154 | return 1; 155 | case FL_RELEASE: 156 | handle_release(); 157 | return 1; 158 | default: 159 | return 0; 160 | } 161 | return 0;} {} 162 | } 163 | Function {type(int ty)} {} { 164 | code {_type = ty;} {} 165 | } 166 | Function {shadow(const int offs,const uchar r,uchar g,uchar b)} {open private 167 | } { 168 | code {int rr,gg,bb; 169 | 170 | rr = r + offs; 171 | rr = rr > 255 ? 255:rr; 172 | rr = rr < 0 ? 0:rr; 173 | gg = g + offs; 174 | gg = gg > 255 ? 255:gg; 175 | gg = gg < 0 ? 0:gg; 176 | bb = b + offs; 177 | bb = bb > 255 ? 255:bb; 178 | bb = bb < 0 ? 0:bb; 179 | fl_color((uchar)rr,(uchar)gg,(uchar)bb);} {} 180 | } 181 | Function {draw_scale(const int ox,const int oy,const int side)} {open private 182 | } { 183 | code {float x1,y1,x2,y2,rds,cx,cy,ca,sa; 184 | 185 | rds = side / 2; 186 | cx = ox + side / 2; 187 | cy = oy + side / 2; 188 | if (!(_type & DOTLOG_3)) 189 | { 190 | if (_scaleticks == 0) return; 191 | double a_step = (10.0*3.14159/6.0) / _scaleticks; 192 | double a_orig = -(3.14159/3.0); 193 | for (int a = 0; a <= _scaleticks; a++) 194 | { 195 | double na = a_orig + a * a_step; 196 | ca = cos(na); 197 | sa = sin(na); 198 | x1 = cx + rds * ca; 199 | y1 = cy - rds * sa; 200 | x2 = cx + (rds-6) * ca; 201 | y2 = cy - (rds-6) * sa; 202 | fl_color(FL_BLACK); 203 | fl_line(x1,y1,x2,y2); 204 | fl_color(FL_WHITE); 205 | if (sa*ca >=0) 206 | fl_line(x1+1,y1+1,x2+1,y2+1); 207 | else 208 | fl_line(x1+1,y1-1,x2+1,y2-1); 209 | } 210 | } 211 | else 212 | { 213 | int nb_dec = (_type & DOTLOG_3); 214 | for (int k = 0; k < nb_dec; k++) 215 | { 216 | double a_step = (10.0*3.14159/6.0) / nb_dec; 217 | double a_orig = -(3.14159/3.0) + k * a_step; 218 | for (int a = (k) ? 2:1; a <= 10; ) 219 | { 220 | double na = a_orig + log10((double)a) * a_step; 221 | ca = cos(na); 222 | sa = sin(na); 223 | x1 = cx - rds * ca; 224 | y1 = cy - rds * sa; 225 | x2 = cx - (rds-6) * ca; 226 | y2 = cy - (rds-6) * sa; 227 | fl_color(FL_BLACK); 228 | fl_line(x1,y1,x2,y2); 229 | fl_color(FL_WHITE); 230 | if (sa*ca <0) 231 | fl_line(x1+1,y1+1,x2+1,y2+1); 232 | else 233 | fl_line(x1+1,y1-1,x2+1,y2-1); 234 | if ((a == 1) || (nb_dec == 1)) 235 | a += 1; 236 | else 237 | a += 2; 238 | } 239 | } 240 | }} {} 241 | } 242 | Function {draw_cursor(const int ox,const int oy,const int side)} {private 243 | } { 244 | code {float rds,cur,cx,cy; 245 | double angle; 246 | 247 | rds = (side - 20) / 2.0; 248 | cur = _percent * rds / 2; 249 | cx = ox + side / 2; 250 | cy = oy + side / 2; 251 | angle = (a2-a1)*(value()-minimum())/(maximum()-minimum()) + a1; 252 | fl_push_matrix(); 253 | fl_scale(1,1); 254 | fl_translate(cx,cy); 255 | fl_rotate(-angle); 256 | fl_translate(0,rds-cur-2.0); 257 | if (_type 1.0) _percent = 1.0; 292 | if (visible()) damage(FL_DAMAGE_CHILD);} {} 293 | } 294 | Function {scaleticks(const int tck)} {} { 295 | code {_scaleticks = tck; 296 | if (_scaleticks < 0) _scaleticks = 0; 297 | if (_scaleticks > 31) _scaleticks = 31; 298 | if (visible()) damage(FL_DAMAGE_ALL);} {} 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CXX = g++ 3 | AR = ar 4 | LD = g++ 5 | FLUID = fluid 6 | 7 | TARGET = Test 8 | INCFLAGS = -I. 9 | CPPFLAGS = -O2 $(INCFLAGS) 10 | CXXFLAGS = -O2 $(INCFLAGS) 11 | ARFLAGS = -ru 12 | OBJS = Fl_Knob/Fl_Knob.o Test.o 13 | 14 | all : $(TARGET) 15 | 16 | .SUFFIXES: .cxx .H .o .fld 17 | 18 | .fld.cxx: 19 | $(FLUID) -c $< 20 | .cxx.o : 21 | $(CXX) $(CXXFLAGS) -c $< -o $(<:.cxx=.o) 22 | 23 | $(TARGET) : $(OBJS) 24 | $(LD) -o $@ $(OBJS) -L/usr/X11/lib -L/usr/local/lib -lfltk -lX11 -lm 25 | 26 | clean: 27 | -rm -f $(OBJS) $(TARGET) 28 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/README.txt: -------------------------------------------------------------------------------- 1 | Fl_Knob -- A Knob widget for FLTK 2 | 3 | Fl_Knob is Copyright 2000 by Yves Usson. Fl_Knob is provided under the terms of the GNU General Public License and GNU Library General Public License. 4 | 5 | --- 6 | 7 | Fl_Knob is a widget attempting to provide a "reallistic" rendering of a potentiometer knob. It displays shiny hilites and casts a shadow... 8 | It inherits from the Fl_Valuator class and offers the following features : 9 | 10 | selectectable cursor shape : DOT or LINE 11 | selectable scale-tick type : LIN or LOG 12 | number of scale ticks (only for LIN) 13 | size of the cursor 14 | The type of the knob is set with the method type(int ty) where ty can be : 15 | Fl_Knob::DOTLIN -> Dot cursor with linear scale ticks 16 | Fl_Knob::DOTLOG1 -> Dot cursor with log scale ticks over one decade 17 | Fl_Knob::DOTLOG2 -> Dot cursor with log scale ticks over two decades 18 | Fl_Knob::DOTLOG3 -> Dot cursor with log scale ticks over three decades 19 | Fl_Knob::LINELIN -> Line cursor with linear scale ticks 20 | Fl_Knob::LINELOG1 -> Line cursor with log scale ticks over one decade 21 | Fl_Knob::LINELOG2 -> Line cursor with log scale ticks over two decades 22 | Fl_Knob::LINELOG3 -> Line cursor with log scale ticks over three decades 23 | The number of scale ticks can be set with the method scaleticks(const int tick) 24 | where tick is an integer from 0 to 31. This is only operating for knob types 25 | Fl_Knob::DOTLIN and Fl_Knob::LINELIN 26 | The size of the cursor is set with the method cursor(const int pc) where pc is an integer from 0 to 100. pc is the percentage of the knob radius occupied by the cursor. 27 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Test.cxx: -------------------------------------------------------------------------------- 1 | // generated by Fast Light User Interface Designer (fluid) version 1.0010 2 | 3 | #include "Test.h" 4 | #include 5 | 6 | int main(int ac,char **av) { 7 | make_window(); 8 | Fl::run(); 9 | } 10 | 11 | Fl_Knob *v1=(Fl_Knob *)0; 12 | 13 | static void cb_v1(Fl_Knob*, void*) { 14 | o1->value(pow(10,v1->value())); 15 | } 16 | 17 | Fl_Value_Output *o1=(Fl_Value_Output *)0; 18 | 19 | Fl_Knob *v2=(Fl_Knob *)0; 20 | 21 | static void cb_v2(Fl_Knob*, void*) { 22 | o2->value(pow(10.0,v2->value())); 23 | } 24 | 25 | Fl_Knob *v3=(Fl_Knob *)0; 26 | 27 | static void cb_v3(Fl_Knob*, void*) { 28 | o3->value(pow(10.0,v3->value())); 29 | } 30 | 31 | Fl_Value_Output *o2=(Fl_Value_Output *)0; 32 | 33 | Fl_Value_Output *o3=(Fl_Value_Output *)0; 34 | 35 | Fl_Knob *v4=(Fl_Knob *)0; 36 | 37 | static void cb_v4(Fl_Knob*, void*) { 38 | o4->value(v4->value()); 39 | v5->scaleticks((int)(v4->value())); 40 | } 41 | 42 | Fl_Value_Output *o4=(Fl_Value_Output *)0; 43 | 44 | Fl_Knob *v5=(Fl_Knob *)0; 45 | 46 | static void cb_v5(Fl_Knob*, void*) { 47 | o5->value(v5->value()); 48 | v4->cursor(v5->value()); 49 | v6->cursor(v5->value()); 50 | } 51 | 52 | Fl_Knob *v6=(Fl_Knob *)0; 53 | 54 | static void cb_v6(Fl_Knob*, void*) { 55 | o6->value(pow(10.0,v6->value())); 56 | } 57 | 58 | Fl_Value_Output *o5=(Fl_Value_Output *)0; 59 | 60 | Fl_Value_Output *o6=(Fl_Value_Output *)0; 61 | 62 | Fl_Window* make_window() { 63 | Fl_Window* w; 64 | { Fl_Window* o = new Fl_Window(274, 211); 65 | w = o; 66 | o->labelsize(10); 67 | { Fl_Knob* o = v1 = new Fl_Knob(20, 12, 55, 58, "Fl_Knob::LINELOG_1"); 68 | o->color(10); 69 | o->selection_color(1); 70 | o->labelsize(9); 71 | o->step(0.001); 72 | o->callback((Fl_Callback*)cb_v1); 73 | o->type(Fl_Knob::LINELOG_1); 74 | } 75 | { Fl_Value_Output* o = o1 = new Fl_Value_Output(30, 85, 40, 15); 76 | o->minimum(1); 77 | o->maximum(10); 78 | o->step(0.01); 79 | o->value(1); 80 | o->textsize(9); 81 | } 82 | { Fl_Group* o = new Fl_Group(95, 0, 175, 105); 83 | o->box(FL_FLAT_BOX); 84 | o->color(147); 85 | { Fl_Knob* o = v2 = new Fl_Knob(110, 15, 55, 56, "Fl_Knob::LINELOG_2"); 86 | o->labelsize(9); 87 | o->labelcolor(7); 88 | o->maximum(2); 89 | o->step(0.001); 90 | o->callback((Fl_Callback*)cb_v2); 91 | o->align(FL_ALIGN_TOP); 92 | o->type(Fl_Knob::LINELOG_2); 93 | } 94 | { Fl_Knob* o = v3 = new Fl_Knob(195, 15, 55, 55, "Fl_Knob::DOTLOG_3"); 95 | o->color(37); 96 | o->selection_color(7); 97 | o->labelsize(9); 98 | o->labelcolor(7); 99 | o->maximum(3); 100 | o->step(0.01); 101 | o->callback((Fl_Callback*)cb_v3); 102 | o->type(Fl_Knob::DOTLOG_3); 103 | } 104 | { Fl_Value_Output* o = o2 = new Fl_Value_Output(120, 85, 35, 15); 105 | o->minimum(1); 106 | o->maximum(100); 107 | o->step(0.05); 108 | o->value(1); 109 | o->textsize(9); 110 | } 111 | { Fl_Value_Output* o = o3 = new Fl_Value_Output(205, 85, 40, 15); 112 | o->minimum(1); 113 | o->maximum(1000); 114 | o->step(0.1); 115 | o->value(1); 116 | o->textsize(9); 117 | } 118 | o->end(); 119 | } 120 | { Fl_Knob* o = v4 = new Fl_Knob(20, 116, 55, 59, "Num. of Ticks-->"); 121 | o->color(230); 122 | o->selection_color(1); 123 | o->labelsize(9); 124 | o->maximum(31); 125 | o->step(1); 126 | o->callback((Fl_Callback*)cb_v4); 127 | o->type(Fl_Knob::LINELIN); 128 | } 129 | { Fl_Value_Output* o = o4 = new Fl_Value_Output(30, 190, 35, 15); 130 | o->maximum(100); 131 | o->step(0.01); 132 | o->textsize(9); 133 | } 134 | { Fl_Group* o = new Fl_Group(95, 110, 175, 100); 135 | o->box(FL_ENGRAVED_BOX); 136 | o->color(246); 137 | { Fl_Knob* o = v5 = new Fl_Knob(110, 120, 55, 55, "<--Cursor size-->"); 138 | o->labelsize(9); 139 | o->maximum(100); 140 | o->step(1); 141 | o->value(20); 142 | o->callback((Fl_Callback*)cb_v5); 143 | o->type(Fl_Knob::DOTLIN); 144 | } 145 | { Fl_Knob* o = v6 = new Fl_Knob(195, 120, 55, 55, "Knob::DOTLOG_3"); 146 | o->labelsize(9); 147 | o->maximum(3); 148 | o->step(0.01); 149 | o->callback((Fl_Callback*)cb_v6); 150 | o->type(Fl_Knob::DOTLOG_3); 151 | } 152 | { Fl_Value_Output* o = o5 = new Fl_Value_Output(120, 190, 35, 15); 153 | o->maximum(10); 154 | o->step(0.1); 155 | o->textsize(9); 156 | } 157 | { Fl_Value_Output* o = o6 = new Fl_Value_Output(210, 190, 35, 15); 158 | o->minimum(1); 159 | o->maximum(1000); 160 | o->step(0.1); 161 | o->value(1); 162 | o->textsize(9); 163 | } 164 | o->end(); 165 | } 166 | o->show(); 167 | o->end(); 168 | } 169 | return w; 170 | } 171 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Test.fld: -------------------------------------------------------------------------------- 1 | # data file for the Fltk User Interface Designer (fluid) 2 | version 1.0010 3 | header_name {.h} 4 | code_name {.cxx} 5 | gridx 1 6 | gridy 1 7 | snap 3 8 | decl {\#include "Fl_Knob.H"} {public 9 | } 10 | 11 | decl {\#include } {} 12 | 13 | Function {main(int ac,char **av)} {open return_type int 14 | } { 15 | code {make_window(); 16 | Fl::run();} {} 17 | } 18 | 19 | Function {make_window()} {open 20 | } { 21 | Fl_Window {} {open selected 22 | xywh {358 376 274 211} labelsize 10 hide 23 | code0 {o->show();} 24 | } { 25 | Fl_Dial v1 { 26 | label {Fl_Knob::LINELOG_1} 27 | callback {o1->value(pow(10,v1->value()));} 28 | xywh {20 12 55 58} color 10 selection_color 1 labelsize 9 step 0.001 29 | code0 {o->type(Fl_Knob::LINELOG_1);} 30 | class Fl_Knob 31 | } 32 | Fl_Value_Output o1 { 33 | xywh {30 85 40 15} minimum 1 maximum 10 step 0.01 value 1 textsize 9 34 | } 35 | Fl_Group {} {open 36 | xywh {95 0 175 105} box FLAT_BOX color 147 37 | } { 38 | Fl_Dial v2 { 39 | label {Fl_Knob::LINELOG_2} 40 | callback {o2->value(pow(10.0,v2->value()));} 41 | xywh {110 15 55 56} labelsize 9 labelcolor 7 align 1 maximum 2 step 0.001 42 | code0 {o->type(Fl_Knob::LINELOG_2);} 43 | class Fl_Knob 44 | } 45 | Fl_Dial v3 { 46 | label {Fl_Knob::DOTLOG_3} 47 | callback {o3->value(pow(10.0,v3->value()));} 48 | xywh {195 15 55 55} color 37 selection_color 7 labelsize 9 labelcolor 7 maximum 3 step 0.01 49 | code0 {o->type(Fl_Knob::DOTLOG_3);} 50 | class Fl_Knob 51 | } 52 | Fl_Value_Output o2 { 53 | xywh {120 85 35 15} minimum 1 maximum 100 step 0.05 value 1 textsize 9 54 | } 55 | Fl_Value_Output o3 { 56 | xywh {205 85 40 15} minimum 1 maximum 1000 step 0.1 value 1 textsize 9 57 | } 58 | } 59 | Fl_Dial v4 { 60 | label {Num. of Ticks-->} 61 | callback {o4->value(v4->value()); 62 | v5->scaleticks((int)(v4->value()));} 63 | xywh {20 116 55 59} color 230 selection_color 1 labelsize 9 maximum 31 step 1 64 | code0 {o->type(Fl_Knob::LINELIN);} 65 | class Fl_Knob 66 | } 67 | Fl_Value_Output o4 { 68 | xywh {30 190 35 15} maximum 100 step 0.01 textsize 9 69 | } 70 | Fl_Group {} {open 71 | xywh {95 110 175 100} box ENGRAVED_BOX color 246 72 | } { 73 | Fl_Dial v5 { 74 | label {<--Cursor size-->} 75 | callback {o5->value(v5->value()); 76 | v4->cursor(v5->value()); 77 | v6->cursor(v5->value());} 78 | xywh {110 120 55 55} labelsize 9 maximum 100 step 1 value 20 79 | code0 {o->type(Fl_Knob::DOTLIN);} 80 | class Fl_Knob 81 | } 82 | Fl_Dial v6 { 83 | label {Knob::DOTLOG_3} 84 | callback {o6->value(pow(10.0,v6->value()));} 85 | xywh {195 120 55 55} labelsize 9 maximum 3 step 0.01 86 | code0 {o->type(Fl_Knob::DOTLOG_3);} 87 | class Fl_Knob 88 | } 89 | Fl_Value_Output o5 { 90 | xywh {120 190 35 15} maximum 10 step 0.1 textsize 9 91 | } 92 | Fl_Value_Output o6 { 93 | xywh {210 190 35 15} minimum 1 maximum 1000 step 0.1 value 1 textsize 9 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /thirdparty/Fl_Knob/Test.h: -------------------------------------------------------------------------------- 1 | // generated by Fast Light User Interface Designer (fluid) version 1.0010 2 | 3 | #ifndef Test_h 4 | #define Test_h 5 | #include 6 | #include "Fl_Knob/Fl_Knob.H" 7 | int main(int ac,char **av); 8 | #include 9 | extern Fl_Knob *v1; 10 | #include 11 | extern Fl_Value_Output *o1; 12 | #include 13 | extern Fl_Knob *v2; 14 | extern Fl_Knob *v3; 15 | extern Fl_Value_Output *o2; 16 | extern Fl_Value_Output *o3; 17 | extern Fl_Knob *v4; 18 | extern Fl_Value_Output *o4; 19 | extern Fl_Knob *v5; 20 | extern Fl_Knob *v6; 21 | extern Fl_Value_Output *o5; 22 | extern Fl_Value_Output *o6; 23 | Fl_Window* make_window(); 24 | #endif 25 | -------------------------------------------------------------------------------- /visu~-help.pd: -------------------------------------------------------------------------------- 1 | #N canvas 32 529 578 392 12; 2 | #X obj 66 54 wfvisu~; 3 | #X text 139 54 Waterfall; 4 | #X obj 66 79 sgvisu~; 5 | #X obj 66 104 ogvisu~; 6 | #X text 139 79 Spectrogram; 7 | #X text 139 104 Oscillogram; 8 | #X obj 46 274 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 9 | -1; 10 | #X obj 46 295 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 11 | -1; 12 | #X obj 46 316 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 13 | -1; 14 | #X obj 212 306 wfvisu~ Waterfall; 15 | #X obj 212 330 sgvisu~ Spectrogram; 16 | #X obj 212 354 ogvisu~ Oscillogram; 17 | #X obj 178 255 adc~ 1; 18 | #X obj 372 255 adc~ 2; 19 | #X text 87 14 - Signal visualizers in external processes; 20 | #X text 66 198 Arguments; 21 | #X text 10 252 show/hide window; 22 | #X text 66 140 This object communicates its inputs to an external process 23 | which displays the signal in a separate window. Bang to show or hide 24 | the visualization.; 25 | #X text 150 198 - title; 26 | #X text 150 213 - number of inputs (up to 4 \, by default 2); 27 | #X connect 6 0 9 0; 28 | #X connect 7 0 10 0; 29 | #X connect 8 0 11 0; 30 | #X connect 12 0 9 0; 31 | #X connect 12 0 10 0; 32 | #X connect 12 0 11 0; 33 | #X connect 13 0 9 1; 34 | #X connect 13 0 10 1; 35 | #X connect 13 0 11 1; 36 | -------------------------------------------------------------------------------- /wfvisu~-help.pd: -------------------------------------------------------------------------------- 1 | visu~-help.pd --------------------------------------------------------------------------------