qmidinet-1.0.1/PaxHeaders/CMakeLists.txt0000644000000000000000000000013214772454326015157 xustar0030 mtime=1743411414.377814177 30 atime=1743411414.377814177 30 ctime=1743411414.377814177 qmidinet-1.0.1/CMakeLists.txt0000644000175000001440000001221314772454326015146 0ustar00rncbcuserscmake_minimum_required (VERSION 3.15) project (QmidiNet VERSION 1.0.1 DESCRIPTION "A MIDI Network Gateway via UDP/IP Multicast" HOMEPAGE_URL "https://qmidinet.sourceforge.io" LANGUAGES C CXX) set (PROJECT_TITLE "${PROJECT_NAME}") string (TOLOWER "${PROJECT_TITLE}" PROJECT_NAME) set (PROJECT_COPYRIGHT "Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved.") set (PROJECT_DOMAIN "rncbc.org") execute_process ( COMMAND git describe --tags --dirty --abbrev=6 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_OUTPUT RESULT_VARIABLE GIT_DESCRIBE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_DESCRIBE_RESULT EQUAL 0) set (GIT_VERSION "${GIT_DESCRIBE_OUTPUT}") string (REGEX REPLACE "^[^0-9]+" "" GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "-g" "git." GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "[_|-]" "." GIT_VERSION "${GIT_VERSION}") execute_process ( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVPARSE_OUTPUT RESULT_VARIABLE GIT_REVPARSE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_REVPARSE_RESULT EQUAL 0 AND NOT GIT_REVPARSE_OUTPUT STREQUAL "main") set (GIT_VERSION "${GIT_VERSION} [${GIT_REVPARSE_OUTPUT}]") endif () set (PROJECT_VERSION "${GIT_VERSION}") endif () if (CMAKE_BUILD_TYPE MATCHES "Debug") set (CONFIG_DEBUG 1) set (CONFIG_BUILD_TYPE "debug") else () set (CONFIG_DEBUG 0) set (CONFIG_BUILD_TYPE "release") set (CMAKE_BUILD_TYPE "Release") endif () set (CONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") include (GNUInstallDirs) set (CONFIG_BINDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_BINDIR}") set (CONFIG_LIBDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_LIBDIR}") set (CONFIG_DATADIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_DATADIR}") set (CONFIG_MANDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_MANDIR}") # Enable ALSA MIDI support option. option (CONFIG_ALSA_MIDI "Enable ALSA MIDI support (default=yes)" 1) # Enable JACK MIDI support option. option (CONFIG_JACK_MIDI "Enable JACK MIDI support (default=yes)" 1) # Enable Network IPv6 support option. option (CONFIG_IPV6 "Enable Network IPv6 support (default=yes)" 1) # Enable unique/single instance. option (CONFIG_XUNIQUE "Enable unique/single instance (default=yes)" 1) # Enable Wayland support option. option (CONFIG_WAYLAND "Enable Wayland support (EXPERIMENTAL) (default=no)" 0) # Enable Qt6 build preference. option (CONFIG_QT6 "Enable Qt6 build (default=yes)" 1) # Fix for new CMAKE_REQUIRED_LIBRARIES policy. if (POLICY CMP0075) cmake_policy (SET CMP0075 NEW) endif () # Check for Qt... if (CONFIG_QT6) find_package (Qt6 QUIET) if (NOT Qt6_FOUND) set (CONFIG_QT6 0) endif () endif () if (CONFIG_QT6) find_package (QT QUIET NAMES Qt6) else () find_package (QT QUIET NAMES Qt5) endif () find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Svg) if (CONFIG_IPV6 OR CONFIG_XUNIQUE) find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network) endif () #find_package (Qt${QT_VERSION_MAJOR}LinguistTools) include (CheckIncludeFile) include (CheckIncludeFiles) include (CheckIncludeFileCXX) include (CheckFunctionExists) include (CheckLibraryExists) # Checks for header files. if (UNIX AND NOT APPLE) check_include_files ("fcntl.h;unistd.h;signal.h" HAVE_SIGNAL_H) endif () # Find package modules include (FindPkgConfig) # Check for ALSA libraries. if (CONFIG_ALSA_MIDI) pkg_check_modules (ALSA IMPORTED_TARGET alsa) if (ALSA_FOUND) find_library(ALSA_LIBRARY NAMES ${ALSA_LIBRARIES} HINTS ${ALSA_LIBDIR}) endif () if (ALSA_LIBRARY) set (CONFIG_ALSA_MIDI 1) #set (CMAKE_REQUIRED_LIBRARIES "${ALSA_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") else () message (WARNING "*** ALSA library not found.") set (CONFIG_ALSA_MIDI 0) endif () endif () # Check for JACK libraries. if (CONFIG_JACK_MIDI) pkg_check_modules (JACK IMPORTED_TARGET jack>=0.100.0) if (JACK_FOUND) find_library(JACK_LIBRARY NAMES ${JACK_LIBRARIES} HINTS ${JACK_LIBDIR}) endif () if (JACK_LIBRARY) set (CONFIG_JACK_MIDI 1) #set (CMAKE_REQUIRED_LIBRARIES "${JACK_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") else () message (WARNING "*** JACK library not found.") set (CONFIG_JACK_MIDI 0) endif () endif () add_subdirectory (src) # Finally check whether Qt is statically linked. if (QT_FEATURE_static) set(QT_VERSION "${QT_VERSION}-static") endif () # Configuration status macro (SHOW_OPTION text value) if (${value}) message ("${text}: yes") else () message ("${text}: no") endif () endmacro () message ("\n ${PROJECT_TITLE} ${PROJECT_VERSION} (Qt ${QT_VERSION})") message ("\n Build target . . . . . . . . . . . . . . . . . . .: ${CONFIG_BUILD_TYPE}\n") show_option (" ALSA MIDI support . . . . . . . . . . . . . . . ." CONFIG_ALSA_MIDI) show_option (" JACK MIDI support . . . . . . . . . . . . . . . ." CONFIG_JACK_MIDI) message ("") show_option (" Network IPv6 support . . . . . . . . . . . . . . ." CONFIG_IPV6) message ("") show_option (" Unique/Single instance support . . . . . . . . . ." CONFIG_XUNIQUE) message ("\n Install prefix . . . . . . . . . . . . . . . . . .: ${CONFIG_PREFIX}\n") qmidinet-1.0.1/PaxHeaders/ChangeLog0000644000000000000000000000013214772454326014171 xustar0030 mtime=1743411414.377814177 30 atime=1743411414.377814177 30 ctime=1743411414.377814177 qmidinet-1.0.1/ChangeLog0000644000175000001440000001767714772454326014203 0ustar00rncbcusersQmidiNet - A MIDI Network Gateway via UDP/IP Multicast ------------------------------------------------------ ChangeLog 1.0.1 2025-03-31 An Early Spring'25 Release. - Fixed command line parsing (QCommandLineParser/Option) to not exiting the application with a segfault when showing help and version information. - Prepping up next development cycle (Qt >= 6.8) 1.0.0 2024-06-19 An Unthinkable Release. - Making up the unthinkable (aka. v1.0.0) 0.9.91 2024-05-01 A Spring'24 Release Candidate 2. - Prepping the unthinkable (aka. v1.0.0-rc2) - Updated to latest framework level (Qt >= 6.7) 0.9.90 2024-04-10 A Spring'24 Release Candidate. - Prepping the unthinkable (aka. v1.0.0-rc1) 0.9.12 2024-01-24 A Winter'24 Release. - Add Unique/Single instance support. - Updated copyright headers into the New Year (2024). 0.9.11 2023-09-09 An End-of-Summer'23 Release. - Preppings to next development cycle (Qt >= 6.6) 0.9.10 2023-06-01 A Spring'23 Release. - Fixed lost or out of order messages on JACK-MIDI client. - Prepping into the next development cycle (with Qt >= 6.5). 0.9.9 2023-03-23 An Early-Spring'23 Release. - Bumping copyright headers to the brand new year. 0.9.8 2022-12-28 An End-of-Year'22 Release. - Just bumping into the next develop cycle/season. 0.9.7 2022-10-03 An Early-Autumn'22 Release. - Bumped version number into the next release season. 0.9.6 2022-04-02 A Spring'22 Release. - Main application icon is now presented in scalable format (SVG). - Migrated command line parsing to QCommandLineParser/Option (Qt >= 5.2) 0.9.5 2022-01-09 A Winter'22 Release. - Dropped autotools (autoconf, automake, etc.) build system. 0.9.4 2021-07-04 An Early-Summer'21 Release. - All builds default to Qt6 (Qt >= 6.1) where available. - CMake is now the official build system. 0.9.3 2021-05-11 A Spring'21 Release. - All packaging builds switching to CMake. 0.9.2 2021-03-14 An End-of-Winter'21 Release. - Bumped version micro/dot number into the next develop cycle. 0.9.1 2021-02-07 A Winter'21 Release. - Early preparations for the New Year develop(ment) cycle. 0.9.0 2020-12-17 A Winter'20 Release. - Early fixing to build for Qt >= 6.0.0 and comply with C++17 standard. 0.6.3 2020-07-31 A Summer'20 Release. - Early fixing to build for Qt >= 5.15.0. 0.6.2 2020-03-24 A Spring'20 Release. - A scalable (.svg) icon version has been added. - Make man page compression reproducible (after request by Jelle van der Waa, while on the Vee-Ones, thanks). - Bumped copyright headers into the New Year (2020). 0.6.1 2019-12-22 The Winter'19 Release. - Second attempt to fix the yet non-official though CMake build configuration. - Provide a default IPv6 address, and feedback to the user in the options dialog (after a merge request by plcl aka. Pedro López-Cabanillas, thanks). - When using ./configure --with-qt=... it is also necessary to adjust the PKG_CONFIG_PATH environment variable (also by plcl aka. Pedro López-Cabanillas). 0.6.0 2019-10-17 An Autumn'19 Release. - Populate automatically the network interface combo-box with detected interface names (after merge request by plcl aka. Pedro López-Cabanillas, thanks). - Complete rewrite of all the basic network interface code, while using the Qt5 framework as far as needed to support IPv4 and IPv6 seamless and interchangeably. - Added alternate yet non-official CMake build option. - Fix HiDPI display screen effective support (Qt >= 5.6). - Make sure compiler flags comply to c++11 as standard. 0.5.5 2019-07-12 A Summer'19 Release. - Configure updated to check for qtchooser availability. - Minor update to Debian packaging control file. 0.5.4 2019-04-11 A Spring-Break'19 Release. - Fix build on linux with musl (according to POSIX.1-2001, POSIX.1-2008; PR#7 by Andreas Müller aka. schnitzeltony, thanks). 0.5.3 2019-03-11 Pre-LAC2019 release frenzy. - HiDPI display screen support (Qt >= 5.6). - Old deprecated Qt4 build support is no more. 0.5.2 2018-07-22 A Summer'18 release. - AppData/AppStream metadata is now settled under an all permisssive license (FSFAP); also updated to be the most compliant with latest freedesktop.org specification and recommendation. - Fixed for some g++ >= 8.1.1 warnings and quietness. 0.5.1 2018-05-21 Pre-LAC2018 release frenzy. - A little hardening on the configure (autoconf) macro side. 0.5.0 2017-12-16 End of Autum'17 release. - Desktop entry specification file is now finally independent from build/configure template chains. - Updated target path for freedesktop.org's AppStream metainfo file (formerly AppData). 0.4.3 2017-04-27 Pre-LAC2017 release frenzy. - Added new and replaced old system-tray menu icons. - Make builds reproducible byte for byte, by getting rid of the configure build date and time stamps. 0.4.2 2016-11-14 A Fall'16 release. - Almost complete overhaul on the configure script command line options, wrt. installation directories specification, eg. --prefix, --bindir, --libdir, --datadir and --mandir. 0.4.1 2016-09-14 End of Summer'16 release. - Dropped the --enable-qt5 from configure as found redundant given that's the build default anyway (suggestion by Guido Scholz, while for Qtractor, thanks). 0.4.0 2016-04-05 Spring'16 release frenzy. - Allegedly fixed for the socketopt(IP_MULTICAST_LOOP) reverse semantics on Windows platforms (as suggested by Paul Davis, from Ardour ipMIDI implementation, thanks). - Added application keywords to freedesktop.org's AppData. 0.3.0 2015-09-21 Summer'15 release frenzy. - System tray icon now blinks on network send/receive activity. - Prefer Qt5 over Qt4 by default with configure script. - Complete rewrite of Qt4 vs. Qt5 configure builds. - Fixed for some strict tests for Qt4 vs. Qt5 configure builds. 0.2.1 2015-03-23 Pre-LAC2015 pre-season release. - Reset (to network defaults) button added to options dialog, which also gets some layout reform. - Added application description as freedesktop.org's AppData. - Previously hard-coded UDP/IP multicast address (225.0.0.37) is now an user configurable option. 0.2.0 2014-06-19 Headless finally. - A man page has beed added (making up Alessio Treglia's work on debian, thanks). - First attempt to allow a headless application run mode, without GUI or system-tray icon accessibility, with all options given as command line arguments. - Allow the build system to include an user specified LDFLAGS. 0.1.3 2013-12-31 A fifth of a Jubilee. - More preparations for Qt5 configure build. - Added missing blank spaces on some warning messages text lines. - Preparations for Qt5 migration. 0.1.2 2012-05-22 JACK-MIDI crashfix release. - JACK MIDI in-bound buffering was originally flawed and often the cause for severe random crashes, mostly due to memory corruption, now hopefully fixed. - Changed order of JACK MIDI and UDP socket initialization, hoping the later is always owned by the current genuine process. - JACK MIDI interface were sinking all incoming events into the the first port, now fixed (heads up from Chris Goddard, thanks). - Make(ing) -jN parallel builds now available for the masses. - Fixed Makefile.in handling of installation directories to the configure script eg. --datadir. - Main context menu simple no-brainer reordering. 0.1.1 2010-09-24 JACK-MIDI bugfix release. - Fixed serious JACK-MIDI DoS bug, now working as advertised. - Minor debug mode JACK-MIDI event print out fix. 0.1.0 2010-09-03 Second public release. - Attain micro-second precision for JACK MIDI timing, through home-brew queue sorting pool =) - Enabling ALSA MIDI and/or JACK MIDI devices is now optional. - Transparent JACK MIDI support is being introduced. - Use standard dialog buttons as provided by QDialogButtonBox. - Context menu is now also triggered when left-clicking on the system tray icon. - Message box title confidence fix (whatever it be:) 0.0.1 2010-03-07 First public release. qmidinet-1.0.1/PaxHeaders/LICENSE0000644000000000000000000000013214772454326013424 xustar0030 mtime=1743411414.377814177 30 atime=1743411414.377814177 30 ctime=1743411414.377814177 qmidinet-1.0.1/LICENSE0000644000175000001440000004310314772454326013415 0ustar00rncbcusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. qmidinet-1.0.1/PaxHeaders/README0000644000000000000000000000013214772454326013277 xustar0030 mtime=1743411414.377814177 30 atime=1743411414.377814177 30 ctime=1743411414.377814177 qmidinet-1.0.1/README0000644000175000001440000000506714772454326013277 0ustar00rncbcusersQmidiNet - A MIDI Network Gateway via UDP/IP Multicast ------------------------------------------------------ QmidiNet is a MIDI network gateway application that sends and receives MIDI data (ALSA Sequencer [2] and/or JACK MIDI [3]) over the network, using UDP/IP multicast. Inspired by multimidicast (https://llg.cubic.org/tools) and designed to be compatible with ipMIDI for Windows (https://nerds.de). Website: https://qmidinet.sourceforge.io http://qmidinet.sourceforge.net Project page: https://sourceforge.net/projects/qmidinet Weblog: https://www.rncbc.org QmidiNet is free, open-source software, distributed under the terms of the GNU General Public License (GPL) [4] version 2 or later. Requirements ------------ The software requirements for build and runtime are listed as follows: Mandatory: [1] Qt framework, C++ class library and tools for cross-platform application and UI development https://qt.io/ [2] ALSA, Advanced Linux Sound Architecture https://www.alsa-project.org/ Optional (opted-in at build time): [3] JACK Audio Connection Kit https://jackaudio.org/ Installation ------------ Unpack the tarball and in the extracted source directory: cmake [-DCMAKE_INSTALL_PREFIX=] -B build cmake --build build [--parallel ] and optionally, as root: [sudo] cmake --install build Note that the default installation path () is /usr/local . Configuration ------------- QmidiNet holds its settings and configuration state per user, in a file located as $HOME/.config/rncbc.org/QmidiNet.conf . Normally, there's no need to edit this file, as it is recreated and rewritten everytime qmidinet is run. Bugs ---- Plenty still, after all this is alpha software ;) Support ------- QmidiNet is open source free software. For bug reports, feature requests, discussion forums, mailling lists, or any other matter related to the development of this piece of software, please use the Sourceforge project page (https://sourceforge.net/projects/qmidinet). You can also find timely and closer contact information on my personal web site (https://www.rncbc.org). References ---------- [1] Qt framework, C++ class library and tools for cross-platform application and UI development https://qt.io/ [2] ALSA, Advanced Linux Sound Architecture https://www.alsa-project.org/ [3] JACK Audio Connection Kit https://jackaudio.org/ [4] GNU General Public License https://www.gnu.org/copyleft/gpl.html Enjoy. rncbc aka Rui Nuno Capela rncbc at rncbc dot org https://www.rncbc.org qmidinet-1.0.1/PaxHeaders/src0000644000000000000000000000013214772454326013131 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.378251695 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/0000755000175000001440000000000014772454326013176 5ustar00rncbcusersqmidinet-1.0.1/src/PaxHeaders/images0000644000000000000000000000013214772454326014376 xustar0030 mtime=1743411414.378593584 30 atime=1743411414.378458544 30 ctime=1743411414.378593584 qmidinet-1.0.1/src/images/0000755000175000001440000000000014772454326014443 5ustar00rncbcusersqmidinet-1.0.1/src/images/PaxHeaders/iconReceive.png0000644000000000000000000000013214772454326017414 xustar0030 mtime=1743411414.378528016 30 atime=1743411414.378458544 30 ctime=1743411414.378528016 qmidinet-1.0.1/src/images/iconReceive.png0000644000175000001440000000142614772454326017407 0ustar00rncbcusersPNG  IHDR szzIDATXOhgٙ5l:;qg׬Ә@#&bH{HdKJZA&"V xj=e1 hv+3l2xAp>|?}H"E)R#-˓`"w`SPDh/' ^,}XMp 3q1bG5>|k.vB@J:mG9s/E!$يDI9vjy*XL Mڅ1ٶG#W˃/ [f N eiQUTe뤐/CYN~Ǹ 3DzBUF…+okv5cOnnR|~ߛn bOZ[J|:_T__>TA^}ȋ'}mwHl%"CKj_!~mlUCJQ/}_ MJ ew x?J<՗>qæMʣ1eçK9\X$Ltj]5uZMOOIA)H:@⌄Wރ=i((N>}1b jwPFO˾`4K0a<=\a) %`j]c%fUkKH?LrlC7~(c=" X(,$sJG$b;yIENDB`qmidinet-1.0.1/src/images/PaxHeaders/menuOptions.png0000644000000000000000000000013214772454326017501 xustar0030 mtime=1743411414.378528016 30 atime=1743411414.378528016 30 ctime=1743411414.378528016 qmidinet-1.0.1/src/images/menuOptions.png0000644000175000001440000000160014772454326017466 0ustar00rncbcusersPNG  IHDRaGIDAT8u}Lu_?vNPy8(ć̖͉֣sVZTnPk.ؒ aBPa wƃ==p-1P𒔐dŒdc,Uu=Mگܸw@Đc[pƎѿtPBD"|fn $LB|6&KP*4mR)x[]a?u7 T@5L4kkk7 kM,^X_+MpVr`\ͥQ`5WC;>G~q5mYrSe@lGGW/v @r\SM$WV._[}΋ 8ޑWME,vBEK0긿7Q*Aׇ4[ԕ\s%)BtjW5>ӝ+0xib Խ]JeLoo6eOE;1tS[ʰe~Qc1<$&b!] y3]_q߇s| "o)/*dG-Ot"d)۪5ݥ c!8  E/+23Pދ TʇIENDB`qmidinet-1.0.1/src/images/PaxHeaders/iconError.png0000644000000000000000000000013214772454326017123 xustar0030 mtime=1743411414.378458544 30 atime=1743411414.378458544 30 ctime=1743411414.378458544 qmidinet-1.0.1/src/images/iconError.png0000644000175000001440000000106314772454326017113 0ustar00rncbcusersPNG  IHDR szzIDATXOKTQϵqjRd`\0H4Z}Zo ZЪ( \A ؈L56.h3_88=.]tҥKL|As/pGD/IN\K2[ri~.PDx:Lo }6\hXYM DԩtDԉ:9<xD$zLCe;oD|3g36GLs'#IK82UCimM,-q]m1>N))TwG1%u;,vH~4O_ӳH[d|lo@9M%sx19)SmS+dhTRIe˗%RIZ"kó346lX4oóEpJe i4 *D`?J[k֗6/*4/q0o-o-vթIENDB`qmidinet-1.0.1/src/images/PaxHeaders/menuQuit.png0000644000000000000000000000013214772454326016770 xustar0030 mtime=1743411414.378528016 30 atime=1743411414.378528016 30 ctime=1743411414.378528016 qmidinet-1.0.1/src/images/menuQuit.png0000644000175000001440000000065114772454326016762 0ustar00rncbcusersPNG  IHDRapIDAT8?Hqϝi Ґ"8GFKC85R$BDBA@eDIMv!m|_y^HdwP^oHƇ'\Ƕ=Z9ZW+ +b;~eޗD/cu.p.xC2+OAFn1~xN%J~Nȧ፠}Hr'xkR dk? \*4c[X t#3(Y*m!]ߓ *='V^W3yM2]7VwsJe0{?6]`|Hr=M cq;VS<Ԛ /5웚?,@yߚ"еrb} Ͷ ZbAh?:HMlRyũ^ʝŶrLTbV52$pcu&`4^ҳ^*毦[BrSx\:/87P̥#@c^=-?GD"kV\@_'Prɿ&o0dvtk0pf`w0Te$_Y1BTI'$GTD Ke^74\fM1R60{ɠX0dс?={k0OƕT0`e&1HM4|ق54935uy819ы>0dvtk0pf`w0Te$_Y1BTI'$GTD4 .   + )5@HMMH@5) ) ,BuΣuB, ' :uu: %#A A#$ AA # :: #,uu,"BB ")uu)!55!@@!HH!MN !MN !HH!@@!55!)uu)!BB "-uu-" ;; #!BB!##A A#% :uu: &-BuΣuB-( )5@INNI@5) f@@Error BL Orig     7 @@ @@ H;H /= -s  8/=DE>12)C\n{|o]F+/ 2]a5 ' ?kÞoC+ 8o֧u=#2g m6#MՑR#*i o1'DLjJ %]۠a+ 6tt: >~}B <{{@ 0km6 R՗X# 7x~> '] c%(>v}C?$#PŏU& ##TձY'-(GnٻqJ+. 2RuwU4'R) 8ITYYTJ:"+ &K[e G2/-+  )'  & &Ke^7 4\fM( &R60{̠X&dс?={k&PƕV&$af',HM-!|ق%(49,5uy8+ :ь?&fvtm&!pf+(`w$&Te$_Y& $BTI'$GTD& +  (e)\5+ 49k G2/-+  )'  & &Ke^7 4\fM( &R60{̠X&dс?={k&PƕV&$af',HM-!|ق%(49,5uy8+ :ь?&fvtm&!pf+(`w$&Te$_Y& $BTI'$GTD& +  (e)\5+ 49k -1<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<%<<><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<50 -("!   !!!!!!"""##$%&&')('&/2 &><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<%<<W4-' #6?6!! '9B;+(:HdujN?Nm|i@'9B>;Hdsk]gvYUelZ6!7B>:Hdsk^hyyw_UM;#:_ri[f|źxY@( !NuyƺvO, %WDz}Ÿj; &YĮ¦zuC #TƷǽ}d9 IǽygH( :sôDz~²y]; !+_¦zǽk?! KƲǽ~tC!7mĿǽd9" %RƲ~³hH'"9n¦z|^; # $Oƽ~Ľk?$2`ƽtC% :f²d9% ) $9RmrB* %7K^r`7, +9ITYWM:!. #%%!1 %@@Waves BL Receive     (@@@@><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - -9==2=>>>>?42 ,<<<<><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ *><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - -9==2=>>>>?42 ,<<<< X61,,+*(((/-*++30 ./098 69@@ Waves BL     %!G@@![@@!k><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ *><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ *><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ * X50++*)'''.,)**2/-./8758Waves BL Shadow     %')* *)7( )7))pp)-)7),su/8ĕ7)7),ss2Dڸa#.pp))7),ss3E٩KIY+.8)*rs3E٩LObAߦY# ?اHMeUڋBIߨa. FQJڍY[,Gt) FbBW`ؓH)Gڗ7 ?يBbcĎH,Jr),LLڋBI̓[BAID,\bB[,GضbJ2 BًD]ؓH)G¨r),]ČH,Jؗ7U؊AI̓[BD`r)/[,GضcWYUOE,UؓH)G®dK2.̌H,J©s)B̓[BC]ؗ7Cض]DBMbr)CطbMBBJME,B¬cQH2 .Vør) /Vؗ7 -B\r) ,?EE?* @@Waves TR Send (blur)     4-@@-@@-<<%<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=& 2/%&')'&&%$##"""!!!!!!   !"(- 16<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<<%<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=& 1!%%# .!:MWYTI:+ ,7_r^K7% *BrmR9$ )>inO2 ( :[tĹ_: &'HfyIJe< %9dƶe: %CtŶ_2$?kĽ~ƲO$ # ;]|ƹzĤn9"'Hhǻ~ƹR% "9dǻĥm7!Ct~ǹƷK !?kǹzƯ_+! :\yǻ~ǽêr: 'HfyǻĨI 9e}ǹƳT# CuzDZļY& ;jŻ~ǾW% +NvƻyuN! (@YxŴ|g[ir`:#>>=2==9, - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<<<<+ 2 3??>>>=2==9, - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=96 890/. 03++*-/(((*+,,16 X@@ Waves TR     ;@@;@@;') *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=8578/.-/2**),.''')*++05 W@@Waves TR Shadow     $C@@C@@C 5*?EE?- 2)r\B-07¬V/.)rضV.-2HQcصB+,EMJBBMbˌC*)rbMBD]̋C)7­]CB[ˋA()sضJ,Hˁ.(2KdˌG)HU&,EOUYWčG,[؈/&)r`DB[ˌIAU&7ضJ,H˄C]׆,&)rˌG)H]DB&2Jb̌G,[؉Bb\%,DIAB[ˌIBMMه%)rJ,H˄DcbB%7̌G)H`WيBb%)t͌G,[،YKQ%.a͌IBUeMI٧%#Y˂AbOL٧E3ss$(7.+YIK٧E3ss,)7)$)pp.#aڥD2ss,)7)$7ĕ8/us,)7)()pp))7)-)7)3U@@Plug     G?@@GS@@GcK<9'3¹9{,3¹8~%3½K6,3iv6rOɪ6Okkɸ6Tuk5ukkmY[u2#ukgOϫmm"2#u=Цsm(1'A3ϤqU+1?Ϧ],03Ϟ[-1?ϢU033ϞmG+,?ϞmG{9,3ޚ`U]G+?nբϻ],3UGmӽ],?U3Ӻ]K-",mӽ[,?mӽ].,3mӽ]#.,?mӽ]''K,3mӽ['&K,?mӽm?9 .,3mU"-,?mн^3-,3mUmfmom-,?3mo$.3mUm$. ,?MUba^m/3mmr-GUm?, 33GUmU‰wtkK+,?3G?ӶV{+12,;,Ӷ9+#,13,G,Ӷ*7,29Ӷ)"23';u,?Ӷ(",23Mf]79Ӱ(-+,UU?b9I5:(0GMJb 9NMn)"9?+[,c93Jo)3,XbS ISP{ *jb m3/VUC+lC$ONwmOm.XADyǻOtk0l#dʼnV"2l*V}3bX},57 6R5M:9(1Ļ8}+1Ļ8'1ĿM6z+1b~n6tE}6E\\}6Hne\}3en\\~xh[]w1!enhYIѭhh#1!en=Ҩuh)1 81ѦsV,1@Ѩz_+01Ѡz]//@ѤzV1/1ѠzhH,)@ѠhH}z: )1zzbV_H(@zpפѽz_(1VHzhտz_(@V1ռz_M(#+hzտz]( @hzտz_ (+1hzտz_$'+@hzտz_(&M+1hzտz](%M+@hzտh@: %+1hzzzzV#$+@hzҿ`1$+1hzVhhqh #+@1zzhq%#1hzVzh%# +@OVdc`h#1hhzt#HVhzz@$ 11HVhVĄvihzM$+-@1H@zΩvQ}% 33+=+zzΩ4&$+31+Hz+Ω'9+3zz:Ωz'#31(=zw+@Ωv'#+31Oh_7:Τp'/,+VV@a:C28 '1HOR>r) Ea c1*JBD*GB%GNyhGc.;CΪgg!2EN6ϥrg'2)5ΣpT*1>Υy\.35ΝyZ,3>ΡyT/35ΝygF*/>ΝgFzy8.5ݘyy_T\F.>ymԡκy\.5TFygҼy\.>T5ҹy\J.!.gyҼyZ/>gyҼy\/.5gyҼy\"/.>gyҼy\&'J.5gyҼyZ&&J.>gyҼg>8 /.5gyyyyT!/.>gyϼ]5/.5gyTgegng..>5yygn#/5gyTyg#. .>MTa`]g/5ggyq.FTgyy>, 55FTgTE)JyJ+.+>5F>y|@z+01.;.yy|+".05.Fy.|*6.1yy8|)!15&;yt.>| (!.15Me\8|(,*.TT>8z')/FM 8F$*!8>$.{9)5.:  :]>+#t5j2-#,Fvg,#q 0Cxƺ,)J0:ă@!2q|4$&t.5G8(';:997777 3 2 2 2 2 100**))))))('&&%%$$$$$%%&'(((((()*+/0 2 4 68@@ Plug Shadow     U@@U@@V 84 3,AIB/ 1,Sv{]: /Avf=! -JlG. ,B{Ġ}aH0 +/]ζ^: ) :fѸf=! ' lšhWE/ ' !G~й^: '.aηf< ' Hg< &0_×g< & ;kØg< ! G~Øh>!  .bÙlH3*&"   Lšj`[TE07mкaA'  $O͹uQ3 3a˭`:  :gؼf<  l Øg<  !H Øg< 1h Øg<  %X Øg< I Øg< 7n Øg<  %SØg< 9oØg<  $P×g< 2`×f:  :f”`2   h—f: &  qmidinet-1.0.1/src/images/PaxHeaders/iconSend.png0000644000000000000000000000013214772454326016723 xustar0030 mtime=1743411414.378528016 30 atime=1743411414.378528016 30 ctime=1743411414.378528016 qmidinet-1.0.1/src/images/iconSend.png0000644000175000001440000000137014772454326016714 0ustar00rncbcusersPNG  IHDR szzIDATXOlq?wmU ,lJ$D88vşAAPĦ*2&bXg޾qJ?y>} v"n(@0P(mĉ&3N{!(Z@milO魣$&q;ꈞCxdP (vnHj&Y 5 $,`q#1H8?O~GJb/vMse6>XķjRV[dyދ؋A3YPnKG_z>5%A8lnX6߭XD%J( IJ%kIENDB`qmidinet-1.0.1/src/images/PaxHeaders/qmidinet.png0000644000000000000000000000013214772454326016773 xustar0030 mtime=1743411414.378528016 30 atime=1743411414.378528016 30 ctime=1743411414.378528016 qmidinet-1.0.1/src/images/qmidinet.png0000644000175000001440000000506214772454326016766 0ustar00rncbcusersPNG  IHDR szz pHYsyytEXtSoftwarewww.inkscape.org< IDATXiPTWki$qAfT JQ jc*Eti 1dI,&,A+”]# (A%X"4tw$5Usޗw=eD`1IF6킝n?cL x/1cX>WF=;EGG "¯L&-''+++(>`2PqݺW7&L _> ZMeem"ƛ111=cv+!!˫SzJ<%n%M1g/vAeee˖-KϼwGz@@ag3ڨy#%?FVmL&8n1v2!!a{^^Ů~(kT*ծΞ]DDzxbtWJ mRi}fI=qgAinn_yر D$Utwwn<@Š1vwEGRnn]9}( @5qzOȈjlllAqƿ'"133XVg @mtHdmiA/.^LG@) ` Ae˖]AAA׷ ,**<44d ~0Oȕ0%Sxh(UW(77 8`ooMPTq`"Ν;5DD~~~nܸ٩I7gϞ,"]F &0t d5*HMG(+k# K '%%}j555,X(bZZZ3g~{}ww&I8p@(S+a&sLv%%s2R nn.?w9rLn|||;6O4i|CCC˒%K|=Z;PPPPPUUuFc<`8%I9QrJ~: ))?u:]þ}4rRնЙccAp([sR8;8$ٳgCގ~h4ӧ @tZ `F adurv`2 G#08JEEEt"r;AE3=>l֖z`IpOt P򼬤A hh "b `  &48܁1h{xQQz"$n\IiىQ!F G:1Al ⥘?.zֲq/^Vۂ>a0 E"2>>dهM-*w3e'ɈVTVWRܻ8\nH)O{b'cƘܹs~ S4ʪ {VEDD`aAAׇ۷nr`ڴd76:99|>JJJTaZQQm۶T*d롡ïc>< . J1ahhk x{hi ]1x6Hjkk3{{{{^6LCɓu:݀gRww  `00@R(_R(IގDQUU4vҝd:qDAʙzX<.$999ۈjkk:.]d2 _{ϵZmАIќ ){=R`P&:UARBPe2-3ii%CHH8bccCO:qܹ5~-J<[[[p) {D--Js@iiv$qR5EdeZ`Y$ L{#Bٳ +** +Vؗ{FS[PPPT*0fi>ҷ6'fRr/ R14k#X M2O_~{֬Y궶{:ΰu֌5kh4Z?99`"#b[[jA>8O?oK[zVGG7ҚJrv%`VUujjn$T{{{'&"!999sĉ[g與03Uqg1 NY97ޠ}%XQ4W_} BuuMwӏvy(!NL:™. L>1'&޽{NzC~~9sqƿ9gs HNx[| ]U*իFQŖ}<pc=RHxrV{jHD"c0,,캗jÆ KU*(2 _cYK4n"jh~n&"񗀌eϐ"[IENDB`qmidinet-1.0.1/src/PaxHeaders/appdata0000644000000000000000000000013214772454326014543 xustar0030 mtime=1743411414.378458544 30 atime=1743411414.378251695 30 ctime=1743411414.378458544 qmidinet-1.0.1/src/appdata/0000755000175000001440000000000014772454326014610 5ustar00rncbcusersqmidinet-1.0.1/src/appdata/PaxHeaders/org.rncbc.qmidinet.metainfo.xml0000644000000000000000000000013214772454326022631 xustar0030 mtime=1743411414.378458544 30 atime=1743411414.378433797 30 ctime=1743411414.378458544 qmidinet-1.0.1/src/appdata/org.rncbc.qmidinet.metainfo.xml0000644000175000001440000000317414772454326022626 0ustar00rncbcusers org.rncbc.qmidinet FSFAP GPL-2.0+ QmidiNet A MIDI Network Gateway via UDP/IP Multicast

QmidiNet is a MIDI network gateway application that sends and receives MIDI data (ALSA Sequencer and/or JACK MIDI) over the network, using UDP/IP multicast. Inspired by multimidicast (http\://llg.cubic.org/tools) and designed to be compatible with ipMIDI for Windows (http\://nerds.de).

org.rncbc.qmidinet.desktop qmidinet https://qmidinet.sourceforge.io/image/qmidinet.png The system tray icon showing the application in action MIDI ALSA JACK Multicast Network UDP IP Qt https://qmidinet.sourceforge.io rncbc.org rncbc aka. Rui Nuno Capela rncbc@rncbc.org
qmidinet-1.0.1/src/appdata/PaxHeaders/org.rncbc.qmidinet.desktop0000644000000000000000000000013214772454326021701 xustar0030 mtime=1743411414.378433797 30 atime=1743411414.378433797 30 ctime=1743411414.378433797 qmidinet-1.0.1/src/appdata/org.rncbc.qmidinet.desktop0000644000175000001440000000074014772454326021672 0ustar00rncbcusers[Desktop Entry] Name=QmidiNet Version=1.0 GenericName=MIDI Network GenericName[fr]=Réseau MIDI Comment=QmidiNet is a MIDI Network Gateway via UDP/IP Multicast Comment[fr]=QmidiNet est une passerelle de réseau MIDI via UDP/IP Multicast Exec=qmidinet Icon=org.rncbc.qmidinet Categories=Audio;AudioVideo;Midi;X-Alsa;X-Jack;Qt; Keywords=MIDI;ALSA;JACK;Multicast;Network;UDP;IP;Qt; Terminal=false Type=Application StartupWMClass=qmidinet X-Window-Icon=qmidinet X-SuSE-translate=true qmidinet-1.0.1/src/PaxHeaders/qmidinetUdpDevice.h0000644000000000000000000000013214772454326016762 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetUdpDevice.h0000644000175000001440000000576014772454326016762 0ustar00rncbcusers// qmidinetUdpDevice.h // /**************************************************************************** Copyright (C) 2010-2020, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetUdpDevice_h #define __qmidinetUdpDevice_h #include "qmidinetAbout.h" #include #if !defined(CONFIG_IPV6) #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) #include #else #include #include #endif #endif // !CONFIG_IPV6 #include #include #if defined(CONFIG_IPV6) #include #include #endif //---------------------------------------------------------------------------- // qmidinetUdpDevice -- Network interface device (UDP/IP). class qmidinetUdpDevice : public QObject { Q_OBJECT public: // Constructor. qmidinetUdpDevice(QObject *pParent = nullptr); // Destructor. ~qmidinetUdpDevice(); // Kind of singleton reference. static qmidinetUdpDevice *getInstance(); // Device initialization method. bool open(const QString& sInterface, const QString& sUdpAddr, int iUdpPort, int iNumPorts = 1); // Device termination method. void close(); // Data transmission methods. bool sendData(unsigned char *data, unsigned short len, int port = 0) const; void recvData(unsigned char *data, unsigned short len, int port = 0); signals: // Received data signal. void received(QByteArray data, int port); public slots: // Receive data slot. void receive(QByteArray data, int port); #if defined(CONFIG_IPV6) protected slots: // Process incoming datagrams. void readPendingDatagrams(); #else protected: // Get interface address from supplied name. static bool get_address(int sock, struct in_addr *iaddr, const char *ifname); #endif // !CONFIG_IPV6 private: // Instance variables, int m_nports; #if defined(CONFIG_IPV6) QUdpSocket **m_sockin; QUdpSocket **m_sockout; QHostAddress m_udpaddr; int *m_udpport; #else int *m_sockin; int *m_sockout; struct sockaddr_in *m_addrout; // Network receiver thread. class qmidinetUdpDeviceThread *m_pRecvThread; #endif // !CONFIG_IPV6 // Kind-of singleton reference. static qmidinetUdpDevice *g_pDevice; }; #endif // __qmidinetUdpDevice_h // end of qmidinetUdpDevice.h qmidinet-1.0.1/src/PaxHeaders/qmidinet.qrc0000644000000000000000000000013214772454326015527 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinet.qrc0000644000175000001440000000052614772454326015522 0ustar00rncbcusers images/qmidinet.png images/qmidinet.svg images/iconError.png images/iconSend.png images/iconReceive.png images/menuOptions.png images/menuReset.png images/menuQuit.png qmidinet-1.0.1/src/PaxHeaders/qmidinetOptionsForm.cpp0000644000000000000000000000013214772454326017724 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetOptionsForm.cpp0000644000175000001440000001455414772454326017725 0ustar00rncbcusers// qmidinetOptionsForm.cpp // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinetAbout.h" #include "qmidinetOptionsForm.h" #include "qmidinetOptions.h" #include #if defined(CONFIG_IPV6) #include #endif //---------------------------------------------------------------------------- // qmidinetOptionsForm -- UI wrapper form. // Constructor. qmidinetOptionsForm::qmidinetOptionsForm ( QWidget *pParent ) : QDialog(pParent) { // Setup UI struct... m_ui.setupUi(this); #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) QDialog::setWindowIcon(QIcon(":/images/qmidinet.png")); #endif // Initialize the dialog widgets with default settings... m_sDefInterface = tr("(Any)"); m_ui.InterfaceComboBox->clear(); m_ui.InterfaceComboBox->addItem(m_sDefInterface); #if defined(CONFIG_IPV6) foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) { if (iface.isValid() && iface.flags().testFlag(QNetworkInterface::CanMulticast) && iface.flags().testFlag(QNetworkInterface::IsUp) && iface.flags().testFlag(QNetworkInterface::IsRunning) && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { m_ui.InterfaceComboBox->addItem(iface.name()); } } #else m_ui.InterfaceComboBox->addItem("wlan0"); m_ui.InterfaceComboBox->addItem("eth0"); #endif m_ui.UdpAddrComboBox->clear(); m_ui.UdpAddrComboBox->addItem(QMIDINET_UDP_IPV4_ADDR); #if defined(CONFIG_IPV6) m_ui.UdpAddrComboBox->addItem(QMIDINET_UDP_IPV6_ADDR); #endif m_ui.UdpPortSpinBox->setValue(QMIDINET_UDP_PORT); // Populate dialog widgets with current settings... qmidinetOptions *pOptions = qmidinetOptions::getInstance(); if (pOptions) { if (pOptions->sInterface.isEmpty()) m_ui.InterfaceComboBox->setCurrentIndex(0); else m_ui.InterfaceComboBox->setEditText(pOptions->sInterface); if (pOptions->sUdpAddr.isEmpty()) m_ui.UdpAddrComboBox->setCurrentIndex(0); else m_ui.UdpAddrComboBox->setEditText(pOptions->sUdpAddr); m_ui.UdpPortSpinBox->setValue(pOptions->iUdpPort); m_ui.NumPortsSpinBox->setValue(pOptions->iNumPorts); m_ui.AlsaMidiCheckBox->setChecked(pOptions->bAlsaMidi); m_ui.JackMidiCheckBox->setChecked(pOptions->bJackMidi); } #ifndef CONFIG_ALSA_MIDI m_ui.AlsaMidiCheckBox->setEnabled(false); #endif #ifndef CONFIG_JACK_MIDI m_ui.JackMidiCheckBox->setEnabled(false); #endif // Start clean. m_iDirtyCount = 0; // Try to fix window geometry. adjustSize(); // UI signal/slot connections... QObject::connect(m_ui.InterfaceComboBox, SIGNAL(editTextChanged(const QString&)), SLOT(change())); QObject::connect(m_ui.UdpAddrComboBox, SIGNAL(editTextChanged(const QString&)), SLOT(change())); QObject::connect(m_ui.UdpPortSpinBox, SIGNAL(valueChanged(int)), SLOT(change())); QObject::connect(m_ui.NumPortsSpinBox, SIGNAL(valueChanged(int)), SLOT(change())); QObject::connect(m_ui.AlsaMidiCheckBox, SIGNAL(toggled(bool)), SLOT(change())); QObject::connect(m_ui.JackMidiCheckBox, SIGNAL(toggled(bool)), SLOT(change())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(accepted()), SLOT(accept())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(rejected()), SLOT(reject())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(clicked(QAbstractButton *)), SLOT(buttonClick(QAbstractButton *))); } // Change settings (anything else slot). void qmidinetOptionsForm::change (void) { ++m_iDirtyCount; } // Accept settings (OK button slot). void qmidinetOptionsForm::accept (void) { // Save options... if (m_iDirtyCount > 0) { qmidinetOptions *pOptions = qmidinetOptions::getInstance(); if (pOptions) { // Display options... pOptions->sInterface = m_ui.InterfaceComboBox->currentText(); pOptions->sUdpAddr = m_ui.UdpAddrComboBox->currentText(); pOptions->iUdpPort = m_ui.UdpPortSpinBox->value(); pOptions->iNumPorts = m_ui.NumPortsSpinBox->value(); pOptions->bAlsaMidi = m_ui.AlsaMidiCheckBox->isChecked(); pOptions->bJackMidi = m_ui.JackMidiCheckBox->isChecked(); // Take care of some translatable adjustments... if (pOptions->sInterface == m_sDefInterface) pOptions->sInterface.clear(); // Save/commit to disk. pOptions->saveOptions(); // Clean all dirt... m_iDirtyCount = 0; } } // Just go with dialog acceptance QDialog::accept(); } // Reject options (Cancel button slot). void qmidinetOptionsForm::reject (void) { // Check if there's any pending changes... if (m_iDirtyCount > 0) { switch (QMessageBox::warning(this, QDialog::windowTitle(), tr("Some settings have been changed.\n\n" "Do you want to apply the changes?"), QMessageBox::Apply | QMessageBox::Discard | QMessageBox::Cancel)) { case QMessageBox::Discard: break; case QMessageBox::Apply: accept(); default: return; } } // Just go with dialog rejection... QDialog::reject(); } // Reset options (generic button slot). void qmidinetOptionsForm::buttonClick ( QAbstractButton *pButton ) { const QDialogButtonBox::ButtonRole buttonRole = m_ui.DialogButtonBox->buttonRole(pButton); if (buttonRole == QDialogButtonBox::ResetRole) { m_ui.InterfaceComboBox->setCurrentIndex(0); #if defined(CONFIG_IPV6) const QString& sUdpAddr = m_ui.UdpAddrComboBox->currentText(); QHostAddress addr; if (addr.setAddress(sUdpAddr) && addr.protocol() == QAbstractSocket::IPv6Protocol) m_ui.UdpAddrComboBox->setEditText(QMIDINET_UDP_IPV6_ADDR); else #endif m_ui.UdpAddrComboBox->setEditText(QMIDINET_UDP_IPV4_ADDR); m_ui.UdpPortSpinBox->setValue(QMIDINET_UDP_PORT); } } // end of qmidinetOptionsForm.cpp qmidinet-1.0.1/src/PaxHeaders/qmidinet.h0000644000000000000000000000013214772454326015171 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinet.h0000644000175000001440000000663014772454326015166 0ustar00rncbcusers// qmidinet.h // /**************************************************************************** Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinet_h #define __qmidinet_h #include "qmidinetUdpDevice.h" #include "qmidinetAlsaMidiDevice.h" #include "qmidinetJackMidiDevice.h" #include #include #include // Forward decls. class qmidinetSystemTrayIcon; #ifdef CONFIG_XUNIQUE class QSharedMemory; class QLocalServer; #endif // CONFIG_XUNIQUE //------------------------------------------------------------------------- // qmidinetApplication -- Singleton application instance. // class qmidinetApplication : public QObject { Q_OBJECT public: // Constructor. qmidinetApplication(int& argc, char **argv, bool bGUI); // Destructor. ~qmidinetApplication(); // Initializers. bool setup(); // Messager. void message(const QString& sTitle, const QString& sText); // Simple accessor. QCoreApplication *app() const { return m_pApp; } #ifdef CONFIG_XUNIQUE // Initialize instance! bool init(); #endif public slots: // Action slots... void reset(); #ifdef CONFIG_JACK_MIDI void shutdown(); #endif #ifdef CONFIG_XUNIQUE protected slots: // Local server slots. void newConnectionSlot(); void readyReadSlot(); protected: // Local server/shmem setup/cleanup. bool setupServer(); void clearServer(); #endif private: // Instance variables. QCoreApplication *m_pApp; qmidinetSystemTrayIcon *m_pIcon; #ifdef CONFIG_ALSA_MIDI qmidinetAlsaMidiDevice m_alsa; #endif #ifdef CONFIG_JACK_MIDI qmidinetJackMidiDevice m_jack; #endif qmidinetUdpDevice m_udpd; #ifdef CONFIG_XUNIQUE QString m_sUnique; QSharedMemory *m_pMemory; QLocalServer *m_pServer; #endif // CONFIG_XUNIQUE }; //------------------------------------------------------------------------- // qmidinetSystemTrayIcon -- Singleton widget instance. // class qmidinetSystemTrayIcon : public QSystemTrayIcon { Q_OBJECT public: // Constructor. qmidinetSystemTrayIcon(qmidinetApplication *pApp); // Initializers. void show(bool bSetup); // Message bubble/dialog. void message(const QString& sTitle, const QString& sText); public slots: // Action slots... void options(); void reset(); void about(); // Handle system tray activity. void activated(QSystemTrayIcon::ActivationReason); // Send/receive ON slots. void sending(); void receiving(); protected slots: // Send/receive timer OFF slot. void timerOff(); private: // Instance variables. qmidinetApplication *m_pApp; QMenu m_menu; int m_iSending; int m_iReceiving; }; #endif // __qmidinet_h // end of qmidinet.h qmidinet-1.0.1/src/PaxHeaders/qmidinetOptions.h0000644000000000000000000000013214772454326016545 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetOptions.h0000644000175000001440000000437514772454326016546 0ustar00rncbcusers// qmidinetOptions.h // /**************************************************************************** Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetOptions_h #define __qmidinetOptions_h #include #include // Some hard-coded default options.... #define QMIDINET_UDP_IPV4_ADDR "225.0.0.37" #define QMIDINET_UDP_IPV6_ADDR "ff12::37" #define QMIDINET_UDP_PORT 21928 //------------------------------------------------------------------------- // qmidinetOptions - Prototype settings class (singleton). // class qmidinetOptions { public: // Constructor. qmidinetOptions(); // Default destructor. ~qmidinetOptions(); // The settings object accessor. QSettings& settings(); // Explicit I/O methods. void loadOptions(); void saveOptions(); // Command line arguments parser. bool parse_args(const QStringList& args); #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) void show_error(const QString& msg); #else // Command line usage helper. void print_usage(const QString& arg0); #endif // General options... int iNumPorts; bool bAlsaMidi; bool bJackMidi; // Network options... QString sInterface; QString sUdpAddr; int iUdpPort; // Singleton instance accessor. static qmidinetOptions *getInstance(); private: // Settings member variables. QSettings m_settings; // The singleton instance. static qmidinetOptions *g_pOptions; }; #endif // __qmidinetOptions_h // end of qmidinetOptions.h qmidinet-1.0.1/src/PaxHeaders/qmidinetAlsaMidiDevice.h0000644000000000000000000000013214772454326017715 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetAlsaMidiDevice.h0000644000175000001440000000511114772454326017703 0ustar00rncbcusers// qmidinetAlsaMidiDevice.h // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetAlsaMidiDevice_h #define __qmidinetAlsaMidiDevice_h #include "qmidinetAbout.h" #ifdef CONFIG_ALSA_MIDI #include #include #include #include #include //---------------------------------------------------------------------------- // qmidinetAlsaMidiDevice -- MIDI interface object. class qmidinetAlsaMidiDevice : public QObject { Q_OBJECT public: // Constructor. qmidinetAlsaMidiDevice(QObject *pParent = nullptr); // Destructor. ~qmidinetAlsaMidiDevice(); // Kind of singleton reference. static qmidinetAlsaMidiDevice *getInstance(); // Device initialization method. bool open(const QString& sClientName, int iNumPorts = 1); // Device termination method. void close(); // MIDI event capture method. void capture(snd_seq_event_t *pEv); // Data transmission methods. bool sendData(unsigned char *data, unsigned short len, int port = 0) const; void recvData(unsigned char *data, unsigned short len, int port = 0); signals: // Received data signal. void received(QByteArray data, int port); public slots: // Receive data slot. void receive(QByteArray data, int port); private: // Instance variables, int m_nports; // Instance variables. snd_seq_t *m_pAlsaSeq; int m_iAlsaClient; int *m_piAlsaPort; snd_midi_event_t **m_ppAlsaEncoder; snd_midi_event_t *m_pAlsaDecoder; // Network receiver thread. class qmidinetAlsaMidiThread *m_pRecvThread; // Kind-of singleton reference. static qmidinetAlsaMidiDevice *g_pDevice; }; #endif // CONFIG_ALSA_MIDI #endif // __qmidinetAlsaMidiDevice_h // end of qmidinetAlsaMidiDevice.h qmidinet-1.0.1/src/PaxHeaders/qmidinetAlsaMidiDevice.cpp0000644000000000000000000000013214772454326020250 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetAlsaMidiDevice.cpp0000644000175000001440000002334514772454326020247 0ustar00rncbcusers// qmidinetAlsaMidiDevice.cpp // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinetAlsaMidiDevice.h" #ifdef CONFIG_ALSA_MIDI #include //---------------------------------------------------------------------------- // qmidinetAlsaMidiThread -- ALSA MIDI listener thread. // class qmidinetAlsaMidiThread : public QThread { public: // Constructor. qmidinetAlsaMidiThread(snd_seq_t *pAlsaSeq); // Run-state accessors. void setRunState(bool bRunState); bool runState() const; protected: // The main thread executive. void run(); private: // The listener socket. snd_seq_t *m_pAlsaSeq; // Whether the thread is logically running. volatile bool m_bRunState; }; // Constructor. qmidinetAlsaMidiThread::qmidinetAlsaMidiThread ( snd_seq_t *pAlsaSeq ) : QThread(), m_pAlsaSeq(pAlsaSeq), m_bRunState(false) { } // Run-state accessors. void qmidinetAlsaMidiThread::setRunState ( bool bRunState ) { m_bRunState = bRunState; } bool qmidinetAlsaMidiThread::runState (void) const { return m_bRunState; } // The main thread executive. void qmidinetAlsaMidiThread::run (void) { const int nfds = snd_seq_poll_descriptors_count(m_pAlsaSeq, POLLIN); struct pollfd pfds[nfds]; snd_seq_poll_descriptors(m_pAlsaSeq, pfds, nfds, POLLIN); m_bRunState = true; int iPoll = 0; while (m_bRunState && iPoll >= 0) { // Wait for events... iPoll = poll(pfds, nfds, 1000); while (iPoll > 0) { snd_seq_event_t *pEv = nullptr; snd_seq_event_input(m_pAlsaSeq, &pEv); // Process input event - ... // - enqueue to input track mapping; qmidinetAlsaMidiDevice::getInstance()->capture(pEv); // snd_seq_free_event(pEv); iPoll = snd_seq_event_input_pending(m_pAlsaSeq, 0); } } } //---------------------------------------------------------------------------- // qmidinetAlsaMidiDevice -- MIDI interface device (ALSA). // qmidinetAlsaMidiDevice *qmidinetAlsaMidiDevice::g_pDevice = nullptr; // Constructor. qmidinetAlsaMidiDevice::qmidinetAlsaMidiDevice ( QObject *pParent ) : QObject(pParent), m_nports(0), m_pAlsaSeq(nullptr), m_iAlsaClient(-1), m_piAlsaPort(nullptr), m_ppAlsaEncoder(nullptr), m_pAlsaDecoder(nullptr), m_pRecvThread(nullptr) { g_pDevice = this; } // Destructor. qmidinetAlsaMidiDevice::~qmidinetAlsaMidiDevice (void) { close(); g_pDevice = nullptr; } // Kind of singleton reference. qmidinetAlsaMidiDevice *qmidinetAlsaMidiDevice::getInstance (void) { return g_pDevice; } // Device initialization method. bool qmidinetAlsaMidiDevice::open ( const QString& sClientName, int iNumPorts ) { // Close if already open. close(); // Open new ALSA sequencer client... if (snd_seq_open(&m_pAlsaSeq, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) return false; int i; // Set client identification... const QByteArray aClientName = sClientName.toLocal8Bit(); snd_seq_set_client_name(m_pAlsaSeq, aClientName.constData()); m_iAlsaClient = snd_seq_client_id(m_pAlsaSeq); m_nports = iNumPorts; // Create duplex ports. m_piAlsaPort = new int [m_nports]; for (i = 0; i < m_nports; ++i) m_piAlsaPort[i] = -1; const QString sPortName("port %1"); for (i = 0; i < m_nports; ++i) { const QByteArray aPortName = sPortName.arg(i).toLocal8Bit(); int port = snd_seq_create_simple_port( m_pAlsaSeq, aPortName.constData(), SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); if (port < 0) { fprintf(stderr, "snd_seq_create_simple_port: %s\n", snd_strerror(port)); return false; } m_piAlsaPort[i] = port; } // Create MIDI (output) encoders. m_ppAlsaEncoder = new snd_midi_event_t * [m_nports]; for (i = 0; i < m_nports; ++i) m_ppAlsaEncoder[i] = nullptr; for (i = 0; i < m_nports; ++i) { long err = snd_midi_event_new(1024, &m_ppAlsaEncoder[i]); if (err < 0) { fprintf(stderr, "snd_midi_event_new: %s\n", snd_strerror(err)); return false; } } // Create MIDI (input) decoders. long err = snd_midi_event_new(1024, &m_pAlsaDecoder); if (err < 0) { fprintf(stderr, "snd_midi_event_new: %s\n", snd_strerror(err)); return false; } // Start listener thread... m_pRecvThread = new qmidinetAlsaMidiThread(m_pAlsaSeq); m_pRecvThread->start(); // Done. return true; } // Device termination method. void qmidinetAlsaMidiDevice::close (void) { if (m_pRecvThread) { if (m_pRecvThread->isRunning()) do { m_pRecvThread->setRunState(false); // m_pRecvThread->terminate(); } while (!m_pRecvThread->wait(200)); delete m_pRecvThread; m_pRecvThread = nullptr; } if (m_pAlsaDecoder) { snd_midi_event_free(m_pAlsaDecoder); m_pAlsaDecoder = nullptr; } if (m_ppAlsaEncoder) { for (int i = 0; i < m_nports; ++i) { if (m_ppAlsaEncoder[i]) snd_midi_event_free(m_ppAlsaEncoder[i]); } delete [] m_ppAlsaEncoder; m_ppAlsaEncoder = nullptr; } if (m_piAlsaPort) { for (int i = 0; i < m_nports; ++i) { if (m_piAlsaPort[i] >= 0) snd_seq_delete_simple_port(m_pAlsaSeq, m_piAlsaPort[i]); } delete [] m_piAlsaPort; m_piAlsaPort = nullptr; } if (m_pAlsaSeq) { snd_seq_close(m_pAlsaSeq); m_iAlsaClient = -1; m_pAlsaSeq = nullptr; } m_nports = 0; } // MIDI event capture method. void qmidinetAlsaMidiDevice::capture ( snd_seq_event_t *pEv ) { if (pEv == nullptr) return; // Ignore some events -- these are all ALSA internal // events, which don't produce any MIDI bytes... switch(pEv->type) { case SND_SEQ_EVENT_OSS: case SND_SEQ_EVENT_CLIENT_START: case SND_SEQ_EVENT_CLIENT_EXIT: case SND_SEQ_EVENT_CLIENT_CHANGE: case SND_SEQ_EVENT_PORT_START: case SND_SEQ_EVENT_PORT_EXIT: case SND_SEQ_EVENT_PORT_CHANGE: case SND_SEQ_EVENT_PORT_SUBSCRIBED: case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: case SND_SEQ_EVENT_USR0: case SND_SEQ_EVENT_USR1: case SND_SEQ_EVENT_USR2: case SND_SEQ_EVENT_USR3: case SND_SEQ_EVENT_USR4: case SND_SEQ_EVENT_USR5: case SND_SEQ_EVENT_USR6: case SND_SEQ_EVENT_USR7: case SND_SEQ_EVENT_USR8: case SND_SEQ_EVENT_USR9: case SND_SEQ_EVENT_BOUNCE: case SND_SEQ_EVENT_USR_VAR0: case SND_SEQ_EVENT_USR_VAR1: case SND_SEQ_EVENT_USR_VAR2: case SND_SEQ_EVENT_USR_VAR3: case SND_SEQ_EVENT_USR_VAR4: case SND_SEQ_EVENT_NONE: return; } #ifdef CONFIG_DEBUG // - show (input) event for debug purposes... fprintf(stderr, "ALSA MIDI In Port %d: 0x%02x", pEv->dest.port, pEv->type); if (pEv->type == SND_SEQ_EVENT_SYSEX) { fprintf(stderr, " SysEx {"); unsigned char *data = (unsigned char *) pEv->data.ext.ptr; for (unsigned int i = 0; i < pEv->data.ext.len; ++i) fprintf(stderr, " %02x", data[i]); fprintf(stderr, " }\n"); } else { for (unsigned int i = 0; i < sizeof(pEv->data.raw8.d); ++i) fprintf(stderr, " %3d", pEv->data.raw8.d[i]); fprintf(stderr, "\n"); } #endif if (pEv->type == SND_SEQ_EVENT_SYSEX) { unsigned char *data = (unsigned char *) pEv->data.ext.ptr; recvData(data, pEv->data.ext.len, pEv->dest.port); } else { // Decode ALSA event into raw bytes... unsigned char data[1024]; long n = snd_midi_event_decode(m_pAlsaDecoder, data, sizeof(data), pEv); if (n > 0) recvData(data, n, pEv->dest.port); else if (n < 0) fprintf(stderr, "snd_midi_event_decode: %s\n", snd_strerror(n)); snd_midi_event_reset_decode(m_pAlsaDecoder); } } // Data transmission methods. bool qmidinetAlsaMidiDevice::sendData ( unsigned char *data, unsigned short len, int port ) const { if (port < 0 || port >= m_nports) return false; snd_seq_event_t ev; unsigned char *d = data; long l = len; while (l > 0) { snd_seq_event_t *pEv = &ev; snd_seq_ev_clear(pEv); snd_seq_ev_set_source(pEv, m_piAlsaPort[port]); snd_seq_ev_set_subs(pEv); snd_seq_ev_set_direct(pEv); long n = snd_midi_event_encode(m_ppAlsaEncoder[port], d, l, pEv); if (n < 0) { fprintf(stderr, "snd_midi_event_encode: %s\n", snd_strerror(n)); return false; } else if (n > 0) { #ifdef CONFIG_DEBUG // - show (output) event for debug purposes... fprintf(stderr, "ALSA MIDI Out Port %d: 0x%02x", pEv->source.port, pEv->type); if (pEv->type == SND_SEQ_EVENT_SYSEX) { fprintf(stderr, " SysEx {"); unsigned char *data = (unsigned char *) pEv->data.ext.ptr; for (unsigned int i = 0; i < pEv->data.ext.len; i++) fprintf(stderr, " %02x", data[i]); fprintf(stderr, " }\n"); } else { for (unsigned int i = 0; i < sizeof(pEv->data.raw8.d); i++) fprintf(stderr, " %3d", pEv->data.raw8.d[i]); fprintf(stderr, "\n"); } #endif snd_seq_event_output(m_pAlsaSeq, pEv); l -= n; d += n; } else break; } snd_seq_drain_output(m_pAlsaSeq); return true; } void qmidinetAlsaMidiDevice::recvData ( unsigned char *data, unsigned short len, int port ) { emit received(QByteArray((const char *) data, len), port); } // Receive data slot. void qmidinetAlsaMidiDevice::receive ( QByteArray data, int port ) { sendData((unsigned char *) data.constData(), data.length(), port); } #endif // CONFIG_ALSA_MIDI // end of qmidinetAlsaMidiDevice.h qmidinet-1.0.1/src/PaxHeaders/CMakeLists.txt0000644000000000000000000000013214772454326015746 xustar0030 mtime=1743411414.378251695 30 atime=1743411414.378251695 30 ctime=1743411414.378251695 qmidinet-1.0.1/src/CMakeLists.txt0000644000175000001440000000527514772454326015747 0ustar00rncbcusers# project (qmidinet) set (CMAKE_INCLUDE_CURRENT_DIR ON) set (CMAKE_AUTOUIC ON) set (CMAKE_AUTOMOC ON) set (CMAKE_AUTORCC ON) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.h) file (REMOVE ${CMAKE_CURRENT_SOURCE_DIR}/config.h) endif () configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) set (HEADERS qmidinet.h qmidinetAbout.h qmidinetUdpDevice.h qmidinetAlsaMidiDevice.h qmidinetJackMidiDevice.h qmidinetOptions.h qmidinetOptionsForm.h ) set (SOURCES qmidinet.cpp qmidinetUdpDevice.cpp qmidinetAlsaMidiDevice.cpp qmidinetJackMidiDevice.cpp qmidinetOptions.cpp qmidinetOptionsForm.cpp ) set (FORMS qmidinetOptionsForm.ui ) set (RESOURCES qmidinet.qrc ) add_executable (${PROJECT_NAME} ${HEADERS} ${SOURCES} ${FORMS} ${RESOURCES} ) # Add some debugger flags. if (CONFIG_DEBUG AND UNIX AND NOT APPLE) set (CONFIG_DEBUG_OPTIONS -g -fsanitize=address -fno-omit-frame-pointer) target_compile_options (${PROJECT_NAME} PRIVATE ${CONFIG_DEBUG_OPTIONS}) target_link_options (${PROJECT_NAME} PRIVATE ${CONFIG_DEBUG_OPTIONS}) endif () set_target_properties (${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) if (WIN32) set_target_properties (${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE true) endif () if (APPLE) set_target_properties (${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE true) endif () target_link_libraries (${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Svg) if (CONFIG_IPV6 OR CONFIG_XUNIQUE) target_link_libraries (${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Network) endif () if (CONFIG_ALSA_MIDI) target_link_libraries (${PROJECT_NAME} PRIVATE PkgConfig::ALSA) endif () if (CONFIG_JACK_MIDI) target_link_libraries (${PROJECT_NAME} PRIVATE PkgConfig::JACK) endif () if (UNIX AND NOT APPLE) install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install (FILES images/${PROJECT_NAME}.png RENAME org.rncbc.${PROJECT_NAME}.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps) install (FILES images/${PROJECT_NAME}.svg RENAME org.rncbc.${PROJECT_NAME}.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) install (FILES appdata/org.rncbc.${PROJECT_NAME}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install (FILES appdata/org.rncbc.${PROJECT_NAME}.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install (FILES man1/${PROJECT_NAME}.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install (FILES man1/${PROJECT_NAME}.fr.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/fr/man1 RENAME ${PROJECT_NAME}.1) endif () if (WIN32) install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) endif () qmidinet-1.0.1/src/PaxHeaders/qmidinetAbout.h0000644000000000000000000000013214772454326016164 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetAbout.h0000644000175000001440000000246514772454326016163 0ustar00rncbcusers// qmidinetAbout.h // /**************************************************************************** Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetAbout_h #define __qmidinetAbout_h #include "config.h" #define QMIDINET_TITLE PROJECT_TITLE #define QMIDINET_SUBTITLE PROJECT_DESCRIPTION #define QMIDINET_WEBSITE PROJECT_HOMEPAGE_URL #define QMIDINET_COPYRIGHT PROJECT_COPYRIGHT #define QMIDINET_DOMAIN PROJECT_DOMAIN #endif // __qmidinetAbout_h // end of qmidinetAbout.h qmidinet-1.0.1/src/PaxHeaders/qmidinetOptionsForm.ui0000644000000000000000000000013214772454326017557 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetOptionsForm.ui0000644000175000001440000002202514772454326017550 0ustar00rncbcusers rncbc aka Rui Nuno Capela QmidiNet - A MIDI Network Gateway via UDP/IP Multicast Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. qmidinetOptionsForm 0 0 360 240 Options :/images/qmidinet.svg 75 true MIDI true 50 false &Number of Ports: NumPortsSpinBox 50 false true 1 32 1 Qt::Horizontal 20 8 50 false &ALSA 50 false &JACK Qt::Vertical 20 8 75 true Network true 50 false &Interface: InterfaceComboBox 50 false true Qt::Horizontal 20 8 50 false &UDP Address: UdpAddrComboBox 50 false true 50 false UDP &Port: UdpPortSpinBox 50 false true 65535 Qt::Horizontal 20 8 Qt::Vertical QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset NumPortsSpinBox AlsaMidiCheckBox JackMidiCheckBox InterfaceComboBox UdpAddrComboBox UdpPortSpinBox DialogButtonBox qmidinet-1.0.1/src/PaxHeaders/config.h.cmake0000644000000000000000000000013214772454326015703 xustar0030 mtime=1743411414.378458544 30 atime=1743411414.378458544 30 ctime=1743411414.378458544 qmidinet-1.0.1/src/config.h.cmake0000644000175000001440000000343614772454326015701 0ustar00rncbcusers#ifndef CONFIG_H #define CONFIG_H /* Define to the title of this package. */ #cmakedefine PROJECT_TITLE "@PROJECT_TITLE@" /* Define to the name of this package. */ #cmakedefine PROJECT_NAME "@PROJECT_NAME@" /* Define to the version of this package. */ #cmakedefine PROJECT_VERSION "@PROJECT_VERSION@" /* Define to the description of this package. */ #cmakedefine PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" /* Define to the homepage of this package. */ #cmakedefine PROJECT_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@" /* Define to the copyright of this package. */ #cmakedefine PROJECT_COPYRIGHT "@PROJECT_COPYRIGHT@" /* Define to the domain of this package. */ #cmakedefine PROJECT_DOMAIN "@PROJECT_DOMAIN@" /* Default installation prefix. */ #cmakedefine CONFIG_PREFIX "@CONFIG_PREFIX@" /* Define to target installation dirs. */ #cmakedefine CONFIG_BINDIR "@CONFIG_BINDIR@" #cmakedefine CONFIG_LIBDIR "@CONFIG_LIBDIR@" #cmakedefine CONFIG_DATADIR "@CONFIG_DATADIR@" #cmakedefine CONFIG_MANDIR "@CONFIG_MANDIR@" /* Define if debugging is enabled. */ #cmakedefine CONFIG_DEBUG @CONFIG_DEBUG@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SIGNAL_H @HAVE_SIGNAL_H@ /* Define if debugging is enabled. */ #cmakedefine CONFIG_DEBUG @CONFIG_DEBUG@ /* Define if ALSA library is available. */ #cmakedefine CONFIG_ALSA_MIDI @CONFIG_ALSA_MIDI@ /* Define if JACK MIDI support is available. */ #cmakedefine CONFIG_JACK_MIDI @CONFIG_JACK_MIDI@ /* Define if IPv6 is supported */ #cmakedefine CONFIG_IPV6 @CONFIG_IPV6@ /* Define if IPv6 is supported */ #cmakedefine CONFIG_IPV6 @CONFIG_IPV6@ /* Define if Unique/Single instance is enabled. */ #cmakedefine CONFIG_XUNIQUE @CONFIG_XUNIQUE@ /* Define if Wayland is supported */ #cmakedefine CONFIG_WAYLAND @CONFIG_WAYLAND@ #endif /* CONFIG_H */ qmidinet-1.0.1/src/PaxHeaders/qmidinetOptions.cpp0000644000000000000000000000013214772454326017100 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetOptions.cpp0000644000175000001440000002437314772454326017101 0ustar00rncbcusers// qmidinetOptions.cpp // /**************************************************************************** Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinetAbout.h" #include "qmidinetOptions.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) #include #include #if defined(Q_OS_WINDOWS) #include #endif #endif //------------------------------------------------------------------------- // qmidinetOptions - Prototype settings structure (pseudo-singleton). // // Singleton instance pointer. qmidinetOptions *qmidinetOptions::g_pOptions = nullptr; // Singleton instance accessor (static). qmidinetOptions *qmidinetOptions::getInstance (void) { return g_pOptions; } // Constructor. qmidinetOptions::qmidinetOptions (void) : m_settings(QMIDINET_DOMAIN, QMIDINET_TITLE) { // Pseudo-singleton reference setup. g_pOptions = this; loadOptions(); } // Default Destructor. qmidinetOptions::~qmidinetOptions (void) { saveOptions(); // Pseudo-singleton reference shut-down. g_pOptions = nullptr; } // Explicit load method. void qmidinetOptions::loadOptions (void) { // And go into general options group. m_settings.beginGroup("/Options"); // General options... m_settings.beginGroup("/General"); iNumPorts = m_settings.value("/NumPorts", 1).toInt(); bAlsaMidi = m_settings.value("/AlsaMidi", true).toBool(); bJackMidi = m_settings.value("/JackMidi", false).toBool(); m_settings.endGroup(); // Network specific options... m_settings.beginGroup("/Network"); sInterface = m_settings.value("/Interface").toString(); sUdpAddr = m_settings.value("/UdpAddr", QMIDINET_UDP_IPV4_ADDR).toString(); iUdpPort = m_settings.value("/UdpPort", QMIDINET_UDP_PORT).toInt(); m_settings.endGroup(); m_settings.endGroup(); } // Explicit save method. void qmidinetOptions::saveOptions (void) { // And go into general options group. m_settings.beginGroup("/Options"); // General options... m_settings.beginGroup("/General"); m_settings.setValue("/NumPorts", iNumPorts); m_settings.setValue("/AlsaMidi", bAlsaMidi); m_settings.setValue("/JackMidi", bJackMidi); m_settings.endGroup(); // Network specific options... m_settings.beginGroup("/Network"); m_settings.setValue("/Interface", sInterface); m_settings.setValue("/UdpAddr", sUdpAddr); m_settings.setValue("/UdpPort", iUdpPort); m_settings.endGroup(); m_settings.endGroup(); // Save/commit to disk. m_settings.sync(); } // Settings accessor. QSettings& qmidinetOptions::settings (void) { return m_settings; } #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) void qmidinetOptions::show_error( const QString& msg ) { #if defined(Q_OS_WINDOWS) QMessageBox::information(nullptr, QApplication::applicationName(), msg); #else const QByteArray tmp = msg.toUtf8() + '\n'; ::fputs(tmp.constData(), stderr); #endif } #else // Help about command line options. void qmidinetOptions::print_usage ( const QString& arg0 ) { QTextStream out(stderr); const QString sEot = "\n\t"; const QString sEol = "\n\n"; out << QObject::tr("Usage: %1 [options]").arg(arg0) + sEol; out << QMIDINET_TITLE " - " << QObject::tr(QMIDINET_SUBTITLE) + sEol; out << QObject::tr("Options:") + sEol; out << " -n, --num-ports=[num-ports]" + sEot + QObject::tr("Use this number of ports (default = %1)") .arg(iNumPorts) + sEol; out << " -i, --interface=[interface]" + sEot + QObject::tr("Use specific network interface (default = %1)") .arg(sInterface.isEmpty() ? "all" : sInterface) + sEol; out << " -u, --udp-addr=[addr]" + sEot + QObject::tr("Use specific network address (default = %1)") .arg(sUdpAddr) + sEol; out << " -p, --udp-port=[port]" + sEot + QObject::tr("Use specific network port (default = %1)") .arg(iUdpPort) + sEol; out << " -a, --alsa-midi[=flag]" + sEot + QObject::tr("Enable ALSA MIDI (0|1|yes|no|on|off, default = %1)") .arg(int(bAlsaMidi)) + sEol; out << " -j, --jack-midi[=flag]" + sEot + QObject::tr("Enable JACK MIDI (0|1|yes|no|on|off, default = %1)") .arg(int(bJackMidi)) + sEol; out << " -g, --no-gui" + sEot + QObject::tr("Disable the graphical user interface (GUI)") + sEol; out << " -h, --help" + sEot + QObject::tr("Show help about command line options.") + sEol; out << " -v, --version" + sEot + QObject::tr("Show version information.") + sEol; } #endif // Parse command line arguments into m_settings. bool qmidinetOptions::parse_args ( const QStringList& args ) { #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QCommandLineParser parser; parser.setApplicationDescription( QMIDINET_TITLE " - " + QObject::tr(QMIDINET_SUBTITLE)); parser.addOption({{"n", "num-ports"}, QObject::tr("Use this number of ports (default = %1)") .arg(iNumPorts), "num"}); parser.addOption({{"i", "interface"}, QObject::tr("Use specific network interface (default = %1)") .arg(sInterface.isEmpty() ? "all" : sInterface), "name"}); parser.addOption({{"u", "udp-addr"}, QObject::tr("Use specific network address (default = %1)") .arg(sUdpAddr), "addr"}); parser.addOption({{"p", "udp-port"}, QObject::tr("Use specific network port (default = %1)") .arg(iUdpPort), "port"}); parser.addOption({{"a", "alsa-midi"}, QObject::tr("Enable ALSA MIDI (0|1|yes|no|on|off, default = %1)") .arg(int(bAlsaMidi)), "flag"}); parser.addOption({{"j", "jack-midi"}, QObject::tr("Enable JACK MIDI (0|1|yes|no|on|off, default = %1)") .arg(int(bJackMidi)), "flag"}); parser.addOption({{"g", "no-gui"}, QObject::tr("Disable the graphical user interface (GUI)")}); const QCommandLineOption& helpOption = parser.addHelpOption(); const QCommandLineOption& versionOption = parser.addVersionOption(); if (!parser.parse(args)) { show_error(parser.errorText()); return false; } if (parser.isSet(helpOption)) { show_error(parser.helpText()); return false; } if (parser.isSet(versionOption)) { QString sVersion = QString("%1 %2\n") .arg(QMIDINET_TITLE) .arg(QCoreApplication::applicationVersion()); sVersion += QString("Qt: %1").arg(qVersion()); #if defined(QT_STATIC) sVersion += "-static"; #endif sVersion += '\n'; show_error(sVersion); return false; } if (parser.isSet("num-ports")) { bool bOK = false; const int iVal = parser.value("num-ports").toInt(&bOK); if (!bOK) { show_error(QObject::tr("Option -n requires an argument (num).")); return false; } iNumPorts = iVal; } if (parser.isSet("interface")) { sInterface = parser.value("interface"); // Maybe empty! } if (parser.isSet("udp-addr")) { const QString& sVal = parser.value("udp-addr"); if (sVal.isEmpty()) { show_error(QObject::tr("Option -u requires an argument (addr).")); return false; } sUdpAddr = sVal; } if (parser.isSet("udp-port")) { bool bOK = false; const int iVal = parser.value("udp-port").toInt(&bOK); if (!bOK) { show_error(QObject::tr("Option -p requires an argument (port).")); return false; } iUdpPort = iVal; } if (parser.isSet("alsa-midi")) { const QString& sVal = parser.value("alsa-midi"); if (sVal.isEmpty()) { bAlsaMidi = true; } else { bAlsaMidi = !(sVal == "0" || sVal == "no" || sVal == "off"); } } if (parser.isSet("jack-midi")) { const QString& sVal = parser.value("jack-midi"); if (sVal.isEmpty()) { bJackMidi = true; } else { bJackMidi = !(sVal == "0" || sVal == "no" || sVal == "off"); } } if (parser.isSet("no-gui")) { // Ignored: parsed on startup... } #else QTextStream out(stderr); const QString sEot = "\n\t"; const QString sEol = "\n\n"; const int argc = args.count(); for (int i = 1; i < argc; ++i) { QString sVal; QString sArg = args.at(i); const int iEqual = sArg.indexOf('='); if (iEqual >= 0) { sVal = sArg.right(sArg.length() - iEqual - 1); sArg = sArg.left(iEqual); } else if (i < argc - 1) { sVal = args.at(i + 1); if (sVal[0] == '-') sVal.clear(); } if (sArg == "-n" || sArg == "--num-ports") { if (sVal.isEmpty()) { out << QObject::tr("Option -n requires an argument (num-ports).") + sEol; return false; } iNumPorts = sVal.toInt(); if (iEqual < 0) ++i; } else if (sArg == "-i" || sArg == "--interface") { sInterface = sVal; // Maybe empty! if (iEqual < 0) ++i; } else if (sArg == "-u" || sArg == "--udp-addr") { if (sVal.isEmpty()) { out << QObject::tr("Option -d requires an argument (address).") + sEol; return false; } sUdpAddr = sVal; if (iEqual < 0) ++i; } else if (sArg == "-p" || sArg == "--udp-port") { if (sVal.isEmpty()) { out << QObject::tr("Option -p requires an argument (port).") + sEol; return false; } iUdpPort = sVal.toInt(); if (iEqual < 0) ++i; } else if (sArg == "-a" || sArg == "--alsa-midi") { if (sVal.isEmpty()) { bAlsaMidi = true; } else { bAlsaMidi = !(sVal == "0" || sVal == "no" || sVal == "off"); if (iEqual < 0) ++i; } } else if (sArg == "-j" || sArg == "--jack-midi") { if (sVal.isEmpty()) { bJackMidi = true; } else { bJackMidi = !(sVal == "0" || sVal == "no" || sVal == "off"); if (iEqual < 0) ++i; } } else if (sArg == "-h" || sArg == "--help") { print_usage(args.at(0)); return false; } else if (sArg == "-v" || sArg == "--version") { out << QString("%1: %2\n") .arg(QMIDINET_TITLE) .arg(PROJECT_VERSION); out << QString("Qt: %1").arg(qVersion()); #if defined(QT_STATIC) out << "-static"; #endif out << '\n'; return false; } } #endif // Alright with argument parsing. return true; } // end of qmidinetOptions.cpp qmidinet-1.0.1/src/PaxHeaders/qmidinetJackMidiDevice.cpp0000644000000000000000000000013214772454326020240 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetJackMidiDevice.cpp0000644000175000001440000004125014772454326020232 0ustar00rncbcusers// qmidinetJackMidiDevice.cpp // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinetJackMidiDevice.h" #ifdef CONFIG_JACK_MIDI #include #include #include // JACK MIDI event, plus the port its destined for... struct qmidinetJackMidiEvent { jack_midi_event_t event; int port; }; //--------------------------------------------------------------------- // qmidinetJackMidiQueue - Home-brew sorter queue. // class qmidinetJackMidiQueue { public: // Constructor. qmidinetJackMidiQueue ( unsigned int size, unsigned int slack ) { m_pool = pool_create(size * (slack + sizeof(Slot))); m_items = new char * [size]; m_size = size; m_count = 0; m_dirty = 0; } // Destructor. ~qmidinetJackMidiQueue () { pool_delete(m_pool); delete [] m_items; } // Queue cleanup. void clear () { pool_clear(m_pool); m_count = 0; m_dirty = 0; } // Queue size accessor. int size () const { return m_size; } // Queue count accessor. int count () const { return m_count; } // Queue dirty accessor. bool isDirty () const { return (m_dirty > 0); } // Queue item push insert. char *push ( int port, jack_nframes_t time, size_t size ) { char *item = push_item(time, size + sizeof(unsigned short)); if (item) { *(unsigned short *) item = port; item += sizeof(unsigned short); } return item; } // Queue item pop/remove. char *pop ( int *port, jack_nframes_t *time, size_t *size ) { char *item = pop_item(time, size); if (item) { if (port) *port = *(unsigned short *) item; if (size) *size -= sizeof(unsigned short); item += sizeof(unsigned short); } return item; } protected: struct Slot { unsigned int size; union { unsigned long key; Slot *next; } u; }; static Slot *pool_slot ( char *data ) { return (Slot *) ((char *) data - sizeof(Slot)); } static unsigned int pool_slot_size ( char *data ) { return pool_slot(data)->size - sizeof(Slot); } static unsigned long pool_slot_key ( char *data ) { return pool_slot(data)->u.key; } void pool_clear ( Slot *pool ) { Slot *p = (Slot *) ((char *) pool + sizeof(Slot)); pool->u.next = p; p->size = pool->size - sizeof(Slot); p->u.next = nullptr; } Slot *pool_create ( unsigned int size ) { Slot *pool = (Slot *) ::malloc(sizeof(Slot) + size); pool->size = size; pool_clear(pool); return pool; } void pool_delete ( Slot *pool ) { ::free(pool); } char *pool_alloc ( Slot *pool, unsigned long key, unsigned int size ) { Slot *q = pool; Slot *p = pool->u.next; size += sizeof(Slot); while (p && p->size < size) { q = p; p = p->u.next; } if (p == nullptr) return nullptr; Slot *pnext = p->u.next; unsigned int psize = p->size - size; if (psize < sizeof(Slot)) { q->u.next = pnext; } else { q->u.next = (Slot *) ((char *) p + size); q->u.next->size = psize; q->u.next->u.next = pnext; } p->size = size; p->u.key = key; return (char *) p + sizeof(Slot); } void pool_free ( Slot *pool, char *data ) { if (data == nullptr) return; Slot *p = pool_slot(data); // p->size = size; p->u.next = pool->u.next; pool->u.next = p; } static int sort_item ( const void *elem1, const void *elem2 ) { return long(pool_slot_key(*(char **) elem2)) - long(pool_slot_key(*(char **) elem1)); } char *push_item ( jack_nframes_t time, size_t size ) { char *item = nullptr; if (m_count < m_size) { item = pool_alloc(m_pool, time, size); if (item) { m_items[m_count++] = item; m_dirty++; } } return item; } char *pop_item ( jack_nframes_t *time, size_t *size ) { char *item = nullptr; if (m_count > 0) { if (m_dirty > 0) { ::qsort(m_items, m_count, sizeof(char *), sort_item); m_dirty = 0; } item = m_items[--m_count]; if (time) *time = pool_slot_key(item); if (size) *size = pool_slot_size(item); } else pool_clear(m_pool); return item; } private: // Queue instance variables. Slot *m_pool; char **m_items; unsigned int m_size; unsigned int m_count; int m_dirty; }; //---------------------------------------------------------------------- // qmidinetJackMidiDevice_process -- JACK client process callback. // static int qmidinetJackMidiDevice_process ( jack_nframes_t nframes, void *pvArg ) { qmidinetJackMidiDevice *pJackMidiDevice = static_cast (pvArg); return pJackMidiDevice->process(nframes); } //---------------------------------------------------------------------- // qmidinetJackMidiDevice_shutdown -- JACK client shutdown callback. // static void qmidinetJackMidiDevice_shutdown ( void *pvArg ) { qmidinetJackMidiDevice *pJackMidiDevice = static_cast (pvArg); pJackMidiDevice->shutdownNotify(); } //---------------------------------------------------------------------------- // qmidinetJackMidiThread -- JACK MIDI transfer thread. // class qmidinetJackMidiThread : public QThread { public: // Constructor. qmidinetJackMidiThread(); // Run-state accessors. void setRunState(bool bRunState); bool runState() const; // Wake from executive wait condition (RT-safe). void sync(); // Sleep for some microseconds. void usleep(unsigned long usecs); protected: // The main thread executive. void run(); private: // Whether the thread is logically running. volatile bool m_bRunState; // Thread synchronization objects. QMutex m_mutex; QWaitCondition m_cond; }; // Constructor. qmidinetJackMidiThread::qmidinetJackMidiThread (void) : QThread(), m_bRunState(false) { } // Run-state accessors. void qmidinetJackMidiThread::setRunState ( bool bRunState ) { QMutexLocker locker(&m_mutex); m_bRunState = bRunState; } bool qmidinetJackMidiThread::runState (void) const { return m_bRunState; } // The main thread executive. void qmidinetJackMidiThread::run (void) { m_mutex.lock(); m_bRunState = true; while (m_bRunState) { // Wait for events... m_cond.wait(&m_mutex); // Process input events... qmidinetJackMidiDevice::getInstance()->capture(); } m_mutex.unlock(); } // Wake from executive wait condition (RT-safe). void qmidinetJackMidiThread::sync (void) { if (m_mutex.tryLock()) { m_cond.wakeAll(); m_mutex.unlock(); } #ifdef CONFIG_DEBUG else qDebug("qmidinetJackMidiThread[%p]::sync(): tryLock() failed.", this); #endif } // Sleep for some microseconds. void qmidinetJackMidiThread::usleep ( unsigned long usecs ) { QThread::usleep(usecs); } //---------------------------------------------------------------------------- // qmidinetJackMidiDevice -- MIDI interface device (JACK). // qmidinetJackMidiDevice *qmidinetJackMidiDevice::g_pDevice = nullptr; // Constructor. qmidinetJackMidiDevice::qmidinetJackMidiDevice ( QObject *pParent ) : QObject(pParent), m_nports(0), m_pJackClient(nullptr), m_ppJackPortIn(nullptr), m_ppJackPortOut(nullptr), m_pJackBufferIn(nullptr), m_pJackBufferOut(nullptr), m_pQueueIn(nullptr), m_pRecvThread(nullptr) { g_pDevice = this; } // Destructor. qmidinetJackMidiDevice::~qmidinetJackMidiDevice (void) { close(); g_pDevice = nullptr; } // Kind of singleton reference. qmidinetJackMidiDevice *qmidinetJackMidiDevice::getInstance (void) { return g_pDevice; } // Device initialization method. bool qmidinetJackMidiDevice::open ( const QString& sClientName, int iNumPorts ) { // Close if already open. close(); // Open new JACK client... const QByteArray aClientName = sClientName.toLocal8Bit(); m_pJackClient = jack_client_open( aClientName.constData(), JackNullOption, nullptr); if (m_pJackClient == nullptr) return false; m_nports = iNumPorts; int i; // Create duplex ports. m_ppJackPortIn = new jack_port_t * [m_nports]; m_ppJackPortOut = new jack_port_t * [m_nports]; for (i = 0; i < m_nports; ++i) { m_ppJackPortIn[i] = nullptr; m_ppJackPortOut[i] = nullptr; } const QString sPortNameIn("in_%1"); const QString sPortNameOut("out_%1"); for (i = 0; i < m_nports; ++i) { m_ppJackPortIn[i] = jack_port_register(m_pJackClient, sPortNameIn.arg(i + 1).toLocal8Bit().constData(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); m_ppJackPortOut[i] = jack_port_register(m_pJackClient, sPortNameOut.arg(i + 1).toLocal8Bit().constData(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); } // Create transient buffers. m_pJackBufferIn = jack_ringbuffer_create(1024 * m_nports); m_pJackBufferOut = jack_ringbuffer_create(1024 * m_nports); // Prepare the queue sorter stuff... m_pQueueIn = new qmidinetJackMidiQueue(1024 * m_nports, 8); // Set and go usual callbacks... jack_set_process_callback(m_pJackClient, qmidinetJackMidiDevice_process, this); jack_on_shutdown(m_pJackClient, qmidinetJackMidiDevice_shutdown, this); jack_activate(m_pJackClient); // Start listener thread... m_pRecvThread = new qmidinetJackMidiThread(); m_pRecvThread->start(); // Done. return true; } // Device termination method. void qmidinetJackMidiDevice::close (void) { if (m_pRecvThread) { if (m_pRecvThread->isRunning()) do { m_pRecvThread->setRunState(false); // m_pRecvThread->terminate(); m_pRecvThread->sync(); } while (!m_pRecvThread->wait(100)); delete m_pRecvThread; m_pRecvThread = nullptr; } if (m_pJackClient) jack_deactivate(m_pJackClient); if (m_ppJackPortIn || m_ppJackPortOut) { for (int i = 0; i < m_nports; ++i) { if (m_ppJackPortIn && m_ppJackPortIn[i]) jack_port_unregister(m_pJackClient, m_ppJackPortIn[i]); if (m_ppJackPortOut && m_ppJackPortOut[i]) jack_port_unregister(m_pJackClient, m_ppJackPortOut[i]); } if (m_ppJackPortIn) delete [] m_ppJackPortIn; if (m_ppJackPortOut) delete [] m_ppJackPortOut; m_ppJackPortIn = nullptr; m_ppJackPortOut = nullptr; } if (m_pJackClient) { jack_client_close(m_pJackClient); m_pJackClient = nullptr; } if (m_pJackBufferIn) { jack_ringbuffer_free(m_pJackBufferIn); m_pJackBufferIn = nullptr; } if (m_pJackBufferOut) { jack_ringbuffer_free(m_pJackBufferOut); m_pJackBufferOut = nullptr; } if (m_pQueueIn) { delete m_pQueueIn; m_pQueueIn = nullptr; } m_nports = 0; } // MIDI events capture method. void qmidinetJackMidiDevice::capture (void) { if (m_pJackBufferIn == nullptr) return; char *pchBuffer; qmidinetJackMidiEvent ev; while (jack_ringbuffer_peek(m_pJackBufferIn, (char *) &ev, sizeof(ev)) == sizeof(ev)) { jack_ringbuffer_read_advance(m_pJackBufferIn, sizeof(ev)); pchBuffer = m_pQueueIn->push(ev.port, ev.event.time, ev.event.size); if (pchBuffer) jack_ringbuffer_read(m_pJackBufferIn, pchBuffer, ev.event.size); else jack_ringbuffer_read_advance(m_pJackBufferIn, ev.event.size); } float sample_rate = jack_get_sample_rate(m_pJackClient); jack_nframes_t frame_time = jack_frame_time(m_pJackClient); while ((pchBuffer = m_pQueueIn->pop( &ev.port, &ev.event.time, &ev.event.size)) != nullptr) { ev.event.time += m_last_frame_time; if (ev.event.time > frame_time) { unsigned long sleep_time = ev.event.time - frame_time; float secs = float(sleep_time) / sample_rate; if (secs > 0.0001f) { #if 0 // defined(__GNUC__) && defined(Q_OS_LINUX) struct timespec ts; ts.tv_sec = time_t(secs); ts.tv_nsec = long(1E+9f * (secs - ts.tv_sec)); ::nanosleep(&ts, nullptr); #else m_pRecvThread->usleep(long(1E+6f * secs)); #endif } frame_time = ev.event.time; } #ifdef CONFIG_DEBUG // - show (input) event for debug purposes... fprintf(stderr, "JACK MIDI In Port %d: (%d)", ev.port, int(ev.event.size)); for (unsigned int i = 0; i < ev.event.size; ++i) fprintf(stderr, " 0x%02x", (unsigned char) pchBuffer[i]); fprintf(stderr, "\n"); #endif recvData((unsigned char *) pchBuffer, ev.event.size, ev.port); } } // JACK specifics. int qmidinetJackMidiDevice::process ( jack_nframes_t nframes ) { jack_nframes_t buffer_size = jack_get_buffer_size(m_pJackClient); m_last_frame_time = jack_last_frame_time(m_pJackClient); // Enqueue/dequeue events // to/from ring-buffers... for (int i = 0; i < m_nports; ++i) { if (m_ppJackPortIn && m_ppJackPortIn[i] && m_pJackBufferIn) { void *pvBufferIn = jack_port_get_buffer(m_ppJackPortIn[i], nframes); const int nevents = jack_midi_get_event_count(pvBufferIn); const unsigned int nlimit = jack_ringbuffer_write_space(m_pJackBufferIn); unsigned char achBuffer[nlimit]; unsigned char *pchBuffer = &achBuffer[0]; unsigned int nwrite = 0; for (int n = 0; n < nevents; ++n) { if (nwrite + sizeof(qmidinetJackMidiEvent) >= nlimit) break; qmidinetJackMidiEvent *pJackEventIn = (struct qmidinetJackMidiEvent *) pchBuffer; jack_midi_event_get(&pJackEventIn->event, pvBufferIn, n); if (nwrite + sizeof(qmidinetJackMidiEvent) + pJackEventIn->event.size >= nlimit) break; pJackEventIn->port = i; pchBuffer += sizeof(qmidinetJackMidiEvent); nwrite += sizeof(qmidinetJackMidiEvent); ::memcpy(pchBuffer, pJackEventIn->event.buffer, pJackEventIn->event.size); pchBuffer += pJackEventIn->event.size; nwrite += pJackEventIn->event.size; } if (nwrite > 0) { jack_ringbuffer_write(m_pJackBufferIn, (const char *) achBuffer, nwrite); } } if (m_ppJackPortOut && m_ppJackPortOut[i] && m_pJackBufferOut) { void *pvBufferOut = jack_port_get_buffer(m_ppJackPortOut[i], nframes); jack_midi_clear_buffer(pvBufferOut); const unsigned int nlimit = jack_midi_max_event_size(pvBufferOut); unsigned int nread = 0; qmidinetJackMidiEvent ev; while (jack_ringbuffer_peek(m_pJackBufferOut, (char *) &ev, sizeof(ev)) == sizeof(ev) && nread < nlimit) { if (ev.port != i) break; if (ev.event.time >= m_last_frame_time) break; jack_nframes_t offset = m_last_frame_time - ev.event.time; if (offset > buffer_size) offset = 0; else offset = buffer_size - offset; jack_ringbuffer_read_advance(m_pJackBufferOut, sizeof(ev)); jack_midi_data_t *pMidiData = jack_midi_event_reserve(pvBufferOut, offset, ev.event.size); if (pMidiData) jack_ringbuffer_read(m_pJackBufferOut, (char *) pMidiData, ev.event.size); else jack_ringbuffer_read_advance(m_pJackBufferOut, ev.event.size); nread += ev.event.size; } } } if (m_pJackBufferIn && jack_ringbuffer_read_space(m_pJackBufferIn) > 0) m_pRecvThread->sync(); return 0; } void qmidinetJackMidiDevice::shutdownNotify (void) { emit shutdown(); } // Data transmission methods. bool qmidinetJackMidiDevice::sendData ( unsigned char *data, unsigned short len, int port ) const { if (port < 0 || port >= m_nports) return false; if (m_pJackBufferOut == nullptr) return false; const unsigned int nlimit = jack_ringbuffer_write_space(m_pJackBufferOut); if (sizeof(qmidinetJackMidiEvent) + len < nlimit) { unsigned char achBuffer[nlimit]; unsigned char *pchBuffer = &achBuffer[0]; qmidinetJackMidiEvent *pJackEventOut = (struct qmidinetJackMidiEvent *) pchBuffer; pchBuffer += sizeof(qmidinetJackMidiEvent); memcpy(pchBuffer, data, len); pJackEventOut->event.time = jack_frame_time(m_pJackClient); pJackEventOut->event.buffer = (jack_midi_data_t *) pchBuffer; pJackEventOut->event.size = len; pJackEventOut->port = port; #ifdef CONFIG_DEBUG // - show (output) event for debug purposes... fprintf(stderr, "JACK MIDI Out Port %d:", port); for (unsigned int i = 0; i < len; ++i) fprintf(stderr, " 0x%02x", (unsigned char) pchBuffer[i]); fprintf(stderr, "\n"); #endif jack_ringbuffer_write(m_pJackBufferOut, (const char *) achBuffer, sizeof(qmidinetJackMidiEvent) + len); } return true; } void qmidinetJackMidiDevice::recvData ( unsigned char *data, unsigned short len, int port ) { emit received(QByteArray((const char *) data, len), port); } // Receive data slot. void qmidinetJackMidiDevice::receive ( QByteArray data, int port ) { sendData((unsigned char *) data.constData(), data.length(), port); } #endif // CONFIG_JACK_MIDI // end of qmidinetJackMidiDevice.h qmidinet-1.0.1/src/PaxHeaders/qmidinetOptionsForm.h0000644000000000000000000000013214772454326017371 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetOptionsForm.h0000644000175000001440000000325114772454326017362 0ustar00rncbcusers// qmidinetOptionsForm.h // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetOptionsForm_h #define __qmidinetOptionsForm_h #include "ui_qmidinetOptionsForm.h" //---------------------------------------------------------------------------- // qmidinetOptionsForm -- UI wrapper form. class qmidinetOptionsForm : public QDialog { Q_OBJECT public: // Constructor. qmidinetOptionsForm(QWidget *pParent = nullptr); protected slots: void change(); void accept(); void reject(); void buttonClick(QAbstractButton *); private: // The Qt-designer UI struct... Ui::qmidinetOptionsForm m_ui; // Instance variables. int m_iDirtyCount; // Default (translatable) interface name. QString m_sDefInterface; }; #endif // __qmidinetOptionsForm_h // end of qmidinetOptionsForm.h qmidinet-1.0.1/src/PaxHeaders/qmidinet.cpp0000644000000000000000000000013214772454326015524 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinet.cpp0000644000175000001440000003536314772454326015526 0ustar00rncbcusers// qmidinet.cpp // /**************************************************************************** Copyright (C) 2010-2025, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinet.h" #include "qmidinetOptions.h" #include "qmidinetOptionsForm.h" #include #include #include #include #ifdef CONFIG_XUNIQUE #include #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) #include #endif #include #include #include #endif // CONFIG_XUNIQUE //------------------------------------------------------------------------- // qmidinetApplication -- Singleton application instance. // // Constructor. qmidinetApplication::qmidinetApplication ( int& argc, char **argv, bool bGUI ) : QObject(nullptr), m_pApp(nullptr), m_pIcon(nullptr) #ifdef CONFIG_ALSA_MIDI , m_alsa(this) #endif #ifdef CONFIG_JACK_MIDI , m_jack(this) #endif , m_udpd(this) #ifdef CONFIG_XUNIQUE , m_pMemory(nullptr) , m_pServer(nullptr) #endif { if (bGUI) { #if defined(Q_OS_LINUX) && !defined(CONFIG_WAYLAND) ::setenv("QT_QPA_PLATFORM", "xcb", 0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #endif QApplication *pApp = new QApplication(argc, argv); #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) pApp->setApplicationDisplayName(QMIDINET_TITLE); // QMIDINET_TITLE " - " + QObject::tr(QMIDINET_SUBTITLE)); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) pApp->setDesktopFileName( QString("org.rncbc.%1").arg(PROJECT_NAME)); #endif pApp->setApplicationVersion(PROJECT_VERSION); #endif pApp->setQuitOnLastWindowClosed(false); m_pApp = pApp; m_pIcon = new qmidinetSystemTrayIcon(this); } else { m_pApp = new QCoreApplication(argc, argv); m_pIcon = nullptr; } #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) m_pApp->setApplicationName(QMIDINET_TITLE); #endif #ifdef CONFIG_ALSA_MIDI QObject::connect( &m_udpd, SIGNAL(received(QByteArray, int)), &m_alsa, SLOT(receive(QByteArray, int))); QObject::connect( &m_alsa, SIGNAL(received(QByteArray, int)), &m_udpd, SLOT(receive(QByteArray, int))); #endif #ifdef CONFIG_JACK_MIDI QObject::connect( &m_udpd, SIGNAL(received(QByteArray, int)), &m_jack, SLOT(receive(QByteArray, int))); QObject::connect( &m_jack, SIGNAL(received(QByteArray, int)), &m_udpd, SLOT(receive(QByteArray, int))); QObject::connect(&m_jack, SIGNAL(shutdown()), SLOT(shutdown())); #endif if (m_pIcon) { QObject::connect( &m_udpd, SIGNAL(received(QByteArray, int)), m_pIcon, SLOT(receiving())); #ifdef CONFIG_ALSA_MIDI QObject::connect( &m_alsa, SIGNAL(received(QByteArray, int)), m_pIcon, SLOT(sending())); #endif #ifdef CONFIG_JACK_MIDI QObject::connect( &m_jack, SIGNAL(received(QByteArray, int)), m_pIcon, SLOT(sending())); #endif } } // Destructor. qmidinetApplication::~qmidinetApplication (void) { #ifdef CONFIG_XUNIQUE clearServer(); #endif // CONFIG_XUNIQUE if (m_pIcon) delete m_pIcon; if (m_pApp) delete m_pApp; } #ifdef CONFIG_XUNIQUE // Initializer (instance). bool qmidinetApplication::init (void) { return setupServer(); } // Local server/shmem setup. bool qmidinetApplication::setupServer (void) { clearServer(); m_sUnique = m_pApp->applicationName(); QString sUserName = QString::fromUtf8(::getenv("USER")); if (sUserName.isEmpty()) sUserName = QString::fromUtf8(::getenv("USERNAME")); if (!sUserName.isEmpty()) { m_sUnique += ':'; m_sUnique += sUserName; } m_sUnique += '@'; m_sUnique += QHostInfo::localHostName(); #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) const QNativeIpcKey nativeKey = QSharedMemory::legacyNativeKey(m_sUnique); #if defined(Q_OS_UNIX) m_pMemory = new QSharedMemory(nativeKey); m_pMemory->attach(); delete m_pMemory; #endif m_pMemory = new QSharedMemory(nativeKey); #else #if defined(Q_OS_UNIX) m_pMemory = new QSharedMemory(m_sUnique); m_pMemory->attach(); delete m_pMemory; #endif m_pMemory = new QSharedMemory(m_sUnique); #endif bool bServer = false; const qint64 pid = QCoreApplication::applicationPid(); struct Data { qint64 pid; }; if (m_pMemory->create(sizeof(Data))) { m_pMemory->lock(); Data *pData = static_cast (m_pMemory->data()); if (pData) { pData->pid = pid; bServer = true; } m_pMemory->unlock(); } else if (m_pMemory->attach()) { m_pMemory->lock(); // maybe not necessary? Data *pData = static_cast (m_pMemory->data()); if (pData) bServer = (pData->pid == pid); m_pMemory->unlock(); } if (bServer) { QLocalServer::removeServer(m_sUnique); m_pServer = new QLocalServer(); m_pServer->setSocketOptions(QLocalServer::UserAccessOption); m_pServer->listen(m_sUnique); QObject::connect(m_pServer, SIGNAL(newConnection()), SLOT(newConnectionSlot())); } else { QLocalSocket socket; socket.connectToServer(m_sUnique); if (socket.state() == QLocalSocket::ConnectingState) socket.waitForConnected(200); if (socket.state() == QLocalSocket::ConnectedState) { socket.write(QCoreApplication::arguments().join(' ').toUtf8()); socket.flush(); socket.waitForBytesWritten(200); } } return bServer; } // Local server/shmem cleanup. void qmidinetApplication::clearServer (void) { if (m_pServer) { m_pServer->close(); delete m_pServer; m_pServer = nullptr; } if (m_pMemory) { delete m_pMemory; m_pMemory = nullptr; } m_sUnique.clear(); } // Local server connection slot. void qmidinetApplication::newConnectionSlot (void) { QLocalSocket *socket = m_pServer->nextPendingConnection(); QObject::connect(socket, SIGNAL(readyRead()), SLOT(readyReadSlot())); } // Local server data-ready slot. void qmidinetApplication::readyReadSlot (void) { QLocalSocket *socket = qobject_cast (sender()); if (socket) { const qint64 nread = socket->bytesAvailable(); if (nread > 0) { const QByteArray data = socket->read(nread); qmidinetOptions *pOptions = qmidinetOptions::getInstance(); if (pOptions && pOptions->parse_args(QString(data).split(' '))) reset(); // Reset the server... setupServer(); } } } #endif // CONFIG_XUNIQUE // Initializer (secondary). bool qmidinetApplication::setup (void) { qmidinetOptions *pOptions = qmidinetOptions::getInstance(); if (pOptions == nullptr) return false; m_udpd.close(); #ifdef CONFIG_JACK_MIDI m_jack.close(); #endif #ifdef CONFIG_ALSA_MIDI m_alsa.close(); #endif #ifdef CONFIG_ALSA_MIDI if (pOptions->bAlsaMidi && !m_alsa.open(QMIDINET_TITLE, pOptions->iNumPorts)) { message(tr("ALSA MIDI Inferface Error"), tr("The ALSA MIDI interface could not be established.\n\n" "Please, make sure you have a ALSA MIDI sub-system working " "correctly and try again.")); return false; } #endif #ifdef CONFIG_JACK_MIDI if (pOptions->bJackMidi && !m_jack.open(QMIDINET_TITLE, pOptions->iNumPorts)) { #ifdef CONFIG_ALSA_MIDI m_alsa.close(); #endif message(tr("JACK MIDI Inferface Error"), tr("The JACK MIDI interface could not be established.\n\n" "Please, make sure you have a JACK MIDI sub-system working " "correctly and try again.")); return false; } #endif if (!m_udpd.open( pOptions->sInterface, pOptions->sUdpAddr, pOptions->iUdpPort, pOptions->iNumPorts)) { #ifdef CONFIG_ALSA_MIDI m_alsa.close(); #endif #ifdef CONFIG_JACK_MIDI m_jack.close(); #endif message(tr("Network Inferface Error"), tr("The network interface could not be established.\n\n" "Please, make sure you have an on-line network connection " "and try again.")); return false; } return true; } void qmidinetApplication::reset (void) { if (m_pIcon) m_pIcon->reset(); else if (!setup()) QTimer::singleShot(60000, this, SLOT(reset())); } // Message bubble/dialog. void qmidinetApplication::message ( const QString& sTitle, const QString& sText ) { if (m_pIcon && m_pIcon->isVisible()) { m_pIcon->message(sTitle, sText); } else { const QString sMessage = sTitle + ": " + sText.simplified(); qCritical("%s", sMessage.toUtf8().constData()); } } #ifdef CONFIG_JACK_MIDI void qmidinetApplication::shutdown (void) { m_jack.close(); message(tr("JACK MIDI Inferface Error"), tr("The JACK MIDI interface has been shutdown.\n\n" "Please, make sure you reactivate the JACK MIDI sub-system " "and try again.")); if (m_pIcon) m_pIcon->show(false); } #endif //------------------------------------------------------------------------- // qmidinetSystemTrayIcon -- Singleton application instance. // // Constructor. qmidinetSystemTrayIcon::qmidinetSystemTrayIcon ( qmidinetApplication *pApp ) : QSystemTrayIcon(pApp), m_pApp(pApp), m_iSending(0), m_iReceiving(0) { // m_menu.addAction(QIcon(":/images/qmidinet.svg"), QMIDINET_TITLE); // m_menu.addSeparator(); m_menu.addAction(QIcon(":/images/menuOptions.png"), tr("Options..."), this, SLOT(options())); m_menu.addAction(QIcon(":/images/menuReset.png"), tr("Reset"), this, SLOT(reset())); m_menu.addSeparator(); m_menu.addAction(tr("About..."), this, SLOT(about())); m_menu.addAction(tr("About Qt..."), m_pApp->app(), SLOT(aboutQt())); m_menu.addSeparator(); m_menu.addAction(QIcon(":/images/menuQuit.png"), tr("Quit"), m_pApp->app(), SLOT(quit())); QSystemTrayIcon::setContextMenu(&m_menu); QSystemTrayIcon::setToolTip(QMIDINET_TITLE " - " + tr(QMIDINET_SUBTITLE)); QObject::connect(this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(activated(QSystemTrayIcon::ActivationReason))); } // Initializer. void qmidinetSystemTrayIcon::show ( bool bSetup ) { QPixmap pm(":/images/qmidinet.png"); if (!bSetup) { // Merge with the error overlay pixmap... const QPixmap pmError(":/images/iconError.png"); if (!pmError.mask().isNull()) { QBitmap mask = pm.mask(); QPainter(&mask).drawPixmap(0, 0, pmError.mask()); pm.setMask(mask); QPainter(&pm).drawPixmap(0, 0, pmError); } // Restart timeout (3 minutes)... QTimer::singleShot(180000, this, SLOT(reset())); } else { // Merge with the status overlay pixmaps... if (m_iSending > 0) { const QPixmap pmSend(":/images/iconSend.png"); if (!pmSend.mask().isNull()) { QBitmap mask = pm.mask(); QPainter(&mask).drawPixmap(0, 0, pmSend.mask()); pm.setMask(mask); QPainter(&pm).drawPixmap(0, 0, pmSend); } } if (m_iReceiving > 0) { const QPixmap pmReceive(":/images/iconReceive.png"); if (!pmReceive.mask().isNull()) { QBitmap mask = pm.mask(); QPainter(&mask).drawPixmap(0, 0, pmReceive.mask()); pm.setMask(mask); QPainter(&pm).drawPixmap(0, 0, pmReceive); } } } QSystemTrayIcon::setIcon(QIcon(pm)); QSystemTrayIcon::show(); } // Options dialog. void qmidinetSystemTrayIcon::options (void) { if (qmidinetOptionsForm(nullptr).exec()) reset(); } // Restart/reset action void qmidinetSystemTrayIcon::reset (void) { show(m_pApp->setup()); } // About dialog. void qmidinetSystemTrayIcon::about (void) { QStringList list; #ifdef CONFIG_DEBUG list << tr("Debugging option enabled."); #endif #ifndef CONFIG_ALSA_MIDI list << tr("ALSA MIDI support disabled."); #endif #ifndef CONFIG_JACK_MIDI list << tr("JACK MIDI support disabled."); #endif // Stuff the about box text... QString sText = "

" QMIDINET_TITLE "

\n"; sText += "

" + tr(QMIDINET_SUBTITLE) + "
\n"; sText += "
\n"; sText += tr("Version") + ": " PROJECT_VERSION "
\n"; // sText += "" + tr("Build") + ": " CONFIG_BUILD_DATE "
\n"; if (!list.isEmpty()) { sText += ""; sText += list.join("
\n"); sText += "
\n"; } sText += "
\n"; sText += tr("Using: Qt %1").arg(qVersion()); #if defined(QT_STATIC) sText += "-static"; #endif sText += "
\n"; sText += "
\n"; sText += tr("Website") + ": " QMIDINET_WEBSITE "
\n"; sText += "
\n"; sText += ""; sText += QMIDINET_COPYRIGHT "
\n"; sText += "
\n"; sText += tr("This program is free software; you can redistribute it and/or modify it") + "
\n"; sText += tr("under the terms of the GNU General Public License version 2 or later."); sText += "
"; sText += "
\n"; sText += "

\n"; QMessageBox abox; abox.setWindowIcon(QSystemTrayIcon::icon()); abox.setWindowTitle(tr("About")); abox.setIconPixmap(QPixmap(":/images/qmidinet.svg") .scaledToWidth(48, Qt::SmoothTransformation)); abox.setText(sText); abox.exec(); } // Message bubble/dialog. void qmidinetSystemTrayIcon::message ( const QString& sTitle, const QString& sText ) { if (QSystemTrayIcon::supportsMessages()) { QSystemTrayIcon::showMessage(sTitle, sText, QSystemTrayIcon::Critical); } else { QMessageBox::critical(nullptr, sTitle, sText); } } // Handle systeam tray activity. void qmidinetSystemTrayIcon::activated ( QSystemTrayIcon::ActivationReason reason ) { if (reason == QSystemTrayIcon::Trigger) QSystemTrayIcon::contextMenu()->exec(QCursor::pos()); } // Status changes. void qmidinetSystemTrayIcon::sending (void) { if (++m_iSending < 2) { show(true); QTimer::singleShot(200, this, SLOT(timerOff())); } } void qmidinetSystemTrayIcon::receiving (void) { if (++m_iReceiving < 2) { show(true); QTimer::singleShot(200, this, SLOT(timerOff())); } } void qmidinetSystemTrayIcon::timerOff (void) { m_iSending = m_iReceiving = 0; show(true); } //------------------------------------------------------------------------- // main - The main program trunk. // int main ( int argc, char* argv[] ) { Q_INIT_RESOURCE(qmidinet); #ifdef Q_WS_X11 bool bGUI = (::getenv("DISPLAY") != 0); #else bool bGUI = true; #endif if (bGUI) for (int i = 1; i < argc; ++i) { const QString& sArg = QString::fromLocal8Bit(argv[i]); if (sArg == "-g" || sArg == "--no-gui") { bGUI = false; break; } } qmidinetApplication app(argc, argv, bGUI); qmidinetOptions opts; if (!opts.parse_args(app.app()->arguments())) { app.app()->quit(); return 1; } #ifdef CONFIG_XUNIQUE if (!app.init()) { app.app()->quit(); return 2; } #endif app.reset(); return app.app()->exec(); } // end of qmidinet.cpp qmidinet-1.0.1/src/PaxHeaders/man10000644000000000000000000000013214772454326013765 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.378593584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/man1/0000755000175000001440000000000014772454326014032 5ustar00rncbcusersqmidinet-1.0.1/src/man1/PaxHeaders/qmidinet.10000644000000000000000000000013214772454326015736 xustar0030 mtime=1743411414.378593584 30 atime=1743411414.378593584 30 ctime=1743411414.378593584 qmidinet-1.0.1/src/man1/qmidinet.10000644000175000001440000000314314772454326015727 0ustar00rncbcusers.TH QMIDINET "1" "June 17, 2014" .SH NAME qmidinet \- MIDI Network Gateway via UDP/IP Multicast .SH SYNOPSIS .B qmidinet [\fIoptions\fR] .SH DESCRIPTION This manual page documents briefly the .B qmidinet command. .PP \fBQmidiNet\fP is a MIDI network gateway application that sends and receives MIDI data (ALSA Sequencer) over the network, using UDP/IP multicast. .PP It was inspired by multimidicast (http://llg.cubic.org/tools) and designed to be compatible with ipMIDI for Windows (http://nerds.de). .SH OPTIONS .HP \fB\-n\fR, \fB\-\-num\-ports\fR=[\fInum\-ports\fR] .IP Use this number of ports (default = 1) .HP \fB\-i\fR, \fB\-\-interface\fR=[\fIinterface\fR] .IP Use specific network interface (default = all) .HP \fB\-u\fR, \fB\-\-address\fR, \fB\-\-udp\-addr\fR=[\fIaddress\fR] .IP Use specific network address (default = 225.0.0.37) .HP \fB\-p\fR, \fB\-\-port\fR, \fB\-\-udp\-port\fR=[\fIport\fR] .IP Use specific network port (default = 21928) .HP \fB\-a\fR, \fB\-\-alsa\-midi\fR[=\fIflag\fR] .IP Enable ALSA MIDI (0|1|yes|no|on|off, default = yes) .HP \fB\-j\fR, \fB\-\-jack\-midi\fR[=\fIflag\fR] .IP Enable JACK MIDI (0|1|yes|no|on|off, default = no) .HP \fB\-g\fR, \fB\-\-no\-gui\fR .IP Disable the graphical user interface (GUI) .HP \fB\-h\fR, \fB\-\-help\fR .IP Show help about command line options .HP \fB\-v\fR, \fB\-\-version\fR .IP Show version information .SH FILES Configuration settings are stored in ~/.config/rncbc.org/QmidiNet.conf .SH AUTHOR QmidiNet was written by Rui Nuno Capela. .PP This manual page was written by Alessio Treglia , for the Debian project (but may be used by others). qmidinet-1.0.1/src/man1/PaxHeaders/qmidinet.fr.10000644000000000000000000000013214772454326016344 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.378593584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/man1/qmidinet.fr.10000644000175000001440000000262314772454326016337 0ustar00rncbcusers.TH QMIDINET "1" "Octobre 2010" .SH NOM qmidinet \- passerelle de réseau MIDI via UDP/IP Multicast .SH SYNOPSIS .B qmidinet [\fIoptions\fR] .SH DESCRIPTION Cette page de manuel documente brièvement la commande .B qmidinet . .PP \fBQmidiNet\fP est une application de passerelle de réseau MIDI qui envoie et reçoit des données MIDI (séquenceur ALSA) à travers le réseau, en utilisant UDP/IP multicast. .PP Il a été inspiré par multimidicast (http://llg.cubic.org/tools) et conçu pour être compatible avec ipMIDI pour Windows (http://nerds.de). .SH OPTIONS .HP \fB\-n\fR, \fB\-\-num\-ports\fR=\fI[num\-ports]\fR .IP Utilise ce nombre de ports (par défaut = 1) .HP \fB\-i\fR, \fB\-\-interface\fR=\fI[interface]\fR .IP Utiliser une interface réseau spécifique (par défaut = all) .HP \fB\-p\fR, \fB\-\-port\fR=\fI[port]\fR .IP Utilise un port de réseau spécifique (par défaut = 21928) .HP \fB\-h\fR, \fB\-\-help\fR .IP Affiche de l'aide à propos des options de ligne de commande .HP \fB\-v\fR, \fB\-\-version\fR .IP Affiche une information de version .PP .SH AUTEUR qmidinet a été écrit par Rui Nuno Capela. .PP Cette page de manuel a été écrite par Alessio Treglia , pour le projet Debian (mais peut être utilisée par d'autres). .PP La version française a été traduite par Olivier Humbert , pour le projet LibraZiK (mais peut être utilisée par d'autres). qmidinet-1.0.1/src/PaxHeaders/qmidinetJackMidiDevice.h0000644000000000000000000000013214772454326017705 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetJackMidiDevice.h0000644000175000001440000000555614772454326017710 0ustar00rncbcusers// qmidinetJackMidiDevice.h // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidinetJackMidiDevice_h #define __qmidinetJackMidiDevice_h #include "qmidinetAbout.h" #ifdef CONFIG_JACK_MIDI #include #include #include #include #include #include #include //---------------------------------------------------------------------------- // qmidinetJackMidiDevice -- JACK MIDI interface object. class qmidinetJackMidiDevice : public QObject { Q_OBJECT public: // Constructor. qmidinetJackMidiDevice(QObject *pParent = nullptr); // Destructor. ~qmidinetJackMidiDevice(); // Kind of singleton reference. static qmidinetJackMidiDevice *getInstance(); // Device initialization method. bool open(const QString& sClientName, int iNumPorts = 1); // Device termination method. void close(); // MIDI events capture method. void capture(); // Data transmission methods. bool sendData(unsigned char *data, unsigned short len, int port = 0) const; void recvData(unsigned char *data, unsigned short len, int port = 0); // JACK specifics. int process (jack_nframes_t nframes); void shutdownNotify(); signals: // Received data signal. void received(QByteArray data, int port); // Shutdown signal. void shutdown(); public slots: // Receive data slot. void receive(QByteArray data, int port); private: // Instance variables, int m_nports; // Instance variables. jack_client_t *m_pJackClient; jack_port_t **m_ppJackPortIn; jack_port_t **m_ppJackPortOut; jack_ringbuffer_t *m_pJackBufferIn; jack_ringbuffer_t *m_pJackBufferOut; jack_nframes_t m_last_frame_time; // Queue sorter. class qmidinetJackMidiQueue *m_pQueueIn; // Network receiver thread. class qmidinetJackMidiThread *m_pRecvThread; // Kind-of singleton reference. static qmidinetJackMidiDevice *g_pDevice; }; #endif // CONFIG_JACK_MIDI #endif // __qmidinetJackMidiDevice_h // end of qmidinetJackMidiDevice.h qmidinet-1.0.1/src/PaxHeaders/qmidinetUdpDevice.cpp0000644000000000000000000000013214772454326017315 xustar0030 mtime=1743411414.379182584 30 atime=1743411414.379182584 30 ctime=1743411414.379182584 qmidinet-1.0.1/src/qmidinetUdpDevice.cpp0000644000175000001440000003640414772454326017314 0ustar00rncbcusers// qmidinetUdpDevice.cpp // /**************************************************************************** Copyright (C) 2010-2020, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidinetUdpDevice.h" #if defined(CONFIG_IPV6) #include #include #include #else #include #include #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) static WSADATA g_wsaData; typedef int socklen_t; #else #include #include #include #include inline void closesocket(int s) { ::close(s); } #endif #include #include //---------------------------------------------------------------------------- // qmidinetUdpDevice::RecvThread -- Network listener thread. // class qmidinetUdpDeviceThread : public QThread { public: // Constructor. qmidinetUdpDeviceThread(int *sockin, int nports = 1); // Run-state accessors. void setRunState(bool bRunState); bool runState() const; protected: // The main thread executive. void run(); private: // The listener socket. int *m_sockin; int m_nports; // Whether the thread is logically running. volatile bool m_bRunState; }; // Constructor. qmidinetUdpDeviceThread::qmidinetUdpDeviceThread ( int *sockin, int nports ) : QThread(), m_sockin(sockin), m_nports(nports), m_bRunState(false) { } // Run-state accessors. void qmidinetUdpDeviceThread::setRunState ( bool bRunState ) { m_bRunState = bRunState; } bool qmidinetUdpDeviceThread::runState (void) const { return m_bRunState; } // The main thread executive. void qmidinetUdpDeviceThread::run (void) { m_bRunState = true; while (m_bRunState) { // Wait for an network event... fd_set fds; FD_ZERO(&fds); int i, fdmax = 0; for(i = 0; i < m_nports; ++i) { FD_SET(m_sockin[i], &fds); if (m_sockin[i] > fdmax) fdmax = m_sockin[i]; } // Set timeout period (1 second)... struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; int s = ::select(fdmax + 1, &fds, nullptr, nullptr, &tv); if (s < 0) { ::perror("select"); break; } if (s == 0) { // Timeout! continue; } // A Network event for (i = 0; i < m_nports; ++i) { if (FD_ISSET(m_sockin[i], &fds)) { // Read from network... unsigned char buf[1024]; struct sockaddr_in sender; socklen_t slen = sizeof(sender); int r = ::recvfrom(m_sockin[i], (char *) buf, sizeof(buf), 0, (struct sockaddr *) &sender, &slen); if (r > 0) qmidinetUdpDevice::getInstance()->recvData(buf, r, i); else if (r < 0) ::perror("recvfrom"); } } } } #endif // !CONFIG_IPV6 //---------------------------------------------------------------------------- // qmidinetUdpDevice -- Network interface device (UDP/IP). // qmidinetUdpDevice *qmidinetUdpDevice::g_pDevice = nullptr; // Constructor. qmidinetUdpDevice::qmidinetUdpDevice ( QObject *pParent ) : QObject(pParent), m_nports(0), m_sockin(nullptr), m_sockout(nullptr) #if defined(CONFIG_IPV6) , m_udpport(nullptr) #else , m_addrout(nullptr) , m_pRecvThread(nullptr) #endif // !CONFIG_IPV6 { #if !defined(CONFIG_IPV6) #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSAStartup(MAKEWORD(1, 1), &g_wsaData); #endif #endif // !CONFIG_IPV6 g_pDevice = this; } // Destructor. qmidinetUdpDevice::~qmidinetUdpDevice (void) { close(); g_pDevice = nullptr; #if !defined(CONFIG_IPV6) #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSACleanup(); #endif #endif // !CONFIG_IPV6 } // Kind of singleton reference. qmidinetUdpDevice *qmidinetUdpDevice::getInstance (void) { return g_pDevice; } // Device initialization method. bool qmidinetUdpDevice::open ( const QString& sInterface, const QString& sUdpAddr, int iUdpPort, int iNumPorts ) { // Close if already open. close(); #if defined(CONFIG_IPV6) // Setup host address for udp multicast... m_udpaddr.setAddress(sUdpAddr); #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // Check whether is real for udp multicast... if (!m_udpaddr.isMulticast()) { qWarning() << "open(udpaddr):" << sUdpAddr << "not an udp multicast address"; return false; } #endif // Check whether protocol is IPv4 or IPv6... const bool ipv6_protocol = (m_udpaddr.protocol() != QAbstractSocket::IPv4Protocol); // Setup network interface... QNetworkInterface iface; if (!sInterface.isEmpty()) iface = QNetworkInterface::interfaceFromName(sInterface); // Set the number of ports. m_nports = iNumPorts; // Allocate sockets and addresses... int i; m_sockin = new QUdpSocket * [m_nports]; m_sockout = new QUdpSocket * [m_nports]; m_udpport = new int [m_nports]; for (i = 0; i < m_nports; ++i) { m_sockin[i] = new QUdpSocket(); m_sockout[i] = new QUdpSocket(); m_udpport[i] = iUdpPort + i; } // Setup sockets and addreses... // for (i = 0; i < m_nports; ++i) { // Bind input socket... if (!m_sockin[i]->bind(ipv6_protocol ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4, iUdpPort + i, QUdpSocket::ShareAddress)) { qWarning() << "open(sockin):" << i << "udp socket error" << m_sockin[i]->error() << m_sockin[i]->errorString(); return false; } #if defined(Q_OS_WIN) m_sockin[i]->setSocketOption( QAbstractSocket::MulticastLoopbackOption, 0); #endif bool joined = false; if (iface.isValid()) joined = m_sockin[i]->joinMulticastGroup(m_udpaddr, iface); else joined = m_sockin[i]->joinMulticastGroup(m_udpaddr); if (!joined) { qWarning() << "open(sockin):" << i << "udp socket error" << m_sockin[i]->error() << m_sockin[i]->errorString(); } QObject::connect(m_sockin[i], SIGNAL(readyRead()), SLOT(readPendingDatagrams())); // Bind output socket... if (!m_sockout[i]->bind(ipv6_protocol ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4, m_sockout[i]->localPort())) { qWarning() << "open(sockout):" << i << "udp socket error" << m_sockout[i]->error() << m_sockout[i]->errorString(); return false; } m_sockout[i]->setSocketOption( QAbstractSocket::MulticastTtlOption, 1); #if defined(Q_OS_UNIX) m_sockout[i]->setSocketOption( QAbstractSocket::MulticastLoopbackOption, 0); #endif if (iface.isValid()) m_sockout[i]->setMulticastInterface(iface); } #else // Setup network protocol... int i, protonum = 0; #if 0 struct protoent *proto = ::getprotobyname("IP"); if (proto) protonum = proto->p_proto; #endif // Stable interface name... const char *ifname = nullptr; const QByteArray aInterface = sInterface.toLocal8Bit(); if (!aInterface.isEmpty()) ifname = aInterface.constData(); const char *udp_addr = nullptr; const QByteArray aUdpAddr = sUdpAddr.toLocal8Bit(); if (!aUdpAddr.isEmpty()) udp_addr = aUdpAddr.constData(); // Set the number of ports. m_nports = iNumPorts; // Input socket stuff... // m_sockin = new int [m_nports]; for (i = 0; i < m_nports; ++i) m_sockin[i] = -1; for (i = 0; i < m_nports; ++i) { m_sockin[i] = ::socket(PF_INET, SOCK_DGRAM, protonum); if (m_sockin[i] < 0) { ::perror("socket(in)"); return false; } struct sockaddr_in addrin; ::memset(&addrin, 0, sizeof(addrin)); addrin.sin_family = AF_INET; addrin.sin_addr.s_addr = htonl(INADDR_ANY); addrin.sin_port = htons(iUdpPort + i); if (::bind(m_sockin[i], (struct sockaddr *) (&addrin), sizeof(addrin)) < 0) { ::perror("bind"); return false; } // Will Hall, 2007 // INADDR_ANY will bind to default interface, // specify alternate interface nameon which to bind... struct in_addr if_addr_in; #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) if_addr_in.s_addr = htonl(INADDR_ANY); #else if (ifname) { if (!get_address(m_sockin[i], &if_addr_in, ifname)) { fprintf(stderr, "socket(in): could not find interface address for %s\n", ifname); return false; } if (::setsockopt(m_sockin[i], IPPROTO_IP, IP_MULTICAST_IF, (char *) &if_addr_in, sizeof(if_addr_in))) { ::perror("setsockopt(IP_MULTICAST_IF)"); return false; } } else { if_addr_in.s_addr = htonl(INADDR_ANY); } #endif struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = ::inet_addr(udp_addr); mreq.imr_interface.s_addr = if_addr_in.s_addr; if(::setsockopt (m_sockin[i], IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) { ::perror("setsockopt(IP_ADD_MEMBERSHIP)"); fprintf(stderr, "socket(in): your kernel is probably missing multicast support.\n"); return false; } #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) unsigned long mode = 1; if (::ioctlsocket(m_sockin[i], FIONBIO, &mode)) { ::perror("ioctlsocket(O_NONBLOCK)"); return false; } #else if (::fcntl(m_sockin[i], F_SETFL, O_NONBLOCK)) ::perror("fcntl(O_NONBLOCK)"); #endif } // Output socket... // m_sockout = new int [m_nports]; m_addrout = new struct sockaddr_in [m_nports]; for (i = 0; i < m_nports; ++i) m_sockout[i] = -1; for (i = 0; i < m_nports; ++i) { m_sockout[i] = ::socket(AF_INET, SOCK_DGRAM, protonum); if (m_sockout[i] < 0) { ::perror("socket(out)"); return false; } // Will Hall, Oct 2007 #if !defined(__WIN32__) && !defined(_WIN32) && !defined(WIN32) if (ifname) { struct in_addr if_addr_out; if (!get_address(m_sockout[i], &if_addr_out, ifname)) { ::fprintf(stderr, "socket(out): could not find interface address for %s\n", ifname); return false; } if (::setsockopt(m_sockout[i], IPPROTO_IP, IP_MULTICAST_IF, (char *) &if_addr_out, sizeof(if_addr_out))) { ::perror("setsockopt(IP_MULTICAST_IF)"); return false; } } #endif ::memset(&m_addrout[i], 0, sizeof(struct sockaddr_in)); m_addrout[i].sin_family = AF_INET; m_addrout[i].sin_addr.s_addr = ::inet_addr(udp_addr); m_addrout[i].sin_port = htons(iUdpPort + i); // Turn off loopback... int loop = 0; #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) // NOTE: The Winsock version of the IP_MULTICAST_LOOP option // is the semantically reverse than the UNIX version. const int sock = m_sockin[i]; #else const int sock = m_sockout[i]; #endif if (::setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof (loop)) < 0) { ::perror("setsockopt(IP_MULTICAST_LOOP)"); return false; } #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) unsigned long mode = 1; if (::ioctlsocket(m_sockout[i], FIONBIO, &mode)) { ::perror("ioctlsocket(O_NONBLOCK)"); return false; } #else if (::fcntl(m_sockout[i], F_SETFL, O_NONBLOCK)) { ::perror("fcntl(O_NONBLOCK)"); return false; } #endif } // Start listener thread... m_pRecvThread = new qmidinetUdpDeviceThread(m_sockin, m_nports); m_pRecvThread->start(); #endif // !CONFIG_IPV6 // Done. return true; } // Device termination method. void qmidinetUdpDevice::close (void) { #if defined(CONFIG_IPV6) if (m_sockin) { for (int i = 0; i < m_nports; ++i) { if (m_sockin[i]) delete m_sockin[i]; } delete [] m_sockin; m_sockin = nullptr; } if (m_sockout) { for (int i = 0; i < m_nports; ++i) { if (m_sockout[i]) delete m_sockout[i]; } delete [] m_sockout; m_sockout = nullptr; } m_udpaddr.clear(); if (m_udpport) { delete [] m_udpport; m_udpport = nullptr; } #else if (m_sockin) { for (int i = 0; i < m_nports; ++i) { if (m_sockin[i] >= 0) ::closesocket(m_sockin[i]); } delete [] m_sockin; m_sockin = nullptr; } if (m_sockout) { for (int i = 0; i < m_nports; ++i) { if (m_sockout[i] >= 0) ::closesocket(m_sockout[i]); } delete [] m_sockout; m_sockout = nullptr; } if (m_addrout) { delete [] m_addrout; m_addrout = nullptr; } if (m_pRecvThread) { if (m_pRecvThread->isRunning()) { m_pRecvThread->setRunState(false); // m_pRecvThread->terminate(); m_pRecvThread->wait(1200); // Timeout>1sec. } delete m_pRecvThread; m_pRecvThread = nullptr; } #endif // !CONFIG_IPV6 m_nports = 0; } // Data transmission methods. bool qmidinetUdpDevice::sendData ( unsigned char *data, unsigned short len, int port ) const { if (port < 0 || port >= m_nports) return false; #if defined(CONFIG_IPV6) if (m_sockout == nullptr) return false; if (m_sockout[port] == nullptr) return false; if (!m_sockout[port]->isValid() || m_sockout[port]->state() != QAbstractSocket::BoundState) { qWarning() << "sendData(sockout):" << port << "udp socket has invalid state" << m_sockout[port]->state(); return false; } QByteArray datagram((const char *) data, len); if (m_sockout[port]->writeDatagram(datagram, m_udpaddr, m_udpport[port]) < len) { qWarning() << "sendData(sockout):" << port << "udp socket error" << m_sockout[port]->error() << " " << m_sockout[port]->errorString(); return false; } #else if (m_sockout == nullptr) return false; if (m_sockout[port] < 0) return false; if (::sendto(m_sockout[port], (char *) data, len, 0, (struct sockaddr *) &m_addrout[port], sizeof(struct sockaddr_in)) < 0) { ::perror("sendto"); return false; } #endif // !CONFIG_IPV6 return true; } void qmidinetUdpDevice::recvData ( unsigned char *data, unsigned short len, int port ) { emit received(QByteArray((const char *) data, len), port); } // Receive data slot. void qmidinetUdpDevice::receive ( QByteArray data, int port ) { sendData((unsigned char *) data.constData(), data.length(), port); } #if defined(CONFIG_IPV6) // Process incoming datagrams. void qmidinetUdpDevice::readPendingDatagrams (void) { if (m_sockin == nullptr) return; for (int i = 0; i < m_nports; ++i) { while (m_sockin[i] && m_sockin[i]->hasPendingDatagrams()) { QByteArray datagram; int nread = m_sockin[i]->pendingDatagramSize(); datagram.resize(nread); nread = m_sockin[i]->readDatagram(datagram.data(), datagram.size()); if (nread > 0) { datagram.resize(nread); emit received(datagram, i); } } } } #else // Get interface address from supplied name. bool qmidinetUdpDevice::get_address ( int sock, struct in_addr *inaddr, const char *ifname ) { #if !defined(__WIN32__) && !defined(_WIN32) && !defined(WIN32) struct ifreq ifr; ::strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); if (::ioctl(sock, SIOCGIFFLAGS, (char *) &ifr)) { ::perror("ioctl(SIOCGIFFLAGS)"); return false; } if ((ifr.ifr_flags & IFF_UP) == 0) { fprintf(stderr, "interface %s is down\n", ifname); return false; } if (::ioctl(sock, SIOCGIFADDR, (char *) &ifr)) { ::perror("ioctl(SIOCGIFADDR)"); return false; } struct sockaddr_in sa; ::memcpy(&sa, &ifr.ifr_addr, sizeof(struct sockaddr_in)); inaddr->s_addr = sa.sin_addr.s_addr; return true; #else return false; #endif // !WIN32 } #endif // !CONFIG_IPV6 // end of qmidinetUdpDevice.h