├── .gitignore ├── bindings ├── python │ ├── pyportmidi │ │ └── __init__.py │ ├── README_PYTHON.txt │ └── setup.py ├── java │ ├── pmdefaults │ │ ├── manifest.txt │ │ ├── pmdefaults │ │ ├── pmdefaults.icns │ │ ├── pmdefaults.ico │ │ ├── portmusic_logo.png │ │ ├── pmdefaults-icon.bmp │ │ ├── pmdefaults-icon.gif │ │ ├── pmdefaults-icon.png │ │ ├── pmdefaults-icon.xcf │ │ ├── PmDefaults.java │ │ ├── readme-win32.txt │ │ ├── README.txt │ │ └── pmdefaults-license.txt │ ├── JavaExe.exe │ ├── UpdateRsrcJavaExe.exe │ ├── setup │ │ └── pmdefaults-setup.exe │ ├── jportmidi │ │ ├── JPortMidiException.java │ │ └── JPortMidiApi.java │ ├── mac-make.sh │ ├── pmjni │ │ ├── pmjni.rc │ │ ├── jportmidi_JportMidiApi.h │ │ └── pmjni.c │ ├── make.bat │ ├── README.txt │ ├── CMakeLists.txt │ └── pmdefaults-setup-script.iss ├── csharp │ ├── pm_managed │ │ ├── app.ico │ │ ├── resource.h │ │ ├── pm_managed.cpp │ │ ├── Stdafx.h │ │ ├── Stdafx.cpp │ │ ├── ReadMe.txt │ │ ├── pm_managed.h │ │ ├── AssemblyInfo.cpp │ │ └── app.rc │ └── README.txt ├── README.txt └── common-lisp │ ├── test-no-cm.lisp │ └── README_CL.txt ├── portmusic_logo.png ├── src ├── portmidi │ ├── mac │ │ ├── pmmac.h │ │ ├── pmmacosxcm.h │ │ ├── pmmac.c │ │ ├── finddefault.c │ │ └── readbinaryplist.h │ ├── linux │ │ ├── pmlinux.h │ │ ├── pmlinuxalsa.h │ │ ├── finddefault.h │ │ ├── pmlinux.c │ │ ├── finddefault.c │ │ └── README_LINUX.txt │ ├── windows │ │ ├── pmwinmm.h │ │ └── pmwin.c │ └── common │ │ ├── pminternal.h │ │ └── pmutil.c └── porttime │ ├── porttime.c │ ├── ptwinmm.c │ ├── ptmacosx_mach.c │ ├── ptmacosx_cf.c │ └── ptlinux.c ├── README.txt ├── packaging ├── PortMidiConfig.cmake.in └── portmidi.pc.in ├── test ├── CMakeLists.txt ├── recvvirtual.c ├── sendvirtual.c ├── qtest.c ├── fastrcv.c ├── midiclock.c ├── fast.c ├── latency.c └── midithread.c ├── .github └── workflows │ └── build.yml ├── license.txt ├── include ├── porttime.h └── pmutil.h ├── CMakeLists.txt └── CHANGELOG.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | html 3 | latex 4 | -------------------------------------------------------------------------------- /bindings/python/pyportmidi/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .midi import * 3 | 4 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/manifest.txt: -------------------------------------------------------------------------------- 1 | Main-Class: pmdefaults/PmDefaults 2 | -------------------------------------------------------------------------------- /portmusic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/portmusic_logo.png -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults: -------------------------------------------------------------------------------- 1 | java -jar /usr/share/java/pmdefaults.jar > /dev/null 2 | -------------------------------------------------------------------------------- /bindings/java/JavaExe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/JavaExe.exe -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/csharp/pm_managed/app.ico -------------------------------------------------------------------------------- /bindings/java/UpdateRsrcJavaExe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/UpdateRsrcJavaExe.exe -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults.icns -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults.ico -------------------------------------------------------------------------------- /bindings/java/setup/pmdefaults-setup.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/setup/pmdefaults-setup.exe -------------------------------------------------------------------------------- /bindings/java/pmdefaults/portmusic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/portmusic_logo.png -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by app.rc 4 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults-icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults-icon.bmp -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults-icon.gif -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults-icon.png -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults-icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixxxdj/portmidi/HEAD/bindings/java/pmdefaults/pmdefaults-icon.xcf -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/pm_managed.cpp: -------------------------------------------------------------------------------- 1 | // This is the main DLL file. 2 | 3 | #include "stdafx.h" 4 | 5 | #include "pm_managed.h" 6 | 7 | -------------------------------------------------------------------------------- /src/portmidi/mac/pmmac.h: -------------------------------------------------------------------------------- 1 | /* pmmac.h */ 2 | 3 | extern PmDeviceID pm_default_input_device_id; 4 | extern PmDeviceID pm_default_output_device_id; 5 | -------------------------------------------------------------------------------- /src/portmidi/linux/pmlinux.h: -------------------------------------------------------------------------------- 1 | /* pmlinux.h */ 2 | 3 | extern PmDeviceID pm_default_input_device_id; 4 | extern PmDeviceID pm_default_output_device_id; 5 | 6 | -------------------------------------------------------------------------------- /src/porttime/porttime.c: -------------------------------------------------------------------------------- 1 | /* porttime.c -- portable API for millisecond timer */ 2 | 3 | /* There is no machine-independent implementation code to put here */ 4 | -------------------------------------------------------------------------------- /src/portmidi/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 | -------------------------------------------------------------------------------- /src/portmidi/windows/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 | -------------------------------------------------------------------------------- /bindings/README.txt: -------------------------------------------------------------------------------- 1 | These bindings are unmaintained and kept in case someone might want them someday. 2 | If you manage to get them working with the current CMake build system, please 3 | send pull requests. 4 | -------------------------------------------------------------------------------- /src/portmidi/linux/finddefault.h: -------------------------------------------------------------------------------- 1 | /* finddefault.h -- find_default_device() declaration 2 | Roger Dannenberg, Jan 2021 3 | */ 4 | 5 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id); 6 | -------------------------------------------------------------------------------- /src/portmidi/mac/pmmacosxcm.h: -------------------------------------------------------------------------------- 1 | /* system-specific definitions */ 2 | 3 | PmError pm_macosxcm_init(void); 4 | void pm_macosxcm_term(void); 5 | 6 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id); 7 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/Stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, 3 | // but are changed infrequently 4 | 5 | #pragma once 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | **UNMAINTAINED** 2 | 3 | This repository was a fork that was made because the original PortMidi 4 | library had not been maintained in years. The original author is now 5 | maintaining it at https://github.com/PortMidi/portmidi 6 | 7 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/Stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // pm_managed.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | -------------------------------------------------------------------------------- /packaging/PortMidiConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | if(UNIX AND NOT APPLE) 5 | find_dependency(ALSA) 6 | endif() 7 | 8 | include("${CMAKE_CURRENT_LIST_DIR}/PortMidiTargets.cmake") 9 | 10 | check_required_components(PortTime) 11 | check_required_components(PortMidi) 12 | -------------------------------------------------------------------------------- /bindings/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 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/PmDefaults.java: -------------------------------------------------------------------------------- 1 | // PmDefaults -- a small application to set PortMIDI default input/output 2 | 3 | package pmdefaults; 4 | 5 | public class PmDefaults { 6 | public static void main(String[] args) { 7 | System.out.println("starting main"); 8 | new PmDefaultsFrame("PortMIDI Setup"); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/readme-win32.txt: -------------------------------------------------------------------------------- 1 | README.txt 2 | Roger B. Dannenberg 3 | 1 Jan 2009 4 | 5 | This directory contains files that implement: 6 | 7 | pmdefaults -- a program to set PortMidi default input/output devices 8 | 9 | You can copy and rename this *whole directory* to move the application 10 | to a convenient place. The application to run is pmdefaults.exe. 11 | 12 | -------------------------------------------------------------------------------- /packaging/portmidi.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | includedir=${prefix}/include 3 | libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | 5 | Name: portmidi 6 | Description: PortMidi is a cross platform library for accessing operating systems' MIDI APIs 7 | Version: @CMAKE_PROJECT_VERSION@ 8 | URL: https://github.com/mixxxdj/portmidi 9 | Libs: -L${libdir} -lportmidi -lporttime 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | macro(add_test name) 2 | add_executable(${name} ${name}.c) 3 | target_link_libraries(${name} PRIVATE PortMidi PortTime) 4 | endmacro(add_test) 5 | 6 | add_test(fast) 7 | add_test(fastrcv) 8 | add_test(latency) 9 | add_test(midiclock) 10 | add_test(midithread) 11 | add_test(midithru) 12 | add_test(mm) 13 | add_test(qtest) 14 | add_test(recvvirtual) 15 | add_test(sendvirtual) 16 | add_test(sysex) 17 | add_test(test) 18 | add_test(testio) 19 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/README.txt: -------------------------------------------------------------------------------- 1 | README.txt 2 | Roger B. Dannenberg 3 | 2 Jan 2009 4 | 5 | PmDefaults is a program to set default input and output devices for PortMidi 6 | applications. After running the PmDefaults program and choosing devices, 7 | identifiers for these devices will be returned by 8 | Pm_GetDefaultInputDeviceID() and Pm_GetDefaultOutputDeviceID(). 9 | 10 | Included in this directory are: 11 | 12 | manifest.txt -- used in pmdefaults.jar 13 | pmdefaults-icon.* -- various icons for applications 14 | pmdefaults-license.txt -- a version of portmidi/license.txt formatted for 15 | the windows installer 16 | portmusic_logo.png -- a logo displayed by the pmdefaults application 17 | readme-win32.txt -- this becomes the readme file for the pmdefaults 18 | application. It is copied to win32/README.txt by make.bat 19 | 20 | TO BUILD THE APPLICATION: see ../README.txt 21 | 22 | -------------------------------------------------------------------------------- /bindings/java/mac-make.sh: -------------------------------------------------------------------------------- 1 | # script to build a jar file to run PmDefaults from the command line on OS X 2 | # (This is for debugging. Normally, you would use XCode to build PmDefaults.app.) 3 | 4 | # Compile the java Portidi interface classes. 5 | javac jportmidi/*.java 6 | 7 | # Compile the pmdefaults application. 8 | javac -classpath . pmdefaults/*.java 9 | 10 | # Temporarily copy the portmusic_logo.png file here to add to the jar file. 11 | cp pmdefaults/portmusic_logo.png . 12 | 13 | # Create a directory to hold the distribution. 14 | mkdir mac-osx 15 | 16 | # Copy the interface DLL to the distribution directory. 17 | cp ../Release/libpmjni.dylib mac-osx 18 | 19 | # Create a java archive (jar) file of the distribution. 20 | jar cmf pmdefaults/manifest.txt mac-osx/pmdefaults.jar pmdefaults/*.class portmusic_logo.png jportmidi/*.class 21 | 22 | # Clean up the temporary image file now that it is in the jar file. 23 | rm portmusic_logo.png 24 | 25 | echo "You now have a jar file in mac-osx" 26 | 27 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | DYNAMIC LINK LIBRARY : pm_managed Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this pm_managed DLL for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your pm_managed application. 9 | 10 | pm_managed.vcproj 11 | This is the main project file for VC++ projects generated using an Application Wizard. 12 | It contains information about the version of Visual C++ that generated the file, and 13 | information about the platforms, configurations, and project features selected with the 14 | Application Wizard. 15 | 16 | pm_managed.cpp 17 | This is the main DLL source file. 18 | 19 | pm_managed.h 20 | This file contains a class declaration. 21 | 22 | AssemblyInfo.cpp 23 | Contains custom attributes for modifying assembly metadata. 24 | 25 | ///////////////////////////////////////////////////////////////////////////// 26 | Other notes: 27 | 28 | AppWizard uses "TODO:" to indicate parts of the source code you 29 | should add to or customize. 30 | 31 | ///////////////////////////////////////////////////////////////////////////// 32 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/pm_managed.h: -------------------------------------------------------------------------------- 1 | // pm_managed.h 2 | 3 | #pragma once 4 | 5 | #include "portmidi.h" 6 | 7 | using namespace System; 8 | 9 | namespace pm_managed { 10 | 11 | 12 | public ref class MpmDeviceInfo 13 | { 14 | public: 15 | int structVersion; 16 | System::String^ interf; /* underlying MIDI API, e.g. MMSystem or DirectX */ 17 | System::String^ name; /* device name, e.g. USB MidiSport 1x1 */ 18 | bool input; /* true iff input is available */ 19 | bool output; /* true iff output is available */ 20 | int opened; /* used by generic PortMidi code to do error checking on arguments */ 21 | 22 | MpmDeviceInfo(const PmDeviceInfo* info) 23 | { 24 | structVersion = info->structVersion; 25 | input = (info->input != 0); 26 | output = (info->output != 0); 27 | opened = info->opened; 28 | 29 | interf = gcnew System::String(info->interf); 30 | name = gcnew System::String(info->name); 31 | } 32 | }; 33 | 34 | public ref class ManagedPortMIDI 35 | { 36 | public: 37 | int Pm_Initialize() 38 | { 39 | ::Pm_Initialize(); 40 | return 0; 41 | } 42 | 43 | int Pm_CountDevices() 44 | { 45 | return ::Pm_CountDevices(); 46 | } 47 | 48 | MpmDeviceInfo^ Pm_GetDeviceInfo(int id) 49 | { 50 | return gcnew MpmDeviceInfo(::Pm_GetDeviceInfo(id)); 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /bindings/python/README_PYTHON.txt: -------------------------------------------------------------------------------- 1 | PyPortMidi v0.03 03/15/05 2 | Python wrappings for PortMidi 3 | John Harrison 4 | harrison@media.mit.edu 5 | 6 | Modified by Roger B. Dannenberg, Nov 2009 7 | 8 | PyPortMidi 9 | ---------- 10 | 11 | PyPortMidi is a Python wrapper for PortMidi. PortMidi is a cross-platform 12 | C library for realtime MIDI control. Using PyPortMidi, you can send and 13 | receive MIDI data in realtime from Python. 14 | 15 | Besides using PyPortMidi to communicate to synthesizers and the 16 | like, it is possible to use PyPortMidi as a way to send MIDI messages 17 | between software packages on the same computer. For example, Using 18 | PyPortMidi and MIDI-YOKE on a Windows machine, it is possible to send 19 | realtime MIDI messages between programs on the same computer using 20 | loopback virtual MIDI ports. (At this time, MIDI-YOKE does not appear 21 | to run on Windows Vista.) 22 | 23 | PyPortMidi is cross-platform, but it will require some small 24 | changes in the setup.py file for it to install correctly on Linux 25 | machines. The changes should be pretty straightforward, and I am 26 | anxious to work with a Linux user on the port. 27 | 28 | PyPortMidi works with Python 2.6 and Python 3.1, although the ports 29 | are mostly separate because of various language incompatibilities. 30 | 31 | Please see README26.txt for information about the Python 2.6 version. 32 | 33 | See README31.txt for information about the Python 3.1 version. 34 | 35 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/AssemblyInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | using namespace System; 4 | using namespace System::Reflection; 5 | using namespace System::Runtime::CompilerServices; 6 | using namespace System::Runtime::InteropServices; 7 | using namespace System::Security::Permissions; 8 | 9 | // 10 | // General Information about an assembly is controlled through the following 11 | // set of attributes. Change these attribute values to modify the information 12 | // associated with an assembly. 13 | // 14 | [assembly:AssemblyTitleAttribute("pm_managed")]; 15 | [assembly:AssemblyDescriptionAttribute("")]; 16 | [assembly:AssemblyConfigurationAttribute("")]; 17 | [assembly:AssemblyCompanyAttribute("Innovative Computer Solutions")]; 18 | [assembly:AssemblyProductAttribute("pm_managed")]; 19 | [assembly:AssemblyCopyrightAttribute("Copyright (c) Innovative Computer Solutions 2006")]; 20 | [assembly:AssemblyTrademarkAttribute("")]; 21 | [assembly:AssemblyCultureAttribute("")]; 22 | 23 | // 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the value or you can default the Revision and Build Numbers 32 | // by using the '*' as shown below: 33 | 34 | [assembly:AssemblyVersionAttribute("1.0.*")]; 35 | 36 | [assembly:ComVisible(false)]; 37 | 38 | [assembly:CLSCompliantAttribute(true)]; 39 | 40 | [assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)]; 41 | -------------------------------------------------------------------------------- /bindings/csharp/pm_managed/app.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | 7 | ///////////////////////////////////////////////////////////////////////////// 8 | #undef APSTUDIO_READONLY_SYMBOLS 9 | 10 | ///////////////////////////////////////////////////////////////////////////// 11 | // English (U.S.) resources 12 | 13 | 14 | ///////////////////////////////////////////////////////////////////////////// 15 | // 16 | // Icon 17 | // 18 | 19 | // Icon placed first or with lowest ID value becomes application icon 20 | 21 | LANGUAGE 9, 3 22 | #pragma code_page(1252) 23 | 1 ICON "app.ico" 24 | 25 | #ifdef APSTUDIO_INVOKED 26 | ///////////////////////////////////////////////////////////////////////////// 27 | // 28 | // TEXTINCLUDE 29 | // 30 | 31 | 1 TEXTINCLUDE 32 | BEGIN 33 | "resource.h\0" 34 | "\0" 35 | END 36 | 37 | 2 TEXTINCLUDE 38 | BEGIN 39 | "#include ""afxres.h""\r\n" 40 | "\0" 41 | END 42 | 43 | 3 TEXTINCLUDE 44 | BEGIN 45 | "\0" 46 | END 47 | 48 | #endif // APSTUDIO_INVOKED 49 | 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 | -------------------------------------------------------------------------------- /bindings/csharp/README.txt: -------------------------------------------------------------------------------- 1 | This code was offered by Aaron Oxford as is. The pm_managed directory contains the code. If you develop a more complete C# wrapper for PortMidi, please consider contributing your code to the project. -RBD 2 | 3 | ---- from Aaron Oxford ---- 4 | 5 | I've attached the managed C++ project which I've inserted into my 2005 version of PortMIDI's VS solution. I wouldn't think the functions I've implemented would have changed so it all should still work with the latest version of PM. Obviously you won't want to permanently embed this since it means the whole solution can only be built under VS2005, but it's easy for a VS2005 user to insert the project after the solution is converted or even just build it separately. 6 | 7 | Making the managed wrapper turned out to be dead easy in the end (it was more of a battle finding the correct build settings & SDK's and learning to configure VS than anything else). Anyone wanting to use something I've not implemented yet simply needs to add more stubs like this 8 | 9 | int Pm_Initialize() 10 | { 11 | ::Pm_Initialize(); 12 | return 0; 13 | } 14 | 15 | to the code. To call from C# it's just a matter of 16 | 17 | ManagedPortMIDI mpm = new ManagedPortMIDI(); 18 | int err = mpm.Pm_Initialize(); 19 | 20 | Anyway as the little code example above indicates, the support really is basic and more likely than not to break at the first hint of something unexpected. As I said, I'd be happy to contribute but I don't think there's much to contribute yet. :-) 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - name: Ubuntu 14 | os: ubuntu-latest 15 | install_dir: ~/portmidi 16 | cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo 17 | - name: macOS 18 | os: macos-latest 19 | install_dir: ~/portmidi 20 | cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo 21 | - name: Windows 22 | os: windows-latest 23 | install_dir: C:\portmidi 24 | cmake_config: --config RelWithDebInfo 25 | 26 | name: ${{ matrix.name }} 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Check out Git repository 30 | uses: actions/checkout@v2 31 | - name: "[Ubuntu] Install dependencies" 32 | run: sudo apt install -y libasound2-dev 33 | if: runner.os == 'Linux' 34 | - name: Configure 35 | run: > 36 | cmake 37 | -DCMAKE_INSTALL_PREFIX=${{ matrix.install_dir }} 38 | -DBUILD_TESTING=ON 39 | ${{ matrix.cmake_extras }} 40 | -S . -B build 41 | - name: Build 42 | run: cmake --build build ${{ matrix.cmake_config }} 43 | env: 44 | CMAKE_BUILD_PARALLEL_LEVEL: 2 45 | - name: Install 46 | run: cmake --install . ${{ matrix.cmake_config }} 47 | working-directory: build 48 | - name: Upload Build Artifact 49 | uses: actions/upload-artifact@v2 50 | with: 51 | name: ${{ matrix.name }} portmidi build 52 | path: ${{ matrix.install_dir }} 53 | -------------------------------------------------------------------------------- /bindings/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 | -------------------------------------------------------------------------------- /src/portmidi/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 | PmDeviceID pm_default_input_device_id = -1; 19 | PmDeviceID pm_default_output_device_id = -1; 20 | 21 | void pm_init() 22 | { 23 | PmError err = pm_macosxcm_init(); 24 | // this is set when we return to Pm_Initialize, but we need it 25 | // now in order to (successfully) call Pm_CountDevices() 26 | pm_initialized = TRUE; 27 | if (!err) { 28 | pm_default_input_device_id = find_default_device( 29 | "/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, 30 | pm_default_input_device_id); 31 | pm_default_output_device_id = find_default_device( 32 | "/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, 33 | pm_default_output_device_id); 34 | } 35 | } 36 | 37 | 38 | void pm_term(void) 39 | { 40 | pm_macosxcm_term(); 41 | } 42 | 43 | 44 | PmDeviceID Pm_GetDefaultInputDeviceID() 45 | { 46 | Pm_Initialize(); 47 | return pm_default_input_device_id; 48 | } 49 | 50 | PmDeviceID Pm_GetDefaultOutputDeviceID() 51 | { 52 | Pm_Initialize(); 53 | return pm_default_output_device_id; 54 | } 55 | 56 | void *pm_alloc(size_t s) { return malloc(s); } 57 | 58 | void pm_free(void *ptr) { free(ptr); } 59 | 60 | 61 | -------------------------------------------------------------------------------- /bindings/java/make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Compile the java PortMidi interface classes. 4 | javac jportmidi/*.java 5 | 6 | rem Compile the pmdefaults application. 7 | javac -classpath . pmdefaults/*.java 8 | 9 | rem Temporarily copy the portmusic_logo.png file here to add to the jar file. 10 | copy pmdefaults\portmusic_logo.png . > nul 11 | 12 | rem Create a directory to hold the distribution. 13 | mkdir win32 14 | 15 | rem Attempt to copy the interface DLL to the distribution directory. 16 | 17 | if exist "..\release\pmjni.dll" goto have-dll 18 | 19 | echo "ERROR: pmjni.dll not found!" 20 | exit /b 1 21 | 22 | :have-dll 23 | copy "..\release\pmjni.dll" win32\pmjni.dll > nul 24 | 25 | rem Create a java archive (jar) file of the distribution. 26 | jar cmf pmdefaults\manifest.txt win32\pmdefaults.jar pmdefaults\*.class portmusic_logo.png jportmidi\*.class 27 | 28 | rem Clean up the temporary image file now that it is in the jar file. 29 | del portmusic_logo.png 30 | 31 | rem Copy the java execution code obtained from 32 | rem http://devwizard.free.fr/html/en/JavaExe.html to the distribution 33 | rem directory. The copy also renames the file to our desired executable 34 | rem name. 35 | copy JavaExe.exe win32\pmdefaults.exe > nul 36 | 37 | rem Integrate the icon into the executable using UpdateRsrcJavaExe from 38 | rem http://devwizard.free.fr 39 | UpdateRsrcJavaExe -run -exe=win32\pmdefaults.exe -ico=pmdefaults\pmdefaults.ico 40 | 41 | rem Copy the 32-bit windows read me file to the distribution directory. 42 | copy pmdefaults\readme-win32.txt win32\README.txt > nul 43 | 44 | rem Copy the license file to the distribution directory. 45 | copy pmdefaults\pmdefaults-license.txt win32\license.txt > nul 46 | 47 | echo "You can run pmdefaults.exe in win32" 48 | -------------------------------------------------------------------------------- /src/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() 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() 56 | { 57 | return time_started_flag; 58 | } 59 | 60 | 61 | PMEXPORT PtTimestamp Pt_Time() 62 | { 63 | return timeGetTime() - time_offset; 64 | } 65 | 66 | 67 | PMEXPORT void Pt_Sleep(int32_t duration) 68 | { 69 | Sleep(duration); 70 | } 71 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults/pmdefaults-license.txt: -------------------------------------------------------------------------------- 1 | LICENSE INFORMATION 2 | 3 | PmDefaults is a small program to set default MIDI input and output 4 | devices for other programs using the PortMidi library. 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 | The text above constitutes the entire PortMidi license; however, 31 | the PortMusic community also makes the following non-binding requests: 32 | 33 | Any person wishing to distribute modifications to the Software is 34 | requested to send the modifications to the original developer so that 35 | they can be incorporated into the canonical version. It is also 36 | requested that these non-binding requests be included along with the 37 | license above. 38 | -------------------------------------------------------------------------------- /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 | * Copyright (c) 2021 Mixxx DJ Software Team 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining 13 | * a copy of this software and associated documentation files 14 | * (the "Software"), to deal in the Software without restriction, 15 | * including without limitation the rights to use, copy, modify, merge, 16 | * publish, distribute, sublicense, and/or sell copies of the Software, 17 | * and to permit persons to whom the Software is furnished to do so, 18 | * subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be 21 | * included in all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 27 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 28 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | */ 31 | 32 | /* 33 | * The text above constitutes the entire PortMidi license; however, 34 | * the PortMusic community also makes the following non-binding requests: 35 | * 36 | * Any person wishing to distribute modifications to the Software is 37 | * requested to send the modifications to the original developer so that 38 | * they can be incorporated into the canonical version. It is also 39 | * requested that these non-binding requests be included along with the 40 | * license above. 41 | */ 42 | -------------------------------------------------------------------------------- /src/portmidi/mac/finddefault.c: -------------------------------------------------------------------------------- 1 | /* finddefault.c -- find_default_device() implementation 2 | Roger Dannenberg, June 2008 3 | */ 4 | 5 | #include 6 | #include 7 | #include "portmidi.h" 8 | #include "pmutil.h" 9 | #include "pminternal.h" 10 | #include "pmmacosxcm.h" 11 | #include "readbinaryplist.h" 12 | 13 | /* Parse preference files, find default device, search devices -- 14 | This parses the preference file(s) once for input and once for 15 | output, which is inefficient but much simpler to manage. Note 16 | that using the readbinaryplist.c module, you cannot keep two 17 | plist files (user and system) open at once (due to a simple 18 | memory management scheme). 19 | */ 20 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id) 21 | /* path -- the name of the preference we are searching for 22 | input -- true iff this is an input device 23 | id -- current default device id 24 | returns matching device id if found, otherwise id 25 | */ 26 | { 27 | static char *pref_file = "com.apple.java.util.prefs.plist"; 28 | char *pref_str = NULL; 29 | // read device preferences 30 | value_ptr prefs = bplist_read_user_pref(pref_file); 31 | if (prefs) { 32 | value_ptr pref_val = value_dict_lookup_using_path(prefs, path); 33 | if (pref_val) { 34 | pref_str = value_get_asciistring(pref_val); 35 | } 36 | } 37 | if (!pref_str) { 38 | bplist_free_data(); /* look elsewhere */ 39 | prefs = bplist_read_system_pref(pref_file); 40 | if (prefs) { 41 | value_ptr pref_val = value_dict_lookup_using_path(prefs, path); 42 | if (pref_val) { 43 | pref_str = value_get_asciistring(pref_val); 44 | } 45 | } 46 | } 47 | if (pref_str) { /* search devices for match */ 48 | int i = pm_find_default_device(pref_str, input); 49 | if (i != pmNoDevice) { 50 | id = i; 51 | } 52 | } 53 | if (prefs) { 54 | bplist_free_data(); 55 | } 56 | return id; 57 | } 58 | -------------------------------------------------------------------------------- /bindings/java/README.txt: -------------------------------------------------------------------------------- 1 | README.txt 2 | Roger B. Dannenberg 3 | 16 Jun 2009 4 | 5 | This directory was created to implement PmDefaults, a program to 6 | set default input and output devices for PortMidi applications. 7 | 8 | There are three main sub-projects here: 9 | 1) pmjni -- a JNI (Java Native Interface) to access PortMidi 10 | 2) jportmidi -- a Java class to access PortMidi (uses pmjni) 11 | 3) pmdefaults -- the PmDefaults application (uses jportmidi) 12 | 13 | For Mac OS X, you should build the PmDefaults application in Xcode. 14 | 15 | For Win32, an installer for PmDefaults is included in setup/. 16 | To build from sources, you should first build everything including 17 | the portmidi dll (that will be used by the Java application) using 18 | Visual C++ and a provided .sln file in the portmidi home directory. 19 | Then, run make.bat in this directory. The subdirectory win32 will be 20 | created with the application pmdefaults.exe. You can run this application 21 | in the normal way. To move the application, you need to copy *everything* 22 | in win32. To build setup/pmdefaults-setup.exe, I have used both 23 | Setup Generator from Gentee software and Inno Setup from jrsoftware.org. 24 | A script for Inno Setup is included in this directory, but since paths 25 | seem to be absolute, you will have to adjust the paths in the script 26 | before you use it. 27 | 28 | ---- implementation notes ---- 29 | 30 | For windows, we use the free software JavaExe.exe. The copy here was 31 | downloaded from 32 | 33 | http://software.techrepublic.com.com/abstract.aspx?kw=javaexe&docid=767485 34 | 35 | I found this page by visiting http://software.techrepublic.com.com and 36 | searching in the "Software" category for "JavaExe" 37 | 38 | JavaExe works by placing the JavaExe.exe file in the directory with the 39 | Java application jar file and then *renaming* JavaExe.exe to the name 40 | of the jar file, but keeping the .exe extension. (See make.bat for this 41 | step.) Documentation for JavaExe can be obtained by downloading the 42 | whole program from the URL(s) above. 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/portmidi/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 | #include "finddefault.h" 18 | #include "pmlinuxalsa.h" 19 | 20 | PmDeviceID pm_default_input_device_id = -1; 21 | PmDeviceID pm_default_output_device_id = -1; 22 | 23 | void pm_init() 24 | { 25 | /* Note: it is not an error for PMALSA to fail to initialize. 26 | * It may be a design error that the client cannot query what subsystems 27 | * are working properly other than by looking at the list of available 28 | * devices. 29 | */ 30 | pm_linuxalsa_init(); 31 | // this is set when we return to Pm_Initialize, but we need it 32 | // now in order to (successfully) call Pm_CountDevices() 33 | pm_initialized = TRUE; 34 | pm_default_input_device_id = find_default_device( 35 | "/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, 36 | pm_default_input_device_id); 37 | pm_default_output_device_id = find_default_device( 38 | "/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, 39 | pm_default_output_device_id); 40 | } 41 | 42 | void pm_term(void) 43 | { 44 | pm_linuxalsa_term(); 45 | } 46 | 47 | PmDeviceID Pm_GetDefaultInputDeviceID() { 48 | Pm_Initialize(); 49 | return pm_default_input_device_id; 50 | } 51 | 52 | PmDeviceID Pm_GetDefaultOutputDeviceID() { 53 | Pm_Initialize(); 54 | return pm_default_output_device_id; 55 | } 56 | 57 | void *pm_alloc(size_t s) { return malloc(s); } 58 | 59 | void pm_free(void *ptr) { free(ptr); } 60 | 61 | -------------------------------------------------------------------------------- /bindings/java/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # pm_java 2 | 3 | if(UNIX) 4 | if(APPLE) 5 | # java not dealt with in CMake -- see pm_mac/pm_mac.xcodeproj 6 | else(APPLE) 7 | # linux 8 | set(JPORTMIDICLASS JPortMidi.class JPortMidiException.class 9 | JPortMidiApi.class) 10 | set(PMDEFAULTSCLASS PmDefaultsFrame.class PmDefaults.class) 11 | prepend_path(JPORTMIDICLASS2 jportmidi/ ${JPORTMIDICLASS}) 12 | prepend_path(PMDEFAULTSCLASS2 pmdefaults/ ${PMDEFAULTSCLASS}) 13 | set(PMDEFAULTS_ALL_CLASSES ${JPORTMIDICLASS2} ${PMDEFAULTSCLASS2}) 14 | # message(STATUS "PMDEFAULTS_ALL_CLASSES is " ${PMDEFAULTS_ALL_CLASSES}) 15 | add_custom_command(OUTPUT pmdefaults/PmDefaultsFrame.class 16 | COMMAND javac -classpath . pmdefaults/PmDefaultsFrame.java 17 | MAIN_DEPENDENCY pmdefaults/PmDefaultsFrame.java 18 | DEPENDS pmdefaults/PmDefaults.java 19 | WORKING_DIRECTORY .) 20 | add_custom_command(OUTPUT pmdefaults/PmDefaults.class 21 | COMMAND javac -classpath . pmdefaults/PmDefaults.java 22 | MAIN_DEPENDENCY pmdefaults/PmDefaults.java 23 | DEPENDS pmdefaults/PmDefaultsFrame.java 24 | WORKING_DIRECTORY .) 25 | add_custom_command(OUTPUT ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/pmdefaults.jar 26 | COMMAND cp pmdefaults/portmusic_logo.png . 27 | COMMAND jar cmf pmdefaults/manifest.txt pmdefaults.jar 28 | pmdefaults/*.class portmusic_logo.png jportmidi/*.class 29 | COMMAND chmod +x pmdefaults/pmdefaults 30 | COMMAND cp pmdefaults/pmdefaults ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 31 | COMMAND mv pmdefaults.jar ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 32 | COMMAND rm portmusic_logo.png 33 | MAIN_DEPENDENCY pmdefaults/PmDefaults.class 34 | DEPENDS ${PMDEFAULTS_ALL_CLASSES} 35 | WORKING_DIRECTORY .) 36 | add_custom_target(pmdefaults_target ALL 37 | DEPENDS ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/pmdefaults.jar) 38 | # message(STATUS "add_custom_target: pmdefaults.jar") 39 | 40 | # install the libraries (Linux only) 41 | INSTALL(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/pmdefaults.jar 42 | DESTINATION /usr/share/java) 43 | INSTALL(PROGRAMS ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/pmdefaults 44 | DESTINATION /usr/local/bin) 45 | endif(APPLE) 46 | endif(UNIX) 47 | # In windows, use pm_java/make.bat 48 | -------------------------------------------------------------------------------- /bindings/java/pmdefaults-setup-script.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | [Setup] 5 | ; NOTE: The value of AppId uniquely identifies this application. 6 | ; Do not use the same AppId value in installers for other applications. 7 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 8 | AppId={{5094958B-3CD7-4780-A883-69C9E5B95AEF} 9 | AppName=PmDefaults 10 | AppVerName=PmDefaults 11 | AppPublisher=Roger Dannenberg - Carnegie Mellon University 12 | AppPublisherURL=http://portmedia.sourceforge.net/ 13 | AppSupportURL=http://portmedia.sourceforge.net/ 14 | AppUpdatesURL=http://portmedia.sourceforge.net/ 15 | DefaultDirName={pf}\PmDefaults 16 | DefaultGroupName=PmDefaults 17 | LicenseFile=C:\Users\rbd\portmedia\portmidi\pm_java\win32\license.txt 18 | OutputBaseFilename=setup 19 | SetupIconFile=C:\Users\rbd\portmedia\portmidi\pm_java\pmdefaults\pmdefaults.ico 20 | Compression=lzma 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Tasks] 27 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 28 | Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 29 | 30 | [Files] 31 | Source: "C:\Users\rbd\portmedia\portmidi\pm_java\win32\pmdefaults.exe"; DestDir: "{app}"; Flags: ignoreversion 32 | Source: "C:\Users\rbd\portmedia\portmidi\pm_java\win32\pmdefaults.jar"; DestDir: "{app}"; Flags: ignoreversion 33 | Source: "C:\Users\rbd\portmedia\portmidi\pm_java\win32\pmjni.dll"; DestDir: "{app}"; Flags: ignoreversion 34 | Source: "C:\Users\rbd\portmedia\portmidi\pm_java\win32\license.txt"; DestDir: "{app}"; Flags: ignoreversion 35 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 36 | 37 | [Icons] 38 | Name: "{group}\PmDefaults"; Filename: "{app}\pmdefaults.exe" 39 | Name: "{commondesktop}\PmDefaults"; Filename: "{app}\pmdefaults.exe"; Tasks: desktopicon 40 | Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\PmDefaults"; Filename: "{app}\pmdefaults.exe"; Tasks: quicklaunchicon 41 | 42 | [Run] 43 | Filename: "{app}\pmdefaults.exe"; Description: "{cm:LaunchProgram,PmDefaults}"; Flags: nowait postinstall skipifsilent 44 | 45 | -------------------------------------------------------------------------------- /src/portmidi/mac/readbinaryplist.h: -------------------------------------------------------------------------------- 1 | /* readbinaryplist.h -- header to read preference files 2 | 3 | Roger B. Dannenberg, Jun 2008 4 | */ 5 | 6 | #include /* for uint8_t ... */ 7 | 8 | #ifndef TRUE 9 | #define TRUE 1 10 | #define FALSE 0 11 | #endif 12 | 13 | #define MAX_KEY_SIZE 256 14 | 15 | enum 16 | { 17 | // Object tags (high nybble) 18 | kTAG_SIMPLE = 0x00, // Null, true, false, filler, or invalid 19 | kTAG_INT = 0x10, 20 | kTAG_REAL = 0x20, 21 | kTAG_DATE = 0x30, 22 | kTAG_DATA = 0x40, 23 | kTAG_ASCIISTRING = 0x50, 24 | kTAG_UNICODESTRING = 0x60, 25 | kTAG_UID = 0x80, 26 | kTAG_ARRAY = 0xA0, 27 | kTAG_DICTIONARY = 0xD0, 28 | 29 | // "simple" object values 30 | kVALUE_NULL = 0x00, 31 | kVALUE_FALSE = 0x08, 32 | kVALUE_TRUE = 0x09, 33 | kVALUE_FILLER = 0x0F, 34 | 35 | kVALUE_FULLDATETAG = 0x33 // Dates are tagged with a whole byte. 36 | }; 37 | 38 | 39 | typedef struct pldata_struct { 40 | uint8_t *data; 41 | size_t len; 42 | } pldata_node, *pldata_ptr; 43 | 44 | 45 | typedef struct array_struct { 46 | struct value_struct **array; 47 | uint64_t length; 48 | } array_node, *array_ptr; 49 | 50 | 51 | // a dict_node is a list of pairs 52 | typedef struct dict_struct { 53 | struct value_struct *key; 54 | struct value_struct *value; 55 | struct dict_struct *next; 56 | } dict_node, *dict_ptr; 57 | 58 | 59 | // an value_node is a value with a tag telling the type 60 | typedef struct value_struct { 61 | int tag; 62 | union { 63 | int64_t integer; 64 | uint64_t uinteger; 65 | double real; 66 | char *string; 67 | pldata_ptr data; 68 | array_ptr array; 69 | struct dict_struct *dict; 70 | }; 71 | } value_node, *value_ptr; 72 | 73 | 74 | value_ptr bplist_read_file(char *filename); 75 | value_ptr bplist_read_user_pref(char *filename); 76 | value_ptr bplist_read_system_pref(char *filename); 77 | void bplist_free_data(); 78 | 79 | /*************** functions for accessing values ****************/ 80 | 81 | char *value_get_asciistring(value_ptr v); 82 | value_ptr value_dict_lookup_using_string(value_ptr v, char *key); 83 | value_ptr value_dict_lookup_using_path(value_ptr v, char *path); 84 | 85 | /*************** functions for debugging ***************/ 86 | 87 | void plist_print(value_ptr v); 88 | 89 | -------------------------------------------------------------------------------- /include/porttime.h: -------------------------------------------------------------------------------- 1 | /* 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 | /* Should there be a way to choose the source of time here? */ 9 | 10 | #ifdef WIN32 11 | #ifndef INT32_DEFINED 12 | // rather than having users install a special .h file for windows, 13 | // just put the required definitions inline here. portmidi.h uses 14 | // these too, so the definitions are (unfortunately) duplicated there 15 | typedef int int32_t; 16 | typedef unsigned int uint32_t; 17 | #define INT32_DEFINED 18 | #endif 19 | #else 20 | #include // needed for int32_t 21 | #endif 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #ifndef PMEXPORT 28 | #ifdef _WINDLL 29 | #define PMEXPORT __declspec(dllexport) 30 | #else 31 | #define PMEXPORT 32 | #endif 33 | #endif 34 | 35 | typedef enum { 36 | ptNoError = 0, /* success */ 37 | ptHostError = -10000, /* a system-specific error occurred */ 38 | ptAlreadyStarted, /* cannot start timer because it is already started */ 39 | ptAlreadyStopped, /* cannot stop timer because it is already stopped */ 40 | ptInsufficientMemory /* memory could not be allocated */ 41 | } PtError; 42 | 43 | 44 | typedef int32_t PtTimestamp; 45 | 46 | typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); 47 | 48 | /* 49 | Pt_Start() starts a real-time service. 50 | 51 | resolution is the timer resolution in ms. The time will advance every 52 | resolution ms. 53 | 54 | callback is a function pointer to be called every resolution ms. 55 | 56 | userData is passed to callback as a parameter. 57 | 58 | return value: 59 | Upon success, returns ptNoError. See PtError for other values. 60 | */ 61 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); 62 | 63 | /* 64 | Pt_Stop() stops the timer. 65 | 66 | return value: 67 | Upon success, returns ptNoError. See PtError for other values. 68 | */ 69 | PMEXPORT PtError Pt_Stop(); 70 | 71 | /* 72 | Pt_Started() returns true iff the timer is running. 73 | */ 74 | PMEXPORT int Pt_Started(); 75 | 76 | /* 77 | Pt_Time() returns the current time in ms. 78 | */ 79 | PMEXPORT PtTimestamp Pt_Time(); 80 | 81 | /* 82 | Pt_Sleep() pauses, allowing other threads to run. 83 | 84 | duration is the length of the pause in ms. The true duration 85 | of the pause may be rounded to the nearest or next clock tick 86 | as determined by resolution in Pt_Start(). 87 | */ 88 | PMEXPORT void Pt_Sleep(int32_t duration); 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | -------------------------------------------------------------------------------- /src/portmidi/linux/finddefault.c: -------------------------------------------------------------------------------- 1 | /* finddefault.c -- find_default_device() implementation 2 | Roger Dannenberg, Jan 2009 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "portmidi.h" 10 | #include "pmutil.h" 11 | #include "pminternal.h" 12 | #include "finddefault.h" 13 | 14 | #define STRING_MAX 256 15 | 16 | /* skip over spaces, return first non-space */ 17 | void skip_spaces(FILE *inf) 18 | { 19 | char c; 20 | while (isspace(c = getc(inf))) ; 21 | ungetc(c, inf); 22 | } 23 | 24 | /* trim leading spaces and match a string */ 25 | int match_string(FILE *inf, char *s) 26 | { 27 | skip_spaces(inf); 28 | while (*s && *s == getc(inf)) s++; 29 | return (*s == 0); 30 | } 31 | 32 | 33 | /* 34 | /* Parse preference files, find default device, search devices -- 35 | */ 36 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id) 37 | /* path -- the name of the preference we are searching for 38 | input -- true iff this is an input device 39 | id -- current default device id 40 | returns matching device id if found, otherwise id 41 | */ 42 | { 43 | static char *pref_2 = "/.java/.userPrefs/"; 44 | static char *pref_3 = "prefs.xml"; 45 | char *pref_1 = getenv("HOME"); 46 | char *full_name, *path_ptr; 47 | FILE *inf; 48 | int c, i; 49 | if (!pref_1) goto nopref; // cannot find preference file 50 | // full_name will be larger than necessary 51 | full_name = malloc(strlen(pref_1) + strlen(pref_2) + strlen(pref_3) + 52 | strlen(path) + 2); 53 | strcpy(full_name, pref_1); 54 | strcat(full_name, pref_2); 55 | // copy all but last path segment to full_name 56 | if (*path == '/') path++; // skip initial slash in path 57 | path_ptr = strrchr(path, '/'); 58 | if (path_ptr) { // copy up to slash after full_name 59 | path_ptr++; 60 | int offset = strlen(full_name); 61 | memcpy(full_name + offset, path, path_ptr - path); 62 | full_name[offset + path_ptr - path] = 0; // end of string 63 | } else { 64 | path_ptr = path; 65 | } 66 | strcat(full_name, pref_3); 67 | inf = fopen(full_name, "r"); 68 | if (!inf) goto nopref; // cannot open preference file 69 | // We're not going to build or link in a full XML parser. 70 | // Instead, find the path string and quoute. Then, look for 71 | // "value", "=", quote. Then get string up to quote. 72 | while ((c = getc(inf)) != EOF) { 73 | char pref_str[STRING_MAX]; 74 | if (c != '"') continue; // scan up to quote 75 | // look for quote string quote 76 | if (!match_string(inf, path_ptr)) continue; // path not found 77 | if (getc(inf) != '"') continue; // path not found, keep scanning 78 | if (!match_string(inf, "value")) goto nopref; // value not found 79 | if (!match_string(inf, "=")) goto nopref; // = not found 80 | if (!match_string(inf, "\"")) goto nopref; // quote not found 81 | // now read the value up to the close quote 82 | for (i = 0; i < STRING_MAX; i++) { 83 | if ((c = getc(inf)) == '"') break; 84 | pref_str[i] = c; 85 | } 86 | if (i == STRING_MAX) continue; // value too long, ignore 87 | pref_str[i] = 0; 88 | i = pm_find_default_device(pref_str, input); 89 | if (i != pmNoDevice) { 90 | id = i; 91 | } 92 | break; 93 | } 94 | nopref: 95 | return id; 96 | } 97 | -------------------------------------------------------------------------------- /bindings/common-lisp/test-no-cm.lisp: -------------------------------------------------------------------------------- 1 | ;; this is a half-baked sequence of PortMidi calls to test the interface 2 | ;; No calls to Common Music are made, hence test-no-cm.lisp 3 | 4 | ; setup cffi if it has not been done already 5 | (if (not (boundp '*clpath*)) 6 | (load "setup-pm.lisp")) 7 | 8 | (defun println (s) (print s) (terpri)) 9 | 10 | ;; initialize portmidi lib 11 | (pm:portmidi) 12 | ;; timer testing 13 | (pt:Start ) 14 | (pt:Started) 15 | (format t "time is ~A, type something~%" (pt:Time)) 16 | (read) 17 | (format t "time is ~A, type something~%" (pt:Time)) 18 | (read) 19 | (pt:Time) 20 | (format t "time is ~A, type something~%" (pt:Time)) 21 | 22 | ;; device testing 23 | (pm:CountDevices) 24 | (pprint (pm:GetDeviceInfo )) 25 | (defparameter inid (pm:GetDefaultInputDeviceID )) 26 | (pm:GetDeviceInfo inid) 27 | (defparameter outid (pm:GetDefaultOutputDeviceID )) 28 | (pm:GetDeviceInfo outid) 29 | ;; output testing 30 | (defparameter outid 4) ; 4 = my SimpleSynth 31 | (defparameter outdev (pm:OpenOutput outid 100 1000)) 32 | (pm:getDeviceInfo outid) ; :OPEN should be T 33 | ;; message tests 34 | (defun pm (m &optional (s t)) 35 | (format s "#" 36 | (ash (logand (pm:Message.status m) #xf0) -4) 37 | (logand (pm:Message.status m) #x0f) 38 | (pm:Message.data1 m) 39 | (pm:Message.data2 m))) 40 | (defparameter on (pm:message #b10010000 60 64)) 41 | (terpri) 42 | (pm on) 43 | (pm:Message.status on) 44 | (logand (ash (pm:Message.status on) -4) #x0f) 45 | (pm:Message.data1 on) 46 | (pm:Message.data2 on) 47 | (pm:WriteShort outdev (+ (pm:time) 100) on) 48 | (defparameter off (pm:message #b10000000 60 64)) 49 | (terpri) 50 | (pm off) 51 | (terpri) 52 | (println "type something for note off") 53 | (read) 54 | (pm:WriteShort outdev (+ (pm:time) 100) off) 55 | (println "type something to close output device") 56 | (read) 57 | (pm:Close outdev) 58 | ;; event buffer testing 59 | (defparameter buff (pm:EventBufferNew 8)) 60 | (loop for i below 8 for x = (pm:EventBufferElt buff i) 61 | ;; set buffer events 62 | do 63 | (pm:Event.message x (pm:message #b1001000 (+ 60 i) (+ 100 i))) 64 | (pm:Event.timestamp x (* 1000 i))) 65 | (loop for i below 8 for x = (pm:EventBufferElt buff i) 66 | ;; check buffer contents 67 | collect (list (pm:Event.timestamp x) 68 | (pm:Message.data1 (pm:Event.message x)) 69 | (pm:Message.data2 (pm:Event.message x)))) 70 | (pm:EventBufferFree buff) 71 | ;; input testing -- requires external midi keyboard 72 | (println (pm:GetDeviceInfo )) 73 | (defparameter inid 1) ; 1 = my external keyboard 74 | (defparameter indev (pm:OpenInput inid 256)) 75 | (pm:GetDeviceInfo inid) ; :OPEN should be T 76 | (pm:SetFilter indev pm:filt-realtime) ; ignore active sensing etc. 77 | (println "poll says:") 78 | (println (pm:Poll indev)) 79 | (println "play midi keyboard and type something") 80 | (read) 81 | ;; 82 | ;; ...play midi keyboard, then ... 83 | ;; 84 | (println "poll says") 85 | (println (pm:Poll indev)) 86 | (defparameter buff (pm:EventBufferNew 32)) 87 | (defparameter num (pm:Read indev buff 32)) 88 | (println "pm:Read gets") 89 | (println num) 90 | (println "input messages:") 91 | (pm:EventBufferMap (lambda (a b) b (terpri) (pm a)) 92 | buff num) 93 | (pm:Poll indev) 94 | 95 | (println "play keyboard, to stop, play middle-C") 96 | 97 | ;;; recv testing 98 | 99 | (defparameter pitch 0) 100 | (loop while (/= pitch 60) do 101 | (let ((n (pm:Read indev buff 1))) 102 | (cond ((= n 1) 103 | (pm:EventBufferMap 104 | (lambda (a b) 105 | b (pm a) (terpri) 106 | (setf pitch (pm:Message.data1 a))) 107 | buff n))))) 108 | 109 | (pm:EventBufferFree buff) 110 | (pm:Close indev) 111 | 112 | 113 | -------------------------------------------------------------------------------- /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 DRIVER_INFO NULL 10 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 11 | #define TIME_INFO NULL 12 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 13 | 14 | #define STRING_MAX 80 /* used for console input */ 15 | 16 | static void prompt_and_exit(void) 17 | { 18 | char line[STRING_MAX]; 19 | printf("type ENTER..."); 20 | fgets(line, STRING_MAX, stdin); 21 | /* this will clean up open ports: */ 22 | exit(-1); 23 | } 24 | 25 | 26 | static PmError checkerror(PmError err) 27 | { 28 | if (err == pmHostError) { 29 | /* it seems pointless to allocate memory and copy the string, 30 | * so I will do the work of Pm_GetHostErrorText directly 31 | */ 32 | char errmsg[80]; 33 | Pm_GetHostErrorText(errmsg, 80); 34 | printf("PortMidi found host error...\n %s\n", errmsg); 35 | prompt_and_exit(); 36 | } else if (err < 0) { 37 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 38 | prompt_and_exit(); 39 | } 40 | return err; 41 | } 42 | 43 | 44 | void main_test_input(int num) 45 | { 46 | PmStream *midi; 47 | PmError status, length; 48 | PmEvent buffer[1]; 49 | /* It is recommended to start timer before Midi; otherwise, PortMidi may 50 | start the timer with its (default) parameters 51 | */ 52 | TIME_START; 53 | 54 | /* create a virtual input device */ 55 | checkerror(Pm_CreateVirtualInput(&midi, "portmidi", NULL, DRIVER_INFO, 56 | INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO)); 57 | 58 | printf("Midi Input opened. Reading %d Midi messages...\n", num); 59 | Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); 60 | /* empty the buffer after setting filter, just in case anything 61 | got through */ 62 | while (Pm_Poll(midi)) { 63 | Pm_Read(midi, buffer, 1); 64 | } 65 | /* now start paying attention to messages */ 66 | int i = 0; /* count messages as they arrive */ 67 | while (i < num) { 68 | status = Pm_Poll(midi); 69 | if (status == TRUE) { 70 | length = Pm_Read(midi, buffer, 1); 71 | if (length > 0) { 72 | printf("Got message %d: time %ld, %2lx %2lx %2lx\n", 73 | i, 74 | (long) buffer[0].timestamp, 75 | (long) Pm_MessageStatus(buffer[0].message), 76 | (long) Pm_MessageData1(buffer[0].message), 77 | (long) Pm_MessageData2(buffer[0].message)); 78 | i++; 79 | } else { 80 | assert(0); 81 | } 82 | } 83 | } 84 | 85 | /* close device (this not explicitly needed in most implementations) */ 86 | printf("ready to close..."); 87 | 88 | Pm_Close(midi); 89 | printf("done closing..."); 90 | } 91 | 92 | 93 | void show_usage() 94 | { 95 | printf("Usage: recvvirtual [-h] [n]\n use -h for this message,\n" 96 | " n is number of message to wait for.\n"); 97 | exit(0); 98 | } 99 | 100 | 101 | int main(int argc, char *argv[]) 102 | { 103 | char line[STRING_MAX]; 104 | int num = 10; 105 | 106 | if (argc > 2) { 107 | show_usage(); 108 | } else if (argc == 2) { 109 | if (strcmp(argv[1], "-h") == 0) { 110 | show_usage(); 111 | } else { 112 | num = atoi(argv[1]); 113 | if (num <= 0) { 114 | show_usage(); 115 | } 116 | } 117 | } 118 | 119 | main_test_input(num); 120 | 121 | printf("finished portMidi test...type ENTER to quit..."); 122 | fgets(line, STRING_MAX, stdin); 123 | return 0; 124 | } 125 | -------------------------------------------------------------------------------- /bindings/common-lisp/README_CL.txt: -------------------------------------------------------------------------------- 1 | README_CL.txt for PortMidi 2 | Roger B. Dannenberg 3 | 17 Jan 2007 4 | 5 | This is a Common Lisp interface to PortMidi. 6 | 7 | On Mac OSX, you need to build PortMidi as a dynamic link library 8 | before you can use PortMidi from Common Lisp. 9 | 10 | You can build PortMidi as a dynamic link library by running this: 11 | 12 | cd portmidi 13 | make -F pm_mac/Makefile.osx install-with-xcode 14 | 15 | This is just a shortcut for: 16 | 17 | cd portmidi/pm_mac 18 | sudo xcodebuild -project pm_mac.xcodeproj -configuration Deployment install DSTROOT=/ 19 | 20 | You can check the file and the architecture for which it is built using: 21 | file /usr/local/lib/libportmidi.dylib 22 | 23 | If you've done this install of portmidi, then you should also have 24 | /usr/local/include/portmidi.h 25 | This will be necessary to successfully build the cffi interface below. 26 | 27 | To test PortMidi with Common Lisp, I (RBD) am using SBCL, which I 28 | downloaded from http://prdownloads.sourceforge.net/sbcl. Currently, I use 29 | sbcl-0.9.17-x86-darwin-binary.tar.bz2 30 | To install this, I unpacked it by just double-clicking in the finder. Then, 31 | from a command window, I became root using "sudo sh", and then typed: 32 | # INSTALL_ROOT=/usr/local 33 | # sh install.sh 34 | # exit 35 | 36 | I also downloaded cffi-061012.tar.gz from 37 | http://common-lisp.net/project/cffi/tarballs/?M=D 38 | 39 | To compile cffi, use the following, where "/Lisp/cffi/" is replaced by 40 | the actual directory of cffi, e.g. 41 | "/Users/rbd/sbcl-0.9.17-x86-darwin/cffi-061012": 42 | 43 | % sbcl 44 | * (require 'asdf) 45 | * (push "/Lisp/cffi/" asdf:*central-registry*) 46 | * (asdf:oos 'asdf:load-op :cffi) 47 | * (quit) 48 | 49 | Download Common Music's portmidi module from cvs and build the c side: 50 | (Replace "/Lisp" with your lisp directory, e.g. 51 | "/Users/rbd/sbcl-0.9.17-x86-darwin". These cvs commands will create 52 | a new directory, portmidi.) 53 | 54 | % cd /Lisp 55 | % export CVSROOT=:pserver:anonymous@commonmusic.cvs.sourceforge.net:/cvsroot/commonmusic 56 | % cvs login # press Return at password prompt 57 | % cvs checkout portmidi 58 | % cd portmidi 59 | % ./configure 60 | % make 61 | % cd .. 62 | 63 | Now compile/load the portmidi module just like cffi. Again, change 64 | "/Lisp/cffi/" and "/Lisp/portmidi" to correspond to your local file system. 65 | (Note that /Lisp becomes your lisp directory, and "cffi" becomes your 66 | cffi folder name, e.g. "cffi-061012". 67 | 68 | % sbcl 69 | * (require 'asdf) 70 | * (push "/Lisp/cffi/" asdf:*central-registry*) 71 | * (asdf:oos 'asdf:load-op :cffi) 72 | * (push "/Lisp/portmidi/" asdf:*central-registry*) 73 | * (asdf:oos 'asdf:load-op :portmidi) 74 | 75 | Look in the file /Lisp/portmidi/test.lisp for a test of the lisp interface to 76 | portmidi. For example, while still running sbcl: 77 | 78 | * (pm:portmidi) ; initialize portmidi 79 | * (pt:start) ; start time 80 | * (pt:time) ; get time 81 | * (pprint (pm:GetDeviceInfo)) ; get list of devices 82 | ((:ID 0 :NAME "IAC Driver Bus 1" :TYPE :INPUT :OPEN NIL) 83 | (:ID 1 :NAME "IAC Driver Bus 1" :TYPE :OUTPUT :OPEN NIL)) 84 | 85 | Notice that test.lisp assumes MIDI input devices are connected 86 | and uses some hard-wired device numbers, so it may not run 87 | as is without error. 88 | 89 | Since test.lisp uses some Common Music calls, I (RBD) wrote a 90 | simpler test, test-no-cm.lisp, which is in the same folder as 91 | this (README_CL.txt) file. To use it, first check that the 92 | values for outid (4) and inid (1) actually match PortMidi device 93 | id's for output and input devices, and make sure the input 94 | device is a keyboard that can generate a middle-C -- otherwise 95 | the program will hang waiting for input. Run sbcl from this 96 | pm_cl folder, and type: 97 | 98 | (load "test-no-cm.lisp") 99 | 100 | The program pauses frequently by calling (READ), so you 101 | should type t or something, then to continue. 102 | 103 | 104 | (Thanks to Leigh Smith and Rick Taube) 105 | -------------------------------------------------------------------------------- /src/portmidi/windows/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 | #ifdef DEBUG 32 | #define STRING_MAX 80 33 | { 34 | char line[STRING_MAX]; 35 | printf("Type ENTER...\n"); 36 | /* note, w/o this prompting, client console application can not see one 37 | of its errors before closing. */ 38 | fgets(line, STRING_MAX, stdin); 39 | } 40 | #endif 41 | } 42 | 43 | 44 | /* pm_init is the windows-dependent initialization.*/ 45 | void pm_init(void) 46 | { 47 | atexit(pm_exit); 48 | #ifdef DEBUG 49 | printf("registered pm_exit with atexit()\n"); 50 | #endif 51 | pm_winmm_init(); 52 | /* initialize other APIs (DirectX?) here */ 53 | } 54 | 55 | 56 | void pm_term(void) { 57 | pm_winmm_term(); 58 | } 59 | 60 | 61 | static PmDeviceID pm_get_default_device_id(int is_input, char *key) { 62 | HKEY hkey; 63 | #define PATTERN_MAX 256 64 | char pattern[PATTERN_MAX]; 65 | long pattern_max = PATTERN_MAX; 66 | DWORD dwType; 67 | /* Find first input or device -- this is the default. */ 68 | PmDeviceID id = pmNoDevice; 69 | int i, j; 70 | Pm_Initialize(); /* make sure descriptors exist! */ 71 | for (i = 0; i < pm_descriptor_index; i++) { 72 | if (descriptors[i].pub.input == is_input) { 73 | id = i; 74 | break; 75 | } 76 | } 77 | /* Look in registry for a default device name pattern. */ 78 | if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software", 0, KEY_READ, &hkey) != 79 | ERROR_SUCCESS) { 80 | return id; 81 | } 82 | if (RegOpenKeyEx(hkey, "JavaSoft", 0, KEY_READ, &hkey) != 83 | ERROR_SUCCESS) { 84 | return id; 85 | } 86 | if (RegOpenKeyEx(hkey, "Prefs", 0, KEY_READ, &hkey) != 87 | ERROR_SUCCESS) { 88 | return id; 89 | } 90 | if (RegOpenKeyEx(hkey, "/Port/Midi", 0, KEY_READ, &hkey) != 91 | ERROR_SUCCESS) { 92 | return id; 93 | } 94 | if (RegQueryValueEx(hkey, key, NULL, &dwType, (BYTE *) pattern, 95 | (DWORD *) &pattern_max) != 96 | ERROR_SUCCESS) { 97 | return id; 98 | } 99 | 100 | /* decode pattern: upper case encoded with "/" prefix */ 101 | i = j = 0; 102 | while (pattern[i]) { 103 | if (pattern[i] == '/' && pattern[i + 1]) { 104 | pattern[j++] = toupper(pattern[++i]); 105 | } else { 106 | pattern[j++] = tolower(pattern[i]); 107 | } 108 | i++; 109 | } 110 | pattern[j] = 0; /* end of string */ 111 | 112 | /* now pattern is the string from the registry; search for match */ 113 | i = pm_find_default_device(pattern, is_input); 114 | if (i != pmNoDevice) { 115 | id = i; 116 | } 117 | return id; 118 | } 119 | 120 | 121 | PmDeviceID Pm_GetDefaultInputDeviceID() { 122 | return pm_get_default_device_id(TRUE, 123 | "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E"); 124 | } 125 | 126 | 127 | PmDeviceID Pm_GetDefaultOutputDeviceID() { 128 | return pm_get_default_device_id(FALSE, 129 | "/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"); 130 | } 131 | 132 | 133 | #include "stdio.h" 134 | 135 | void *pm_alloc(size_t s) { 136 | return malloc(s); 137 | } 138 | 139 | 140 | void pm_free(void *ptr) { 141 | free(ptr); 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/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 | 13 | #include "porttime.h" 14 | #include "sys/time.h" 15 | #include "pthread.h" 16 | 17 | #ifndef NSEC_PER_MSEC 18 | #define NSEC_PER_MSEC 1000000 19 | #endif 20 | #define THREAD_IMPORTANCE 30 21 | 22 | static int time_started_flag = FALSE; 23 | static UInt64 start_time; 24 | static pthread_t pt_thread_pid; 25 | 26 | /* note that this is static data -- we only need one copy */ 27 | typedef struct { 28 | int id; 29 | int resolution; 30 | PtCallback *callback; 31 | void *userData; 32 | } pt_callback_parameters; 33 | 34 | static int pt_callback_proc_id = 0; 35 | 36 | static void *Pt_CallbackProc(void *p) 37 | { 38 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; 39 | int mytime = 1; 40 | 41 | kern_return_t error; 42 | thread_extended_policy_data_t extendedPolicy; 43 | thread_precedence_policy_data_t precedencePolicy; 44 | 45 | extendedPolicy.timeshare = 0; 46 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, 47 | (thread_policy_t)&extendedPolicy, 48 | THREAD_EXTENDED_POLICY_COUNT); 49 | if (error != KERN_SUCCESS) { 50 | mach_error("Couldn't set thread timeshare policy", error); 51 | } 52 | 53 | precedencePolicy.importance = THREAD_IMPORTANCE; 54 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, 55 | (thread_policy_t)&precedencePolicy, 56 | THREAD_PRECEDENCE_POLICY_COUNT); 57 | if (error != KERN_SUCCESS) { 58 | mach_error("Couldn't set thread precedence policy", error); 59 | } 60 | 61 | 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, parameters->id); */ 64 | while (pt_callback_proc_id == parameters->id) { 65 | /* wait for a multiple of resolution ms */ 66 | UInt64 wait_time; 67 | int delay = mytime++ * parameters->resolution - Pt_Time(); 68 | PtTimestamp timestamp; 69 | if (delay < 0) delay = 0; 70 | wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); 71 | wait_time += AudioGetCurrentHostTime(); 72 | error = mach_wait_until(wait_time); 73 | timestamp = Pt_Time(); 74 | (*(parameters->callback))(timestamp, parameters->userData); 75 | } 76 | free(parameters); 77 | return NULL; 78 | } 79 | 80 | 81 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 82 | { 83 | if (time_started_flag) return ptAlreadyStarted; 84 | start_time = AudioGetCurrentHostTime(); 85 | 86 | if (callback) { 87 | int res; 88 | pt_callback_parameters *parms; 89 | 90 | parms = (pt_callback_parameters *) 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, Pt_CallbackProc, parms); 97 | if (res != 0) return ptHostError; 98 | } 99 | 100 | time_started_flag = TRUE; 101 | return ptNoError; 102 | } 103 | 104 | 105 | PtError Pt_Stop() 106 | { 107 | /* printf("Pt_Stop called\n"); */ 108 | pt_callback_proc_id++; 109 | pthread_join(pt_thread_pid, NULL); 110 | time_started_flag = FALSE; 111 | return ptNoError; 112 | } 113 | 114 | 115 | int Pt_Started() 116 | { 117 | return time_started_flag; 118 | } 119 | 120 | 121 | PtTimestamp Pt_Time() 122 | { 123 | UInt64 clock_time, nsec_time; 124 | clock_time = AudioGetCurrentHostTime() - start_time; 125 | nsec_time = AudioConvertHostTimeToNanos(clock_time); 126 | return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); 127 | } 128 | 129 | 130 | void Pt_Sleep(int32_t duration) 131 | { 132 | usleep(duration * 1000); 133 | } 134 | -------------------------------------------------------------------------------- /src/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() 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() 125 | { 126 | return time_started_flag; 127 | } 128 | 129 | 130 | PtTimestamp Pt_Time() 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 | -------------------------------------------------------------------------------- /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 DRIVER_INFO NULL 15 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 16 | #define TIME_INFO NULL 17 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 18 | 19 | #define STRING_MAX 80 /* used for console input */ 20 | 21 | int latency = 0; 22 | 23 | static void prompt_and_exit(void) 24 | { 25 | char line[STRING_MAX]; 26 | printf("type ENTER..."); 27 | fgets(line, STRING_MAX, stdin); 28 | /* this will clean up open ports: */ 29 | exit(-1); 30 | } 31 | 32 | 33 | static PmError checkerror(PmError err) 34 | { 35 | if (err == pmHostError) { 36 | /* it seems pointless to allocate memory and copy the string, 37 | * so I will do the work of Pm_GetHostErrorText directly 38 | */ 39 | char errmsg[80]; 40 | Pm_GetHostErrorText(errmsg, 80); 41 | printf("PortMidi found host error...\n %s\n", errmsg); 42 | prompt_and_exit(); 43 | } else if (err < 0) { 44 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 45 | prompt_and_exit(); 46 | } 47 | return err; 48 | } 49 | 50 | 51 | void wait_until(PmTimestamp when) 52 | { 53 | PtTimestamp now = TIME_PROC(TIME_INFO); 54 | if (when > now) { 55 | Pt_Sleep(when - now); 56 | } 57 | } 58 | 59 | 60 | void main_test_output(int num) 61 | { 62 | PmStream *midi; 63 | int32_t next_time; 64 | PmEvent buffer[1]; 65 | PmTimestamp timestamp; 66 | int pitch = 60; 67 | char line[STRING_MAX]; 68 | 69 | /* It is recommended to start timer before Midi; otherwise, PortMidi may 70 | start the timer with its (default) parameters 71 | */ 72 | TIME_START; 73 | 74 | /* create a virtual output device */ 75 | checkerror(Pm_CreateVirtualOutput(&midi, "portmidi", NULL, DRIVER_INFO, 76 | OUTPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO, latency)); 77 | 78 | printf("Midi Output Virtual Device \"portmidi\" created.\n"); 79 | printf("Type ENTER to send messages: "); 80 | fgets(line, STRING_MAX, stdin); 81 | 82 | buffer[0].timestamp = TIME_PROC(TIME_INFO); 83 | #define PROGRAM 0 84 | buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); 85 | Pm_Write(midi, buffer, 1); 86 | next_time = TIME_PROC(TIME_INFO) + 1000; /* wait 1s */ 87 | while (num > 0) { 88 | wait_until(next_time); 89 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100)); 90 | printf("Note On pitch %d\n", pitch); 91 | num--; 92 | next_time += 500; 93 | 94 | wait_until(next_time); 95 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0)); 96 | printf("Note Off pitch %d\n", pitch); 97 | num--; 98 | pitch = (pitch + 1) % 12 + 60; 99 | next_time += 500; 100 | } 101 | 102 | /* close device (this not explicitly needed in most implementations) */ 103 | printf("ready to close..."); 104 | 105 | Pm_Close(midi); 106 | printf("done closing..."); 107 | } 108 | 109 | 110 | void show_usage() 111 | { 112 | printf("Usage: sendvirtual [-h] [-l latency-in-ms] [n]\n" 113 | " -h for this message,\n" 114 | " -l ms designates latency for precise timing (default 0),\n" 115 | " n is number of message to send.\n" 116 | "sends change program to 1, then one note per second with 0.5s on,\n" 117 | "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n" 118 | "precise timing (see PortMidi documentation).\n"); 119 | exit(0); 120 | } 121 | 122 | 123 | int main(int argc, char *argv[]) 124 | { 125 | char line[STRING_MAX]; 126 | int num = 10; 127 | int i; 128 | for (i = 1; i < argc; i++) { 129 | if (strcmp(argv[i], "-h") == 0) { 130 | show_usage(); 131 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { 132 | i = i + 1; 133 | latency = atoi(argv[i]); 134 | printf("Latency will be %d\n", latency); 135 | } else { 136 | num = atoi(argv[1]); 137 | if (num <= 0) { 138 | show_usage(); 139 | } 140 | printf("Sending %d messages.\n", num); 141 | } 142 | } 143 | 144 | main_test_output(num); 145 | 146 | printf("finished sendvirtual test...type ENTER to quit..."); 147 | fgets(line, STRING_MAX, stdin); 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /src/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 "sys/time.h" 33 | #include "sys/resource.h" 34 | #include "sys/timeb.h" 35 | #include "pthread.h" 36 | 37 | #define TRUE 1 38 | #define FALSE 0 39 | 40 | static int time_started_flag = FALSE; 41 | static struct timeb time_offset = {0, 0, 0, 0}; 42 | static pthread_t pt_thread_pid; 43 | static int pt_thread_created = FALSE; 44 | 45 | /* note that this is static data -- we only need one copy */ 46 | typedef struct { 47 | int id; 48 | int resolution; 49 | PtCallback *callback; 50 | void *userData; 51 | } pt_callback_parameters; 52 | 53 | static int pt_callback_proc_id = 0; 54 | 55 | static void *Pt_CallbackProc(void *p) 56 | { 57 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; 58 | int mytime = 1; 59 | /* to kill a process, just increment the pt_callback_proc_id */ 60 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, 61 | parameters->id); */ 62 | if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); 63 | while (pt_callback_proc_id == parameters->id) { 64 | /* wait for a multiple of resolution ms */ 65 | struct timeval timeout; 66 | int delay = mytime++ * parameters->resolution - Pt_Time(); 67 | if (delay < 0) delay = 0; 68 | timeout.tv_sec = 0; 69 | timeout.tv_usec = delay * 1000; 70 | select(0, NULL, NULL, NULL, &timeout); 71 | (*(parameters->callback))(Pt_Time(), parameters->userData); 72 | } 73 | /* printf("Pt_CallbackProc exiting\n"); */ 74 | // free(parameters); 75 | return NULL; 76 | } 77 | 78 | 79 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) 80 | { 81 | if (time_started_flag) return ptNoError; 82 | ftime(&time_offset); /* need this set before process runs */ 83 | if (callback) { 84 | int res; 85 | pt_callback_parameters *parms = (pt_callback_parameters *) 86 | malloc(sizeof(pt_callback_parameters)); 87 | if (!parms) return ptInsufficientMemory; 88 | parms->id = pt_callback_proc_id; 89 | parms->resolution = resolution; 90 | parms->callback = callback; 91 | parms->userData = userData; 92 | res = pthread_create(&pt_thread_pid, NULL, 93 | Pt_CallbackProc, parms); 94 | if (res != 0) return ptHostError; 95 | pt_thread_created = TRUE; 96 | } 97 | time_started_flag = TRUE; 98 | return ptNoError; 99 | } 100 | 101 | 102 | PtError Pt_Stop() 103 | { 104 | /* printf("Pt_Stop called\n"); */ 105 | pt_callback_proc_id++; 106 | if (pt_thread_created) { 107 | pthread_join(pt_thread_pid, NULL); 108 | pt_thread_created = FALSE; 109 | } 110 | time_started_flag = FALSE; 111 | return ptNoError; 112 | } 113 | 114 | 115 | int Pt_Started() 116 | { 117 | return time_started_flag; 118 | } 119 | 120 | 121 | PtTimestamp Pt_Time() 122 | { 123 | long seconds, milliseconds; 124 | struct timeb now; 125 | ftime(&now); 126 | seconds = now.time - time_offset.time; 127 | milliseconds = now.millitm - time_offset.millitm; 128 | return seconds * 1000 + milliseconds; 129 | } 130 | 131 | 132 | void Pt_Sleep(int32_t duration) 133 | { 134 | usleep(duration * 1000); 135 | } 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /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() 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 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(PortMidi VERSION 236) 3 | 4 | set(SOVERSION 1) 5 | set(LIBRARY_VERSION 1.0.${CMAKE_PROJECT_VERSION}) 6 | 7 | option(BUILD_SHARED_LIBS "Build dynamic library" ON) 8 | 9 | add_library(PortTime src/porttime/porttime.c) 10 | target_include_directories(PortTime PUBLIC 11 | $ 12 | $ 13 | ) 14 | set_target_properties(PortTime PROPERTIES OUTPUT_NAME porttime) 15 | set_target_properties(PortTime PROPERTIES 16 | WINDOWS_EXPORT_ALL_SYMBOLS TRUE 17 | ) 18 | 19 | add_library(PortMidi 20 | src/portmidi/common/portmidi.c 21 | src/portmidi/common/pmutil.c 22 | ) 23 | target_link_libraries(PortMidi PUBLIC PortTime) 24 | set_target_properties(PortMidi PROPERTIES OUTPUT_NAME portmidi) 25 | set_target_properties(PortMidi PROPERTIES 26 | WINDOWS_EXPORT_ALL_SYMBOLS TRUE 27 | ) 28 | target_include_directories(PortMidi PUBLIC 29 | $ 30 | $ 31 | $ 32 | ) 33 | target_include_directories(PortMidi PRIVATE pm_common porttime) 34 | 35 | option(DEBUGMESSAGES "Print debugging messages (not implemented on Linux)" OFF) 36 | if(DEBUGMESSAGES) 37 | target_compile_definitions(PortMidi PRIVATE DEBUG) 38 | endif() 39 | 40 | option(CHECKERRORS 41 | "Insert a check for error return values at the end of each PortMidi function. 42 | If an error is encountered, a text message is printed using printf(), the user 43 | is asked to type ENTER, and then exit(-1) is called to clean up and terminate 44 | the program. 45 | 46 | You should not use PM_CHECK_ERRORS if printf() does not work (e.g. this is not 47 | a console application under Windows, or there is no visible console on some 48 | other OS), and you should not use CHECKERRORS if you intend to recover from 49 | errors rather than abruptly terminate the program." OFF) 50 | if(CHECKERRORS) 51 | target_compile_definitions(PortMidi PRIVATE PM_CHECK_ERRORS) 52 | endif() 53 | 54 | # FreeBSD has a reimplementation of alsalib, so don't restrict the ALSA backend strictly to Linux. 55 | if(UNIX AND NOT APPLE) 56 | find_package(ALSA REQUIRED) 57 | target_sources(PortTime PRIVATE src/porttime/ptlinux.c) 58 | target_sources(PortMidi PRIVATE 59 | src/portmidi/linux/pmlinux.c 60 | src/portmidi/linux/pmlinuxalsa.c 61 | src/portmidi/linux/finddefault.c) 62 | target_link_libraries(PortMidi PRIVATE ALSA::ALSA) 63 | elseif(APPLE) 64 | find_library(COREAUDIO_LIBRARY CoreAudio REQUIRED) 65 | find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED) 66 | find_library(COREMIDI_LIBRARY CoreMidi REQUIRED) 67 | find_library(CORESERVICES_LIBRARY CoreServices REQUIRED) 68 | 69 | target_sources(PortTime PRIVATE src/porttime/ptmacosx_mach.c) 70 | target_link_libraries(PortTime PRIVATE ${COREAUDIO_LIBRARY} ${COREFOUNDATION_LIBRARY}) 71 | 72 | target_sources(PortMidi PRIVATE 73 | src/portmidi/mac/pmmac.c 74 | src/portmidi/mac/pmmacosxcm.c 75 | src/portmidi/mac/finddefault.c 76 | src/portmidi/mac/readbinaryplist.c) 77 | target_link_libraries(PortMidi PRIVATE ${COREAUDIO_LIBRARY} ${COREFOUNDATION_LIBRARY} ${COREMIDI_LIBRARY} ${CORESERVICES_LIBRARY}) 78 | elseif(WIN32) 79 | target_sources(PortTime PRIVATE src/porttime/ptwinmm.c) 80 | target_link_libraries(PortTime PRIVATE winmm) 81 | target_sources(PortMidi PRIVATE 82 | src/portmidi/windows/pmwin.c 83 | src/portmidi/windows/pmwinmm.c) 84 | target_link_libraries(PortMidi PRIVATE winmm) 85 | else() 86 | message(FATAL_ERROR "Operating system not supported.") 87 | endif() 88 | 89 | set_target_properties(PortMidi PROPERTIES 90 | SOVERSION ${SOVERSION} 91 | VERSION ${LIBRARY_VERSION} 92 | ) 93 | 94 | set_target_properties(PortTime PROPERTIES 95 | SOVERSION ${SOVERSION} 96 | VERSION ${LIBRARY_VERSION} 97 | ) 98 | 99 | include(GNUInstallDirs) 100 | include(CMakePackageConfigHelpers) 101 | 102 | # Library 103 | install(TARGETS PortMidi PortTime 104 | EXPORT PortMidiTargets 105 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 106 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 107 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 108 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 109 | ) 110 | 111 | # Headers 112 | install(FILES 113 | include/pmutil.h 114 | include/portmidi.h 115 | include/porttime.h 116 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 117 | 118 | # pkgconfig 119 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/portmidi.pc.in 120 | ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc @ONLY) 121 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") 122 | 123 | # CMake config 124 | set(PORTMIDI_INSTALL_CMAKEDIR "lib/cmake/PortMidi") 125 | install( 126 | EXPORT PortMidiTargets 127 | FILE PortMidiTargets.cmake 128 | NAMESPACE PortMidi:: 129 | DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 130 | ) 131 | configure_package_config_file(packaging/PortMidiConfig.cmake.in 132 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake" 133 | INSTALL_DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 134 | ) 135 | write_basic_package_version_file( 136 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake" 137 | VERSION "${CMAKE_PROJECT_VERSION}" 138 | COMPATIBILITY SameMajorVersion 139 | ) 140 | install( 141 | FILES 142 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake" 143 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake" 144 | DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" 145 | ) 146 | 147 | option(BUILD_TESTING "Include test projects" OFF) 148 | if(BUILD_TESTING) 149 | add_subdirectory(test) 150 | endif() 151 | -------------------------------------------------------------------------------- /src/portmidi/linux/README_LINUX.txt: -------------------------------------------------------------------------------- 1 | README_LINUX.txt for PortMidi 2 | Roger Dannenberg 3 | 6 Dec 2012 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 | To make PortMidi, you need cmake and the Java SDK. 16 | Go back up to the portmidi directory and type: 17 | 18 | ccmake . 19 | 20 | Type 'c' (configure) and then 'g' (generate). You may have 21 | to manually set JAVA_INCLUDE_PATH and JAVA_JVM_LIBRARY 22 | by typing 't' (toggle to advanced mode) and using the 23 | editor to change the fields. You can find possible values 24 | for JAVA_INCLUDE_PATH by typing "locate jni.h", and for 25 | JAVA_JVM_LIBRARY by typing locate libjvm". 26 | 27 | Alternatively, try 28 | export JAVA_HOME=/usr/lib/jvm/java-8-oracle/ 29 | before running ccmake ., and note that your JAVA_HOME 30 | may be different. 31 | 32 | You also need JAVA_INCLUDE_PATH2, but this will normally 33 | be set automatically after you set JAVA_INCLUDE_PATH and 34 | run "configure" (type "c" to ccmake). Normally, 35 | JAVA_INCLUDE_PATH2 is the linux subdirectory within 36 | JAVA_INCLUDE_PATH. 37 | 38 | Notice that the CMAKE_BUILD_TYPE can be Debug or Release. 39 | Stick with Release if you are not debugging. 40 | 41 | After successfully generating make files with ccmake, you 42 | can run make: 43 | 44 | make 45 | 46 | The Makefile will build all test programs and the portmidi 47 | library. For experimental software, 48 | especially programs running from the command line, we 49 | recommend using the Debug version -- it will terminate your 50 | program and print a helpful message if any PortMidi 51 | function returns an error code. (Released software should 52 | check for error codes and handle them, but for quick, 53 | non-critical projects, the automatic "print and die" 54 | handling can save some work.) 55 | 56 | THE pmdefaults PROGRAM 57 | 58 | You should install pmdefaults. It provides a graphical interface 59 | for selecting default MIDI IN and OUT devices so that you don't 60 | have to build device selection interfaces into all your programs 61 | and so users have a single place to set a preference. 62 | 63 | Follow the instructions above to run ccmake, making sure that 64 | CMAKE_BUILD_TYPE is Release. Run make as described above. Then: 65 | 66 | sudo make install 67 | 68 | This will install PortMidi libraries and the pmdefault program. 69 | You must alos have the environment variable LD_LIBRARY_PATH set 70 | to include /usr/local/lib (where libpmjni.so is installed). 71 | 72 | Now, you can run pmdefault. 73 | 74 | 75 | SETTING LD_LIBRARY_PATH 76 | 77 | pmdefaults will not work unless LD_LIBRARY_PATH includes a 78 | directory (normally /usr/local/lib) containing libpmjni.so, 79 | installed as described above. 80 | 81 | To set LD_LIBRARY_PATH, you might want to add this to your 82 | ~/.profile (if you use the bash shell): 83 | 84 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 85 | export LD_LIBRARY_PATH 86 | 87 | 88 | A NOTE ABOUT AMD64: 89 | 90 | When compiling portmidi under linux on an AMD64, I had to add the -fPIC 91 | flag to the gcc flags. 92 | 93 | Reason: when trying to build John Harrison's pyPortMidi gcc bailed out 94 | with this error: 95 | 96 | ./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 97 | ./linux/libportmidi.a: could not read symbols: Bad value 98 | collect2: ld returned 1 exit status 99 | error: command 'gcc' failed with exit status 1 100 | 101 | What they said: 102 | http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3 103 | On certain architectures (AMD64 amongst them), shared libraries *must* 104 | be "PIC-enabled". 105 | 106 | USING AUTOCONF 107 | 108 | Audacity (the audio editor ) uses an autoconf-based build system, and 109 | the files are also included in the PortMidi download. The associated 110 | files are tested only in the context of building Audacity, but if you 111 | really prefer autoconf to cmake or want to include portmidi in another 112 | autoconf-based project (like Audacity), maybe this will be useful. 113 | 114 | The files are: configure.ac, portmidi-uninstalled.pc.in, portmidi.pc.in, 115 | and everything in autotools/. 116 | 117 | To build from these files, run aclocal, which will produce 118 | aclocal.m4 and autom4te.cache 119 | 120 | Run autoconf, which will produce 121 | configure 122 | 123 | Run automake, which will produce 124 | Makefile.in 125 | 126 | Now that you have a configure and Makefile.in, 127 | continue with the next section. 128 | 129 | 130 | USING CONFIGURE 131 | 132 | Run ./configure to produce: 133 | config.status 134 | Makefile 135 | portmidi.pc 136 | portmidi-uninstalled.pc 137 | 138 | 139 | Finally, run make to create 140 | 141 | 142 | 143 | CHANGELOG 144 | 145 | 6-dec-2012 Roger B. Dannenberg 146 | Copied notes on Autoconf from Audacity sources 147 | 148 | 22-jan-2010 Roger B. Dannenberg 149 | Updated instructions about Java paths 150 | 151 | 14-oct-2009 Roger B. Dannenberg 152 | Using CMake now for building and configuration 153 | 154 | 29-aug-2006 Roger B. Dannenberg 155 | Fixed PortTime to join with time thread for clean exit. 156 | 157 | 28-aug-2006 Roger B. Dannenberg 158 | Updated this documentation. 159 | 160 | 08-Jun-2004 Roger B. Dannenberg 161 | Updated code to use new system abstraction. 162 | 163 | 12-Apr-2003 Roger B. Dannenberg 164 | Fixed pm_test/test.c to filter clocks and active messages. 165 | Integrated changes from Clemens Ladisch: 166 | cleaned up pmlinuxalsa.c 167 | record timestamp on sysex input 168 | deallocate some resources previously left open 169 | -------------------------------------------------------------------------------- /bindings/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 | -------------------------------------------------------------------------------- /bindings/python/setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | 5 | from distutils.core import setup, Command 6 | from distutils.extension import Extension 7 | try: 8 | from Cython.Distutils import build_ext 9 | except ImportError: 10 | logging.warn("Cython is preferred over pyrex for python3 compatibility.") 11 | from Pyrex.Distutils import build_ext 12 | 13 | 14 | 15 | 16 | 17 | DESCRIPTION = open('README_PYTHON.txt').read() 18 | CHANGES = open('CHANGES.txt').read() 19 | TODO = open('TODO.txt').read() 20 | 21 | EXTRAS = {} 22 | 23 | long_description = DESCRIPTION + CHANGES + TODO 24 | #import sys 25 | #if "checkdocs" in sys.argv: 26 | # print long_description 27 | 28 | 29 | METADATA = { 30 | 'name': 'pyportmidi', 31 | 'version': '0.0.7', 32 | 'license': 'MIT License', 33 | 'url': 'http://pypi.python.org/pyportmidi/', 34 | 'author': 'John Harrison, Roger B. Dannenberg, Rene Dudfield, others...', 35 | 'author_email': 'renesd@gmail.com', 36 | 'maintainer': 'Rene Dudfield', 37 | 'maintainer_email': 'renesd@gmail.com', 38 | 'description': 'Python Wrappings for PortMidi #python. CHANGES: new package layout.', 39 | 'long_description': long_description, 40 | 'classifiers': [ 41 | 'Development Status :: 2 - Pre-Alpha', 42 | 'Intended Audience :: Developers', 43 | 'Intended Audience :: Information Technology', 44 | 'License :: OSI Approved :: BSD License', 45 | 'Operating System :: MacOS :: MacOS X', 46 | 'Operating System :: Microsoft :: Windows', 47 | 'Operating System :: POSIX :: Linux', 48 | 'Programming Language :: Cython', 49 | 'Programming Language :: C', 50 | 'Programming Language :: Python :: 2', 51 | 'Programming Language :: Python :: 2.5', 52 | 'Programming Language :: Python :: 2.6', 53 | 'Programming Language :: Python :: 2.7', 54 | 'Programming Language :: Python :: 3', 55 | 'Programming Language :: Python :: 3.0', 56 | 'Programming Language :: Python :: 3.1', 57 | 'Programming Language :: Python :: 3.2', 58 | 'Topic :: Multimedia :: Sound/Audio :: MIDI', 59 | 'Topic :: Software Development :: Libraries', 60 | ], 61 | } 62 | 63 | 64 | if "bdist_msi" in sys.argv: 65 | # hack the version name to a format msi doesn't have trouble with 66 | METADATA["version"] = METADATA["version"].replace("pre", "a0") 67 | METADATA["version"] = METADATA["version"].replace("rc", "b0") 68 | METADATA["version"] = METADATA["version"].replace("release", "") 69 | 70 | 71 | 72 | 73 | 74 | # allow optionally using setuptools for bdist_egg. 75 | using_setuptools = False 76 | 77 | if "-setuptools" in sys.argv: 78 | using_setuptools = True 79 | 80 | from setuptools import setup, Command 81 | sys.argv.remove ("-setuptools") 82 | 83 | EXTRAS.update({'include_package_data': True, 84 | 'install_requires': [], 85 | 'zip_safe': False, 86 | 'test_suite' : 'pyportmidi.tests', 87 | } 88 | ) 89 | 90 | 91 | # test command. For doing 'python setup.py test' 92 | class TestCommand(Command): 93 | user_options = [ ] 94 | 95 | def initialize_options(self): 96 | self._dir = os.getcwd() 97 | 98 | def finalize_options(self): 99 | pass 100 | 101 | def run(self): 102 | ''' 103 | runs the tests with default options. 104 | ''' 105 | import pyportmidi.tests 106 | pyportmidi.tests.main() 107 | 108 | #import subprocess 109 | #return subprocess.call([sys.executable, "run_tests.py"]) 110 | 111 | 112 | cmdclass = {'build_ext': build_ext} 113 | 114 | # we use our test command. 115 | if not using_setuptools: 116 | import os 117 | cmdclass['test'] = TestCommand 118 | 119 | 120 | 121 | scripts = [] 122 | 123 | PACKAGEDATA = { 124 | 'cmdclass': cmdclass, 125 | 126 | 'package_dir': {'pyportmidi': 'pyportmidi', 127 | #'pyportmidi.tests': 'test', 128 | #'pyportmidi.docs': 'docs', 129 | #'pyportmidi.examples': 'examples', 130 | 131 | }, 132 | 'packages': ['pyportmidi', 133 | 'pyportmidi.tests', 134 | ], 135 | 'scripts': scripts, 136 | } 137 | 138 | 139 | PACKAGEDATA.update(METADATA) 140 | PACKAGEDATA.update(EXTRAS) 141 | 142 | 143 | 144 | if sys.platform == 'win32': 145 | print "Found Win32 platform" 146 | EXTENSION = dict( 147 | ext_modules=[ 148 | Extension("pyportmidi._pyportmidi", [os.path.join("pyportmidi", "_pyportmidi.pyx")], 149 | library_dirs = ["../Release"], 150 | libraries = ["portmidi", "winmm"], 151 | include_dirs = ["../porttime"], 152 | # define_macros = [("_WIN32_", None)]) # needed by portmidi.h 153 | extra_compile_args = ["/DWIN32"]) # needed by portmidi.h 154 | ] 155 | ) 156 | elif sys.platform == 'darwin': 157 | print "Found darwin (OS X) platform" 158 | library_dirs = ["/usr/local/lib"] 159 | include_dirs = ["/usr/local/include"] 160 | EXTENSION = dict( 161 | ext_modules=[ 162 | Extension("pyportmidi._pyportmidi", [os.path.join("pyportmidi", "_pyportmidi.pyx")], 163 | library_dirs = library_dirs, 164 | include_dirs = include_dirs, 165 | libraries = ["portmidi"], 166 | extra_link_args=["-framework", "CoreFoundation", 167 | "-framework", "CoreMIDI", 168 | "-framework", "CoreAudio"]) 169 | ] 170 | ) 171 | else: 172 | print "Assuming Linux platform" 173 | EXTENSION = dict( 174 | ext_modules=[ 175 | Extension("pyportmidi._pyportmidi", [os.path.join("pyportmidi", "_pyportmidi.pyx")], 176 | library_dirs=["./linux"], 177 | libraries = ["portmidi", "asound", "pthread"] 178 | ) 179 | ] 180 | 181 | ) 182 | 183 | PACKAGEDATA.update(EXTENSION) 184 | 185 | setup(**PACKAGEDATA) 186 | -------------------------------------------------------------------------------- /include/pmutil.h: -------------------------------------------------------------------------------- 1 | /* pmutil.h -- some helpful utilities for building midi 2 | applications that use PortMidi 3 | */ 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif /* __cplusplus */ 8 | 9 | typedef void PmQueue; 10 | 11 | /* 12 | A single-reader, single-writer queue is created by 13 | Pm_QueueCreate(), which takes the number of messages and 14 | the message size as parameters. The queue only accepts 15 | fixed sized messages. Returns NULL if memory cannot be allocated. 16 | 17 | This queue implementation uses the "light pipe" algorithm which 18 | operates correctly even with multi-processors and out-of-order 19 | memory writes. (see Alexander Dokumentov, "Lock-free Interprocess 20 | Communication," Dr. Dobbs Portal, http://www.ddj.com/, 21 | articleID=189401457, June 15, 2006. This algorithm requires 22 | that messages be translated to a form where no words contain 23 | zeros. Each word becomes its own "data valid" tag. Because of 24 | this translation, we cannot return a pointer to data still in 25 | the queue when the "peek" method is called. Instead, a buffer 26 | is preallocated so that data can be copied there. Pm_QueuePeek() 27 | dequeues a message into this buffer and returns a pointer to 28 | it. A subsequent Pm_Dequeue() will copy from this buffer. 29 | 30 | This implementation does not try to keep reader/writer data in 31 | separate cache lines or prevent thrashing on cache lines. 32 | However, this algorithm differs by doing inserts/removals in 33 | units of messages rather than units of machine words. Some 34 | performance improvement might be obtained by not clearing data 35 | immediately after a read, but instead by waiting for the end 36 | of the cache line, especially if messages are smaller than 37 | cache lines. See the Dokumentov article for explanation. 38 | 39 | The algorithm is extended to handle "overflow" reporting. To report 40 | an overflow, the sender writes the current tail position to a field. 41 | The receiver must acknowlege receipt by zeroing the field. The sender 42 | will not send more until the field is zeroed. 43 | 44 | Pm_QueueDestroy() destroys the queue and frees its storage. 45 | */ 46 | 47 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); 48 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); 49 | 50 | /* 51 | Pm_Dequeue() removes one item from the queue, copying it into msg. 52 | Returns 1 if successful, and 0 if the queue is empty. 53 | Returns pmBufferOverflow if what would have been the next thing 54 | in the queue was dropped due to overflow. (So when overflow occurs, 55 | the receiver can receive a queue full of messages before getting the 56 | overflow report. This protocol ensures that the reader will be 57 | notified when data is lost due to overflow. 58 | */ 59 | PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); 60 | 61 | 62 | /* 63 | Pm_Enqueue() inserts one item into the queue, copying it from msg. 64 | Returns pmNoError if successful and pmBufferOverflow if the queue was 65 | already full. If pmBufferOverflow is returned, the overflow flag is set. 66 | */ 67 | PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); 68 | 69 | 70 | /* 71 | Pm_QueueFull() returns non-zero if the queue is full 72 | Pm_QueueEmpty() returns non-zero if the queue is empty 73 | 74 | Either condition may change immediately because a parallel 75 | enqueue or dequeue operation could be in progress. Furthermore, 76 | Pm_QueueEmpty() is optimistic: it may say false, when due to 77 | out-of-order writes, the full message has not arrived. Therefore, 78 | Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns 79 | false. On the other hand, Pm_QueueFull() is pessimistic: if it 80 | returns false, then Pm_Enqueue() is guaranteed to succeed. 81 | 82 | Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. 83 | Pm_QueueEmpty() returns FALSE if queue is NULL. 84 | */ 85 | PMEXPORT int Pm_QueueFull(PmQueue *queue); 86 | PMEXPORT int Pm_QueueEmpty(PmQueue *queue); 87 | 88 | 89 | /* 90 | Pm_QueuePeek() returns a pointer to the item at the head of the queue, 91 | or NULL if the queue is empty. The item is not removed from the queue. 92 | Pm_QueuePeek() will not indicate when an overflow occurs. If you want 93 | to get and check pmBufferOverflow messages, use the return value of 94 | Pm_QueuePeek() *only* as an indication that you should call 95 | Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would 96 | return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally 97 | clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume 98 | enqueuing messages. A subsequent call to Pm_QueuePeek() 99 | will return a pointer to the first message *after* the overflow. 100 | Using this as an indication to call Pm_Dequeue(), the first call 101 | to Pm_Dequeue() will return pmBufferOverflow. The second call will 102 | return success, copying the same message pointed to by the previous 103 | Pm_QueuePeek(). 104 | 105 | When to use Pm_QueuePeek(): (1) when you need to look at the message 106 | data to decide who should be called to receive it. (2) when you need 107 | to know a message is ready but cannot accept the message. 108 | 109 | Note that Pm_QueuePeek() is not a fast check, so if possible, you 110 | might as well just call Pm_Dequeue() and accept the data if it is there. 111 | */ 112 | PMEXPORT void *Pm_QueuePeek(PmQueue *queue); 113 | 114 | /* 115 | Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow 116 | condition to the reader (dequeuer). E.g. when transfering data from 117 | the OS to an application, if the OS indicates a buffer overrun, 118 | Pm_SetOverflow() can be used to insure that the reader receives a 119 | pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue 120 | is NULL, returns pmBufferOverflow if buffer is already in an overflow 121 | state, returns pmNoError if successfully set overflow state. 122 | */ 123 | PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); 124 | 125 | #ifdef __cplusplus 126 | } 127 | #endif /* __cplusplus */ 128 | -------------------------------------------------------------------------------- /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 DRIVER_INFO NULL 22 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) 23 | #define TIME_INFO NULL 24 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 25 | 26 | #define STRING_MAX 80 /* used for console input */ 27 | // need to get declaration for Sleep() 28 | #ifdef WIN32 29 | #include "windows.h" 30 | #else 31 | #include 32 | #define Sleep(n) usleep(n * 1000) 33 | #endif 34 | 35 | 36 | int deviceno = -9999; 37 | int verbose = FALSE; 38 | 39 | /* read a number from console */ 40 | /**/ 41 | int get_number(const char *prompt) 42 | { 43 | char line[STRING_MAX]; 44 | int n = 0, i; 45 | fputs(prompt, stdout); 46 | while (n != 1) { 47 | n = scanf("%d", &i); 48 | fgets(line, STRING_MAX, stdin); 49 | } 50 | return i; 51 | } 52 | 53 | 54 | void fastrcv_test() 55 | { 56 | PmStream * midi; 57 | PmError status, length; 58 | PmEvent buffer[1]; 59 | 60 | /* It is recommended to start timer before PortMidi */ 61 | TIME_START; 62 | 63 | /* open output device */ 64 | if (deviceno == Pm_CountDevices()) { 65 | Pm_CreateVirtualInput(&midi, "fastrcv", NULL, DRIVER_INFO, 66 | INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO); 67 | } else { 68 | Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE, 69 | TIME_PROC, TIME_INFO); 70 | } 71 | printf("Midi Input opened.\n"); 72 | 73 | /* wait a sec after printing previous line */ 74 | PmTimestamp start = Pt_Time() + 1000; 75 | while (start > Pt_Time()) { 76 | Sleep(10); 77 | } 78 | 79 | /* every 10ms read all messages, keep counts */ 80 | /* every 1000ms, print report */ 81 | int msgcnt = 0; 82 | /* expect repeating sequence of 60 through 71, alternating on/off */ 83 | int expected_pitch = 60; 84 | int expected_on = TRUE; 85 | int report_time = Pt_Time() + 1000; /* report every 1s */ 86 | PmTimestamp last_timestamp = -1; 87 | PmTimestamp last_delta = -1; 88 | while (TRUE) { 89 | PmTimestamp now = Pt_Time(); 90 | status = Pm_Poll(midi); 91 | if (status == TRUE) { 92 | length = Pm_Read(midi, buffer, 1); 93 | if (length > 0) { 94 | int status = Pm_MessageStatus(buffer[0].message); 95 | if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */ 96 | status = 0x90; 97 | buffer[0].message = Pm_Message(status, 98 | Pm_MessageData1(buffer[0].message), 0); 99 | } 100 | /* only listen to NOTEON messages */ 101 | if (status == 0x90) { 102 | int pitch = Pm_MessageData1(buffer[0].message); 103 | int vel = Pm_MessageData2(buffer[0].message); 104 | int is_on = (vel > 0); 105 | if (verbose) { 106 | printf("Note pitch %d vel %d\n", pitch, vel); 107 | } 108 | msgcnt++; 109 | if (pitch != expected_pitch || expected_on != is_on) { 110 | printf("Unexpected note-on: pitch %d vel %d, " 111 | "expected: pitch %d Note%s\n", pitch, vel, 112 | expected_pitch, (expected_on ? "On" : "Off")); 113 | } 114 | if (is_on) { 115 | expected_on = FALSE; 116 | expected_pitch = pitch; 117 | } else { 118 | expected_on = TRUE; 119 | expected_pitch = (pitch + 1) % 72; 120 | if (expected_pitch < 60) expected_pitch = 60; 121 | } 122 | if (last_timestamp >= 0) { 123 | last_delta = buffer[0].timestamp - last_timestamp; 124 | } 125 | last_timestamp = buffer[0].timestamp; 126 | } 127 | } 128 | } 129 | if (now >= report_time) { 130 | printf("%d msgs/sec", msgcnt); 131 | /* if available, print the last timestamp and last delta time */ 132 | if (last_timestamp >= 0) { 133 | printf(" last timestamp %d", (int) last_timestamp); 134 | last_timestamp = -1; 135 | } 136 | if (last_delta >= 0) { 137 | printf(" last delta time %d", (int) last_delta); 138 | last_delta = -1; 139 | } 140 | printf("\n"); 141 | report_time += 1000; 142 | msgcnt = 0; 143 | } 144 | } 145 | } 146 | 147 | 148 | void show_usage() 149 | { 150 | printf("Usage: fastrcv [-h] [-v] [-d device], where\n" 151 | "device is the PortMidi device number,\n" 152 | "-h means help,\n" 153 | "-v means verbose (print messages)\n"); 154 | } 155 | 156 | int main(int argc, char *argv[]) 157 | { 158 | int default_in; 159 | int default_out; 160 | char *deflt; 161 | 162 | int i = 0, n = 0; 163 | int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0; 164 | int stream_test = 0; 165 | int device_valid = FALSE; 166 | 167 | if (sizeof(void *) == 8) 168 | printf("Apparently this is a 64-bit machine.\n"); 169 | else if (sizeof(void *) == 4) 170 | printf ("Apparently this is a 32-bit machine.\n"); 171 | 172 | if (argc <= 1) { 173 | show_usage(); 174 | } else { 175 | for (i = 1; i < argc; i++) { 176 | if (strcmp(argv[i], "-h") == 0) { 177 | show_usage(); 178 | } else if (strcmp(argv[i], "-v") == 0) { 179 | verbose = TRUE; 180 | } else if (strcmp(argv[i], "-d") == 0) { 181 | i = i + 1; 182 | deviceno = atoi(argv[i]); 183 | printf("Device will be %d\n", deviceno); 184 | } else { 185 | show_usage(); 186 | } 187 | } 188 | } 189 | 190 | /* list device information */ 191 | default_in = Pm_GetDefaultInputDeviceID(); 192 | default_out = Pm_GetDefaultOutputDeviceID(); 193 | for (i = 0; i < Pm_CountDevices(); i++) { 194 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 195 | if (!info->output) { 196 | printf("%d: %s, %s", i, info->interf, info->name); 197 | if (i == deviceno) { 198 | device_valid = TRUE; 199 | deflt = "selected "; 200 | } else if (i == default_out) { 201 | deflt = "default "; 202 | } else { 203 | deflt = ""; 204 | } 205 | printf(" (%sinput)\n", deflt); 206 | } 207 | } 208 | printf("%d: Create virtual port named \"fastrcv\"", i); 209 | if (i == deviceno) { 210 | device_valid = TRUE; 211 | deflt = "selected "; 212 | } else { 213 | deflt = ""; 214 | } 215 | printf(" (%sinput)\n", deflt); 216 | 217 | if (!device_valid) { 218 | deviceno = get_number("Input device number: "); 219 | } 220 | 221 | fastrcv_test(); 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /src/portmidi/common/pminternal.h: -------------------------------------------------------------------------------- 1 | /* pminternal.h -- header for interface 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 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | extern int pm_initialized; /* see note in portmidi.c */ 25 | 26 | /* these are defined in system-specific file */ 27 | void *pm_alloc(size_t s); 28 | void pm_free(void *ptr); 29 | 30 | /* if an error occurs while opening or closing a midi stream, set these: */ 31 | extern int pm_hosterror; 32 | extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; 33 | 34 | struct pm_internal_struct; 35 | 36 | /* these do not use PmInternal because it is not defined yet... */ 37 | typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, 38 | PmEvent *buffer); 39 | typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, 40 | PmTimestamp timestamp); 41 | typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, 42 | PmTimestamp timestamp); 43 | typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, 44 | unsigned char byte, PmTimestamp timestamp); 45 | typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, 46 | PmEvent *buffer); 47 | typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi, 48 | PmTimestamp timestamp); 49 | typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); 50 | /* pm_open_fn should clean up all memory and close the device if any part 51 | of the open fails */ 52 | typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, 53 | void *driverInfo); 54 | typedef PmError (*pm_create_fn)(struct pm_internal_struct *midi, int is_input, 55 | const char *name, void *driverInfo); 56 | typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); 57 | /* pm_close_fn should clean up all memory and close the device if any 58 | part of the close fails. */ 59 | typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); 60 | typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); 61 | typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, 62 | unsigned int len); 63 | typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); 64 | 65 | typedef struct { 66 | pm_write_short_fn write_short; /* output short MIDI msg */ 67 | pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ 68 | pm_end_sysex_fn end_sysex; /* marks end of sysex message */ 69 | pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ 70 | pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ 71 | pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ 72 | pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ 73 | pm_open_fn open; /* open MIDI device */ 74 | pm_abort_fn abort; /* abort */ 75 | pm_close_fn close; /* close device */ 76 | pm_poll_fn poll; /* read pending midi events into portmidi buffer */ 77 | pm_has_host_error_fn has_host_error; /* true when device has had host 78 | error message */ 79 | pm_host_error_fn host_error; /* provide text readable host error message 80 | for device (clears and resets) */ 81 | } pm_fns_node, *pm_fns_type; 82 | 83 | 84 | /* when open fails, the dictionary gets this set of functions: */ 85 | extern pm_fns_node pm_none_dictionary; 86 | 87 | typedef struct { 88 | PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic 89 | device closing -- see PmDeviceInfo struct) */ 90 | void *descriptor; /* ID number passed to win32 multimedia API open */ 91 | void *internalDescriptor; /* points to PmInternal device, allows automatic 92 | device closing */ 93 | pm_fns_type dictionary; 94 | } descriptor_node, *descriptor_type; 95 | 96 | extern int pm_descriptor_max; 97 | extern descriptor_type descriptors; 98 | extern int pm_descriptor_index; 99 | 100 | typedef uint32_t (*time_get_proc_type)(void *time_info); 101 | 102 | typedef struct pm_internal_struct { 103 | int device_id; /* which device is open (index to descriptors) */ 104 | short is_input; /* MIDI IN (true) or MIDI OUT (false) */ 105 | 106 | PmTimeProcPtr time_proc; /* where to get the time */ 107 | void *time_info; /* pass this to get_time() */ 108 | int32_t buffer_len; /* how big is the buffer or queue? */ 109 | PmQueue *queue; 110 | 111 | int32_t latency; /* time delay in ms between timestamps and actual output */ 112 | /* set to zero to get immediate, simple blocking output */ 113 | /* if latency is zero, timestamps will be ignored; */ 114 | /* if midi input device, this field ignored */ 115 | 116 | int sysex_in_progress; /* when sysex status is seen, this flag becomes 117 | * true until EOX is seen. When true, new data is appended to the 118 | * stream of outgoing bytes. When overflow occurs, sysex data is 119 | * dropped (until an EOX or non-real-timei status byte is seen) so 120 | * that, if the overflow condition is cleared, we don't start 121 | * sending data from the middle of a sysex message. If a sysex 122 | * message is filtered, sysex_in_progress is false, causing the 123 | * message to be dropped. */ 124 | PmMessage sysex_message; /* buffer for 4 bytes of sysex data */ 125 | int sysex_message_count; /* how many bytes in sysex_message so far */ 126 | 127 | int32_t filters; /* flags that filter incoming message classes */ 128 | int32_t channel_mask; /* filter incoming messages based on channel */ 129 | PmTimestamp last_msg_time; /* timestamp of last message */ 130 | PmTimestamp sync_time; /* time of last synchronization */ 131 | PmTimestamp now; /* set by PmWrite to current time */ 132 | int first_message; /* initially true, used to run first synchronization */ 133 | pm_fns_type dictionary; /* implementation functions */ 134 | void *api_info; /* system-dependent state */ 135 | /* the following are used to expedite sysex data */ 136 | /* on windows, in debug mode, based on some profiling, these optimizations 137 | * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, 138 | * but this does not count time in the driver, so I don't know if it is 139 | * important 140 | */ 141 | unsigned char *fill_base; /* addr of ptr to sysex data */ 142 | uint32_t *fill_offset_ptr; /* offset of next sysex byte */ 143 | uint32_t fill_length; /* how many sysex bytes to write */ 144 | } PmInternal; 145 | 146 | 147 | /* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ 148 | void pm_init(void); 149 | void pm_term(void); 150 | 151 | /* defined by portMidi, used by pmwinmm */ 152 | PmError none_write_short(PmInternal *midi, PmEvent *buffer); 153 | PmError none_write_byte(PmInternal *midi, unsigned char byte, 154 | PmTimestamp timestamp); 155 | PmTimestamp none_synchronize(PmInternal *midi); 156 | 157 | PmError pm_fail_fn(PmInternal *midi); 158 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); 159 | PmError pm_success_fn(PmInternal *midi); 160 | PmError pm_add_interf(char *interf, pm_create_fn create_fn); 161 | PmError pm_add_device(char *interf, const char *name, int is_input, 162 | void *descriptor, pm_fns_type dictionary); 163 | void pm_undo_add_device(void); 164 | uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, 165 | PmTimestamp timestamp); 166 | void pm_read_short(PmInternal *midi, PmEvent *event); 167 | 168 | #define none_write_flush pm_fail_timestamp_fn 169 | #define none_sysex pm_fail_timestamp_fn 170 | #define none_poll pm_fail_fn 171 | #define success_poll pm_success_fn 172 | 173 | #define MIDI_REALTIME_MASK 0xf8 174 | #define is_real_time(msg) \ 175 | ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) 176 | 177 | int pm_find_default_device(char *pattern, int is_input); 178 | 179 | #ifdef __cplusplus 180 | } 181 | #endif 182 | 183 | -------------------------------------------------------------------------------- /src/portmidi/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 | #ifdef WIN32 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 | bzero(queue->buffer, queue->len * sizeof(int32_t)); 46 | if (!queue->buffer) { 47 | pm_free(queue); 48 | return NULL; 49 | } else { /* allocate the "peek" buffer */ 50 | queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); 51 | if (!queue->peek) { 52 | /* free everything allocated so far and return */ 53 | pm_free(queue->buffer); 54 | pm_free(queue); 55 | return NULL; 56 | } 57 | } 58 | bzero(queue->buffer, queue->len * sizeof(int32_t)); 59 | queue->head = 0; 60 | queue->tail = 0; 61 | /* msg_size is in words */ 62 | queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ 63 | queue->overflow = FALSE; 64 | queue->peek_overflow = FALSE; 65 | queue->peek_flag = FALSE; 66 | return queue; 67 | } 68 | 69 | 70 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) 71 | { 72 | PmQueueRep *queue = (PmQueueRep *) q; 73 | 74 | /* arg checking */ 75 | if (!queue || !queue->buffer || !queue->peek) 76 | return pmBadPtr; 77 | 78 | pm_free(queue->peek); 79 | pm_free(queue->buffer); 80 | pm_free(queue); 81 | return pmNoError; 82 | } 83 | 84 | 85 | PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) 86 | { 87 | long head; 88 | PmQueueRep *queue = (PmQueueRep *) q; 89 | int i; 90 | int32_t *msg_as_int32 = (int32_t *) msg; 91 | 92 | /* arg checking */ 93 | if (!queue) 94 | return pmBadPtr; 95 | /* a previous peek operation encountered an overflow, but the overflow 96 | * has not yet been reported to client, so do it now. No message is 97 | * returned, but on the next call, we will return the peek buffer. 98 | */ 99 | if (queue->peek_overflow) { 100 | queue->peek_overflow = FALSE; 101 | return pmBufferOverflow; 102 | } 103 | if (queue->peek_flag) { 104 | memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); 105 | queue->peek_flag = FALSE; 106 | return pmGotData; 107 | } 108 | 109 | head = queue->head; 110 | /* if writer overflows, it writes queue->overflow = tail+1 so that 111 | * when the reader gets to that position in the buffer, it can 112 | * return the overflow condition to the reader. The problem is that 113 | * at overflow, things have wrapped around, so tail == head, and the 114 | * reader will detect overflow immediately instead of waiting until 115 | * it reads everything in the buffer, wrapping around again to the 116 | * point where tail == head. So the condition also checks that 117 | * queue->buffer[head] is zero -- if so, then the buffer is now 118 | * empty, and we're at the point in the msg stream where overflow 119 | * occurred. It's time to signal overflow to the reader. If 120 | * queue->buffer[head] is non-zero, there's a message there and we 121 | * should read all the way around the buffer before signalling overflow. 122 | * There is a write-order dependency here, but to fail, the overflow 123 | * field would have to be written while an entire buffer full of 124 | * writes are still pending. I'm assuming out-of-order writes are 125 | * possible, but not that many. 126 | */ 127 | if (queue->overflow == head + 1 && !queue->buffer[head]) { 128 | queue->overflow = 0; /* non-overflow condition */ 129 | return pmBufferOverflow; 130 | } 131 | 132 | /* test to see if there is data in the queue -- test from back 133 | * to front so if writer is simultaneously writing, we don't 134 | * waste time discovering the write is not finished 135 | */ 136 | for (i = queue->msg_size - 1; i >= 0; i--) { 137 | if (!queue->buffer[head + i]) { 138 | return pmNoData; 139 | } 140 | } 141 | memcpy(msg, (char *) &queue->buffer[head + 1], 142 | sizeof(int32_t) * (queue->msg_size - 1)); 143 | /* fix up zeros */ 144 | i = queue->buffer[head]; 145 | while (i < queue->msg_size) { 146 | int32_t j; 147 | i--; /* msg does not have extra word so shift down */ 148 | j = msg_as_int32[i]; 149 | msg_as_int32[i] = 0; 150 | i = j; 151 | } 152 | /* signal that data has been removed by zeroing: */ 153 | bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); 154 | 155 | /* update head */ 156 | head += queue->msg_size; 157 | if (head == queue->len) head = 0; 158 | queue->head = head; 159 | return pmGotData; /* success */ 160 | } 161 | 162 | 163 | 164 | PMEXPORT PmError Pm_SetOverflow(PmQueue *q) 165 | { 166 | PmQueueRep *queue = (PmQueueRep *) q; 167 | long tail; 168 | /* arg checking */ 169 | if (!queue) 170 | return pmBadPtr; 171 | /* no more enqueue until receiver acknowledges overflow */ 172 | if (queue->overflow) return pmBufferOverflow; 173 | tail = queue->tail; 174 | queue->overflow = tail + 1; 175 | return pmBufferOverflow; 176 | } 177 | 178 | 179 | PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) 180 | { 181 | PmQueueRep *queue = (PmQueueRep *) q; 182 | long tail; 183 | int i; 184 | int32_t *src = (int32_t *) msg; 185 | int32_t *ptr; 186 | int32_t *dest; 187 | int rslt; 188 | if (!queue) 189 | return pmBadPtr; 190 | /* no more enqueue until receiver acknowledges overflow */ 191 | if (queue->overflow) return pmBufferOverflow; 192 | rslt = Pm_QueueFull(q); 193 | /* already checked above: if (rslt == pmBadPtr) return rslt; */ 194 | tail = queue->tail; 195 | if (rslt) { 196 | queue->overflow = tail + 1; 197 | return pmBufferOverflow; 198 | } 199 | 200 | /* queue is has room for message, and overflow flag is cleared */ 201 | ptr = &queue->buffer[tail]; 202 | dest = ptr + 1; 203 | for (i = 1; i < queue->msg_size; i++) { 204 | int32_t j = src[i - 1]; 205 | if (!j) { 206 | *ptr = i; 207 | ptr = dest; 208 | } else { 209 | *dest = j; 210 | } 211 | dest++; 212 | } 213 | *ptr = i; 214 | tail += queue->msg_size; 215 | if (tail == queue->len) tail = 0; 216 | queue->tail = tail; 217 | return pmNoError; 218 | } 219 | 220 | 221 | PMEXPORT int Pm_QueueEmpty(PmQueue *q) 222 | { 223 | PmQueueRep *queue = (PmQueueRep *) q; 224 | return (!queue) || /* null pointer -> return "empty" */ 225 | (queue->buffer[queue->head] == 0 && !queue->peek_flag); 226 | } 227 | 228 | 229 | PMEXPORT int Pm_QueueFull(PmQueue *q) 230 | { 231 | long tail; 232 | int i; 233 | PmQueueRep *queue = (PmQueueRep *) q; 234 | /* arg checking */ 235 | if (!queue) 236 | return pmBadPtr; 237 | tail = queue->tail; 238 | /* test to see if there is space in the queue */ 239 | for (i = 0; i < queue->msg_size; i++) { 240 | if (queue->buffer[tail + i]) { 241 | return TRUE; 242 | } 243 | } 244 | return FALSE; 245 | } 246 | 247 | 248 | PMEXPORT void *Pm_QueuePeek(PmQueue *q) 249 | { 250 | PmError rslt; 251 | int32_t temp; 252 | PmQueueRep *queue = (PmQueueRep *) q; 253 | /* arg checking */ 254 | if (!queue) 255 | return NULL; 256 | 257 | if (queue->peek_flag) { 258 | return queue->peek; 259 | } 260 | /* this is ugly: if peek_overflow is set, then Pm_Dequeue() 261 | * returns immediately with pmBufferOverflow, but here, we 262 | * want Pm_Dequeue() to really check for data. If data is 263 | * there, we can return it 264 | */ 265 | temp = queue->peek_overflow; 266 | queue->peek_overflow = FALSE; 267 | rslt = Pm_Dequeue(q, queue->peek); 268 | queue->peek_overflow = temp; 269 | 270 | if (rslt == 1) { 271 | queue->peek_flag = TRUE; 272 | return queue->peek; 273 | } else if (rslt == pmBufferOverflow) { 274 | /* when overflow is indicated, the queue is empty and the 275 | * first message that was dropped by Enqueue (signalling 276 | * pmBufferOverflow to its caller) would have been the next 277 | * message in the queue. Pm_QueuePeek will return NULL, but 278 | * remember that an overflow occurred. (see Pm_Dequeue) 279 | */ 280 | queue->peek_overflow = TRUE; 281 | } 282 | return NULL; 283 | } 284 | 285 | -------------------------------------------------------------------------------- /bindings/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 | -------------------------------------------------------------------------------- /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 | char line[STRING_MAX]; 169 | int n = 0, i; 170 | fputs(prompt, stdout); 171 | while (n != 1) { 172 | n = scanf("%d", &i); 173 | fgets(line, STRING_MAX, stdin); 174 | 175 | } 176 | return i; 177 | } 178 | 179 | /**************************************************************************** 180 | * showhelp 181 | * Effect: print help text 182 | ****************************************************************************/ 183 | 184 | private void showhelp() 185 | { 186 | printf("\n"); 187 | printf("t toggles sending MIDI Time Code (MTC)\n"); 188 | printf("c toggles sending MIDI CLOCK (initially on)\n"); 189 | printf("m to set tempo (from 1bpm to 300bpm)\n"); 190 | printf("q quits\n"); 191 | printf("\n"); 192 | } 193 | 194 | /**************************************************************************** 195 | * doascii 196 | * Inputs: 197 | * char c: input character 198 | * Effect: interpret to control output 199 | ****************************************************************************/ 200 | 201 | private void doascii(char c) 202 | { 203 | if (isupper(c)) c = tolower(c); 204 | if (c == 'q') done = true; 205 | else if (c == 'c') { 206 | printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting")); 207 | send_start_stop = true; 208 | } else if (c == 't') { 209 | printf("%s MIDI Time Code\n", 210 | (time_code_running ? "Stopping" : "Starting")); 211 | time_code_running = !time_code_running; 212 | } else if (c == 'm') { 213 | int input_tempo = get_number("Enter new tempo (bpm): "); 214 | if (input_tempo >= 1 && input_tempo <= 300) { 215 | printf("Changing tempo to %d\n", input_tempo); 216 | tempo = (float) input_tempo; 217 | } else { 218 | printf("Tempo range is 1 to 300, current tempo is %g bpm\n", 219 | tempo); 220 | } 221 | } else { 222 | showhelp(); 223 | } 224 | } 225 | 226 | 227 | /* main - prompt for parameters, start processing */ 228 | /* 229 | * Prompt user to type return. 230 | * Then send START and MIDI CLOCK for 60 beats/min. 231 | * Commands: 232 | * t - toggle sending MIDI Time Code (MTC) 233 | * c - toggle sending MIDI CLOCK 234 | * m - set tempo 235 | * q - quit 236 | */ 237 | int main(int argc, char **argv) 238 | { 239 | char s[STRING_MAX]; /* console input */ 240 | int outp; 241 | PmError err; 242 | int i; 243 | if (argc > 1) { 244 | printf("Warning: command line arguments ignored\n"); 245 | } 246 | showhelp(); 247 | /* use porttime callback to send midi */ 248 | Pt_Start(1, timer_poll, 0); 249 | /* list device information */ 250 | printf("MIDI output devices:\n"); 251 | for (i = 0; i < Pm_CountDevices(); i++) { 252 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 253 | if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name); 254 | } 255 | outp = get_number("Type output device number: "); 256 | err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE, 257 | TIME_PROC, TIME_INFO, LATENCY); 258 | if (err) { 259 | puts(Pm_GetErrorText(err)); 260 | goto error_exit_no_device; 261 | } 262 | active = true; 263 | 264 | printf("Type ENTER to start MIDI CLOCK:\n"); 265 | if (!fgets(s, STRING_MAX, stdin)) goto error_exit; 266 | send_start_stop = true; /* send START and then CLOCKs */ 267 | 268 | while (!done) { 269 | if (fgets(s, STRING_MAX, stdin)) { 270 | doascii(s[0]); 271 | } 272 | } 273 | 274 | error_exit: 275 | active = false; 276 | Pt_Sleep(2); /* this is to allow callback to complete -- it's 277 | real time, so it's either ok and it runs on 278 | time, or there's no point to synchronizing 279 | with it */ 280 | /* now we "own" portmidi again */ 281 | Pm_Close(midi); 282 | error_exit_no_device: 283 | Pt_Stop(); 284 | Pm_Terminate(); 285 | exit(0); 286 | } 287 | 288 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | /* CHANGELOG FOR PORTMIDI 2 | * 3 | * 2021-06-16 Be 4 | * - Converted from SVN to Git 5 | * - Forked to Mixxx DJ Software organization: https://github.com/mixxxdj/portmidi/ 6 | * - Rewrote build system with modern CMake, added pkg-config and CMake config files 7 | * 8 | * 19Oct09 Roger Dannenberg 9 | * - Changes dynamic library names from portmidi_d to portmidi to 10 | * be backward-compatible with programs expecting a library by 11 | * the old name. 12 | * 13 | * 04Oct09 Roger Dannenberg 14 | * - Converted to using Cmake. 15 | * - Renamed static and dynamic library files to portmidi_s and portmidi_d 16 | * - Eliminated VC9 and VC8 files (went back to simply test.vcproj, etc., 17 | * use Cmake to switch from the provided VC9 files to VC8 or other) 18 | * - Many small changes to prepare for 64-bit architectures (but only 19 | * tested on 32-bit machines) 20 | * 21 | * 16Jun09 Roger Dannenberg 22 | * - Started using Microsoft Visual C++ Version 9 (Express). Converted 23 | * all *-VC9.vcproj file to *.vcproj and renamed old project files to 24 | * *-VC8.proj. Previously, output from VC9 went to special VC9 files, 25 | * that breaks any program or script looking for output in release or 26 | * debug files, so now both compiler version output to the same folders. 27 | * Now, debug version uses static linking with debug DLL runtime, and 28 | * release version uses static linking with statically linked runtime. 29 | * Converted to Inno Setup and worked on scripts to make things build 30 | * properly, especially pmdefaults. 31 | * 32 | * 02Jan09 Roger Dannenberg 33 | * - Created Java interface and wrote PmDefaults application to set 34 | * values for Pm_GetDefaultInputDeviceID() and 35 | * Pm_GetDefaultOutputDeviceID(). Other fixes. 36 | * 37 | * 19Jun08 Roger Dannenberg and Austin Sung 38 | * - Removed USE_DLL_FOR_CLEANUP -- Windows 2000 through Vista seem to be 39 | * fixed now, and can recover if MIDI ports are left open 40 | * - Various other minor patches 41 | * 42 | * 17Jan07 Roger Dannenberg 43 | * - Lots more help for Common Lisp user in pm_cl 44 | * - Minor fix to eliminate a compiler warning 45 | * - Went back to single library in OS X for both portmidi and porttime 46 | * 47 | * 16Jan07 Roger Dannenberg 48 | * - OOPS! fixed bug where short messages all had zero data 49 | * - Makefile.osx static library build now makes universal (i386 + ppc) 50 | * binaries 51 | * 52 | * 15Jan07 Roger Dannenberg 53 | * - multiple rewrites of sysex handling code to take care of 54 | * error-handling, embedded messages, message filtering, 55 | * driver bugs, and host limitations. 56 | * - fixed windows to use dwBufferLength rather than 57 | * dwBytesRecorded for long buffer output (fix by Nigel Brown) 58 | * - Win32 MME code always appends an extra zero to long buffer 59 | * output to work around a problem with earlier versions of Midi Yoke 60 | * - Added mm, a command line Midi Monitor to pm_test suite 61 | * - Revised copyright notice to match PortAudio/MIT license (requests 62 | * are moved out of the license proper and into a separate paragraph) 63 | * 64 | * 18Oct06 Roger Dannenberg 65 | * - replace FIFO in pmutil with Light Pipe-based multiprocessor-safe alg. 66 | * - replace FIFO in portmidi.c with PmQueue from pmutil 67 | * 68 | * 07Oct06 cpr & Roger Dannenberg 69 | * - overhaul of CoreMIDI input to handle running status and multiple 70 | * - messages per packet, with additional error detection 71 | * - added Leigh Smith and Rick Taube support for Common Lisp and 72 | * - dynamic link libraries in OSX 73 | * - initialize static global seq = NULL in pmlinuxalsa.c 74 | * 75 | * 05Sep06 Sebastien Frippiat 76 | * - check if (ALSA) seq exists before closing it in pm_linuxalsa_term() 77 | * 78 | * 05Sep06 Andreas Micheler and Cecilio 79 | * - fixed memory leak by freeing someo objects in pm_winmm_term() 80 | * - and another leak by freeing descriptors in Pm_Terminate() 81 | * 82 | * 23Aug06 RBD 83 | * - various minor fixes 84 | * 85 | * 04Nov05 Olivier Tristan 86 | * - changes to OS X to properly retrieve real device name on CoreMidi 87 | * 88 | * 19Jul05 Roger Dannenberg 89 | * - included pmBufferMaxSize in Pm_GetErrorText() 90 | * 91 | * 23Mar05 Torgier Strand Henriksen 92 | * - cleaner termination of porttime thread under Linux 93 | * 94 | * 15Nov04 Ben Allison 95 | * - sysex output now uses one buffer/message and reallocates buffer 96 | * - if needed 97 | * - filters expanded for many message types and channels 98 | * - detailed changes are as follows: 99 | * ------------- in pmwinmm.c -------------- 100 | * - new #define symbol: OUTPUT_BYTES_PER_BUFFER 101 | * - change SYSEX_BYTES_PER_BUFFER to 1024 102 | * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length 103 | * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) 104 | * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro 105 | * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH 106 | * - similar to above, but counts appropriately for sysex messages 107 | * - added the following members to midiwinmm_struct for sysex data: 108 | * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data ** 109 | * - int num_sysex_buffers; ** how many sysex buffers ** 110 | * - int next_sysex_buffer; ** index of next sysexbuffer to send ** 111 | * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer ** 112 | * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer 113 | * - into equivalent sysex_buffer form 114 | * - changed winmm_in_open to initialize new midiwinmm_struct members and 115 | * - to use the new allocate_sysex_buffer() function instead of 116 | * - allocate_buffer() 117 | * - changed winmm_out_open to initialize new members, create sysex buffer 118 | * - signal, and allocate 2 sysex buffers 119 | * - changed winmm_out_delete to free sysex buffers and shut down the sysex 120 | * - buffer signal 121 | * - create new function resize_sysex_buffer which resizes m->hdr to the 122 | * - passed size, and corrects the midiwinmm_struct accordingly. 123 | * - changed winmm_write_byte to use new resize_sysex_buffer function, 124 | * - if resize fails, write current buffer to output and continue 125 | * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal 126 | * - depending on which buffer was finished 127 | * ------------- in portmidi.h -------------- 128 | * - added pmBufferMaxSize to PmError to indicate that the buffer would be 129 | * - too large for the underlying API 130 | * - added additional filters 131 | * - added prototype, documentation, and helper macro for Pm_SetChannelMask 132 | * ------------- in portmidi.c -------------- 133 | * - added pm_status_filtered() and pm_realtime_filtered() functions to 134 | * separate filtering logic from buffer logic in pm_read_short 135 | * - added Pm_SetChannelMask function 136 | * - added pm_channel_filtered() function 137 | * ------------- in pminternal.h -------------- 138 | * - added member to PortMidiStream for channel mask 139 | * 140 | * 25May04 RBD 141 | * - removed support for MIDI THRU 142 | * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl 143 | * - extensive work on Mac OS X port, especially sysex and error handling 144 | * 145 | * 18May04 RBD 146 | * - removed side-effects from assert() calls. Now you can disable assert(). 147 | * - no longer check pm_hosterror everywhere, fixing a bug where an open 148 | * failure could cause a write not to work on a previously opened port 149 | * until you call Pm_GetHostErrorText(). 150 | * 16May04 RBD and Chris Roberts 151 | * - Some documentation wordsmithing in portmidi.h 152 | * - Dynamically allocate port descriptor structures 153 | * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer. 154 | * 155 | * 09Oct03 RBD 156 | * - Changed Thru handling. Now the client does all the work and the client 157 | * must poll or read to keep thru messages flowing. 158 | * 159 | * 31May03 RBD 160 | * - Fixed various bugs. 161 | * - Added linux ALSA support with help from Clemens Ladisch 162 | * - Added Mac OS X support, implemented by Jon Parise, updated and 163 | * integrated by Andrew Zeldis and Zico Kolter 164 | * - Added latency program to build histogram of system latency using PortTime. 165 | * 166 | * 30Jun02 RBD Extensive rewrite of sysex handling. It works now. 167 | * Extensive reworking of error reporting and error text -- no 168 | * longer use dictionary call to delete data; instead, Pm_Open 169 | * and Pm_Close clean up before returning an error code, and 170 | * error text is saved in a system-independent location. 171 | * Wrote sysex.c to test sysex message handling. 172 | * 173 | * 15Jun02 BCT changes: 174 | * - Added pmHostError text handling. 175 | * - For robustness, check PortMidi stream args not NULL. 176 | * - Re-C-ANSI-fied code (changed many C++ comments to C style) 177 | * - Reorganized code in pmwinmm according to input/output functionality (made 178 | * cleanup handling easier to reason about) 179 | * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error) 180 | * - Cleaned up memory handling (now system specific data deleted via dictionary 181 | * call in PortMidi, allows client to query host errors). 182 | * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as 183 | * logic implies it should. Specifically: verified callback routines not reentrant and 184 | * all verified status for all unchecked Win32 MMedia API calls perform successfully 185 | * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API 186 | * bug (i.e. if devices not explicitly closed, must reboot to debug application further). 187 | * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or 188 | * explicitly Pm_Close open devices when using WinMM version of PortMidi. 189 | * 190 | * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling 191 | * 192 | * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent 193 | * opening an input as output and vice versa. 194 | * Added comments and documentation. 195 | * Implemented Pm_Terminate(). 196 | * 197 | */ 198 | -------------------------------------------------------------------------------- /bindings/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 *) (long) (*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) (long) 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 | -------------------------------------------------------------------------------- /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 DRIVER_INFO NULL 24 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ 25 | 26 | #define STRING_MAX 80 /* used for console input */ 27 | // need to get declaration for Sleep() 28 | #ifdef WIN32 29 | #include "windows.h" 30 | #else 31 | #include 32 | #define Sleep(n) usleep(n * 1000) 33 | #endif 34 | 35 | 36 | int32_t latency = 0; 37 | int32_t msgrate = 0; 38 | int deviceno = -9999; 39 | int duration = 0; 40 | int expired_timestamps = FALSE; 41 | int use_timeoffset = 0; 42 | 43 | /* read a number from console */ 44 | /**/ 45 | int get_number(const char *prompt) 46 | { 47 | char line[STRING_MAX]; 48 | int n = 0, i; 49 | fputs(prompt, stdout); 50 | while (n != 1) { 51 | n = scanf("%d", &i); 52 | fgets(line, STRING_MAX, stdin); 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() 73 | { 74 | PmStream * midi; 75 | char line[STRING_MAX]; 76 | int pause = FALSE; /* pause if this is a virtual output port */ 77 | PmError err; 78 | 79 | /* It is recommended to start timer before PortMidi */ 80 | TIME_START; 81 | 82 | /* open output device */ 83 | /* output buffer size should be a little more than 84 | msgrate * latency / 1000. PortMidi will guarantee 85 | a minimum of latency / 2 */ 86 | int buffer_size = msgrate * latency / 900; 87 | if (deviceno == Pm_CountDevices()) { 88 | err = Pm_CreateVirtualOutput(&midi, "fast", NULL, DRIVER_INFO, 89 | buffer_size, get_time, NULL, latency); 90 | pause = TRUE; 91 | } else { 92 | err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, 93 | get_time, NULL, latency); 94 | } 95 | if (err == pmHostError) { 96 | Pm_GetHostErrorText(line, STRING_MAX); 97 | printf("PortMidi found host error...\n %s\n", line); 98 | goto done; 99 | } else if (err < 0) { 100 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); 101 | goto done; 102 | } 103 | printf("Midi Output opened with %ld ms latency.\n", (long) latency); 104 | if (pause) { 105 | char line[STRING_MAX]; 106 | printf("Pausing so you can connect a receiver to the newly created\n" 107 | " \"fast\" port. Type ENTER to proceed: "); 108 | fgets(line, STRING_MAX, stdin); 109 | } 110 | /* wait a sec after printing previous line */ 111 | PmTimestamp start = get_time(NULL) + 1000; 112 | while (start > get_time(NULL)) { 113 | Sleep(10); 114 | } 115 | printf("sending output...\n"); 116 | fflush(stdout); /* make sure message goes to console */ 117 | 118 | /* every 10ms send on/off pairs at timestamps set to current time */ 119 | PmTimestamp now = get_time(NULL); 120 | int msgcnt = 0; 121 | int polling_count = 0; 122 | int pitch = 60; 123 | int printtime = 1000; 124 | /* if expired_timestamps, we want to send timestamps that have 125 | * expired. They should be sent immediately, but there's a suggestion 126 | * that negative delay might cause problems in the ALSA implementation 127 | * so this is something we can test using the -n flag. 128 | */ 129 | if (expired_timestamps) { 130 | now = now - 2 * latency; 131 | } 132 | 133 | while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) { 134 | /* how many messages do we send? Total should be 135 | * (elapsed * rate) / 1000 136 | */ 137 | int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000; 138 | /* always send until pitch would be 60 so if we run again, the 139 | next pitch (60) will be expected */ 140 | if (msgcnt < send_total) { 141 | if ((msgcnt & 1) == 0) { 142 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100)); 143 | } else { 144 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0)); 145 | /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */ 146 | pitch = (pitch - 59) % 12 + 60; 147 | } 148 | msgcnt += 1; 149 | if (((PmTimestamp) (now - start)) >= printtime) { 150 | printf("%d at %dms, polling count %d\n", msgcnt, now - start, 151 | polling_count); 152 | fflush(stdout); /* make sure message goes to console */ 153 | printtime += 1000; /* next msg in 1s */ 154 | } 155 | } 156 | now = get_time(NULL); 157 | polling_count++; 158 | } 159 | /* close device (this not explicitly needed in most implementations) */ 160 | printf("ready to close and terminate... (type RETURN):"); 161 | fgets(line, STRING_MAX, stdin); 162 | 163 | Pm_Close(midi); 164 | done: 165 | Pm_Terminate(); 166 | printf("done closing and terminating...\n"); 167 | } 168 | 169 | 170 | void show_usage() 171 | { 172 | printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] " 173 | "[-n] [-p] [-m]\n" 174 | ", where latency is in ms,\n" 175 | " rate is messages per second,\n" 176 | " device is the PortMidi device number,\n" 177 | " dur is the length of the test in seconds,\n" 178 | " -n means send timestamps in the past,\n" 179 | " -p means use a large positive time offset,\n" 180 | " -m means use a large negative time offset, and\n" 181 | " -h means help.\n"); 182 | } 183 | 184 | int main(int argc, char *argv[]) 185 | { 186 | int default_in; 187 | int default_out; 188 | char *deflt; 189 | int i = 0; 190 | int latency_valid = FALSE; 191 | int rate_valid = FALSE; 192 | int device_valid = FALSE; 193 | int dur_valid = FALSE; 194 | 195 | if (sizeof(void *) == 8) 196 | printf("Apparently this is a 64-bit machine.\n"); 197 | else if (sizeof(void *) == 4) 198 | printf ("Apparently this is a 32-bit machine.\n"); 199 | 200 | if (argc <= 1) { 201 | show_usage(); 202 | } else { 203 | for (i = 1; i < argc; i++) { 204 | if (strcmp(argv[i], "-h") == 0) { 205 | show_usage(); 206 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { 207 | i = i + 1; 208 | latency = atoi(argv[i]); 209 | printf("Latency will be %ld\n", (long) latency); 210 | latency_valid = TRUE; 211 | } else if (strcmp(argv[i], "-r") == 0) { 212 | i = i + 1; 213 | msgrate = atoi(argv[i]); 214 | printf("Rate will be %d messages/second\n", msgrate); 215 | rate_valid = TRUE; 216 | } else if (strcmp(argv[i], "-d") == 0) { 217 | i = i + 1; 218 | deviceno = atoi(argv[i]); 219 | printf("Device will be %d\n", deviceno); 220 | } else if (strcmp(argv[i], "-s") == 0) { 221 | i = i + 1; 222 | duration = atoi(argv[i]); 223 | printf("Duration will be %d seconds\n", duration); 224 | dur_valid = TRUE; 225 | } else if (strcmp(argv[i], "-n") == 0) { 226 | printf("Sending expired timestamps (-n)\n"); 227 | expired_timestamps = TRUE; 228 | } else if (strcmp(argv[i], "-p") == 0) { 229 | printf("Time offset set to 2147473648 (-p)\n"); 230 | use_timeoffset = 2147473648; 231 | } else if (strcmp(argv[i], "-m") == 0) { 232 | printf("Time offset set to -10000 (-m)\n"); 233 | use_timeoffset = -10000; 234 | } else { 235 | show_usage(); 236 | } 237 | } 238 | } 239 | 240 | if (!latency_valid) { 241 | // coerce to known size 242 | latency = (int32_t) get_number("Latency in ms: "); 243 | } 244 | 245 | if (!rate_valid) { 246 | // coerce from "%d" to known size 247 | msgrate = (int32_t) get_number("Rate in messages per second: "); 248 | } 249 | 250 | if (!dur_valid) { 251 | duration = get_number("Duration in seconds: "); 252 | } 253 | 254 | /* list device information */ 255 | default_in = Pm_GetDefaultInputDeviceID(); 256 | default_out = Pm_GetDefaultOutputDeviceID(); 257 | for (i = 0; i < Pm_CountDevices(); i++) { 258 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 259 | if (info->output) { 260 | printf("%d: %s, %s", i, info->interf, info->name); 261 | if (i == deviceno) { 262 | device_valid = TRUE; 263 | deflt = "selected "; 264 | } else if (i == default_out) { 265 | deflt = "default "; 266 | } else { 267 | deflt = ""; 268 | } 269 | printf(" (%soutput)\n", deflt); 270 | } 271 | } 272 | printf("%d: Create virtual port named \"fast\"", i); 273 | if (i == deviceno) { 274 | device_valid = TRUE; 275 | deflt = "selected "; 276 | } else { 277 | deflt = ""; 278 | } 279 | printf(" (%soutput)\n", deflt); 280 | 281 | if (!device_valid) { 282 | deviceno = get_number("Output device number: "); 283 | } 284 | 285 | fast_test(); 286 | return 0; 287 | } 288 | -------------------------------------------------------------------------------- /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(NULL); 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() 159 | { 160 | char line[STRING_MAX]; 161 | int i; 162 | int len; 163 | int choice; 164 | PtTimestamp stop; 165 | printf("Latency histogram.\n"); 166 | period = 0; 167 | while (period < 1) { 168 | period = get_number("Choose timer period (in ms, >= 1): "); 169 | } 170 | printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", 171 | "1. No MIDI traffic", 172 | "2. MIDI input", 173 | "3. MIDI output", 174 | "4. MIDI input and output"); 175 | choice = get_number("? "); 176 | switch (choice) { 177 | case 1: test_in = 0; test_out = 0; break; 178 | case 2: test_in = 1; test_out = 0; break; 179 | case 3: test_in = 0; test_out = 1; break; 180 | case 4: test_in = 1; test_out = 1; break; 181 | default: assert(0); 182 | } 183 | if (test_in || test_out) { 184 | /* list device information */ 185 | for (i = 0; i < Pm_CountDevices(); i++) { 186 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); 187 | if ((test_in && info->input) || 188 | (test_out && info->output)) { 189 | printf("%d: %s, %s", i, info->interf, info->name); 190 | if (info->input) printf(" (input)"); 191 | if (info->output) printf(" (output)"); 192 | printf("\n"); 193 | } 194 | } 195 | /* open stream(s) */ 196 | if (test_in) { 197 | int i = get_number("MIDI input device number: "); 198 | Pm_OpenInput(&in, 199 | i, 200 | NULL, 201 | INPUT_BUFFER_SIZE, 202 | (PmTimestamp (*)(void *)) Pt_Time, 203 | NULL); 204 | /* turn on filtering; otherwise, input might overflow in the 205 | 5-second period before timer callback starts reading midi */ 206 | Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); 207 | } 208 | if (test_out) { 209 | int i = get_number("MIDI output device number: "); 210 | PmEvent buffer[1]; 211 | Pm_OpenOutput(&out, 212 | i, 213 | NULL, 214 | OUTPUT_BUFFER_SIZE, 215 | (PmTimestamp (*)(void *)) Pt_Time, 216 | NULL, 217 | 0); /* no latency scheduling */ 218 | 219 | /* send a program change to force a status byte -- this fixes 220 | a problem with a buggy linux MidiSport driver, and shouldn't 221 | hurt anything else 222 | */ 223 | buffer[0].timestamp = 0; 224 | buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ 225 | Pm_Write(out, buffer, 1); 226 | 227 | output_period = get_number( 228 | "MIDI out should be sent every __ callback iterations: "); 229 | 230 | assert(output_period >= 1); 231 | } 232 | } 233 | 234 | printf("%s%s", "Latency measurements will start in 5 seconds. ", 235 | "Type return to stop: "); 236 | Pt_Start(period, &pt_callback, 0); 237 | fgets(line, STRING_MAX, stdin); 238 | stop = Pt_Time(); 239 | Pt_Stop(); 240 | 241 | /* courteously turn off the last note, if necessary */ 242 | if (note_on) { 243 | PmEvent buffer[1]; 244 | buffer[0].timestamp = Pt_Time(NULL); 245 | buffer[0].message = Pm_Message(0x90, 60, 0); 246 | Pm_Write(out, buffer, 1); 247 | } 248 | 249 | /* print the histogram */ 250 | printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); 251 | printf("Latency(ms) Number of occurrences\n"); 252 | /* avoid printing beyond last non-zero histogram entry */ 253 | len = min(HIST_LEN, max_latency + 1); 254 | for (i = 0; i < len; i++) { 255 | printf("%2d %10d\n", i, histogram[i]); 256 | } 257 | printf("Number of points greater than %dms: %d\n", 258 | HIST_LEN - 1, out_of_range); 259 | printf("Maximum latency: %d milliseconds\n", max_latency); 260 | printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); 261 | printf("than the numbers reported here.\n"); 262 | printf("Type return to exit..."); 263 | fgets(line, STRING_MAX, stdin); 264 | 265 | if(choice == 2) 266 | Pm_Close(in); 267 | else if(choice == 3) 268 | Pm_Close(out); 269 | else if(choice == 4) 270 | { 271 | Pm_Close(in); 272 | Pm_Close(out); 273 | } 274 | return 0; 275 | } 276 | 277 | 278 | /* read a number from console */ 279 | int get_number(const char *prompt) 280 | { 281 | char line[STRING_MAX]; 282 | int n = 0, i; 283 | fputs(prompt, stdout); 284 | while (n != 1) { 285 | n = scanf("%d", &i); 286 | fgets(line, STRING_MAX, stdin); 287 | 288 | } 289 | return i; 290 | } 291 | -------------------------------------------------------------------------------- /test/midithread.c: -------------------------------------------------------------------------------- 1 | /* midithread.c -- example program showing how to do midi processing 2 | in a preemptive thread 3 | 4 | Notes: if you handle midi I/O from your main program, there will be 5 | some delay before handling midi messages whenever the program is 6 | doing something like file I/O, graphical interface updates, etc. 7 | 8 | To handle midi with minimal delay, you should do all midi processing 9 | in a separate, high priority thread. A convenient way to get a high 10 | priority thread in windows is to use the timer callback provided by 11 | the PortTime library. That is what we show here. 12 | 13 | If the high priority thread writes to a file, prints to the console, 14 | or does just about anything other than midi processing, this may 15 | create delays, so all this processing should be off-loaded to the 16 | "main" process or thread. Communication between threads can be tricky. 17 | If one thread is writing at the same time the other is reading, very 18 | tricky race conditions can arise, causing programs to behave 19 | incorrectly, but only under certain timing conditions -- a terrible 20 | thing to debug. Advanced programmers know this as a synchronization 21 | problem. See any operating systems textbook for the complete story. 22 | 23 | To avoid synchronization problems, a simple, reliable approach is 24 | to communicate via messages. PortMidi offers a message queue as a 25 | datatype, and operations to insert and remove messages. Use two 26 | queues as follows: midi_to_main transfers messages from the midi 27 | thread to the main thread, and main_to_midi transfers messages from 28 | the main thread to the midi thread. Queues are safe for use between 29 | threads as long as ONE thread writes and ONE thread reads. You must 30 | NEVER allow two threads to write to the same queue. 31 | 32 | This program transposes incoming midi data by an amount controlled 33 | by the main program. To change the transposition, type an integer 34 | followed by return. The main program sends this via a message queue 35 | to the midi thread. To quit, type 'q' followed by return. 36 | 37 | The midi thread can also send a pitch to the main program on request. 38 | Type 'm' followed by return to wait for the next midi message and 39 | print the pitch. 40 | 41 | This program illustrates: 42 | Midi processing in a high-priority thread. 43 | Communication with a main process via message queues. 44 | 45 | */ 46 | 47 | #include "stdio.h" 48 | #include "stdlib.h" 49 | #include "string.h" 50 | #include "assert.h" 51 | #include "portmidi.h" 52 | #include "pmutil.h" 53 | #include "porttime.h" 54 | 55 | /* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */ 56 | #define INPUT_BUFFER_SIZE 0 57 | 58 | #define OUTPUT_BUFFER_SIZE 100 59 | #define DRIVER_INFO NULL 60 | #define TIME_PROC NULL 61 | #define TIME_INFO NULL 62 | /* use zero latency because we want output to be immediate */ 63 | #define LATENCY 0 64 | 65 | #define STRING_MAX 80 66 | 67 | /**********************************/ 68 | /* DATA USED ONLY BY process_midi */ 69 | /* (except during initialization) */ 70 | /**********************************/ 71 | 72 | int active = FALSE; 73 | int monitor = FALSE; 74 | int midi_thru = TRUE; 75 | 76 | int transpose; 77 | PmStream *midi_in; 78 | PmStream *midi_out; 79 | 80 | /****************************/ 81 | /* END OF process_midi DATA */ 82 | /****************************/ 83 | 84 | /* shared queues */ 85 | PmQueue *midi_to_main; 86 | PmQueue *main_to_midi; 87 | 88 | #define QUIT_MSG 1000 89 | #define MONITOR_MSG 1001 90 | #define THRU_MSG 1002 91 | 92 | /* timer interrupt for processing midi data */ 93 | void process_midi(PtTimestamp timestamp, void *userData) 94 | { 95 | PmError result; 96 | PmEvent buffer; /* just one message at a time */ 97 | int32_t msg; 98 | 99 | /* do nothing until initialization completes */ 100 | if (!active) 101 | return; 102 | 103 | /* check for messages */ 104 | do { 105 | result = Pm_Dequeue(main_to_midi, &msg); 106 | if (result) { 107 | if (msg >= -127 && msg <= 127) 108 | transpose = msg; 109 | else if (msg == QUIT_MSG) { 110 | /* acknowledge receipt of quit message */ 111 | Pm_Enqueue(midi_to_main, &msg); 112 | active = FALSE; 113 | return; 114 | } else if (msg == MONITOR_MSG) { 115 | /* main has requested a pitch. monitor is a flag that 116 | * records the request: 117 | */ 118 | monitor = TRUE; 119 | } else if (msg == THRU_MSG) { 120 | /* toggle Thru on or off */ 121 | midi_thru = !midi_thru; 122 | } 123 | } 124 | } while (result); 125 | 126 | /* see if there is any midi input to process */ 127 | do { 128 | result = Pm_Poll(midi_in); 129 | if (result) { 130 | int status, data1, data2; 131 | if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) 132 | continue; 133 | if (midi_thru) 134 | Pm_Write(midi_out, &buffer, 1); 135 | /* unless there was overflow, we should have a message now */ 136 | status = Pm_MessageStatus(buffer.message); 137 | data1 = Pm_MessageData1(buffer.message); 138 | data2 = Pm_MessageData2(buffer.message); 139 | if ((status & 0xF0) == 0x90 || 140 | (status & 0xF0) == 0x80) { 141 | 142 | /* this is a note-on or note-off, so transpose and send */ 143 | data1 += transpose; 144 | 145 | /* keep within midi pitch range, keep proper pitch class */ 146 | while (data1 > 127) 147 | data1 -= 12; 148 | while (data1 < 0) 149 | data1 += 12; 150 | 151 | /* send the message */ 152 | buffer.message = Pm_Message(status, data1, data2); 153 | Pm_Write(midi_out, &buffer, 1); 154 | 155 | /* if monitor is set, send the pitch to the main thread */ 156 | if (monitor) { 157 | Pm_Enqueue(midi_to_main, &data1); 158 | monitor = FALSE; /* only send one pitch per request */ 159 | } 160 | } 161 | } 162 | } while (result); 163 | } 164 | 165 | void exit_with_message(char *msg) 166 | { 167 | char line[STRING_MAX]; 168 | printf("%s\n", msg); 169 | fgets(line, STRING_MAX, stdin); 170 | exit(1); 171 | } 172 | 173 | int main(int argc, char *argv[]) 174 | { 175 | int32_t n; 176 | const PmDeviceInfo *info; 177 | char line[STRING_MAX]; 178 | int spin; 179 | int done = FALSE; 180 | int i; 181 | int input = -1, output = -1; 182 | 183 | printf("Usage: midithread [-i input] [-o output]\n" 184 | "where input and output are portmidi device numbers\n"); 185 | for (i = 1; i < argc; i++) { 186 | if (strcmp(argv[i], "-i") == 0) { 187 | i++; 188 | input = atoi(argv[i]); 189 | printf("Input device number: %d\n", input); 190 | } else if (strcmp(argv[i], "-o") == 0) { 191 | i++; 192 | output = atoi(argv[i]); 193 | printf("Output device number: %d\n", output); 194 | } else { 195 | return -1; 196 | } 197 | } 198 | printf("begin PortMidi multithread test...\n"); 199 | 200 | /* note that it is safe to call PortMidi from the main thread for 201 | initialization and opening devices. You should not make any 202 | calls to PortMidi from this thread once the midi thread begins. 203 | to make PortMidi calls. 204 | */ 205 | 206 | /* make the message queues */ 207 | /* messages can be of any size and any type, but all messages in 208 | * a given queue must have the same size. We'll just use int32_t's 209 | * for our messages in this simple example 210 | */ 211 | midi_to_main = Pm_QueueCreate(32, sizeof(int32_t)); 212 | assert(midi_to_main != NULL); 213 | main_to_midi = Pm_QueueCreate(32, sizeof(int32_t)); 214 | assert(main_to_midi != NULL); 215 | 216 | /* a little test of enqueue and dequeue operations. Ordinarily, 217 | * you would call Pm_Enqueue from one thread and Pm_Dequeue from 218 | * the other. Since the midi thread is not running, this is safe. 219 | */ 220 | n = 1234567890; 221 | Pm_Enqueue(midi_to_main, &n); 222 | n = 987654321; 223 | Pm_Enqueue(midi_to_main, &n); 224 | Pm_Dequeue(midi_to_main, &n); 225 | if (n != 1234567890) { 226 | exit_with_message("Pm_Dequeue produced unexpected result."); 227 | } 228 | Pm_Dequeue(midi_to_main, &n); 229 | if(n != 987654321) { 230 | exit_with_message("Pm_Dequeue produced unexpected result."); 231 | } 232 | 233 | /* always start the timer before you start midi */ 234 | Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */ 235 | /* the timer will call our function, process_midi() every millisecond */ 236 | 237 | Pm_Initialize(); 238 | 239 | output = (output < 0 ? Pm_GetDefaultOutputDeviceID() : output); 240 | info = Pm_GetDeviceInfo(output); 241 | if (info == NULL) { 242 | printf("Could not open output device (%d).", output); 243 | exit_with_message(""); 244 | } 245 | printf("Opening output device %s %s\n", info->interf, info->name); 246 | 247 | /* use zero latency because we want output to be immediate */ 248 | Pm_OpenOutput(&midi_out, 249 | output, 250 | DRIVER_INFO, 251 | OUTPUT_BUFFER_SIZE, 252 | TIME_PROC, 253 | TIME_INFO, 254 | LATENCY); 255 | 256 | input = (input < 0 ? Pm_GetDefaultInputDeviceID() : input); 257 | info = Pm_GetDeviceInfo(input); 258 | if (info == NULL) { 259 | printf("Could not open default input device (%d).", input); 260 | exit_with_message(""); 261 | } 262 | printf("Opening input device %s %s\n", info->interf, info->name); 263 | Pm_OpenInput(&midi_in, 264 | input, 265 | DRIVER_INFO, 266 | INPUT_BUFFER_SIZE, 267 | TIME_PROC, 268 | TIME_INFO); 269 | 270 | active = TRUE; /* enable processing in the midi thread -- yes, this 271 | is a shared variable without synchronization, but 272 | this simple assignment is safe */ 273 | 274 | printf("Enter midi input; it will be transformed as specified by...\n"); 275 | printf("%s\n%s\n%s\n", 276 | "Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or", 277 | "type a number to specify transposition.", 278 | "Must terminate with [ENTER]"); 279 | 280 | while (!done) { 281 | int32_t msg; 282 | int input; 283 | int len; 284 | fgets(line, STRING_MAX, stdin); 285 | /* remove the newline: */ 286 | len = (int) strlen(line); 287 | if (len > 0) line[len - 1] = 0; /* overwrite the newline char */ 288 | if (strcmp(line, "q") == 0) { 289 | msg = QUIT_MSG; 290 | Pm_Enqueue(main_to_midi, &msg); 291 | /* wait for acknowlegement */ 292 | do { 293 | spin = Pm_Dequeue(midi_to_main, &msg); 294 | } while (spin == 0); /* spin */ ; 295 | done = TRUE; /* leave the command loop and wrap up */ 296 | } else if (strcmp(line, "m") == 0) { 297 | msg = MONITOR_MSG; 298 | Pm_Enqueue(main_to_midi, &msg); 299 | printf("Waiting for note...\n"); 300 | do { 301 | spin = Pm_Dequeue(midi_to_main, &msg); 302 | } while (spin == 0); /* spin */ ; 303 | // convert int32_t to long for safe printing 304 | printf("... pitch is %ld\n", (long) msg); 305 | } else if (strcmp(line, "t") == 0) { 306 | /* reading midi_thru asynchronously could give incorrect results, 307 | e.g. if you type "t" twice before the midi thread responds to 308 | the first one, but we'll do it this way anyway. Perhaps a more 309 | correct way would be to wait for an acknowledgement message 310 | containing the new state. */ 311 | printf("Setting THRU %s\n", (midi_thru ? "off" : "on")); 312 | msg = THRU_MSG; 313 | Pm_Enqueue(main_to_midi, &msg); 314 | } else if (sscanf(line, "%d", &input) == 1) { 315 | if (input >= -127 && input <= 127) { 316 | /* send transposition value, make sur */ 317 | printf("Transposing by %d\n", input); 318 | msg = (int32_t) input; 319 | Pm_Enqueue(main_to_midi, &msg); 320 | } else { 321 | printf("Transposition must be within -127...127\n"); 322 | } 323 | } else { 324 | printf("%s\n%s\n", 325 | "Type 'q[ENTER]' to quit, 'm[ENTER]' to monitor next pitch, or", 326 | "enter a number to specify transposition."); 327 | } 328 | } 329 | 330 | /* at this point, midi thread is inactive and we need to shut down 331 | * the midi input and output 332 | */ 333 | Pt_Stop(); /* stop the timer */ 334 | Pm_QueueDestroy(midi_to_main); 335 | Pm_QueueDestroy(main_to_midi); 336 | 337 | /* Belinda! if close fails here, some memory is deleted, right??? */ 338 | Pm_Close(midi_in); 339 | Pm_Close(midi_out); 340 | 341 | printf("finished portMidi multithread test...enter any character to quit [RETURN]..."); 342 | fgets(line, STRING_MAX, stdin); 343 | return 0; 344 | } 345 | --------------------------------------------------------------------------------