├── portmusic_logo.png ├── pm_win ├── debugging_dlls.txt ├── pmwinmm.h ├── static.cmake ├── pmwin.c └── README_WIN.txt ├── pm_mac ├── pmmacosxcm.h ├── pmmac.c ├── README_MAC.txt └── Makefile.osx ├── pm_sndio ├── pmsndio.h └── pmsndio.c ├── porttime ├── porttime.c ├── ptwinmm.c ├── pthaiku.cpp ├── porttime.h ├── ptmacosx_cf.c ├── ptlinux.c └── ptmacosx_mach.c ├── pm_linux ├── pmlinuxalsa.h ├── pmlinuxnull.h ├── pmlinuxnull.c ├── pmlinux.c └── README_LINUX.txt ├── pm_java ├── jportmidi │ ├── JPortMidiException.java │ └── JPortMidiApi.java ├── pmjni │ ├── pmjni.rc │ ├── jportmidi_JportMidiApi.h │ └── pmjni.c ├── make.bat ├── CMakeLists.txt └── README.txt ├── packaging ├── portmidi.pc.in └── PortMidiConfig.cmake.in ├── pm_test ├── CMakeLists.txt ├── pmlist.c ├── qtest.c ├── recvvirtual.c ├── sendvirtual.c ├── multivirtual.c ├── fastrcv.c ├── midiclock.c ├── fast.c └── latency.c ├── license.txt ├── README.txt ├── README.md ├── pm_common ├── pmutil.h ├── CMakeLists.txt ├── pmutil.c └── pminternal.h └── CMakeLists.txt /portmusic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortMidi/portmidi/HEAD/portmusic_logo.png -------------------------------------------------------------------------------- /pm_win/debugging_dlls.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortMidi/portmidi/HEAD/pm_win/debugging_dlls.txt -------------------------------------------------------------------------------- /pm_mac/pmmacosxcm.h: -------------------------------------------------------------------------------- 1 | /* system-specific definitions */ 2 | 3 | PmError pm_macosxcm_init(void); 4 | void pm_macosxcm_term(void); 5 | -------------------------------------------------------------------------------- /pm_sndio/pmsndio.h: -------------------------------------------------------------------------------- 1 | /* pmsndio.h */ 2 | 3 | extern PmDeviceID pm_default_input_device_id; 4 | extern PmDeviceID pm_default_output_device_id; 5 | 6 | -------------------------------------------------------------------------------- /porttime/porttime.c: -------------------------------------------------------------------------------- 1 | /* porttime.c -- portable API for millisecond timer */ 2 | 3 | /* There is no machine-independent implementation code to put here */ 4 | -------------------------------------------------------------------------------- /pm_linux/pmlinuxalsa.h: -------------------------------------------------------------------------------- 1 | /* pmlinuxalsa.h -- system-specific definitions */ 2 | 3 | PmError pm_linuxalsa_init(void); 4 | void pm_linuxalsa_term(void); 5 | 6 | 7 | -------------------------------------------------------------------------------- /pm_linux/pmlinuxnull.h: -------------------------------------------------------------------------------- 1 | /* pmlinuxnull.h -- system-specific definitions */ 2 | 3 | PmError pm_linuxnull_init(void); 4 | void pm_linuxnull_term(void); 5 | 6 | 7 | -------------------------------------------------------------------------------- /pm_win/pmwinmm.h: -------------------------------------------------------------------------------- 1 | /* pmwinmm.h -- system-specific definitions for windows multimedia API */ 2 | 3 | void pm_winmm_init( void ); 4 | void pm_winmm_term( void ); 5 | 6 | -------------------------------------------------------------------------------- /pm_java/jportmidi/JPortMidiException.java: -------------------------------------------------------------------------------- 1 | // JPortMidiException -- thrown by JPortMidi methods 2 | 3 | package jportmidi; 4 | 5 | public class JPortMidiException extends Exception { 6 | public int error = 0; 7 | public JPortMidiException(int err, String msg) { 8 | super(msg); 9 | error = err; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /packaging/portmidi.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@PKGCONFIG_LIBDIR@ 4 | includedir=@PKGCONFIG_INCLUDEDIR@ 5 | 6 | Name: @CMAKE_PROJECT_NAME@ 7 | Description: @CMAKE_PROJECT_DESCRIPTION@ 8 | Version: @CMAKE_PROJECT_VERSION@ 9 | Cflags: -I${includedir} 10 | Libs: -L${libdir} -l@CMAKE_PROJECT_NAME@ 11 | Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@ 12 | -------------------------------------------------------------------------------- /pm_linux/pmlinuxnull.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pmlinuxnull.c -- system specific definitions 3 | * 4 | * written by: 5 | * Roger Dannenberg 6 | * 7 | * If there is no ALSA, you can define PMNULL and build PortMidi. It will 8 | * not report any devices, so you will not be able to open any, but if 9 | * you wanted to disable MIDI from some application, this could be used. 10 | * Mainly, this code shows the possibility of supporting multiple 11 | * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux. 12 | * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA. 13 | */ 14 | 15 | #ifdef PMNULL 16 | 17 | #include "portmidi.h" 18 | #include "pmlinuxnull.h" 19 | 20 | 21 | PmError pm_linuxnull_init(void) 22 | { 23 | return pmNoError; 24 | } 25 | 26 | 27 | void pm_linuxnull_term(void) 28 | { 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /packaging/PortMidiConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | # The imported target's INTERFACE_LINK_LIBRARIES property contains targets 4 | # provided by other CMake packages. Finding these dependencies is necessary 5 | # for this config to be actually usable and selected, but they must not be 6 | # looked with the REQUIRED keyword because this would raise an immediate error 7 | # also when the user wants CMake to continue (other locations, custom script). 8 | # https://cmake.org/cmake/help/latest/module/CMakeFindDependencyMacro.html 9 | 10 | include(CMakeFindDependencyMacro) 11 | 12 | if(UNIX AND NOT APPLE AND NOT HAIKU AND (@LINUX_DEFINES@ MATCHES ".*PMALSA.*")) 13 | find_dependency(ALSA) 14 | endif() 15 | 16 | if(NOT WIN32) 17 | set(THREADS_PREFER_PTHREAD_FLAG ON) 18 | find_dependency(Threads) 19 | endif() 20 | 21 | include("${CMAKE_CURRENT_LIST_DIR}/PortMidiTargets.cmake") 22 | 23 | check_required_components(PortMidi) 24 | -------------------------------------------------------------------------------- /pm_mac/pmmac.c: -------------------------------------------------------------------------------- 1 | /* pmmac.c -- PortMidi os-dependent code */ 2 | 3 | /* This file only needs to implement: 4 | pm_init(), which calls various routines to register the 5 | available midi devices, 6 | Pm_GetDefaultInputDeviceID(), and 7 | Pm_GetDefaultOutputDeviceID(). 8 | It is seperate from pmmacosxcm because we might want to register 9 | non-CoreMIDI devices. 10 | */ 11 | 12 | #include "stdlib.h" 13 | #include "portmidi.h" 14 | #include "pmutil.h" 15 | #include "pminternal.h" 16 | #include "pmmacosxcm.h" 17 | 18 | void pm_init(void) 19 | { 20 | pm_macosxcm_init(); 21 | } 22 | 23 | 24 | void pm_term(void) 25 | { 26 | pm_macosxcm_term(); 27 | } 28 | 29 | PmDeviceID Pm_GetDefaultInputDeviceID(void) 30 | { 31 | Pm_Initialize(); 32 | return pm_default_input_device_id; 33 | } 34 | 35 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { 36 | Pm_Initialize(); 37 | return pm_default_output_device_id; 38 | } 39 | 40 | void *pm_alloc(size_t s) { return malloc(s); } 41 | 42 | void pm_free(void *ptr) { free(ptr); } 43 | 44 | 45 | -------------------------------------------------------------------------------- /pm_win/static.cmake: -------------------------------------------------------------------------------- 1 | # static.cmake -- change flags to link with static runtime libraries 2 | # 3 | # Even when BUILD_SHARED_LIBS is OFF, CMake specifies linking wtih 4 | # multithread DLL, so you give inconsistent linking instructions 5 | # resulting in warning messages from MS Visual Studio. If you want 6 | # a static binary, I've found this approach works to eliminate 7 | # warnings and make everything static: 8 | # 9 | # Changes /MD (multithread DLL) to /MT (multithread static) 10 | 11 | if(MSVC) 12 | foreach(flag_var 13 | CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE 14 | CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO 15 | CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE 16 | CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) 17 | if(${flag_var} MATCHES "/MD") 18 | string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") 19 | endif(${flag_var} MATCHES "/MD") 20 | endforeach(flag_var) 21 | 22 | message(STATUS 23 | "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library") 24 | endif(MSVC) 25 | -------------------------------------------------------------------------------- /pm_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake file to build tests in this directory: pm_test 2 | 3 | # set the build directory to be in portmidi, not in portmidi/pm_test 4 | # this is required for Xcode: 5 | if(APPLE) 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) 7 | endif(APPLE) 8 | 9 | # if(WIN32) 10 | # if(NOT BUILD_SHARED_LIBS) 11 | # /MDd is multithread debug DLL, /MTd is multithread debug 12 | # /MD is multithread DLL, /MT is multithread. Change to static: 13 | # include(../pm_win/static.cmake) 14 | # endif() 15 | # endif(WIN32) 16 | 17 | if(HAIKU) 18 | add_compile_options(-fPIC) # Haiku x86_64 needs this explicitly 19 | endif() 20 | 21 | macro(add_test name) 22 | add_executable(${name} ${name}.c) 23 | target_link_libraries(${name} PRIVATE portmidi) 24 | set_property(TARGET ${name} PROPERTY MSVC_RUNTIME_LIBRARY 25 | "MultiThreaded$<$:Debug>${MSVCRT_DLL}") 26 | endmacro(add_test) 27 | 28 | add_test(testio) 29 | add_test(midithread) 30 | add_test(midithru) 31 | add_test(sysex) 32 | add_test(latency) 33 | add_test(mm) 34 | add_test(midiclock) 35 | add_test(qtest) 36 | add_test(fast) 37 | add_test(fastrcv) 38 | add_test(pmlist) 39 | if(WIN32) 40 | # windows does not implement Pm_CreateVirtualInput or Pm_CreateVirtualOutput 41 | else(WIN32) 42 | add_test(recvvirtual) 43 | add_test(sendvirtual) 44 | add_test(multivirtual) 45 | add_test(virttest) 46 | endif(WIN32) 47 | -------------------------------------------------------------------------------- /pm_java/pmjni/pmjni.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "afxres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (U.S.) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | #ifdef _WIN32 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | #pragma code_page(1252) 22 | #endif //_WIN32 23 | 24 | #ifdef APSTUDIO_INVOKED 25 | ///////////////////////////////////////////////////////////////////////////// 26 | // 27 | // TEXTINCLUDE 28 | // 29 | 30 | 1 TEXTINCLUDE 31 | BEGIN 32 | "resource.h\0" 33 | END 34 | 35 | 2 TEXTINCLUDE 36 | BEGIN 37 | "#include ""afxres.h""\r\n" 38 | "\0" 39 | END 40 | 41 | 3 TEXTINCLUDE 42 | BEGIN 43 | "\r\n" 44 | "\0" 45 | END 46 | 47 | #endif // APSTUDIO_INVOKED 48 | 49 | #endif // English (U.S.) resources 50 | ///////////////////////////////////////////////////////////////////////////// 51 | 52 | 53 | 54 | #ifndef APSTUDIO_INVOKED 55 | ///////////////////////////////////////////////////////////////////////////// 56 | // 57 | // Generated from the TEXTINCLUDE 3 resource. 58 | // 59 | 60 | 61 | ///////////////////////////////////////////////////////////////////////////// 62 | #endif // not APSTUDIO_INVOKED 63 | 64 | -------------------------------------------------------------------------------- /porttime/ptwinmm.c: -------------------------------------------------------------------------------- 1 | /* ptwinmm.c -- portable timer implementation for win32 */ 2 | 3 | 4 | #include "porttime.h" 5 | #include "windows.h" 6 | #include "time.h" 7 | 8 | 9 | TIMECAPS caps; 10 | 11 | static long time_offset = 0; 12 | static int time_started_flag = FALSE; 13 | static long time_resolution; 14 | static MMRESULT timer_id; 15 | static PtCallback *time_callback; 16 | 17 | void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, 18 | DWORD_PTR dw1, DWORD_PTR dw2) 19 | { 20 | (*time_callback)(Pt_Time(), (void *) dwUser); 21 | } 22 | 23 | 24 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 25 | { 26 | if (time_started_flag) return ptAlreadyStarted; 27 | timeBeginPeriod(resolution); 28 | time_resolution = resolution; 29 | time_offset = timeGetTime(); 30 | time_started_flag = TRUE; 31 | time_callback = callback; 32 | if (callback) { 33 | timer_id = timeSetEvent(resolution, 1, winmm_time_callback, 34 | (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); 35 | if (!timer_id) return ptHostError; 36 | } 37 | return ptNoError; 38 | } 39 | 40 | 41 | PMEXPORT PtError Pt_Stop(void) 42 | { 43 | if (!time_started_flag) return ptAlreadyStopped; 44 | if (time_callback && timer_id) { 45 | timeKillEvent(timer_id); 46 | time_callback = NULL; 47 | timer_id = 0; 48 | } 49 | time_started_flag = FALSE; 50 | timeEndPeriod(time_resolution); 51 | return ptNoError; 52 | } 53 | 54 | 55 | PMEXPORT int Pt_Started(void) 56 | { 57 | return time_started_flag; 58 | } 59 | 60 | 61 | PMEXPORT PtTimestamp Pt_Time(void) 62 | { 63 | return timeGetTime() - time_offset; 64 | } 65 | 66 | 67 | PMEXPORT void Pt_Sleep(int32_t duration) 68 | { 69 | Sleep(duration); 70 | } 71 | -------------------------------------------------------------------------------- /pm_linux/pmlinux.c: -------------------------------------------------------------------------------- 1 | /* pmlinux.c -- PortMidi os-dependent code */ 2 | 3 | /* This file only needs to implement pm_init(), which calls various 4 | routines to register the available midi devices. This file must 5 | be separate from the main portmidi.c file because it is system 6 | dependent, and it is separate from, pmlinuxalsa.c, because it 7 | might need to register non-alsa devices as well. 8 | 9 | NOTE: if you add non-ALSA support, you need to fix :alsa_poll() 10 | in pmlinuxalsa.c, which assumes all input devices are ALSA. 11 | */ 12 | 13 | #include "stdlib.h" 14 | #include "portmidi.h" 15 | #include "pmutil.h" 16 | #include "pminternal.h" 17 | 18 | #ifdef PMALSA 19 | #include "pmlinuxalsa.h" 20 | #endif 21 | 22 | #ifdef PMNULL 23 | #include "pmlinuxnull.h" 24 | #endif 25 | 26 | #if !(defined(PMALSA) || defined(PMNULL)) 27 | #error One of PMALSA or PMNULL must be defined 28 | #endif 29 | 30 | void pm_init(void) 31 | { 32 | /* Note: it is not an error for PMALSA to fail to initialize. 33 | * It may be a design error that the client cannot query what subsystems 34 | * are working properly other than by looking at the list of available 35 | * devices. 36 | */ 37 | #ifdef PMALSA 38 | pm_linuxalsa_init(); 39 | #endif 40 | #ifdef PMNULL 41 | pm_linuxnull_init(); 42 | #endif 43 | } 44 | 45 | void pm_term(void) 46 | { 47 | #ifdef PMALSA 48 | pm_linuxalsa_term(); 49 | #endif 50 | #ifdef PMNULL 51 | pm_linuxnull_term(); 52 | #endif 53 | } 54 | 55 | PmDeviceID Pm_GetDefaultInputDeviceID(void) { 56 | Pm_Initialize(); 57 | return pm_default_input_device_id; 58 | } 59 | 60 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { 61 | Pm_Initialize(); 62 | return pm_default_output_device_id; 63 | } 64 | 65 | void *pm_alloc(size_t s) { return malloc(s); } 66 | 67 | void pm_free(void *ptr) { free(ptr); } 68 | 69 | -------------------------------------------------------------------------------- /pm_java/make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem This is an out-of-date script for Windows to build a Java application 4 | rem (PmDefaults) with PortMidi external library.xb 5 | 6 | rem Compile the java PortMidi interface classes. 7 | javac jportmidi/*.java 8 | 9 | rem Compile the pmdefaults application. 10 | javac -classpath . pmdefaults/*.java 11 | 12 | rem Temporarily copy the portmusic_logo.png file here to add to the jar file. 13 | copy pmdefaults\portmusic_logo.png . > nul 14 | 15 | rem Create a directory to hold the distribution. 16 | mkdir win32 17 | 18 | rem Attempt to copy the interface DLL to the distribution directory. 19 | 20 | if exist "..\release\pmjni.dll" goto have-dll 21 | 22 | echo "ERROR: pmjni.dll not found!" 23 | exit /b 1 24 | 25 | :have-dll 26 | copy "..\release\pmjni.dll" win32\pmjni.dll > nul 27 | 28 | rem Create a java archive (jar) file of the distribution. 29 | jar cmf pmdefaults\manifest.txt win32\pmdefaults.jar pmdefaults\*.class portmusic_logo.png jportmidi\*.class 30 | 31 | rem Clean up the temporary image file now that it is in the jar file. 32 | del portmusic_logo.png 33 | 34 | rem Copy the java execution code obtained from 35 | rem http://devwizard.free.fr/html/en/JavaExe.html to the distribution 36 | rem directory. The copy also renames the file to our desired executable 37 | rem name. 38 | copy JavaExe.exe win32\pmdefaults.exe > nul 39 | 40 | rem Integrate the icon into the executable using UpdateRsrcJavaExe from 41 | rem http://devwizard.free.fr 42 | UpdateRsrcJavaExe -run -exe=win32\pmdefaults.exe -ico=pmdefaults\pmdefaults.ico 43 | 44 | rem Copy the 32-bit windows read me file to the distribution directory. 45 | copy pmdefaults\readme-win32.txt win32\README.txt > nul 46 | 47 | rem Copy the license file to the distribution directory. 48 | copy pmdefaults\pmdefaults-license.txt win32\license.txt > nul 49 | 50 | echo "You can run pmdefaults.exe in win32" 51 | -------------------------------------------------------------------------------- /pm_test/pmlist.c: -------------------------------------------------------------------------------- 1 | /* pmlist.c -- list portmidi devices and numbers 2 | * 3 | * This program lists devices. When you type return, it 4 | * restarts portmidi and lists devices again. It is mainly 5 | * a test for shutting down and restarting. 6 | * 7 | * Roger B. Dannenberg, Feb 2022 8 | */ 9 | 10 | #include "portmidi.h" 11 | #include "porttime.h" 12 | #include "stdlib.h" 13 | #include "stdio.h" 14 | #include "string.h" 15 | #include "assert.h" 16 | 17 | #define DEVICE_INFO NULL 18 | #define DRIVER_INFO NULL 19 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 20 | 21 | #define STRING_MAX 80 /* used for console input */ 22 | 23 | void show_usage(void) 24 | { 25 | printf("Usage: pmlist [-h]\n -h means help.\n" 26 | " Type return to rescan and list devices, q to quit\n"); 27 | } 28 | 29 | 30 | int main(int argc, char *argv[]) 31 | { 32 | if (argc > 1) { 33 | show_usage(); 34 | exit(0); 35 | } 36 | 37 | while (1) { 38 | char input[STRING_MAX]; 39 | const char *deflt; 40 | const char *in_or_out; 41 | int default_in, default_out, i; 42 | 43 | // Pm_Initialize(); 44 | /* list device information */ 45 | default_in = Pm_GetDefaultInputDeviceID(); 46 | default_out = Pm_GetDefaultOutputDeviceID(); 47 | for (i = 0; i < Pm_CountDevices(); i++) { 48 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 49 | printf("%d: %s, %s", i, info->interf, info->name); 50 | deflt = ""; 51 | if (i == default_out || i == default_in) { 52 | deflt = "default "; 53 | } 54 | in_or_out = (info->input ? "input" : "output"); 55 | printf(" (%s%s)\n", deflt, in_or_out); 56 | } 57 | if (fgets(input, STRING_MAX, stdin) && input[0] == 'q') { 58 | return 0; 59 | } 60 | Pm_Terminate(); 61 | } 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * PortMidi Portable Real-Time MIDI Library 3 | * 4 | * license.txt -- a copy of the PortMidi copyright notice and license information 5 | * 6 | * Latest version available at: http://sourceforge.net/projects/portmedia 7 | * 8 | * Copyright (c) 1999-2000 Ross Bencina and Phil Burk 9 | * Copyright (c) 2001-2009 Roger B. Dannenberg 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining 12 | * a copy of this software and associated documentation files 13 | * (the "Software"), to deal in the Software without restriction, 14 | * including without limitation the rights to use, copy, modify, merge, 15 | * publish, distribute, sublicense, and/or sell copies of the Software, 16 | * and to permit persons to whom the Software is furnished to do so, 17 | * subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 26 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 27 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | */ 30 | 31 | /* 32 | * The text above constitutes the entire PortMidi license; however, 33 | * the PortMusic community also makes the following non-binding requests: 34 | * 35 | * Any person wishing to distribute modifications to the Software is 36 | * requested to send the modifications to the original developer so that 37 | * they can be incorporated into the canonical version. It is also 38 | * requested that these non-binding requests be included along with the 39 | * license above. 40 | */ 41 | -------------------------------------------------------------------------------- /pm_win/pmwin.c: -------------------------------------------------------------------------------- 1 | /* pmwin.c -- PortMidi os-dependent code */ 2 | 3 | /* This file only needs to implement: 4 | pm_init(), which calls various routines to register the 5 | available midi devices, 6 | Pm_GetDefaultInputDeviceID(), and 7 | Pm_GetDefaultOutputDeviceID(). 8 | This file must 9 | be separate from the main portmidi.c file because it is system 10 | dependent, and it is separate from, say, pmwinmm.c, because it 11 | might need to register devices for winmm, directx, and others. 12 | 13 | */ 14 | 15 | #include "stdlib.h" 16 | #include "portmidi.h" 17 | #include "pmutil.h" 18 | #include "pminternal.h" 19 | #include "pmwinmm.h" 20 | #ifdef DEBUG 21 | #include "stdio.h" 22 | #endif 23 | #include 24 | 25 | /* pm_exit is called when the program exits. 26 | It calls pm_term to make sure PortMidi is properly closed. 27 | If DEBUG is on, we prompt for input to avoid losing error messages. 28 | */ 29 | static void pm_exit(void) { 30 | pm_term(); 31 | } 32 | 33 | 34 | static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType) 35 | { 36 | exit(1); /* invokes pm_exit() */ 37 | ExitProcess(1); /* probably never called */ 38 | return TRUE; 39 | } 40 | 41 | /* pm_init is the windows-dependent initialization.*/ 42 | void pm_init(void) 43 | { 44 | atexit(pm_exit); 45 | SetConsoleCtrlHandler(ctrl_c_handler, TRUE); 46 | #ifdef DEBUG 47 | printf("registered pm_exit with atexit()\n"); 48 | #endif 49 | pm_winmm_init(); 50 | /* initialize other APIs (DirectX?) here */ 51 | } 52 | 53 | 54 | void pm_term(void) { 55 | pm_winmm_term(); 56 | } 57 | 58 | 59 | static PmDeviceID pm_get_default_device_id(int is_input, char *key) { 60 | #define PATTERN_MAX 256 61 | /* Find first input or device -- this is the default. */ 62 | PmDeviceID id = pmNoDevice; 63 | int i; 64 | Pm_Initialize(); /* make sure descriptors exist! */ 65 | for (i = 0; i < pm_descriptor_len; i++) { 66 | if (pm_descriptors[i].pub.input == is_input) { 67 | id = i; 68 | break; 69 | } 70 | } 71 | return id; 72 | } 73 | 74 | 75 | PmDeviceID Pm_GetDefaultInputDeviceID(void) { 76 | return pm_get_default_device_id(TRUE, 77 | "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E"); 78 | } 79 | 80 | 81 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { 82 | return pm_get_default_device_id(FALSE, 83 | "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E"); 84 | } 85 | 86 | 87 | #include "stdio.h" 88 | 89 | void *pm_alloc(size_t s) { 90 | return malloc(s); 91 | } 92 | 93 | 94 | void pm_free(void *ptr) { 95 | free(ptr); 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /porttime/pthaiku.cpp: -------------------------------------------------------------------------------- 1 | // pthaiku.cpp - portable timer implementation for Haiku 2 | 3 | #include "porttime.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace { 9 | const uint32 timerMessage = 'PTTM'; 10 | 11 | struct TimerLooper : BLooper { 12 | TimerLooper() : BLooper() { 13 | } 14 | 15 | 16 | virtual void MessageReceived(BMessage *message) 17 | { 18 | PtCallback *callback; 19 | void *userData; 20 | if (message->what == timerMessage && message->FindPointer("callback", (void**)&callback) == B_OK && message->FindPointer("userData", &userData) == B_OK) { 21 | (*callback)(Pt_Time(), userData); 22 | } 23 | BLooper::MessageReceived(message); 24 | } 25 | }; 26 | 27 | bool time_started_flag = false; 28 | bigtime_t time_offset; 29 | TimerLooper *timerLooper; 30 | BMessageRunner *timerRunner; 31 | } 32 | 33 | extern "C" { 34 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 35 | { 36 | if (time_started_flag) return ptAlreadyStarted; 37 | time_offset = system_time(); 38 | if (callback) { 39 | timerLooper = new TimerLooper; 40 | timerLooper->Run(); 41 | BMessenger target(timerLooper); 42 | BMessage message(timerMessage); 43 | message.AddPointer("callback", (void*)callback); 44 | message.AddPointer("userData", userData); 45 | bigtime_t interval = resolution * 1000; 46 | timerRunner = new BMessageRunner(target, &message, interval); 47 | if(timerRunner->InitCheck() != B_OK) { 48 | delete timerRunner; 49 | timerRunner = NULL; 50 | timerLooper->PostMessage(B_QUIT_REQUESTED); 51 | timerLooper = NULL; 52 | return ptHostError; 53 | } 54 | } 55 | time_started_flag = true; 56 | return ptNoError; 57 | } 58 | 59 | 60 | PtError Pt_Stop() 61 | { 62 | if (!time_started_flag) return ptAlreadyStopped; 63 | time_started_flag = false; 64 | delete timerRunner; 65 | timerRunner = NULL; 66 | timerLooper->PostMessage(B_QUIT_REQUESTED); 67 | timerLooper = NULL; 68 | return ptNoError; 69 | } 70 | 71 | 72 | int Pt_Started() 73 | { 74 | return time_started_flag; 75 | } 76 | 77 | 78 | PtTimestamp Pt_Time() 79 | { 80 | return (system_time() - time_offset) / 1000; 81 | } 82 | 83 | 84 | void Pt_Sleep(int32_t duration) 85 | { 86 | snooze(duration * 1000); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pm_java/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # pm_java/CMakeLists.txt -- builds pmjni 2 | 3 | find_package(Java) 4 | message(STATUS "Java_JAVA_EXECUTABLE is " ${Java_JAVA_EXECUTABLE}) 5 | 6 | # Build pmjni 7 | # this CMakeLists.txt is only loaded if BUILD_JAVA_NATIVE_INTERFACE 8 | # This jni library includes portmidi sources to give just 9 | # one library for JPortMidi users to manage rather than two. 10 | if(UNIX) 11 | include(FindJNI) 12 | # message(STATUS "JAVA_JVM_LIB_PATH is " ${JAVA_JVM_LIB_PATH}) 13 | # message(STATUS "JAVA_INCLUDE_PATH is " ${JAVA_INCLUDE_PATH}) 14 | # note: should use JAVA_JVM_LIB_PATH, but it is not set properly 15 | # note: user might need to set JAVA_INCLUDE_PATH manually 16 | # 17 | # this will probably break on BSD and other Unix systems; the fix 18 | # depends on whether FindJNI can find Java or not. If yes, then 19 | # we should try to rely on automatically set JAVA_INCLUDE_PATH and 20 | # JAVA_INCLUDE_PATH2; if no, then we need to make both JAVA_INCLUDE_PATH 21 | # and JAVA_INCLUDE_PATH2 set by user (will need clear documentation 22 | # because JAVA_INCLUDE_PATH2 is pretty obscure) 23 | set(JAVA_INCLUDE_PATH ${JAVA_INCLUDE_PATH-UNKNOWN} 24 | CACHE STRING "where to find Java SDK include directory") 25 | # libjvm.so is found relative to JAVA_INCLUDE_PATH: 26 | if (HAIKU) 27 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/haiku) 28 | else() 29 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/linux) 30 | endif() 31 | elseif(WIN32) 32 | include(FindJNI) 33 | # note: should use JAVA_JVM_LIB_PATH, but it is not set properly 34 | set(JAVAVM_LIB ${JAVA_INCLUDE_PATH}/../lib/jvm.lib) 35 | 36 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) 37 | # message(STATUS "JAVA_INCLUDE_PATHS: " ${JAVA_INCLUDE_PATHS}) 38 | # message(STATUS "JAVAVM_LIB: " ${JAVAVM_LIB}) 39 | endif() 40 | 41 | add_library(pmjni SHARED pmjni/pmjni.c) 42 | target_sources(pmjni PRIVATE ${PM_LIB_PUBLIC_SRC} ${PM_LIB_PRIVATE_SRC}) 43 | message(STATUS "Java paths ${JAVA_INCLUDE_PATHS}") 44 | # message(STATUS "Java pmjni src: pmjni/pmjni.c ${PM_LIB_SHARED_SRC} " 45 | # "${PM_LIB_PRIVATE_SRC}") 46 | target_include_directories(pmjni PUBLIC ${JAVA_INCLUDE_PATHS}) 47 | target_link_libraries(pmjni ${PM_NEEDED_LIBS}) 48 | set_target_properties(pmjni PROPERTIES 49 | VERSION ${LIBRARY_VERSION} 50 | SOVERSION ${LIBRARY_SOVERSION} 51 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 52 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 53 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" 54 | EXECUTABLE_EXTENSION "jnilib" 55 | MACOSX_RPATH ON) 56 | 57 | -------------------------------------------------------------------------------- /pm_java/README.txt: -------------------------------------------------------------------------------- 1 | README.txt 2 | Roger B. Dannenberg 3 | 16 Jun 2009 4 | updated 2021 5 | 6 | This directory implements a JNI library so that Java programs can use 7 | the PortMidi API. This was mainly created to implement PmDefaults, a 8 | program to set default input and output devices for PortMidi 9 | applications. Because it is rarely used, PmDefaults was dropped from 10 | PortMidi starting with v3. I recommend you implement per-application 11 | preferences and store default PortMidi device numbers for input and 12 | output there. (Or better yet, store device *names* since numbers can 13 | change if you plug in or remove USB devices.) 14 | 15 | Even without PmDefaults, a PortMidi API for Java is probably an 16 | improvement over other Java libraries, but there is very little MIDI 17 | development in Java, so I have not maintained this API. The only thing 18 | probably seriously wrong now is an interface to the 19 | Pm_CreateVirtualInput and Pm_CreateVirtualOutput functions, which are 20 | new additions. 21 | 22 | I will leave the code here, and if there is a demand, please either 23 | update it or let your needs be known. Perhaps I or someone can help. 24 | 25 | ================================================================== 26 | 27 | BUILDING Java EXTERNAL LIBRARY 28 | 29 | You must have a JDK installed (Java development kit including javac 30 | (the Java compiler), jni.h, etc. 31 | 32 | Test java on the command line, e.g., type: javac -version 33 | 34 | Enable these options in the main CMakeLists.txt file (run CMake 35 | from your top-level repository directory): 36 | BUILD_JAVA_NATIVE_INTERFACE 37 | In my Ubuntu linux with jdk-15, ccmake was unable to find my JDK, so 38 | I have to manually set CMake variables as follows (type 't' to see 39 | these in ccmake): 40 | JAVA_AWT_INCLUDE_PATH /usr/lib/jvm/jdk-15/include 41 | JAVA_AWT_LIBRARY /usr/lib/jvm/jdk-15/lib 42 | JAVA_INCLUDE_PATH /usr/lib/jvm/jdk-15/include 43 | JAVA_INCLUDE_PATH2 /usr/lib/jvm/jdk-15/include 44 | JAVA_JVM_LIBRARY /usr/lib/jvm/jdk-15/lib 45 | Of course, your paths may differ. 46 | 47 | 48 | ---- old implementation notes ---- 49 | 50 | For Windows, we use the free software JavaExe.exe. The copy here was 51 | downloaded from 52 | 53 | http://software.techrepublic.com.com/abstract.aspx?kw=javaexe&docid=767485 54 | 55 | I found this page by visiting http://software.techrepublic.com.com and 56 | searching in the "Software" category for "JavaExe" 57 | 58 | JavaExe works by placing the JavaExe.exe file in the directory with the 59 | Java application jar file and then *renaming* JavaExe.exe to the name 60 | of the jar file, but keeping the .exe extension. (See make.bat for this 61 | step.) Documentation for JavaExe can be obtained by downloading the 62 | whole program from the URL(s) above. 63 | -------------------------------------------------------------------------------- /pm_mac/README_MAC.txt: -------------------------------------------------------------------------------- 1 | README_MAC.txt for PortMidi 2 | Roger Dannenberg 3 | 20 nov 2009 4 | 5 | revised Mar 2024 to remove pmdefaults references 6 | revised Jan 2022 for the PortMidi/portmidi repo on github.com 7 | revised 20 Sep 2010 for Xcode 4.3.2 and CMake 2.8.8 8 | 9 | This documents how I build PortMidi for macOS. It's not the only way, 10 | and command-line/scripting enthusiasts will say it's not even a good 11 | way. Feel free to contribute your approach if you are willing to 12 | describe it carefully and test it. 13 | 14 | Install Xcode and the CMake application, CMake.app. I use the GUI 15 | version of CMake which makes it easy to see/edit variables and 16 | options. 17 | 18 | ==== USING CMAKE ==== 19 | 20 | Run CMake.app and select your portmidi repo working directory as the 21 | location for source and build. (Yes, I use so called "in-tree" 22 | builds -- it doesn't hurt, but I don't think it is necessary.) 23 | 24 | Default settings should all be fine, but select options under BUILD if 25 | you wish: 26 | 27 | BUILD_NATIVE_JAVA_INTERFACE to build a Java interface (JNI) library. 28 | 29 | BUILD_PORTMIDI_TESTS to create some test programs. Of particular 30 | interest are test/mm, a handy command-line MIDI Input Monitor, and 31 | test/testio, a simple command-line program to send or receive some 32 | MIDI notes in case you need a quick test: What devices do I have? Does 33 | this input work? Does this output work? 34 | 35 | I disable BUILD_SHARED_LIBS and always link statically: Static linking only 36 | adds about 40KB to any application and then you don't have to worry 37 | about versions, instally, copying or finding the dynamic link library, 38 | etc. 39 | 40 | To make sure you link statically, I rename the library to 41 | libportmidi_static.a. To do this, set PM_STATIC_LIB_NAME (in CMake, 42 | under the "PM" group) to "portmidi_static", and of course your 43 | application will have to specify portmidi_static as the library to 44 | link to. 45 | 46 | If you are building simple command-line applications, you might want 47 | to enable PM_CHECK_ERRORS. If you do, then calls into the PortMidi 48 | library will print error messages and exit in the event of an error 49 | (such as trying to open a device that does not exist). This saves you 50 | from having to check for errors everytime you call a library function 51 | or getting confused when errors are detected but not reported. For 52 | high-quality applications, do NOT enable PM_CHECK_ERRORS -- any 53 | failure could immediately abort your whole application, which is not 54 | very friendly to users. 55 | 56 | Click on Configure (maybe a couple of times). 57 | 58 | Click on Generate and make an Xcode project. 59 | 60 | Open portmidi/portmidi.xcodeproj with Xcode and build what you 61 | need. The simplest thing is to build the ALL_BUILD target. Be careful 62 | to specify a Debug or Release depending on what you want. "ALL_BUILD" 63 | is a misnomer -- it only builds the version you select. 64 | 65 | 66 | -------------------------------------------------------------------------------- /porttime/porttime.h: -------------------------------------------------------------------------------- 1 | /** @file porttime.h portable interface to millisecond timer. */ 2 | 3 | /* CHANGE LOG FOR PORTTIME 4 | 10-Jun-03 Mark Nelson & RBD 5 | boost priority of timer thread in ptlinux.c implementation 6 | */ 7 | 8 | #ifndef PORTMIDI_PORTTIME_H 9 | #define PORTMIDI_PORTTIME_H 10 | 11 | /* Should there be a way to choose the source of time here? */ 12 | 13 | #ifdef WIN32 14 | #ifndef INT32_DEFINED 15 | /* rather than having users install a special .h file for windows, 16 | just put the required definitions inline here. portmidi.h uses 17 | these too, so the definitions are (unfortunately) duplicated there 18 | */ 19 | typedef int int32_t; 20 | typedef unsigned int uint32_t; 21 | #define INT32_DEFINED 22 | #endif 23 | #else 24 | #include /* needed for int32_t */ 25 | #endif 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | #ifndef PMEXPORT 32 | #ifdef _WINDLL 33 | #define PMEXPORT __declspec(dllexport) 34 | #else 35 | #define PMEXPORT 36 | #endif 37 | #endif 38 | 39 | /** @defgroup grp_porttime PortTime: Millisecond Timer 40 | @{ 41 | */ 42 | 43 | typedef enum { 44 | ptNoError = 0, /**< success */ 45 | ptHostError = -10000, /**< a system-specific error occurred */ 46 | ptAlreadyStarted, /**< cannot start timer because it is already 47 | started */ 48 | ptAlreadyStopped, /**< cannot stop timer because it is already 49 | stopped */ 50 | ptInsufficientMemory /**< memory could not be allocated */ 51 | } PtError; /**< @brief @enum PtError PortTime error code; a common return type. 52 | * No error is indicated by zero; errors are indicated by < 0. 53 | */ 54 | 55 | /** real time or time offset in milliseconds. */ 56 | typedef int32_t PtTimestamp; 57 | 58 | /** a function that gets a current time */ 59 | typedef void (PtCallback)(PtTimestamp timestamp, void *userData); 60 | 61 | /** start a real-time clock service. 62 | 63 | @param resolution the timer resolution in ms. The time will advance every 64 | \p resolution ms. 65 | 66 | @param callback a function pointer to be called every resolution ms. 67 | 68 | @param userData is passed to \p callback as a parameter. 69 | 70 | @return #ptNoError on success. See #PtError for other values. 71 | */ 72 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); 73 | 74 | /** stop the timer. 75 | 76 | @return #ptNoError on success. See #PtError for other values. 77 | */ 78 | PMEXPORT PtError Pt_Stop(void); 79 | 80 | /** test if the timer is running. 81 | 82 | @return TRUE or FALSE 83 | */ 84 | PMEXPORT int Pt_Started(void); 85 | 86 | /** get the current time in ms. 87 | 88 | @return the current time 89 | */ 90 | PMEXPORT PtTimestamp Pt_Time(void); 91 | 92 | /** pauses the current thread, allowing other threads to run. 93 | 94 | @param duration the length of the pause in ms. The true duration 95 | of the pause may be rounded to the nearest or next clock tick 96 | as determined by resolution in #Pt_Start(). 97 | */ 98 | PMEXPORT void Pt_Sleep(int32_t duration); 99 | 100 | /** @} */ 101 | 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | 106 | #endif /* PORTMIDI_PORTTIME_H */ 107 | -------------------------------------------------------------------------------- /pm_linux/README_LINUX.txt: -------------------------------------------------------------------------------- 1 | README_LINUX.txt for PortMidi 2 | Roger Dannenberg 3 | 6 Dec 2012, revised May 2022 4 | 5 | Contents: 6 | To make PortMidi 7 | The pmdefaults program 8 | Setting LD_LIBRARY_PATH 9 | A note about amd64 10 | Using autoconf 11 | Using configure 12 | Changelog 13 | 14 | 15 | See ../README.md for general instructions. 16 | 17 | THE pmdefaults PROGRAM 18 | 19 | (This may be obsolete. It is older than `../README.md` which 20 | also discusses pmdefaults, and Java support may be removed 21 | unless someone claims they use it... -RBD) 22 | 23 | You should install pmdefaults. It provides a graphical interface 24 | for selecting default MIDI IN and OUT devices so that you don't 25 | have to build device selection interfaces into all your programs 26 | and so users have a single place to set a preference. 27 | 28 | Follow the instructions above to run ccmake, making sure that 29 | CMAKE_BUILD_TYPE is Release. Run make as described above. Then: 30 | 31 | sudo make install 32 | 33 | This will install PortMidi libraries and the pmdefault program. 34 | You must alos have the environment variable LD_LIBRARY_PATH set 35 | to include /usr/local/lib (where libpmjni.so is installed). 36 | 37 | Now, you can run pmdefault. 38 | 39 | 40 | SETTING LD_LIBRARY_PATH 41 | 42 | pmdefaults will not work unless LD_LIBRARY_PATH includes a 43 | directory (normally /usr/local/lib) containing libpmjni.so, 44 | installed as described above. 45 | 46 | To set LD_LIBRARY_PATH, you might want to add this to your 47 | ~/.profile (if you use the bash shell): 48 | 49 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 50 | export LD_LIBRARY_PATH 51 | 52 | 53 | A NOTE ABOUT AMD64: 54 | 55 | When compiling portmidi under linux on an AMD64, I had to add the -fPIC 56 | flag to the gcc flags. 57 | 58 | Reason: when trying to build John Harrison's pyPortMidi gcc bailed out 59 | with this error: 60 | 61 | ./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC 62 | ./linux/libportmidi.a: could not read symbols: Bad value 63 | collect2: ld returned 1 exit status 64 | error: command 'gcc' failed with exit status 1 65 | 66 | What they said: 67 | http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3 68 | On certain architectures (AMD64 amongst them), shared libraries *must* 69 | be "PIC-enabled". 70 | 71 | CHANGELOG 72 | 73 | 27-may-2022 Roger B. Dannenberg 74 | Some updates to this file. 75 | 76 | 6-dec-2012 Roger B. Dannenberg 77 | Copied notes on Autoconf from Audacity sources 78 | 79 | 22-jan-2010 Roger B. Dannenberg 80 | Updated instructions about Java paths 81 | 82 | 14-oct-2009 Roger B. Dannenberg 83 | Using CMake now for building and configuration 84 | 85 | 29-aug-2006 Roger B. Dannenberg 86 | Fixed PortTime to join with time thread for clean exit. 87 | 88 | 28-aug-2006 Roger B. Dannenberg 89 | Updated this documentation. 90 | 91 | 08-Jun-2004 Roger B. Dannenberg 92 | Updated code to use new system abstraction. 93 | 94 | 12-Apr-2003 Roger B. Dannenberg 95 | Fixed pm_test/test.c to filter clocks and active messages. 96 | Integrated changes from Clemens Ladisch: 97 | cleaned up pmlinuxalsa.c 98 | record timestamp on sysex input 99 | deallocate some resources previously left open 100 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | README for PortMidi 2 | 3 | Roger B. Dannenberg 4 | 5 | Documentation for PortMidi is found in pm_common/portmidi.h. 6 | Documentation in HTML is available at portmidi.github.io/portmidi_docs/ 7 | 8 | Additional documentation: 9 | - README.md (overview, how to build, what's new) 10 | - Windows: see pm_win/README_WIN.txt and pm_win/debugging_dlls.txt 11 | - Linux: see pm_linux/README_LINUX.txt 12 | - Mac OSX: see pm_mac/README_MAC.txt 13 | - Other Languages: look for other repos at github.com/PortMidi, 14 | and search README.md for pointers to other projects. 15 | 16 | ---------- some notes on the design of PortMidi ---------- 17 | 18 | POINTERS VS DEVICE NUMBERS 19 | 20 | When you open a MIDI port, PortMidi allocates a structure to 21 | maintain the state of the open device. Since every device is 22 | also listed in a table, you might think it would be simpler to 23 | use the table index rather than a pointer to identify a device. 24 | This would also help with error checking (it's hard to make 25 | sure a pointer is valid). PortMidi's design parallels that of 26 | PortAudio. 27 | 28 | ERROR HANDLING 29 | 30 | Error handling turned out to be much more complicated than expected. 31 | PortMidi functions return error codes that the caller can check. 32 | In addition, errors may occur asynchronously due to MIDI input. 33 | However, for Windows, there are virtually no errors that can 34 | occur if the code is correct and not passing bogus values. One 35 | exception is an error that the system is out of memory, but my 36 | guess is that one is unlikely to recover gracefully from that. 37 | Therefore, all errors in callbacks are guarded by assert(), which 38 | means not guarded at all in release configurations. 39 | 40 | Ordinarily, the caller checks for an error code. If the error is 41 | system-dependent, pmHostError is returned and the caller can 42 | call Pm_GetHostErrorText to get a text description of the error. 43 | 44 | Host error codes are system-specific and are recorded in the 45 | system-specific data allocated for each open MIDI port. 46 | However, if an error occurs on open or close, 47 | we cannot store the error with the device because there will be 48 | no device data (assuming PortMidi cleans up after devices that 49 | are not open). For open and close, we will convert the error 50 | to text, copy it to a global string, and set pm_hosterror, a 51 | global flag. 52 | 53 | Similarly, whenever a Read or Write operation returns pmHostError, 54 | the corresponding error string is copied to a global string 55 | and pm_hosterror is set. This makes getting error strings 56 | simple and uniform, although it does cost a string copy and some 57 | overhead even if the user does not want to look at the error data. 58 | 59 | The system-specific Read, Write, Poll, etc. implementations should 60 | check for asynchronous errors and return immediately if one is 61 | found so that these get reported. This happens in the Mac OS X 62 | code, where lots of things are happening in callbacks, but again, 63 | in Windows, there are no error codes recorded in callbacks. 64 | 65 | DEBUGGING 66 | 67 | If you are building a console application for research, we suggest 68 | compiling with the option PM_CHECK_ERRORS. This will insert a 69 | check for error return values at the end of each PortMidi 70 | function. If an error is encountered, a text message is printed 71 | using printf(), the user is asked to type ENTER, and then exit(-1) 72 | is called to clean up and terminate the program. 73 | 74 | You should not use PM_CHECK_ERRORS if printf() does not work 75 | (e.g. this is not a console application under Windows, or there 76 | is no visible console on some other OS), and you should not use 77 | PM_CHECK_ERRORS if you intend to recover from errors rather than 78 | abruptly terminate the program. 79 | 80 | The Windows version (and perhaps others) also offers a DEBUG 81 | compile-time option. See README_WIN.txt. 82 | 83 | RELEASE 84 | 85 | To make a new release, update VERSION variable in CMakeLists.txt. 86 | 87 | Update CHANGELOG.txt. What's new? 88 | 89 | -------------------------------------------------------------------------------- /porttime/ptmacosx_cf.c: -------------------------------------------------------------------------------- 1 | /* ptmacosx.c -- portable timer implementation for mac os x */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | 13 | #include "porttime.h" 14 | 15 | #define THREAD_IMPORTANCE 30 16 | #define LONG_TIME 1000000000.0 17 | 18 | static int time_started_flag = FALSE; 19 | static CFAbsoluteTime startTime = 0.0; 20 | static CFRunLoopRef timerRunLoop; 21 | 22 | typedef struct { 23 | int resolution; 24 | PtCallback *callback; 25 | void *userData; 26 | } PtThreadParams; 27 | 28 | 29 | void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) 30 | { 31 | PtThreadParams *params = (PtThreadParams*)info; 32 | (*params->callback)(Pt_Time(), params->userData); 33 | } 34 | 35 | static void* Pt_Thread(void *p) 36 | { 37 | CFTimeInterval timerInterval; 38 | CFRunLoopTimerContext timerContext; 39 | CFRunLoopTimerRef timer; 40 | PtThreadParams *params = (PtThreadParams*)p; 41 | /* CFTimeInterval timeout; */ 42 | 43 | /* raise the thread's priority */ 44 | kern_return_t error; 45 | thread_extended_policy_data_t extendedPolicy; 46 | thread_precedence_policy_data_t precedencePolicy; 47 | 48 | extendedPolicy.timeshare = 0; 49 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, 50 | (thread_policy_t)&extendedPolicy, 51 | THREAD_EXTENDED_POLICY_COUNT); 52 | if (error != KERN_SUCCESS) { 53 | mach_error("Couldn't set thread timeshare policy", error); 54 | } 55 | 56 | precedencePolicy.importance = THREAD_IMPORTANCE; 57 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, 58 | (thread_policy_t)&precedencePolicy, 59 | THREAD_PRECEDENCE_POLICY_COUNT); 60 | if (error != KERN_SUCCESS) { 61 | mach_error("Couldn't set thread precedence policy", error); 62 | } 63 | 64 | /* set up the timer context */ 65 | timerContext.version = 0; 66 | timerContext.info = params; 67 | timerContext.retain = NULL; 68 | timerContext.release = NULL; 69 | timerContext.copyDescription = NULL; 70 | 71 | /* create a new timer */ 72 | timerInterval = (double)params->resolution / 1000.0; 73 | timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, 74 | 0, 0, Pt_CFTimerCallback, &timerContext); 75 | 76 | timerRunLoop = CFRunLoopGetCurrent(); 77 | CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); 78 | 79 | /* run until we're told to stop by Pt_Stop() */ 80 | CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); 81 | 82 | CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); 83 | CFRelease(timer); 84 | free(params); 85 | 86 | return NULL; 87 | } 88 | 89 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 90 | { 91 | PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); 92 | pthread_t pthread_id; 93 | 94 | printf("Pt_Start() called\n"); 95 | 96 | /* make sure we're not already playing */ 97 | if (time_started_flag) return ptAlreadyStarted; 98 | startTime = CFAbsoluteTimeGetCurrent(); 99 | 100 | if (callback) { 101 | 102 | params->resolution = resolution; 103 | params->callback = callback; 104 | params->userData = userData; 105 | 106 | pthread_create(&pthread_id, NULL, Pt_Thread, params); 107 | } 108 | 109 | time_started_flag = TRUE; 110 | return ptNoError; 111 | } 112 | 113 | 114 | PtError Pt_Stop(void) 115 | { 116 | printf("Pt_Stop called\n"); 117 | 118 | CFRunLoopStop(timerRunLoop); 119 | time_started_flag = FALSE; 120 | return ptNoError; 121 | } 122 | 123 | 124 | int Pt_Started(void) 125 | { 126 | return time_started_flag; 127 | } 128 | 129 | 130 | PtTimestamp Pt_Time(void) 131 | { 132 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); 133 | return (PtTimestamp) ((now - startTime) * 1000.0); 134 | } 135 | 136 | 137 | void Pt_Sleep(int32_t duration) 138 | { 139 | usleep(duration * 1000); 140 | } 141 | -------------------------------------------------------------------------------- /pm_mac/Makefile.osx: -------------------------------------------------------------------------------- 1 | # MAKEFILE FOR PORTMIDI 2 | 3 | # Roger B. Dannenberg 4 | # Sep 2009 5 | 6 | # NOTE: PortMidi is currently built and tested with CMake. 7 | # This makefile is probably broken, but if you want to 8 | # directly use make, start here, and please contribute any 9 | # fixes. 10 | 11 | # NOTE: you can use 12 | # make -f pm_mac/Makefile.osx configuration=Release 13 | # to override the default Debug configuration 14 | configuration=Release 15 | 16 | PF=/usr/local 17 | 18 | # For debugging, define PM_CHECK_ERRORS 19 | ifeq ($(configuration),Release) 20 | CONFIG = Release 21 | else 22 | CONFIG = Debug 23 | endif 24 | 25 | current: all 26 | 27 | all: $(CONFIG)/CMakeCache.txt 28 | cd $(CONFIG); make 29 | 30 | $(CONFIG)/CMakeCache.txt: 31 | rm -f $(CONFIG)/CMakeCache.txt 32 | mkdir -p $(CONFIG) 33 | cd $(CONFIG); cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(CONFIG) 34 | 35 | 36 | **** For instructions: make -f pm_mac/Makefile.osx help ****\n' 37 | 38 | help: 39 | echo $$'\n\n\ 40 | This is help for portmidi/pm_mac/Makefile.osx\n\n\ 41 | Installation path for dylib is $(PF)\n\ 42 | To build Release version libraries and test applications,\n \ 43 | make -f pm_mac/Makefile.osx\n\ 44 | To build Debug version libraries and test applications,\n \ 45 | make -f pm_mac/Makefile.osx configuration=Debug\n\ 46 | To install universal dynamic library,\n \ 47 | sudo make -f pm_mac/Makefile.osx install\n\ 48 | To install universal dynamic library with xcode,\n \ 49 | make -f pm_mac/Makefile.osx install-with-xcode\n\ 50 | To make PmDefaults Java application,\n \ 51 | make -f pm_mac/Makefile.osx pmdefaults\n\n \ 52 | configuration = $(configuration)\n' 53 | 54 | 55 | clean: 56 | rm -f *.o *~ core* */*.o */*/*.o */*~ */core* pm_test/*/pm_dll.dll 57 | rm -f *.opt *.ncb *.plg pm_win/Debug/pm_dll.lib pm_win/Release/pm_dll.lib 58 | rm -f pm_test/*.opt pm_test/*.ncb 59 | rm -f pm_java/pmjni/*.o pm_java/pmjni/*~ pm_java/*.h 60 | rm -rf Release/CMakeFiles Debug/CMakeFiles 61 | rm -rf pm_mac/pmdefaults/lib pm_mac/pmdefaults/src 62 | 63 | cleaner: clean 64 | rm -rf pm_mac/build 65 | rm -rf pm_mac/Debug pm_mac/Release pm_test/Debug pm_test/Release 66 | rm -f Debug/*.dylib Release/*.dylib 67 | rm -f pm_java/pmjni/Debug/*.jnilib 68 | rm -f pm_java/pmjni/Release/*.jnilib 69 | 70 | cleanest: cleaner 71 | rm -f Debug/CMakeCache.txt Release/CMakeCache.txt 72 | rm -f CMakeCache.txt 73 | rm -f Debug/libportmidi_s.a Release/libportmidi_s.a 74 | rm -f pm_test/Debug/test pm_test/Debug/sysex pm_test/Debug/midithread 75 | rm -f pm_test/Debug/latency pm_test/Debug/midithru 76 | rm -f pm_test/Debug/qtest pm_test/Debug/mm 77 | rm -f pm_test/Release/test pm_test/Release/sysex pm_test/Release/midithread 78 | rm -f pm_test/Release/latency pm_test/Release/midithru 79 | rm -f pm_test/Release/qtest pm_test/Release/mm 80 | rm -f pm_java/*/*.class 81 | rm -f pm_java/pmjni/jportmidi_JPortMidiApi_PortMidiStream.h 82 | 83 | backup: cleanest 84 | cd ..; zip -r portmidi.zip portmidi 85 | 86 | install: porttime/porttime.h pm_common/portmidi.h \ 87 | $(CONFIG)/libportmidi.dylib 88 | install porttime/porttime.h $(PF)/include/ 89 | install pm_common/portmidi.h $(PF)/include 90 | install $(CONFIG)/libportmidi.dylib $(PF)/lib/ 91 | 92 | # note - this uses xcode to build and install portmidi universal binaries 93 | install-with-xcode: 94 | sudo xcodebuild -project pm_mac/pm_mac.xcodeproj \ 95 | -configuration Release install DSTROOT=/ 96 | 97 | ##### build pmdefault ###### 98 | 99 | pm_java/pmjni/jportmidi_JPortMidiApi.h: pm_java/jportmidi/JPortMidiApi.class 100 | cd pm_java; javah jportmidi.JPortMidiApi 101 | mv pm_java/jportmidi_JportMidiApi.h pm_java/pmjni 102 | 103 | JAVASRC = pmdefaults/PmDefaultsFrame.java \ 104 | pmdefaults/PmDefaults.java \ 105 | jportmidi/JPortMidiApi.java jportmidi/JPortMidi.java \ 106 | jportmidi/JPortMidiException.java 107 | 108 | # this compiles ALL of the java code 109 | pm_java/jportmidi/JPortMidiApi.class: $(JAVASRC:%=pm_java/%) 110 | cd pm_java; javac $(JAVASRC) 111 | 112 | $(CONFIG)/libpmjni.dylib: 113 | mkdir -p $(CONFIG) 114 | cd $(CONFIG); make -f ../pm_mac/$(MAKEFILE) 115 | 116 | pmdefaults: $(CONFIG)/libpmjni.dylib pm_java/jportmidi/JPortMidiApi.class 117 | ifeq ($(CONFIG),Debug) 118 | echo "Error: you cannot build pmdefaults in a Debug configuration \n\ 119 | You should use configuration=Release in the Makefile command line. " 120 | @exit 2 121 | endif 122 | xcodebuild -project pm_mac/pm_mac.xcodeproj \ 123 | -configuration Release -target PmDefaults 124 | echo "pmdefaults java application is made" 125 | 126 | -------------------------------------------------------------------------------- /porttime/ptlinux.c: -------------------------------------------------------------------------------- 1 | /* ptlinux.c -- portable timer implementation for linux */ 2 | 3 | 4 | /* IMPLEMENTATION NOTES (by Mark Nelson): 5 | 6 | Unlike Windows, Linux has no system call to request a periodic callback, 7 | so if Pt_Start() receives a callback parameter, it must create a thread 8 | that wakes up periodically and calls the provided callback function. 9 | If running as superuser, use setpriority() to renice thread to -20. 10 | One could also set the timer thread to a real-time priority (SCHED_FIFO 11 | and SCHED_RR), but this is dangerous for This is necessary because 12 | if the callback hangs it'll never return. A more serious reason 13 | is that the current scheduler implementation busy-waits instead 14 | of sleeping when realtime threads request a sleep of <=2ms (as a way 15 | to get around the 10ms granularity), which means the thread would never 16 | let anyone else on the CPU. 17 | 18 | CHANGE LOG 19 | 20 | 18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer 21 | thread. Simplified implementation notes. 22 | 23 | */ 24 | /* stdlib, stdio, unistd, and sys/types were added because they appeared 25 | * in a Gentoo patch, but I'm not sure why they are needed. -RBD 26 | */ 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "porttime.h" 32 | #include "time.h" 33 | #include "sys/resource.h" 34 | #include "pthread.h" 35 | 36 | #define TRUE 1 37 | #define FALSE 0 38 | 39 | #ifndef CLOCK_MONOTONIC_RAW 40 | #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC 41 | #endif 42 | 43 | static int time_started_flag = FALSE; 44 | static struct timespec time_offset = {0, 0}; 45 | static pthread_t pt_thread_pid; 46 | static int pt_thread_created = FALSE; 47 | 48 | /* note that this is static data -- we only need one copy */ 49 | typedef struct { 50 | int id; 51 | int resolution; 52 | PtCallback *callback; 53 | void *userData; 54 | } pt_callback_parameters; 55 | 56 | static int pt_callback_proc_id = 0; 57 | 58 | static void *Pt_CallbackProc(void *p) 59 | { 60 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; 61 | int mytime = 1; 62 | /* to kill a process, just increment the pt_callback_proc_id */ 63 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, 64 | parameters->id); */ 65 | if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); 66 | while (pt_callback_proc_id == parameters->id) { 67 | /* wait for a multiple of resolution ms */ 68 | struct timeval timeout; 69 | int delay = mytime++ * parameters->resolution - Pt_Time(); 70 | if (delay < 0) delay = 0; 71 | timeout.tv_sec = 0; 72 | timeout.tv_usec = delay * 1000; 73 | select(0, NULL, NULL, NULL, &timeout); 74 | (*(parameters->callback))(Pt_Time(), parameters->userData); 75 | } 76 | /* printf("Pt_CallbackProc exiting\n"); */ 77 | // free(parameters); 78 | return NULL; 79 | } 80 | 81 | 82 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 83 | { 84 | if (time_started_flag) return ptNoError; 85 | /* need this set before process runs: */ 86 | clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset); 87 | if (callback) { 88 | int res; 89 | pt_callback_parameters *parms = (pt_callback_parameters *) 90 | malloc(sizeof(pt_callback_parameters)); 91 | if (!parms) return ptInsufficientMemory; 92 | parms->id = pt_callback_proc_id; 93 | parms->resolution = resolution; 94 | parms->callback = callback; 95 | parms->userData = userData; 96 | res = pthread_create(&pt_thread_pid, NULL, 97 | Pt_CallbackProc, parms); 98 | if (res != 0) return ptHostError; 99 | pt_thread_created = TRUE; 100 | } 101 | time_started_flag = TRUE; 102 | return ptNoError; 103 | } 104 | 105 | 106 | PtError Pt_Stop(void) 107 | { 108 | /* printf("Pt_Stop called\n"); */ 109 | pt_callback_proc_id++; 110 | if (pt_thread_created) { 111 | pthread_join(pt_thread_pid, NULL); 112 | pt_thread_created = FALSE; 113 | } 114 | time_started_flag = FALSE; 115 | return ptNoError; 116 | } 117 | 118 | 119 | int Pt_Started(void) 120 | { 121 | return time_started_flag; 122 | } 123 | 124 | 125 | PtTimestamp Pt_Time(void) 126 | { 127 | long seconds, ms; 128 | struct timespec now; 129 | clock_gettime(CLOCK_MONOTONIC_RAW, &now); 130 | seconds = now.tv_sec - time_offset.tv_sec; 131 | ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */ 132 | return seconds * 1000 + ms; 133 | } 134 | 135 | 136 | void Pt_Sleep(int32_t duration) 137 | { 138 | usleep(duration * 1000); 139 | } 140 | -------------------------------------------------------------------------------- /pm_test/qtest.c: -------------------------------------------------------------------------------- 1 | #include "portmidi.h" 2 | #include "pmutil.h" 3 | #include "stdlib.h" 4 | #include "stdio.h" 5 | 6 | 7 | /* make_msg -- make a psuedo-random message of length n whose content 8 | * is purely a function of i 9 | */ 10 | void make_msg(long msg[], int n, int i) 11 | { 12 | int j; 13 | for (j = 0; j < n; j++) { 14 | msg[j] = i % (j + 5); 15 | } 16 | } 17 | 18 | 19 | /* print_msg -- print the content of msg of length n to stdout */ 20 | /**/ 21 | void print_msg(long msg[], int n) 22 | { 23 | int i; 24 | for (i = 0; i < n; i++) { 25 | printf(" %li", msg[i]); 26 | } 27 | } 28 | 29 | 30 | /* cmp_msg -- compare two messages of length n */ 31 | /**/ 32 | int cmp_msg(long msg[], long msg2[], int n, int i) 33 | { 34 | int j; 35 | for (j = 0; j < n; j++) { 36 | if (msg[j] != msg2[j]) { 37 | printf("Received message %d doesn't match sent message\n", i); 38 | printf("in: "); print_msg(msg, n); printf("\n"); 39 | printf("out:"); print_msg(msg2, n); printf("\n"); 40 | return FALSE; 41 | } 42 | } 43 | return TRUE; 44 | } 45 | 46 | 47 | int main(int argc, char *argv[]) 48 | { 49 | int msg_len; 50 | for (msg_len = 4; msg_len < 100; msg_len += 5) { 51 | PmQueue *queue = Pm_QueueCreate(100, msg_len * sizeof(long)); 52 | int i; 53 | long msg[100]; 54 | long msg2[100]; 55 | 56 | printf("msg_len = %d\n", msg_len); 57 | if (!queue) { 58 | printf("Could not allocate queue\n"); 59 | return 1; 60 | } 61 | 62 | /* insert/remove 1000 messages */ 63 | printf("test 1\n"); 64 | for (i = 0; i < 1357; i++) { 65 | make_msg(msg, msg_len, i); 66 | if (Pm_Enqueue(queue, msg)) { 67 | printf("Pm_Enqueue error\n"); 68 | return 1; 69 | } 70 | if (Pm_Dequeue(queue, msg2) != 1) { 71 | printf("Pm_Dequeue error\n"); 72 | return 1; 73 | } 74 | if (!cmp_msg(msg, msg2, msg_len, i)) { 75 | return 1; 76 | } 77 | } 78 | 79 | /* make full */ 80 | printf("test 2\n"); 81 | for (i = 0; i < 100; i++) { 82 | make_msg(msg, msg_len, i); 83 | if (Pm_Enqueue(queue, msg)) { 84 | printf("Pm_Enqueue error\n"); 85 | return 1; 86 | } 87 | } 88 | 89 | /* alternately remove and insert */ 90 | for (i = 100; i < 1234; i++) { 91 | make_msg(msg, msg_len, i - 100); /* what we expect */ 92 | if (Pm_Dequeue(queue, msg2) != 1) { 93 | printf("Pm_Dequeue error\n"); 94 | return 1; 95 | } 96 | if (!cmp_msg(msg, msg2, msg_len, i)) { 97 | return 1; 98 | } 99 | make_msg(msg, msg_len, i); 100 | if (Pm_Enqueue(queue, msg)) { 101 | printf("Pm_Enqueue error\n"); 102 | return 1; 103 | } 104 | } 105 | 106 | /* remove all */ 107 | while (!Pm_QueueEmpty(queue)) { 108 | make_msg(msg, msg_len, i - 100); /* what we expect */ 109 | if (Pm_Dequeue(queue, msg2) != 1) { 110 | printf("Pm_Dequeue error\n"); 111 | return 1; 112 | } 113 | if (!cmp_msg(msg, msg2, msg_len, i)) { 114 | return 1; 115 | } 116 | i++; 117 | } 118 | if (i != 1334) { 119 | printf("Message count error\n"); 120 | return 1; 121 | } 122 | 123 | /* now test overflow */ 124 | printf("test 3\n"); 125 | for (i = 0; i < 110; i++) { 126 | make_msg(msg, msg_len, i); 127 | if (Pm_Enqueue(queue, msg) == pmBufferOverflow) { 128 | break; /* this is supposed to execute after 100 messages */ 129 | } 130 | } 131 | for (i = 0; i < 100; i++) { 132 | make_msg(msg, msg_len, i); 133 | if (Pm_Dequeue(queue, msg2) != 1) { 134 | printf("Pm_Dequeue error\n"); 135 | return 1; 136 | } 137 | if (!cmp_msg(msg, msg2, msg_len, i)) { 138 | return 1; 139 | } 140 | } 141 | /* we should detect overflow after removing 100 messages */ 142 | if (Pm_Dequeue(queue, msg2) != pmBufferOverflow) { 143 | printf("Pm_Dequeue overflow expected\n"); 144 | return 1; 145 | } 146 | 147 | /* after overflow is detected (and cleared), sender can 148 | * send again 149 | */ 150 | /* see if function is restored, also test peek */ 151 | printf("test 4\n"); 152 | for (i = 0; i < 1357; i++) { 153 | long *peek; 154 | make_msg(msg, msg_len, i); 155 | if (Pm_Enqueue(queue, msg)) { 156 | printf("Pm_Enqueue error\n"); 157 | return 1; 158 | } 159 | peek = (long *) Pm_QueuePeek(queue); 160 | if (!cmp_msg(msg, peek, msg_len, i)) { 161 | return 1; 162 | } 163 | if (Pm_Dequeue(queue, msg2) != 1) { 164 | printf("Pm_Dequeue error\n"); 165 | return 1; 166 | } 167 | if (!cmp_msg(msg, msg2, msg_len, i)) { 168 | return 1; 169 | } 170 | } 171 | Pm_QueueDestroy(queue); 172 | } 173 | return 0; 174 | } 175 | -------------------------------------------------------------------------------- /pm_java/jportmidi/JPortMidiApi.java: -------------------------------------------------------------------------------- 1 | package jportmidi; 2 | 3 | public class JPortMidiApi { 4 | public static class PortMidiStream { 5 | private long address; 6 | } 7 | public static class PmEvent { 8 | public int message; 9 | public int timestamp; 10 | } 11 | 12 | // PmError bindings 13 | public final int pmNoError = 0; 14 | public final int pmNoData = 0; 15 | public final int pmGotData = -1; 16 | public final int pmHostError = -10000; 17 | public final int pmInvalidDeviceId = -9999; 18 | public final int pmInsufficientMemory = -9998; 19 | public final int pmBufferTooSmall = -9997; 20 | public final int pmBufferOverflow = -9996; 21 | public final int pmBadPtr = -9995; 22 | public final int pmBadData = -9994; 23 | public final int pmInternalError = -9993; 24 | public final int pmBufferMaxSize = -9992; 25 | 26 | static public native int Pm_Initialize(); 27 | static public native int Pm_Terminate(); 28 | static public native int Pm_HasHostError(PortMidiStream stream); 29 | static public native String Pm_GetErrorText(int errnum); 30 | static public native String Pm_GetHostErrorText(); 31 | final int pmNoDevice = -1; 32 | static public native int Pm_CountDevices(); 33 | static public native int Pm_GetDefaultInputDeviceID(); 34 | static public native int Pm_GetDefaultOutputDeviceID(); 35 | static public native String Pm_GetDeviceInterf(int i); 36 | static public native String Pm_GetDeviceName(int i); 37 | static public native boolean Pm_GetDeviceInput(int i); 38 | static public native boolean Pm_GetDeviceOutput(int i); 39 | static public native int Pm_OpenInput(PortMidiStream stream, 40 | int inputDevice, 41 | String inputDriverInfo, 42 | int bufferSize); 43 | static public native int Pm_OpenOutput(PortMidiStream stream, 44 | int outputDevice, 45 | String outnputDriverInfo, 46 | int bufferSize, 47 | int latency); 48 | final static public int PM_FILT_ACTIVE = (1 << 0x0E); 49 | final static public int PM_FILT_SYSEX = (1 << 0x00); 50 | final static public int PM_FILT_CLOCK = (1 << 0x08); 51 | final static public int PM_FILT_PLAY = 52 | (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B); 53 | final static public int PM_FILT_TICK = (1 << 0x09); 54 | final static public int PM_FILT_FD = (1 << 0x0D); 55 | final static public int PM_FILT_UNDEFINED = PM_FILT_FD; 56 | final static public int PM_FILT_RESET = (1 << 0x0F); 57 | final static public int PM_FILT_REALTIME = 58 | PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK; 59 | final static public int PM_FILT_NOTE = (1 << 0x19) | (1 << 0x18); 60 | final static public int PM_FILT_CHANNEL_AFTERTOUCH = (1 << 0x1D); 61 | final static public int PM_FILT_POLY_AFTERTOUCH = (1 << 0x1A); 62 | final static public int PM_FILT_AFTERTOUCH = 63 | (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH); 64 | final static public int PM_FILT_PROGRAM = (1 << 0x1C); 65 | final static public int PM_FILT_CONTROL = (1 << 0x1B); 66 | final static public int PM_FILT_PITCHBEND = (1 << 0x1E); 67 | final static public int PM_FILT_MTC = (1 << 0x01); 68 | final static public int PM_FILT_SONG_POSITION = (1 << 0x02); 69 | final static public int PM_FILT_SONG_SELECT = (1 << 0x03); 70 | final static public int PM_FILT_TUNE = (1 << 0x06); 71 | final static public int PM_FILT_SYSTEMCOMMON = 72 | (PM_FILT_MTC | PM_FILT_SONG_POSITION | 73 | PM_FILT_SONG_SELECT | PM_FILT_TUNE); 74 | static public native int Pm_SetFilter(PortMidiStream stream, int filters); 75 | static public int Pm_Channel(int channel) { return 1 << channel; } 76 | final static public native int Pm_SetChannelMask(PortMidiStream stream, 77 | int mask); 78 | final static public native int Pm_Abort(PortMidiStream stream); 79 | final static public native int Pm_Close(PortMidiStream stream); 80 | static public int Pm_Message(int status, int data1, int data2) { 81 | return (((data2 << 16) & 0xFF0000) | 82 | ((data1 << 8) & 0xFF00) | 83 | (status & 0xFF)); 84 | } 85 | static public int Pm_MessageStatus(int msg) { 86 | return msg & 0xFF; 87 | } 88 | static public int Pm_MessageData1(int msg) { 89 | return (msg >> 8) & 0xFF; 90 | } 91 | static public int Pm_MessageData2(int msg) { 92 | return (msg >> 16) & 0xFF; 93 | } 94 | // only supports reading one buffer at a time 95 | static public native int Pm_Read(PortMidiStream stream, PmEvent buffer); 96 | static public native int Pm_Poll(PortMidiStream stream); 97 | // only supports writing one buffer at a time 98 | static public native int Pm_Write(PortMidiStream stream, PmEvent buffer); 99 | static public native int Pm_WriteShort(PortMidiStream stream, 100 | int when, int msg); 101 | static public native int Pm_WriteSysEx(PortMidiStream stream, 102 | int when, byte msg[]); 103 | 104 | public final int ptNoError = 0; 105 | public final int ptAlreadyStarted = -10000; 106 | public final int ptAlreadyStopped = -9999; 107 | public final int PtInsufficientMemory = -9998; 108 | static public native int Pt_TimeStart(int resolution); 109 | static public native int Pt_TimeStop(); 110 | static public native int Pt_Time(); 111 | static public native boolean Pt_TimeStarted(); 112 | static { 113 | System.out.println("Loading pmjni"); 114 | System.loadLibrary("pmjni"); 115 | System.out.println("done loading pmjni"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pm_test/recvvirtual.c: -------------------------------------------------------------------------------- 1 | #include "portmidi.h" 2 | #include "porttime.h" 3 | #include "stdlib.h" 4 | #include "stdio.h" 5 | #include "string.h" 6 | #include "assert.h" 7 | 8 | #define INPUT_BUFFER_SIZE 100 9 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 10 | #define TIME_INFO NULL 11 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 12 | 13 | #define STRING_MAX 80 /* used for console input */ 14 | 15 | char *portname = "portmidi"; 16 | 17 | PmSysDepInfo *sysdepinfo = NULL; 18 | char *port_name = "portmidi"; 19 | 20 | static void set_sysdepinfo(char m_or_p, const char *name) 21 | { 22 | if (!sysdepinfo) { 23 | // allocate some space we will alias with open-ended PmDriverInfo: 24 | // there is space for 4 parameters: 25 | static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; 26 | sysdepinfo = (PmSysDepInfo *) dimem; 27 | // build the driver info structure: 28 | sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; 29 | sysdepinfo->length = 0; 30 | } 31 | if (sysdepinfo->length > 1) { 32 | printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); 33 | exit(1); 34 | } 35 | int i = sysdepinfo->length++; 36 | enum PmSysDepPropertyKey k = pmKeyNone; 37 | if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; 38 | else if (m_or_p == 'p') k = pmKeyAlsaPortName; 39 | else if (m_or_p == 'c') k = pmKeyAlsaClientName; 40 | sysdepinfo->properties[i].key = k; 41 | sysdepinfo->properties[i].value = name; 42 | } 43 | 44 | 45 | static void prompt_and_exit(void) 46 | { 47 | printf("type ENTER..."); 48 | while (getchar() != '\n') ; 49 | /* this will clean up open ports: */ 50 | exit(-1); 51 | } 52 | 53 | 54 | static PmError checkerror(PmError err) 55 | { 56 | if (err == pmHostError) { 57 | /* it seems pointless to allocate memory and copy the string, 58 | * so I will do the work of Pm_GetHostErrorText directly 59 | */ 60 | char errmsg[80]; 61 | Pm_GetHostErrorText(errmsg, 80); 62 | printf("PortMidi found host error...\n %s\n", errmsg); 63 | prompt_and_exit(); 64 | } else if (err < 0) { 65 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 66 | prompt_and_exit(); 67 | } 68 | return err; 69 | } 70 | 71 | 72 | void main_test_input(int num) 73 | { 74 | PmStream *midi; 75 | PmError status, length; 76 | PmEvent buffer[1]; 77 | int id; 78 | int i = 0; /* count messages as they arrive */ 79 | /* It is recommended to start timer before Midi; otherwise, PortMidi may 80 | start the timer with its (default) parameters 81 | */ 82 | TIME_START; 83 | 84 | /* create a virtual input device */ 85 | id = checkerror(Pm_CreateVirtualInput(port_name, NULL, sysdepinfo)); 86 | checkerror(Pm_OpenInput(&midi, id, sysdepinfo, 0, NULL, NULL)); 87 | 88 | printf("Midi Input opened. Reading %d Midi messages...\n", num); 89 | Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); 90 | /* empty the buffer after setting filter, just in case anything 91 | got through */ 92 | while (Pm_Poll(midi)) { 93 | Pm_Read(midi, buffer, 1); 94 | } 95 | /* now start paying attention to messages */ 96 | while (i < num) { 97 | status = Pm_Poll(midi); 98 | if (status == TRUE) { 99 | length = Pm_Read(midi, buffer, 1); 100 | if (length > 0) { 101 | printf("Got message %d: time %ld, %2lx %2lx %2lx\n", 102 | i, 103 | (long) buffer[0].timestamp, 104 | (long) Pm_MessageStatus(buffer[0].message), 105 | (long) Pm_MessageData1(buffer[0].message), 106 | (long) Pm_MessageData2(buffer[0].message)); 107 | i++; 108 | } else { 109 | assert(0); 110 | } 111 | } 112 | } 113 | 114 | /* close device (this not explicitly needed in most implementations) */ 115 | printf("ready to close..."); 116 | Pm_Close(midi); 117 | printf("done closing.\nNow delete the virtual device..."); 118 | checkerror(Pm_DeleteVirtualDevice(id)); 119 | printf("done deleting.\n"); 120 | } 121 | 122 | 123 | void show_usage(void) 124 | { 125 | printf("Usage: recvvirtual [-h] [-m manufacturer] [-c clientname] " 126 | "[-p portname] [n]\n" 127 | " -h for this message,\n" 128 | " -m name designates a manufacturer name (macOS only),\n" 129 | " -c name designates a client name (linux only),\n" 130 | " -p name designates a port name (linux only),\n" 131 | " n is number of message to wait for.\n"); 132 | exit(0); 133 | } 134 | 135 | 136 | int main(int argc, char *argv[]) 137 | { 138 | char line[STRING_MAX]; 139 | int num = 10; 140 | int i; 141 | if (argc <= 1) { 142 | show_usage(); 143 | } 144 | for (i = 1; i < argc; i++) { 145 | if (strcmp(argv[i], "-h") == 0) { 146 | show_usage(); 147 | } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { 148 | i = i + 1; 149 | set_sysdepinfo('m', argv[i]); 150 | printf("Manufacturer name will be %s\n", argv[i]); 151 | } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { 152 | i = i + 1; 153 | port_name = argv[i]; 154 | set_sysdepinfo('p', port_name); 155 | printf("Port name will be %s\n", port_name); 156 | } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { 157 | i = i + 1; 158 | set_sysdepinfo('c', argv[i]); 159 | printf("Client name will be %s\n", argv[i]); 160 | } else { 161 | num = atoi(argv[i]); 162 | if (num <= 0) { 163 | printf("Zero value or non-number for n\n"); 164 | show_usage(); 165 | } 166 | printf("Waiting for %d messages.\n", num); 167 | } 168 | } 169 | 170 | main_test_input(num); 171 | 172 | printf("finished portMidi test...type ENTER to quit..."); 173 | while (getchar() != '\n') ; 174 | return 0; 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PortMidi - Cross-Platform MIDI IO 2 | 3 | This is the canonical release of PortMidi. 4 | 5 | See other repositories within [PortMidi](https://github.com/PortMidi) 6 | for related code and bindings (although currently, not much is here). 7 | 8 | ## [Full C API documentation is here.](https://portmidi.github.io/portmidi_docs/) 9 | 10 | ## Compiling and Using PortMidi 11 | 12 | 13 | Use CMake (or ccmake) to create a Makefile for Linux/BSD or a 14 | project file for Xcode or MS Visual Studio. Use make or an IDE to compile: 15 | 16 | On Linux, you need ALSA. 17 | 18 | Ubuntu / Debian: 19 | `sudo apt install libasound2-dev` 20 | 21 | Fedora / Red Hat / CentOS: 22 | `sudo dnf install alsa-lib-devel` 23 | 24 | All: 25 | ``` 26 | cd portmidi # start in the top-level portmidi directory 27 | ccmake . # set any options interactively, type c to configure 28 | # type g to generate a Makefile or IDE project 29 | # type q to quit 30 | # (alternatively, run the CMake GUI and use 31 | # Configure and Generate buttons to build IDE project) 32 | make # compile sources and build PortMidi library 33 | # (alternatively, open project file with your IDE) 34 | sudo make install # if you want to install to your system 35 | ``` 36 | 37 | ## Installation 38 | 39 | My advice is to build PortMidi and statically link it to your 40 | application. This gives you more control over versions. However, 41 | installing PortMidi to your system is preferred by some, and the 42 | following should do it: 43 | ``` 44 | cmake . 45 | make 46 | sudo make install 47 | ``` 48 | 49 | ## Language Bindings 50 | 51 | Here is a guide to some other projects using PortMidi. There is not 52 | much coordination, so let us know if there are better or alternative 53 | bindings for these and other languages: 54 | 55 | - Python: Various libraries and packages exist; search and ye shall 56 | find. If you wouldn't like to do research, check out [mido](https://mido.readthedocs.io/en/stable/) 57 | - [SML](https://github.com/jh-midi/portmidi-sml2) 58 | - [OCaml](https://ocaml.org/p/portmidi/0.1) 59 | - [Haskell](https://hackage.haskell.org/package/PortMidi) 60 | - [Erlang](https://hexdocs.pm/portmidi/PortMidi.html) 61 | - [Julia](https://github.com/SteffenPL/PortMidi.jl) 62 | - [C#](https://github.com/net-core-audio/portmidi) 63 | - [Rust](https://musitdev.github.io/portmidi-rs/) 64 | - [Go](https://github.com/rakyll/portmidi) 65 | - [Odin](https://pkg.odin-lang.org/vendor/portmidi/) 66 | - [Serpent](https://sourceforge.net/projects/serpent/) - a real-time 67 | Python-like language has PortMidi built-in, a MIDI-timestamp-aware 68 | scheduler, and GUI support for device selection. 69 | - [Pd (Pure Data)](https://puredata.info/) uses PortMidi. 70 | 71 | 72 | ## What's New? 73 | 74 | (Not so new, but significant:) Support for the **PmDefaults** program, 75 | which enabled a graphical interface to select default MIDI devices, 76 | has been removed for lack of interest. This allowed us to also remove 77 | C code to read and parse Java preference files on various systems, 78 | simplifying the library. **PmDefaults** allowed simple command-line 79 | programs to use `Pm_DefaultInputDeviceID()` and 80 | `Pm_DefaultOutputDeviceID()` rather than creating device selection 81 | interfaces. Now, you should either pass devices on the command line or 82 | create your own selection interface when building a GUI 83 | application. (See pm_tests for examples.) `Pm_DefaultInputDeviceID()` 84 | and `Pm_DefaultOutputDeviceID()` now return a valid device if 85 | possible, but they may not actually reflect any user preference. 86 | 87 | Haiku support in a minimal implementation. See TODO's in source. 88 | 89 | sndio is also minimally supported, allowing basic PortMidi functions 90 | in OpenBSD, FreeBSD, and NetBSD by setting USE_SNDIO for CMake, but 91 | not delayed/timestamped output and virtual devices. 92 | 93 | See CHANGELOG.txt for more details. 94 | 95 | # Other Repositories 96 | 97 | PortMidi used to be part of the PortMedia suite, but this repo has 98 | been reduced to mostly just C/C++ code for PortMidi. You will find 99 | some other repositories in this PortMidi project set up for language 100 | bindings (volunteers and contributors are invited!). Other code 101 | removed from previous releases of PortMedia include: 102 | 103 | ## PortSMF 104 | 105 | A Standard MIDI File (SMF) (and more) library is in the [portsmf 106 | repository](https://github.com/rbdannenberg/portsmf). 107 | 108 | PortSMF is a library for reading/writing/editing Standard MIDI 109 | Files. It is actually much more, with a general representation of 110 | events and updates with properties consisting of attributes and typed 111 | values. Familiar properties of pitch, time, duration, and channel are 112 | built into events and updates to make them faster to access and more 113 | compact. 114 | 115 | To my knowledge, PortSMF has the most complete and useful handling of 116 | MIDI tempo tracks. E.g., you can edit notes according to either beat 117 | or time, and you can edit tempo tracks, for example, flattening the 118 | tempo while preserving the beat alignment, preserving the real time 119 | while changing the tempo or stretching the tempo over some interval. 120 | 121 | In addition to Standard MIDI Files, PortSMF supports an ASCII 122 | representation called Allegro. PortSMF and Allegro are used for 123 | Audacity Note Tracks. 124 | 125 | ## scorealign 126 | 127 | Scorealign used to be part of the PortMedia suite. It is now at the 128 | [scorealign repository](https://github.com/rbdannenberg/scorealign). 129 | 130 | Scorealign aligns audio-to-audio, audio-to-MIDI or MIDI-to-MIDI using 131 | dynamic time warping (DTW) of a computed chromagram 132 | representation. There are some added smoothing tricks to improve 133 | performance. This library is written in C and runs substantially 134 | faster than most other implementations, especially those written in 135 | MATLAB, due to the core DTW algorithm. Users should be warned that 136 | while chromagrams are robust features for alignment, they achieve 137 | robustness by operating at fairly high granularity, e.g., durations of 138 | around 100ms, which limits time precision. Other more recent 139 | algorithms can doubtless do better, but be cautious of claims, since 140 | it all depends on what assumptions you can make about the music. 141 | -------------------------------------------------------------------------------- /pm_test/sendvirtual.c: -------------------------------------------------------------------------------- 1 | /* sendvirtual.c -- test for creating a virtual device and sending to it */ 2 | /* 3 | * Roger B. Dannenberg 4 | * Sep 2021 5 | */ 6 | #include "portmidi.h" 7 | #include "porttime.h" 8 | #include "stdlib.h" 9 | #include "stdio.h" 10 | #include "string.h" 11 | #include "assert.h" 12 | 13 | #define OUTPUT_BUFFER_SIZE 0 14 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 15 | #define TIME_INFO NULL 16 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 17 | 18 | int latency = 0; 19 | PmSysDepInfo *sysdepinfo = NULL; 20 | char *port_name = "portmidi"; 21 | 22 | static void set_sysdepinfo(char m_or_p, const char *name) 23 | { 24 | if (!sysdepinfo) { 25 | // allocate some space we will alias with open-ended PmDriverInfo: 26 | // there is space for 4 parameters: 27 | static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; 28 | sysdepinfo = (PmSysDepInfo *) dimem; 29 | // build the driver info structure: 30 | sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; 31 | sysdepinfo->length = 0; 32 | } 33 | if (sysdepinfo->length > 1) { 34 | printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); 35 | exit(1); 36 | } 37 | int i = sysdepinfo->length++; 38 | enum PmSysDepPropertyKey k = pmKeyNone; 39 | if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; 40 | else if (m_or_p == 'p') k = pmKeyAlsaPortName; 41 | else if (m_or_p == 'c') k = pmKeyAlsaClientName; 42 | sysdepinfo->properties[i].key = k; 43 | sysdepinfo->properties[i].value = name; 44 | } 45 | 46 | 47 | static void prompt_and_exit(void) 48 | { 49 | printf("type ENTER..."); 50 | while (getchar() != '\n') ; 51 | /* this will clean up open ports: */ 52 | exit(-1); 53 | } 54 | 55 | 56 | static PmError checkerror(PmError err) 57 | { 58 | if (err == pmHostError) { 59 | /* it seems pointless to allocate memory and copy the string, 60 | * so I will do the work of Pm_GetHostErrorText directly 61 | */ 62 | char errmsg[80]; 63 | Pm_GetHostErrorText(errmsg, 80); 64 | printf("PortMidi found host error...\n %s\n", errmsg); 65 | prompt_and_exit(); 66 | } else if (err < 0) { 67 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 68 | prompt_and_exit(); 69 | } 70 | return err; 71 | } 72 | 73 | 74 | void wait_until(PmTimestamp when) 75 | { 76 | PtTimestamp now = Pt_Time(); 77 | if (when > now) { 78 | Pt_Sleep(when - now); 79 | } 80 | } 81 | 82 | 83 | void main_test_output(int num) 84 | { 85 | PmStream *midi; 86 | int32_t next_time; 87 | PmEvent buffer[1]; 88 | PmTimestamp timestamp; 89 | int pitch = 60; 90 | int id; 91 | 92 | /* It is recommended to start timer before Midi; otherwise, PortMidi may 93 | start the timer with its (default) parameters 94 | */ 95 | TIME_START; 96 | 97 | /* create a virtual output device */ 98 | id = checkerror(Pm_CreateVirtualOutput(port_name, NULL, sysdepinfo)); 99 | checkerror(Pm_OpenOutput(&midi, id, sysdepinfo, OUTPUT_BUFFER_SIZE, 100 | TIME_PROC, TIME_INFO, latency)); 101 | 102 | printf("Midi Output Virtual Device \"%s\" created.\n", port_name); 103 | printf("Type ENTER to send messages: "); 104 | while (getchar() != '\n') ; 105 | 106 | buffer[0].timestamp = Pt_Time(); 107 | #define PROGRAM 0 108 | buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); 109 | Pm_Write(midi, buffer, 1); 110 | next_time = Pt_Time() + 1000; /* wait 1s */ 111 | while (num > 0) { 112 | wait_until(next_time); 113 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100)); 114 | printf("Note On pitch %d\n", pitch); 115 | num--; 116 | next_time += 500; 117 | 118 | wait_until(next_time); 119 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0)); 120 | printf("Note Off pitch %d\n", pitch); 121 | num--; 122 | pitch = (pitch + 1) % 12 + 60; 123 | next_time += 500; 124 | } 125 | 126 | /* close device (this not explicitly needed in most implementations) */ 127 | printf("ready to close..."); 128 | Pm_Close(midi); 129 | printf("done closing.\nNow delete the virtual device..."); 130 | checkerror(Pm_DeleteVirtualDevice(id)); 131 | printf("done deleting.\n"); 132 | } 133 | 134 | 135 | void show_usage(void) 136 | { 137 | printf("Usage: sendvirtual [-h] [-l latency-in-ms] [-m manufacturer] " 138 | "[-c clientname] [-p portname] [n]\n" 139 | " -h for this message,\n" 140 | " -l ms designates latency for precise timing (default 0),\n" 141 | " -m name designates a manufacturer name (macOS only),\n" 142 | " -c name designates a client name (linux only),\n" 143 | " -p name designates a port name (linux only),\n" 144 | " n is number of message to send.\n" 145 | "sends change program to 1, then one note per second with 0.5s on,\n" 146 | "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n" 147 | "precise timing (see PortMidi documentation).\n"); 148 | exit(0); 149 | } 150 | 151 | 152 | int main(int argc, char *argv[]) 153 | { 154 | int num = 10; 155 | int i; 156 | if (argc <= 1) { 157 | show_usage(); 158 | } 159 | for (i = 1; i < argc; i++) { 160 | if (strcmp(argv[i], "-h") == 0) { 161 | show_usage(); 162 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { 163 | i = i + 1; 164 | latency = atoi(argv[i]); 165 | printf("Latency will be %d\n", latency); 166 | } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { 167 | i = i + 1; 168 | set_sysdepinfo('m', argv[i]); 169 | printf("Manufacturer name will be %s\n", argv[i]); 170 | } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { 171 | i = i + 1; 172 | port_name = argv[i]; 173 | set_sysdepinfo('p', port_name); 174 | printf("Port name will be %s\n", port_name); 175 | } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { 176 | i = i + 1; 177 | set_sysdepinfo('c', argv[i]); 178 | printf("Client name will be %s\n", argv[i]); 179 | } else { 180 | num = atoi(argv[i]); 181 | if (num <= 0) { 182 | printf("Zero value or non-number for n\n"); 183 | show_usage(); 184 | } 185 | printf("Sending %d messages.\n", num); 186 | } 187 | } 188 | 189 | main_test_output(num); 190 | 191 | printf("finished sendvirtual test...type ENTER to quit..."); 192 | while (getchar() != '\n') ; 193 | return 0; 194 | } 195 | -------------------------------------------------------------------------------- /porttime/ptmacosx_mach.c: -------------------------------------------------------------------------------- 1 | /* ptmacosx.c -- portable timer implementation for mac os x */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #import 8 | #import 9 | #import 10 | #import 11 | #include 12 | #include 13 | 14 | #include "porttime.h" 15 | #include "sys/time.h" 16 | #include "pthread.h" 17 | 18 | #ifndef NSEC_PER_MSEC 19 | #define NSEC_PER_MSEC 1000000 20 | #endif 21 | #define THREAD_IMPORTANCE 63 22 | 23 | /* QOS headers are available as of macOS 10.10 */ 24 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 25 | #include "sys/qos.h" 26 | #define HAVE_APPLE_QOS 1 27 | #else 28 | #undef HAVE_APPLE_QOS 29 | #endif 30 | 31 | static int time_started_flag = FALSE; 32 | static UInt64 start_time; 33 | static pthread_t pt_thread_pid; 34 | 35 | /* note that this is static data -- we only need one copy */ 36 | typedef struct { 37 | int id; 38 | int resolution; 39 | PtCallback *callback; 40 | void *userData; 41 | } pt_callback_parameters; 42 | 43 | static int pt_callback_proc_id = 0; 44 | 45 | static void *Pt_CallbackProc(void *p) 46 | { 47 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; 48 | int mytime = 1; 49 | 50 | kern_return_t error; 51 | thread_extended_policy_data_t extendedPolicy; 52 | thread_precedence_policy_data_t precedencePolicy; 53 | 54 | extendedPolicy.timeshare = 0; 55 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, 56 | (thread_policy_t)&extendedPolicy, 57 | THREAD_EXTENDED_POLICY_COUNT); 58 | if (error != KERN_SUCCESS) { 59 | mach_error("Couldn't set thread timeshare policy", error); 60 | } 61 | 62 | precedencePolicy.importance = THREAD_IMPORTANCE; 63 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, 64 | (thread_policy_t)&precedencePolicy, 65 | THREAD_PRECEDENCE_POLICY_COUNT); 66 | if (error != KERN_SUCCESS) { 67 | mach_error("Couldn't set thread precedence policy", error); 68 | } 69 | 70 | /* Most important, set real-time constraints. 71 | Define the guaranteed and max fraction of time for the audio thread. 72 | These "duty cycle" values can range from 0 to 1. A value of 0.5 73 | means the scheduler would give half the time to the thread. 74 | These values have empirically been found to yield good behavior. 75 | Good means that audio performance is high and other threads won't starve. 76 | */ 77 | const double kGuaranteedAudioDutyCycle = 0.75; 78 | const double kMaxAudioDutyCycle = 0.85; 79 | 80 | /* Define constants determining how much time the audio thread can 81 | use in a given time quantum. All times are in milliseconds. 82 | */ 83 | /* About 128 frames @44.1KHz */ 84 | const double kTimeQuantum = 2.9; 85 | 86 | /* Time guaranteed each quantum. */ 87 | const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; 88 | 89 | /* Maximum time each quantum. */ 90 | const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; 91 | 92 | /* Get the conversion factor from milliseconds to absolute time 93 | which is what the time-constraints call needs. 94 | */ 95 | mach_timebase_info_data_t tb_info; 96 | mach_timebase_info(&tb_info); 97 | double ms_to_abs_time = 98 | ((double)tb_info.denom / (double)tb_info.numer) * 1000000; 99 | 100 | thread_time_constraint_policy_data_t time_constraints; 101 | time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time); 102 | time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time); 103 | time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time); 104 | time_constraints.preemptible = 0; 105 | 106 | error = thread_policy_set(mach_thread_self(), 107 | THREAD_TIME_CONSTRAINT_POLICY, 108 | (thread_policy_t)&time_constraints, 109 | THREAD_TIME_CONSTRAINT_POLICY_COUNT); 110 | if (error != KERN_SUCCESS) { 111 | mach_error("Couldn't set thread precedence policy", error); 112 | } 113 | 114 | /* to kill a process, just increment the pt_callback_proc_id */ 115 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, 116 | parameters->id); */ 117 | while (pt_callback_proc_id == parameters->id) { 118 | /* wait for a multiple of resolution ms */ 119 | UInt64 wait_time; 120 | int delay = mytime++ * parameters->resolution - Pt_Time(); 121 | PtTimestamp timestamp; 122 | if (delay < 0) delay = 0; 123 | wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); 124 | wait_time += AudioGetCurrentHostTime(); 125 | mach_wait_until(wait_time); 126 | timestamp = Pt_Time(); 127 | (*(parameters->callback))(timestamp, parameters->userData); 128 | } 129 | free(parameters); 130 | return NULL; 131 | } 132 | 133 | 134 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 135 | { 136 | if (time_started_flag) return ptAlreadyStarted; 137 | start_time = AudioGetCurrentHostTime(); 138 | 139 | if (callback) { 140 | int res; 141 | pt_callback_parameters *parms; 142 | 143 | parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); 144 | if (!parms) return ptInsufficientMemory; 145 | parms->id = pt_callback_proc_id; 146 | parms->resolution = resolution; 147 | parms->callback = callback; 148 | parms->userData = userData; 149 | 150 | #ifdef HAVE_APPLE_QOS 151 | pthread_attr_t qosAttribute; 152 | pthread_attr_init(&qosAttribute); 153 | pthread_attr_set_qos_class_np(&qosAttribute, 154 | QOS_CLASS_USER_INTERACTIVE, 0); 155 | 156 | res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc, 157 | parms); 158 | #else 159 | res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); 160 | #endif 161 | 162 | struct sched_param sp; 163 | memset(&sp, 0, sizeof(struct sched_param)); 164 | sp.sched_priority = sched_get_priority_max(SCHED_RR); 165 | if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == -1) { 166 | return ptHostError; 167 | } 168 | 169 | if (res != 0) return ptHostError; 170 | } 171 | 172 | time_started_flag = TRUE; 173 | return ptNoError; 174 | } 175 | 176 | 177 | PtError Pt_Stop(void) 178 | { 179 | /* printf("Pt_Stop called\n"); */ 180 | pt_callback_proc_id++; 181 | pthread_join(pt_thread_pid, NULL); 182 | time_started_flag = FALSE; 183 | return ptNoError; 184 | } 185 | 186 | 187 | int Pt_Started(void) 188 | { 189 | return time_started_flag; 190 | } 191 | 192 | 193 | PtTimestamp Pt_Time(void) 194 | { 195 | UInt64 clock_time, nsec_time; 196 | clock_time = AudioGetCurrentHostTime() - start_time; 197 | nsec_time = AudioConvertHostTimeToNanos(clock_time); 198 | return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); 199 | } 200 | 201 | 202 | void Pt_Sleep(int32_t duration) 203 | { 204 | usleep(duration * 1000); 205 | } 206 | -------------------------------------------------------------------------------- /pm_win/README_WIN.txt: -------------------------------------------------------------------------------- 1 | File: PortMidi Win32 Readme 2 | Author: Belinda Thom, June 16 2002 3 | Revised by: Roger Dannenberg, June 2002, May 2004, June 2007, 4 | Umpei Kurokawa, June 2007 5 | Roger Dannenberg Sep 2009, May 2022 6 | 7 | Contents: 8 | Using Portmidi 9 | To Install Portmidi 10 | To Compile Portmidi 11 | About Cmake 12 | Using other versions of Visual C++ 13 | To Create Your Own Portmidi Client Application 14 | 15 | 16 | 17 | ============================================================================= 18 | USING PORTMIDI: 19 | ============================================================================= 20 | 21 | I recommend building a static library and linking with your 22 | application. PortMidi is not large. See ../README.md for 23 | basic compiling instructions. 24 | 25 | The Windows version has a couple of extra switches: You can define 26 | DEBUG and MMDEBUG for a few extra messages (see the code). 27 | 28 | If PM_CHECK_ERRORS is defined, PortMidi reports and exits on any 29 | error. This requires terminal output to see, and aborts your 30 | application, so it's only intended for quick command line programs 31 | where you do not care to check return values and handle errors 32 | more robustly. 33 | 34 | PortMidi is designed to run without a console and should work perfectly 35 | well within a graphical user interface application. 36 | 37 | Read the portmidi.h file for PortMidi API details on using the PortMidi API. 38 | See <...>\pm_test\testio.c and other files in pm_test for usage examples. 39 | 40 | There are many other programs in pm_test, including a MIDI monitor. 41 | 42 | 43 | ============================================================================ 44 | DESIGN NOTES 45 | ============================================================================ 46 | 47 | Orderly cleanup after errors are encountered is based on a fixed order of 48 | steps and state changes to reflect each step. Here's the order: 49 | 50 | To open input: 51 | initialize return value to NULL 52 | - allocate the PmInternal strucure (representation of PortMidiStream) 53 | return value is (non-null) PmInternal structure 54 | - allocate midi buffer 55 | set buffer field of PmInternal structure 56 | - call system-dependent open code 57 | - allocate midiwinmm_type for winmm dependent data 58 | set descriptor field of PmInternal structure 59 | - open device 60 | set handle field of midiwinmm_type structure 61 | - allocate buffers 62 | - start device 63 | - return 64 | - return 65 | 66 | SYSEX HANDLING 67 | 68 | There are three cases: simple output, stream output, input 69 | Each must deal with: 70 | 1. Buffer Initialization (creating buffers) 71 | 2. Buffer Allocation (finding a free buffer) 72 | 3. Buffer Fill (putting bytes in the buffer) 73 | 4. Buffer Preparation (midiOutPrepare, etc.) 74 | 5. Buffer Send (to Midi device) 75 | 6. Buffer Receive (in callback) 76 | 7. Buffer Empty (removing bytes from buffer) 77 | 8. Buffer Free (returning to the buffer pool) 78 | 9. Buffer Finalization (returning to heap) 79 | 80 | Here's how simple output handles sysex: 81 | 1. Buffer Initialization (creating buffers) 82 | allocated when code tries to write first byte to a buffer 83 | the test is "if (!m->sysex_buffers[0]) { ... }" 84 | this field is initialized to NULL when device is opened 85 | the size is SYSEX_BYTES_PER_BUFFER 86 | allocate_sysex_buffers() does the initialization 87 | note that the actual size of the allocation includes 88 | additional space for a MIDIEVENT (3 longs) which are 89 | not used in this case 90 | 2. Buffer Allocation (finding a free buffer) 91 | see get_free_sysex_buffer() 92 | cycle through m->sysex_buffers[] using m->next_sysex_buffer 93 | to determine where to look next 94 | if nothing is found, wait by blocking on m->sysex_buffer_signal 95 | this is signaled by the callback every time a message is 96 | received 97 | 3. Buffer Fill (putting bytes in the buffer) 98 | essentially a state machine approach 99 | hdr->dwBytesRecorded is a position in message pointed to by m->hdr 100 | keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER 101 | then send the message, reseting the state to initial values 102 | 4. Buffer Preparation (midiOutPrepare, etc.) 103 | just before sending in winmm_end_sysex() 104 | 5. Buffer Send (to Midi device) 105 | message is padded with zero at end (since extra space was allocated 106 | this is ok) -- the zero works around a bug in (an old version of) 107 | MIDI YOKE drivers 108 | dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0 109 | uses midiOutLongMsg() 110 | 6. Buffer Receive (in callback) 111 | 7. Buffer Empty (removing bytes from buffer) 112 | not applicable for output 113 | 8. Buffer Free (returning to the buffer pool) 114 | unprepare message to indicate that it is free 115 | SetEvent on m->buffer_signal in case client is waiting 116 | 9. Buffer Finalization (returning to heap) 117 | when device is closed, winmm_out_delete frees all sysex buffers 118 | 119 | Here's how stream output handles sysex: 120 | 1. Buffer Initialization (creating buffers) 121 | same code as simple output (see above) 122 | 2. Buffer Allocation (finding a free buffer) 123 | same code as simple output (see above) 124 | 3. Buffer Fill (putting bytes in the buffer) 125 | essentially a state machine approach 126 | m->dwBytesRecorded is a position in message 127 | keep appending bytes until buffer is full (one byte to spare) 128 | 4. Buffer Preparation (midiOutPrepare, etc.) 129 | done before sending message 130 | dwBytesRecorded and dwBufferLength are set in winmm_end_sysex 131 | 5. Buffer Send (to Midi device) 132 | uses midiStreamOutMsg() 133 | 6. Buffer Receive (in callback) 134 | 7. Buffer Empty (removing bytes from buffer) 135 | not applicable for output 136 | 8. Buffer Free (returning to the buffer pool) 137 | unprepare message to indicate that it is free 138 | SetEvent on m->buffer_signal in case client is waiting 139 | 9. Buffer Finalization (returning to heap) 140 | when device is closed, winmm_out_delete frees all sysex buffers 141 | 142 | 143 | Here's how input handles sysex: 144 | 1. Buffer Initialization (creating buffers) 145 | two buffers are allocated in winmm_in_open 146 | 2. Buffer Allocation (finding a free buffer) 147 | same code as simple output (see above) 148 | 3. Buffer Fill (putting bytes in the buffer) 149 | not applicable for input 150 | 4. Buffer Preparation (midiOutPrepare, etc.) 151 | done before sending message -- in winmm_in_open and in callback 152 | 5. Buffer Send (to Midi device) 153 | uses midiInAddbuffer in allocate_sysex_input_buffer (called from 154 | winmm_in_open) and callback 155 | 6. Buffer Receive (in callback) 156 | 7. Buffer Empty (removing bytes from buffer) 157 | done without pause in loop in callback 158 | 8. Buffer Free (returning to the buffer pool) 159 | done by midiInAddBuffer in callback, no pointer to buffers 160 | is retained except by device 161 | 9. Buffer Finalization (returning to heap) 162 | when device is closed, empty buffers are delivered to callback, 163 | which frees them 164 | 165 | IMPORTANT: In addition to the above, PortMidi now has 166 | "shortcuts" to optimize the transfer of sysex data. To enable 167 | the optimization for sysex output, the system-dependent code 168 | sets fields in the pmInternal structure: fill_base, fill_offset_ptr, 169 | and fill_length. When fill_base is non-null, the system-independent 170 | part of PortMidi is allowed to directly copy sysex bytes to 171 | "fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches 172 | fill_length. See the code for details. 173 | 174 | 175 | -------------------------------------------------------------------------------- /pm_common/pmutil.h: -------------------------------------------------------------------------------- 1 | /** @file pmutil.h lock-free queue for building MIDI 2 | applications with PortMidi. 3 | 4 | PortMidi is not reentrant, and locks can suffer from priority 5 | inversion. To support coordination between system callbacks, a 6 | high-priority thread created with PortTime, and the main 7 | application thread, PortMidi uses a lock-free, non-blocking 8 | queue. The queue implementation is not particular to MIDI and is 9 | available for other uses. 10 | */ 11 | 12 | #ifndef PORTMIDI_PMUTIL_H 13 | #define PORTMIDI_PMUTIL_H 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif /* __cplusplus */ 18 | 19 | /** @defgroup grp_pmutil Lock-free Queue 20 | @{ 21 | */ 22 | 23 | /** The queue representation is opaque. Declare a queue as PmQueue * */ 24 | typedef void PmQueue; 25 | 26 | /** create a single-reader, single-writer queue. 27 | 28 | @param num_msgs the number of messages the queue can hold 29 | 30 | @param bytes_per_msg the fixed message size 31 | 32 | @return the allocated and initialized queue, or NULL if memory 33 | cannot be allocated. Allocation uses pm_alloc(). 34 | 35 | The queue only accepts fixed sized messages. 36 | 37 | This queue implementation uses the "light pipe" algorithm which 38 | operates correctly even with multi-processors and out-of-order 39 | memory writes. (see Alexander Dokumentov, "Lock-free Interprocess 40 | Communication," Dr. Dobbs Portal, http://www.ddj.com/, 41 | articleID=189401457, June 15, 2006. This algorithm requires that 42 | messages be translated to a form where no words contain 43 | zeros. Each word becomes its own "data valid" tag. Because of this 44 | translation, we cannot return a pointer to data still in the queue 45 | when the "peek" method is called. Instead, a buffer is 46 | preallocated so that data can be copied there. Pm_QueuePeek() 47 | dequeues a message into this buffer and returns a pointer to it. A 48 | subsequent Pm_Dequeue() will copy from this buffer. 49 | 50 | This implementation does not try to keep reader/writer data in 51 | separate cache lines or prevent thrashing on cache lines. 52 | However, this algorithm differs by doing inserts/removals in 53 | units of messages rather than units of machine words. Some 54 | performance improvement might be obtained by not clearing data 55 | immediately after a read, but instead by waiting for the end 56 | of the cache line, especially if messages are smaller than 57 | cache lines. See the Dokumentov article for explanation. 58 | 59 | The algorithm is extended to handle "overflow" reporting. To 60 | report an overflow, the sender writes the current tail position to 61 | a field. The receiver must acknowlege receipt by zeroing the 62 | field. The sender will not send more until the field is zeroed. 63 | */ 64 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); 65 | 66 | /** destroy a queue and free its storage. 67 | 68 | @param queue a queue created by #Pm_QueueCreate(). 69 | 70 | @return pmNoError or an error code. 71 | 72 | Uses pm_free(). 73 | 74 | */ 75 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); 76 | 77 | /** remove one message from the queue, copying it into \p msg. 78 | 79 | @param queue a queue created by #Pm_QueueCreate(). 80 | 81 | @param msg address to which the message, if any, is copied. 82 | 83 | @return 1 if successful, and 0 if the queue is empty. Returns 84 | #pmBufferOverflow if what would have been the next thing in the 85 | queue was dropped due to overflow. (So when overflow occurs, the 86 | receiver can receive a queue full of messages before getting the 87 | overflow report. This protocol ensures that the reader will be 88 | notified when data is lost due to overflow. 89 | */ 90 | PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); 91 | 92 | /** insert one message into the queue, copying it from \p msg. 93 | 94 | @param queue a queue created by #Pm_QueueCreate(). 95 | 96 | @param msg address of the message to be enqueued. 97 | 98 | @return #pmNoError if successful and #pmBufferOverflow if the 99 | queue was already full. If #pmBufferOverflow is returned, the 100 | overflow flag is set. 101 | */ 102 | PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); 103 | 104 | /** test if the queue is full. 105 | 106 | @param queue a queue created by #Pm_QueueCreate(). 107 | 108 | @return non-zero iff the queue is empty, and #pmBadPtr if \p queue 109 | is NULL. 110 | 111 | The full condition may change immediately because a parallel 112 | dequeue operation could be in progress. The result is 113 | pessimistic: if it returns false (zero) to the single writer, then 114 | #Pm_Enqueue() is guaranteed to succeed. 115 | */ 116 | PMEXPORT int Pm_QueueFull(PmQueue *queue); 117 | 118 | /** test if the queue is empty. 119 | 120 | @param queue a queue created by #Pm_QueueCreate(). 121 | 122 | @return zero iff the queue is either empty or NULL. 123 | 124 | The empty condition may change immediately because a parallel 125 | enqueue operation could be in progress. Furthermore, the 126 | result is optimistic: it may say false, when due to 127 | out-of-order writes, the full message has not arrived. Therefore, 128 | #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns 129 | false. 130 | */ 131 | PMEXPORT int Pm_QueueEmpty(PmQueue *queue); 132 | 133 | /** get a pointer to the item at the head of the queue. 134 | 135 | @param queue a queue created by #Pm_QueueCreate(). 136 | 137 | @result a pointer to the head message or NULL if the queue is empty. 138 | 139 | The message is not removed from the queue. #Pm_QueuePeek() will 140 | not indicate when an overflow occurs. If you want to get and check 141 | #pmBufferOverflow messages, use the return value of 142 | #Pm_QueuePeek() *only* as an indication that you should call 143 | #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue() 144 | would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL, 145 | but internally clear the #pmBufferOverflow flag, enabling 146 | #Pm_Enqueue() to resume enqueuing messages. A subsequent call to 147 | #Pm_QueuePeek() will return a pointer to the first message *after* 148 | the overflow. Using this as an indication to call #Pm_Dequeue(), 149 | the first call to #Pm_Dequeue() will return #pmBufferOverflow. The 150 | second call will return success, copying the same message pointed 151 | to by the previous #Pm_QueuePeek(). 152 | 153 | When to use #Pm_QueuePeek(): (1) when you need to look at the message 154 | data to decide who should be called to receive it. (2) when you need 155 | to know a message is ready but cannot accept the message. 156 | 157 | Note that #Pm_QueuePeek() is not a fast check, so if possible, you 158 | might as well just call #Pm_Dequeue() and accept the data if it is there. 159 | */ 160 | PMEXPORT void *Pm_QueuePeek(PmQueue *queue); 161 | 162 | /** allows the writer (enqueuer) to signal an overflow 163 | condition to the reader (dequeuer). 164 | 165 | @param queue a queue created by #Pm_QueueCreate(). 166 | 167 | @return #pmNoError if overflow is set, or #pmBadPtr if queue is 168 | NULL, or #pmBufferOverflow if buffer is already in an overflow 169 | state. 170 | 171 | E.g., when transfering data from the OS to an application, if the 172 | OS indicates a buffer overrun, #Pm_SetOverflow() can be used to 173 | insure that the reader receives a #pmBufferOverflow result from 174 | #Pm_Dequeue(). 175 | */ 176 | PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); 177 | 178 | /** @} */ 179 | 180 | #ifdef __cplusplus 181 | } 182 | #endif /* __cplusplus */ 183 | 184 | #endif /* PORTMIDI_PMUTIL_H */ 185 | -------------------------------------------------------------------------------- /pm_common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # pm_common/CMakeLists.txt -- how to build portmidi library 2 | 3 | # creates the portmidi library 4 | # exports PM_NEEDED_LIBS to parent. It seems that PM_NEEDED_LIBS for 5 | # Linux should include Thread::Thread and ALSA::ALSA, but these 6 | # are not visible in other CMake files, even though the portmidi 7 | # target is. Therefore, Thread::Thread is replaced by 8 | # CMAKE_THREAD_LIBS_INIT and ALSA::ALSA is replaced by ALSA_LIBRARIES. 9 | # Is there a better way to do this? Maybe this whole file should be 10 | # at the parent level. 11 | 12 | # Support alternative name for static libraries to avoid confusion. 13 | # (In particular, Xcode has automatically converted portmidi.a to 14 | # portmidi.dylib without warning, so using portmidi-static.a eliminates 15 | # this possibility, but default for all libs is "portmidi"). 16 | # Note about CMake scoping: We get a *copy* of parent variables 17 | # when pm_common is loaded, so set() will only set the local copy, and 18 | # set(... PARENT_SCOPE) will *only* set the parent copy. You need to 19 | # set twice to use a new value both locally and in the parent. 20 | # 21 | set(PM_STATIC_LIB_NAME "portmidi" CACHE STRING 22 | "For static builds, the PortMidi library name, e.g. portmidi-static. 23 | Default is portmidi") 24 | set(PM_ACTUAL_LIB_NAME "portmidi") 25 | if(NOT BUILD_SHARED_LIBS) 26 | set(PM_ACTUAL_LIB_NAME ${PM_STATIC_LIB_NAME}) 27 | endif() 28 | # copy local variable to parent's copy of the variable: 29 | set(PM_ACTUAL_LIB_NAME ${PM_ACTUAL_LIB_NAME} PARENT_SCOPE) 30 | 31 | # set the build directory for libportmidi.a to be in portmidi, not in 32 | # portmidi/pm_common. Must be done here BEFORE add_library below. 33 | if(APPLE OR WIN32) 34 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) 35 | # set the build directory for .dylib libraries 36 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) 37 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) 38 | endif(APPLE OR WIN32) 39 | 40 | # we need full paths to sources because they are shared with other targets 41 | # (in particular pmjni). Set PMDIR to the top-level portmidi directory: 42 | get_filename_component(PMDIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) 43 | set(PM_LIB_PUBLIC_SRC ${PMDIR}/pm_common/portmidi.c 44 | ${PMDIR}/pm_common/pmutil.c 45 | ${PMDIR}/porttime/porttime.c) 46 | add_library(portmidi ${PM_LIB_PUBLIC_SRC}) 47 | 48 | # MSVCRT_DLL is "DLL" for shared runtime library, and "" for static: 49 | set_target_properties(portmidi PROPERTIES 50 | VERSION ${LIBRARY_VERSION} 51 | SOVERSION ${LIBRARY_SOVERSION} 52 | OUTPUT_NAME "${PM_ACTUAL_LIB_NAME}" 53 | MSVC_RUNTIME_LIBRARY 54 | "MultiThreaded$<$:Debug>${MSVCRT_DLL}" 55 | WINDOWS_EXPORT_ALL_SYMBOLS TRUE) 56 | target_include_directories(portmidi PUBLIC 57 | $ 58 | $) 59 | 60 | 61 | option(PM_CHECK_ERRORS 62 | "Insert a check for error return values at the end of each PortMidi function. 63 | If an error is encountered, a text message is printed using printf(), the user 64 | is asked to type ENTER, and then exit(-1) is called to clean up and terminate 65 | the program. 66 | 67 | You should not use PM_CHECK_ERRORS if printf() does not work (e.g. this is not 68 | a console application under Windows, or there is no visible console on some 69 | other OS), and you should not use PM_CHECK_ERRORS if you intend to recover 70 | from errors rather than abruptly terminate the program." OFF) 71 | if(PM_CHECK_ERRORS) 72 | target_compile_definitions(portmidi PRIVATE PM_CHECK_ERRORS) 73 | endif(PM_CHECK_ERRORS) 74 | 75 | macro(prepend_path RESULT PATH) 76 | set(${RESULT}) 77 | foreach(FILE ${ARGN}) 78 | list(APPEND ${RESULT} "${PATH}${FILE}") 79 | endforeach(FILE) 80 | endmacro(prepend_path) 81 | 82 | # UNIX needs pthread library 83 | if(NOT WIN32) 84 | set(THREADS_PREFER_PTHREAD_FLAG ON) 85 | find_package(Threads REQUIRED) 86 | endif() 87 | 88 | # Check for sndio 89 | if(USE_SNDIO) 90 | include (FindPackageHandleStandardArgs) 91 | find_path(SNDIO_INCLUDE_DIRS NAMES sndio.h) 92 | find_library(SNDIO_LIBRARY sndio) 93 | find_package_handle_standard_args(Sndio 94 | REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIRS) 95 | endif(USE_SNDIO) 96 | 97 | # first include the appropriate system-dependent file: 98 | if(SNDIO_FOUND AND USE_SNDIO) 99 | set(PM_LIB_PRIVATE_SRC 100 | ${PMDIR}/porttime/ptlinux.c 101 | ${PMDIR}/pm_sndio/pmsndio.c) 102 | set(PM_NEEDED_LIBS Threads::Threads ${SNDIO_LIBRARY} PARENT_SCOPE) 103 | target_link_libraries(portmidi PRIVATE Threads::Threads ${SNDIO_LIBRARY}) 104 | target_include_directories(portmidi PRIVATE ${SNDIO_INCLUDE_DIRS}) 105 | elseif(UNIX AND APPLE) 106 | set(Threads::Threads "" PARENT_SCOPE) 107 | set(PM_LIB_PRIVATE_SRC 108 | ${PMDIR}/porttime/ptmacosx_mach.c 109 | ${PMDIR}/pm_mac/pmmac.c 110 | ${PMDIR}/pm_mac/pmmacosxcm.c) 111 | set(PM_NEEDED_LIBS 112 | ${CMAKE_THREAD_LIBS_INIT} 113 | -Wl,-framework,CoreAudio 114 | -Wl,-framework,CoreFoundation 115 | -Wl,-framework,CoreMIDI 116 | -Wl,-framework,CoreServices 117 | PARENT_SCOPE) 118 | target_link_libraries(portmidi PRIVATE 119 | Threads::Threads 120 | -Wl,-framework,CoreAudio 121 | -Wl,-framework,CoreFoundation 122 | -Wl,-framework,CoreMIDI 123 | -Wl,-framework,CoreServices 124 | ) 125 | # set to CMake default; is this right?: 126 | set_target_properties(portmidi PROPERTIES MACOSX_RPATH ON) 127 | elseif(HAIKU) 128 | set(PM_LIB_PRIVATE_SRC 129 | ${PMDIR}/porttime/pthaiku.cpp 130 | ${PMDIR}/pm_haiku/pmhaiku.cpp) 131 | set(PM_NEEDED_LIBS be midi midi2 PARENT_SCOPE) 132 | target_link_libraries(portmidi PRIVATE be midi midi2) 133 | elseif(UNIX) 134 | target_compile_definitions(portmidi PRIVATE ${LINUX_FLAGS}) 135 | set(PM_LIB_PRIVATE_SRC 136 | ${PMDIR}/porttime/ptlinux.c 137 | ${PMDIR}/pm_linux/pmlinux.c 138 | ${PMDIR}/pm_linux/pmlinuxnull.c) 139 | if(${LINUX_DEFINES} MATCHES ".*PMALSA.*") 140 | # Note that ALSA is not required if PMNULL is defined -- PortMidi will then 141 | # compile without ALSA and report no MIDI devices. Later, PMSNDIO or PMJACK 142 | # might be additional options. 143 | find_package(ALSA REQUIRED) 144 | list(APPEND PM_LIB_PRIVATE_SRC ${PMDIR}/pm_linux/pmlinuxalsa.c) 145 | set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} ${ALSA_LIBRARIES} PARENT_SCOPE) 146 | target_link_libraries(portmidi PRIVATE Threads::Threads ALSA::ALSA) 147 | set(PKGCONFIG_REQUIRES_PRIVATE "alsa" PARENT_SCOPE) 148 | else() 149 | message(WARNING "No PMALSA, so PortMidi will not use ALSA, " 150 | "and will not find or open MIDI devices.") 151 | set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} PARENT_SCOPE) 152 | target_link_libraries(portmidi PRIVATE Threads::Threads) 153 | endif() 154 | elseif(WIN32) 155 | set(PM_LIB_PRIVATE_SRC 156 | ${PMDIR}/porttime/ptwinmm.c 157 | ${PMDIR}/pm_win/pmwin.c 158 | ${PMDIR}/pm_win/pmwinmm.c) 159 | set(PM_NEEDED_LIBS winmm PARENT_SCOPE) 160 | target_link_libraries(portmidi PRIVATE winmm) 161 | # if(NOT BUILD_SHARED_LIBS AND PM_USE_STATIC_RUNTIME) 162 | # /MDd is multithread debug DLL, /MTd is multithread debug 163 | # /MD is multithread DLL, /MT is multithread. Change to static: 164 | # include(../pm_win/static.cmake) 165 | # endif() 166 | else() 167 | message(FATAL_ERROR "Operating system not supported.") 168 | endif() 169 | 170 | set(PM_LIB_PUBLIC_SRC ${PM_LIB_PUBLIC_SRC} PARENT_SCOPE) # export to parent 171 | set(PM_LIB_PRIVATE_SRC ${PM_LIB_PRIVATE_SRC} PARENT_SCOPE) # export to parent 172 | 173 | target_sources(portmidi PRIVATE ${PM_LIB_PRIVATE_SRC}) 174 | 175 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # portmidi 2 | # Roger B. Dannenberg (and others) 3 | # Sep 2009 - 2021 4 | 5 | cmake_minimum_required(VERSION 3.21) 6 | # (ALSA::ALSA new in 3.12 and used in pm_common/CMakeLists.txt) 7 | # Some Java stuff failed on 3.17 but works with 3.20+ 8 | 9 | cmake_policy(SET CMP0091 NEW) # enables MSVC_RUNTIME_LIBRARY target property 10 | 11 | # Previously, PortMidi versions were simply SVN commit version numbers. 12 | # Versions are now in the form x.y.z 13 | # Changed 1.0 to 2.0 because API is extended with virtual ports: 14 | set(SOVERSION "2") 15 | set(VERSION "2.0.7") 16 | 17 | project(portmidi VERSION "${VERSION}" 18 | DESCRIPTION "Cross-Platform MIDI IO") 19 | 20 | set(LIBRARY_SOVERSION "${SOVERSION}") 21 | set(LIBRARY_VERSION "${VERSION}") 22 | 23 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) 24 | 25 | option(PM_USE_STATIC_RUNTIME 26 | "Use MSVC static runtime. Only applies when BUILD_SHARED_LIBS is OFF" 27 | ON) 28 | 29 | option(USE_SNDIO "Use sndio" OFF) 30 | 31 | # MSVCRT_DLL is used to construct the MSVC_RUNTIME_LIBRARY property 32 | # (see pm_common/CMakeLists.txt and pm_test/CMakeLists.txt) 33 | if(PM_USE_STATIC_RUNTIME AND NOT BUILD_SHARED_LIBS) 34 | set(MSVCRT_DLL "") 35 | else() 36 | set(MSVCRT_DLL "DLL") 37 | endif() 38 | 39 | # Always build with position-independent code (-fPIC) 40 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 41 | 42 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING 43 | "make for this OS version or higher") 44 | 45 | # PM_ACTUAL_LIB_NAME is in this scope -- see pm_common/CMakeLists.txt 46 | # PM_NEEDED_LIBS is in this scope -- see pm_common/CMakeLists.txt 47 | 48 | include(GNUInstallDirs) 49 | 50 | # Build Types 51 | # credit: http://cliutils.gitlab.io/modern-cmake/chapters/features.html 52 | set(DEFAULT_BUILD_TYPE "Release") 53 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 54 | message(STATUS 55 | "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") 56 | set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE 57 | STRING "Choose the type of build." FORCE) 58 | # Set the possible values of build type for cmake-gui 59 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 60 | "Debug" "Release" "MinSizeRel" "RelWithDebInfo") 61 | endif() 62 | 63 | # where to put libraries? Everything goes here in this directory 64 | # (or Debug or Release, depending on the OS) 65 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 66 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 67 | 68 | option(BUILD_JAVA_NATIVE_INTERFACE 69 | "build the Java PortMidi interface library" OFF) 70 | 71 | # Defines are used in both portmidi (in pm_common/) and pmjni (in pm_java), 72 | # so define them here to be inherited by both libraries. 73 | # 74 | # PortMidi software architecture supports multiple system API's to lower- 75 | # level MIDI drivers, e.g. PMNULL (no drivers), Jack (but not supported yet), 76 | # and sndio (BSD, not supported yet). Interfaces are selected by defining, 77 | # e.g., PMALSA. (In principle, we should require PMCOREMIDI (for macOS) 78 | # and PMWINMM (for windows), but these are assumed. 79 | # 80 | if(APPLE OR WIN32) 81 | else(APPLE OR WIN32) 82 | set(LINUX_DEFINES "PMALSA" CACHE STRING "must define either PMALSA or PMNULL") 83 | add_compile_definitions(${LINUX_DEFINES}) 84 | endif(APPLE OR WIN32) 85 | 86 | if(BUILD_JAVA_NATIVE_INTERFACE) 87 | message(WARNING 88 | "Java API and PmDefaults program updated 2021, but support has " 89 | "been discontinued. If you need/use this, let developers know.") 90 | set(PMJNI_IF_EXISTS "pmjni") # used by INSTALL below 91 | else(BUILD_JAVA_NATIVE_INTERFACE) 92 | set(PMJNI_IF_EXISTS "") # used by INSTALL below 93 | endif(BUILD_JAVA_NATIVE_INTERFACE) 94 | 95 | 96 | # Something like this might help if you need to build for a specific cpu type: 97 | # set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING 98 | # "change to support other architectures" FORCE) 99 | 100 | include_directories(pm_common porttime) 101 | add_subdirectory(pm_common) 102 | 103 | option(BUILD_PORTMIDI_TESTS 104 | "Build test programs, including midi monitor (mm)" OFF) 105 | if(BUILD_PORTMIDI_TESTS) 106 | add_subdirectory(pm_test) 107 | endif(BUILD_PORTMIDI_TESTS) 108 | 109 | # See note above about Java support (probably) discontinued 110 | if(BUILD_JAVA_NATIVE_INTERFACE) 111 | add_subdirectory(pm_java) 112 | endif(BUILD_JAVA_NATIVE_INTERFACE) 113 | 114 | # Install the libraries and headers (Linux and Mac OS X command line) 115 | INSTALL(TARGETS portmidi ${PMJNI_IF_EXISTS} 116 | EXPORT PortMidiTargets 117 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 118 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 119 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 120 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 121 | 122 | INSTALL(FILES 123 | pm_common/portmidi.h 124 | pm_common/pmutil.h 125 | porttime/porttime.h 126 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 127 | 128 | # Documentation 129 | option(BUILD_DOC "Build documentation" OFF) 130 | if(BUILD_DOC) 131 | find_package(Doxygen) 132 | if (NOT DOXYGEN_FOUND) 133 | message(ERROR "Doxygen is needed to build the documentation") 134 | endif() 135 | 136 | set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 137 | set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 138 | configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) 139 | 140 | add_custom_target(doc ALL 141 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} 142 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 143 | COMMENT "Generating API documentation with Doxygen" 144 | VERBATIM ) 145 | endif (BUILD_DOC) 146 | 147 | # pkgconfig - generate pc file 148 | # See https://cmake.org/cmake/help/latest/command/configure_file.html 149 | if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") 150 | set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") 151 | else() 152 | set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") 153 | endif() 154 | if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") 155 | set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") 156 | else() 157 | set(PKGCONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") 158 | endif() 159 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/portmidi.pc.in 160 | ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc @ONLY) 161 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc 162 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 163 | 164 | # CMake config 165 | set(PORTMIDI_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/PortMidi") 166 | install( 167 | EXPORT PortMidiTargets 168 | FILE PortMidiTargets.cmake 169 | NAMESPACE PortMidi:: 170 | DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 171 | ) 172 | include(CMakePackageConfigHelpers) 173 | configure_package_config_file(packaging/PortMidiConfig.cmake.in 174 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake" 175 | INSTALL_DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 176 | ) 177 | write_basic_package_version_file( 178 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake" 179 | VERSION "${CMAKE_PROJECT_VERSION}" 180 | COMPATIBILITY SameMajorVersion 181 | ) 182 | install( 183 | FILES 184 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake" 185 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake" 186 | DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 187 | ) 188 | 189 | 190 | 191 | 192 | # Finding out what CMake is doing is really hard, e.g. COMPILE_FLAGS 193 | # does not include COMPILE_OPTIONS or COMPILE_DEFINTIONS. Thus, the 194 | # following report is probably not complete... 195 | MESSAGE(STATUS "PortMidi Library name: " ${PM_ACTUAL_LIB_NAME}) 196 | MESSAGE(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) 197 | MESSAGE(STATUS "Build shared library: " ${BUILD_SHARED_LIBS}) 198 | MESSAGE(STATUS "Compiler flags: " ${CMAKE_CXX_COMPILE_FLAGS}) 199 | get_directory_property(prop COMPILE_DEFINITIONS) 200 | MESSAGE(STATUS "Compile definitions: " ${prop}) 201 | get_directory_property(prop COMPILE_OPTIONS) 202 | MESSAGE(STATUS "Compile options: " ${prop}) 203 | MESSAGE(STATUS "Compiler cxx debug flags: " ${CMAKE_CXX_FLAGS_DEBUG}) 204 | MESSAGE(STATUS "Compiler cxx release flags: " ${CMAKE_CXX_FLAGS_RELEASE}) 205 | MESSAGE(STATUS "Compiler cxx min size flags: " ${CMAKE_CXX_FLAGS_MINSIZEREL}) 206 | MESSAGE(STATUS "Compiler cxx flags: " ${CMAKE_CXX_FLAGS}) 207 | 208 | -------------------------------------------------------------------------------- /pm_test/multivirtual.c: -------------------------------------------------------------------------------- 1 | /* multivirtual.c -- test for creating two input and two output virtual ports */ 2 | /* 3 | * Roger B. Dannenberg 4 | * Oct 2021 5 | */ 6 | #include "portmidi.h" 7 | #include "porttime.h" 8 | #include "stdlib.h" 9 | #include "stdio.h" 10 | #include "string.h" 11 | #include "assert.h" 12 | 13 | #define OUTPUT_BUFFER_SIZE 0 14 | #define DEVICE_INFO NULL 15 | #define DRIVER_INFO NULL 16 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 17 | #define TIME_INFO NULL 18 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 19 | 20 | int latency = 0; 21 | 22 | static void prompt_and_exit(void) 23 | { 24 | printf("type ENTER..."); 25 | while (getchar() != '\n') ; 26 | /* this will clean up open ports: */ 27 | exit(-1); 28 | } 29 | 30 | 31 | static PmError checkerror(PmError err) 32 | { 33 | if (err == pmHostError) { 34 | /* it seems pointless to allocate memory and copy the string, 35 | * so I will do the work of Pm_GetHostErrorText directly 36 | */ 37 | char errmsg[80]; 38 | Pm_GetHostErrorText(errmsg, 80); 39 | printf("PortMidi found host error...\n %s\n", errmsg); 40 | prompt_and_exit(); 41 | } else if (err < 0) { 42 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 43 | prompt_and_exit(); 44 | } 45 | return err; 46 | } 47 | 48 | static int msg_count[2] = {0, 0}; 49 | 50 | void poll_input(PmStream *in, int which) 51 | { 52 | PmEvent buffer[1]; 53 | int pitch, expected, length; 54 | PmError status = Pm_Poll(in); 55 | if (status == TRUE) { 56 | length = Pm_Read(in, buffer, 1); 57 | if (length > 0) { 58 | printf("Got message %d from portmidi%d: " 59 | "time %ld, %2x %2x %2x\n", 60 | msg_count[which], which + 1, (long) buffer[0].timestamp, 61 | (status = Pm_MessageStatus(buffer[0].message)), 62 | (pitch = Pm_MessageData1(buffer[0].message)), 63 | Pm_MessageData2(buffer[0].message)); 64 | if (status == 0x90) { /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */ 65 | expected = (((msg_count[which] - 1) / 2) % 12) + 60 + 66 | which * 12; 67 | if (pitch != expected) { 68 | printf("WARNING: expected pitch %d, got pitch %d\n", 69 | expected, pitch); 70 | } 71 | } 72 | msg_count[which]++; 73 | } else { 74 | assert(0); 75 | } 76 | } 77 | } 78 | 79 | 80 | void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2) 81 | { 82 | while (when > Pt_Time()) { 83 | poll_input(in1, 0); 84 | poll_input(in2, 1); 85 | Pt_Sleep(10); 86 | } 87 | } 88 | 89 | 90 | /* create one virtual output device and one input device */ 91 | void init(const char *name, PmStream **midi_out, PmStream **midi_in, 92 | int *id_out, int *id_in) 93 | { 94 | PmEvent buffer[1]; 95 | 96 | *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO)); 97 | checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE, 98 | TIME_PROC, TIME_INFO, latency)); 99 | printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out); 100 | 101 | *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO)); 102 | checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL)); 103 | printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in); 104 | Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); 105 | /* empty the buffer after setting filter, just in case anything 106 | got through */ 107 | while (Pm_Read(*midi_in, buffer, 1)) ; 108 | } 109 | 110 | 111 | void main_test(int num) 112 | { 113 | PmStream *midi1_out; 114 | PmStream *midi2_out; 115 | PmStream *midi1_in; 116 | PmStream *midi2_in; 117 | int id1_out; 118 | int id2_out; 119 | int id1_in; 120 | int id2_in; 121 | int32_t next_time; 122 | PmEvent buffer[1]; 123 | int pitch = 60; 124 | int expected_count = num + 1; /* add 1 for MIDI Program message */ 125 | 126 | /* It is recommended to start timer before Midi; otherwise, PortMidi may 127 | start the timer with its (default) parameters 128 | */ 129 | TIME_START; 130 | 131 | init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in); 132 | init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in); 133 | 134 | printf("Type ENTER to send messages: "); 135 | while (getchar() != '\n') ; 136 | 137 | buffer[0].timestamp = Pt_Time(); 138 | #define PROGRAM 0 139 | buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); 140 | Pm_Write(midi1_out, buffer, 1); 141 | Pm_Write(midi2_out, buffer, 1); 142 | next_time = Pt_Time() + 1000; /* wait 1s */ 143 | while (num > 0) { 144 | wait_until(next_time, midi1_in, midi2_in); 145 | Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100)); 146 | Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100)); 147 | printf("Note On pitch %d\n", pitch); 148 | num--; 149 | next_time += 500; 150 | 151 | wait_until(next_time, midi1_in, midi2_in); 152 | Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0)); 153 | Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0)); 154 | printf("Note Off pitch %d\n", pitch); 155 | num--; 156 | pitch = (pitch + 1) % 12 + 60; 157 | next_time += 500; 158 | } 159 | wait_until(next_time, midi1_in, midi2_in); /* get final note-offs */ 160 | 161 | printf("Got %d messages from portmidi1 and %d from portmidi2; " 162 | "expected %d.\n", msg_count[0], msg_count[1], expected_count); 163 | 164 | /* close devices (this not explicitly needed in most implementations) */ 165 | printf("ready to close..."); 166 | checkerror(Pm_Close(midi1_out)); 167 | checkerror(Pm_Close(midi2_out)); 168 | checkerror(Pm_Close(midi1_in)); 169 | checkerror(Pm_Close(midi2_in)); 170 | printf("done closing.\nNow delete the virtual devices..."); 171 | checkerror(Pm_DeleteVirtualDevice(id1_out)); 172 | checkerror(Pm_DeleteVirtualDevice(id1_in)); 173 | checkerror(Pm_DeleteVirtualDevice(id2_out)); 174 | checkerror(Pm_DeleteVirtualDevice(id2_in)); 175 | printf("done deleting.\n"); 176 | } 177 | 178 | 179 | void show_usage(void) 180 | { 181 | printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n" 182 | " -h for this message,\n" 183 | " -l ms designates latency for precise timing (default 0),\n" 184 | " n is number of message to send each output, not counting\n" 185 | " initial program change.\n" 186 | "sends change program to 1, then one note per second with 0.5s on,\n" 187 | "0.5s off, for n/2 seconds to both output ports portmidi1 and\n" 188 | "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n" 189 | "pitches an octave higher. Latency >0 uses the device driver for \n" 190 | "precise timing (see PortMidi documentation). Inputs print what\n" 191 | "they get and print WARNING if they get something unexpected.\n" 192 | "The expected test is use two instances of testio to loop\n" 193 | "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n"); 194 | exit(0); 195 | } 196 | 197 | 198 | int main(int argc, char *argv[]) 199 | { 200 | int num = 10; 201 | int i; 202 | for (i = 1; i < argc; i++) { 203 | if (strcmp(argv[i], "-h") == 0) { 204 | show_usage(); 205 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { 206 | i = i + 1; 207 | latency = atoi(argv[i]); 208 | printf("Latency will be %d\n", latency); 209 | } else { 210 | num = atoi(argv[1]); 211 | if (num <= 0) { 212 | show_usage(); 213 | } 214 | printf("Sending %d messages.\n", num); 215 | } 216 | } 217 | 218 | main_test(num); 219 | 220 | printf("finished sendvirtual test...type ENTER to quit..."); 221 | while (getchar() != '\n') ; 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /pm_test/fastrcv.c: -------------------------------------------------------------------------------- 1 | /* fastrcv.c -- send many MIDI messages very fast. 2 | * 3 | * This is a stress test created to explore reports of 4 | * pm_write() call blocking (forever) on Linux when 5 | * sending very dense MIDI sequences. 6 | * 7 | * Modified 8 Aug 2017 with -n to send expired timestamps 8 | * to test a theory about why Linux ALSA hangs in Audacity. 9 | * 10 | * Roger B. Dannenberg, Aug 2017 11 | */ 12 | 13 | #include "portmidi.h" 14 | #include "porttime.h" 15 | #include "stdlib.h" 16 | #include "stdio.h" 17 | #include "string.h" 18 | #include "assert.h" 19 | 20 | #define INPUT_BUFFER_SIZE 1000 /* big to avoid losing any input */ 21 | #define DEVICE_INFO NULL 22 | #define DRIVER_INFO NULL 23 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 24 | #define TIME_INFO NULL 25 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 26 | 27 | #define STRING_MAX 80 /* used for console input */ 28 | // need to get declaration for Sleep() 29 | #ifdef WIN32 30 | #include "windows.h" 31 | #else 32 | #include 33 | #define Sleep(n) usleep(n * 1000) 34 | #endif 35 | 36 | 37 | int deviceno = -9999; 38 | int verbose = FALSE; 39 | 40 | 41 | static void prompt_and_exit(void) 42 | { 43 | printf("type ENTER..."); 44 | while (getchar() != '\n') ; 45 | /* this will clean up open ports: */ 46 | exit(-1); 47 | } 48 | 49 | 50 | static PmError checkerror(PmError err) 51 | { 52 | if (err == pmHostError) { 53 | /* it seems pointless to allocate memory and copy the string, 54 | * so I will do the work of Pm_GetHostErrorText directly 55 | */ 56 | char errmsg[80]; 57 | Pm_GetHostErrorText(errmsg, 80); 58 | printf("PortMidi found host error...\n %s\n", errmsg); 59 | prompt_and_exit(); 60 | } else if (err < 0) { 61 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 62 | prompt_and_exit(); 63 | } 64 | return err; 65 | } 66 | 67 | 68 | /* read a number from console */ 69 | /**/ 70 | int get_number(const char *prompt) 71 | { 72 | int n = 0, i; 73 | fputs(prompt, stdout); 74 | while (n != 1) { 75 | n = scanf("%d", &i); 76 | while (getchar() != '\n') ; 77 | } 78 | return i; 79 | } 80 | 81 | 82 | void fastrcv_test(void) 83 | { 84 | PmStream * midi; 85 | PmError status, length; 86 | PmEvent buffer[1]; 87 | PmTimestamp start; 88 | /* every 10ms read all messages, keep counts */ 89 | /* every 1000ms, print report */ 90 | int msgcnt = 0; 91 | /* expect repeating sequence of 60 through 71, alternating on/off */ 92 | int expected_pitch = 60; 93 | int expected_on = TRUE; 94 | int report_time; 95 | PmTimestamp last_timestamp = -1; 96 | PmTimestamp last_delta = -1; 97 | 98 | /* It is recommended to start timer before PortMidi */ 99 | TIME_START; 100 | 101 | /* open output device */ 102 | if (deviceno == Pm_CountDevices()) { 103 | int id = Pm_CreateVirtualInput("fastrcv", NULL, DEVICE_INFO); 104 | if (id < 0) checkerror(id); /* error reporting */ 105 | checkerror(Pm_OpenInput(&midi, id, DRIVER_INFO, 106 | INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO)); 107 | } else { 108 | Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE, 109 | TIME_PROC, TIME_INFO); 110 | } 111 | printf("Midi Input opened.\n"); 112 | 113 | /* wait a sec after printing previous line */ 114 | start = Pt_Time() + 1000; 115 | while (start > Pt_Time()) { 116 | Sleep(10); 117 | } 118 | 119 | report_time = Pt_Time() + 1000; /* report every 1s */ 120 | while (TRUE) { 121 | PmTimestamp now = Pt_Time(); 122 | status = Pm_Poll(midi); 123 | if (status == TRUE) { 124 | length = Pm_Read(midi, buffer, 1); 125 | if (length > 0) { 126 | int status = Pm_MessageStatus(buffer[0].message); 127 | if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */ 128 | status = 0x90; 129 | buffer[0].message = Pm_Message(status, 130 | Pm_MessageData1(buffer[0].message), 0); 131 | } 132 | /* only listen to NOTEON messages */ 133 | if (status == 0x90) { 134 | int pitch = Pm_MessageData1(buffer[0].message); 135 | int vel = Pm_MessageData2(buffer[0].message); 136 | int is_on = (vel > 0); 137 | if (verbose) { 138 | printf("Note pitch %d vel %d\n", pitch, vel); 139 | } 140 | msgcnt++; 141 | if (pitch != expected_pitch || expected_on != is_on) { 142 | printf("Unexpected note-on: pitch %d vel %d, " 143 | "expected: pitch %d Note%s\n", pitch, vel, 144 | expected_pitch, (expected_on ? "On" : "Off")); 145 | } 146 | if (is_on) { 147 | expected_on = FALSE; 148 | expected_pitch = pitch; 149 | } else { 150 | expected_on = TRUE; 151 | expected_pitch = (pitch + 1) % 72; 152 | if (expected_pitch < 60) expected_pitch = 60; 153 | } 154 | if (last_timestamp >= 0) { 155 | last_delta = buffer[0].timestamp - last_timestamp; 156 | } 157 | last_timestamp = buffer[0].timestamp; 158 | } 159 | } 160 | } 161 | if (now >= report_time) { 162 | printf("%d msgs/sec", msgcnt); 163 | /* if available, print the last timestamp and last delta time */ 164 | if (last_timestamp >= 0) { 165 | printf(" last timestamp %d", (int) last_timestamp); 166 | last_timestamp = -1; 167 | } 168 | if (last_delta >= 0) { 169 | printf(" last delta time %d", (int) last_delta); 170 | last_delta = -1; 171 | } 172 | printf("\n"); 173 | report_time += 1000; 174 | msgcnt = 0; 175 | } 176 | } 177 | } 178 | 179 | 180 | void show_usage(void) 181 | { 182 | printf("Usage: fastrcv [-h] [-v] [-d device], where\n" 183 | "device is the PortMidi device number,\n" 184 | "-h means help,\n" 185 | "-v means verbose (print messages)\n"); 186 | } 187 | 188 | int main(int argc, char *argv[]) 189 | { 190 | int default_in; 191 | int default_out; 192 | char *deflt; 193 | 194 | int i = 0; 195 | int test_input = 0, test_output = 0, test_both = 0; 196 | int stream_test = 0; 197 | int device_valid = FALSE; 198 | 199 | if (sizeof(void *) == 8) 200 | printf("Apparently this is a 64-bit machine.\n"); 201 | else if (sizeof(void *) == 4) 202 | printf ("Apparently this is a 32-bit machine.\n"); 203 | 204 | if (argc <= 1) { 205 | show_usage(); 206 | } else { 207 | for (i = 1; i < argc; i++) { 208 | if (strcmp(argv[i], "-h") == 0) { 209 | show_usage(); 210 | } else if (strcmp(argv[i], "-v") == 0) { 211 | verbose = TRUE; 212 | } else if (strcmp(argv[i], "-d") == 0) { 213 | i = i + 1; 214 | deviceno = atoi(argv[i]); 215 | printf("Device will be %d\n", deviceno); 216 | } else { 217 | show_usage(); 218 | } 219 | } 220 | } 221 | 222 | /* list device information */ 223 | default_in = Pm_GetDefaultInputDeviceID(); 224 | default_out = Pm_GetDefaultOutputDeviceID(); 225 | for (i = 0; i < Pm_CountDevices(); i++) { 226 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 227 | if (!info->output) { 228 | printf("%d: %s, %s", i, info->interf, info->name); 229 | if (i == deviceno) { 230 | device_valid = TRUE; 231 | deflt = "selected "; 232 | } else if (i == default_out) { 233 | deflt = "default "; 234 | } else { 235 | deflt = ""; 236 | } 237 | printf(" (%sinput)\n", deflt); 238 | } 239 | } 240 | printf("%d: Create virtual port named \"fastrcv\"", i); 241 | if (i == deviceno) { 242 | device_valid = TRUE; 243 | deflt = "selected "; 244 | } else { 245 | deflt = ""; 246 | } 247 | printf(" (%sinput)\n", deflt); 248 | 249 | if (!device_valid) { 250 | deviceno = get_number("Input device number: "); 251 | } 252 | 253 | fastrcv_test(); 254 | return 0; 255 | } 256 | -------------------------------------------------------------------------------- /pm_common/pmutil.c: -------------------------------------------------------------------------------- 1 | /* pmutil.c -- some helpful utilities for building midi 2 | applications that use PortMidi 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include "portmidi.h" 8 | #include "pmutil.h" 9 | #include "pminternal.h" 10 | 11 | #if defined(WIN32) || defined(__ANDROID__) 12 | #define bzero(addr, siz) memset(addr, 0, siz) 13 | #endif 14 | 15 | /* #define QUEUE_DEBUG 1 */ 16 | #ifdef QUEUE_DEBUG 17 | #include "stdio.h" 18 | #endif 19 | 20 | typedef struct { 21 | long head; 22 | long tail; 23 | long len; 24 | long overflow; 25 | int32_t msg_size; /* number of int32_t in a message including extra word */ 26 | int32_t peek_overflow; 27 | int32_t *buffer; 28 | int32_t *peek; 29 | int32_t peek_flag; 30 | } PmQueueRep; 31 | 32 | 33 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) 34 | { 35 | int32_t int32s_per_msg = 36 | (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & 37 | ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); 38 | PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); 39 | if (!queue) /* memory allocation failed */ 40 | return NULL; 41 | 42 | /* need extra word per message for non-zero encoding */ 43 | queue->len = num_msgs * (int32s_per_msg + 1); 44 | queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); 45 | if (!queue->buffer) { 46 | pm_free(queue); 47 | return NULL; 48 | } else { /* allocate the "peek" buffer */ 49 | queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); 50 | if (!queue->peek) { 51 | /* free everything allocated so far and return */ 52 | pm_free(queue->buffer); 53 | pm_free(queue); 54 | return NULL; 55 | } 56 | } 57 | bzero(queue->buffer, queue->len * sizeof(int32_t)); 58 | queue->head = 0; 59 | queue->tail = 0; 60 | /* msg_size is in words */ 61 | queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ 62 | queue->overflow = FALSE; 63 | queue->peek_overflow = FALSE; 64 | queue->peek_flag = FALSE; 65 | return queue; 66 | } 67 | 68 | 69 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) 70 | { 71 | PmQueueRep *queue = (PmQueueRep *) q; 72 | 73 | /* arg checking */ 74 | if (!queue || !queue->buffer || !queue->peek) 75 | return pmBadPtr; 76 | 77 | pm_free(queue->peek); 78 | pm_free(queue->buffer); 79 | pm_free(queue); 80 | return pmNoError; 81 | } 82 | 83 | 84 | PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) 85 | { 86 | long head; 87 | PmQueueRep *queue = (PmQueueRep *) q; 88 | int i; 89 | int32_t *msg_as_int32 = (int32_t *) msg; 90 | 91 | /* arg checking */ 92 | if (!queue) 93 | return pmBadPtr; 94 | /* a previous peek operation encountered an overflow, but the overflow 95 | * has not yet been reported to client, so do it now. No message is 96 | * returned, but on the next call, we will return the peek buffer. 97 | */ 98 | if (queue->peek_overflow) { 99 | queue->peek_overflow = FALSE; 100 | return pmBufferOverflow; 101 | } 102 | if (queue->peek_flag) { 103 | memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); 104 | queue->peek_flag = FALSE; 105 | return pmGotData; 106 | } 107 | 108 | head = queue->head; 109 | /* if writer overflows, it writes queue->overflow = tail+1 so that 110 | * when the reader gets to that position in the buffer, it can 111 | * return the overflow condition to the reader. The problem is that 112 | * at overflow, things have wrapped around, so tail == head, and the 113 | * reader will detect overflow immediately instead of waiting until 114 | * it reads everything in the buffer, wrapping around again to the 115 | * point where tail == head. So the condition also checks that 116 | * queue->buffer[head] is zero -- if so, then the buffer is now 117 | * empty, and we're at the point in the msg stream where overflow 118 | * occurred. It's time to signal overflow to the reader. If 119 | * queue->buffer[head] is non-zero, there's a message there and we 120 | * should read all the way around the buffer before signalling overflow. 121 | * There is a write-order dependency here, but to fail, the overflow 122 | * field would have to be written while an entire buffer full of 123 | * writes are still pending. I'm assuming out-of-order writes are 124 | * possible, but not that many. 125 | */ 126 | if (queue->overflow == head + 1 && !queue->buffer[head]) { 127 | queue->overflow = 0; /* non-overflow condition */ 128 | return pmBufferOverflow; 129 | } 130 | 131 | /* test to see if there is data in the queue -- test from back 132 | * to front so if writer is simultaneously writing, we don't 133 | * waste time discovering the write is not finished 134 | */ 135 | for (i = queue->msg_size - 1; i >= 0; i--) { 136 | if (!queue->buffer[head + i]) { 137 | return pmNoData; 138 | } 139 | } 140 | memcpy(msg, (char *) &queue->buffer[head + 1], 141 | sizeof(int32_t) * (queue->msg_size - 1)); 142 | /* fix up zeros */ 143 | i = queue->buffer[head]; 144 | while (i < queue->msg_size) { 145 | int32_t j; 146 | i--; /* msg does not have extra word so shift down */ 147 | j = msg_as_int32[i]; 148 | msg_as_int32[i] = 0; 149 | i = j; 150 | } 151 | /* signal that data has been removed by zeroing: */ 152 | bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); 153 | 154 | /* update head */ 155 | head += queue->msg_size; 156 | if (head == queue->len) head = 0; 157 | queue->head = head; 158 | return pmGotData; /* success */ 159 | } 160 | 161 | 162 | 163 | PMEXPORT PmError Pm_SetOverflow(PmQueue *q) 164 | { 165 | PmQueueRep *queue = (PmQueueRep *) q; 166 | long tail; 167 | /* arg checking */ 168 | if (!queue) 169 | return pmBadPtr; 170 | /* no more enqueue until receiver acknowledges overflow */ 171 | if (queue->overflow) return pmBufferOverflow; 172 | tail = queue->tail; 173 | queue->overflow = tail + 1; 174 | return pmBufferOverflow; 175 | } 176 | 177 | 178 | PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) 179 | { 180 | PmQueueRep *queue = (PmQueueRep *) q; 181 | long tail; 182 | int i; 183 | int32_t *src = (int32_t *) msg; 184 | int32_t *ptr; 185 | int32_t *dest; 186 | int rslt; 187 | if (!queue) 188 | return pmBadPtr; 189 | /* no more enqueue until receiver acknowledges overflow */ 190 | if (queue->overflow) return pmBufferOverflow; 191 | rslt = Pm_QueueFull(q); 192 | /* already checked above: if (rslt == pmBadPtr) return rslt; */ 193 | tail = queue->tail; 194 | if (rslt) { 195 | queue->overflow = tail + 1; 196 | return pmBufferOverflow; 197 | } 198 | 199 | /* queue is has room for message, and overflow flag is cleared */ 200 | ptr = &queue->buffer[tail]; 201 | dest = ptr + 1; 202 | for (i = 1; i < queue->msg_size; i++) { 203 | int32_t j = src[i - 1]; 204 | if (!j) { 205 | *ptr = i; 206 | ptr = dest; 207 | } else { 208 | *dest = j; 209 | } 210 | dest++; 211 | } 212 | *ptr = i; 213 | tail += queue->msg_size; 214 | if (tail == queue->len) tail = 0; 215 | queue->tail = tail; 216 | return pmNoError; 217 | } 218 | 219 | 220 | PMEXPORT int Pm_QueueEmpty(PmQueue *q) 221 | { 222 | PmQueueRep *queue = (PmQueueRep *) q; 223 | return (!queue) || /* null pointer -> return "empty" */ 224 | (queue->buffer[queue->head] == 0 && !queue->peek_flag); 225 | } 226 | 227 | 228 | PMEXPORT int Pm_QueueFull(PmQueue *q) 229 | { 230 | long tail; 231 | int i; 232 | PmQueueRep *queue = (PmQueueRep *) q; 233 | /* arg checking */ 234 | if (!queue) 235 | return pmBadPtr; 236 | tail = queue->tail; 237 | /* test to see if there is space in the queue */ 238 | for (i = 0; i < queue->msg_size; i++) { 239 | if (queue->buffer[tail + i]) { 240 | return TRUE; 241 | } 242 | } 243 | return FALSE; 244 | } 245 | 246 | 247 | PMEXPORT void *Pm_QueuePeek(PmQueue *q) 248 | { 249 | PmError rslt; 250 | int32_t temp; 251 | PmQueueRep *queue = (PmQueueRep *) q; 252 | /* arg checking */ 253 | if (!queue) 254 | return NULL; 255 | 256 | if (queue->peek_flag) { 257 | return queue->peek; 258 | } 259 | /* this is ugly: if peek_overflow is set, then Pm_Dequeue() 260 | * returns immediately with pmBufferOverflow, but here, we 261 | * want Pm_Dequeue() to really check for data. If data is 262 | * there, we can return it 263 | */ 264 | temp = queue->peek_overflow; 265 | queue->peek_overflow = FALSE; 266 | rslt = Pm_Dequeue(q, queue->peek); 267 | queue->peek_overflow = temp; 268 | 269 | if (rslt == 1) { 270 | queue->peek_flag = TRUE; 271 | return queue->peek; 272 | } else if (rslt == pmBufferOverflow) { 273 | /* when overflow is indicated, the queue is empty and the 274 | * first message that was dropped by Enqueue (signalling 275 | * pmBufferOverflow to its caller) would have been the next 276 | * message in the queue. Pm_QueuePeek will return NULL, but 277 | * remember that an overflow occurred. (see Pm_Dequeue) 278 | */ 279 | queue->peek_overflow = TRUE; 280 | } 281 | return NULL; 282 | } 283 | 284 | -------------------------------------------------------------------------------- /pm_common/pminternal.h: -------------------------------------------------------------------------------- 1 | /** @file pminternal.h header for PortMidi implementations */ 2 | 3 | /* this file is included by files that implement library internals */ 4 | /* Here is a guide to implementers: 5 | provide an initialization function similar to pm_winmm_init() 6 | add your initialization function to pm_init() 7 | Note that your init function should never require not-standard 8 | libraries or fail in any way. If the interface is not available, 9 | simply do not call pm_add_device. This means that non-standard 10 | libraries should try to do dynamic linking at runtime using a DLL 11 | and return without error if the DLL cannot be found or if there 12 | is any other failure. 13 | implement functions as indicated in pm_fns_type to open, read, write, 14 | close, etc. 15 | call pm_add_device() for each input and output device, passing it a 16 | pm_fns_type structure. 17 | assumptions about pm_fns_type functions are given below. 18 | */ 19 | 20 | /* add INTERNAL to Doxygen ENABLED_SECTIONS to include this: */ 21 | /** @cond INTERNAL */ 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | extern int pm_initialized; /* see note in portmidi.c */ 28 | extern PmDeviceID pm_default_input_device_id; 29 | extern PmDeviceID pm_default_output_device_id; 30 | 31 | /* these are defined in system-specific file */ 32 | void *pm_alloc(size_t s); 33 | void pm_free(void *ptr); 34 | 35 | /* if a host error (an error reported by the host MIDI API that is not 36 | * mapped to a PortMidi error code) occurs in a synchronous operation 37 | * (i.e., not in a callback from another thread) set these: */ 38 | extern int pm_hosterror; /* boolean */ 39 | extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; 40 | 41 | struct pm_internal_struct; 42 | 43 | /* these do not use PmInternal because it is not defined yet... */ 44 | typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, 45 | PmEvent *buffer); 46 | typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, 47 | PmTimestamp timestamp); 48 | typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, 49 | PmTimestamp timestamp); 50 | typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, 51 | unsigned char byte, PmTimestamp timestamp); 52 | typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, 53 | PmEvent *buffer); 54 | typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi, 55 | PmTimestamp timestamp); 56 | typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); 57 | /* pm_open_fn should clean up all memory and close the device if any part 58 | of the open fails */ 59 | typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, 60 | void *driverInfo); 61 | typedef PmError (*pm_create_fn)(int is_input, const char *name, 62 | void *driverInfo); 63 | typedef PmError (*pm_delete_fn)(PmDeviceID id); 64 | typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); 65 | /* pm_close_fn should clean up all memory and close the device if any 66 | part of the close fails. */ 67 | typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); 68 | typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); 69 | typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi); 70 | 71 | typedef struct { 72 | pm_write_short_fn write_short; /* output short MIDI msg */ 73 | pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ 74 | pm_end_sysex_fn end_sysex; /* marks end of sysex message */ 75 | pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ 76 | pm_write_realtime_fn write_realtime; /* send real-time msg within sysex */ 77 | pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ 78 | pm_synchronize_fn synchronize; /* synchronize PM time to stream time */ 79 | pm_open_fn open; /* open MIDI device */ 80 | pm_abort_fn abort; /* abort */ 81 | pm_close_fn close; /* close device */ 82 | pm_poll_fn poll; /* read pending midi events into portmidi buffer */ 83 | pm_check_host_error_fn check_host_error; /* true when device has had host */ 84 | /* error; sets pm_hosterror and writes message to pm_hosterror_text */ 85 | } pm_fns_node, *pm_fns_type; 86 | 87 | 88 | /* when open fails, the dictionary gets this set of functions: */ 89 | extern pm_fns_node pm_none_dictionary; 90 | 91 | typedef struct { 92 | PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic 93 | device closing -- see PmDeviceInfo struct) */ 94 | int deleted; /* is this is a deleted virtual device? */ 95 | void *descriptor; /* ID number passed to win32 multimedia API open, 96 | * coreMIDI endpoint, etc., representing the device */ 97 | struct pm_internal_struct *pm_internal; /* points to PmInternal device */ 98 | /* when the device is open, allows automatic device closing */ 99 | pm_fns_type dictionary; 100 | } descriptor_node, *descriptor_type; 101 | 102 | extern int pm_descriptor_max; 103 | extern descriptor_type pm_descriptors; 104 | extern int pm_descriptor_len; 105 | 106 | typedef uint32_t (*time_get_proc_type)(void *time_info); 107 | 108 | typedef struct pm_internal_struct { 109 | int device_id; /* which device is open (index to pm_descriptors) */ 110 | short is_input; /* MIDI IN (true) or MIDI OUT (false) */ 111 | short is_removed; /* MIDI device was removed */ 112 | PmTimeProcPtr time_proc; /* where to get the time */ 113 | void *time_info; /* pass this to get_time() */ 114 | int32_t buffer_len; /* how big is the buffer or queue? */ 115 | PmQueue *queue; 116 | 117 | int32_t latency; /* time delay in ms between timestamps and actual output */ 118 | /* set to zero to get immediate, simple blocking output */ 119 | /* if latency is zero, timestamps will be ignored; */ 120 | /* if midi input device, this field ignored */ 121 | 122 | int sysex_in_progress; /* when sysex status is seen, this flag becomes 123 | * true until EOX is seen. When true, new data is appended to the 124 | * stream of outgoing bytes. When overflow occurs, sysex data is 125 | * dropped (until an EOX or non-real-timei status byte is seen) so 126 | * that, if the overflow condition is cleared, we don't start 127 | * sending data from the middle of a sysex message. If a sysex 128 | * message is filtered, sysex_in_progress is false, causing the 129 | * message to be dropped. */ 130 | PmMessage message; /* buffer for 4 bytes of sysex data */ 131 | int message_count; /* how many bytes in sysex_message so far */ 132 | int short_message_count; /* how many bytes are expected in short message */ 133 | unsigned char running_status; /* running status byte or zero if none */ 134 | int32_t filters; /* flags that filter incoming message classes */ 135 | int32_t channel_mask; /* filter incoming messages based on channel */ 136 | PmTimestamp last_msg_time; /* timestamp of last message */ 137 | PmTimestamp sync_time; /* time of last synchronization */ 138 | PmTimestamp now; /* set by PmWrite to current time */ 139 | int first_message; /* initially true, used to run first synchronization */ 140 | pm_fns_type dictionary; /* implementation functions */ 141 | void *api_info; /* system-dependent state */ 142 | /* the following are used to expedite sysex data */ 143 | /* on windows, in debug mode, based on some profiling, these optimizations 144 | * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, 145 | * but this does not count time in the driver, so I don't know if it is 146 | * important 147 | */ 148 | unsigned char *fill_base; /* addr of ptr to sysex data */ 149 | uint32_t *fill_offset_ptr; /* offset of next sysex byte */ 150 | uint32_t fill_length; /* how many sysex bytes to write */ 151 | } PmInternal; 152 | 153 | /* what is the length of this short message? */ 154 | int pm_midi_length(PmMessage msg); 155 | 156 | /* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ 157 | void pm_init(void); 158 | void pm_term(void); 159 | 160 | /* defined by portMidi, used by pmwinmm */ 161 | PmError none_write_short(PmInternal *midi, PmEvent *buffer); 162 | PmError none_write_byte(PmInternal *midi, unsigned char byte, 163 | PmTimestamp timestamp); 164 | PmTimestamp none_synchronize(PmInternal *midi); 165 | 166 | PmError pm_fail_fn(PmInternal *midi); 167 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); 168 | PmError pm_success_fn(PmInternal *midi); 169 | PmError pm_add_interf(const char *interf, pm_create_fn create_fn, 170 | pm_delete_fn delete_fn); 171 | PmError pm_add_device(const char *interf, const char *name, int is_input, 172 | int is_virtual, void *descriptor, pm_fns_type dictionary); 173 | void pm_undo_add_device(int id); 174 | uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, 175 | PmTimestamp timestamp); 176 | void pm_read_short(PmInternal *midi, PmEvent *event); 177 | 178 | #define none_write_flush pm_fail_timestamp_fn 179 | #define none_sysex pm_fail_timestamp_fn 180 | #define none_poll pm_fail_fn 181 | #define success_poll pm_success_fn 182 | 183 | #define MIDI_REALTIME_MASK 0xf8 184 | #define is_real_time(msg) \ 185 | ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) 186 | 187 | #ifdef __cplusplus 188 | } 189 | #endif 190 | 191 | /** @endcond */ 192 | -------------------------------------------------------------------------------- /pm_java/pmjni/jportmidi_JportMidiApi.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class jportmidi_JPortMidiApi */ 4 | 5 | #ifndef _Included_jportmidi_JPortMidiApi 6 | #define _Included_jportmidi_JPortMidiApi 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | #undef jportmidi_JPortMidiApi_PM_FILT_ACTIVE 11 | #define jportmidi_JPortMidiApi_PM_FILT_ACTIVE 16384L 12 | #undef jportmidi_JPortMidiApi_PM_FILT_SYSEX 13 | #define jportmidi_JPortMidiApi_PM_FILT_SYSEX 1L 14 | #undef jportmidi_JPortMidiApi_PM_FILT_CLOCK 15 | #define jportmidi_JPortMidiApi_PM_FILT_CLOCK 256L 16 | #undef jportmidi_JPortMidiApi_PM_FILT_PLAY 17 | #define jportmidi_JPortMidiApi_PM_FILT_PLAY 7168L 18 | #undef jportmidi_JPortMidiApi_PM_FILT_TICK 19 | #define jportmidi_JPortMidiApi_PM_FILT_TICK 512L 20 | #undef jportmidi_JPortMidiApi_PM_FILT_FD 21 | #define jportmidi_JPortMidiApi_PM_FILT_FD 8192L 22 | #undef jportmidi_JPortMidiApi_PM_FILT_UNDEFINED 23 | #define jportmidi_JPortMidiApi_PM_FILT_UNDEFINED 8192L 24 | #undef jportmidi_JPortMidiApi_PM_FILT_RESET 25 | #define jportmidi_JPortMidiApi_PM_FILT_RESET 32768L 26 | #undef jportmidi_JPortMidiApi_PM_FILT_REALTIME 27 | #define jportmidi_JPortMidiApi_PM_FILT_REALTIME 16641L 28 | #undef jportmidi_JPortMidiApi_PM_FILT_NOTE 29 | #define jportmidi_JPortMidiApi_PM_FILT_NOTE 50331648L 30 | #undef jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH 31 | #define jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH 536870912L 32 | #undef jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH 33 | #define jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH 67108864L 34 | #undef jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH 35 | #define jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH 603979776L 36 | #undef jportmidi_JPortMidiApi_PM_FILT_PROGRAM 37 | #define jportmidi_JPortMidiApi_PM_FILT_PROGRAM 268435456L 38 | #undef jportmidi_JPortMidiApi_PM_FILT_CONTROL 39 | #define jportmidi_JPortMidiApi_PM_FILT_CONTROL 134217728L 40 | #undef jportmidi_JPortMidiApi_PM_FILT_PITCHBEND 41 | #define jportmidi_JPortMidiApi_PM_FILT_PITCHBEND 1073741824L 42 | #undef jportmidi_JPortMidiApi_PM_FILT_MTC 43 | #define jportmidi_JPortMidiApi_PM_FILT_MTC 2L 44 | #undef jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION 45 | #define jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION 4L 46 | #undef jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT 47 | #define jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT 8L 48 | #undef jportmidi_JPortMidiApi_PM_FILT_TUNE 49 | #define jportmidi_JPortMidiApi_PM_FILT_TUNE 64L 50 | #undef jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON 51 | #define jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON 78L 52 | /* 53 | * Class: jportmidi_JPortMidiApi 54 | * Method: Pm_Initialize 55 | * Signature: ()I 56 | */ 57 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize 58 | (JNIEnv *, jclass); 59 | 60 | /* 61 | * Class: jportmidi_JPortMidiApi 62 | * Method: Pm_Terminate 63 | * Signature: ()I 64 | */ 65 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate 66 | (JNIEnv *, jclass); 67 | 68 | /* 69 | * Class: jportmidi_JPortMidiApi 70 | * Method: Pm_HasHostError 71 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I 72 | */ 73 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError 74 | (JNIEnv *, jclass, jobject); 75 | 76 | /* 77 | * Class: jportmidi_JPortMidiApi 78 | * Method: Pm_GetErrorText 79 | * Signature: (I)Ljava/lang/String; 80 | */ 81 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText 82 | (JNIEnv *, jclass, jint); 83 | 84 | /* 85 | * Class: jportmidi_JPortMidiApi 86 | * Method: Pm_GetHostErrorText 87 | * Signature: ()Ljava/lang/String; 88 | */ 89 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText 90 | (JNIEnv *, jclass); 91 | 92 | /* 93 | * Class: jportmidi_JPortMidiApi 94 | * Method: Pm_CountDevices 95 | * Signature: ()I 96 | */ 97 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices 98 | (JNIEnv *, jclass); 99 | 100 | /* 101 | * Class: jportmidi_JPortMidiApi 102 | * Method: Pm_GetDefaultInputDeviceID 103 | * Signature: ()I 104 | */ 105 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID 106 | (JNIEnv *, jclass); 107 | 108 | /* 109 | * Class: jportmidi_JPortMidiApi 110 | * Method: Pm_GetDefaultOutputDeviceID 111 | * Signature: ()I 112 | */ 113 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID 114 | (JNIEnv *, jclass); 115 | 116 | /* 117 | * Class: jportmidi_JPortMidiApi 118 | * Method: Pm_GetDeviceInterf 119 | * Signature: (I)Ljava/lang/String; 120 | */ 121 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf 122 | (JNIEnv *, jclass, jint); 123 | 124 | /* 125 | * Class: jportmidi_JPortMidiApi 126 | * Method: Pm_GetDeviceName 127 | * Signature: (I)Ljava/lang/String; 128 | */ 129 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName 130 | (JNIEnv *, jclass, jint); 131 | 132 | /* 133 | * Class: jportmidi_JPortMidiApi 134 | * Method: Pm_GetDeviceInput 135 | * Signature: (I)Z 136 | */ 137 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput 138 | (JNIEnv *, jclass, jint); 139 | 140 | /* 141 | * Class: jportmidi_JPortMidiApi 142 | * Method: Pm_GetDeviceOutput 143 | * Signature: (I)Z 144 | */ 145 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput 146 | (JNIEnv *, jclass, jint); 147 | 148 | /* 149 | * Class: jportmidi_JPortMidiApi 150 | * Method: Pm_OpenInput 151 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;I)I 152 | */ 153 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput 154 | (JNIEnv *, jclass, jobject, jint, jstring, jint); 155 | 156 | /* 157 | * Class: jportmidi_JPortMidiApi 158 | * Method: Pm_OpenOutput 159 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;II)I 160 | */ 161 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput 162 | (JNIEnv *, jclass, jobject, jint, jstring, jint, jint); 163 | 164 | /* 165 | * Class: jportmidi_JPortMidiApi 166 | * Method: Pm_SetFilter 167 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I 168 | */ 169 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter 170 | (JNIEnv *, jclass, jobject, jint); 171 | 172 | /* 173 | * Class: jportmidi_JPortMidiApi 174 | * Method: Pm_SetChannelMask 175 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I 176 | */ 177 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask 178 | (JNIEnv *, jclass, jobject, jint); 179 | 180 | /* 181 | * Class: jportmidi_JPortMidiApi 182 | * Method: Pm_Abort 183 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I 184 | */ 185 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort 186 | (JNIEnv *, jclass, jobject); 187 | 188 | /* 189 | * Class: jportmidi_JPortMidiApi 190 | * Method: Pm_Close 191 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I 192 | */ 193 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close 194 | (JNIEnv *, jclass, jobject); 195 | 196 | /* 197 | * Class: jportmidi_JPortMidiApi 198 | * Method: Pm_Read 199 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I 200 | */ 201 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read 202 | (JNIEnv *, jclass, jobject, jobject); 203 | 204 | /* 205 | * Class: jportmidi_JPortMidiApi 206 | * Method: Pm_Poll 207 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I 208 | */ 209 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll 210 | (JNIEnv *, jclass, jobject); 211 | 212 | /* 213 | * Class: jportmidi_JPortMidiApi 214 | * Method: Pm_Write 215 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I 216 | */ 217 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write 218 | (JNIEnv *, jclass, jobject, jobject); 219 | 220 | /* 221 | * Class: jportmidi_JPortMidiApi 222 | * Method: Pm_WriteShort 223 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;II)I 224 | */ 225 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort 226 | (JNIEnv *, jclass, jobject, jint, jint); 227 | 228 | /* 229 | * Class: jportmidi_JPortMidiApi 230 | * Method: Pm_WriteSysEx 231 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I[B)I 232 | */ 233 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx 234 | (JNIEnv *, jclass, jobject, jint, jbyteArray); 235 | 236 | /* 237 | * Class: jportmidi_JPortMidiApi 238 | * Method: Pt_TimeStart 239 | * Signature: (I)I 240 | */ 241 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart 242 | (JNIEnv *, jclass, jint); 243 | 244 | /* 245 | * Class: jportmidi_JPortMidiApi 246 | * Method: Pt_TimeStop 247 | * Signature: ()I 248 | */ 249 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop 250 | (JNIEnv *, jclass); 251 | 252 | /* 253 | * Class: jportmidi_JPortMidiApi 254 | * Method: Pt_Time 255 | * Signature: ()I 256 | */ 257 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time 258 | (JNIEnv *, jclass); 259 | 260 | /* 261 | * Class: jportmidi_JPortMidiApi 262 | * Method: Pt_TimeStarted 263 | * Signature: ()Z 264 | */ 265 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted 266 | (JNIEnv *, jclass); 267 | 268 | #ifdef __cplusplus 269 | } 270 | #endif 271 | #endif 272 | /* Header for class jportmidi_JPortMidiApi_PmEvent */ 273 | 274 | #ifndef _Included_jportmidi_JPortMidiApi_PmEvent 275 | #define _Included_jportmidi_JPortMidiApi_PmEvent 276 | #ifdef __cplusplus 277 | extern "C" { 278 | #endif 279 | #ifdef __cplusplus 280 | } 281 | #endif 282 | #endif 283 | /* Header for class jportmidi_JPortMidiApi_PortMidiStream */ 284 | 285 | #ifndef _Included_jportmidi_JPortMidiApi_PortMidiStream 286 | #define _Included_jportmidi_JPortMidiApi_PortMidiStream 287 | #ifdef __cplusplus 288 | extern "C" { 289 | #endif 290 | #ifdef __cplusplus 291 | } 292 | #endif 293 | #endif 294 | -------------------------------------------------------------------------------- /pm_test/midiclock.c: -------------------------------------------------------------------------------- 1 | /* miditime.c -- a test program that sends midi clock and MTC */ 2 | 3 | #include "portmidi.h" 4 | #include "porttime.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef false 12 | #define false 0 13 | #define true 1 14 | #endif 15 | 16 | #define private static 17 | typedef int boolean; 18 | 19 | #define MIDI_TIME_CLOCK 0xf8 20 | #define MIDI_START 0xfa 21 | #define MIDI_CONTINUE 0xfb 22 | #define MIDI_STOP 0xfc 23 | #define MIDI_Q_FRAME 0xf1 24 | 25 | #define OUTPUT_BUFFER_SIZE 0 26 | #define DRIVER_INFO NULL 27 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 28 | #define TIME_INFO NULL 29 | #define LATENCY 0 30 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 31 | 32 | #define STRING_MAX 80 /* used for console input */ 33 | 34 | /* to determine ms per clock: 35 | * time per beat in seconds = 60 / tempo 36 | * multiply by 1000 to get time per beat in ms: 60000 / tempo 37 | * divide by 24 CLOCKs per beat: (60000/24) / tempo 38 | * simplify: 2500 / tempo 39 | */ 40 | #define TEMPO_TO_CLOCK 2500.0 41 | 42 | boolean done = false; 43 | PmStream *midi; 44 | /* shared flags to control callback output generation: */ 45 | boolean clock_running = false; 46 | boolean send_start_stop = false; 47 | boolean time_code_running = false; 48 | boolean active = false; /* tells callback to do its thing */ 49 | float tempo = 60.0F; 50 | /* protocol for handing off portmidi to callback thread: 51 | main owns portmidi 52 | main sets active = true: ownership transfers to callback 53 | main sets active = false: main requests ownership 54 | callback sees active == false, yields ownership back to main 55 | main waits 2ms to make sure callback has a chance to yield 56 | (stop making PortMidi calls), then assumes it can close 57 | PortMidi 58 | */ 59 | 60 | /* timer_poll -- the timer callback function */ 61 | /* 62 | * All MIDI sends take place here 63 | */ 64 | void timer_poll(PtTimestamp timestamp, void *userData) 65 | { 66 | static int callback_owns_portmidi = false; 67 | static PmTimestamp clock_start_time = 0; 68 | static double next_clock_time = 0; 69 | /* SMPTE time */ 70 | static int frames = 0; 71 | static int seconds = 0; 72 | static int minutes = 0; 73 | static int hours = 0; 74 | static int mtc_count = 0; /* where are we in quarter frame sequence? */ 75 | static int smpte_start_time = 0; 76 | static double next_smpte_time = 0; 77 | #define QUARTER_FRAME_PERIOD (1.0 / 120.0) /* 30fps, 1/4 frame */ 78 | 79 | if (callback_owns_portmidi && !active) { 80 | /* main is requesting (by setting active to false) that we shut down */ 81 | callback_owns_portmidi = false; 82 | return; 83 | } 84 | if (!active) return; /* main still getting ready or it's closing down */ 85 | callback_owns_portmidi = true; /* main is ready, we have portmidi */ 86 | if (send_start_stop) { 87 | if (clock_running) { 88 | Pm_WriteShort(midi, 0, MIDI_STOP); 89 | } else { 90 | Pm_WriteShort(midi, 0, MIDI_START); 91 | clock_start_time = timestamp; 92 | next_clock_time = TEMPO_TO_CLOCK / tempo; 93 | } 94 | clock_running = !clock_running; 95 | send_start_stop = false; /* until main sets it again */ 96 | /* note that there's a slight race condition here: main could 97 | set send_start_stop asynchronously, but we assume user is 98 | typing slower than the clock rate */ 99 | } 100 | if (clock_running) { 101 | if ((timestamp - clock_start_time) > next_clock_time) { 102 | Pm_WriteShort(midi, 0, MIDI_TIME_CLOCK); 103 | next_clock_time += TEMPO_TO_CLOCK / tempo; 104 | } 105 | } 106 | if (time_code_running) { 107 | int data = 0; // initialization avoids compiler warning 108 | if ((timestamp - smpte_start_time) < next_smpte_time) 109 | return; 110 | switch (mtc_count) { 111 | case 0: /* frames low nibble */ 112 | data = frames; 113 | break; 114 | case 1: /* frames high nibble */ 115 | data = frames >> 4; 116 | break; 117 | case 2: /* frames seconds low nibble */ 118 | data = seconds; 119 | break; 120 | case 3: /* frames seconds high nibble */ 121 | data = seconds >> 4; 122 | break; 123 | case 4: /* frames minutes low nibble */ 124 | data = minutes; 125 | break; 126 | case 5: /* frames minutes high nibble */ 127 | data = minutes >> 4; 128 | break; 129 | case 6: /* hours low nibble */ 130 | data = hours; 131 | break; 132 | case 7: /* hours high nibble */ 133 | data = hours >> 4; 134 | break; 135 | } 136 | data &= 0xF; /* take only 4 bits */ 137 | Pm_WriteShort(midi, 0, 138 | Pm_Message(MIDI_Q_FRAME, (mtc_count << 4) + data, 0)); 139 | mtc_count = (mtc_count + 1) & 7; /* wrap around */ 140 | if (mtc_count == 0) { /* update time by two frames */ 141 | frames += 2; 142 | if (frames >= 30) { 143 | frames = 0; 144 | seconds++; 145 | if (seconds >= 60) { 146 | seconds = 0; 147 | minutes++; 148 | if (minutes >= 60) { 149 | minutes = 0; 150 | hours++; 151 | /* just let hours wrap if it gets that far */ 152 | } 153 | } 154 | } 155 | } 156 | next_smpte_time += QUARTER_FRAME_PERIOD; 157 | } else { /* time_code_running is false */ 158 | smpte_start_time = timestamp; 159 | /* so that when it finally starts, we'll be in sync */ 160 | } 161 | } 162 | 163 | 164 | /* read a number from console */ 165 | /**/ 166 | int get_number(const char *prompt) 167 | { 168 | int n = 0, i; 169 | fputs(prompt, stdout); 170 | while (n != 1) { 171 | n = scanf("%d", &i); 172 | while (getchar() != '\n') ; 173 | } 174 | return i; 175 | } 176 | 177 | /**************************************************************************** 178 | * showhelp 179 | * Effect: print help text 180 | ****************************************************************************/ 181 | 182 | private void showhelp(void) 183 | { 184 | printf("\n"); 185 | printf("t toggles sending MIDI Time Code (MTC)\n"); 186 | printf("c toggles sending MIDI CLOCK (initially on)\n"); 187 | printf("m to set tempo (from 1bpm to 300bpm)\n"); 188 | printf("q quits\n"); 189 | printf("\n"); 190 | } 191 | 192 | /**************************************************************************** 193 | * doascii 194 | * Inputs: 195 | * char c: input character 196 | * Effect: interpret to control output 197 | ****************************************************************************/ 198 | 199 | private void doascii(char c) 200 | { 201 | if (isupper(c)) c = tolower(c); 202 | if (c == 'q') done = true; 203 | else if (c == 'c') { 204 | printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting")); 205 | send_start_stop = true; 206 | } else if (c == 't') { 207 | printf("%s MIDI Time Code\n", 208 | (time_code_running ? "Stopping" : "Starting")); 209 | time_code_running = !time_code_running; 210 | } else if (c == 'm') { 211 | int input_tempo = get_number("Enter new tempo (bpm): "); 212 | if (input_tempo >= 1 && input_tempo <= 300) { 213 | printf("Changing tempo to %d\n", input_tempo); 214 | tempo = (float) input_tempo; 215 | } else { 216 | printf("Tempo range is 1 to 300, current tempo is %g bpm\n", 217 | tempo); 218 | } 219 | } else { 220 | showhelp(); 221 | } 222 | } 223 | 224 | 225 | /* main - prompt for parameters, start processing */ 226 | /* 227 | * Prompt user to type return. 228 | * Then send START and MIDI CLOCK for 60 beats/min. 229 | * Commands: 230 | * t - toggle sending MIDI Time Code (MTC) 231 | * c - toggle sending MIDI CLOCK 232 | * m - set tempo 233 | * q - quit 234 | */ 235 | int main(int argc, char **argv) 236 | { 237 | int outp; 238 | PmError err; 239 | int i; 240 | if (argc > 1) { 241 | printf("Warning: command line arguments ignored\n"); 242 | } 243 | showhelp(); 244 | /* use porttime callback to send midi */ 245 | Pt_Start(1, timer_poll, 0); 246 | /* list device information */ 247 | printf("MIDI output devices:\n"); 248 | for (i = 0; i < Pm_CountDevices(); i++) { 249 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 250 | if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name); 251 | } 252 | outp = get_number("Type output device number: "); 253 | err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE, 254 | TIME_PROC, TIME_INFO, LATENCY); 255 | if (err) { 256 | puts(Pm_GetErrorText(err)); 257 | goto error_exit_no_device; 258 | } 259 | active = true; 260 | 261 | printf("Type ENTER to start MIDI CLOCK:\n"); 262 | while (getchar() != '\n') ; 263 | send_start_stop = true; /* send START and then CLOCKs */ 264 | 265 | while (!done) { 266 | doascii(getchar()); 267 | while (getchar() != '\n') ; 268 | } 269 | 270 | active = false; 271 | Pt_Sleep(2); /* this is to allow callback to complete -- it's 272 | real time, so it's either ok and it runs on 273 | time, or there's no point to synchronizing 274 | with it */ 275 | /* now we "own" portmidi again */ 276 | Pm_Close(midi); 277 | error_exit_no_device: 278 | Pt_Stop(); 279 | Pm_Terminate(); 280 | exit(0); 281 | } 282 | 283 | -------------------------------------------------------------------------------- /pm_java/pmjni/pmjni.c: -------------------------------------------------------------------------------- 1 | #include "portmidi.h" 2 | #include "porttime.h" 3 | #include "jportmidi_JportMidiApi.h" 4 | #include 5 | 6 | // these macros assume JNIEnv *env is declared and valid: 7 | // 8 | #define CLASS(c, obj) jclass c = (*env)->GetObjectClass(env, obj) 9 | #define ADDRESS_FID(fid, c) \ 10 | jfieldID fid = (*env)->GetFieldID(env, c, "address", "J") 11 | // Uses Java Long (64-bit) to make sure there is room to store a 12 | // pointer. Cast this to a C long (either 32 or 64 bit) to match 13 | // the size of a pointer. Finally cast int to pointer. All this 14 | // is supposed to avoid C compiler warnings and (worse) losing 15 | // address bits. 16 | #define PMSTREAM(obj, fid) ((PmStream *) (intptr_t) (*env)->GetLongField(env, obj, fid)) 17 | // Cast stream to long to convert integer to pointer, then expand 18 | // integer to 64-bit jlong. This avoids compiler warnings. 19 | #define SET_PMSTREAM(obj, fid, stream) \ 20 | (*env)->SetLongField(env, obj, fid, (jlong) (intptr_t) stream) 21 | 22 | 23 | /* 24 | * Method: Pm_Initialize 25 | */ 26 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize 27 | (JNIEnv *env, jclass cl) 28 | { 29 | return Pm_Initialize(); 30 | } 31 | 32 | 33 | /* 34 | * Method: Pm_Terminate 35 | */ 36 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate 37 | (JNIEnv *env, jclass cl) 38 | { 39 | return Pm_Terminate(); 40 | } 41 | 42 | 43 | /* 44 | * Method: Pm_HasHostError 45 | */ 46 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError 47 | (JNIEnv *env, jclass cl, jobject jstream) 48 | { 49 | CLASS(c, jstream); 50 | ADDRESS_FID(fid, c); 51 | return Pm_HasHostError(PMSTREAM(jstream, fid)); 52 | } 53 | 54 | 55 | /* 56 | * Method: Pm_GetErrorText 57 | */ 58 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText 59 | (JNIEnv *env, jclass cl, jint i) 60 | { 61 | return (*env)->NewStringUTF(env, Pm_GetErrorText(i)); 62 | } 63 | 64 | 65 | /* 66 | * Method: Pm_GetHostErrorText 67 | */ 68 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText 69 | (JNIEnv *env, jclass cl) 70 | { 71 | char msg[PM_HOST_ERROR_MSG_LEN]; 72 | Pm_GetHostErrorText(msg, PM_HOST_ERROR_MSG_LEN); 73 | return (*env)->NewStringUTF(env, msg); 74 | } 75 | 76 | 77 | /* 78 | * Method: Pm_CountDevices 79 | */ 80 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices 81 | (JNIEnv *env, jclass cl) 82 | { 83 | return Pm_CountDevices(); 84 | } 85 | 86 | 87 | /* 88 | * Method: Pm_GetDefaultInputDeviceID 89 | */ 90 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID 91 | (JNIEnv *env, jclass cl) 92 | { 93 | return Pm_GetDefaultInputDeviceID(); 94 | } 95 | 96 | 97 | /* 98 | * Method: Pm_GetDefaultOutputDeviceID 99 | */ 100 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID 101 | (JNIEnv *env, jclass cl) 102 | { 103 | return Pm_GetDefaultOutputDeviceID(); 104 | } 105 | 106 | 107 | /* 108 | * Method: Pm_GetDeviceInterf 109 | */ 110 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf 111 | (JNIEnv *env, jclass cl, jint i) 112 | { 113 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 114 | if (!info) return NULL; 115 | return (*env)->NewStringUTF(env, info->interf); 116 | } 117 | 118 | 119 | /* 120 | * Method: Pm_GetDeviceName 121 | */ 122 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName 123 | (JNIEnv *env, jclass cl, jint i) 124 | { 125 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 126 | if (!info) return NULL; 127 | return (*env)->NewStringUTF(env, info->name); 128 | } 129 | 130 | 131 | /* 132 | * Method: Pm_GetDeviceInput 133 | */ 134 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput 135 | (JNIEnv *env, jclass cl, jint i) 136 | { 137 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 138 | if (!info) return (jboolean) 0; 139 | return (jboolean) info->input; 140 | } 141 | 142 | 143 | /* 144 | * Method: Pm_GetDeviceOutput 145 | */ 146 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput 147 | (JNIEnv *env, jclass cl, jint i) 148 | { 149 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 150 | if (!info) return (jboolean) 0; 151 | return (jboolean) info->output; 152 | } 153 | 154 | 155 | /* 156 | * Method: Pm_OpenInput 157 | */ 158 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput 159 | (JNIEnv *env, jclass cl, 160 | jobject jstream, jint index, jstring extras, jint bufsiz) 161 | { 162 | PmError rslt; 163 | PortMidiStream *stream; 164 | CLASS(c, jstream); 165 | ADDRESS_FID(fid, c); 166 | rslt = Pm_OpenInput(&stream, index, NULL, bufsiz, NULL, NULL); 167 | SET_PMSTREAM(jstream, fid, stream); 168 | return rslt; 169 | } 170 | 171 | 172 | /* 173 | * Method: Pm_OpenOutput 174 | */ 175 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput 176 | (JNIEnv *env, jclass cl, jobject jstream, jint index, jstring extras, 177 | jint bufsiz, jint latency) 178 | { 179 | PmError rslt; 180 | PortMidiStream *stream; 181 | CLASS(c, jstream); 182 | ADDRESS_FID(fid, c); 183 | rslt = Pm_OpenOutput(&stream, index, NULL, bufsiz, NULL, NULL, latency); 184 | SET_PMSTREAM(jstream, fid, stream); 185 | return rslt; 186 | } 187 | 188 | 189 | /* 190 | * Method: Pm_SetFilter 191 | */ 192 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter 193 | (JNIEnv *env, jclass cl, jobject jstream, jint filters) 194 | { 195 | CLASS(c, jstream); 196 | ADDRESS_FID(fid, c); 197 | return Pm_SetFilter(PMSTREAM(jstream, fid), filters); 198 | } 199 | 200 | 201 | /* 202 | * Method: Pm_SetChannelMask 203 | */ 204 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask 205 | (JNIEnv *env, jclass cl, jobject jstream, jint mask) 206 | { 207 | CLASS(c, jstream); 208 | ADDRESS_FID(fid, c); 209 | return Pm_SetChannelMask(PMSTREAM(jstream, fid), mask); 210 | } 211 | 212 | 213 | /* 214 | * Method: Pm_Abort 215 | */ 216 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort 217 | (JNIEnv *env, jclass cl, jobject jstream) 218 | { 219 | CLASS(c, jstream); 220 | ADDRESS_FID(fid, c); 221 | return Pm_Abort(PMSTREAM(jstream, fid)); 222 | } 223 | 224 | 225 | /* 226 | * Method: Pm_Close 227 | */ 228 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close 229 | (JNIEnv *env, jclass cl, jobject jstream) 230 | { 231 | CLASS(c, jstream); 232 | ADDRESS_FID(fid, c); 233 | return Pm_Close(PMSTREAM(jstream, fid)); 234 | } 235 | 236 | 237 | /* 238 | * Method: Pm_Read 239 | */ 240 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read 241 | (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent) 242 | { 243 | CLASS(jstream_class, jstream); 244 | ADDRESS_FID(address_fid, jstream_class); 245 | jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent); 246 | jfieldID message_fid = 247 | (*env)->GetFieldID(env, jpmevent_class, "message", "I"); 248 | jfieldID timestamp_fid = 249 | (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I"); 250 | PmEvent buffer; 251 | PmError rslt = Pm_Read(PMSTREAM(jstream, address_fid), &buffer, 1); 252 | (*env)->SetIntField(env, jpmevent, message_fid, buffer.message); 253 | (*env)->SetIntField(env, jpmevent, timestamp_fid, buffer.timestamp); 254 | return rslt; 255 | } 256 | 257 | 258 | /* 259 | * Method: Pm_Poll 260 | */ 261 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll 262 | (JNIEnv *env, jclass cl, jobject jstream) 263 | { 264 | CLASS(c, jstream); 265 | ADDRESS_FID(fid, c); 266 | return Pm_Poll(PMSTREAM(jstream, fid)); 267 | } 268 | 269 | 270 | /* 271 | * Method: Pm_Write 272 | */ 273 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write 274 | (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent) 275 | { 276 | CLASS(jstream_class, jstream); 277 | ADDRESS_FID(address_fid, jstream_class); 278 | jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent); 279 | jfieldID message_fid = 280 | (*env)->GetFieldID(env, jpmevent_class, "message", "I"); 281 | jfieldID timestamp_fid = 282 | (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I"); 283 | // note that we call WriteShort because it's simpler than constructing 284 | // a buffer and passing it to Pm_Write 285 | return Pm_WriteShort(PMSTREAM(jstream, address_fid), 286 | (*env)->GetIntField(env, jpmevent, timestamp_fid), 287 | (*env)->GetIntField(env, jpmevent, message_fid)); 288 | } 289 | 290 | 291 | /* 292 | * Method: Pm_WriteShort 293 | */ 294 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort 295 | (JNIEnv *env, jclass cl, jobject jstream, jint when, jint msg) 296 | { 297 | CLASS(c, jstream); 298 | ADDRESS_FID(fid, c); 299 | return Pm_WriteShort(PMSTREAM(jstream, fid), when, msg); 300 | } 301 | 302 | 303 | /* 304 | * Method: Pm_WriteSysEx 305 | */ 306 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx 307 | (JNIEnv *env, jclass cl, jobject jstream, jint when, jbyteArray jmsg) 308 | { 309 | CLASS(c, jstream); 310 | ADDRESS_FID(fid, c); 311 | jbyte *bytes = (*env)->GetByteArrayElements(env, jmsg, 0); 312 | PmError rslt = Pm_WriteSysEx(PMSTREAM(jstream, fid), when, 313 | (unsigned char *) bytes); 314 | (*env)->ReleaseByteArrayElements(env, jmsg, bytes, 0); 315 | return rslt; 316 | } 317 | 318 | /* 319 | * Method: Pt_TimeStart 320 | */ 321 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart 322 | (JNIEnv *env, jclass c, jint resolution) 323 | { 324 | return Pt_Start(resolution, NULL, NULL); 325 | } 326 | 327 | /* 328 | * Method: Pt_TimeStop 329 | */ 330 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop 331 | (JNIEnv *env, jclass c) 332 | { 333 | return Pt_Stop(); 334 | } 335 | 336 | /* 337 | * Method: Pt_Time 338 | */ 339 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time 340 | (JNIEnv *env, jclass c) 341 | { 342 | return Pt_Time(); 343 | } 344 | 345 | /* 346 | * Method: Pt_TimeStarted 347 | */ 348 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted 349 | (JNIEnv *env, jclass c) 350 | { 351 | return Pt_Started(); 352 | } 353 | 354 | 355 | -------------------------------------------------------------------------------- /pm_test/fast.c: -------------------------------------------------------------------------------- 1 | /* fast.c -- send many MIDI messages very fast. 2 | * 3 | * This is a stress test created to explore reports of 4 | * pm_write() call blocking (forever) on Linux when 5 | * sending very dense MIDI sequences. 6 | * 7 | * Modified 8 Aug 2017 with -n to send expired timestamps 8 | * to test a theory about why Linux ALSA hangs in Audacity. 9 | * 10 | * Modified 9 Aug 2017 with -m, -p to test when timestamps are 11 | * wrapping from negative to positive or positive to negative. 12 | * 13 | * Roger B. Dannenberg, Aug 2017 14 | */ 15 | 16 | #include "portmidi.h" 17 | #include "porttime.h" 18 | #include "stdlib.h" 19 | #include "stdio.h" 20 | #include "string.h" 21 | #include "assert.h" 22 | 23 | #define DEVICE_INFO NULL 24 | #define DRIVER_INFO NULL 25 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 26 | 27 | #define STRING_MAX 80 /* used for console input */ 28 | // need to get declaration for Sleep() 29 | #ifdef WIN32 30 | #include "windows.h" 31 | #else 32 | #include 33 | #define Sleep(n) usleep(n * 1000) 34 | #endif 35 | 36 | 37 | int32_t latency = 0; 38 | int32_t msgrate = 0; 39 | int deviceno = -9999; 40 | int duration = 0; 41 | int expired_timestamps = FALSE; 42 | int use_timeoffset = 0; 43 | 44 | /* read a number from console */ 45 | /**/ 46 | int get_number(const char *prompt) 47 | { 48 | int n = 0, i; 49 | fputs(prompt, stdout); 50 | while (n != 1) { 51 | n = scanf("%d", &i); 52 | while (getchar() != '\n') ; 53 | } 54 | return i; 55 | } 56 | 57 | 58 | /* get_time -- the time reference. Normally, this will be the default 59 | * time, Pt_Time(), but if you use the -p or -m option, the time 60 | * reference will start at an offset of -10s for -m, or 61 | * maximum_time - 10s for -p, so that we can observe what happens 62 | * with negative time or when time changes sign or wraps (by 63 | * generating output for more than 10s). 64 | */ 65 | PmTimestamp get_time(void *info) 66 | { 67 | PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset); 68 | return now; 69 | } 70 | 71 | 72 | void fast_test(void) 73 | { 74 | PmStream *midi; 75 | char line[STRING_MAX]; 76 | int pause = FALSE; /* pause if this is a virtual output port */ 77 | PmError err = pmNoError; 78 | /* output buffer size should be a little more than 79 | msgrate * latency / 1000. PortMidi will guarantee 80 | a minimum of latency / 2 */ 81 | int buffer_size = msgrate * latency / 900; 82 | PmTimestamp start, now; 83 | int msgcnt = 0; 84 | int polling_count = 0; 85 | int pitch = 60; 86 | int printtime = 1000; 87 | 88 | /* It is recommended to start timer before PortMidi */ 89 | TIME_START; 90 | 91 | /* open output device */ 92 | if (deviceno == Pm_CountDevices()) { 93 | deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO); 94 | if (deviceno >= 0) { 95 | err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, 96 | get_time, NULL, latency); 97 | pause = TRUE; 98 | } 99 | } else if (err >= pmNoError) { 100 | err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, 101 | get_time, NULL, latency); 102 | } 103 | if (err == pmHostError) { 104 | Pm_GetHostErrorText(line, STRING_MAX); 105 | printf("PortMidi found host error...\n %s\n", line); 106 | goto done; 107 | } else if (err < 0) { 108 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 109 | goto done; 110 | } 111 | printf("Midi Output opened with %ld ms latency.\n", (long) latency); 112 | if (pause) { 113 | printf("Pausing so you can connect a receiver to the newly created\n" 114 | " \"fast\" port. Type ENTER to proceed: "); 115 | while (getchar() != '\n') ; 116 | } 117 | /* wait a sec after printing previous line */ 118 | start = get_time(NULL) + 1000; 119 | while (start > get_time(NULL)) { 120 | Sleep(10); 121 | } 122 | printf("sending output...\n"); 123 | fflush(stdout); /* make sure message goes to console */ 124 | 125 | /* every 10ms send on/off pairs at timestamps set to current time */ 126 | now = get_time(NULL); 127 | /* if expired_timestamps, we want to send timestamps that have 128 | * expired. They should be sent immediately, but there's a suggestion 129 | * that negative delay might cause problems in the ALSA implementation 130 | * so this is something we can test using the -n flag. 131 | */ 132 | if (expired_timestamps) { 133 | now = now - 2 * latency; 134 | } 135 | 136 | while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) { 137 | /* how many messages do we send? Total should be 138 | * (elapsed * rate) / 1000 139 | */ 140 | int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000; 141 | /* always send until pitch would be 60 so if we run again, the 142 | next pitch (60) will be expected */ 143 | if (msgcnt < send_total) { 144 | if ((msgcnt & 1) == 0) { 145 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100)); 146 | } else { 147 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0)); 148 | /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */ 149 | pitch = (pitch - 59) % 12 + 60; 150 | } 151 | msgcnt += 1; 152 | if (((PmTimestamp) (now - start)) >= printtime) { 153 | printf("%d at %dms, polling count %d\n", msgcnt, now - start, 154 | polling_count); 155 | fflush(stdout); /* make sure message goes to console */ 156 | printtime += 1000; /* next msg in 1s */ 157 | } 158 | } 159 | now = get_time(NULL); 160 | polling_count++; 161 | } 162 | /* close device (this not explicitly needed in most implementations) */ 163 | printf("ready to close and terminate... (type RETURN):"); 164 | while (getchar() != '\n') ; 165 | 166 | Pm_Close(midi); 167 | done: 168 | Pm_Terminate(); 169 | printf("done closing and terminating...\n"); 170 | } 171 | 172 | 173 | void show_usage(void) 174 | { 175 | printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] " 176 | "[-n] [-p] [-m]\n" 177 | ", where latency is in ms,\n" 178 | " rate is messages per second,\n" 179 | " device is the PortMidi device number,\n" 180 | " dur is the length of the test in seconds,\n" 181 | " -n means send timestamps in the past,\n" 182 | " -p means use a large positive time offset,\n" 183 | " -m means use a large negative time offset, and\n" 184 | " -h means help.\n"); 185 | } 186 | 187 | int main(int argc, char *argv[]) 188 | { 189 | int default_in; 190 | int default_out; 191 | char *deflt; 192 | int i = 0; 193 | int latency_valid = FALSE; 194 | int rate_valid = FALSE; 195 | int device_valid = FALSE; 196 | int dur_valid = FALSE; 197 | 198 | if (sizeof(void *) == 8) 199 | printf("Apparently this is a 64-bit machine.\n"); 200 | else if (sizeof(void *) == 4) 201 | printf ("Apparently this is a 32-bit machine.\n"); 202 | 203 | if (argc <= 1) { 204 | show_usage(); 205 | } else { 206 | for (i = 1; i < argc; i++) { 207 | if (strcmp(argv[i], "-h") == 0) { 208 | show_usage(); 209 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { 210 | i = i + 1; 211 | latency = atoi(argv[i]); 212 | printf("Latency will be %ld\n", (long) latency); 213 | latency_valid = TRUE; 214 | } else if (strcmp(argv[i], "-r") == 0) { 215 | i = i + 1; 216 | msgrate = atoi(argv[i]); 217 | printf("Rate will be %d messages/second\n", msgrate); 218 | rate_valid = TRUE; 219 | } else if (strcmp(argv[i], "-d") == 0) { 220 | i = i + 1; 221 | deviceno = atoi(argv[i]); 222 | printf("Device will be %d\n", deviceno); 223 | } else if (strcmp(argv[i], "-s") == 0) { 224 | i = i + 1; 225 | duration = atoi(argv[i]); 226 | printf("Duration will be %d seconds\n", duration); 227 | dur_valid = TRUE; 228 | } else if (strcmp(argv[i], "-n") == 0) { 229 | printf("Sending expired timestamps (-n)\n"); 230 | expired_timestamps = TRUE; 231 | } else if (strcmp(argv[i], "-p") == 0) { 232 | printf("Time offset set to 2147473648 (-p)\n"); 233 | use_timeoffset = 2147473648; 234 | } else if (strcmp(argv[i], "-m") == 0) { 235 | printf("Time offset set to -10000 (-m)\n"); 236 | use_timeoffset = -10000; 237 | } else { 238 | show_usage(); 239 | } 240 | } 241 | } 242 | 243 | if (!latency_valid) { 244 | // coerce to known size 245 | latency = (int32_t) get_number("Latency in ms: "); 246 | } 247 | 248 | if (!rate_valid) { 249 | // coerce from "%d" to known size 250 | msgrate = (int32_t) get_number("Rate in messages per second: "); 251 | } 252 | 253 | if (!dur_valid) { 254 | duration = get_number("Duration in seconds: "); 255 | } 256 | 257 | /* list device information */ 258 | default_in = Pm_GetDefaultInputDeviceID(); 259 | default_out = Pm_GetDefaultOutputDeviceID(); 260 | for (i = 0; i < Pm_CountDevices(); i++) { 261 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 262 | if (info->output) { 263 | printf("%d: %s, %s", i, info->interf, info->name); 264 | if (i == deviceno) { 265 | device_valid = TRUE; 266 | deflt = "selected "; 267 | } else if (i == default_out) { 268 | deflt = "default "; 269 | } else { 270 | deflt = ""; 271 | } 272 | printf(" (%soutput)\n", deflt); 273 | } 274 | } 275 | printf("%d: Create virtual port named \"fast\"", i); 276 | if (i == deviceno) { 277 | device_valid = TRUE; 278 | deflt = "selected "; 279 | } else { 280 | deflt = ""; 281 | } 282 | printf(" (%soutput)\n", deflt); 283 | 284 | if (!device_valid) { 285 | deviceno = get_number("Output device number: "); 286 | } 287 | 288 | fast_test(); 289 | return 0; 290 | } 291 | -------------------------------------------------------------------------------- /pm_test/latency.c: -------------------------------------------------------------------------------- 1 | /* latency.c -- measure latency of OS */ 2 | 3 | #include "porttime.h" 4 | #include "portmidi.h" 5 | #include "stdlib.h" 6 | #include "stdio.h" 7 | #include "string.h" 8 | #include "assert.h" 9 | 10 | /* Latency is defined here to mean the time starting when a 11 | process becomes ready to run, and ending when the process 12 | actually runs. Latency is due to contention for the 13 | processor, usually due to other processes, OS activity 14 | including device drivers handling interrupts, and 15 | waiting for the scheduler to suspend the currently running 16 | process and activate the one that is waiting. 17 | 18 | Latency can affect PortMidi applications: if a process fails 19 | to wake up promptly, MIDI input may sit in the input buffer 20 | waiting to be handled, and MIDI output may not be generated 21 | with accurate timing. Using the latency parameter when 22 | opening a MIDI output port allows the caller to defer timing 23 | to PortMidi, which in most implementations will pass the 24 | data on to the OS. By passing timestamps and data to the 25 | OS kernel, device driver, or even hardware, there are fewer 26 | sources of latency that can affect the ultimate timing of 27 | the data. On the other hand, the application must generate 28 | and deliver the data ahead of the timestamp. The amount by 29 | which data is computed early must be at least as large as 30 | the worst-case latency to avoid timing problems. 31 | 32 | Latency is even more important in audio applications. If an 33 | application lets an audio output buffer underflow, an audible 34 | pop or click is produced. Audio input buffers can overflow, 35 | causing data to be lost. In general the audio buffers must 36 | be large enough to buffer the worst-case latency that the 37 | application will encounter. 38 | 39 | This program measures latency by recording the difference 40 | between the scheduled callback time and the current real time. 41 | We do not really know the scheduled callback time, so we will 42 | record the differences between the real time of each callback 43 | and the real time of the previous callback. Differences that 44 | are larger than the scheduled difference are recorded. Smaller 45 | differences indicate the system is recovering from an earlier 46 | latency, so these are ignored. 47 | Since printing by the callback process can cause all sorts of 48 | delays, this program records latency observations in a 49 | histogram. When the program is stopped, the histogram is 50 | printed to the console. 51 | 52 | Optionally the system can be tested under a load of MIDI input, 53 | MIDI output, or both. If MIDI input is selected, the callback 54 | thread will read any waiting MIDI events each iteration. You 55 | must generate events on this interface for the test to actually 56 | put any appreciable load on PortMidi. If MIDI output is 57 | selected, alternating note on and note off events are sent each 58 | X iterations, where you specify X. For example, with a timer 59 | callback period of 2ms and X=1, a MIDI event is sent every 2ms. 60 | 61 | 62 | INTERPRETING RESULTS: Time is quantized to 1ms, so there is 63 | some uncertainty due to rounding. A microsecond latency that 64 | spans the time when the clock is incremented will be reported 65 | as a latency of 1. On the other hand, a latency of almost 66 | 1ms that falls between two clock ticks will be reported as 67 | zero. In general, if the highest nonzero bin is numbered N, 68 | then the maximum latency is N+1. 69 | 70 | CHANGE LOG 71 | 72 | 18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive 73 | MIDI during test, and made period user-settable. 74 | */ 75 | 76 | #define HIST_LEN 21 /* how many 1ms bins in the histogram */ 77 | 78 | #define STRING_MAX 80 /* used for console input */ 79 | 80 | #define INPUT_BUFFER_SIZE 100 81 | #define OUTPUT_BUFFER_SIZE 0 82 | 83 | #ifndef max 84 | #define max(a, b) ((a) > (b) ? (a) : (b)) 85 | #endif 86 | #ifndef min 87 | #define min(a, b) ((a) <= (b) ? (a) : (b)) 88 | #endif 89 | 90 | int get_number(const char *prompt); 91 | 92 | PtTimestamp previous_callback_time = 0; 93 | 94 | int period; /* milliseconds per callback */ 95 | 96 | int histogram[HIST_LEN]; 97 | int max_latency = 0; /* worst latency observed */ 98 | int out_of_range = 0; /* how many points outside of HIST_LEN? */ 99 | 100 | int test_in, test_out; /* test MIDI in and/or out? */ 101 | int output_period; /* output MIDI every __ iterations if test_out true */ 102 | int iteration = 0; 103 | PmStream *in, *out; 104 | int note_on = 0; /* is the note currently on? */ 105 | 106 | /* callback function for PortTime -- computes histogram */ 107 | void pt_callback(PtTimestamp timestamp, void *userData) 108 | { 109 | PtTimestamp difference = timestamp - previous_callback_time - period; 110 | previous_callback_time = timestamp; 111 | 112 | /* allow 5 seconds for the system to settle down */ 113 | if (timestamp < 5000) return; 114 | 115 | iteration++; 116 | /* send a note on/off if user requested it */ 117 | if (test_out && (iteration % output_period == 0)) { 118 | PmEvent buffer[1]; 119 | buffer[0].timestamp = Pt_Time(); 120 | if (note_on) { 121 | /* note off */ 122 | buffer[0].message = Pm_Message(0x90, 60, 0); 123 | note_on = 0; 124 | } else { 125 | /* note on */ 126 | buffer[0].message = Pm_Message(0x90, 60, 100); 127 | note_on = 1; 128 | } 129 | Pm_Write(out, buffer, 1); 130 | iteration = 0; 131 | } 132 | 133 | /* read all waiting events (if user requested) */ 134 | if (test_in) { 135 | PmError status; 136 | PmEvent buffer[1]; 137 | do { 138 | status = Pm_Poll(in); 139 | if (status == TRUE) { 140 | Pm_Read(in,buffer,1); 141 | } 142 | } while (status == TRUE); 143 | } 144 | 145 | if (difference < 0) return; /* ignore when system is "catching up" */ 146 | 147 | /* update the histogram */ 148 | if (difference < HIST_LEN) { 149 | histogram[difference]++; 150 | } else { 151 | out_of_range++; 152 | } 153 | 154 | if (max_latency < difference) max_latency = difference; 155 | } 156 | 157 | 158 | int main(int argc, char *argv[]) 159 | { 160 | int i; 161 | int len; 162 | int choice; 163 | PtTimestamp stop; 164 | printf("Latency histogram.\n"); 165 | period = 0; 166 | while (period < 1) { 167 | period = get_number("Choose timer period (in ms, >= 1): "); 168 | } 169 | printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", 170 | "1. No MIDI traffic", 171 | "2. MIDI input", 172 | "3. MIDI output", 173 | "4. MIDI input and output"); 174 | choice = get_number("? "); 175 | switch (choice) { 176 | case 1: test_in = 0; test_out = 0; break; 177 | case 2: test_in = 1; test_out = 0; break; 178 | case 3: test_in = 0; test_out = 1; break; 179 | case 4: test_in = 1; test_out = 1; break; 180 | default: assert(0); 181 | } 182 | if (test_in || test_out) { 183 | /* list device information */ 184 | for (i = 0; i < Pm_CountDevices(); i++) { 185 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 186 | if ((test_in && info->input) || 187 | (test_out && info->output)) { 188 | printf("%d: %s, %s", i, info->interf, info->name); 189 | if (info->input) printf(" (input)"); 190 | if (info->output) printf(" (output)"); 191 | printf("\n"); 192 | } 193 | } 194 | /* open stream(s) */ 195 | if (test_in) { 196 | int i = get_number("MIDI input device number: "); 197 | Pm_OpenInput(&in, 198 | i, 199 | NULL, 200 | INPUT_BUFFER_SIZE, 201 | (PmTimestamp (*)(void *)) Pt_Time, 202 | NULL); 203 | /* turn on filtering; otherwise, input might overflow in the 204 | 5-second period before timer callback starts reading midi */ 205 | Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); 206 | } 207 | if (test_out) { 208 | int i = get_number("MIDI output device number: "); 209 | PmEvent buffer[1]; 210 | Pm_OpenOutput(&out, 211 | i, 212 | NULL, 213 | OUTPUT_BUFFER_SIZE, 214 | (PmTimestamp (*)(void *)) Pt_Time, 215 | NULL, 216 | 0); /* no latency scheduling */ 217 | 218 | /* send a program change to force a status byte -- this fixes 219 | a problem with a buggy linux MidiSport driver, and shouldn't 220 | hurt anything else 221 | */ 222 | buffer[0].timestamp = 0; 223 | buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ 224 | Pm_Write(out, buffer, 1); 225 | 226 | output_period = get_number( 227 | "MIDI out should be sent every __ callback iterations: "); 228 | 229 | assert(output_period >= 1); 230 | } 231 | } 232 | 233 | printf("Latency measurements will start in 5 seconds. " 234 | "Type return to stop: "); 235 | Pt_Start(period, &pt_callback, 0); 236 | while (getchar() != '\n') ; 237 | stop = Pt_Time(); 238 | Pt_Stop(); 239 | 240 | /* courteously turn off the last note, if necessary */ 241 | if (note_on) { 242 | PmEvent buffer[1]; 243 | buffer[0].timestamp = Pt_Time(); 244 | buffer[0].message = Pm_Message(0x90, 60, 0); 245 | Pm_Write(out, buffer, 1); 246 | } 247 | 248 | /* print the histogram */ 249 | printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); 250 | printf("Latency(ms) Number of occurrences\n"); 251 | /* avoid printing beyond last non-zero histogram entry */ 252 | len = min(HIST_LEN, max_latency + 1); 253 | for (i = 0; i < len; i++) { 254 | printf("%2d %10d\n", i, histogram[i]); 255 | } 256 | printf("Number of points greater than %dms: %d\n", 257 | HIST_LEN - 1, out_of_range); 258 | printf("Maximum latency: %d milliseconds\n", max_latency); 259 | printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); 260 | printf("than the numbers reported here.\n"); 261 | printf("Type return to exit..."); 262 | while (getchar() != '\n') ; 263 | 264 | if(choice == 2) 265 | Pm_Close(in); 266 | else if(choice == 3) 267 | Pm_Close(out); 268 | else if(choice == 4) 269 | { 270 | Pm_Close(in); 271 | Pm_Close(out); 272 | } 273 | return 0; 274 | } 275 | 276 | 277 | /* read a number from console */ 278 | int get_number(const char *prompt) 279 | { 280 | int n = 0, i; 281 | fputs(prompt, stdout); 282 | while (n != 1) { 283 | n = scanf("%d", &i); 284 | while (getchar() != '\n') ; 285 | } 286 | return i; 287 | } 288 | -------------------------------------------------------------------------------- /pm_sndio/pmsndio.c: -------------------------------------------------------------------------------- 1 | /* pmsndio.c -- PortMidi os-dependent code */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "portmidi.h" 11 | #include "pmutil.h" 12 | #include "pminternal.h" 13 | #include "porttime.h" 14 | 15 | #define NDEVS 9 16 | #define SYSEX_MAXLEN 1024 17 | 18 | #define SYSEX_START 0xf0 19 | #define SYSEX_END 0xf7 20 | 21 | extern pm_fns_node pm_sndio_in_dictionary; 22 | extern pm_fns_node pm_sndio_out_dictionary; 23 | 24 | /* length of voice and common messages (status byte included) */ 25 | unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; 26 | unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; 27 | 28 | struct mio_dev { 29 | char name[16]; 30 | struct mio_hdl *hdl; 31 | int mode; 32 | char errmsg[PM_HOST_ERROR_MSG_LEN]; 33 | pthread_t thread; 34 | } devs[NDEVS]; 35 | 36 | static void set_mode(struct mio_dev *, unsigned int); 37 | 38 | void pm_init(void) 39 | { 40 | int i, j, k = 0; 41 | char devices[][16] = {"midithru", "rmidi", "midi", "snd"}; 42 | 43 | /* default */ 44 | strcpy(devs[0].name, MIO_PORTANY); 45 | pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k], 46 | &pm_sndio_in_dictionary); 47 | pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k], 48 | &pm_sndio_out_dictionary); 49 | k++; 50 | 51 | for (i = 0; i < 4; i++) { 52 | for (j = 0; j < 2; j++) { 53 | sprintf(devs[k].name, "%s/%d", devices[i], j); 54 | pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k], 55 | &pm_sndio_in_dictionary); 56 | pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k], 57 | &pm_sndio_out_dictionary); 58 | k++; 59 | } 60 | } 61 | 62 | // this is set when we return to Pm_Initialize, but we need it 63 | // now in order to (successfully) call Pm_CountDevices() 64 | pm_initialized = TRUE; 65 | pm_default_input_device_id = 0; 66 | pm_default_output_device_id = 1; 67 | } 68 | 69 | void pm_term(void) 70 | { 71 | int i; 72 | for(i = 0; i < NDEVS; i++) { 73 | if (devs[i].mode != 0) { 74 | set_mode(&devs[i], 0); 75 | if (devs[i].thread) { 76 | pthread_join(devs[i].thread, NULL); 77 | devs[i].thread = NULL; 78 | } 79 | } 80 | } 81 | } 82 | 83 | PmDeviceID Pm_GetDefaultInputDeviceID(void) { 84 | Pm_Initialize(); 85 | return pm_default_input_device_id; 86 | } 87 | 88 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { 89 | Pm_Initialize(); 90 | return pm_default_output_device_id; 91 | } 92 | 93 | void *pm_alloc(size_t s) { return malloc(s); } 94 | 95 | void pm_free(void *ptr) { free(ptr); } 96 | 97 | /* midi_message_length -- how many bytes in a message? */ 98 | static int midi_message_length(PmMessage message) 99 | { 100 | unsigned char st = message & 0xff; 101 | if (st >= 0xf8) 102 | return 1; 103 | else if (st >= 0xf0) 104 | return common_len[st & 7]; 105 | else if (st >= 0x80) 106 | return voice_len[(st >> 4) & 7]; 107 | else 108 | return 0; 109 | } 110 | 111 | void* input_thread(void *param) 112 | { 113 | PmInternal *midi = (PmInternal*)param; 114 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 115 | struct pollfd pfd[1]; 116 | nfds_t nfds; 117 | unsigned char st = 0, c = 0; 118 | int rc, revents, idx = 0, len = 0; 119 | size_t todo = 0; 120 | unsigned char buf[0x200], *p; 121 | PmEvent pm_ev, pm_ev_rt; 122 | unsigned char sysex_data[SYSEX_MAXLEN]; 123 | 124 | while(dev->mode & MIO_IN) { 125 | if (todo == 0) { 126 | nfds = mio_pollfd(dev->hdl, pfd, POLLIN); 127 | rc = poll(pfd, nfds, 100); 128 | if (rc < 0) { 129 | if (errno == EINTR) 130 | continue; 131 | break; 132 | } 133 | revents = mio_revents(dev->hdl, pfd); 134 | if (!(revents & POLLIN)) 135 | continue; 136 | 137 | todo = mio_read(dev->hdl, buf, sizeof(buf)); 138 | if (todo == 0) 139 | continue; 140 | p = buf; 141 | } 142 | c = *p++; 143 | todo--; 144 | 145 | if (c >= 0xf8) { 146 | pm_ev_rt.message = c; 147 | pm_ev_rt.timestamp = Pt_Time(); 148 | pm_read_short(midi, &pm_ev_rt); 149 | } else if (c == SYSEX_END) { 150 | /* note: PortMidi is designed to avoid the need for SYSEX_MAXLEN. 151 | With the new implementation of pm_read_bytes, it would be 152 | better to simply call pm_read_bytes() and let it parse buf, 153 | which can contain any number of whole or partial messages with 154 | interleaved realtime messages. I did not change the code because 155 | I cannot test it. -RBD */ 156 | if (st == SYSEX_START) { 157 | sysex_data[idx++] = c; 158 | pm_read_bytes(midi, sysex_data, idx, Pt_Time()); 159 | } 160 | st = 0; 161 | idx = 0; 162 | } else if (c == SYSEX_START) { 163 | st = c; 164 | idx = 0; 165 | sysex_data[idx++] = c; 166 | } else if (c >= 0xf0) { 167 | pm_ev.message = c; 168 | len = common_len[c & 7]; 169 | st = c; 170 | idx = 1; 171 | } else if (c >= 0x80) { 172 | pm_ev.message = c; 173 | len = voice_len[(c >> 4) & 7]; 174 | st = c; 175 | idx = 1; 176 | } else if (st == SYSEX_START) { 177 | if (idx == SYSEX_MAXLEN) { 178 | fprintf(stderr, "the message is too long\n"); 179 | idx = st = 0; 180 | } else { 181 | sysex_data[idx++] = c; 182 | } 183 | } else if (st) { 184 | if (idx == 0 && st != SYSEX_START) 185 | pm_ev.message |= (c << (8 * idx++)); 186 | pm_ev.message |= (c << (8 * idx++)); 187 | if (idx == len) { 188 | pm_read_short(midi, &pm_ev); 189 | if (st >= 0xf0) 190 | st = 0; 191 | idx = 0; 192 | } 193 | } 194 | } 195 | 196 | pthread_exit(NULL); 197 | return NULL; 198 | } 199 | 200 | static void set_mode(struct mio_dev *dev, unsigned int mode) { 201 | if (dev->mode != 0) 202 | mio_close(dev->hdl); 203 | dev->mode = 0; 204 | if (mode != 0) 205 | dev->hdl = mio_open(dev->name, mode, 0); 206 | if (dev->hdl) 207 | dev->mode = mode; 208 | } 209 | 210 | static PmError sndio_out_open(PmInternal *midi, void *driverInfo) 211 | { 212 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 213 | 214 | if (dev->mode & MIO_OUT) 215 | return pmNoError; 216 | 217 | set_mode(dev, dev->mode | MIO_OUT); 218 | if (!(dev->mode & MIO_OUT)) { 219 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, 220 | "mio_open (output) failed: %s\n", dev->name); 221 | return pmHostError; 222 | } 223 | 224 | return pmNoError; 225 | } 226 | 227 | static PmError sndio_in_open(PmInternal *midi, void *driverInfo) 228 | { 229 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 230 | 231 | if (dev->mode & MIO_IN) 232 | return pmNoError; 233 | 234 | set_mode(dev, dev->mode | MIO_IN); 235 | if (!(dev->mode & MIO_IN)) { 236 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, 237 | "mio_open (input) failed: %s\n", dev->name); 238 | return pmHostError; 239 | } 240 | pthread_attr_t attr; 241 | pthread_attr_init(&attr); 242 | pthread_create(&dev->thread, &attr, input_thread, ( void* )midi); 243 | return pmNoError; 244 | } 245 | 246 | static PmError sndio_out_close(PmInternal *midi) 247 | { 248 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 249 | 250 | if (dev->mode & MIO_OUT) 251 | set_mode(dev, dev->mode & ~MIO_OUT); 252 | return pmNoError; 253 | } 254 | 255 | static PmError sndio_in_close(PmInternal *midi) 256 | { 257 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 258 | 259 | if (dev->mode & MIO_IN) { 260 | set_mode(dev, dev->mode & ~MIO_IN); 261 | pthread_join(dev->thread, NULL); 262 | dev->thread = NULL; 263 | } 264 | return pmNoError; 265 | } 266 | 267 | static PmError sndio_abort(PmInternal *midi) 268 | { 269 | return pmNoError; 270 | } 271 | 272 | static PmTimestamp sndio_synchronize(PmInternal *midi) 273 | { 274 | return 0; 275 | } 276 | 277 | static PmError do_write(struct mio_dev *dev, const void *addr, size_t nbytes) 278 | { 279 | size_t w = mio_write(dev->hdl, addr, nbytes); 280 | 281 | if (w != nbytes) { 282 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, 283 | "mio_write failed, bytes written:%zu\n", w); 284 | return pmHostError; 285 | } 286 | return pmNoError; 287 | } 288 | 289 | static PmError sndio_write_byte(PmInternal *midi, unsigned char byte, 290 | PmTimestamp timestamp) 291 | { 292 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 293 | 294 | return do_write(dev, &byte, 1); 295 | } 296 | 297 | static PmError sndio_write_short(PmInternal *midi, PmEvent *event) 298 | { 299 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 300 | int nbytes = midi_message_length(event->message); 301 | 302 | if (midi->latency > 0) { 303 | /* XXX the event should be queued for later playback */ 304 | return do_write(dev, &event->message, nbytes); 305 | } else { 306 | return do_write(dev, &event->message, nbytes); 307 | } 308 | return pmNoError; 309 | } 310 | 311 | static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp) 312 | { 313 | return pmNoError; 314 | } 315 | 316 | PmError sndio_sysex(PmInternal *midi, PmTimestamp timestamp) 317 | { 318 | return pmNoError; 319 | } 320 | 321 | static unsigned int sndio_has_host_error(PmInternal *midi) 322 | { 323 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 324 | 325 | return (dev->errmsg[0] != '\0'); 326 | } 327 | 328 | static void sndio_get_host_error(PmInternal *midi, char *msg, unsigned int len) 329 | { 330 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; 331 | 332 | strlcpy(msg, dev->errmsg, len); 333 | dev->errmsg[0] = '\0'; 334 | } 335 | 336 | pm_fns_node pm_sndio_in_dictionary = { 337 | none_write_short, 338 | none_sysex, 339 | none_sysex, 340 | none_write_byte, 341 | none_write_short, 342 | none_write_flush, 343 | sndio_synchronize, 344 | sndio_in_open, 345 | sndio_abort, 346 | sndio_in_close, 347 | success_poll, 348 | sndio_has_host_error, 349 | }; 350 | 351 | pm_fns_node pm_sndio_out_dictionary = { 352 | sndio_write_short, 353 | sndio_sysex, 354 | sndio_sysex, 355 | sndio_write_byte, 356 | sndio_write_short, 357 | sndio_write_flush, 358 | sndio_synchronize, 359 | sndio_out_open, 360 | sndio_abort, 361 | sndio_out_close, 362 | none_poll, 363 | sndio_has_host_error, 364 | }; 365 | 366 | --------------------------------------------------------------------------------