aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile10
-rw-r--r--README.md8
-rw-r--r--example1.c23
-rw-r--r--example2.c21
-rw-r--r--example3.c53
-rwxr-xr-xportmidi.h974
-rw-r--r--portmidi/.github/workflows/build.yml47
-rw-r--r--portmidi/.github/workflows/docs.yml28
-rw-r--r--portmidi/.gitignore62
-rw-r--r--portmidi/CHANGELOG.txt213
-rw-r--r--portmidi/CMakeLists.txt188
-rw-r--r--portmidi/Doxyfile2682
-rw-r--r--portmidi/README.md128
-rwxr-xr-xportmidi/README.txt88
-rw-r--r--portmidi/license.txt40
-rw-r--r--portmidi/packaging/PortMidiConfig.cmake.in15
-rw-r--r--portmidi/packaging/portmidi.pc.in11
-rw-r--r--portmidi/pm_common/CMakeLists.txt167
-rwxr-xr-xportmidi/pm_common/pminternal.h190
-rwxr-xr-xportmidi/pm_common/pmutil.c284
-rwxr-xr-xportmidi/pm_common/pmutil.h184
-rwxr-xr-xportmidi/pm_common/portmidi.c1472
-rwxr-xr-xportmidi/pm_common/portmidi.h974
-rw-r--r--portmidi/pm_haiku/pmhaiku.cpp473
-rw-r--r--portmidi/pm_java/CMakeLists.txt56
-rw-r--r--portmidi/pm_java/README.txt62
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidi.java541
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidiApi.java117
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidiException.java12
-rw-r--r--portmidi/pm_java/make.bat50
-rw-r--r--portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h293
-rw-r--r--portmidi/pm_java/pmjni/pmjni.c354
-rw-r--r--portmidi/pm_java/pmjni/pmjni.rc63
-rwxr-xr-xportmidi/pm_linux/README_LINUX.txt99
-rwxr-xr-xportmidi/pm_linux/pmlinux.c68
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.c893
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.h6
-rw-r--r--portmidi/pm_linux/pmlinuxnull.c31
-rw-r--r--portmidi/pm_linux/pmlinuxnull.h6
-rwxr-xr-xportmidi/pm_mac/Makefile.osx125
-rw-r--r--portmidi/pm_mac/README_MAC.txt65
-rwxr-xr-xportmidi/pm_mac/pmmac.c44
-rwxr-xr-xportmidi/pm_mac/pmmacosxcm.c1179
-rwxr-xr-xportmidi/pm_mac/pmmacosxcm.h4
-rw-r--r--portmidi/pm_sndio/pmsndio.c365
-rw-r--r--portmidi/pm_sndio/pmsndio.h5
-rw-r--r--portmidi/pm_test/CMakeLists.txt46
-rw-r--r--portmidi/pm_test/README.txt363
-rw-r--r--portmidi/pm_test/fast.c290
-rw-r--r--portmidi/pm_test/fastrcv.c255
-rwxr-xr-xportmidi/pm_test/latency.c287
-rw-r--r--portmidi/pm_test/midiclock.c282
-rwxr-xr-xportmidi/pm_test/midithread.c343
-rwxr-xr-xportmidi/pm_test/midithru.c455
-rwxr-xr-xportmidi/pm_test/mm.c595
-rw-r--r--portmidi/pm_test/multivirtual.c223
-rw-r--r--portmidi/pm_test/pmlist.c63
-rw-r--r--portmidi/pm_test/qtest.c174
-rw-r--r--portmidi/pm_test/recvvirtual.c175
-rw-r--r--portmidi/pm_test/sendvirtual.c194
-rwxr-xr-xportmidi/pm_test/sysex.c556
-rwxr-xr-xportmidi/pm_test/testio.c594
-rwxr-xr-xportmidi/pm_test/txdata.syx257
-rw-r--r--portmidi/pm_test/virttest.c339
-rwxr-xr-xportmidi/pm_win/README_WIN.txt174
-rwxr-xr-xportmidi/pm_win/debugging_dlls.txt145
-rwxr-xr-xportmidi/pm_win/pmwin.c98
-rwxr-xr-xportmidi/pm_win/pmwinmm.c1196
-rwxr-xr-xportmidi/pm_win/pmwinmm.h5
-rw-r--r--portmidi/pm_win/static.cmake24
-rw-r--r--portmidi/portmusic_logo.pngbin0 -> 753 bytes
-rwxr-xr-xportmidi/porttime/porttime.c3
-rwxr-xr-xportmidi/porttime/porttime.h103
-rw-r--r--portmidi/porttime/pthaiku.cpp88
-rwxr-xr-xportmidi/porttime/ptlinux.c139
-rwxr-xr-xportmidi/porttime/ptmacosx_cf.c140
-rwxr-xr-xportmidi/porttime/ptmacosx_mach.c204
-rwxr-xr-xportmidi/porttime/ptwinmm.c70
-rw-r--r--tml.h531
80 files changed, 21161 insertions, 27 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c21c3a6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
1example1
2example2
3example3
4
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..51e4b2d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
1tests: build-portmidi
2 $(CC) -Wall example1.c minisdl_audio.c -lm -ldl -lpthread -o example1
3 $(CC) -Wall example2.c minisdl_audio.c -lm -ldl -lpthread -o example2
4 $(CC) -Wall example3.c -L./portmidi -lportmidi -o example3
5
6run-example3:
7 LD_LIBRARY_PATH=./portmidi ./example3
8
9build-portmidi:
10 cd portmidi && cmake . && make
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..379eb42
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
1# Tiny terminal DAW
2
3## Soundfonts
4
5- https://dev.nando.audio/pages/soundfonts.html
6- https://archive.org/details/500-soundfonts-full-gm-sets
7- https://github.com/marmooo/free-soundfonts
8
diff --git a/example1.c b/example1.c
index ce38a7c..200fd1b 100644
--- a/example1.c
+++ b/example1.c
@@ -4,9 +4,8 @@
4#include "minisdl_audio.h" 4#include "minisdl_audio.h"
5 5
6//This is a minimal SoundFont with a single loopin saw-wave sample/instrument/preset (484 bytes) 6//This is a minimal SoundFont with a single loopin saw-wave sample/instrument/preset (484 bytes)
7const static unsigned char MinimalSoundFont[] = 7const static unsigned char MinimalSoundFont[] = {
8{ 8#define TEN0 0,0,0,0,0,0,0,0,0,0
9 #define TEN0 0,0,0,0,0,0,0,0,0,0
10 'R','I','F','F',220,1,0,0,'s','f','b','k', 9 'R','I','F','F',220,1,0,0,'s','f','b','k',
11 'L','I','S','T',88,1,0,0,'p','d','t','a', 10 'L','I','S','T',88,1,0,0,'p','d','t','a',
12 'p','h','d','r',76,TEN0,TEN0,TEN0,TEN0,0,0,0,0,TEN0,0,0,0,0,0,0,0,255,0,255,0,1,TEN0,0,0,0, 11 'p','h','d','r',76,TEN0,TEN0,TEN0,TEN0,0,0,0,0,TEN0,0,0,0,0,0,0,0,255,0,255,0,1,TEN0,0,0,0,
@@ -16,15 +15,14 @@ const static unsigned char MinimalSoundFont[] =
16 'i','g','e','n',12,0,0,0,54,0,1,0,53,0,0,0,0,0,0,0, 15 'i','g','e','n',12,0,0,0,54,0,1,0,53,0,0,0,0,0,0,0,
17 's','h','d','r',92,TEN0,TEN0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,49,0,0,0,34,86,0,0,60,0,0,0,1,TEN0,TEN0,TEN0,TEN0,0,0,0,0,0,0,0, 16 's','h','d','r',92,TEN0,TEN0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,49,0,0,0,34,86,0,0,60,0,0,0,1,TEN0,TEN0,TEN0,TEN0,0,0,0,0,0,0,0,
18 'L','I','S','T',112,0,0,0,'s','d','t','a','s','m','p','l',100,0,0,0,86,0,119,3,31,7,147,10,43,14,169,17,58,21,189,24,73,28,204,31,73,35,249,38,46,42,71,46,250,48,150,53,242,55,126,60,151,63,108,66,126,72,207, 17 'L','I','S','T',112,0,0,0,'s','d','t','a','s','m','p','l',100,0,0,0,86,0,119,3,31,7,147,10,43,14,169,17,58,21,189,24,73,28,204,31,73,35,249,38,46,42,71,46,250,48,150,53,242,55,126,60,151,63,108,66,126,72,207,
19 70,86,83,100,72,74,100,163,39,241,163,59,175,59,179,9,179,134,187,6,186,2,194,5,194,15,200,6,202,96,206,159,209,35,213,213,216,45,220,221,223,76,227,221,230,91,234,242,237,105,241,8,245,118,248,32,252 18 70,86,83,100,72,74,100,163,39,241,163,59,175,59,179,9,179,134,187,6,186,2,194,5,194,15,200,6,202,96,206,159,209,35,213,213,216,45,220,221,223,76,227,221,230,91,234,242,237,105,241,8,245,118,248,32,252
20}; 19};
21 20
22// Holds the global instance pointer 21// Holds the global instance pointer
23static tsf* g_TinySoundFont; 22static tsf* g_TinySoundFont;
24 23
25// Callback function called by the audio thread 24// Callback function called by the audio thread
26static void AudioCallback(void* data, Uint8 *stream, int len) 25static void AudioCallback(void* data, Uint8 *stream, int len) {
27{
28 // Note we don't do any thread concurrency control here because in this 26 // Note we don't do any thread concurrency control here because in this
29 // example all notes are started before the audio playback begins. 27 // example all notes are started before the audio playback begins.
30 // If you do play notes while the audio thread renders output you 28 // If you do play notes while the audio thread renders output you
@@ -33,8 +31,7 @@ static void AudioCallback(void* data, Uint8 *stream, int len)
33 tsf_render_short(g_TinySoundFont, (short*)stream, SampleCount, 0); 31 tsf_render_short(g_TinySoundFont, (short*)stream, SampleCount, 0);
34} 32}
35 33
36int main(int argc, char *argv[]) 34int main(int argc, char *argv[]) {
37{
38 // Define the desired audio output format we request 35 // Define the desired audio output format we request
39 SDL_AudioSpec OutputAudioSpec; 36 SDL_AudioSpec OutputAudioSpec;
40 OutputAudioSpec.freq = 44100; 37 OutputAudioSpec.freq = 44100;
@@ -44,16 +41,14 @@ int main(int argc, char *argv[])
44 OutputAudioSpec.callback = AudioCallback; 41 OutputAudioSpec.callback = AudioCallback;
45 42
46 // Initialize the audio system 43 // Initialize the audio system
47 if (SDL_AudioInit(NULL) < 0) 44 if (SDL_AudioInit(NULL) < 0) {
48 {
49 fprintf(stderr, "Could not initialize audio hardware or driver\n"); 45 fprintf(stderr, "Could not initialize audio hardware or driver\n");
50 return 1; 46 return 1;
51 } 47 }
52 48
53 // Load the SoundFont from the memory block 49 // Load the SoundFont from the memory block
54 g_TinySoundFont = tsf_load_memory(MinimalSoundFont, sizeof(MinimalSoundFont)); 50 g_TinySoundFont = tsf_load_memory(MinimalSoundFont, sizeof(MinimalSoundFont));
55 if (!g_TinySoundFont) 51 if (!g_TinySoundFont) {
56 {
57 fprintf(stderr, "Could not load soundfont\n"); 52 fprintf(stderr, "Could not load soundfont\n");
58 return 1; 53 return 1;
59 } 54 }
@@ -66,8 +61,7 @@ int main(int argc, char *argv[])
66 tsf_note_on(g_TinySoundFont, 0, 52, 1.0f); //E2 61 tsf_note_on(g_TinySoundFont, 0, 52, 1.0f); //E2
67 62
68 // Request the desired audio output format 63 // Request the desired audio output format
69 if (SDL_OpenAudio(&OutputAudioSpec, NULL) < 0) 64 if (SDL_OpenAudio(&OutputAudioSpec, NULL) < 0) {
70 {
71 fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n"); 65 fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n");
72 return 1; 66 return 1;
73 } 67 }
@@ -84,3 +78,4 @@ int main(int argc, char *argv[])
84 // because the process ends here. 78 // because the process ends here.
85 return 0; 79 return 0;
86} 80}
81
diff --git a/example2.c b/example2.c
index ea14db1..cfc0fc0 100644
--- a/example2.c
+++ b/example2.c
@@ -9,8 +9,7 @@ static tsf* g_TinySoundFont;
9// A Mutex so we don't call note_on/note_off while rendering audio samples 9// A Mutex so we don't call note_on/note_off while rendering audio samples
10static SDL_mutex* g_Mutex; 10static SDL_mutex* g_Mutex;
11 11
12static void AudioCallback(void* data, Uint8 *stream, int len) 12static void AudioCallback(void* data, Uint8 *stream, int len) {
13{
14 // Render the audio samples in float format 13 // Render the audio samples in float format
15 int SampleCount = (len / (2 * sizeof(float))); //2 output channels 14 int SampleCount = (len / (2 * sizeof(float))); //2 output channels
16 SDL_LockMutex(g_Mutex); //get exclusive lock 15 SDL_LockMutex(g_Mutex); //get exclusive lock
@@ -18,8 +17,7 @@ static void AudioCallback(void* data, Uint8 *stream, int len)
18 SDL_UnlockMutex(g_Mutex); 17 SDL_UnlockMutex(g_Mutex);
19} 18}
20 19
21int main(int argc, char *argv[]) 20int main(int argc, char *argv[]) {
22{
23 int i, Notes[7] = { 48, 50, 52, 53, 55, 57, 59 }; 21 int i, Notes[7] = { 48, 50, 52, 53, 55, 57, 59 };
24 22
25 // Define the desired audio output format we request 23 // Define the desired audio output format we request
@@ -31,16 +29,14 @@ int main(int argc, char *argv[])
31 OutputAudioSpec.callback = AudioCallback; 29 OutputAudioSpec.callback = AudioCallback;
32 30
33 // Initialize the audio system 31 // Initialize the audio system
34 if (SDL_AudioInit(TSF_NULL) < 0) 32 if (SDL_AudioInit(TSF_NULL) < 0) {
35 {
36 fprintf(stderr, "Could not initialize audio hardware or driver\n"); 33 fprintf(stderr, "Could not initialize audio hardware or driver\n");
37 return 1; 34 return 1;
38 } 35 }
39 36
40 // Load the SoundFont from a file 37 // Load the SoundFont from a file
41 g_TinySoundFont = tsf_load_filename("florestan-subset.sf2"); 38 g_TinySoundFont = tsf_load_filename("sf2/florestan-subset.sf2");
42 if (!g_TinySoundFont) 39 if (!g_TinySoundFont) {
43 {
44 fprintf(stderr, "Could not load SoundFont\n"); 40 fprintf(stderr, "Could not load SoundFont\n");
45 return 1; 41 return 1;
46 } 42 }
@@ -52,8 +48,7 @@ int main(int argc, char *argv[])
52 g_Mutex = SDL_CreateMutex(); 48 g_Mutex = SDL_CreateMutex();
53 49
54 // Request the desired audio output format 50 // Request the desired audio output format
55 if (SDL_OpenAudio(&OutputAudioSpec, TSF_NULL) < 0) 51 if (SDL_OpenAudio(&OutputAudioSpec, TSF_NULL) < 0) {
56 {
57 fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n"); 52 fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n");
58 return 1; 53 return 1;
59 } 54 }
@@ -63,8 +58,7 @@ int main(int argc, char *argv[])
63 SDL_PauseAudio(0); 58 SDL_PauseAudio(0);
64 59
65 // Loop through all the presets in the loaded SoundFont 60 // Loop through all the presets in the loaded SoundFont
66 for (i = 0; i < tsf_get_presetcount(g_TinySoundFont); i++) 61 for (i = 0; i < tsf_get_presetcount(g_TinySoundFont); i++) {
67 {
68 //Get exclusive mutex lock, end the previous note and play a new note 62 //Get exclusive mutex lock, end the previous note and play a new note
69 printf("Play note %d with preset #%d '%s'\n", Notes[i % 7], i, tsf_get_presetname(g_TinySoundFont, i)); 63 printf("Play note %d with preset #%d '%s'\n", Notes[i % 7], i, tsf_get_presetname(g_TinySoundFont, i));
70 SDL_LockMutex(g_Mutex); 64 SDL_LockMutex(g_Mutex);
@@ -79,3 +73,4 @@ int main(int argc, char *argv[])
79 // because the process ends here. 73 // because the process ends here.
80 return 0; 74 return 0;
81} 75}
76
diff --git a/example3.c b/example3.c
new file mode 100644
index 0000000..9dea995
--- /dev/null
+++ b/example3.c
@@ -0,0 +1,53 @@
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5#include "portmidi/pm_common/portmidi.h"
6
7#define NUM_INPUTS 16
8
9int main() {
10 PmError err;
11 PmStream *stream;
12 PmEvent events[128];
13 int i, num_events;
14
15 // Initialize PortMIDI
16 err = Pm_Initialize();
17 if (err != pmNoError) {
18 fprintf(stderr, "Error initializing PortMIDI: %s\n", Pm_GetErrorText(err));
19 exit(1);
20 }
21
22 /* // Open a MIDI input device */
23 /* stream = Pm_OpenInput(&err, NULL, NULL, NUM_INPUTS, NULL, 0); */
24 /* if (err != pmNoError) { */
25 /* fprintf(stderr, "Error opening MIDI input device: %s\n", Pm_GetErrorText(err)); */
26 /* Pm_Terminate(); */
27 /* exit(1); */
28 /* } */
29
30 /* // Read MIDI messages from the input device */
31 /* while (1) { */
32 /* num_events = Pm_Read(stream, events, 128); */
33 /* if (num_events > 0) { */
34 /* for (i = 0; i < num_events; i++) { */
35 /* if (events[i].message & 0xff00) { */
36 /* // This is a status message (note on, note off, etc.) */
37 /* printf("Message: 0x%02x\n", events[i].message); */
38 /* /1* printf("Status: 0x%02x, Data 1: 0x%02x, Data 2: 0x%02x\n", *1/ */
39 /* /1* events[i].message & 0xf0, events[i].message[0], events[i].message[1]); *1/ */
40 /* } */
41 /* } */
42 /* } */
43 /* } */
44
45 /* // Close the MIDI input device */
46 /* Pm_Close(stream); */
47
48 // Terminate PortMIDI
49 Pm_Terminate();
50
51 return 0;
52}
53
diff --git a/portmidi.h b/portmidi.h
new file mode 100755
index 0000000..8696a73
--- /dev/null
+++ b/portmidi.h
@@ -0,0 +1,974 @@
1#ifndef PORTMIDI_PORTMIDI_H
2#define PORTMIDI_PORTMIDI_H
3
4#ifdef __cplusplus
5extern "C" {
6#endif /* __cplusplus */
7
8/*
9 * PortMidi Portable Real-Time MIDI Library
10 * PortMidi API Header File
11 * Latest version available at: http://sourceforge.net/projects/portmedia
12 *
13 * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
14 * Copyright (c) 2001-2006 Roger B. Dannenberg
15 *
16 * Permission is hereby granted, free of charge, to any person obtaining
17 * a copy of this software and associated documentation files
18 * (the "Software"), to deal in the Software without restriction,
19 * including without limitation the rights to use, copy, modify, merge,
20 * publish, distribute, sublicense, and/or sell copies of the Software,
21 * and to permit persons to whom the Software is furnished to do so,
22 * subject to the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be
25 * included in all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
31 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
32 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 */
35
36/*
37 * The text above constitutes the entire PortMidi license; however,
38 * the PortMusic community also makes the following non-binding requests:
39 *
40 * Any person wishing to distribute modifications to the Software is
41 * requested to send the modifications to the original developer so that
42 * they can be incorporated into the canonical version. It is also
43 * requested that these non-binding requests be included along with the
44 * license above.
45 */
46
47/* CHANGELOG FOR PORTMIDI
48 * (see ../CHANGELOG.txt)
49 *
50 * NOTES ON HOST ERROR REPORTING:
51 *
52 * PortMidi errors (of type PmError) are generic,
53 * system-independent errors. When an error does not map to one of
54 * the more specific PmErrors, the catch-all code pmHostError is
55 * returned. This means that PortMidi has retained a more specific
56 * system-dependent error code. The caller can get more information
57 * by calling Pm_GetHostErrorText() to get a text string describing
58 * the error. Host errors can arise asynchronously from callbacks,
59 * * so there is no specific return code. Asynchronous errors are
60 * checked and reported by Pm_Poll. You can also check by calling
61 * Pm_HasHostError(). If this returns TRUE, Pm_GetHostErrorText()
62 * will return a text description of the error.
63 *
64 * NOTES ON COMPILE-TIME SWITCHES
65 *
66 * DEBUG assumes stdio and a console. Use this if you want
67 * automatic, simple error reporting, e.g. for prototyping. If
68 * you are using MFC or some other graphical interface with no
69 * console, DEBUG probably should be undefined.
70 * PM_CHECK_ERRORS more-or-less takes over error checking for
71 * return values, stopping your program and printing error
72 * messages when an error occurs. This also uses stdio for
73 * console text I/O. You can selectively disable this error
74 * checking by declaring extern int pm_check_errors; and
75 * setting pm_check_errors = FALSE; You can also reenable.
76 */
77/**
78 \defgroup grp_basics Basic Definitions
79 @{
80*/
81
82#include <stdint.h>
83
84#ifdef _WINDLL
85#define PMEXPORT __declspec(dllexport)
86#else
87#define PMEXPORT
88#endif
89
90#ifndef FALSE
91 #define FALSE 0
92#endif
93#ifndef TRUE
94 #define TRUE 1
95#endif
96
97/* default size of buffers for sysex transmission: */
98#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
99
100
101typedef enum {
102 pmNoError = 0, /**< Normal return value indicating no error. */
103 pmNoData = 0, /**< @brief No error, also indicates no data available.
104 * Use this constant where a value greater than zero would
105 * indicate data is available.
106 */
107 pmGotData = 1, /**< A "no error" return also indicating data available. */
108 pmHostError = -10000,
109 pmInvalidDeviceId, /**< Out of range or
110 * output device when input is requested or
111 * input device when output is requested or
112 * device is already opened.
113 */
114 pmInsufficientMemory,
115 pmBufferTooSmall,
116 pmBufferOverflow,
117 pmBadPtr, /**< #PortMidiStream parameter is NULL or
118 * stream is not opened or
119 * stream is output when input is required or
120 * stream is input when output is required. */
121 pmBadData, /**< Illegal midi data, e.g., missing EOX. */
122 pmInternalError,
123 pmBufferMaxSize, /**< Buffer is already as large as it can be. */
124 pmNotImplemented, /**< The function is not implemented, nothing was done. */
125 pmInterfaceNotSupported, /**< The requested interface is not supported. */
126 pmNameConflict, /**< Cannot create virtual device because name is taken. */
127 pmDeviceRemoved /**< Output attempted after (USB) device was removed. */
128 /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */
129} PmError; /**< @brief @enum PmError PortMidi error code; a common return type.
130 * No error is indicated by zero; errors are indicated by < 0.
131 */
132
133/**
134 Pm_Initialize() is the library initialization function - call this before
135 using the library.
136
137 *NOTE:* PortMidi scans for available devices when #Pm_Initialize
138 is called. To observe subsequent changes in the available
139 devices, you must shut down PortMidi by calling #Pm_Terminate and
140 then restart by calling #Pm_Initialize again. *IMPORTANT*: On
141 MacOS, #Pm_Initialize *must* always be called on the same
142 thread. Otherwise, changes in the available MIDI devices will
143 *not* be seen by PortMidi. As an example, if you start PortMidi in
144 a thread for processing MIDI, do not try to rescan devices by
145 calling #Pm_Initialize in a GUI thread. Instead, start PortMidi
146 the first time and every time in the GUI thread. Alternatively,
147 let the GUI request a restart in the MIDI thread. (These
148 restrictions only apply to macOS.) Speaking of threads, on all
149 platforms, you are allowed to call #Pm_Initialize in one thread,
150 yet send MIDI or poll for incoming MIDI in another
151 thread. However, PortMidi is not "thread safe," which means you
152 cannot allow threads to call PortMidi functions concurrently.
153
154 @return pmNoError.
155
156 PortMidi is designed to support multiple interfaces (such as ALSA,
157 CoreMIDI and WinMM). It is possible to return pmNoError because there
158 are no supported interfaces. In that case, zero devices will be
159 available.
160*/
161PMEXPORT PmError Pm_Initialize(void);
162
163/**
164 Pm_Terminate() is the library termination function - call this after
165 using the library.
166*/
167PMEXPORT PmError Pm_Terminate(void);
168
169/** Represents an open MIDI device. */
170typedef void PortMidiStream;
171
172/** A shorter form of #PortMidiStream. */
173#define PmStream PortMidiStream
174
175/** Test whether stream has a pending host error. Normally, the client
176 finds out about errors through returned error codes, but some
177 errors can occur asynchronously where the client does not
178 explicitly call a function, and therefore cannot receive an error
179 code. The client can test for a pending error using
180 Pm_HasHostError(). If true, the error can be accessed by calling
181 Pm_GetHostErrorText(). Pm_Poll() is similar to Pm_HasHostError(),
182 but if there is no error, it will return TRUE (1) if there is a
183 pending input message.
184*/
185PMEXPORT int Pm_HasHostError(PortMidiStream * stream);
186
187
188/** Translate portmidi error number into human readable message.
189 These strings are constants (set at compile time) so client has
190 no need to allocate storage.
191*/
192PMEXPORT const char *Pm_GetErrorText(PmError errnum);
193
194/** Translate portmidi host error into human readable message.
195 These strings are computed at run time, so client has to allocate storage.
196 After this routine executes, the host error is cleared.
197*/
198PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);
199
200/** Any host error msg has at most this many characters, including EOS. */
201#define PM_HOST_ERROR_MSG_LEN 256u
202
203/** Devices are represented as small integers. Device ids range from 0
204 to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information
205 about the device, and Pm_OpenInput() and PmOpenOutput() are used to
206 open the device.
207*/
208typedef int PmDeviceID;
209
210/** This PmDeviceID (constant) value represents no device and may be
211 returned by Pm_GetDefaultInputDeviceID() or
212 Pm_GetDefaultOutputDeviceID() if no default exists.
213*/
214#define pmNoDevice -1
215
216/** MIDI device information is returned in this structure, which is
217 owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo().
218*/
219#define PM_DEVICEINFO_VERS 200
220typedef struct {
221 int structVersion; /**< @brief this internal structure version */
222 const char *interf; /**< @brief underlying MIDI API, e.g.
223 "MMSystem" or "DirectX" */
224 char *name; /**< @brief device name, e.g. "USB MidiSport 1x1" */
225 int input; /**< @brief true iff input is available */
226 int output; /**< @brief true iff output is available */
227 int opened; /**< @brief used by generic PortMidi for error checking */
228 int is_virtual; /**< @brief true iff this is/was a virtual device */
229} PmDeviceInfo;
230
231/** MIDI system-dependent device or driver info is passed in this
232 structure, which is owned by the caller.
233*/
234#define PM_SYSDEPINFO_VERS 210
235
236enum PmSysDepPropertyKey {
237 pmKeyNone = 0, /**< a "noop" key value */
238 /** CoreMIDI Manufacturer name, value is string */
239 pmKeyCoreMidiManufacturer = 1,
240 /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be
241 passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening
242 a device. The created port will be named accordingly and will be
243 visible for externally made connections (subscriptions). (Linux ALSA
244 ports are always enabled for this, but only get application-specific
245 names if you give it one.) This key/value is ignored when opening
246 virtual ports, which are named when they are created.) */
247 pmKeyAlsaPortName = 2,
248 /** Linux ALSA snd_seq_set_client_name, value is a string.
249 Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput.
250 Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override
251 any previously set client name and applies to all ports. */
252 pmKeyAlsaClientName = 3
253 /* if system-dependent code introduces more options, register
254 the key here to avoid conflicts. */
255};
256
257/** System-dependent information can be passed when creating and opening
258 ports using this data structure, which stores alternating keys and
259 values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`,
260 and `pm_test/testio.c` for examples.
261 */
262typedef struct {
263 int structVersion; /**< @brief this structure version */
264 int length; /**< @brief number of properties in this structure */
265 struct {
266 enum PmSysDepPropertyKey key;
267 const void *value;
268 } properties[];
269} PmSysDepInfo;
270
271
272/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */
273PMEXPORT int Pm_CountDevices(void);
274
275/**
276 Return the default device ID or pmNoDevice if there are no devices.
277 The result (but not pmNoDevice) can be passed to Pm_OpenMidi().
278
279 The use of these functions is not recommended. There is no natural
280 "default device" on any system, so defaults must be set by users.
281 (Currently, PortMidi just returns the first device it finds as
282 "default", so if there *is* a default, implementors should use
283 pm_add_device to add system default input and output devices
284 first.)
285
286 The recommended solution is pass the burden to applications. It is
287 easy to scan devices with PortMidi and build a device menu, and to
288 save menu selections in application preferences for next
289 time. This is my recommendation for any GUI program. For simple
290 command-line applications and utilities, see pm_test where all the
291 test programs now accept device numbers on the command line and/or
292 prompt for their entry.
293
294 On linux, you can create virtual ports and use an external program
295 to set up inter-application and device connections.
296
297 Some advice for preferences: MIDI devices used to be built-in or
298 plug-in cards, so the numbers rarely changed. Now MIDI devices are
299 often plug-in USB devices, so device numbers change, and you
300 probably need to design to reinitialize PortMidi to rescan
301 devices. MIDI is pretty stateless, so this isn't a big problem,
302 although it means you cannot find a new device while playing or
303 recording MIDI.
304
305 Since device numbering can change whenever a USB device is plugged
306 in, preferences should record *names* of devices rather than
307 device numbers. It is simple enough to use string matching to find
308 a prefered device, so PortMidi does not provide any built-in
309 lookup function.
310*/
311PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void);
312
313/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */
314PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void);
315
316/** Find a device that matches a pattern.
317
318 @param pattern a substring of the device name, or if the pattern
319 contains the two-character separator ", ", then the first part of
320 the pattern represents a device interface substring and the second
321 part after the separator represents a device name substring.
322
323 @param is_input restricts the search to an input when true, or an
324 output when false.
325
326 @return the number of the first device whose device interface
327 contains the interface pattern (if any), whose device name
328 contains the name pattern, and whose direction (input or output)
329 matches the #is_input parameter. If no match is found, #pmNoDevice
330 (-1) is returned.
331*/
332PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input);
333
334
335/** Represents a millisecond clock with arbitrary start time.
336 This type is used for all MIDI timestamps and clocks.
337*/
338typedef int32_t PmTimestamp;
339typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
340
341/** TRUE if t1 before t2 */
342#define PmBefore(t1,t2) (((t1)-(t2)) < 0)
343/** @} */
344/**
345 \defgroup grp_device Input/Output Devices Handling
346 @{
347*/
348/** Get a PmDeviceInfo structure describing a MIDI device.
349
350 @param id the device to be queried.
351
352 If \p id is out of range or if the device designates a deleted
353 virtual device, the function returns NULL.
354
355 The returned structure is owned by the PortMidi implementation and
356 must not be manipulated or freed. The pointer is guaranteed to be
357 valid between calls to Pm_Initialize() and Pm_Terminate().
358*/
359PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id);
360
361/** Open a MIDI device for input.
362
363 @param stream the address of a #PortMidiStream pointer which will
364 receive a pointer to the newly opened stream.
365
366 @param inputDevice the ID of the device to be opened (see #PmDeviceID).
367
368 @param inputSysDepInfo a pointer to an optional system-dependent
369 data structure (a #PmSysDepInfo struct) containing additional
370 information for device setup or handle processing. This parameter
371 is never required for correct operation. If not used, specify
372 NULL. Declared `void *` here for backward compatibility. Note that
373 with Linux ALSA, you can use this parameter to specify a client name
374 and port name.
375
376 @param bufferSize the number of input events to be buffered
377 waiting to be read using Pm_Read(). Messages will be lost if the
378 number of unread messages exceeds this value.
379
380 @param time_proc (address of) a procedure that returns time in
381 milliseconds. It may be NULL, in which case a default millisecond
382 timebase (PortTime) is used. If the application wants to use
383 PortTime, it should start the timer (call Pt_Start) before calling
384 Pm_OpenInput or Pm_OpenOutput. If the application tries to start
385 the timer *after* Pm_OpenInput or Pm_OpenOutput, it may get a
386 ptAlreadyStarted error from Pt_Start, and the application's
387 preferred time resolution and callback function will be ignored.
388 \p time_proc result values are appended to incoming MIDI data,
389 normally by mapping system-provided timestamps to the \p time_proc
390 timestamps to maintain the precision of system-provided
391 timestamps.
392
393 @param time_info is a pointer passed to time_proc.
394
395 @return #pmNoError and places a pointer to a valid
396 #PortMidiStream in the stream argument. If the open operation
397 fails, a nonzero error code is returned (see #PMError) and
398 the value of stream is invalid.
399
400 Any stream that is successfully opened should eventually be closed
401 by calling Pm_Close().
402*/
403PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,
404 PmDeviceID inputDevice,
405 void *inputSysDepInfo,
406 int32_t bufferSize,
407 PmTimeProcPtr time_proc,
408 void *time_info);
409
410/** Open a MIDI device for output.
411
412 @param stream the address of a #PortMidiStream pointer which will
413 receive a pointer to the newly opened stream.
414
415 @param outputDevice the ID of the device to be opened (see #PmDeviceID).
416
417 @param inputSysDepInfo a pointer to an optional system-specific
418 data structure (a #PmSysDepInfo struct) containing additional
419 information for device setup or handle processing. This parameter
420 is never required for correct operation. If not used, specify
421 NULL. Declared `void *` here for backward compatibility. Note that
422 with Linux ALSA, you can use this parameter to specify a client name
423 and port name.
424
425 @param bufferSize the number of output events to be buffered
426 waiting for output. In some cases -- see below -- PortMidi does
427 not buffer output at all and merely passes data to a lower-level
428 API, in which case \p bufferSize is ignored. Since MIDI speeds now
429 vary from 1 to 50 or more messages per ms (over USB), put some
430 thought into this number. E.g. if latency is 20ms and you want to
431 burst 100 messages in that time (5000 messages per second), you
432 should set \p bufferSize to at least 100. The default on Windows
433 assumes an average rate of 500 messages per second and in this
434 example, output would be slowed waiting for free buffers.
435
436 @param latency the delay in milliseconds applied to timestamps
437 to determine when the output should actually occur. (If latency is
438 < 0, 0 is assumed.) If latency is zero, timestamps are ignored
439 and all output is delivered immediately. If latency is greater
440 than zero, output is delayed until the message timestamp plus the
441 latency. (NOTE: the time is measured relative to the time source
442 indicated by time_proc. Timestamps are absolute, not relative
443 delays or offsets.) In some cases, PortMidi can obtain better
444 timing than your application by passing timestamps along to the
445 device driver or hardware, so the best strategy to minimize jitter
446 is: wait until the real time to send the message, compute the
447 message, attach the *ideal* output time (not the current real
448 time, because some time may have elapsed), and send the
449 message. The \p latency will be added to the timestamp, and
450 provided the elapsed computation time has not exceeded \p latency,
451 the message will be delivered according to the timestamp. If the
452 real time is already past the timestamp, the message will be
453 delivered as soon as possible. Latency may also help you to
454 synchronize MIDI data to audio data by matching \p latency to the
455 audio buffer latency.
456
457 @param time_proc (address of) a pointer to a procedure that
458 returns time in milliseconds. It may be NULL, in which case a
459 default millisecond timebase (PortTime) is used. If the
460 application wants to use PortTime, it should start the timer (call
461 Pt_Start) before calling #Pm_OpenInput or #Pm_OpenOutput. If the
462 application tries to start the timer *after* #Pm_OpenInput or
463 #Pm_OpenOutput, it may get a #ptAlreadyStarted error from #Pt_Start,
464 and the application's preferred time resolution and callback
465 function will be ignored. \p time_proc times are used to schedule
466 outgoing MIDI data (when latency is non-zero), usually by mapping
467 from time_proc timestamps to internal system timestamps to
468 maintain the precision of system-supported timing.
469
470 @param time_info a pointer passed to time_proc.
471
472 @return #pmNoError and places a pointer to a valid #PortMidiStream
473 in the stream argument. If the operation fails, a nonzero error
474 code is returned (see PMError) and the value of \p stream is
475 invalid.
476
477 Note: ALSA appears to have a fixed-size priority queue for timed
478 output messages. Testing indicates the queue can hold a little
479 over 400 3-byte MIDI messages. Thus, you can send 10,000
480 messages/second if the latency is 30ms (30ms * 10000 msgs/sec *
481 0.001 sec/ms = 300 msgs), but not if the latency is 50ms
482 (resulting in about 500 pending messages, which is greater than
483 the 400 message limit). Since timestamps in ALSA are relative,
484 they are of less value than absolute timestamps in macOS and
485 Windows. This is a limitation of ALSA and apparently a design
486 flaw.
487
488 Example 1: If I provide a timestamp of 5000, latency is 1, and
489 time_proc returns 4990, then the desired output time will be when
490 time_proc returns timestamp+latency = 5001. This will be 5001-4990
491 = 11ms from now.
492
493 Example 2: If I want to send at exactly 5010, and latency is 10, I
494 should wait until 5000, compute the messages and provide a
495 timestamp of 5000. As long as computation takes less than 10ms,
496 the message will be delivered at time 5010.
497
498 Example 3 (recommended): It is often convenient to ignore latency.
499 E.g. if a sequence says to output at time 5010, just wait until
500 5010, compute the message and use 5010 for the timestamp. Delivery
501 will then be at 5010+latency, but unless you are synchronizing to
502 something else, the absolute delay by latency will not matter.
503
504 Any stream that is successfully opened should eventually be closed
505 by calling Pm_Close().
506*/
507PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
508 PmDeviceID outputDevice,
509 void *outputSysDepInfo,
510 int32_t bufferSize,
511 PmTimeProcPtr time_proc,
512 void *time_info,
513 int32_t latency);
514
515/** Create a virtual input device.
516
517 @param name gives the virtual device name, which is visible to
518 other applications.
519
520 @param interf is the interface (System API) used to create the
521 device Default interfaces are "MMSystem", "CoreMIDI" and
522 "ALSA". Currently, these are the only ones implemented, but future
523 implementations could support DirectMusic, Jack, sndio, or others.
524
525 @param sysDepInfo contains interface-dependent additional
526 information (a #PmSysDepInfo struct), e.g., hints or options. This
527 parameter is never required for correct operation. If not used,
528 specify NULL. Declared `void *` here for backward compatibility.
529
530 @return a device ID or #pmNameConflict (\p name is invalid or
531 already exists) or #pmInterfaceNotSupported (\p interf is does not
532 match a supported interface).
533
534 The created virtual device appears to other applications as if it
535 is an output device. The device must be opened to obtain a stream
536 and read from it.
537
538 Virtual devices are not supported by Windows (Multimedia API). Calls
539 on Windows do nothing except return #pmNotImplemented.
540*/
541PMEXPORT PmError Pm_CreateVirtualInput(const char *name,
542 const char *interf,
543 void *sysDepInfo);
544
545/** Create a virtual output device.
546
547 @param name gives the virtual device name, which is visible to
548 other applications.
549
550 @param interf is the interface (System API) used to create the
551 device Default interfaces are "MMSystem", "CoreMIDI" and
552 "ALSA". Currently, these are the only ones implemented, but future
553 implementations could support DirectMusic, Jack, sndio, or others.
554
555 @param sysDepInfo contains interface-dependent additional
556 information (a #PmSysDepInfo struct), e.g., hints or options. This
557 parameter is never required for correct operation. If not used,
558 specify NULL. Declared `void *` here for backward compatibility.
559
560 @return a device ID or #pmInvalidDeviceId (\p name is invalid or
561 already exists) or #pmInterfaceNotSupported (\p interf is does not
562 match a supported interface).
563
564 The created virtual device appears to other applications as if it
565 is an input device. The device must be opened to obtain a stream
566 and write to it.
567
568 Virtual devices are not supported by Windows (Multimedia API). Calls
569 on Windows do nothing except return #pmNotImplemented.
570*/
571PMEXPORT PmError Pm_CreateVirtualOutput(const char *name,
572 const char *interf,
573 void *sysDepInfo);
574
575/** Remove a virtual device.
576
577 @param device a device ID (small integer) designating the device.
578
579 The device is removed; other applications can no longer see or open
580 this virtual device, which may be either for input or output. The
581 device must not be open. The device ID may be reused, but existing
582 devices are not renumbered. This means that the device ID could be
583 in the range from 0 to #Pm_CountDevices(), yet the device ID does
584 not designate a device. In that case, passing the ID to
585 #Pm_GetDeviceInfo() will return NULL.
586
587 @return #pmNoError if the device was deleted or #pmInvalidDeviceId
588 if the device is open, already deleted, or \p device is out of
589 range.
590*/
591PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device);
592 /** @} */
593
594/**
595 @defgroup grp_events_filters Events and Filters Handling
596 @{
597*/
598
599/* Filter bit-mask definitions */
600/** filter active sensing messages (0xFE): */
601#define PM_FILT_ACTIVE (1 << 0x0E)
602/** filter system exclusive messages (0xF0): */
603#define PM_FILT_SYSEX (1 << 0x00)
604/** filter MIDI clock message (0xF8) */
605#define PM_FILT_CLOCK (1 << 0x08)
606/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
607#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
608/** filter tick messages (0xF9) */
609#define PM_FILT_TICK (1 << 0x09)
610/** filter undefined FD messages */
611#define PM_FILT_FD (1 << 0x0D)
612/** filter undefined real-time messages */
613#define PM_FILT_UNDEFINED PM_FILT_FD
614/** filter reset messages (0xFF) */
615#define PM_FILT_RESET (1 << 0x0F)
616/** filter all real-time messages */
617#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
618 PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
619/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
620#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
621/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
622#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
623/** per-note aftertouch (0xA0-0xAF) */
624#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
625/** filter both channel and poly aftertouch */
626#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | \
627 PM_FILT_POLY_AFTERTOUCH)
628/** Program changes (0xC0-0xCF) */
629#define PM_FILT_PROGRAM (1 << 0x1C)
630/** Control Changes (CC's) (0xB0-0xBF)*/
631#define PM_FILT_CONTROL (1 << 0x1B)
632/** Pitch Bender (0xE0-0xEF*/
633#define PM_FILT_PITCHBEND (1 << 0x1E)
634/** MIDI Time Code (0xF1)*/
635#define PM_FILT_MTC (1 << 0x01)
636/** Song Position (0xF2) */
637#define PM_FILT_SONG_POSITION (1 << 0x02)
638/** Song Select (0xF3)*/
639#define PM_FILT_SONG_SELECT (1 << 0x03)
640/** Tuning request (0xF6) */
641#define PM_FILT_TUNE (1 << 0x06)
642/** All System Common messages (mtc, song position, song select, tune request) */
643#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | \
644 PM_FILT_SONG_SELECT | PM_FILT_TUNE)
645
646
647/* Set filters on an open input stream to drop selected input types.
648
649 @param stream an open MIDI input stream.
650
651 @param filters indicate message types to filter (block).
652
653 @return #pmNoError or an error code.
654
655 By default, only active sensing messages are filtered.
656 To prohibit, say, active sensing and sysex messages, call
657 Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
658
659 Filtering is useful when midi routing or midi thru functionality
660 is being provided by the user application.
661 For example, you may want to exclude timing messages (clock, MTC,
662 start/stop/continue), while allowing note-related messages to pass.
663 Or you may be using a sequencer or drum-machine for MIDI clock
664 information but want to exclude any notes it may play.
665 */
666PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters);
667
668/** Create a mask that filters one channel. */
669#define Pm_Channel(channel) (1<<(channel))
670
671/** Filter incoming messages based on channel.
672
673 @param stream an open MIDI input stream.
674
675 @param mask indicates channels to be received.
676
677 @return #pmNoError or an error code.
678
679 The \p mask is a 16-bit bitfield corresponding to appropriate channels.
680 The #Pm_Channel macro can assist in calling this function.
681 I.e. to receive only input on channel 1, call with
682 Pm_SetChannelMask(Pm_Channel(1));
683 Multiple channels should be OR'd together, like
684 Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
685
686 Note that channels are numbered 0 to 15 (not 1 to 16). Most
687 synthesizer and interfaces number channels starting at 1, but
688 PortMidi numbers channels starting at 0.
689
690 All channels are allowed by default
691*/
692PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
693
694/** Terminate outgoing messages immediately.
695
696 @param stream an open MIDI output stream.
697
698 @result #pmNoError or an error code.
699
700 The caller should immediately close the output port; this call may
701 result in transmission of a partial MIDI message. There is no
702 abort for Midi input because the user can simply ignore messages
703 in the buffer and close an input device at any time. If the
704 specified behavior cannot be achieved through the system-level
705 interface (ALSA, CoreMIDI, etc.), the behavior may be that of
706 Pm_Close().
707 */
708PMEXPORT PmError Pm_Abort(PortMidiStream* stream);
709
710/** Close a midi stream, flush any pending buffers if possible.
711
712 @param stream an open MIDI input or output stream.
713
714 @result #pmNoError or an error code.
715
716 If the system-level interface (ALSA, CoreMIDI, etc.) does not
717 support flushing remaining messages, the behavior may be one of
718 the following (most preferred first): block until all pending
719 timestamped messages are delivered; deliver messages to a server
720 or kernel process for later delivery but return immediately; drop
721 messages (as in Pm_Abort()). Therefore, to be safe, applications
722 should wait until the output queue is empty before calling
723 Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a
724 100ms "cushion" beyond latency (if any) before closing.
725*/
726PMEXPORT PmError Pm_Close(PortMidiStream* stream);
727
728/** (re)synchronize to the time_proc passed when the stream was opened.
729
730 @param stream an open MIDI input or output stream.
731
732 @result #pmNoError or an error code.
733
734 Typically, this is used when the stream must be opened before the
735 time_proc reference is actually advancing. In this case, message
736 timing may be erratic, but since timestamps of zero mean "send
737 immediately," initialization messages with zero timestamps can be
738 written without a functioning time reference and without
739 problems. Before the first MIDI message with a non-zero timestamp
740 is written to the stream, the time reference must begin to advance
741 (for example, if the time_proc computes time based on audio
742 samples, time might begin to advance when an audio stream becomes
743 active). After time_proc return values become valid, and BEFORE
744 writing the first non-zero timestamped MIDI message, call
745 Pm_Synchronize() so that PortMidi can observe the difference
746 between the current time_proc value and its MIDI stream time.
747
748 In the more normal case where time_proc values advance
749 continuously, there is no need to call #Pm_Synchronize. PortMidi
750 will always synchronize at the first output message and
751 periodically thereafter.
752*/
753PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream);
754
755
756/** Encode a short Midi message into a 32-bit word. If data1
757 and/or data2 are not present, use zero.
758*/
759#define Pm_Message(status, data1, data2) \
760 ((((data2) << 16) & 0xFF0000) | \
761 (((data1) << 8) & 0xFF00) | \
762 ((status) & 0xFF))
763/** Extract the status field from a 32-bit midi message. */
764#define Pm_MessageStatus(msg) ((msg) & 0xFF)
765/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */
766#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
767/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */
768#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
769
770typedef uint32_t PmMessage; /**< @brief see #PmEvent */
771/**
772 All MIDI data comes in the form of PmEvent structures. A sysex
773 message is encoded as a sequence of PmEvent structures, with each
774 structure carrying 4 bytes of the message, i.e. only the first
775 PmEvent carries the status byte.
776
777 All other MIDI messages take 1 to 3 bytes and are encoded in a whole
778 PmMessage with status in the low-order byte and remaining bytes
779 unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes
780 of PmMessage, leaving the high-order byte unused.
781
782 Note that MIDI allows nested messages: the so-called "real-time" MIDI
783 messages can be inserted into the MIDI byte stream at any location,
784 including within a sysex message. MIDI real-time messages are one-byte
785 messages used mainly for timing (see the MIDI spec). PortMidi retains
786 the order of non-real-time MIDI messages on both input and output, but
787 it does not specify exactly how real-time messages are processed. This
788 is particulary problematic for MIDI input, because the input parser
789 must either prepare to buffer an unlimited number of sysex message
790 bytes or to buffer an unlimited number of real-time messages that
791 arrive embedded in a long sysex message. To simplify things, the input
792 parser is allowed to pass real-time MIDI messages embedded within a
793 sysex message, and it is up to the client to detect, process, and
794 remove these messages as they arrive.
795
796 When receiving sysex messages, the sysex message is terminated
797 by either an EOX status byte (anywhere in the 4 byte messages) or
798 by a non-real-time status byte in the low order byte of the message.
799 If you get a non-real-time status byte but there was no EOX byte, it
800 means the sysex message was somehow truncated. This is not
801 considered an error; e.g., a missing EOX can result from the user
802 disconnecting a MIDI cable during sysex transmission.
803
804 A real-time message can occur within a sysex message. A real-time
805 message will always occupy a full PmEvent with the status byte in
806 the low-order byte of the PmEvent message field. (This implies that
807 the byte-order of sysex bytes and real-time message bytes may not
808 be preserved -- for example, if a real-time message arrives after
809 3 bytes of a sysex message, the real-time message will be delivered
810 first. The first word of the sysex message will be delivered only
811 after the 4th byte arrives, filling the 4-byte PmEvent message field.
812
813 The timestamp field is observed when the output port is opened with
814 a non-zero latency. A timestamp of zero means "use the current time",
815 which in turn means to deliver the message with a delay of
816 latency (the latency parameter used when opening the output port.)
817 Do not expect PortMidi to sort data according to timestamps --
818 messages should be sent in the correct order, and timestamps MUST
819 be non-decreasing. See also "Example" for Pm_OpenOutput() above.
820
821 A sysex message will generally fill many #PmEvent structures. On
822 output to a #PortMidiStream with non-zero latency, the first timestamp
823 on sysex message data will determine the time to begin sending the
824 message. PortMidi implementations may ignore timestamps for the
825 remainder of the sysex message.
826
827 On input, the timestamp ideally denotes the arrival time of the
828 status byte of the message. The first timestamp on sysex message
829 data will be valid. Subsequent timestamps may denote
830 when message bytes were actually received, or they may be simply
831 copies of the first timestamp.
832
833 Timestamps for nested messages: If a real-time message arrives in
834 the middle of some other message, it is enqueued immediately with
835 the timestamp corresponding to its arrival time. The interrupted
836 non-real-time message or 4-byte packet of sysex data will be enqueued
837 later. The timestamp of interrupted data will be equal to that of
838 the interrupting real-time message to insure that timestamps are
839 non-decreasing.
840 */
841typedef struct {
842 PmMessage message;
843 PmTimestamp timestamp;
844} PmEvent;
845
846/** @} */
847
848/** \defgroup grp_io Reading and Writing Midi Messages
849 @{
850*/
851/** Retrieve midi data into a buffer.
852
853 @param stream the open input stream.
854
855 @return the number of events read, or, if the result is negative,
856 a #PmError value will be returned.
857
858 The Buffer Overflow Problem
859
860 The problem: if an input overflow occurs, data will be lost,
861 ultimately because there is no flow control all the way back to
862 the data source. When data is lost, the receiver should be
863 notified and some sort of graceful recovery should take place,
864 e.g. you shouldn't resume receiving in the middle of a long sysex
865 message.
866
867 With a lock-free fifo, which is pretty much what we're stuck with
868 to enable portability to the Mac, it's tricky for the producer and
869 consumer to synchronously reset the buffer and resume normal
870 operation.
871
872 Solution: the entire buffer managed by PortMidi will be flushed
873 when an overflow occurs. The consumer (Pm_Read()) gets an error
874 message (#pmBufferOverflow) and ordinary processing resumes as
875 soon as a new message arrives. The remainder of a partial sysex
876 message is not considered to be a "new message" and will be
877 flushed as well.
878*/
879PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length);
880
881/** Test whether input is available.
882
883 @param stream an open input stream.
884
885 @return TRUE, FALSE, or an error value.
886
887 If there was an asynchronous error, pmHostError is returned and you must
888 call again to determine if input is (also) available.
889
890 You should probably *not* use this function. Call Pm_Read()
891 instead. If it returns 0, then there is no data available. It is
892 possible for Pm_Poll() to return TRUE before the complete message
893 is available, so Pm_Read() could return 0 even after Pm_Poll()
894 returns TRUE. Only call Pm_Poll() if you want to know that data is
895 probably available even though you are not ready to receive data.
896*/
897PMEXPORT PmError Pm_Poll(PortMidiStream *stream);
898
899/** Write MIDI data from a buffer.
900
901 @param stream an open output stream.
902
903 @param buffer (address of) an array of MIDI event data.
904
905 @param length the length of the \p buffer.
906
907 @return TRUE, FALSE, or an error value.
908
909 \b buffer may contain:
910 - short messages
911 - sysex messages that are converted into a sequence of PmEvent
912 structures, e.g. sending data from a file or forwarding them
913 from midi input, with 4 SysEx bytes per PmEvent message,
914 low-order byte first, until the last message, which may
915 contain from 1 to 4 bytes ending in MIDI EOX (0xF7).
916 - PortMidi allows 1-byte real-time messages to be embedded
917 within SysEx messages, but only on 4-byte boundaries so
918 that SysEx data always uses a full 4 bytes (except possibly
919 at the end). Each real-time message always occupies a full
920 PmEvent (3 of the 4 bytes in the PmEvent's message are
921 ignored) even when embedded in a SysEx message.
922
923 Use Pm_WriteSysEx() to write a sysex message stored as a contiguous
924 array of bytes.
925
926 Sysex data may contain embedded real-time messages.
927
928 \p buffer is managed by the caller. The buffer may be destroyed
929 as soon as this call returns.
930*/
931PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,
932 int32_t length);
933
934/** Write a timestamped non-system-exclusive midi message.
935
936 @param stream an open output stream.
937
938 @param when timestamp for the event.
939
940 @param msg the data for the event.
941
942 @result #pmNoError or an error code.
943
944 Messages are delivered in order, and timestamps must be
945 non-decreasing. (But timestamps are ignored if the stream was
946 opened with latency = 0, and otherwise, non-decreasing timestamps
947 are "corrected" to the lowest valid value.)
948*/
949PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
950 PmMessage msg);
951
952/** Write a timestamped system-exclusive midi message.
953
954 @param stream an open output stream.
955
956 @param when timestamp for the event.
957
958 @param msg the sysex message, terminated with an EOX status byte.
959
960 @result #pmNoError or an error code.
961
962 \p msg is managed by the caller and may be destroyed when this
963 call returns.
964*/
965PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when,
966 unsigned char *msg);
967
968/** @} */
969
970#ifdef __cplusplus
971}
972#endif /* __cplusplus */
973
974#endif /* PORTMIDI_PORTMIDI_H */
diff --git a/portmidi/.github/workflows/build.yml b/portmidi/.github/workflows/build.yml
new file mode 100644
index 0000000..351b5cb
--- /dev/null
+++ b/portmidi/.github/workflows/build.yml
@@ -0,0 +1,47 @@
1name: build
2
3on:
4 push:
5 pull_request:
6
7jobs:
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: cmake -D CMAKE_INSTALL_PREFIX=${{ matrix.install_dir }} ${{ matrix.cmake_extras }} -S . -B build
36 - name: Build
37 run: cmake --build build ${{ matrix.cmake_config }}
38 env:
39 CMAKE_BUILD_PARALLEL_LEVEL: 2
40 - name: Install
41 run: cmake --install . ${{ matrix.cmake_config }}
42 working-directory: build
43 - name: Upload Build Artifact
44 uses: actions/upload-artifact@v2
45 with:
46 name: ${{ matrix.name }} portmidi build
47 path: ${{ matrix.install_dir }}
diff --git a/portmidi/.github/workflows/docs.yml b/portmidi/.github/workflows/docs.yml
new file mode 100644
index 0000000..d0e251b
--- /dev/null
+++ b/portmidi/.github/workflows/docs.yml
@@ -0,0 +1,28 @@
1name: Generate Docs
2
3on:
4 push:
5 branches:
6 - main
7 workflow_dispatch:
8
9jobs:
10 doxygen:
11 name: Doxygen
12 runs-on: ubuntu-latest
13 steps:
14 - name: "Check out repository"
15 uses: actions/checkout@v2
16
17 - name: Install Doxygen
18 run: sudo apt-get update && sudo apt-get install -y --no-install-recommends doxygen
19
20 - name: Generate Documentation
21 run: doxygen
22 working-directory: .
23
24 - name: Deploy to GitHub Pages
25 uses: peaceiris/actions-gh-pages@v3
26 with:
27 github_token: ${{ secrets.GITHUB_TOKEN }}
28 publish_dir: docs/html
diff --git a/portmidi/.gitignore b/portmidi/.gitignore
new file mode 100644
index 0000000..19d6650
--- /dev/null
+++ b/portmidi/.gitignore
@@ -0,0 +1,62 @@
1.DS_Store
2build*/
3*~
4CMakeCache.txt
5CMakeFiles/
6CMakeScripts/
7/portmidi.pc
8/x64/
9/Debug/
10/Release/
11/pm_java/pmdefaults/pmdefaults.jar
12/pm_java/pmdefaults.sln
13/pm_java/pmjni.dir/
14/pm_java/x64/
15portmidi.build/
16cmake_install.cmake
17*.xcodeproj/
18/.vs/
19/portmidi.sln
20*.vcxproj
21*.vcxproj.filters
22*.vcxproj.user
23/Makefile
24/libportmidi.so*
25/libportmidi.a
26/libportmidi_static.a
27/libpmjni.so*
28/packaging/PortMidiConfig.cmake
29/packaging/PortMidiConfigVersion.cmake
30/packaging/portmidi.pc
31/pm_common/Makefile
32/pm_common/portmidi.dir/
33/pm_java/Makefile
34/pm_test/Debug/
35/pm_test/Release/
36/pm_test/Makefile
37/pm_test/fastrcv
38/pm_test/fastrcv.dir/
39/pm_test/latency
40/pm_test/latency.dir/
41/pm_test/midiclock
42/pm_test/midiclock.dir/
43/pm_test/midithread
44/pm_test/midithread.dir/
45/pm_test/midithru
46/pm_test/midithru.dir/
47/pm_test/mm
48/pm_test/mm.dir/
49/pm_test/multivirtual
50/pm_test/qtest
51/pm_test/qtest.dir/
52/pm_test/recvvirtual
53/pm_test/sendvirtual
54/pm_test/sysex
55/pm_test/sysex.dir/
56/pm_test/testio
57/pm_test/testio.dir/
58/pm_test/virttest
59/pm_test/fast
60/pm_test/fast.dir/
61/pm_test/pmlist
62/pm_test/pmlist.dir/
diff --git a/portmidi/CHANGELOG.txt b/portmidi/CHANGELOG.txt
new file mode 100644
index 0000000..fee4330
--- /dev/null
+++ b/portmidi/CHANGELOG.txt
@@ -0,0 +1,213 @@
1/* CHANGELOG FOR PORTMIDI
2 *
3 * 21Feb22 v2.0.3 Roger Dannenberg
4 * - this version allows multiple hardware devices to have the same name.
5 *
6 * 03Jan22 v2.0.2 Roger Dannenberg
7 * - many changes for CMake including install support
8 * - bare-bones Java and PmDefaults support. It runs, but no
9 * installation.
10 *
11 * 16Sep21 Roger Dannenberg
12 * - Added CreateVirtualInput and CreateVirtualOutput functions (macOS
13 * & Linux) only.
14 * - Fix for unicode endpoints on macOS CoreMIDI.
15 * - Parsing in macOS of realtime message embedded in short messages
16 * (can this actually happen?)
17 * - renamed pm_test/test.c to pm_test/testio.c
18 * - with this release, pm_java, pm_csharp, pm_cl, pm_python, pm_qt
19 * are marked as "legacy code" and README.txt's refer to other
20 * projects. I had hoped for "one-stop shopping" for language
21 * bindings, but developers decided to move work to independent
22 * repositories. Maybe that's better.
23 *
24 * 19Oct09 Roger Dannenberg
25 * - Changes dynamic library names from portmidi_d to portmidi to
26 * be backward-compatible with programs expecting a library by
27 * the old name.
28 *
29 * 04Oct09 Roger Dannenberg
30 * - Converted to using Cmake.
31 * - Renamed static and dynamic library files to portmidi_s and portmidi_d
32 * - Eliminated VC9 and VC8 files (went back to simply test.vcproj, etc.,
33 * use Cmake to switch from the provided VC9 files to VC8 or other)
34 * - Many small changes to prepare for 64-bit architectures (but only
35 * tested on 32-bit machines)
36 *
37 * 16Jun09 Roger Dannenberg
38 * - Started using Microsoft Visual C++ Version 9 (Express). Converted
39 * all *-VC9.vcproj file to *.vcproj and renamed old project files to
40 * *-VC8.proj. Previously, output from VC9 went to special VC9 files,
41 * that breaks any program or script looking for output in release or
42 * debug files, so now both compiler version output to the same folders.
43 * Now, debug version uses static linking with debug DLL runtime, and
44 * release version uses static linking with statically linked runtime.
45 * Converted to Inno Setup and worked on scripts to make things build
46 * properly, especially pmdefaults.
47 *
48 * 02Jan09 Roger Dannenberg
49 * - Created Java interface and wrote PmDefaults application to set
50 * values for Pm_GetDefaultInputDeviceID() and
51 * Pm_GetDefaultOutputDeviceID(). Other fixes.
52 *
53 * 19Jun08 Roger Dannenberg and Austin Sung
54 * - Removed USE_DLL_FOR_CLEANUP -- Windows 2000 through Vista seem to be
55 * fixed now, and can recover if MIDI ports are left open
56 * - Various other minor patches
57 *
58 * 17Jan07 Roger Dannenberg
59 * - Lots more help for Common Lisp user in pm_cl
60 * - Minor fix to eliminate a compiler warning
61 * - Went back to single library in OS X for both portmidi and porttime
62 *
63 * 16Jan07 Roger Dannenberg
64 * - OOPS! fixed bug where short messages all had zero data
65 * - Makefile.osx static library build now makes universal (i386 + ppc)
66 * binaries
67 *
68 * 15Jan07 Roger Dannenberg
69 * - multiple rewrites of sysex handling code to take care of
70 * error-handling, embedded messages, message filtering,
71 * driver bugs, and host limitations.
72 * - fixed windows to use dwBufferLength rather than
73 * dwBytesRecorded for long buffer output (fix by Nigel Brown)
74 * - Win32 MME code always appends an extra zero to long buffer
75 * output to work around a problem with earlier versions of Midi Yoke
76 * - Added mm, a command line Midi Monitor to pm_test suite
77 * - Revised copyright notice to match PortAudio/MIT license (requests
78 * are moved out of the license proper and into a separate paragraph)
79 *
80 * 18Oct06 Roger Dannenberg
81 * - replace FIFO in pmutil with Light Pipe-based multiprocessor-safe alg.
82 * - replace FIFO in portmidi.c with PmQueue from pmutil
83 *
84 * 07Oct06 cpr & Roger Dannenberg
85 * - overhaul of CoreMIDI input to handle running status and multiple
86 * - messages per packet, with additional error detection
87 * - added Leigh Smith and Rick Taube support for Common Lisp and
88 * - dynamic link libraries in OSX
89 * - initialize static global seq = NULL in pmlinuxalsa.c
90 *
91 * 05Sep06 Sebastien Frippiat
92 * - check if (ALSA) seq exists before closing it in pm_linuxalsa_term()
93 *
94 * 05Sep06 Andreas Micheler and Cecilio
95 * - fixed memory leak by freeing someo objects in pm_winmm_term()
96 * - and another leak by freeing descriptors in Pm_Terminate()
97 *
98 * 23Aug06 RBD
99 * - various minor fixes
100 *
101 * 04Nov05 Olivier Tristan
102 * - changes to OS X to properly retrieve real device name on CoreMidi
103 *
104 * 19Jul05 Roger Dannenberg
105 * - included pmBufferMaxSize in Pm_GetErrorText()
106 *
107 * 23Mar05 Torgier Strand Henriksen
108 * - cleaner termination of porttime thread under Linux
109 *
110 * 15Nov04 Ben Allison
111 * - sysex output now uses one buffer/message and reallocates buffer
112 * - if needed
113 * - filters expanded for many message types and channels
114 * - detailed changes are as follows:
115 * ------------- in pmwinmm.c --------------
116 * - new #define symbol: OUTPUT_BYTES_PER_BUFFER
117 * - change SYSEX_BYTES_PER_BUFFER to 1024
118 * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length
119 * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
120 * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro
121 * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH
122 * - similar to above, but counts appropriately for sysex messages
123 * - added the following members to midiwinmm_struct for sysex data:
124 * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data **
125 * - int num_sysex_buffers; ** how many sysex buffers **
126 * - int next_sysex_buffer; ** index of next sysexbuffer to send **
127 * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer **
128 * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer
129 * - into equivalent sysex_buffer form
130 * - changed winmm_in_open to initialize new midiwinmm_struct members and
131 * - to use the new allocate_sysex_buffer() function instead of
132 * - allocate_buffer()
133 * - changed winmm_out_open to initialize new members, create sysex buffer
134 * - signal, and allocate 2 sysex buffers
135 * - changed winmm_out_delete to free sysex buffers and shut down the sysex
136 * - buffer signal
137 * - create new function resize_sysex_buffer which resizes m->hdr to the
138 * - passed size, and corrects the midiwinmm_struct accordingly.
139 * - changed winmm_write_byte to use new resize_sysex_buffer function,
140 * - if resize fails, write current buffer to output and continue
141 * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal
142 * - depending on which buffer was finished
143 * ------------- in portmidi.h --------------
144 * - added pmBufferMaxSize to PmError to indicate that the buffer would be
145 * - too large for the underlying API
146 * - added additional filters
147 * - added prototype, documentation, and helper macro for Pm_SetChannelMask
148 * ------------- in portmidi.c --------------
149 * - added pm_status_filtered() and pm_realtime_filtered() functions to
150 * separate filtering logic from buffer logic in pm_read_short
151 * - added Pm_SetChannelMask function
152 * - added pm_channel_filtered() function
153 * ------------- in pminternal.h --------------
154 * - added member to PortMidiStream for channel mask
155 *
156 * 25May04 RBD
157 * - removed support for MIDI THRU
158 * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl
159 * - extensive work on Mac OS X port, especially sysex and error handling
160 *
161 * 18May04 RBD
162 * - removed side-effects from assert() calls. Now you can disable assert().
163 * - no longer check pm_hosterror everywhere, fixing a bug where an open
164 * failure could cause a write not to work on a previously opened port
165 * until you call Pm_GetHostErrorText().
166 * 16May04 RBD and Chris Roberts
167 * - Some documentation wordsmithing in portmidi.h
168 * - Dynamically allocate port descriptor structures
169 * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer.
170 *
171 * 09Oct03 RBD
172 * - Changed Thru handling. Now the client does all the work and the client
173 * must poll or read to keep thru messages flowing.
174 *
175 * 31May03 RBD
176 * - Fixed various bugs.
177 * - Added linux ALSA support with help from Clemens Ladisch
178 * - Added Mac OS X support, implemented by Jon Parise, updated and
179 * integrated by Andrew Zeldis and Zico Kolter
180 * - Added latency program to build histogram of system latency using PortTime.
181 *
182 * 30Jun02 RBD Extensive rewrite of sysex handling. It works now.
183 * Extensive reworking of error reporting and error text -- no
184 * longer use dictionary call to delete data; instead, Pm_Open
185 * and Pm_Close clean up before returning an error code, and
186 * error text is saved in a system-independent location.
187 * Wrote sysex.c to test sysex message handling.
188 *
189 * 15Jun02 BCT changes:
190 * - Added pmHostError text handling.
191 * - For robustness, check PortMidi stream args not NULL.
192 * - Re-C-ANSI-fied code (changed many C++ comments to C style)
193 * - Reorganized code in pmwinmm according to input/output functionality (made
194 * cleanup handling easier to reason about)
195 * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error)
196 * - Cleaned up memory handling (now system specific data deleted via dictionary
197 * call in PortMidi, allows client to query host errors).
198 * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as
199 * logic implies it should. Specifically: verified callback routines not reentrant and
200 * all verified status for all unchecked Win32 MMedia API calls perform successfully
201 * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API
202 * bug (i.e. if devices not explicitly closed, must reboot to debug application further).
203 * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or
204 * explicitly Pm_Close open devices when using WinMM version of PortMidi.
205 *
206 * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling
207 *
208 * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent
209 * opening an input as output and vice versa.
210 * Added comments and documentation.
211 * Implemented Pm_Terminate().
212 *
213 */
diff --git a/portmidi/CMakeLists.txt b/portmidi/CMakeLists.txt
new file mode 100644
index 0000000..0107e8c
--- /dev/null
+++ b/portmidi/CMakeLists.txt
@@ -0,0 +1,188 @@
1# portmidi
2# Roger B. Dannenberg (and others)
3# Sep 2009 - 2021
4
5cmake_minimum_required(VERSION 3.21)
6# (ALSA::ALSA new in 3.12 and used in pm_common/CMakeLists.txt)
7# Some Java stuff failed on 3.17 but works with 3.20+
8
9cmake_policy(SET CMP0091 NEW) # enables MSVC_RUNTIME_LIBRARY target property
10
11# Previously, PortMidi versions were simply SVN commit version numbers.
12# Versions are now in the form x.y.z
13# Changed 1.0 to 2.0 because API is extended with virtual ports:
14set(SOVERSION "2")
15set(VERSION "2.0.4")
16
17project(portmidi VERSION "${VERSION}"
18 DESCRIPTION "Cross-Platform MIDI IO")
19
20set(LIBRARY_SOVERSION "${SOVERSION}")
21set(LIBRARY_VERSION "${VERSION}")
22
23option(BUILD_SHARED_LIBS "Build shared libraries" ON)
24
25option(PM_USE_STATIC_RUNTIME
26 "Use MSVC static runtime. Only applies when BUILD_SHARED_LIBS is OFF"
27 ON)
28
29option(USE_SNDIO "Use sndio" OFF)
30
31# MSVCRT_DLL is used to construct the MSVC_RUNTIME_LIBRARY property
32# (see pm_common/CMakeLists.txt and pm_test/CMakeLists.txt)
33if(PM_USE_STATIC_RUNTIME AND NOT BUILD_SHARED_LIBS)
34 set(MSVCRT_DLL "")
35else()
36 set(MSVCRT_DLL "DLL")
37endif()
38
39# Always build with position-independent code (-fPIC)
40set(CMAKE_POSITION_INDEPENDENT_CODE ON)
41
42set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING
43 "make for this OS version or higher")
44
45# PM_ACTUAL_LIB_NAME is in this scope -- see pm_common/CMakeLists.txt
46# PM_NEEDED_LIBS is in this scope -- see pm_common/CMakeLists.txt
47
48include(GNUInstallDirs)
49
50# Build Types
51# credit: http://cliutils.gitlab.io/modern-cmake/chapters/features.html
52set(DEFAULT_BUILD_TYPE "Release")
53if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
54 message(STATUS
55 "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
56 set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
57 STRING "Choose the type of build." FORCE)
58 # Set the possible values of build type for cmake-gui
59 set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
60 "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
61endif()
62
63# where to put libraries? Everything goes here in this directory
64# (or Debug or Release, depending on the OS)
65set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
66set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
67
68option(BUILD_JAVA_NATIVE_INTERFACE
69 "build the Java PortMidi interface library" OFF)
70
71# Defines are used in both portmidi (in pm_common/) and pmjni (in pm_java),
72# so define them here to be inherited by both libraries.
73#
74# PortMidi software architecture supports multiple system API's to lower-
75# level MIDI drivers, e.g. PMNULL (no drivers), Jack (but not supported yet),
76# and sndio (BSD, not supported yet). Interfaces are selected by defining,
77# e.g., PMALSA. (In principle, we should require PMCOREMIDI (for macOS)
78# and PMWINMM (for windows), but these are assumed.
79#
80if(APPLE OR WIN32)
81else(APPLE_OR_WIN32)
82 set(LINUX_DEFINES "PMALSA" CACHE STRING "must define either PMALSA or PMNULL")
83 add_compile_definitions(${LINUX_DEFINES})
84endif(APPLE OR WIN32)
85
86if(BUILD_JAVA_NATIVE_INTERFACE)
87 message(WARNING
88 "Java API and PmDefaults program updated 2021, but support has "
89 "been discontinued. If you need/use this, let developers know.")
90 set(PMJNI_IF_EXISTS "pmjni") # used by INSTALL below
91else(BUILD_JAVA_NATIVE_INTERFACE)
92 set(PMJNI_IF_EXISTS "") # used by INSTALL below
93endif(BUILD_JAVA_NATIVE_INTERFACE)
94
95
96# Something like this might help if you need to build for a specific cpu type:
97# set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING
98# "change to support other architectures" FORCE)
99
100include_directories(pm_common porttime)
101add_subdirectory(pm_common)
102
103option(BUILD_PORTMIDI_TESTS
104 "Build test programs, including midi monitor (mm)" OFF)
105if(BUILD_PORTMIDI_TESTS)
106 add_subdirectory(pm_test)
107endif(BUILD_PORTMIDI_TESTS)
108
109# See note above about Java support (probably) discontinued
110if(BUILD_JAVA_NATIVE_INTERFACE)
111 add_subdirectory(pm_java)
112endif(BUILD_JAVA_NATIVE_INTERFACE)
113
114# Install the libraries and headers (Linux and Mac OS X command line)
115INSTALL(TARGETS portmidi ${PMJNI_IF_EXISTS}
116 EXPORT PortMidiTargets
117 LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
118 ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
119 RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
120 INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
121
122INSTALL(FILES
123 pm_common/portmidi.h
124 pm_common/pmutil.h
125 porttime/porttime.h
126 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
127
128# pkgconfig - generate pc file
129# See https://cmake.org/cmake/help/latest/command/configure_file.html
130if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
131 set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
132else()
133 set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
134endif()
135if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
136 set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
137else()
138 set(PKGCONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
139endif()
140configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/portmidi.pc.in
141 ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc @ONLY)
142install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc
143 DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
144
145# CMake config
146set(PORTMIDI_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/PortMidi")
147install(
148 EXPORT PortMidiTargets
149 FILE PortMidiTargets.cmake
150 NAMESPACE PortMidi::
151 DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
152)
153include(CMakePackageConfigHelpers)
154configure_package_config_file(packaging/PortMidiConfig.cmake.in
155 "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake"
156 INSTALL_DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
157)
158write_basic_package_version_file(
159 "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake"
160 VERSION "${CMAKE_PROJECT_VERSION}"
161 COMPATIBILITY SameMajorVersion
162)
163install(
164 FILES
165 "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake"
166 "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake"
167 DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
168)
169
170
171
172
173# Finding out what CMake is doing is really hard, e.g. COMPILE_FLAGS
174# does not include COMPILE_OPTIONS or COMPILE_DEFINTIONS. Thus, the
175# following report is probably not complete...
176MESSAGE(STATUS "PortMidi Library name: " ${PM_ACTUAL_LIB_NAME})
177MESSAGE(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
178MESSAGE(STATUS "Library Type: " ${LIB_TYPE})
179MESSAGE(STATUS "Compiler flags: " ${CMAKE_CXX_COMPILE_FLAGS})
180get_directory_property(prop COMPILE_DEFINITIONS)
181MESSAGE(STATUS "Compile definitions: " ${prop})
182get_directory_property(prop COMPILE_OPTIONS)
183MESSAGE(STATUS "Compile options: " ${prop})
184MESSAGE(STATUS "Compiler cxx debug flags: " ${CMAKE_CXX_FLAGS_DEBUG})
185MESSAGE(STATUS "Compiler cxx release flags: " ${CMAKE_CXX_FLAGS_RELEASE})
186MESSAGE(STATUS "Compiler cxx min size flags: " ${CMAKE_CXX_FLAGS_MINSIZEREL})
187MESSAGE(STATUS "Compiler cxx flags: " ${CMAKE_CXX_FLAGS})
188
diff --git a/portmidi/Doxyfile b/portmidi/Doxyfile
new file mode 100644
index 0000000..95e3708
--- /dev/null
+++ b/portmidi/Doxyfile
@@ -0,0 +1,2682 @@
1# Doxyfile 1.9.2
2
3# This file describes the settings to be used by the documentation system
4# doxygen (www.doxygen.org) for a project.
5#
6# All text after a double hash (##) is considered a comment and is placed in
7# front of the TAG it is preceding.
8#
9# All text after a single hash (#) is considered a comment and will be ignored.
10# The format is:
11# TAG = value [value, ...]
12# For lists, items can also be appended using:
13# TAG += value [value, ...]
14# Values that contain spaces should be placed between quotes (\" \").
15
16#---------------------------------------------------------------------------
17# Project related configuration options
18#---------------------------------------------------------------------------
19
20# This tag specifies the encoding used for all characters in the configuration
21# file that follow. The default is UTF-8 which is also the encoding used for all
22# text before the first occurrence of this tag. Doxygen uses libiconv (or the
23# iconv built into libc) for the transcoding. See
24# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
25# The default value is: UTF-8.
26
27DOXYFILE_ENCODING = UTF-8
28
29# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
30# double-quotes, unless you are using Doxywizard) that should identify the
31# project for which the documentation is generated. This name is used in the
32# title of most generated pages and in a few other places.
33# The default value is: My Project.
34
35PROJECT_NAME = PortMidi
36
37# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
38# could be handy for archiving the generated documentation or if some version
39# control system is used.
40
41PROJECT_NUMBER =
42
43# Using the PROJECT_BRIEF tag one can provide an optional one line description
44# for a project that appears at the top of each page and should give viewer a
45# quick idea about the purpose of the project. Keep the description short.
46
47PROJECT_BRIEF = "Cross-platform MIDI IO library"
48
49# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
50# in the documentation. The maximum height of the logo should not exceed 55
51# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
52# the logo to the output directory.
53
54PROJECT_LOGO = portmusic_logo.png
55
56# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
57# into which the generated documentation will be written. If a relative path is
58# entered, it will be relative to the location where doxygen was started. If
59# left blank the current directory will be used.
60
61OUTPUT_DIRECTORY = ../github-portmidi-portmidi_docs
62
63# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
64# directories (in 2 levels) under the output directory of each output format and
65# will distribute the generated files over these directories. Enabling this
66# option can be useful when feeding doxygen a huge amount of source files, where
67# putting all generated files in the same directory would otherwise causes
68# performance problems for the file system.
69# The default value is: NO.
70
71CREATE_SUBDIRS = NO
72
73# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
74# characters to appear in the names of generated files. If set to NO, non-ASCII
75# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
76# U+3044.
77# The default value is: NO.
78
79ALLOW_UNICODE_NAMES = NO
80
81# The OUTPUT_LANGUAGE tag is used to specify the language in which all
82# documentation generated by doxygen is written. Doxygen will use this
83# information to generate all constant output in the proper language.
84# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
85# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
86# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
87# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
88# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
89# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
90# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
91# Ukrainian and Vietnamese.
92# The default value is: English.
93
94OUTPUT_LANGUAGE = English
95
96# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
97# descriptions after the members that are listed in the file and class
98# documentation (similar to Javadoc). Set to NO to disable this.
99# The default value is: YES.
100
101BRIEF_MEMBER_DESC = YES
102
103# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
104# description of a member or function before the detailed description
105#
106# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
107# brief descriptions will be completely suppressed.
108# The default value is: YES.
109
110REPEAT_BRIEF = YES
111
112# This tag implements a quasi-intelligent brief description abbreviator that is
113# used to form the text in various listings. Each string in this list, if found
114# as the leading text of the brief description, will be stripped from the text
115# and the result, after processing the whole list, is used as the annotated
116# text. Otherwise, the brief description is used as-is. If left blank, the
117# following values are used ($name is automatically replaced with the name of
118# the entity):The $name class, The $name widget, The $name file, is, provides,
119# specifies, contains, represents, a, an and the.
120
121ABBREVIATE_BRIEF = "The $name class" \
122 "The $name widget" \
123 "The $name file" \
124 is \
125 provides \
126 specifies \
127 contains \
128 represents \
129 a \
130 an \
131 the
132
133# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
134# doxygen will generate a detailed section even if there is only a brief
135# description.
136# The default value is: NO.
137
138ALWAYS_DETAILED_SEC = NO
139
140# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
141# inherited members of a class in the documentation of that class as if those
142# members were ordinary class members. Constructors, destructors and assignment
143# operators of the base classes will not be shown.
144# The default value is: NO.
145
146INLINE_INHERITED_MEMB = NO
147
148# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
149# before files name in the file list and in the header files. If set to NO the
150# shortest path that makes the file name unique will be used
151# The default value is: YES.
152
153FULL_PATH_NAMES = YES
154
155# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
156# Stripping is only done if one of the specified strings matches the left-hand
157# part of the path. The tag can be used to show relative paths in the file list.
158# If left blank the directory from which doxygen is run is used as the path to
159# strip.
160#
161# Note that you can specify absolute paths here, but also relative paths, which
162# will be relative from the directory where doxygen is started.
163# This tag requires that the tag FULL_PATH_NAMES is set to YES.
164
165STRIP_FROM_PATH =
166
167# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
168# path mentioned in the documentation of a class, which tells the reader which
169# header file to include in order to use a class. If left blank only the name of
170# the header file containing the class definition is used. Otherwise one should
171# specify the list of include paths that are normally passed to the compiler
172# using the -I flag.
173
174STRIP_FROM_INC_PATH =
175
176# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
177# less readable) file names. This can be useful is your file systems doesn't
178# support long names like on DOS, Mac, or CD-ROM.
179# The default value is: NO.
180
181SHORT_NAMES = NO
182
183# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
184# first line (until the first dot) of a Javadoc-style comment as the brief
185# description. If set to NO, the Javadoc-style will behave just like regular Qt-
186# style comments (thus requiring an explicit @brief command for a brief
187# description.)
188# The default value is: NO.
189
190JAVADOC_AUTOBRIEF = YES
191
192# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
193# such as
194# /***************
195# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
196# Javadoc-style will behave just like regular comments and it will not be
197# interpreted by doxygen.
198# The default value is: NO.
199
200JAVADOC_BANNER = NO
201
202# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
203# line (until the first dot) of a Qt-style comment as the brief description. If
204# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
205# requiring an explicit \brief command for a brief description.)
206# The default value is: NO.
207
208QT_AUTOBRIEF = NO
209
210# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
211# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
212# a brief description. This used to be the default behavior. The new default is
213# to treat a multi-line C++ comment block as a detailed description. Set this
214# tag to YES if you prefer the old behavior instead.
215#
216# Note that setting this tag to YES also means that rational rose comments are
217# not recognized any more.
218# The default value is: NO.
219
220MULTILINE_CPP_IS_BRIEF = NO
221
222# By default Python docstrings are displayed as preformatted text and doxygen's
223# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
224# doxygen's special commands can be used and the contents of the docstring
225# documentation blocks is shown as doxygen documentation.
226# The default value is: YES.
227
228PYTHON_DOCSTRING = YES
229
230# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
231# documentation from any documented member that it re-implements.
232# The default value is: YES.
233
234INHERIT_DOCS = YES
235
236# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
237# page for each member. If set to NO, the documentation of a member will be part
238# of the file/class/namespace that contains it.
239# The default value is: NO.
240
241SEPARATE_MEMBER_PAGES = NO
242
243# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
244# uses this value to replace tabs by spaces in code fragments.
245# Minimum value: 1, maximum value: 16, default value: 4.
246
247TAB_SIZE = 4
248
249# This tag can be used to specify a number of aliases that act as commands in
250# the documentation. An alias has the form:
251# name=value
252# For example adding
253# "sideeffect=@par Side Effects:^^"
254# will allow you to put the command \sideeffect (or @sideeffect) in the
255# documentation, which will result in a user-defined paragraph with heading
256# "Side Effects:". Note that you cannot put \n's in the value part of an alias
257# to insert newlines (in the resulting output). You can put ^^ in the value part
258# of an alias to insert a newline as if a physical newline was in the original
259# file. When you need a literal { or } or , in the value part of an alias you
260# have to escape them by means of a backslash (\), this can lead to conflicts
261# with the commands \{ and \} for these it is advised to use the version @{ and
262# @} or use a double escape (\\{ and \\})
263
264ALIASES =
265
266# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
267# only. Doxygen will then generate output that is more tailored for C. For
268# instance, some of the names that are used will be different. The list of all
269# members will be omitted, etc.
270# The default value is: NO.
271
272OPTIMIZE_OUTPUT_FOR_C = NO
273
274# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
275# Python sources only. Doxygen will then generate output that is more tailored
276# for that language. For instance, namespaces will be presented as packages,
277# qualified scopes will look different, etc.
278# The default value is: NO.
279
280OPTIMIZE_OUTPUT_JAVA = NO
281
282# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
283# sources. Doxygen will then generate output that is tailored for Fortran.
284# The default value is: NO.
285
286OPTIMIZE_FOR_FORTRAN = NO
287
288# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
289# sources. Doxygen will then generate output that is tailored for VHDL.
290# The default value is: NO.
291
292OPTIMIZE_OUTPUT_VHDL = NO
293
294# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
295# sources only. Doxygen will then generate output that is more tailored for that
296# language. For instance, namespaces will be presented as modules, types will be
297# separated into more groups, etc.
298# The default value is: NO.
299
300OPTIMIZE_OUTPUT_SLICE = NO
301
302# Doxygen selects the parser to use depending on the extension of the files it
303# parses. With this tag you can assign which parser to use for a given
304# extension. Doxygen has a built-in mapping, but you can override or extend it
305# using this tag. The format is ext=language, where ext is a file extension, and
306# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
307# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
308# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
309# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
310# tries to guess whether the code is fixed or free formatted code, this is the
311# default for Fortran type files). For instance to make doxygen treat .inc files
312# as Fortran files (default is PHP), and .f files as C (default is Fortran),
313# use: inc=Fortran f=C.
314#
315# Note: For files without extension you can use no_extension as a placeholder.
316#
317# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
318# the files are not read by doxygen. When specifying no_extension you should add
319# * to the FILE_PATTERNS.
320#
321# Note see also the list of default file extension mappings.
322
323EXTENSION_MAPPING =
324
325# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
326# according to the Markdown format, which allows for more readable
327# documentation. See https://daringfireball.net/projects/markdown/ for details.
328# The output of markdown processing is further processed by doxygen, so you can
329# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
330# case of backward compatibilities issues.
331# The default value is: YES.
332
333MARKDOWN_SUPPORT = YES
334
335# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
336# to that level are automatically included in the table of contents, even if
337# they do not have an id attribute.
338# Note: This feature currently applies only to Markdown headings.
339# Minimum value: 0, maximum value: 99, default value: 5.
340# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
341
342TOC_INCLUDE_HEADINGS = 5
343
344# When enabled doxygen tries to link words that correspond to documented
345# classes, or namespaces to their corresponding documentation. Such a link can
346# be prevented in individual cases by putting a % sign in front of the word or
347# globally by setting AUTOLINK_SUPPORT to NO.
348# The default value is: YES.
349
350AUTOLINK_SUPPORT = YES
351
352# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
353# to include (a tag file for) the STL sources as input, then you should set this
354# tag to YES in order to let doxygen match functions declarations and
355# definitions whose arguments contain STL classes (e.g. func(std::string);
356# versus func(std::string) {}). This also make the inheritance and collaboration
357# diagrams that involve STL classes more complete and accurate.
358# The default value is: NO.
359
360BUILTIN_STL_SUPPORT = NO
361
362# If you use Microsoft's C++/CLI language, you should set this option to YES to
363# enable parsing support.
364# The default value is: NO.
365
366CPP_CLI_SUPPORT = NO
367
368# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
369# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
370# will parse them like normal C++ but will assume all classes use public instead
371# of private inheritance when no explicit protection keyword is present.
372# The default value is: NO.
373
374SIP_SUPPORT = NO
375
376# For Microsoft's IDL there are propget and propput attributes to indicate
377# getter and setter methods for a property. Setting this option to YES will make
378# doxygen to replace the get and set methods by a property in the documentation.
379# This will only work if the methods are indeed getting or setting a simple
380# type. If this is not the case, or you want to show the methods anyway, you
381# should set this option to NO.
382# The default value is: YES.
383
384IDL_PROPERTY_SUPPORT = YES
385
386# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
387# tag is set to YES then doxygen will reuse the documentation of the first
388# member in the group (if any) for the other members of the group. By default
389# all members of a group must be documented explicitly.
390# The default value is: NO.
391
392DISTRIBUTE_GROUP_DOC = NO
393
394# If one adds a struct or class to a group and this option is enabled, then also
395# any nested class or struct is added to the same group. By default this option
396# is disabled and one has to add nested compounds explicitly via \ingroup.
397# The default value is: NO.
398
399GROUP_NESTED_COMPOUNDS = NO
400
401# Set the SUBGROUPING tag to YES to allow class member groups of the same type
402# (for instance a group of public functions) to be put as a subgroup of that
403# type (e.g. under the Public Functions section). Set it to NO to prevent
404# subgrouping. Alternatively, this can be done per class using the
405# \nosubgrouping command.
406# The default value is: YES.
407
408SUBGROUPING = YES
409
410# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
411# are shown inside the group in which they are included (e.g. using \ingroup)
412# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
413# and RTF).
414#
415# Note that this feature does not work in combination with
416# SEPARATE_MEMBER_PAGES.
417# The default value is: NO.
418
419INLINE_GROUPED_CLASSES = NO
420
421# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
422# with only public data fields or simple typedef fields will be shown inline in
423# the documentation of the scope in which they are defined (i.e. file,
424# namespace, or group documentation), provided this scope is documented. If set
425# to NO, structs, classes, and unions are shown on a separate page (for HTML and
426# Man pages) or section (for LaTeX and RTF).
427# The default value is: NO.
428
429INLINE_SIMPLE_STRUCTS = NO
430
431# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
432# enum is documented as struct, union, or enum with the name of the typedef. So
433# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
434# with name TypeT. When disabled the typedef will appear as a member of a file,
435# namespace, or class. And the struct will be named TypeS. This can typically be
436# useful for C code in case the coding convention dictates that all compound
437# types are typedef'ed and only the typedef is referenced, never the tag name.
438# The default value is: NO.
439
440TYPEDEF_HIDES_STRUCT = YES
441
442# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
443# cache is used to resolve symbols given their name and scope. Since this can be
444# an expensive process and often the same symbol appears multiple times in the
445# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
446# doxygen will become slower. If the cache is too large, memory is wasted. The
447# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
448# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
449# symbols. At the end of a run doxygen will report the cache usage and suggest
450# the optimal cache size from a speed point of view.
451# Minimum value: 0, maximum value: 9, default value: 0.
452
453LOOKUP_CACHE_SIZE = 0
454
455# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
456# during processing. When set to 0 doxygen will based this on the number of
457# cores available in the system. You can set it explicitly to a value larger
458# than 0 to get more control over the balance between CPU load and processing
459# speed. At this moment only the input processing can be done using multiple
460# threads. Since this is still an experimental feature the default is set to 1,
461# which effectively disables parallel processing. Please report any issues you
462# encounter. Generating dot graphs in parallel is controlled by the
463# DOT_NUM_THREADS setting.
464# Minimum value: 0, maximum value: 32, default value: 1.
465
466NUM_PROC_THREADS = 1
467
468#---------------------------------------------------------------------------
469# Build related configuration options
470#---------------------------------------------------------------------------
471
472# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
473# documentation are documented, even if no documentation was available. Private
474# class members and static file members will be hidden unless the
475# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
476# Note: This will also disable the warnings about undocumented members that are
477# normally produced when WARNINGS is set to YES.
478# The default value is: NO.
479
480EXTRACT_ALL = NO
481
482# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
483# be included in the documentation.
484# The default value is: NO.
485
486EXTRACT_PRIVATE = NO
487
488# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
489# methods of a class will be included in the documentation.
490# The default value is: NO.
491
492EXTRACT_PRIV_VIRTUAL = NO
493
494# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
495# scope will be included in the documentation.
496# The default value is: NO.
497
498EXTRACT_PACKAGE = NO
499
500# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
501# included in the documentation.
502# The default value is: NO.
503
504EXTRACT_STATIC = NO
505
506# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
507# locally in source files will be included in the documentation. If set to NO,
508# only classes defined in header files are included. Does not have any effect
509# for Java sources.
510# The default value is: YES.
511
512EXTRACT_LOCAL_CLASSES = YES
513
514# This flag is only useful for Objective-C code. If set to YES, local methods,
515# which are defined in the implementation section but not in the interface are
516# included in the documentation. If set to NO, only methods in the interface are
517# included.
518# The default value is: NO.
519
520EXTRACT_LOCAL_METHODS = NO
521
522# If this flag is set to YES, the members of anonymous namespaces will be
523# extracted and appear in the documentation as a namespace called
524# 'anonymous_namespace{file}', where file will be replaced with the base name of
525# the file that contains the anonymous namespace. By default anonymous namespace
526# are hidden.
527# The default value is: NO.
528
529EXTRACT_ANON_NSPACES = NO
530
531# If this flag is set to YES, the name of an unnamed parameter in a declaration
532# will be determined by the corresponding definition. By default unnamed
533# parameters remain unnamed in the output.
534# The default value is: YES.
535
536RESOLVE_UNNAMED_PARAMS = YES
537
538# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
539# undocumented members inside documented classes or files. If set to NO these
540# members will be included in the various overviews, but no documentation
541# section is generated. This option has no effect if EXTRACT_ALL is enabled.
542# The default value is: NO.
543
544HIDE_UNDOC_MEMBERS = NO
545
546# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
547# undocumented classes that are normally visible in the class hierarchy. If set
548# to NO, these classes will be included in the various overviews. This option
549# has no effect if EXTRACT_ALL is enabled.
550# The default value is: NO.
551
552HIDE_UNDOC_CLASSES = NO
553
554# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
555# declarations. If set to NO, these declarations will be included in the
556# documentation.
557# The default value is: NO.
558
559HIDE_FRIEND_COMPOUNDS = NO
560
561# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
562# documentation blocks found inside the body of a function. If set to NO, these
563# blocks will be appended to the function's detailed documentation block.
564# The default value is: NO.
565
566HIDE_IN_BODY_DOCS = NO
567
568# The INTERNAL_DOCS tag determines if documentation that is typed after a
569# \internal command is included. If the tag is set to NO then the documentation
570# will be excluded. Set it to YES to include the internal documentation.
571# The default value is: NO.
572
573INTERNAL_DOCS = NO
574
575# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
576# able to match the capabilities of the underlying filesystem. In case the
577# filesystem is case sensitive (i.e. it supports files in the same directory
578# whose names only differ in casing), the option must be set to YES to properly
579# deal with such files in case they appear in the input. For filesystems that
580# are not case sensitive the option should be be set to NO to properly deal with
581# output files written for symbols that only differ in casing, such as for two
582# classes, one named CLASS and the other named Class, and to also support
583# references to files without having to specify the exact matching casing. On
584# Windows (including Cygwin) and MacOS, users should typically set this option
585# to NO, whereas on Linux or other Unix flavors it should typically be set to
586# YES.
587# The default value is: system dependent.
588
589CASE_SENSE_NAMES = NO
590
591# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
592# their full class and namespace scopes in the documentation. If set to YES, the
593# scope will be hidden.
594# The default value is: NO.
595
596HIDE_SCOPE_NAMES = YES
597
598# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
599# append additional text to a page's title, such as Class Reference. If set to
600# YES the compound reference will be hidden.
601# The default value is: NO.
602
603HIDE_COMPOUND_REFERENCE= NO
604
605# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
606# will show which file needs to be included to use the class.
607# The default value is: YES.
608
609SHOW_HEADERFILE = YES
610
611# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
612# the files that are included by a file in the documentation of that file.
613# The default value is: YES.
614
615SHOW_INCLUDE_FILES = YES
616
617# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
618# grouped member an include statement to the documentation, telling the reader
619# which file to include in order to use the member.
620# The default value is: NO.
621
622SHOW_GROUPED_MEMB_INC = NO
623
624# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
625# files with double quotes in the documentation rather than with sharp brackets.
626# The default value is: NO.
627
628FORCE_LOCAL_INCLUDES = NO
629
630# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
631# documentation for inline members.
632# The default value is: YES.
633
634INLINE_INFO = YES
635
636# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
637# (detailed) documentation of file and class members alphabetically by member
638# name. If set to NO, the members will appear in declaration order.
639# The default value is: YES.
640
641SORT_MEMBER_DOCS = YES
642
643# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
644# descriptions of file, namespace and class members alphabetically by member
645# name. If set to NO, the members will appear in declaration order. Note that
646# this will also influence the order of the classes in the class list.
647# The default value is: NO.
648
649SORT_BRIEF_DOCS = NO
650
651# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
652# (brief and detailed) documentation of class members so that constructors and
653# destructors are listed first. If set to NO the constructors will appear in the
654# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
655# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
656# member documentation.
657# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
658# detailed member documentation.
659# The default value is: NO.
660
661SORT_MEMBERS_CTORS_1ST = NO
662
663# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
664# of group names into alphabetical order. If set to NO the group names will
665# appear in their defined order.
666# The default value is: NO.
667
668SORT_GROUP_NAMES = NO
669
670# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
671# fully-qualified names, including namespaces. If set to NO, the class list will
672# be sorted only by class name, not including the namespace part.
673# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
674# Note: This option applies only to the class list, not to the alphabetical
675# list.
676# The default value is: NO.
677
678SORT_BY_SCOPE_NAME = NO
679
680# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
681# type resolution of all parameters of a function it will reject a match between
682# the prototype and the implementation of a member function even if there is
683# only one candidate or it is obvious which candidate to choose by doing a
684# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
685# accept a match between prototype and implementation in such cases.
686# The default value is: NO.
687
688STRICT_PROTO_MATCHING = NO
689
690# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
691# list. This list is created by putting \todo commands in the documentation.
692# The default value is: YES.
693
694GENERATE_TODOLIST = YES
695
696# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
697# list. This list is created by putting \test commands in the documentation.
698# The default value is: YES.
699
700GENERATE_TESTLIST = YES
701
702# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
703# list. This list is created by putting \bug commands in the documentation.
704# The default value is: YES.
705
706GENERATE_BUGLIST = YES
707
708# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
709# the deprecated list. This list is created by putting \deprecated commands in
710# the documentation.
711# The default value is: YES.
712
713GENERATE_DEPRECATEDLIST= YES
714
715# The ENABLED_SECTIONS tag can be used to enable conditional documentation
716# sections, marked by \if <section_label> ... \endif and \cond <section_label>
717# ... \endcond blocks.
718
719ENABLED_SECTIONS =
720
721# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
722# initial value of a variable or macro / define can have for it to appear in the
723# documentation. If the initializer consists of more lines than specified here
724# it will be hidden. Use a value of 0 to hide initializers completely. The
725# appearance of the value of individual variables and macros / defines can be
726# controlled using \showinitializer or \hideinitializer command in the
727# documentation regardless of this setting.
728# Minimum value: 0, maximum value: 10000, default value: 30.
729
730MAX_INITIALIZER_LINES = 30
731
732# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
733# the bottom of the documentation of classes and structs. If set to YES, the
734# list will mention the files that were used to generate the documentation.
735# The default value is: YES.
736
737SHOW_USED_FILES = YES
738
739# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
740# will remove the Files entry from the Quick Index and from the Folder Tree View
741# (if specified).
742# The default value is: YES.
743
744SHOW_FILES = YES
745
746# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
747# page. This will remove the Namespaces entry from the Quick Index and from the
748# Folder Tree View (if specified).
749# The default value is: YES.
750
751SHOW_NAMESPACES = YES
752
753# The FILE_VERSION_FILTER tag can be used to specify a program or script that
754# doxygen should invoke to get the current version for each file (typically from
755# the version control system). Doxygen will invoke the program by executing (via
756# popen()) the command command input-file, where command is the value of the
757# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
758# by doxygen. Whatever the program writes to standard output is used as the file
759# version. For an example see the documentation.
760
761FILE_VERSION_FILTER =
762
763# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
764# by doxygen. The layout file controls the global structure of the generated
765# output files in an output format independent way. To create the layout file
766# that represents doxygen's defaults, run doxygen with the -l option. You can
767# optionally specify a file name after the option, if omitted DoxygenLayout.xml
768# will be used as the name of the layout file. See also section "Changing the
769# layout of pages" for information.
770#
771# Note that if you run doxygen from a directory containing a file called
772# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
773# tag is left empty.
774
775LAYOUT_FILE =
776
777# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
778# the reference definitions. This must be a list of .bib files. The .bib
779# extension is automatically appended if omitted. This requires the bibtex tool
780# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
781# For LaTeX the style of the bibliography can be controlled using
782# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
783# search path. See also \cite for info how to create references.
784
785CITE_BIB_FILES =
786
787#---------------------------------------------------------------------------
788# Configuration options related to warning and progress messages
789#---------------------------------------------------------------------------
790
791# The QUIET tag can be used to turn on/off the messages that are generated to
792# standard output by doxygen. If QUIET is set to YES this implies that the
793# messages are off.
794# The default value is: NO.
795
796QUIET = NO
797
798# The WARNINGS tag can be used to turn on/off the warning messages that are
799# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
800# this implies that the warnings are on.
801#
802# Tip: Turn warnings on while writing the documentation.
803# The default value is: YES.
804
805WARNINGS = YES
806
807# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
808# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
809# will automatically be disabled.
810# The default value is: YES.
811
812WARN_IF_UNDOCUMENTED = YES
813
814# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
815# potential errors in the documentation, such as documenting some parameters in
816# a documented function twice, or documenting parameters that don't exist or
817# using markup commands wrongly.
818# The default value is: YES.
819
820WARN_IF_DOC_ERROR = YES
821
822# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
823# function parameter documentation. If set to NO, doxygen will accept that some
824# parameters have no documentation without warning.
825# The default value is: YES.
826
827WARN_IF_INCOMPLETE_DOC = YES
828
829# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
830# are documented, but have no documentation for their parameters or return
831# value. If set to NO, doxygen will only warn about wrong parameter
832# documentation, but not about the absence of documentation. If EXTRACT_ALL is
833# set to YES then this flag will automatically be disabled. See also
834# WARN_IF_INCOMPLETE_DOC
835# The default value is: NO.
836
837WARN_NO_PARAMDOC = NO
838
839# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
840# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
841# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
842# at the end of the doxygen process doxygen will return with a non-zero status.
843# Possible values are: NO, YES and FAIL_ON_WARNINGS.
844# The default value is: NO.
845
846WARN_AS_ERROR = NO
847
848# The WARN_FORMAT tag determines the format of the warning messages that doxygen
849# can produce. The string should contain the $file, $line, and $text tags, which
850# will be replaced by the file and line number from which the warning originated
851# and the warning text. Optionally the format may contain $version, which will
852# be replaced by the version of the file (if it could be obtained via
853# FILE_VERSION_FILTER)
854# The default value is: $file:$line: $text.
855
856WARN_FORMAT = "$file:$line: $text"
857
858# The WARN_LOGFILE tag can be used to specify a file to which warning and error
859# messages should be written. If left blank the output is written to standard
860# error (stderr).
861
862WARN_LOGFILE =
863
864#---------------------------------------------------------------------------
865# Configuration options related to the input files
866#---------------------------------------------------------------------------
867
868# The INPUT tag is used to specify the files and/or directories that contain
869# documented source files. You may enter file names like myfile.cpp or
870# directories like /usr/src/myproject. Separate the files or directories with
871# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
872# Note: If this tag is empty the current directory is searched.
873
874INPUT =pm_common porttime/porttime.h pm_common/pmutil.h
875
876# This tag can be used to specify the character encoding of the source files
877# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
878# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
879# documentation (see:
880# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
881# The default value is: UTF-8.
882
883INPUT_ENCODING = UTF-8
884
885# If the value of the INPUT tag contains directories, you can use the
886# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
887# *.h) to filter out the source-files in the directories.
888#
889# Note that for custom extensions or not directly supported extensions you also
890# need to set EXTENSION_MAPPING for the extension otherwise the files are not
891# read by doxygen.
892#
893# Note the list of default checked file patterns might differ from the list of
894# default file extension mappings.
895#
896# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
897# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
898# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
899# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
900# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
901# *.vhdl, *.ucf, *.qsf and *.ice.
902
903FILE_PATTERNS = *.c \
904 *.cc \
905 *.cxx \
906 *.cpp \
907 *.c++ \
908 *.java \
909 *.ii \
910 *.ixx \
911 *.ipp \
912 *.i++ \
913 *.inl \
914 *.idl \
915 *.ddl \
916 *.odl \
917 *.h \
918 *.hh \
919 *.hxx \
920 *.hpp \
921 *.h++ \
922 *.l \
923 *.cs \
924 *.d \
925 *.php \
926 *.php4 \
927 *.php5 \
928 *.phtml \
929 *.inc \
930 *.m \
931 *.markdown \
932 *.md \
933 *.mm \
934 *.dox \
935 *.py \
936 *.pyw \
937 *.f90 \
938 *.f95 \
939 *.f03 \
940 *.f08 \
941 *.f18 \
942 *.f \
943 *.for \
944 *.vhd \
945 *.vhdl \
946 *.ucf \
947 *.qsf \
948 *.ice
949
950# The RECURSIVE tag can be used to specify whether or not subdirectories should
951# be searched for input files as well.
952# The default value is: NO.
953
954RECURSIVE = YES
955
956# The EXCLUDE tag can be used to specify files and/or directories that should be
957# excluded from the INPUT source files. This way you can easily exclude a
958# subdirectory from a directory tree whose root is specified with the INPUT tag.
959#
960# Note that relative paths are relative to the directory from which doxygen is
961# run.
962
963EXCLUDE =
964
965# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
966# directories that are symbolic links (a Unix file system feature) are excluded
967# from the input.
968# The default value is: NO.
969
970EXCLUDE_SYMLINKS = NO
971
972# If the value of the INPUT tag contains directories, you can use the
973# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
974# certain files from those directories.
975#
976# Note that the wildcards are matched against the file with absolute path, so to
977# exclude all test directories for example use the pattern */test/*
978
979EXCLUDE_PATTERNS =
980
981# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
982# (namespaces, classes, functions, etc.) that should be excluded from the
983# output. The symbol name can be a fully qualified name, a word, or if the
984# wildcard * is used, a substring. Examples: ANamespace, AClass,
985# AClass::ANamespace, ANamespace::*Test
986#
987# Note that the wildcards are matched against the file with absolute path, so to
988# exclude all test directories use the pattern */test/*
989
990EXCLUDE_SYMBOLS = TRUE, FALSE, PMEXPORT
991
992# The EXAMPLE_PATH tag can be used to specify one or more files or directories
993# that contain example code fragments that are included (see the \include
994# command).
995
996EXAMPLE_PATH =
997
998# If the value of the EXAMPLE_PATH tag contains directories, you can use the
999# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
1000# *.h) to filter out the source-files in the directories. If left blank all
1001# files are included.
1002
1003EXAMPLE_PATTERNS = *
1004
1005# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
1006# searched for input files to be used with the \include or \dontinclude commands
1007# irrespective of the value of the RECURSIVE tag.
1008# The default value is: NO.
1009
1010EXAMPLE_RECURSIVE = NO
1011
1012# The IMAGE_PATH tag can be used to specify one or more files or directories
1013# that contain images that are to be included in the documentation (see the
1014# \image command).
1015
1016IMAGE_PATH =
1017
1018# The INPUT_FILTER tag can be used to specify a program that doxygen should
1019# invoke to filter for each input file. Doxygen will invoke the filter program
1020# by executing (via popen()) the command:
1021#
1022# <filter> <input-file>
1023#
1024# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
1025# name of an input file. Doxygen will then use the output that the filter
1026# program writes to standard output. If FILTER_PATTERNS is specified, this tag
1027# will be ignored.
1028#
1029# Note that the filter must not add or remove lines; it is applied before the
1030# code is scanned, but not when the output code is generated. If lines are added
1031# or removed, the anchors will not be placed correctly.
1032#
1033# Note that for custom extensions or not directly supported extensions you also
1034# need to set EXTENSION_MAPPING for the extension otherwise the files are not
1035# properly processed by doxygen.
1036
1037INPUT_FILTER =
1038
1039# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
1040# basis. Doxygen will compare the file name with each pattern and apply the
1041# filter if there is a match. The filters are a list of the form: pattern=filter
1042# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
1043# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
1044# patterns match the file name, INPUT_FILTER is applied.
1045#
1046# Note that for custom extensions or not directly supported extensions you also
1047# need to set EXTENSION_MAPPING for the extension otherwise the files are not
1048# properly processed by doxygen.
1049
1050FILTER_PATTERNS =
1051
1052# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
1053# INPUT_FILTER) will also be used to filter the input files that are used for
1054# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
1055# The default value is: NO.
1056
1057FILTER_SOURCE_FILES = NO
1058
1059# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
1060# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
1061# it is also possible to disable source filtering for a specific pattern using
1062# *.ext= (so without naming a filter).
1063# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
1064
1065FILTER_SOURCE_PATTERNS =
1066
1067# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
1068# is part of the input, its contents will be placed on the main page
1069# (index.html). This can be useful if you have a project on for instance GitHub
1070# and want to reuse the introduction page also for the doxygen output.
1071
1072USE_MDFILE_AS_MAINPAGE =
1073
1074#---------------------------------------------------------------------------
1075# Configuration options related to source browsing
1076#---------------------------------------------------------------------------
1077
1078# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
1079# generated. Documented entities will be cross-referenced with these sources.
1080#
1081# Note: To get rid of all source code in the generated output, make sure that
1082# also VERBATIM_HEADERS is set to NO.
1083# The default value is: NO.
1084
1085SOURCE_BROWSER = NO
1086
1087# Setting the INLINE_SOURCES tag to YES will include the body of functions,
1088# classes and enums directly into the documentation.
1089# The default value is: NO.
1090
1091INLINE_SOURCES = NO
1092
1093# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
1094# special comment blocks from generated source code fragments. Normal C, C++ and
1095# Fortran comments will always remain visible.
1096# The default value is: YES.
1097
1098STRIP_CODE_COMMENTS = YES
1099
1100# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
1101# entity all documented functions referencing it will be listed.
1102# The default value is: NO.
1103
1104REFERENCED_BY_RELATION = NO
1105
1106# If the REFERENCES_RELATION tag is set to YES then for each documented function
1107# all documented entities called/used by that function will be listed.
1108# The default value is: NO.
1109
1110REFERENCES_RELATION = NO
1111
1112# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
1113# to YES then the hyperlinks from functions in REFERENCES_RELATION and
1114# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
1115# link to the documentation.
1116# The default value is: YES.
1117
1118REFERENCES_LINK_SOURCE = YES
1119
1120# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
1121# source code will show a tooltip with additional information such as prototype,
1122# brief description and links to the definition and documentation. Since this
1123# will make the HTML file larger and loading of large files a bit slower, you
1124# can opt to disable this feature.
1125# The default value is: YES.
1126# This tag requires that the tag SOURCE_BROWSER is set to YES.
1127
1128SOURCE_TOOLTIPS = YES
1129
1130# If the USE_HTAGS tag is set to YES then the references to source code will
1131# point to the HTML generated by the htags(1) tool instead of doxygen built-in
1132# source browser. The htags tool is part of GNU's global source tagging system
1133# (see https://www.gnu.org/software/global/global.html). You will need version
1134# 4.8.6 or higher.
1135#
1136# To use it do the following:
1137# - Install the latest version of global
1138# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
1139# - Make sure the INPUT points to the root of the source tree
1140# - Run doxygen as normal
1141#
1142# Doxygen will invoke htags (and that will in turn invoke gtags), so these
1143# tools must be available from the command line (i.e. in the search path).
1144#
1145# The result: instead of the source browser generated by doxygen, the links to
1146# source code will now point to the output of htags.
1147# The default value is: NO.
1148# This tag requires that the tag SOURCE_BROWSER is set to YES.
1149
1150USE_HTAGS = NO
1151
1152# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
1153# verbatim copy of the header file for each class for which an include is
1154# specified. Set to NO to disable this.
1155# See also: Section \class.
1156# The default value is: YES.
1157
1158VERBATIM_HEADERS = YES
1159
1160# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
1161# clang parser (see:
1162# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
1163# performance. This can be particularly helpful with template rich C++ code for
1164# which doxygen's built-in parser lacks the necessary type information.
1165# Note: The availability of this option depends on whether or not doxygen was
1166# generated with the -Duse_libclang=ON option for CMake.
1167# The default value is: NO.
1168
1169CLANG_ASSISTED_PARSING = NO
1170
1171# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
1172# tag is set to YES then doxygen will add the directory of each input to the
1173# include path.
1174# The default value is: YES.
1175# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
1176
1177CLANG_ADD_INC_PATHS = YES
1178
1179# If clang assisted parsing is enabled you can provide the compiler with command
1180# line options that you would normally use when invoking the compiler. Note that
1181# the include paths will already be set by doxygen for the files and directories
1182# specified with INPUT and INCLUDE_PATH.
1183# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
1184
1185CLANG_OPTIONS =
1186
1187# If clang assisted parsing is enabled you can provide the clang parser with the
1188# path to the directory containing a file called compile_commands.json. This
1189# file is the compilation database (see:
1190# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
1191# options used when the source files were built. This is equivalent to
1192# specifying the -p option to a clang tool, such as clang-check. These options
1193# will then be passed to the parser. Any options specified with CLANG_OPTIONS
1194# will be added as well.
1195# Note: The availability of this option depends on whether or not doxygen was
1196# generated with the -Duse_libclang=ON option for CMake.
1197
1198CLANG_DATABASE_PATH =
1199
1200#---------------------------------------------------------------------------
1201# Configuration options related to the alphabetical class index
1202#---------------------------------------------------------------------------
1203
1204# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
1205# compounds will be generated. Enable this if the project contains a lot of
1206# classes, structs, unions or interfaces.
1207# The default value is: YES.
1208
1209ALPHABETICAL_INDEX = YES
1210
1211# In case all classes in a project start with a common prefix, all classes will
1212# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
1213# can be used to specify a prefix (or a list of prefixes) that should be ignored
1214# while generating the index headers.
1215# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
1216
1217IGNORE_PREFIX =
1218
1219#---------------------------------------------------------------------------
1220# Configuration options related to the HTML output
1221#---------------------------------------------------------------------------
1222
1223# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
1224# The default value is: YES.
1225
1226GENERATE_HTML = YES
1227
1228# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
1229# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
1230# it.
1231# The default directory is: html.
1232# This tag requires that the tag GENERATE_HTML is set to YES.
1233
1234HTML_OUTPUT = docs
1235
1236# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
1237# generated HTML page (for example: .htm, .php, .asp).
1238# The default value is: .html.
1239# This tag requires that the tag GENERATE_HTML is set to YES.
1240
1241HTML_FILE_EXTENSION = .html
1242
1243# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
1244# each generated HTML page. If the tag is left blank doxygen will generate a
1245# standard header.
1246#
1247# To get valid HTML the header file that includes any scripts and style sheets
1248# that doxygen needs, which is dependent on the configuration options used (e.g.
1249# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
1250# default header using
1251# doxygen -w html new_header.html new_footer.html new_stylesheet.css
1252# YourConfigFile
1253# and then modify the file new_header.html. See also section "Doxygen usage"
1254# for information on how to generate the default header that doxygen normally
1255# uses.
1256# Note: The header is subject to change so you typically have to regenerate the
1257# default header when upgrading to a newer version of doxygen. For a description
1258# of the possible markers and block names see the documentation.
1259# This tag requires that the tag GENERATE_HTML is set to YES.
1260
1261HTML_HEADER =
1262
1263# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
1264# generated HTML page. If the tag is left blank doxygen will generate a standard
1265# footer. See HTML_HEADER for more information on how to generate a default
1266# footer and what special commands can be used inside the footer. See also
1267# section "Doxygen usage" for information on how to generate the default footer
1268# that doxygen normally uses.
1269# This tag requires that the tag GENERATE_HTML is set to YES.
1270
1271HTML_FOOTER =
1272
1273# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
1274# sheet that is used by each HTML page. It can be used to fine-tune the look of
1275# the HTML output. If left blank doxygen will generate a default style sheet.
1276# See also section "Doxygen usage" for information on how to generate the style
1277# sheet that doxygen normally uses.
1278# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
1279# it is more robust and this tag (HTML_STYLESHEET) will in the future become
1280# obsolete.
1281# This tag requires that the tag GENERATE_HTML is set to YES.
1282
1283HTML_STYLESHEET =
1284
1285# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
1286# cascading style sheets that are included after the standard style sheets
1287# created by doxygen. Using this option one can overrule certain style aspects.
1288# This is preferred over using HTML_STYLESHEET since it does not replace the
1289# standard style sheet and is therefore more robust against future updates.
1290# Doxygen will copy the style sheet files to the output directory.
1291# Note: The order of the extra style sheet files is of importance (e.g. the last
1292# style sheet in the list overrules the setting of the previous ones in the
1293# list). For an example see the documentation.
1294# This tag requires that the tag GENERATE_HTML is set to YES.
1295
1296HTML_EXTRA_STYLESHEET =
1297
1298# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
1299# other source files which should be copied to the HTML output directory. Note
1300# that these files will be copied to the base HTML output directory. Use the
1301# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
1302# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
1303# files will be copied as-is; there are no commands or markers available.
1304# This tag requires that the tag GENERATE_HTML is set to YES.
1305
1306HTML_EXTRA_FILES =
1307
1308# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
1309# will adjust the colors in the style sheet and background images according to
1310# this color. Hue is specified as an angle on a color-wheel, see
1311# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
1312# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
1313# purple, and 360 is red again.
1314# Minimum value: 0, maximum value: 359, default value: 220.
1315# This tag requires that the tag GENERATE_HTML is set to YES.
1316
1317HTML_COLORSTYLE_HUE = 220
1318
1319# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
1320# in the HTML output. For a value of 0 the output will use gray-scales only. A
1321# value of 255 will produce the most vivid colors.
1322# Minimum value: 0, maximum value: 255, default value: 100.
1323# This tag requires that the tag GENERATE_HTML is set to YES.
1324
1325HTML_COLORSTYLE_SAT = 100
1326
1327# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
1328# luminance component of the colors in the HTML output. Values below 100
1329# gradually make the output lighter, whereas values above 100 make the output
1330# darker. The value divided by 100 is the actual gamma applied, so 80 represents
1331# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
1332# change the gamma.
1333# Minimum value: 40, maximum value: 240, default value: 80.
1334# This tag requires that the tag GENERATE_HTML is set to YES.
1335
1336HTML_COLORSTYLE_GAMMA = 80
1337
1338# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
1339# page will contain the date and time when the page was generated. Setting this
1340# to YES can help to show when doxygen was last run and thus if the
1341# documentation is up to date.
1342# The default value is: NO.
1343# This tag requires that the tag GENERATE_HTML is set to YES.
1344
1345HTML_TIMESTAMP = NO
1346
1347# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
1348# documentation will contain a main index with vertical navigation menus that
1349# are dynamically created via JavaScript. If disabled, the navigation index will
1350# consists of multiple levels of tabs that are statically embedded in every HTML
1351# page. Disable this option to support browsers that do not have JavaScript,
1352# like the Qt help browser.
1353# The default value is: YES.
1354# This tag requires that the tag GENERATE_HTML is set to YES.
1355
1356HTML_DYNAMIC_MENUS = YES
1357
1358# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
1359# documentation will contain sections that can be hidden and shown after the
1360# page has loaded.
1361# The default value is: NO.
1362# This tag requires that the tag GENERATE_HTML is set to YES.
1363
1364HTML_DYNAMIC_SECTIONS = NO
1365
1366# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
1367# shown in the various tree structured indices initially; the user can expand
1368# and collapse entries dynamically later on. Doxygen will expand the tree to
1369# such a level that at most the specified number of entries are visible (unless
1370# a fully collapsed tree already exceeds this amount). So setting the number of
1371# entries 1 will produce a full collapsed tree by default. 0 is a special value
1372# representing an infinite number of entries and will result in a full expanded
1373# tree by default.
1374# Minimum value: 0, maximum value: 9999, default value: 100.
1375# This tag requires that the tag GENERATE_HTML is set to YES.
1376
1377HTML_INDEX_NUM_ENTRIES = 100
1378
1379# If the GENERATE_DOCSET tag is set to YES, additional index files will be
1380# generated that can be used as input for Apple's Xcode 3 integrated development
1381# environment (see:
1382# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
1383# create a documentation set, doxygen will generate a Makefile in the HTML
1384# output directory. Running make will produce the docset in that directory and
1385# running make install will install the docset in
1386# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
1387# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
1388# genXcode/_index.html for more information.
1389# The default value is: NO.
1390# This tag requires that the tag GENERATE_HTML is set to YES.
1391
1392GENERATE_DOCSET = NO
1393
1394# This tag determines the name of the docset feed. A documentation feed provides
1395# an umbrella under which multiple documentation sets from a single provider
1396# (such as a company or product suite) can be grouped.
1397# The default value is: Doxygen generated docs.
1398# This tag requires that the tag GENERATE_DOCSET is set to YES.
1399
1400DOCSET_FEEDNAME = "Doxygen generated docs"
1401
1402# This tag specifies a string that should uniquely identify the documentation
1403# set bundle. This should be a reverse domain-name style string, e.g.
1404# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
1405# The default value is: org.doxygen.Project.
1406# This tag requires that the tag GENERATE_DOCSET is set to YES.
1407
1408DOCSET_BUNDLE_ID = org.doxygen.Project
1409
1410# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
1411# the documentation publisher. This should be a reverse domain-name style
1412# string, e.g. com.mycompany.MyDocSet.documentation.
1413# The default value is: org.doxygen.Publisher.
1414# This tag requires that the tag GENERATE_DOCSET is set to YES.
1415
1416DOCSET_PUBLISHER_ID = org.doxygen.Publisher
1417
1418# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
1419# The default value is: Publisher.
1420# This tag requires that the tag GENERATE_DOCSET is set to YES.
1421
1422DOCSET_PUBLISHER_NAME = Publisher
1423
1424# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
1425# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
1426# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
1427# on Windows. In the beginning of 2021 Microsoft took the original page, with
1428# a.o. the download links, offline the HTML help workshop was already many years
1429# in maintenance mode). You can download the HTML help workshop from the web
1430# archives at Installation executable (see:
1431# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
1432# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
1433#
1434# The HTML Help Workshop contains a compiler that can convert all HTML output
1435# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
1436# files are now used as the Windows 98 help format, and will replace the old
1437# Windows help format (.hlp) on all Windows platforms in the future. Compressed
1438# HTML files also contain an index, a table of contents, and you can search for
1439# words in the documentation. The HTML workshop also contains a viewer for
1440# compressed HTML files.
1441# The default value is: NO.
1442# This tag requires that the tag GENERATE_HTML is set to YES.
1443
1444GENERATE_HTMLHELP = NO
1445
1446# The CHM_FILE tag can be used to specify the file name of the resulting .chm
1447# file. You can add a path in front of the file if the result should not be
1448# written to the html output directory.
1449# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1450
1451CHM_FILE =
1452
1453# The HHC_LOCATION tag can be used to specify the location (absolute path
1454# including file name) of the HTML help compiler (hhc.exe). If non-empty,
1455# doxygen will try to run the HTML help compiler on the generated index.hhp.
1456# The file has to be specified with full path.
1457# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1458
1459HHC_LOCATION =
1460
1461# The GENERATE_CHI flag controls if a separate .chi index file is generated
1462# (YES) or that it should be included in the main .chm file (NO).
1463# The default value is: NO.
1464# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1465
1466GENERATE_CHI = NO
1467
1468# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
1469# and project file content.
1470# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1471
1472CHM_INDEX_ENCODING =
1473
1474# The BINARY_TOC flag controls whether a binary table of contents is generated
1475# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
1476# enables the Previous and Next buttons.
1477# The default value is: NO.
1478# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1479
1480BINARY_TOC = NO
1481
1482# The TOC_EXPAND flag can be set to YES to add extra items for group members to
1483# the table of contents of the HTML help documentation and to the tree view.
1484# The default value is: NO.
1485# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
1486
1487TOC_EXPAND = NO
1488
1489# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
1490# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
1491# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
1492# (.qch) of the generated HTML documentation.
1493# The default value is: NO.
1494# This tag requires that the tag GENERATE_HTML is set to YES.
1495
1496GENERATE_QHP = NO
1497
1498# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
1499# the file name of the resulting .qch file. The path specified is relative to
1500# the HTML output folder.
1501# This tag requires that the tag GENERATE_QHP is set to YES.
1502
1503QCH_FILE =
1504
1505# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
1506# Project output. For more information please see Qt Help Project / Namespace
1507# (see:
1508# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
1509# The default value is: org.doxygen.Project.
1510# This tag requires that the tag GENERATE_QHP is set to YES.
1511
1512QHP_NAMESPACE = org.doxygen.Project
1513
1514# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
1515# Help Project output. For more information please see Qt Help Project / Virtual
1516# Folders (see:
1517# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
1518# The default value is: doc.
1519# This tag requires that the tag GENERATE_QHP is set to YES.
1520
1521QHP_VIRTUAL_FOLDER = doc
1522
1523# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
1524# filter to add. For more information please see Qt Help Project / Custom
1525# Filters (see:
1526# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
1527# This tag requires that the tag GENERATE_QHP is set to YES.
1528
1529QHP_CUST_FILTER_NAME =
1530
1531# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
1532# custom filter to add. For more information please see Qt Help Project / Custom
1533# Filters (see:
1534# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
1535# This tag requires that the tag GENERATE_QHP is set to YES.
1536
1537QHP_CUST_FILTER_ATTRS =
1538
1539# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
1540# project's filter section matches. Qt Help Project / Filter Attributes (see:
1541# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
1542# This tag requires that the tag GENERATE_QHP is set to YES.
1543
1544QHP_SECT_FILTER_ATTRS =
1545
1546# The QHG_LOCATION tag can be used to specify the location (absolute path
1547# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
1548# run qhelpgenerator on the generated .qhp file.
1549# This tag requires that the tag GENERATE_QHP is set to YES.
1550
1551QHG_LOCATION =
1552
1553# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
1554# generated, together with the HTML files, they form an Eclipse help plugin. To
1555# install this plugin and make it available under the help contents menu in
1556# Eclipse, the contents of the directory containing the HTML and XML files needs
1557# to be copied into the plugins directory of eclipse. The name of the directory
1558# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
1559# After copying Eclipse needs to be restarted before the help appears.
1560# The default value is: NO.
1561# This tag requires that the tag GENERATE_HTML is set to YES.
1562
1563GENERATE_ECLIPSEHELP = NO
1564
1565# A unique identifier for the Eclipse help plugin. When installing the plugin
1566# the directory name containing the HTML and XML files should also have this
1567# name. Each documentation set should have its own identifier.
1568# The default value is: org.doxygen.Project.
1569# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
1570
1571ECLIPSE_DOC_ID = org.doxygen.Project
1572
1573# If you want full control over the layout of the generated HTML pages it might
1574# be necessary to disable the index and replace it with your own. The
1575# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
1576# of each HTML page. A value of NO enables the index and the value YES disables
1577# it. Since the tabs in the index contain the same information as the navigation
1578# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
1579# The default value is: NO.
1580# This tag requires that the tag GENERATE_HTML is set to YES.
1581
1582DISABLE_INDEX = NO
1583
1584# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
1585# structure should be generated to display hierarchical information. If the tag
1586# value is set to YES, a side panel will be generated containing a tree-like
1587# index structure (just like the one that is generated for HTML Help). For this
1588# to work a browser that supports JavaScript, DHTML, CSS and frames is required
1589# (i.e. any modern browser). Windows users are probably better off using the
1590# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
1591# further fine tune the look of the index (see "Fine-tuning the output"). As an
1592# example, the default style sheet generated by doxygen has an example that
1593# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
1594# Since the tree basically has the same information as the tab index, you could
1595# consider setting DISABLE_INDEX to YES when enabling this option.
1596# The default value is: NO.
1597# This tag requires that the tag GENERATE_HTML is set to YES.
1598
1599GENERATE_TREEVIEW = YES
1600
1601# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
1602# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
1603# area (value NO) or if it should extend to the full height of the window (value
1604# YES). Setting this to YES gives a layout similar to
1605# https://docs.readthedocs.io with more room for contents, but less room for the
1606# project logo, title, and description. If either GENERATOR_TREEVIEW or
1607# DISABLE_INDEX is set to NO, this option has no effect.
1608# The default value is: NO.
1609# This tag requires that the tag GENERATE_HTML is set to YES.
1610
1611FULL_SIDEBAR = NO
1612
1613# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
1614# doxygen will group on one line in the generated HTML documentation.
1615#
1616# Note that a value of 0 will completely suppress the enum values from appearing
1617# in the overview section.
1618# Minimum value: 0, maximum value: 20, default value: 4.
1619# This tag requires that the tag GENERATE_HTML is set to YES.
1620
1621ENUM_VALUES_PER_LINE = 4
1622
1623# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
1624# to set the initial width (in pixels) of the frame in which the tree is shown.
1625# Minimum value: 0, maximum value: 1500, default value: 250.
1626# This tag requires that the tag GENERATE_HTML is set to YES.
1627
1628TREEVIEW_WIDTH = 250
1629
1630# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
1631# external symbols imported via tag files in a separate window.
1632# The default value is: NO.
1633# This tag requires that the tag GENERATE_HTML is set to YES.
1634
1635EXT_LINKS_IN_WINDOW = NO
1636
1637# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
1638# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
1639# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
1640# the HTML output. These images will generally look nicer at scaled resolutions.
1641# Possible values are: png (the default) and svg (looks nicer but requires the
1642# pdf2svg or inkscape tool).
1643# The default value is: png.
1644# This tag requires that the tag GENERATE_HTML is set to YES.
1645
1646HTML_FORMULA_FORMAT = png
1647
1648# Use this tag to change the font size of LaTeX formulas included as images in
1649# the HTML documentation. When you change the font size after a successful
1650# doxygen run you need to manually remove any form_*.png images from the HTML
1651# output directory to force them to be regenerated.
1652# Minimum value: 8, maximum value: 50, default value: 10.
1653# This tag requires that the tag GENERATE_HTML is set to YES.
1654
1655FORMULA_FONTSIZE = 10
1656
1657# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
1658# generated for formulas are transparent PNGs. Transparent PNGs are not
1659# supported properly for IE 6.0, but are supported on all modern browsers.
1660#
1661# Note that when changing this option you need to delete any form_*.png files in
1662# the HTML output directory before the changes have effect.
1663# The default value is: YES.
1664# This tag requires that the tag GENERATE_HTML is set to YES.
1665
1666FORMULA_TRANSPARENT = YES
1667
1668# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
1669# to create new LaTeX commands to be used in formulas as building blocks. See
1670# the section "Including formulas" for details.
1671
1672FORMULA_MACROFILE =
1673
1674# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
1675# https://www.mathjax.org) which uses client side JavaScript for the rendering
1676# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
1677# installed or if you want to formulas look prettier in the HTML output. When
1678# enabled you may also need to install MathJax separately and configure the path
1679# to it using the MATHJAX_RELPATH option.
1680# The default value is: NO.
1681# This tag requires that the tag GENERATE_HTML is set to YES.
1682
1683USE_MATHJAX = NO
1684
1685# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
1686# Note that the different versions of MathJax have different requirements with
1687# regards to the different settings, so it is possible that also other MathJax
1688# settings have to be changed when switching between the different MathJax
1689# versions.
1690# Possible values are: MathJax_2 and MathJax_3.
1691# The default value is: MathJax_2.
1692# This tag requires that the tag USE_MATHJAX is set to YES.
1693
1694MATHJAX_VERSION = MathJax_2
1695
1696# When MathJax is enabled you can set the default output format to be used for
1697# the MathJax output. For more details about the output format see MathJax
1698# version 2 (see:
1699# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
1700# (see:
1701# http://docs.mathjax.org/en/latest/web/components/output.html).
1702# Possible values are: HTML-CSS (which is slower, but has the best
1703# compatibility. This is the name for Mathjax version 2, for MathJax version 3
1704# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
1705# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
1706# is the name for Mathjax version 3, for MathJax version 2 this will be
1707# translated into HTML-CSS) and SVG.
1708# The default value is: HTML-CSS.
1709# This tag requires that the tag USE_MATHJAX is set to YES.
1710
1711MATHJAX_FORMAT = HTML-CSS
1712
1713# When MathJax is enabled you need to specify the location relative to the HTML
1714# output directory using the MATHJAX_RELPATH option. The destination directory
1715# should contain the MathJax.js script. For instance, if the mathjax directory
1716# is located at the same level as the HTML output directory, then
1717# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
1718# Content Delivery Network so you can quickly see the result without installing
1719# MathJax. However, it is strongly recommended to install a local copy of
1720# MathJax from https://www.mathjax.org before deployment. The default value is:
1721# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
1722# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
1723# This tag requires that the tag USE_MATHJAX is set to YES.
1724
1725MATHJAX_RELPATH =
1726
1727# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
1728# extension names that should be enabled during MathJax rendering. For example
1729# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
1730# #tex-and-latex-extensions):
1731# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
1732# For example for MathJax version 3 (see
1733# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
1734# MATHJAX_EXTENSIONS = ams
1735# This tag requires that the tag USE_MATHJAX is set to YES.
1736
1737MATHJAX_EXTENSIONS =
1738
1739# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
1740# of code that will be used on startup of the MathJax code. See the MathJax site
1741# (see:
1742# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
1743# example see the documentation.
1744# This tag requires that the tag USE_MATHJAX is set to YES.
1745
1746MATHJAX_CODEFILE =
1747
1748# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
1749# the HTML output. The underlying search engine uses javascript and DHTML and
1750# should work on any modern browser. Note that when using HTML help
1751# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
1752# there is already a search function so this one should typically be disabled.
1753# For large projects the javascript based search engine can be slow, then
1754# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
1755# search using the keyboard; to jump to the search box use <access key> + S
1756# (what the <access key> is depends on the OS and browser, but it is typically
1757# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
1758# key> to jump into the search results window, the results can be navigated
1759# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
1760# the search. The filter options can be selected when the cursor is inside the
1761# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
1762# to select a filter and <Enter> or <escape> to activate or cancel the filter
1763# option.
1764# The default value is: YES.
1765# This tag requires that the tag GENERATE_HTML is set to YES.
1766
1767SEARCHENGINE = YES
1768
1769# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
1770# implemented using a web server instead of a web client using JavaScript. There
1771# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
1772# setting. When disabled, doxygen will generate a PHP script for searching and
1773# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
1774# and searching needs to be provided by external tools. See the section
1775# "External Indexing and Searching" for details.
1776# The default value is: NO.
1777# This tag requires that the tag SEARCHENGINE is set to YES.
1778
1779SERVER_BASED_SEARCH = NO
1780
1781# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
1782# script for searching. Instead the search results are written to an XML file
1783# which needs to be processed by an external indexer. Doxygen will invoke an
1784# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
1785# search results.
1786#
1787# Doxygen ships with an example indexer (doxyindexer) and search engine
1788# (doxysearch.cgi) which are based on the open source search engine library
1789# Xapian (see:
1790# https://xapian.org/).
1791#
1792# See the section "External Indexing and Searching" for details.
1793# The default value is: NO.
1794# This tag requires that the tag SEARCHENGINE is set to YES.
1795
1796EXTERNAL_SEARCH = NO
1797
1798# The SEARCHENGINE_URL should point to a search engine hosted by a web server
1799# which will return the search results when EXTERNAL_SEARCH is enabled.
1800#
1801# Doxygen ships with an example indexer (doxyindexer) and search engine
1802# (doxysearch.cgi) which are based on the open source search engine library
1803# Xapian (see:
1804# https://xapian.org/). See the section "External Indexing and Searching" for
1805# details.
1806# This tag requires that the tag SEARCHENGINE is set to YES.
1807
1808SEARCHENGINE_URL =
1809
1810# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
1811# search data is written to a file for indexing by an external tool. With the
1812# SEARCHDATA_FILE tag the name of this file can be specified.
1813# The default file is: searchdata.xml.
1814# This tag requires that the tag SEARCHENGINE is set to YES.
1815
1816SEARCHDATA_FILE = searchdata.xml
1817
1818# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
1819# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
1820# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
1821# projects and redirect the results back to the right project.
1822# This tag requires that the tag SEARCHENGINE is set to YES.
1823
1824EXTERNAL_SEARCH_ID =
1825
1826# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
1827# projects other than the one defined by this configuration file, but that are
1828# all added to the same external search index. Each project needs to have a
1829# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
1830# to a relative location where the documentation can be found. The format is:
1831# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
1832# This tag requires that the tag SEARCHENGINE is set to YES.
1833
1834EXTRA_SEARCH_MAPPINGS =
1835
1836#---------------------------------------------------------------------------
1837# Configuration options related to the LaTeX output
1838#---------------------------------------------------------------------------
1839
1840# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
1841# The default value is: YES.
1842
1843GENERATE_LATEX = NO
1844
1845# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
1846# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
1847# it.
1848# The default directory is: latex.
1849# This tag requires that the tag GENERATE_LATEX is set to YES.
1850
1851LATEX_OUTPUT = latex
1852
1853# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
1854# invoked.
1855#
1856# Note that when not enabling USE_PDFLATEX the default is latex when enabling
1857# USE_PDFLATEX the default is pdflatex and when in the later case latex is
1858# chosen this is overwritten by pdflatex. For specific output languages the
1859# default can have been set differently, this depends on the implementation of
1860# the output language.
1861# This tag requires that the tag GENERATE_LATEX is set to YES.
1862
1863LATEX_CMD_NAME =
1864
1865# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
1866# index for LaTeX.
1867# Note: This tag is used in the Makefile / make.bat.
1868# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
1869# (.tex).
1870# The default file is: makeindex.
1871# This tag requires that the tag GENERATE_LATEX is set to YES.
1872
1873MAKEINDEX_CMD_NAME = makeindex
1874
1875# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
1876# generate index for LaTeX. In case there is no backslash (\) as first character
1877# it will be automatically added in the LaTeX code.
1878# Note: This tag is used in the generated output file (.tex).
1879# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
1880# The default value is: makeindex.
1881# This tag requires that the tag GENERATE_LATEX is set to YES.
1882
1883LATEX_MAKEINDEX_CMD = makeindex
1884
1885# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
1886# documents. This may be useful for small projects and may help to save some
1887# trees in general.
1888# The default value is: NO.
1889# This tag requires that the tag GENERATE_LATEX is set to YES.
1890
1891COMPACT_LATEX = NO
1892
1893# The PAPER_TYPE tag can be used to set the paper type that is used by the
1894# printer.
1895# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
1896# 14 inches) and executive (7.25 x 10.5 inches).
1897# The default value is: a4.
1898# This tag requires that the tag GENERATE_LATEX is set to YES.
1899
1900PAPER_TYPE = a4
1901
1902# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
1903# that should be included in the LaTeX output. The package can be specified just
1904# by its name or with the correct syntax as to be used with the LaTeX
1905# \usepackage command. To get the times font for instance you can specify :
1906# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
1907# To use the option intlimits with the amsmath package you can specify:
1908# EXTRA_PACKAGES=[intlimits]{amsmath}
1909# If left blank no extra packages will be included.
1910# This tag requires that the tag GENERATE_LATEX is set to YES.
1911
1912EXTRA_PACKAGES =
1913
1914# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
1915# the generated LaTeX document. The header should contain everything until the
1916# first chapter. If it is left blank doxygen will generate a standard header. It
1917# is highly recommended to start with a default header using
1918# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
1919# and then modify the file new_header.tex. See also section "Doxygen usage" for
1920# information on how to generate the default header that doxygen normally uses.
1921#
1922# Note: Only use a user-defined header if you know what you are doing!
1923# Note: The header is subject to change so you typically have to regenerate the
1924# default header when upgrading to a newer version of doxygen. The following
1925# commands have a special meaning inside the header (and footer): For a
1926# description of the possible markers and block names see the documentation.
1927# This tag requires that the tag GENERATE_LATEX is set to YES.
1928
1929LATEX_HEADER =
1930
1931# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
1932# the generated LaTeX document. The footer should contain everything after the
1933# last chapter. If it is left blank doxygen will generate a standard footer. See
1934# LATEX_HEADER for more information on how to generate a default footer and what
1935# special commands can be used inside the footer. See also section "Doxygen
1936# usage" for information on how to generate the default footer that doxygen
1937# normally uses. Note: Only use a user-defined footer if you know what you are
1938# doing!
1939# This tag requires that the tag GENERATE_LATEX is set to YES.
1940
1941LATEX_FOOTER =
1942
1943# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
1944# LaTeX style sheets that are included after the standard style sheets created
1945# by doxygen. Using this option one can overrule certain style aspects. Doxygen
1946# will copy the style sheet files to the output directory.
1947# Note: The order of the extra style sheet files is of importance (e.g. the last
1948# style sheet in the list overrules the setting of the previous ones in the
1949# list).
1950# This tag requires that the tag GENERATE_LATEX is set to YES.
1951
1952LATEX_EXTRA_STYLESHEET =
1953
1954# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
1955# other source files which should be copied to the LATEX_OUTPUT output
1956# directory. Note that the files will be copied as-is; there are no commands or
1957# markers available.
1958# This tag requires that the tag GENERATE_LATEX is set to YES.
1959
1960LATEX_EXTRA_FILES =
1961
1962# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
1963# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
1964# contain links (just like the HTML output) instead of page references. This
1965# makes the output suitable for online browsing using a PDF viewer.
1966# The default value is: YES.
1967# This tag requires that the tag GENERATE_LATEX is set to YES.
1968
1969PDF_HYPERLINKS = YES
1970
1971# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
1972# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
1973# files. Set this option to YES, to get a higher quality PDF documentation.
1974#
1975# See also section LATEX_CMD_NAME for selecting the engine.
1976# The default value is: YES.
1977# This tag requires that the tag GENERATE_LATEX is set to YES.
1978
1979USE_PDFLATEX = YES
1980
1981# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
1982# command to the generated LaTeX files. This will instruct LaTeX to keep running
1983# if errors occur, instead of asking the user for help.
1984# The default value is: NO.
1985# This tag requires that the tag GENERATE_LATEX is set to YES.
1986
1987LATEX_BATCHMODE = NO
1988
1989# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
1990# index chapters (such as File Index, Compound Index, etc.) in the output.
1991# The default value is: NO.
1992# This tag requires that the tag GENERATE_LATEX is set to YES.
1993
1994LATEX_HIDE_INDICES = NO
1995
1996# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
1997# bibliography, e.g. plainnat, or ieeetr. See
1998# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
1999# The default value is: plain.
2000# This tag requires that the tag GENERATE_LATEX is set to YES.
2001
2002LATEX_BIB_STYLE = plain
2003
2004# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
2005# page will contain the date and time when the page was generated. Setting this
2006# to NO can help when comparing the output of multiple runs.
2007# The default value is: NO.
2008# This tag requires that the tag GENERATE_LATEX is set to YES.
2009
2010LATEX_TIMESTAMP = NO
2011
2012# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
2013# path from which the emoji images will be read. If a relative path is entered,
2014# it will be relative to the LATEX_OUTPUT directory. If left blank the
2015# LATEX_OUTPUT directory will be used.
2016# This tag requires that the tag GENERATE_LATEX is set to YES.
2017
2018LATEX_EMOJI_DIRECTORY =
2019
2020#---------------------------------------------------------------------------
2021# Configuration options related to the RTF output
2022#---------------------------------------------------------------------------
2023
2024# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
2025# RTF output is optimized for Word 97 and may not look too pretty with other RTF
2026# readers/editors.
2027# The default value is: NO.
2028
2029GENERATE_RTF = NO
2030
2031# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
2032# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
2033# it.
2034# The default directory is: rtf.
2035# This tag requires that the tag GENERATE_RTF is set to YES.
2036
2037RTF_OUTPUT = rtf
2038
2039# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
2040# documents. This may be useful for small projects and may help to save some
2041# trees in general.
2042# The default value is: NO.
2043# This tag requires that the tag GENERATE_RTF is set to YES.
2044
2045COMPACT_RTF = NO
2046
2047# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
2048# contain hyperlink fields. The RTF file will contain links (just like the HTML
2049# output) instead of page references. This makes the output suitable for online
2050# browsing using Word or some other Word compatible readers that support those
2051# fields.
2052#
2053# Note: WordPad (write) and others do not support links.
2054# The default value is: NO.
2055# This tag requires that the tag GENERATE_RTF is set to YES.
2056
2057RTF_HYPERLINKS = NO
2058
2059# Load stylesheet definitions from file. Syntax is similar to doxygen's
2060# configuration file, i.e. a series of assignments. You only have to provide
2061# replacements, missing definitions are set to their default value.
2062#
2063# See also section "Doxygen usage" for information on how to generate the
2064# default style sheet that doxygen normally uses.
2065# This tag requires that the tag GENERATE_RTF is set to YES.
2066
2067RTF_STYLESHEET_FILE =
2068
2069# Set optional variables used in the generation of an RTF document. Syntax is
2070# similar to doxygen's configuration file. A template extensions file can be
2071# generated using doxygen -e rtf extensionFile.
2072# This tag requires that the tag GENERATE_RTF is set to YES.
2073
2074RTF_EXTENSIONS_FILE =
2075
2076#---------------------------------------------------------------------------
2077# Configuration options related to the man page output
2078#---------------------------------------------------------------------------
2079
2080# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
2081# classes and files.
2082# The default value is: NO.
2083
2084GENERATE_MAN = NO
2085
2086# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
2087# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
2088# it. A directory man3 will be created inside the directory specified by
2089# MAN_OUTPUT.
2090# The default directory is: man.
2091# This tag requires that the tag GENERATE_MAN is set to YES.
2092
2093MAN_OUTPUT = man
2094
2095# The MAN_EXTENSION tag determines the extension that is added to the generated
2096# man pages. In case the manual section does not start with a number, the number
2097# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
2098# optional.
2099# The default value is: .3.
2100# This tag requires that the tag GENERATE_MAN is set to YES.
2101
2102MAN_EXTENSION = .3
2103
2104# The MAN_SUBDIR tag determines the name of the directory created within
2105# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
2106# MAN_EXTENSION with the initial . removed.
2107# This tag requires that the tag GENERATE_MAN is set to YES.
2108
2109MAN_SUBDIR =
2110
2111# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
2112# will generate one additional man file for each entity documented in the real
2113# man page(s). These additional files only source the real man page, but without
2114# them the man command would be unable to find the correct page.
2115# The default value is: NO.
2116# This tag requires that the tag GENERATE_MAN is set to YES.
2117
2118MAN_LINKS = NO
2119
2120#---------------------------------------------------------------------------
2121# Configuration options related to the XML output
2122#---------------------------------------------------------------------------
2123
2124# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
2125# captures the structure of the code including all documentation.
2126# The default value is: NO.
2127
2128GENERATE_XML = NO
2129
2130# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
2131# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
2132# it.
2133# The default directory is: xml.
2134# This tag requires that the tag GENERATE_XML is set to YES.
2135
2136XML_OUTPUT = xml
2137
2138# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
2139# listings (including syntax highlighting and cross-referencing information) to
2140# the XML output. Note that enabling this will significantly increase the size
2141# of the XML output.
2142# The default value is: YES.
2143# This tag requires that the tag GENERATE_XML is set to YES.
2144
2145XML_PROGRAMLISTING = YES
2146
2147# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
2148# namespace members in file scope as well, matching the HTML output.
2149# The default value is: NO.
2150# This tag requires that the tag GENERATE_XML is set to YES.
2151
2152XML_NS_MEMB_FILE_SCOPE = NO
2153
2154#---------------------------------------------------------------------------
2155# Configuration options related to the DOCBOOK output
2156#---------------------------------------------------------------------------
2157
2158# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
2159# that can be used to generate PDF.
2160# The default value is: NO.
2161
2162GENERATE_DOCBOOK = NO
2163
2164# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
2165# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
2166# front of it.
2167# The default directory is: docbook.
2168# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
2169
2170DOCBOOK_OUTPUT = docbook
2171
2172#---------------------------------------------------------------------------
2173# Configuration options for the AutoGen Definitions output
2174#---------------------------------------------------------------------------
2175
2176# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
2177# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
2178# the structure of the code including all documentation. Note that this feature
2179# is still experimental and incomplete at the moment.
2180# The default value is: NO.
2181
2182GENERATE_AUTOGEN_DEF = NO
2183
2184#---------------------------------------------------------------------------
2185# Configuration options related to Sqlite3 output
2186#---------------------------------------------------------------------------
2187
2188#---------------------------------------------------------------------------
2189# Configuration options related to the Perl module output
2190#---------------------------------------------------------------------------
2191
2192# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
2193# file that captures the structure of the code including all documentation.
2194#
2195# Note that this feature is still experimental and incomplete at the moment.
2196# The default value is: NO.
2197
2198GENERATE_PERLMOD = NO
2199
2200# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
2201# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
2202# output from the Perl module output.
2203# The default value is: NO.
2204# This tag requires that the tag GENERATE_PERLMOD is set to YES.
2205
2206PERLMOD_LATEX = NO
2207
2208# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
2209# formatted so it can be parsed by a human reader. This is useful if you want to
2210# understand what is going on. On the other hand, if this tag is set to NO, the
2211# size of the Perl module output will be much smaller and Perl will parse it
2212# just the same.
2213# The default value is: YES.
2214# This tag requires that the tag GENERATE_PERLMOD is set to YES.
2215
2216PERLMOD_PRETTY = YES
2217
2218# The names of the make variables in the generated doxyrules.make file are
2219# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
2220# so different doxyrules.make files included by the same Makefile don't
2221# overwrite each other's variables.
2222# This tag requires that the tag GENERATE_PERLMOD is set to YES.
2223
2224PERLMOD_MAKEVAR_PREFIX =
2225
2226#---------------------------------------------------------------------------
2227# Configuration options related to the preprocessor
2228#---------------------------------------------------------------------------
2229
2230# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
2231# C-preprocessor directives found in the sources and include files.
2232# The default value is: YES.
2233
2234ENABLE_PREPROCESSING = YES
2235
2236# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
2237# in the source code. If set to NO, only conditional compilation will be
2238# performed. Macro expansion can be done in a controlled way by setting
2239# EXPAND_ONLY_PREDEF to YES.
2240# The default value is: NO.
2241# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2242
2243MACRO_EXPANSION = NO
2244
2245# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
2246# the macro expansion is limited to the macros specified with the PREDEFINED and
2247# EXPAND_AS_DEFINED tags.
2248# The default value is: NO.
2249# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2250
2251EXPAND_ONLY_PREDEF = NO
2252
2253# If the SEARCH_INCLUDES tag is set to YES, the include files in the
2254# INCLUDE_PATH will be searched if a #include is found.
2255# The default value is: YES.
2256# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2257
2258SEARCH_INCLUDES = YES
2259
2260# The INCLUDE_PATH tag can be used to specify one or more directories that
2261# contain include files that are not input files but should be processed by the
2262# preprocessor.
2263# This tag requires that the tag SEARCH_INCLUDES is set to YES.
2264
2265INCLUDE_PATH =
2266
2267# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
2268# patterns (like *.h and *.hpp) to filter out the header-files in the
2269# directories. If left blank, the patterns specified with FILE_PATTERNS will be
2270# used.
2271# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2272
2273INCLUDE_FILE_PATTERNS =
2274
2275# The PREDEFINED tag can be used to specify one or more macro names that are
2276# defined before the preprocessor is started (similar to the -D option of e.g.
2277# gcc). The argument of the tag is a list of macros of the form: name or
2278# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
2279# is assumed. To prevent a macro definition from being undefined via #undef or
2280# recursively expanded use the := operator instead of the = operator.
2281# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2282
2283PREDEFINED =
2284
2285# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
2286# tag can be used to specify a list of macro names that should be expanded. The
2287# macro definition that is found in the sources will be used. Use the PREDEFINED
2288# tag if you want to use a different macro definition that overrules the
2289# definition found in the source code.
2290# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2291
2292EXPAND_AS_DEFINED =
2293
2294# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
2295# remove all references to function-like macros that are alone on a line, have
2296# an all uppercase name, and do not end with a semicolon. Such function macros
2297# are typically used for boiler-plate code, and will confuse the parser if not
2298# removed.
2299# The default value is: YES.
2300# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
2301
2302SKIP_FUNCTION_MACROS = YES
2303
2304#---------------------------------------------------------------------------
2305# Configuration options related to external references
2306#---------------------------------------------------------------------------
2307
2308# The TAGFILES tag can be used to specify one or more tag files. For each tag
2309# file the location of the external documentation should be added. The format of
2310# a tag file without this location is as follows:
2311# TAGFILES = file1 file2 ...
2312# Adding location for the tag files is done as follows:
2313# TAGFILES = file1=loc1 "file2 = loc2" ...
2314# where loc1 and loc2 can be relative or absolute paths or URLs. See the
2315# section "Linking to external documentation" for more information about the use
2316# of tag files.
2317# Note: Each tag file must have a unique name (where the name does NOT include
2318# the path). If a tag file is not located in the directory in which doxygen is
2319# run, you must also specify the path to the tagfile here.
2320
2321TAGFILES =
2322
2323# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
2324# tag file that is based on the input files it reads. See section "Linking to
2325# external documentation" for more information about the usage of tag files.
2326
2327GENERATE_TAGFILE =
2328
2329# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
2330# the class index. If set to NO, only the inherited external classes will be
2331# listed.
2332# The default value is: NO.
2333
2334ALLEXTERNALS = NO
2335
2336# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
2337# in the modules index. If set to NO, only the current project's groups will be
2338# listed.
2339# The default value is: YES.
2340
2341EXTERNAL_GROUPS = YES
2342
2343# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
2344# the related pages index. If set to NO, only the current project's pages will
2345# be listed.
2346# The default value is: YES.
2347
2348EXTERNAL_PAGES = YES
2349
2350#---------------------------------------------------------------------------
2351# Configuration options related to the dot tool
2352#---------------------------------------------------------------------------
2353
2354# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
2355# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
2356# NO turns the diagrams off. Note that this option also works with HAVE_DOT
2357# disabled, but it is recommended to install and use dot, since it yields more
2358# powerful graphs.
2359# The default value is: YES.
2360
2361CLASS_DIAGRAMS = NO
2362
2363# You can include diagrams made with dia in doxygen documentation. Doxygen will
2364# then run dia to produce the diagram and insert it in the documentation. The
2365# DIA_PATH tag allows you to specify the directory where the dia binary resides.
2366# If left empty dia is assumed to be found in the default search path.
2367
2368DIA_PATH =
2369
2370# If set to YES the inheritance and collaboration graphs will hide inheritance
2371# and usage relations if the target is undocumented or is not a class.
2372# The default value is: YES.
2373
2374HIDE_UNDOC_RELATIONS = YES
2375
2376# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
2377# available from the path. This tool is part of Graphviz (see:
2378# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
2379# Bell Labs. The other options in this section have no effect if this option is
2380# set to NO
2381# The default value is: NO.
2382
2383HAVE_DOT = NO
2384
2385# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
2386# to run in parallel. When set to 0 doxygen will base this on the number of
2387# processors available in the system. You can set it explicitly to a value
2388# larger than 0 to get control over the balance between CPU load and processing
2389# speed.
2390# Minimum value: 0, maximum value: 32, default value: 0.
2391# This tag requires that the tag HAVE_DOT is set to YES.
2392
2393DOT_NUM_THREADS = 0
2394
2395# When you want a differently looking font in the dot files that doxygen
2396# generates you can specify the font name using DOT_FONTNAME. You need to make
2397# sure dot is able to find the font, which can be done by putting it in a
2398# standard location or by setting the DOTFONTPATH environment variable or by
2399# setting DOT_FONTPATH to the directory containing the font.
2400# The default value is: Helvetica.
2401# This tag requires that the tag HAVE_DOT is set to YES.
2402
2403DOT_FONTNAME = Helvetica
2404
2405# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
2406# dot graphs.
2407# Minimum value: 4, maximum value: 24, default value: 10.
2408# This tag requires that the tag HAVE_DOT is set to YES.
2409
2410DOT_FONTSIZE = 10
2411
2412# By default doxygen will tell dot to use the default font as specified with
2413# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
2414# the path where dot can find it using this tag.
2415# This tag requires that the tag HAVE_DOT is set to YES.
2416
2417DOT_FONTPATH =
2418
2419# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
2420# each documented class showing the direct and indirect inheritance relations.
2421# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
2422# The default value is: YES.
2423# This tag requires that the tag HAVE_DOT is set to YES.
2424
2425CLASS_GRAPH = YES
2426
2427# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
2428# graph for each documented class showing the direct and indirect implementation
2429# dependencies (inheritance, containment, and class references variables) of the
2430# class with other documented classes.
2431# The default value is: YES.
2432# This tag requires that the tag HAVE_DOT is set to YES.
2433
2434COLLABORATION_GRAPH = YES
2435
2436# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
2437# groups, showing the direct groups dependencies.
2438# The default value is: YES.
2439# This tag requires that the tag HAVE_DOT is set to YES.
2440
2441GROUP_GRAPHS = YES
2442
2443# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
2444# collaboration diagrams in a style similar to the OMG's Unified Modeling
2445# Language.
2446# The default value is: NO.
2447# This tag requires that the tag HAVE_DOT is set to YES.
2448
2449UML_LOOK = NO
2450
2451# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
2452# class node. If there are many fields or methods and many nodes the graph may
2453# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
2454# number of items for each type to make the size more manageable. Set this to 0
2455# for no limit. Note that the threshold may be exceeded by 50% before the limit
2456# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
2457# but if the number exceeds 15, the total amount of fields shown is limited to
2458# 10.
2459# Minimum value: 0, maximum value: 100, default value: 10.
2460# This tag requires that the tag UML_LOOK is set to YES.
2461
2462UML_LIMIT_NUM_FIELDS = 10
2463
2464# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
2465# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
2466# tag is set to YES, doxygen will add type and arguments for attributes and
2467# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
2468# will not generate fields with class member information in the UML graphs. The
2469# class diagrams will look similar to the default class diagrams but using UML
2470# notation for the relationships.
2471# Possible values are: NO, YES and NONE.
2472# The default value is: NO.
2473# This tag requires that the tag UML_LOOK is set to YES.
2474
2475DOT_UML_DETAILS = NO
2476
2477# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
2478# to display on a single line. If the actual line length exceeds this threshold
2479# significantly it will wrapped across multiple lines. Some heuristics are apply
2480# to avoid ugly line breaks.
2481# Minimum value: 0, maximum value: 1000, default value: 17.
2482# This tag requires that the tag HAVE_DOT is set to YES.
2483
2484DOT_WRAP_THRESHOLD = 17
2485
2486# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
2487# collaboration graphs will show the relations between templates and their
2488# instances.
2489# The default value is: NO.
2490# This tag requires that the tag HAVE_DOT is set to YES.
2491
2492TEMPLATE_RELATIONS = NO
2493
2494# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
2495# YES then doxygen will generate a graph for each documented file showing the
2496# direct and indirect include dependencies of the file with other documented
2497# files.
2498# The default value is: YES.
2499# This tag requires that the tag HAVE_DOT is set to YES.
2500
2501INCLUDE_GRAPH = YES
2502
2503# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
2504# set to YES then doxygen will generate a graph for each documented file showing
2505# the direct and indirect include dependencies of the file with other documented
2506# files.
2507# The default value is: YES.
2508# This tag requires that the tag HAVE_DOT is set to YES.
2509
2510INCLUDED_BY_GRAPH = YES
2511
2512# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
2513# dependency graph for every global function or class method.
2514#
2515# Note that enabling this option will significantly increase the time of a run.
2516# So in most cases it will be better to enable call graphs for selected
2517# functions only using the \callgraph command. Disabling a call graph can be
2518# accomplished by means of the command \hidecallgraph.
2519# The default value is: NO.
2520# This tag requires that the tag HAVE_DOT is set to YES.
2521
2522CALL_GRAPH = NO
2523
2524# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
2525# dependency graph for every global function or class method.
2526#
2527# Note that enabling this option will significantly increase the time of a run.
2528# So in most cases it will be better to enable caller graphs for selected
2529# functions only using the \callergraph command. Disabling a caller graph can be
2530# accomplished by means of the command \hidecallergraph.
2531# The default value is: NO.
2532# This tag requires that the tag HAVE_DOT is set to YES.
2533
2534CALLER_GRAPH = NO
2535
2536# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
2537# hierarchy of all classes instead of a textual one.
2538# The default value is: YES.
2539# This tag requires that the tag HAVE_DOT is set to YES.
2540
2541GRAPHICAL_HIERARCHY = YES
2542
2543# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
2544# dependencies a directory has on other directories in a graphical way. The
2545# dependency relations are determined by the #include relations between the
2546# files in the directories.
2547# The default value is: YES.
2548# This tag requires that the tag HAVE_DOT is set to YES.
2549
2550DIRECTORY_GRAPH = YES
2551
2552# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
2553# generated by dot. For an explanation of the image formats see the section
2554# output formats in the documentation of the dot tool (Graphviz (see:
2555# http://www.graphviz.org/)).
2556# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
2557# to make the SVG files visible in IE 9+ (other browsers do not have this
2558# requirement).
2559# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
2560# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
2561# png:gdiplus:gdiplus.
2562# The default value is: png.
2563# This tag requires that the tag HAVE_DOT is set to YES.
2564
2565DOT_IMAGE_FORMAT = png
2566
2567# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
2568# enable generation of interactive SVG images that allow zooming and panning.
2569#
2570# Note that this requires a modern browser other than Internet Explorer. Tested
2571# and working are Firefox, Chrome, Safari, and Opera.
2572# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
2573# the SVG files visible. Older versions of IE do not have SVG support.
2574# The default value is: NO.
2575# This tag requires that the tag HAVE_DOT is set to YES.
2576
2577INTERACTIVE_SVG = NO
2578
2579# The DOT_PATH tag can be used to specify the path where the dot tool can be
2580# found. If left blank, it is assumed the dot tool can be found in the path.
2581# This tag requires that the tag HAVE_DOT is set to YES.
2582
2583DOT_PATH =
2584
2585# The DOTFILE_DIRS tag can be used to specify one or more directories that
2586# contain dot files that are included in the documentation (see the \dotfile
2587# command).
2588# This tag requires that the tag HAVE_DOT is set to YES.
2589
2590DOTFILE_DIRS =
2591
2592# The MSCFILE_DIRS tag can be used to specify one or more directories that
2593# contain msc files that are included in the documentation (see the \mscfile
2594# command).
2595
2596MSCFILE_DIRS =
2597
2598# The DIAFILE_DIRS tag can be used to specify one or more directories that
2599# contain dia files that are included in the documentation (see the \diafile
2600# command).
2601
2602DIAFILE_DIRS =
2603
2604# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
2605# path where java can find the plantuml.jar file. If left blank, it is assumed
2606# PlantUML is not used or called during a preprocessing step. Doxygen will
2607# generate a warning when it encounters a \startuml command in this case and
2608# will not generate output for the diagram.
2609
2610PLANTUML_JAR_PATH =
2611
2612# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
2613# configuration file for plantuml.
2614
2615PLANTUML_CFG_FILE =
2616
2617# When using plantuml, the specified paths are searched for files specified by
2618# the !include statement in a plantuml block.
2619
2620PLANTUML_INCLUDE_PATH =
2621
2622# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
2623# that will be shown in the graph. If the number of nodes in a graph becomes
2624# larger than this value, doxygen will truncate the graph, which is visualized
2625# by representing a node as a red box. Note that doxygen if the number of direct
2626# children of the root node in a graph is already larger than
2627# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
2628# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
2629# Minimum value: 0, maximum value: 10000, default value: 50.
2630# This tag requires that the tag HAVE_DOT is set to YES.
2631
2632DOT_GRAPH_MAX_NODES = 50
2633
2634# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
2635# generated by dot. A depth value of 3 means that only nodes reachable from the
2636# root by following a path via at most 3 edges will be shown. Nodes that lay
2637# further from the root node will be omitted. Note that setting this option to 1
2638# or 2 may greatly reduce the computation time needed for large code bases. Also
2639# note that the size of a graph can be further restricted by
2640# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
2641# Minimum value: 0, maximum value: 1000, default value: 0.
2642# This tag requires that the tag HAVE_DOT is set to YES.
2643
2644MAX_DOT_GRAPH_DEPTH = 0
2645
2646# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
2647# background. This is disabled by default, because dot on Windows does not seem
2648# to support this out of the box.
2649#
2650# Warning: Depending on the platform used, enabling this option may lead to
2651# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
2652# read).
2653# The default value is: NO.
2654# This tag requires that the tag HAVE_DOT is set to YES.
2655
2656DOT_TRANSPARENT = NO
2657
2658# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
2659# files in one run (i.e. multiple -o and -T options on the command line). This
2660# makes dot run faster, but since only newer versions of dot (>1.8.10) support
2661# this, this feature is disabled by default.
2662# The default value is: NO.
2663# This tag requires that the tag HAVE_DOT is set to YES.
2664
2665DOT_MULTI_TARGETS = NO
2666
2667# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
2668# explaining the meaning of the various boxes and arrows in the dot generated
2669# graphs.
2670# The default value is: YES.
2671# This tag requires that the tag HAVE_DOT is set to YES.
2672
2673GENERATE_LEGEND = YES
2674
2675# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
2676# files that are used to generate the various graphs.
2677#
2678# Note: This setting is not only used for dot files but also for msc temporary
2679# files.
2680# The default value is: YES.
2681
2682DOT_CLEANUP = YES
diff --git a/portmidi/README.md b/portmidi/README.md
new file mode 100644
index 0000000..3f0463c
--- /dev/null
+++ b/portmidi/README.md
@@ -0,0 +1,128 @@
1# PortMidi - Cross-Platform MIDI IO
2
3This is the canonical release of PortMidi.
4
5See other repositories within [PortMidi](https://github.com/PortMidi)
6for related code and bindings (although currently, not much is here).
7
8## [Full C API documentation is here.](https://portmidi.github.io/portmidi_docs/)
9
10## Compiling and Using PortMidi
11
12Use CMake (or ccmake) to create a Makefile for Linux/BSD or a
13project file for Xcode or MS Visual Studio. Use make or an IDE to compile:
14```
15sudo apt install libasound2-dev # on Linux, you need ALSA
16cd portmidi # start in the top-level portmidi directory
17ccmake . # set any options interactively, type c to configure
18 # type g to generate a Makefile or IDE project
19 # type q to quit
20 # (alternatively, run the CMake GUI and use
21 # Configure and Generate buttons to build IDE project)
22make # compile sources and build PortMidi library
23 # (alternatively, open project file with your IDE)
24sudo make install # if you want to install to your system
25```
26
27## Installation
28
29My advice is to build PortMidi and statically link it to your
30application. This gives you more control over versions. However,
31installing PortMidi to your system is preferred by some, and the
32following should do it:
33```
34cmake .
35make
36sudo make install
37```
38
39## Language Bindings
40
41Here is a guide to some other projects using PortMidi. There is not
42much coordination, so let us know if there are better or alternative
43bindings for these and other languages:
44
45- Python: Various libraries and packages exist; search and ye shall
46 find. If you wouldn't like to do research, check out [mido](https://mido.readthedocs.io/en/stable/)
47- [SML](https://github.com/jh-midi/portmidi-sml2)
48- [OCaml](https://ocaml.org/p/portmidi/0.1)
49- [Haskell](https://hackage.haskell.org/package/PortMidi)
50- [Erlang](https://hexdocs.pm/portmidi/PortMidi.html)
51- [Julia](https://github.com/SteffenPL/PortMidi.jl)
52- [C#](https://github.com/net-core-audio/portmidi)
53- [Rust](https://musitdev.github.io/portmidi-rs/)
54- [Go](https://github.com/rakyll/portmidi)
55- [Odin](https://pkg.odin-lang.org/vendor/portmidi/)
56- [Serpent](https://sourceforge.net/projects/serpent/) - a real-time
57 Python-like language has PortMidi built-in, a MIDI-timestamp-aware
58 scheduler, and GUI support for device selection.
59- [Pd (Pure Data)](https://puredata.info/) uses PortMidi.
60
61
62## What's New?
63
64(Not so new, but significant:) Support for the **PmDefaults** program,
65which enabled a graphical interface to select default MIDI devices,
66has been removed for lack of interest. This allowed us to also remove
67C code to read and parse Java preference files on various systems,
68simplifying the library. **PmDefaults** allowed simple command-line
69programs to use `Pm_DefaultInputDeviceID()` and
70`Pm_DefaultOutputDeviceID()` rather than creating device selection
71interfaces. Now, you should either pass devices on the command line or
72create your own selection interface when building a GUI
73application. (See pm_tests for examples.) `Pm_DefaultInputDeviceID()`
74and `Pm_DefaultOutputDeviceID()` now return a valid device if
75possible, but they may not actually reflect any user preference.
76
77Haiku support in a minimal implementation. See TODO's in source.
78
79sndio is also minimally supported, allowing basic PortMidi functions
80in OpenBSD, FreeBSD, and NetBSD by setting USE_SNDIO for CMake, but
81not delayed/timestamped output and virtual devices.
82
83# Other Repositories
84
85PortMidi used to be part of the PortMedia suite, but this repo has
86been reduced to mostly just C/C++ code for PortMidi. You will find
87some other repositories in this PortMidi project set up for language
88bindings (volunteers and contributors are invited!). Other code
89removed from previous releases of PortMedia include:
90
91## PortSMF
92
93A Standard MIDI File (SMF) (and more) library is in the [portsmf
94repository](https://github.com/rbdannenberg/portsmf).
95
96PortSMF is a library for reading/writing/editing Standard MIDI
97Files. It is actually much more, with a general representation of
98events and updates with properties consisting of attributes and typed
99values. Familiar properties of pitch, time, duration, and channel are
100built into events and updates to make them faster to access and more
101compact.
102
103To my knowledge, PortSMF has the most complete and useful handling of
104MIDI tempo tracks. E.g., you can edit notes according to either beat
105or time, and you can edit tempo tracks, for example, flattening the
106tempo while preserving the beat alignment, preserving the real time
107while changing the tempo or stretching the tempo over some interval.
108
109In addition to Standard MIDI Files, PortSMF supports an ASCII
110representation called Allegro. PortSMF and Allegro are used for
111Audacity Note Tracks.
112
113## scorealign
114
115Scorealign used to be part of the PortMedia suite. It is now at the
116[scorealign repository](https://github.com/rbdannenberg/scorealign).
117
118Scorealign aligns audio-to-audio, audio-to-MIDI or MIDI-to-MIDI using
119dynamic time warping (DTW) of a computed chromagram
120representation. There are some added smoothing tricks to improve
121performance. This library is written in C and runs substantially
122faster than most other implementations, especially those written in
123MATLAB, due to the core DTW algorithm. Users should be warned that
124while chromagrams are robust features for alignment, they achieve
125robustness by operating at fairly high granularity, e.g., durations of
126around 100ms, which limits time precision. Other more recent
127algorithms can doubtless do better, but be cautious of claims, since
128it all depends on what assumptions you can make about the music.
diff --git a/portmidi/README.txt b/portmidi/README.txt
new file mode 100755
index 0000000..e09aa2e
--- /dev/null
+++ b/portmidi/README.txt
@@ -0,0 +1,88 @@
1README for PortMidi
2
3Roger B. Dannenberg
4
5Documentation for PortMidi is found in pm_common/portmidi.h.
6Documentation in HTML is available at portmidi.github.io/portmidi_docs/
7
8Additional documentation:
9 - README.md (overview, how to build, what's new)
10 - Windows: see pm_win/README_WIN.txt and pm_win/debugging_dlls.txt
11 - Linux: see pm_linux/README_LINUX.txt
12 - Mac OSX: see pm_mac/README_MAC.txt
13 - Other Languages: look for other repos at github.com/PortMidi,
14 and search README.md for pointers to other projects.
15
16---------- some notes on the design of PortMidi ----------
17
18POINTERS VS DEVICE NUMBERS
19
20When you open a MIDI port, PortMidi allocates a structure to
21maintain the state of the open device. Since every device is
22also listed in a table, you might think it would be simpler to
23use the table index rather than a pointer to identify a device.
24This would also help with error checking (it's hard to make
25sure a pointer is valid). PortMidi's design parallels that of
26PortAudio.
27
28ERROR HANDLING
29
30Error handling turned out to be much more complicated than expected.
31PortMidi functions return error codes that the caller can check.
32In addition, errors may occur asynchronously due to MIDI input.
33However, for Windows, there are virtually no errors that can
34occur if the code is correct and not passing bogus values. One
35exception is an error that the system is out of memory, but my
36guess is that one is unlikely to recover gracefully from that.
37Therefore, all errors in callbacks are guarded by assert(), which
38means not guarded at all in release configurations.
39
40Ordinarily, the caller checks for an error code. If the error is
41system-dependent, pmHostError is returned and the caller can
42call Pm_GetHostErrorText to get a text description of the error.
43
44Host error codes are system-specific and are recorded in the
45system-specific data allocated for each open MIDI port.
46However, if an error occurs on open or close,
47we cannot store the error with the device because there will be
48no device data (assuming PortMidi cleans up after devices that
49are not open). For open and close, we will convert the error
50to text, copy it to a global string, and set pm_hosterror, a
51global flag.
52
53Similarly, whenever a Read or Write operation returns pmHostError,
54the corresponding error string is copied to a global string
55and pm_hosterror is set. This makes getting error strings
56simple and uniform, although it does cost a string copy and some
57overhead even if the user does not want to look at the error data.
58
59The system-specific Read, Write, Poll, etc. implementations should
60check for asynchronous errors and return immediately if one is
61found so that these get reported. This happens in the Mac OS X
62code, where lots of things are happening in callbacks, but again,
63in Windows, there are no error codes recorded in callbacks.
64
65DEBUGGING
66
67If you are building a console application for research, we suggest
68compiling with the option PM_CHECK_ERRORS. This will insert a
69check for error return values at the end of each PortMidi
70function. If an error is encountered, a text message is printed
71using printf(), the user is asked to type ENTER, and then exit(-1)
72is called to clean up and terminate the program.
73
74You should not use PM_CHECK_ERRORS if printf() does not work
75(e.g. this is not a console application under Windows, or there
76is no visible console on some other OS), and you should not use
77PM_CHECK_ERRORS if you intend to recover from errors rather than
78abruptly terminate the program.
79
80The Windows version (and perhaps others) also offers a DEBUG
81compile-time option. See README_WIN.txt.
82
83RELEASE
84
85To make a new release, update VERSION variable in CMakeLists.txt.
86
87Update CHANGELOG.txt. What's new?
88
diff --git a/portmidi/license.txt b/portmidi/license.txt
new file mode 100644
index 0000000..c757b37
--- /dev/null
+++ b/portmidi/license.txt
@@ -0,0 +1,40 @@
1/*
2 * PortMidi Portable Real-Time MIDI Library
3 *
4 * license.txt -- a copy of the PortMidi copyright notice and license information
5 *
6 * Latest version available at: http://sourceforge.net/projects/portmedia
7 *
8 * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
9 * Copyright (c) 2001-2009 Roger B. Dannenberg
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining
12 * a copy of this software and associated documentation files
13 * (the "Software"), to deal in the Software without restriction,
14 * including without limitation the rights to use, copy, modify, merge,
15 * publish, distribute, sublicense, and/or sell copies of the Software,
16 * and to permit persons to whom the Software is furnished to do so,
17 * subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be
20 * included in all copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
26 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 */
30
31/*
32 * The text above constitutes the entire PortMidi license; however,
33 * the PortMusic community also makes the following non-binding requests:
34 *
35 * Any person wishing to distribute modifications to the Software is
36 * requested to send the modifications to the original developer so that
37 * they can be incorporated into the canonical version. It is also
38 * requested that these non-binding requests be included along with the
39 * license above.
40 */
diff --git a/portmidi/packaging/PortMidiConfig.cmake.in b/portmidi/packaging/PortMidiConfig.cmake.in
new file mode 100644
index 0000000..0d24f4d
--- /dev/null
+++ b/portmidi/packaging/PortMidiConfig.cmake.in
@@ -0,0 +1,15 @@
1@PACKAGE_INIT@
2
3include(CMakeFindDependencyMacro)
4if(UNIX AND NOT APPLE AND NOT HAIKU AND (@LINUX_DEFINES@ MATCHES ".*PMALSA.*"))
5 find_dependency(ALSA)
6endif()
7
8if(NOT WIN32)
9 set(THREADS_PREFER_PTHREAD_FLAG ON)
10 find_package(Threads REQUIRED)
11endif()
12
13include("${CMAKE_CURRENT_LIST_DIR}/PortMidiTargets.cmake")
14
15check_required_components(PortMidi)
diff --git a/portmidi/packaging/portmidi.pc.in b/portmidi/packaging/portmidi.pc.in
new file mode 100644
index 0000000..e9d929c
--- /dev/null
+++ b/portmidi/packaging/portmidi.pc.in
@@ -0,0 +1,11 @@
1prefix=@CMAKE_INSTALL_PREFIX@
2exec_prefix=${prefix}
3libdir=@PKGCONFIG_LIBDIR@
4includedir=@PKGCONFIG_INCLUDEDIR@
5
6Name: @CMAKE_PROJECT_NAME@
7Description: @CMAKE_PROJECT_DESCRIPTION@
8Version: @CMAKE_PROJECT_VERSION@
9Cflags: -I${includedir}
10Libs: -L${libdir} -l@CMAKE_PROJECT_NAME@
11Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@
diff --git a/portmidi/pm_common/CMakeLists.txt b/portmidi/pm_common/CMakeLists.txt
new file mode 100644
index 0000000..1ad54ad
--- /dev/null
+++ b/portmidi/pm_common/CMakeLists.txt
@@ -0,0 +1,167 @@
1# pm_common/CMakeLists.txt -- how to build portmidi library
2
3# creates the portmidi library
4# exports PM_NEEDED_LIBS to parent. It seems that PM_NEEDED_LIBS for
5# Linux should include Thread::Thread and ALSA::ALSA, but these
6# are not visible in other CMake files, even though the portmidi
7# target is. Therefore, Thread::Thread is replaced by
8# CMAKE_THREAD_LIBS_INIT and ALSA::ALSA is replaced by ALSA_LIBRARIES.
9# Is there a better way to do this? Maybe this whole file should be
10# at the parent level.
11
12# Support alternative name for static libraries to avoid confusion.
13# (In particular, Xcode has automatically converted portmidi.a to
14# portmidi.dylib without warning, so using portmidi-static.a eliminates
15# this possibility, but default for all libs is "portmidi"):
16set(PM_STATIC_LIB_NAME "portmidi" CACHE STRING
17 "For static builds, the PortMidi library name, e.g. portmidi-static.
18 Default is portmidi")
19set(PM_ACTUAL_LIB_NAME "portmidi")
20if(NOT BUILD_SHARED_LIBS)
21 set(PM_ACTUAL_LIB_NAME ${PM_STATIC_LIB_NAME})
22endif()
23
24# set the build directory for libportmidi.a to be in portmidi, not in
25# portmidi/pm_common. Must be done here BEFORE add_library below.
26if(APPLE OR WIN32)
27 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
28 # set the build directory for .dylib libraries
29 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
30 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
31endif(APPLE OR WIN32)
32
33# we need full paths to sources because they are shared with other targets
34# (in particular pmjni). Set PMDIR to the top-level portmidi directory:
35get_filename_component(PMDIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
36set(PM_LIB_PUBLIC_SRC ${PMDIR}/pm_common/portmidi.c
37 ${PMDIR}/pm_common/pmutil.c
38 ${PMDIR}/porttime/porttime.c)
39add_library(portmidi ${PM_LIB_PUBLIC_SRC})
40
41# MSVCRT_DLL is "DLL" for shared runtime library, and "" for static:
42set_target_properties(portmidi PROPERTIES
43 VERSION ${LIBRARY_VERSION}
44 SOVERSION ${LIBRARY_SOVERSION}
45 OUTPUT_NAME "${PM_ACTUAL_LIB_NAME}"
46 MSVC_RUNTIME_LIBRARY
47 "MultiThreaded$<$<CONFIG:Debug>:Debug>${MSVCRT_DLL}"
48 WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
49target_include_directories(portmidi PUBLIC
50 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
51 $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
52
53
54option(PM_CHECK_ERRORS
55"Insert a check for error return values at the end of each PortMidi function.
56If an error is encountered, a text message is printed using printf(), the user
57is asked to type ENTER, and then exit(-1) is called to clean up and terminate
58the program.
59
60You should not use PM_CHECK_ERRORS if printf() does not work (e.g. this is not
61a console application under Windows, or there is no visible console on some
62other OS), and you should not use PM_CHECK_ERRORS if you intend to recover
63from errors rather than abruptly terminate the program." OFF)
64if(PM_CHECK_ERRORS)
65 target_compile_definitions(portmidi PRIVATE PM_CHECK_ERRORS)
66endif(PM_CHECK_ERRORS)
67
68macro(prepend_path RESULT PATH)
69 set(${RESULT})
70 foreach(FILE ${ARGN})
71 list(APPEND ${RESULT} "${PATH}${FILE}")
72 endforeach(FILE)
73endmacro(prepend_path)
74
75# UNIX needs pthread library
76if(NOT WIN32)
77 set(THREADS_PREFER_PTHREAD_FLAG ON)
78 find_package(Threads REQUIRED)
79endif()
80
81# Check for sndio
82if(USE_SNDIO)
83 include (FindPackageHandleStandardArgs)
84 find_path(SNDIO_INCLUDE_DIRS NAMES sndio.h)
85 find_library(SNDIO_LIBRARY sndio)
86 find_package_handle_standard_args(Sndio
87 REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIRS)
88endif(USE_SNDIO)
89
90# first include the appropriate system-dependent file:
91if(SNDIO_FOUND AND USE_SNDIO)
92 set(PM_LIB_PRIVATE_SRC
93 ${PMDIR}/porttime/ptlinux.c
94 ${PMDIR}/pm_sndio/pmsndio.c)
95 set(PM_NEEDED_LIBS Threads::Threads ${SNDIO_LIBRARY} PARENT_SCOPE)
96 target_link_libraries(portmidi PRIVATE Threads::Threads ${SNDIO_LIBRARY})
97 target_include_directories(portmidi PRIVATE ${SNDIO_INCLUDE_DIRS})
98elseif(UNIX AND APPLE)
99 set(Threads::Threads "" PARENT_SCOPE)
100 set(PM_LIB_PRIVATE_SRC
101 ${PMDIR}/porttime/ptmacosx_mach.c
102 ${PMDIR}/pm_mac/pmmac.c
103 ${PMDIR}/pm_mac/pmmacosxcm.c)
104 set(PM_NEEDED_LIBS
105 ${CMAKE_THREAD_LIBS_INIT}
106 -Wl,-framework,CoreAudio
107 -Wl,-framework,CoreFoundation
108 -Wl,-framework,CoreMidi
109 -Wl,-framework,CoreServices
110 PARENT_SCOPE)
111 target_link_libraries(portmidi PRIVATE
112 Threads::Threads
113 -Wl,-framework,CoreAudio
114 -Wl,-framework,CoreFoundation
115 -Wl,-framework,CoreMidi
116 -Wl,-framework,CoreServices
117 )
118 # set to CMake default; is this right?:
119 set_target_properties(portmidi PROPERTIES MACOSX_RPATH ON)
120elseif(HAIKU)
121 set(PM_LIB_PRIVATE_SRC
122 ${PMDIR}/porttime/pthaiku.cpp
123 ${PMDIR}/pm_haiku/pmhaiku.cpp)
124 set(PM_NEEDED_LIBS be midi midi2 PARENT_SCOPE)
125 target_link_libraries(portmidi PRIVATE be midi midi2)
126elseif(UNIX)
127 target_compile_definitions(portmidi PRIVATE ${LINUX_FLAGS})
128 set(PM_LIB_PRIVATE_SRC
129 ${PMDIR}/porttime/ptlinux.c
130 ${PMDIR}/pm_linux/pmlinux.c
131 ${PMDIR}/pm_linux/pmlinuxnull.c)
132 if(${LINUX_DEFINES} MATCHES ".*PMALSA.*")
133 # Note that ALSA is not required if PMNULL is defined -- PortMidi will then
134 # compile without ALSA and report no MIDI devices. Later, PMSNDIO or PMJACK
135 # might be additional options.
136 find_package(ALSA REQUIRED)
137 list(APPEND PM_LIB_PRIVATE_SRC ${PMDIR}/pm_linux/pmlinuxalsa.c)
138 set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} ${ALSA_LIBRARIES} PARENT_SCOPE)
139 target_link_libraries(portmidi PRIVATE Threads::Threads ALSA::ALSA)
140 set(PKGCONFIG_REQUIRES_PRIVATE "alsa" PARENT_SCOPE)
141 else()
142 message(WARNING "No PMALSA, so PortMidi will not use ALSA, "
143 "and will not find or open MIDI devices.")
144 set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} PARENT_SCOPE)
145 target_link_libraries(portmidi PRIVATE Threads::Threads)
146 endif()
147elseif(WIN32)
148 set(PM_LIB_PRIVATE_SRC
149 ${PMDIR}/porttime/ptwinmm.c
150 ${PMDIR}/pm_win/pmwin.c
151 ${PMDIR}/pm_win/pmwinmm.c)
152 set(PM_NEEDED_LIBS winmm PARENT_SCOPE)
153 target_link_libraries(portmidi PRIVATE winmm)
154# if(NOT BUILD_SHARED_LIBS AND PM_USE_STATIC_RUNTIME)
155 # /MDd is multithread debug DLL, /MTd is multithread debug
156 # /MD is multithread DLL, /MT is multithread. Change to static:
157# include(../pm_win/static.cmake)
158# endif()
159else()
160 message(FATAL_ERROR "Operating system not supported.")
161endif()
162
163set(PM_LIB_PUBLIC_SRC ${PM_LIB_PUBLIC_SRC} PARENT_SCOPE) # export to parent
164set(PM_LIB_PRIVATE_SRC ${PM_LIB_PRIVATE_SRC} PARENT_SCOPE) # export to parent
165
166target_sources(portmidi PRIVATE ${PM_LIB_PRIVATE_SRC})
167
diff --git a/portmidi/pm_common/pminternal.h b/portmidi/pm_common/pminternal.h
new file mode 100755
index 0000000..8b3d8f5
--- /dev/null
+++ b/portmidi/pm_common/pminternal.h
@@ -0,0 +1,190 @@
1/** @file pminternal.h header for PortMidi implementations */
2
3/* this file is included by files that implement library internals */
4/* Here is a guide to implementers:
5 provide an initialization function similar to pm_winmm_init()
6 add your initialization function to pm_init()
7 Note that your init function should never require not-standard
8 libraries or fail in any way. If the interface is not available,
9 simply do not call pm_add_device. This means that non-standard
10 libraries should try to do dynamic linking at runtime using a DLL
11 and return without error if the DLL cannot be found or if there
12 is any other failure.
13 implement functions as indicated in pm_fns_type to open, read, write,
14 close, etc.
15 call pm_add_device() for each input and output device, passing it a
16 pm_fns_type structure.
17 assumptions about pm_fns_type functions are given below.
18 */
19
20/** @cond INTERNAL - add INTERNAL to Doxygen ENABLED_SECTIONS to include */
21
22#ifdef __cplusplus
23extern "C" {
24#endif
25
26extern int pm_initialized; /* see note in portmidi.c */
27extern PmDeviceID pm_default_input_device_id;
28extern PmDeviceID pm_default_output_device_id;
29
30/* these are defined in system-specific file */
31void *pm_alloc(size_t s);
32void pm_free(void *ptr);
33
34/* if a host error (an error reported by the host MIDI API that is not
35 * mapped to a PortMidi error code) occurs in a synchronous operation
36 * (i.e., not in a callback from another thread) set these: */
37extern int pm_hosterror; /* boolean */
38extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
39
40struct pm_internal_struct;
41
42/* these do not use PmInternal because it is not defined yet... */
43typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi,
44 PmEvent *buffer);
45typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
46 PmTimestamp timestamp);
47typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
48 PmTimestamp timestamp);
49typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
50 unsigned char byte, PmTimestamp timestamp);
51typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
52 PmEvent *buffer);
53typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
54 PmTimestamp timestamp);
55typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
56/* pm_open_fn should clean up all memory and close the device if any part
57 of the open fails */
58typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
59 void *driverInfo);
60typedef PmError (*pm_create_fn)(int is_input, const char *name,
61 void *driverInfo);
62typedef PmError (*pm_delete_fn)(PmDeviceID id);
63typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
64/* pm_close_fn should clean up all memory and close the device if any
65 part of the close fails. */
66typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
67typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
68typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi);
69
70typedef struct {
71 pm_write_short_fn write_short; /* output short MIDI msg */
72 pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
73 pm_end_sysex_fn end_sysex; /* marks end of sysex message */
74 pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
75 pm_write_realtime_fn write_realtime; /* send real-time msg within sysex */
76 pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
77 pm_synchronize_fn synchronize; /* synchronize PM time to stream time */
78 pm_open_fn open; /* open MIDI device */
79 pm_abort_fn abort; /* abort */
80 pm_close_fn close; /* close device */
81 pm_poll_fn poll; /* read pending midi events into portmidi buffer */
82 pm_check_host_error_fn check_host_error; /* true when device has had host */
83 /* error; sets pm_hosterror and writes message to pm_hosterror_text */
84} pm_fns_node, *pm_fns_type;
85
86
87/* when open fails, the dictionary gets this set of functions: */
88extern pm_fns_node pm_none_dictionary;
89
90typedef struct {
91 PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic
92 device closing -- see PmDeviceInfo struct) */
93 int deleted; /* is this is a deleted virtual device? */
94 void *descriptor; /* ID number passed to win32 multimedia API open,
95 * coreMIDI endpoint, etc., representing the device */
96 struct pm_internal_struct *pm_internal; /* points to PmInternal device */
97 /* when the device is open, allows automatic device closing */
98 pm_fns_type dictionary;
99} descriptor_node, *descriptor_type;
100
101extern int pm_descriptor_max;
102extern descriptor_type pm_descriptors;
103extern int pm_descriptor_len;
104
105typedef uint32_t (*time_get_proc_type)(void *time_info);
106
107typedef struct pm_internal_struct {
108 int device_id; /* which device is open (index to pm_descriptors) */
109 short is_input; /* MIDI IN (true) or MIDI OUT (false) */
110 short is_removed; /* MIDI device was removed */
111 PmTimeProcPtr time_proc; /* where to get the time */
112 void *time_info; /* pass this to get_time() */
113 int32_t buffer_len; /* how big is the buffer or queue? */
114 PmQueue *queue;
115
116 int32_t latency; /* time delay in ms between timestamps and actual output */
117 /* set to zero to get immediate, simple blocking output */
118 /* if latency is zero, timestamps will be ignored; */
119 /* if midi input device, this field ignored */
120
121 int sysex_in_progress; /* when sysex status is seen, this flag becomes
122 * true until EOX is seen. When true, new data is appended to the
123 * stream of outgoing bytes. When overflow occurs, sysex data is
124 * dropped (until an EOX or non-real-timei status byte is seen) so
125 * that, if the overflow condition is cleared, we don't start
126 * sending data from the middle of a sysex message. If a sysex
127 * message is filtered, sysex_in_progress is false, causing the
128 * message to be dropped. */
129 PmMessage message; /* buffer for 4 bytes of sysex data */
130 int message_count; /* how many bytes in sysex_message so far */
131 int short_message_count; /* how many bytes are expected in short message */
132 unsigned char running_status; /* running status byte or zero if none */
133 int32_t filters; /* flags that filter incoming message classes */
134 int32_t channel_mask; /* filter incoming messages based on channel */
135 PmTimestamp last_msg_time; /* timestamp of last message */
136 PmTimestamp sync_time; /* time of last synchronization */
137 PmTimestamp now; /* set by PmWrite to current time */
138 int first_message; /* initially true, used to run first synchronization */
139 pm_fns_type dictionary; /* implementation functions */
140 void *api_info; /* system-dependent state */
141 /* the following are used to expedite sysex data */
142 /* on windows, in debug mode, based on some profiling, these optimizations
143 * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,
144 * but this does not count time in the driver, so I don't know if it is
145 * important
146 */
147 unsigned char *fill_base; /* addr of ptr to sysex data */
148 uint32_t *fill_offset_ptr; /* offset of next sysex byte */
149 uint32_t fill_length; /* how many sysex bytes to write */
150} PmInternal;
151
152/* what is the length of this short message? */
153int pm_midi_length(PmMessage msg);
154
155/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
156void pm_init(void);
157void pm_term(void);
158
159/* defined by portMidi, used by pmwinmm */
160PmError none_write_short(PmInternal *midi, PmEvent *buffer);
161PmError none_write_byte(PmInternal *midi, unsigned char byte,
162 PmTimestamp timestamp);
163PmTimestamp none_synchronize(PmInternal *midi);
164
165PmError pm_fail_fn(PmInternal *midi);
166PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
167PmError pm_success_fn(PmInternal *midi);
168PmError pm_add_interf(char *interf, pm_create_fn create_fn,
169 pm_delete_fn delete_fn);
170PmError pm_add_device(char *interf, const char *name, int is_input,
171 int is_virtual, void *descriptor, pm_fns_type dictionary);
172void pm_undo_add_device(int id);
173uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,
174 PmTimestamp timestamp);
175void pm_read_short(PmInternal *midi, PmEvent *event);
176
177#define none_write_flush pm_fail_timestamp_fn
178#define none_sysex pm_fail_timestamp_fn
179#define none_poll pm_fail_fn
180#define success_poll pm_success_fn
181
182#define MIDI_REALTIME_MASK 0xf8
183#define is_real_time(msg) \
184 ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
185
186#ifdef __cplusplus
187}
188#endif
189
190/** @endcond */
diff --git a/portmidi/pm_common/pmutil.c b/portmidi/pm_common/pmutil.c
new file mode 100755
index 0000000..a70fe2f
--- /dev/null
+++ b/portmidi/pm_common/pmutil.c
@@ -0,0 +1,284 @@
1/* pmutil.c -- some helpful utilities for building midi
2 applications that use PortMidi
3 */
4#include <stdlib.h>
5#include <assert.h>
6#include <string.h>
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
20typedef 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
33PMEXPORT 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
70PMEXPORT 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
85PMEXPORT 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
164PMEXPORT 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
179PMEXPORT 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
221PMEXPORT 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
229PMEXPORT 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
248PMEXPORT 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
diff --git a/portmidi/pm_common/pmutil.h b/portmidi/pm_common/pmutil.h
new file mode 100755
index 0000000..46c618e
--- /dev/null
+++ b/portmidi/pm_common/pmutil.h
@@ -0,0 +1,184 @@
1/** @file pmutil.h lock-free queue for building MIDI
2 applications with PortMidi.
3
4 PortMidi is not reentrant, and locks can suffer from priority
5 inversion. To support coordination between system callbacks, a
6 high-priority thread created with PortTime, and the main
7 application thread, PortMidi uses a lock-free, non-blocking
8 queue. The queue implementation is not particular to MIDI and is
9 available for other uses.
10 */
11
12#ifndef PORTMIDI_PMUTIL_H
13#define PORTMIDI_PMUTIL_H
14
15#ifdef __cplusplus
16extern "C" {
17#endif /* __cplusplus */
18
19/** @defgroup grp_pmutil Lock-free Queue
20 @{
21*/
22
23/** The queue representation is opaque. Declare a queue as PmQueue * */
24typedef void PmQueue;
25
26/** create a single-reader, single-writer queue.
27
28 @param num_msgs the number of messages the queue can hold
29
30 @param the fixed message size
31
32 @return the allocated and initialized queue, or NULL if memory
33 cannot be allocated. Allocation uses #pm_malloc().
34
35 The queue only accepts fixed sized messages.
36
37 This queue implementation uses the "light pipe" algorithm which
38 operates correctly even with multi-processors and out-of-order
39 memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
40 Communication," Dr. Dobbs Portal, http://www.ddj.com/,
41 articleID=189401457, June 15, 2006. This algorithm requires that
42 messages be translated to a form where no words contain
43 zeros. Each word becomes its own "data valid" tag. Because of this
44 translation, we cannot return a pointer to data still in the queue
45 when the "peek" method is called. Instead, a buffer is
46 preallocated so that data can be copied there. Pm_QueuePeek()
47 dequeues a message into this buffer and returns a pointer to it. A
48 subsequent Pm_Dequeue() will copy from this buffer.
49
50 This implementation does not try to keep reader/writer data in
51 separate cache lines or prevent thrashing on cache lines.
52 However, this algorithm differs by doing inserts/removals in
53 units of messages rather than units of machine words. Some
54 performance improvement might be obtained by not clearing data
55 immediately after a read, but instead by waiting for the end
56 of the cache line, especially if messages are smaller than
57 cache lines. See the Dokumentov article for explanation.
58
59 The algorithm is extended to handle "overflow" reporting. To
60 report an overflow, the sender writes the current tail position to
61 a field. The receiver must acknowlege receipt by zeroing the
62 field. The sender will not send more until the field is zeroed.
63 */
64PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg);
65
66/** destroy a queue and free its storage.
67
68 @param queue a queue created by #Pm_QueueCreate().
69
70 @return pmNoError or an error code.
71
72 Uses #pm_free().
73
74 */
75PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue);
76
77/** remove one message from the queue, copying it into \p msg.
78
79 @param queue a queue created by #Pm_QueueCreate().
80
81 @param msg address to which the message, if any, is copied.
82
83 @return 1 if successful, and 0 if the queue is empty. Returns
84 #pmBufferOverflow if what would have been the next thing in the
85 queue was dropped due to overflow. (So when overflow occurs, the
86 receiver can receive a queue full of messages before getting the
87 overflow report. This protocol ensures that the reader will be
88 notified when data is lost due to overflow.
89 */
90PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg);
91
92/** insert one message into the queue, copying it from \p msg.
93
94 @param queue a queue created by #Pm_QueueCreate().
95
96 @param msg address of the message to be enqueued.
97
98 @return #pmNoError if successful and #pmBufferOverflow if the
99 queue was already full. If #pmBufferOverflow is returned, the
100 overflow flag is set.
101 */
102PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg);
103
104/** test if the queue is full.
105
106 @param queue a queue created by #Pm_QueueCreate().
107
108 @return non-zero iff the queue is empty, and @pmBadPtr if \p queue
109 is NULL.
110
111 The full condition may change immediately because a parallel
112 dequeue operation could be in progress. The result is
113 pessimistic: if it returns false (zero) to the single writer, then
114 #Pm_Enqueue() is guaranteed to succeed.
115 */
116PMEXPORT int Pm_QueueFull(PmQueue *queue);
117
118/** test if the queue is empty.
119
120 @param queue a queue created by #Pm_QueueCreate().
121
122 @return zero iff the queue is either empty or NULL.
123
124 The empty condition may change immediately because a parallel
125 enqueue operation could be in progress. Furthermore, the
126 result is optimistic: it may say false, when due to
127 out-of-order writes, the full message has not arrived. Therefore,
128 #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns
129 false.
130*/
131PMEXPORT int Pm_QueueEmpty(PmQueue *queue);
132
133/** get a pointer to the item at the head of the queue.
134
135 @param queue a queue created by #Pm_QueueCreate().
136
137 @result a pointer to the head message or NULL if the queue is empty.
138
139 The message is not removed from the queue. #Pm_QueuePeek() will
140 not indicate when an overflow occurs. If you want to get and check
141 #pmBufferOverflow messages, use the return value of
142 #Pm_QueuePeek() *only* as an indication that you should call
143 #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue()
144 would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL,
145 but internally clear the #pmBufferOverflow flag, enabling
146 #Pm_Enqueue() to resume enqueuing messages. A subsequent call to
147 #Pm_QueuePeek() will return a pointer to the first message *after*
148 the overflow. Using this as an indication to call #Pm_Dequeue(),
149 the first call to #Pm_Dequeue() will return #pmBufferOverflow. The
150 second call will return success, copying the same message pointed
151 to by the previous #Pm_QueuePeek().
152
153 When to use #Pm_QueuePeek(): (1) when you need to look at the message
154 data to decide who should be called to receive it. (2) when you need
155 to know a message is ready but cannot accept the message.
156
157 Note that #Pm_QueuePeek() is not a fast check, so if possible, you
158 might as well just call #Pm_Dequeue() and accept the data if it is there.
159 */
160PMEXPORT void *Pm_QueuePeek(PmQueue *queue);
161
162/** allows the writer (enqueuer) to signal an overflow
163 condition to the reader (dequeuer).
164
165 @param queue a queue created by #Pm_QueueCreate().
166
167 @return #pmNoError if overflow is set, or #pmBadPtr if queue is
168 NULL, or #pmBufferOverflow if buffer is already in an overflow
169 state.
170
171 E.g., when transfering data from the OS to an application, if the
172 OS indicates a buffer overrun, #Pm_SetOverflow() can be used to
173 insure that the reader receives a #pmBufferOverflow result from
174 #Pm_Dequeue().
175 */
176PMEXPORT PmError Pm_SetOverflow(PmQueue *queue);
177
178/** @} */
179
180#ifdef __cplusplus
181}
182#endif /* __cplusplus */
183
184#endif // PORTMIDI_PMUTIL_H
diff --git a/portmidi/pm_common/portmidi.c b/portmidi/pm_common/portmidi.c
new file mode 100755
index 0000000..e78ee73
--- /dev/null
+++ b/portmidi/pm_common/portmidi.c
@@ -0,0 +1,1472 @@
1/* portmidi.c -- cross-platform MIDI I/O library */
2/* see license.txt for license */
3
4#include "stdlib.h"
5#include "string.h"
6#include "portmidi.h"
7#include "porttime.h"
8#include "pmutil.h"
9#include "pminternal.h"
10#include <assert.h>
11
12#define MIDI_CLOCK 0xf8
13#define MIDI_ACTIVE 0xfe
14#define MIDI_STATUS_MASK 0x80
15#define MIDI_SYSEX 0xf0
16#define MIDI_EOX 0xf7
17#define MIDI_START 0xFA
18#define MIDI_STOP 0xFC
19#define MIDI_CONTINUE 0xFB
20#define MIDI_F9 0xF9
21#define MIDI_FD 0xFD
22#define MIDI_RESET 0xFF
23#define MIDI_NOTE_ON 0x90
24#define MIDI_NOTE_OFF 0x80
25#define MIDI_CHANNEL_AT 0xD0
26#define MIDI_POLY_AT 0xA0
27#define MIDI_PROGRAM 0xC0
28#define MIDI_CONTROL 0xB0
29#define MIDI_PITCHBEND 0xE0
30#define MIDI_MTC 0xF1
31#define MIDI_SONGPOS 0xF2
32#define MIDI_SONGSEL 0xF3
33#define MIDI_TUNE 0xF6
34
35#define is_empty(midi) ((midi)->tail == (midi)->head)
36
37/* these are not static so that (possibly) some system-dependent code
38 * could override the portmidi.c default which is to use the first
39 * device added using pm_add_device()
40 */
41PmDeviceID pm_default_input_device_id = -1;
42PmDeviceID pm_default_output_device_id = -1;
43
44/* this is not static so that pm_init can set it directly
45 * (see pmmac.c:pm_init())
46 */
47int pm_initialized = FALSE;
48
49int pm_hosterror; /* boolean */
50
51/* if PM_CHECK_ERRORS is enabled, but the caller wants to
52 * handle an error condition, declare this as extern and
53 * set to FALSE (this override is provided specifically
54 * for the test program virttest.c, where pmNameConflict
55 * is expected in a call to Pm_CreateVirtualInput()):
56 */
57int pm_check_errors = TRUE;
58
59char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
60
61#ifdef PM_CHECK_ERRORS
62
63#include <stdio.h>
64
65#define STRING_MAX 80
66
67static void prompt_and_exit(void)
68{
69 char line[STRING_MAX];
70 printf("type ENTER...");
71 char *rslt = fgets(line, STRING_MAX, stdin);
72 /* this will clean up open ports: */
73 exit(-1);
74}
75
76static PmError pm_errmsg(PmError err)
77{
78 if (!pm_check_errors) { /* see pm_check_errors declaration above */
79 ;
80 } else if (err == pmHostError) {
81 /* it seems pointless to allocate memory and copy the string,
82 * so I will do the work of Pm_GetHostErrorText directly
83 */
84 printf("PortMidi found host error...\n %s\n", pm_hosterror_text);
85 pm_hosterror = FALSE;
86 pm_hosterror_text[0] = 0; /* clear the message */
87 prompt_and_exit();
88 } else if (err < 0) {
89 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
90 prompt_and_exit();
91 }
92 return err;
93}
94#else
95#define pm_errmsg(err) err
96#endif
97
98
99int pm_midi_length(PmMessage msg)
100{
101 int status, high, low;
102 static int high_lengths[] = {
103 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
104 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
105 };
106 static int low_lengths[] = {
107 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
108 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
109 };
110
111 status = msg & 0xFF;
112 high = status >> 4;
113 low = status & 15;
114
115 return (high != 0xF) ? high_lengths[high] : low_lengths[low];
116}
117
118
119/*
120====================================================================
121system implementation of portmidi interface
122====================================================================
123*/
124
125int pm_descriptor_max = 0;
126int pm_descriptor_len = 0;
127descriptor_type pm_descriptors = NULL;
128
129/* interface pm_descriptors are simple: an array of string/fnptr pairs: */
130#define MAX_INTERF 4
131static struct {
132 const char *interf;
133 pm_create_fn create_fn;
134 pm_delete_fn delete_fn;
135} pm_interf_list[MAX_INTERF];
136
137static int pm_interf_list_len = 0;
138
139
140/* pm_add_interf -- describe an interface to library
141 *
142 * This is called at initialization time, once for each
143 * supported interface (e.g., CoreMIDI). The strings
144 * are retained but NOT COPIED, so do not destroy them!
145 *
146 * The purpose is to register functions that create/delete
147 * a virtual input or output device.
148 *
149 * returns pmInsufficientMemor if interface memory is
150 * exceeded, otherwise returns pmNoError.
151 */
152PmError pm_add_interf(char *interf, pm_create_fn create_fn,
153 pm_delete_fn delete_fn)
154{
155 if (pm_interf_list_len >= MAX_INTERF) {
156 return pmInsufficientMemory;
157 }
158 pm_interf_list[pm_interf_list_len].interf = interf;
159 pm_interf_list[pm_interf_list_len].create_fn = create_fn;
160 pm_interf_list[pm_interf_list_len].delete_fn = delete_fn;
161 pm_interf_list_len++;
162 return pmNoError;
163}
164
165
166PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf,
167 const char *name, void *device_info)
168{
169 int i;
170 if (pm_interf_list_len == 0) {
171 return pmNotImplemented;
172 }
173 if (!interf) {
174 /* default interface is the first one */
175 interf = pm_interf_list[0].interf;
176 }
177 for (i = 0; i < pm_interf_list_len; i++) {
178 if (strcmp(pm_interf_list[i].interf,
179 interf) == 0) {
180 int id = (*pm_interf_list[i].create_fn)(is_input, name,
181 device_info);
182 pm_descriptors[id].pub.is_virtual = TRUE;
183 return id;
184 }
185 }
186 return pmInterfaceNotSupported;
187}
188
189
190/* pm_add_device -- describe interface/device pair to library
191 *
192 * This is called at intialization time, once for each
193 * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1).
194 * This is also called when user creates a virtual device.
195 *
196 * Normally, increasing integer indices are returned. If the device
197 * is virtual, a linear search is performed to ensure that the name
198 * is unique. If the name is already taken, the call will fail and
199 * no device is added.
200 *
201 * interf is assumed to be static memory, so it is NOT COPIED and
202 * NOT FREED.
203 * name is owned by caller, COPIED if needed, and FREED by PortMidi.
204 * Caller is resposible for freeing name when pm_add_device returns.
205 *
206 * returns pmInvalidDeviceId if device memory is exceeded or a virtual
207 * device would take the name of an existing device.
208 * otherwise returns index (portmidi device_id) of the added device
209 */
210PmError pm_add_device(char *interf, const char *name, int is_input,
211 int is_virtual, void *descriptor, pm_fns_type dictionary) {
212 /* printf("pm_add_device: %s %s %d %p %p\n",
213 interf, name, is_input, descriptor, dictionary); */
214 int device_id;
215 PmDeviceInfo *d;
216 /* if virtual, search for duplicate name or unused ID; otherwise,
217 * just add a new device at the next integer available:
218 */
219 for (device_id = (is_virtual ? 0 : pm_descriptor_len);
220 device_id < pm_descriptor_len; device_id++) {
221 d = &pm_descriptors[device_id].pub;
222 d->structVersion = PM_DEVICEINFO_VERS;
223 if (strcmp(d->interf, interf) == 0 && strcmp(d->name, name) == 0) {
224 /* only reuse a name if it is a deleted virtual device with
225 * a matching direction (input or output) */
226 if (pm_descriptors[device_id].deleted && is_input == d->input) {
227 /* here, we know d->is_virtual because only virtual devices
228 * can be deleted, and we know is_virtual because we are
229 * in this loop.
230 */
231 pm_free((void *) d->name); /* reuse this device entry */
232 d->name = NULL;
233 break;
234 /* name conflict exists if the new device appears to others as
235 * the same direction (input or output) as the existing device.
236 * Note that virtual inputs appear to others as outputs and
237 * vice versa.
238 * The direction of the new virtual device to others is "output"
239 * if is_input, i.e., virtual inputs appear to others as outputs.
240 * The existing device appears to others as "output" if
241 * (d->is_virtual == d->input) by the same logic.
242 * The compare will detect if device directions are the same:
243 */
244 } else if (is_input == (d->is_virtual == d->input)) {
245 return pmNameConflict;
246 }
247 }
248 }
249 if (device_id >= pm_descriptor_max) {
250 // expand pm_descriptors
251 descriptor_type new_descriptors = (descriptor_type)
252 pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
253 if (!new_descriptors) return pmInsufficientMemory;
254 if (pm_descriptors) {
255 memcpy(new_descriptors, pm_descriptors,
256 sizeof(descriptor_node) * pm_descriptor_max);
257 pm_free(pm_descriptors);
258 }
259 pm_descriptor_max += 32;
260 pm_descriptors = new_descriptors;
261 }
262 if (device_id == pm_descriptor_len) {
263 pm_descriptor_len++; /* extending array of pm_descriptors */
264 }
265 d = &pm_descriptors[device_id].pub;
266 d->interf = interf;
267 d->name = pm_alloc(strlen(name) + 1);
268 if (!d->name) {
269 return pmInsufficientMemory;
270 }
271#if defined(WIN32) && !defined(_WIN32)
272#pragma warning(suppress: 4996) // don't use suggested strncpy_s
273#endif
274 strcpy(d->name, name);
275 d->input = is_input;
276 d->output = !is_input;
277 d->is_virtual = FALSE; /* caller should set to TRUE if this is virtual */
278
279 /* default state: nothing to close (for automatic device closing) */
280 d->opened = FALSE;
281
282 pm_descriptors[device_id].deleted = FALSE;
283
284 /* ID number passed to win32 multimedia API open */
285 pm_descriptors[device_id].descriptor = descriptor;
286
287 /* points to PmInternal, allows automatic device closing */
288 pm_descriptors[device_id].pm_internal = NULL;
289
290 pm_descriptors[device_id].dictionary = dictionary;
291
292 /* set the defaults to the first input and output we see */
293 if (is_input && pm_default_input_device_id == -1) {
294 pm_default_input_device_id = device_id;
295 } else if (!is_input && pm_default_output_device_id == -1) {
296 pm_default_output_device_id = device_id;
297 }
298
299 return device_id;
300}
301
302
303/* Undo a successful call to pm_add_device(). If a new device was
304 * allocated, it must be the last device in pm_descriptors, so it is
305 * easy to delete by decrementing the length of pm_descriptors, but
306 * first free the name (which was copied to the heap). Otherwise,
307 * the device must be a virtual device that was created previously
308 * and is in the interior of the array of pm_descriptors. Leave it,
309 * but mark it as deleted.
310 */
311void pm_undo_add_device(int id)
312{
313 /* Clear some fields (not all are strictly necessary) */
314 pm_descriptors[id].deleted = TRUE;
315 pm_descriptors[id].descriptor = NULL;
316 pm_descriptors[id].pm_internal = NULL;
317
318 if (id == pm_descriptor_len - 1) {
319 pm_free(pm_descriptors[id].pub.name);
320 pm_descriptor_len--;
321 }
322}
323
324
325/* utility to look up device, given a pattern,
326 note: pattern is modified
327 */
328int Pm_FindDevice(char *pattern, int is_input)
329{
330 int id = pmNoDevice;
331 int i;
332 /* first parse pattern into name, interf parts */
333 char *interf_pref = ""; /* initially assume it is not there */
334 char *name_pref = strstr(pattern, ", ");
335
336 if (name_pref) { /* found separator, adjust the pointer */
337 interf_pref = pattern;
338 name_pref[0] = 0;
339 name_pref += 2;
340 } else {
341 name_pref = pattern; /* whole string is the name pattern */
342 }
343 for (i = 0; i < pm_descriptor_len; i++) {
344 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
345 if (info->input == is_input &&
346 strstr(info->name, name_pref) &&
347 strstr(info->interf, interf_pref)) {
348 id = i;
349 break;
350 }
351 }
352 return id;
353}
354
355
356/*
357====================================================================
358portmidi implementation
359====================================================================
360*/
361
362PMEXPORT int Pm_CountDevices(void)
363{
364 Pm_Initialize();
365 /* no error checking -- Pm_Initialize() does not fail */
366 return pm_descriptor_len;
367}
368
369
370PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id)
371{
372 Pm_Initialize(); /* no error check needed */
373 if (id >= 0 && id < pm_descriptor_len && !pm_descriptors[id].deleted) {
374 return &pm_descriptors[id].pub;
375 }
376 return NULL;
377}
378
379/* pm_success_fn -- "noop" function pointer */
380PmError pm_success_fn(PmInternal *midi)
381{
382 return pmNoError;
383}
384
385/* none_write -- returns an error if called */
386PmError none_write_short(PmInternal *midi, PmEvent *buffer)
387{
388 return pmBadPtr;
389}
390
391/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */
392PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp)
393{
394 return pmBadPtr;
395}
396
397PmError none_write_byte(PmInternal *midi, unsigned char byte,
398 PmTimestamp timestamp)
399{
400 return pmBadPtr;
401}
402
403/* pm_fail_fn -- generic function, returns error if called */
404PmError pm_fail_fn(PmInternal *midi)
405{
406 return pmBadPtr;
407}
408
409static PmError none_open(PmInternal *midi, void *driverInfo)
410{
411 return pmBadPtr;
412}
413
414static unsigned int none_check_host_error(PmInternal * midi)
415{
416 return FALSE;
417}
418
419PmTimestamp none_synchronize(PmInternal *midi)
420{
421 return 0;
422}
423
424#define none_abort pm_fail_fn
425#define none_close pm_fail_fn
426
427pm_fns_node pm_none_dictionary = {
428 none_write_short,
429 none_sysex,
430 none_sysex,
431 none_write_byte,
432 none_write_short,
433 none_write_flush,
434 none_synchronize,
435 none_open,
436 none_abort,
437 none_close,
438 none_poll,
439 none_check_host_error,
440};
441
442
443PMEXPORT const char *Pm_GetErrorText(PmError errnum)
444{
445 const char *msg;
446
447 switch(errnum)
448 {
449 case pmNoError:
450 msg = "";
451 break;
452 case pmHostError:
453 msg = "PortMidi: Host error";
454 break;
455 case pmInvalidDeviceId:
456 msg = "PortMidi: Invalid device ID";
457 break;
458 case pmInsufficientMemory:
459 msg = "PortMidi: Insufficient memory";
460 break;
461 case pmBufferTooSmall:
462 msg = "PortMidi: Buffer too small";
463 break;
464 case pmBadPtr:
465 msg = "PortMidi: Bad pointer";
466 break;
467 case pmInternalError:
468 msg = "PortMidi: Internal PortMidi Error";
469 break;
470 case pmBufferOverflow:
471 msg = "PortMidi: Buffer overflow";
472 break;
473 case pmBadData:
474 msg = "PortMidi: Invalid MIDI message Data";
475 break;
476 case pmBufferMaxSize:
477 msg = "PortMidi: Buffer cannot be made larger";
478 break;
479 case pmNotImplemented:
480 msg = "PortMidi: Function is not implemented";
481 break;
482 case pmInterfaceNotSupported:
483 msg = "PortMidi: Interface not supported";
484 break;
485 case pmNameConflict:
486 msg = "PortMidi: Cannot create virtual device: name is taken";
487 break;
488 case pmDeviceRemoved:
489 msg = "PortMidi: Output attempted after (USB) device removed";
490 break;
491 default:
492 msg = "PortMidi: Illegal error number";
493 break;
494 }
495 return msg;
496}
497
498
499/* This can be called whenever you get a pmHostError return value
500 * or TRUE from Pm_HasHostError().
501 * The error will always be in the global pm_hosterror_text.
502 */
503PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len)
504{
505 assert(msg);
506 assert(len > 0);
507 if (pm_hosterror) {
508#if defined(WIN32) && !defined(_WIN32)
509#pragma warning(suppress: 4996) // don't use suggested strncpy_s
510#endif
511 strncpy(msg, (char *) pm_hosterror_text, len);
512 pm_hosterror = FALSE;
513 pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
514 might help with debugging */
515 msg[len - 1] = 0; /* make sure string is terminated */
516 } else {
517 msg[0] = 0; /* no string to return */
518 }
519}
520
521
522PMEXPORT int Pm_HasHostError(PortMidiStream * stream)
523{
524 if (pm_hosterror) return TRUE;
525 if (stream) {
526 PmInternal * midi = (PmInternal *) stream;
527 return (*midi->dictionary->check_host_error)(midi);
528 }
529 return FALSE;
530}
531
532
533PMEXPORT PmError Pm_Initialize(void)
534{
535 if (!pm_initialized) {
536 pm_descriptor_len = 0;
537 pm_interf_list_len = 0;
538 pm_hosterror = FALSE;
539 pm_hosterror_text[0] = 0; /* the null string */
540 pm_init();
541 pm_initialized = TRUE;
542 }
543 return pmNoError;
544}
545
546
547PMEXPORT PmError Pm_Terminate(void)
548{
549 if (pm_initialized) {
550 pm_term();
551 /* if there are no devices, pm_descriptors might still be NULL */
552 if (pm_descriptors != NULL) {
553 int i; /* free names copied into pm_descriptors */
554 for (i = 0; i < pm_descriptor_len; i++) {
555 if (pm_descriptors[i].pub.name) {
556 pm_free(pm_descriptors[i].pub.name);
557 }
558 }
559 pm_free(pm_descriptors);
560 pm_descriptors = NULL;
561 }
562 pm_descriptor_len = 0;
563 pm_descriptor_max = 0;
564 pm_interf_list_len = 0;
565 pm_initialized = FALSE;
566 }
567 return pmNoError;
568}
569
570
571/* Pm_Read -- read up to length messages from source into buffer */
572/*
573 * returns number of messages actually read, or error code
574 */
575PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length)
576{
577 PmInternal *midi = (PmInternal *) stream;
578 int n = 0;
579 PmError err = pmNoError;
580 pm_hosterror = FALSE;
581 /* arg checking */
582 if(midi == NULL)
583 err = pmBadPtr;
584 else if(!pm_descriptors[midi->device_id].pub.opened)
585 err = pmBadPtr;
586 else if(!pm_descriptors[midi->device_id].pub.input)
587 err = pmBadPtr;
588 /* First poll for data in the buffer...
589 * This either simply checks for data, or attempts first to fill the buffer
590 * with data from the MIDI hardware; this depends on the implementation.
591 * We could call Pm_Poll here, but that would redo a lot of redundant
592 * parameter checking, so I copied some code from Pm_Poll to here: */
593 else err = (*(midi->dictionary->poll))(midi);
594
595 if (err != pmNoError) {
596 if (err == pmHostError) {
597 midi->dictionary->check_host_error(midi);
598 }
599 return pm_errmsg(err);
600 }
601
602 while (n < length) {
603 err = Pm_Dequeue(midi->queue, buffer++);
604 if (err == pmBufferOverflow) {
605 /* ignore the data we have retreived so far */
606 return pm_errmsg(pmBufferOverflow);
607 } else if (err == 0) { /* empty queue */
608 break;
609 }
610 n++;
611 }
612 return n;
613}
614
615PMEXPORT PmError Pm_Poll(PortMidiStream *stream)
616{
617 PmInternal *midi = (PmInternal *) stream;
618 PmError err;
619
620 pm_hosterror = FALSE;
621 /* arg checking */
622 if(midi == NULL)
623 err = pmBadPtr;
624 else if (!pm_descriptors[midi->device_id].pub.opened)
625 err = pmBadPtr;
626 else if (!pm_descriptors[midi->device_id].pub.input)
627 err = pmBadPtr;
628 else
629 err = (*(midi->dictionary->poll))(midi);
630
631 if (err != pmNoError) {
632 return pm_errmsg(err);
633 }
634
635 return (PmError) !Pm_QueueEmpty(midi->queue);
636}
637
638
639/* this is called from Pm_Write and Pm_WriteSysEx to issue a
640 * call to the system-dependent end_sysex function and handle
641 * the error return
642 */
643static PmError pm_end_sysex(PmInternal *midi)
644{
645 PmError err = (*midi->dictionary->end_sysex)(midi, 0);
646 midi->sysex_in_progress = FALSE;
647 return err;
648}
649
650
651/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
652 Pm_WriteSysEx all operate a state machine that "outputs" calls to
653 write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
654
655PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,
656 int32_t length)
657{
658 PmInternal *midi = (PmInternal *) stream;
659 PmError err = pmNoError;
660 int i;
661 int bits;
662
663 pm_hosterror = FALSE;
664 /* arg checking */
665 if (midi == NULL) {
666 err = pmBadPtr;
667 } else {
668 descriptor_type desc = &pm_descriptors[midi->device_id];
669 if (!desc || !desc->pub.opened ||
670 !desc->pub.output || !desc->pm_internal) {
671 err = pmBadPtr;
672 } else if (desc->pm_internal->is_removed) {
673 err = pmDeviceRemoved;
674 }
675 }
676 if (err != pmNoError) goto pm_write_error;
677
678 if (midi->latency == 0) {
679 midi->now = 0;
680 } else {
681 midi->now = (*(midi->time_proc))(midi->time_info);
682 if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
683 /* time to resync */
684 midi->now = (*midi->dictionary->synchronize)(midi);
685 midi->first_message = FALSE;
686 }
687 }
688 /* error recovery: when a sysex is detected, we call
689 * dictionary->begin_sysex() followed by calls to
690 * dictionary->write_byte() and dictionary->write_realtime()
691 * until an end-of-sysex is detected, when we call
692 * dictionary->end_sysex(). After an error occurs,
693 * Pm_Write() continues to call functions. For example,
694 * it will continue to call write_byte() even after
695 * an error sending a sysex message, and end_sysex() will be
696 * called when an EOX or non-real-time status is found.
697 * When errors are detected, Pm_Write() returns immediately,
698 * so it is possible that this will drop data and leave
699 * sysex messages in a partially transmitted state.
700 */
701 for (i = 0; i < length; i++) {
702 uint32_t msg = buffer[i].message;
703 bits = 0;
704 /* is this a sysex message? */
705 if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
706 if (midi->sysex_in_progress) {
707 /* error: previous sysex was not terminated by EOX */
708 midi->sysex_in_progress = FALSE;
709 err = pmBadData;
710 goto pm_write_error;
711 }
712 midi->sysex_in_progress = TRUE;
713 if ((err = (*midi->dictionary->begin_sysex)(midi,
714 buffer[i].timestamp)) != pmNoError)
715 goto pm_write_error;
716 if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
717 buffer[i].timestamp)) != pmNoError)
718 goto pm_write_error;
719 bits = 8;
720 /* fall through to continue sysex processing */
721 } else if ((msg & MIDI_STATUS_MASK) &&
722 (Pm_MessageStatus(msg) != MIDI_EOX)) {
723 /* a non-sysex message */
724 if (midi->sysex_in_progress) {
725 /* this should be a realtime message */
726 if (is_real_time(msg)) {
727 if ((err = (*midi->dictionary->write_realtime)(midi,
728 &(buffer[i]))) != pmNoError)
729 goto pm_write_error;
730 } else {
731 midi->sysex_in_progress = FALSE;
732 err = pmBadData;
733 /* ignore any error from this, because we already have one */
734 /* pass 0 as timestamp -- it's ignored */
735 (*midi->dictionary->end_sysex)(midi, 0);
736 goto pm_write_error;
737 }
738 } else { /* regular short midi message */
739 if ((err = (*midi->dictionary->write_short)(midi,
740 &(buffer[i]))) != pmNoError)
741 goto pm_write_error;
742 continue;
743 }
744 }
745 if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
746 /* see if we can accelerate data transfer */
747 if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
748 (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
749 (msg & 0x80808080) == 0) { /* all data */
750 /* copy 4 bytes from msg to fill_base + fill_offset */
751 unsigned char *ptr = midi->fill_base +
752 *(midi->fill_offset_ptr);
753 ptr[0] = msg; ptr[1] = msg >> 8;
754 ptr[2] = msg >> 16; ptr[3] = msg >> 24;
755 (*midi->fill_offset_ptr) += 4;
756 continue;
757 }
758 /* no acceleration, so do byte-by-byte copying */
759 while (bits < 32) {
760 unsigned char midi_byte = (unsigned char) (msg >> bits);
761 if ((err = (*midi->dictionary->write_byte)(midi, midi_byte,
762 buffer[i].timestamp)) != pmNoError)
763 goto pm_write_error;
764 if (midi_byte == MIDI_EOX) {
765 err = pm_end_sysex(midi);
766 if (err != pmNoError) goto error_exit;
767 break; /* from while loop */
768 }
769 bits += 8;
770 }
771 } else {
772 /* not in sysex mode, but message did not start with status */
773 err = pmBadData;
774 goto pm_write_error;
775 }
776 }
777 /* after all messages are processed, send the data */
778 if (!midi->sysex_in_progress)
779 err = (*midi->dictionary->write_flush)(midi, 0);
780pm_write_error:
781 if (err == pmHostError) {
782 midi->dictionary->check_host_error(midi);
783 }
784error_exit:
785 return pm_errmsg(err);
786}
787
788
789PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
790 PmMessage msg)
791{
792 PmEvent event;
793
794 event.timestamp = when;
795 event.message = msg;
796 return Pm_Write(stream, &event, 1);
797}
798
799
800PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when,
801 unsigned char *msg)
802{
803 /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
804 /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
805 #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)))
806 PmEvent buffer[BUFLEN];
807 int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */
808 PmInternal *midi = (PmInternal *) stream;
809 PmError err = pmNoError;
810 /* the next byte in the buffer is represented by an index, bufx, and
811 a shift in bits */
812 int shift = 0;
813 int bufx = 0;
814 buffer[0].message = 0;
815 buffer[0].timestamp = when;
816
817 while (1) {
818 /* insert next byte into buffer */
819 buffer[bufx].message |= ((*msg) << shift);
820 shift += 8;
821 if (*msg++ == MIDI_EOX) break;
822 if (shift == 32) {
823 shift = 0;
824 bufx++;
825 if (bufx == buffer_size) {
826 err = Pm_Write(stream, buffer, buffer_size);
827 /* note: Pm_Write has already called errmsg() */
828 if (err) return err;
829 /* prepare to fill another buffer */
830 bufx = 0;
831 buffer_size = BUFLEN;
832 /* optimization: maybe we can just copy bytes */
833 if (midi->fill_base) {
834 while (*(midi->fill_offset_ptr) < midi->fill_length) {
835 midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;
836 if (*msg++ == MIDI_EOX) {
837 err = pm_end_sysex(midi);
838 if (err != pmNoError) return pm_errmsg(err);
839 goto end_of_sysex;
840 }
841 }
842 /* I thought that I could do a pm_Write here and
843 * change this if to a loop, avoiding calls in Pm_Write
844 * to the slower write_byte, but since
845 * sysex_in_progress is true, this will not flush
846 * the buffer, and we'll infinite loop: */
847 /* err = Pm_Write(stream, buffer, 0);
848 if (err) return err; */
849 /* instead, the way this works is that Pm_Write calls
850 * write_byte on 4 bytes. The first, since the buffer
851 * is full, will flush the buffer and allocate a new
852 * one. This primes the buffer so
853 * that we can return to the loop above and fill it
854 * efficiently without a lot of function calls.
855 */
856 buffer_size = 1; /* get another message started */
857 }
858 }
859 buffer[bufx].message = 0;
860 buffer[bufx].timestamp = when;
861 }
862 /* keep inserting bytes until you find MIDI_EOX */
863 }
864end_of_sysex:
865 /* we're finished sending full buffers, but there may
866 * be a partial one left.
867 */
868 if (shift != 0) bufx++; /* add partial message to buffer len */
869 if (bufx) { /* bufx is number of PmEvents to send from buffer */
870 err = Pm_Write(stream, buffer, bufx);
871 if (err) return err;
872 }
873 return pmNoError;
874}
875
876
877
878PmError pm_create_internal(PmInternal **stream, PmDeviceID device_id,
879 int is_input, int latency, PmTimeProcPtr time_proc,
880 void *time_info, int buffer_size)
881{
882 PmInternal *midi;
883 if (device_id < 0 || device_id >= pm_descriptor_len) {
884 return pmInvalidDeviceId;
885 }
886 if (latency < 0) { /* force a legal value */
887 latency = 0;
888 }
889 /* create portMidi internal data */
890 midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
891 *stream = midi;
892 if (!midi) {
893 return pmInsufficientMemory;
894 }
895 midi->device_id = device_id;
896 midi->is_input = is_input;
897 midi->is_removed = FALSE;
898 midi->time_proc = time_proc;
899 /* if latency != 0, we need a time reference for output.
900 we always need a time reference for input.
901 If none is provided, use PortTime library */
902 if (time_proc == NULL && (latency != 0 || is_input)) {
903 if (!Pt_Started())
904 Pt_Start(1, 0, 0);
905 /* time_get does not take a parameter, so coerce */
906 midi->time_proc = (PmTimeProcPtr) Pt_Time;
907 }
908 midi->time_info = time_info;
909 if (is_input) {
910 midi->latency = 0; /* unused by input */
911 if (buffer_size <= 0) buffer_size = 256; /* default buffer size */
912 midi->queue = Pm_QueueCreate(buffer_size, (int32_t) sizeof(PmEvent));
913 if (!midi->queue) {
914 /* free portMidi data */
915 *stream = NULL;
916 pm_free(midi);
917 return pmInsufficientMemory;
918 }
919 } else {
920 /* if latency zero, output immediate (timestamps ignored) */
921 /* if latency < 0, use 0 but don't return an error */
922 if (latency < 0) latency = 0;
923 midi->latency = latency;
924 midi->queue = NULL; /* unused by output; input needs to allocate: */
925 }
926 midi->buffer_len = buffer_size; /* portMidi input storage */
927 midi->sysex_in_progress = FALSE;
928 midi->message = 0;
929 midi->message_count = 0;
930 midi->filters = (is_input ? PM_FILT_ACTIVE : 0);
931 midi->channel_mask = 0xFFFF;
932 midi->sync_time = 0;
933 midi->first_message = TRUE;
934 midi->api_info = NULL;
935 midi->fill_base = NULL;
936 midi->fill_offset_ptr = NULL;
937 midi->fill_length = 0;
938 midi->dictionary = pm_descriptors[device_id].dictionary;
939 pm_descriptors[device_id].pm_internal = midi;
940 return pmNoError;
941}
942
943
944PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,
945 PmDeviceID inputDevice,
946 void *inputDriverInfo,
947 int32_t bufferSize,
948 PmTimeProcPtr time_proc,
949 void *time_info)
950{
951 PmInternal *midi;
952 PmError err = pmNoError;
953 pm_hosterror = FALSE;
954 *stream = NULL; /* invariant: *stream == midi */
955
956 /* arg checking */
957 if (!pm_descriptors[inputDevice].pub.input)
958 err = pmInvalidDeviceId;
959 else if (pm_descriptors[inputDevice].pub.opened)
960 err = pmInvalidDeviceId;
961 if (err != pmNoError)
962 goto error_return;
963
964 /* common initialization of PmInternal structure (midi): */
965 err = pm_create_internal(&midi, inputDevice, TRUE, 0, time_proc,
966 time_info, bufferSize);
967 *stream = midi;
968 if (err) {
969 goto error_return;
970 }
971
972 /* open system dependent input device */
973 err = (*midi->dictionary->open)(midi, inputDriverInfo);
974 if (err) {
975 *stream = NULL;
976 pm_descriptors[inputDevice].pm_internal = NULL;
977 /* free portMidi data */
978 Pm_QueueDestroy(midi->queue);
979 pm_free(midi);
980 } else {
981 /* portMidi input open successful */
982 pm_descriptors[inputDevice].pub.opened = TRUE;
983 }
984error_return:
985 /* note: if there is a pmHostError, it is the responsibility
986 * of the system-dependent code (*midi->dictionary->open)()
987 * to set pm_hosterror and pm_hosterror_text
988 */
989 return pm_errmsg(err);
990}
991
992
993PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
994 PmDeviceID outputDevice,
995 void *outputDriverInfo,
996 int32_t bufferSize,
997 PmTimeProcPtr time_proc,
998 void *time_info,
999 int32_t latency)
1000{
1001 PmInternal *midi;
1002 PmError err = pmNoError;
1003 pm_hosterror = FALSE;
1004 *stream = NULL;
1005
1006 /* arg checking */
1007 if (outputDevice < 0 || outputDevice >= pm_descriptor_len)
1008 err = pmInvalidDeviceId;
1009 else if (!pm_descriptors[outputDevice].pub.output)
1010 err = pmInvalidDeviceId;
1011 else if (pm_descriptors[outputDevice].pub.opened)
1012 err = pmInvalidDeviceId;
1013 if (err != pmNoError)
1014 goto error_return;
1015
1016 /* common initialization of PmInternal structure (midi): */
1017 err = pm_create_internal(&midi, outputDevice, FALSE, latency, time_proc,
1018 time_info, bufferSize);
1019 *stream = midi;
1020 if (err) {
1021 goto error_return;
1022 }
1023
1024 /* open system dependent output device */
1025 err = (*midi->dictionary->open)(midi, outputDriverInfo);
1026 if (err) {
1027 *stream = NULL;
1028 pm_descriptors[outputDevice].pm_internal = NULL;
1029 /* free portMidi data */
1030 pm_free(midi);
1031 } else {
1032 /* portMidi input open successful */
1033 pm_descriptors[outputDevice].pub.opened = TRUE;
1034 }
1035error_return:
1036 /* note: system-dependent code must set pm_hosterror and
1037 * pm_hosterror_text if a pmHostError occurs
1038 */
1039 return pm_errmsg(err);
1040}
1041
1042
1043static PmError create_virtual_device(const char *name, const char *interf,
1044 void *device_info, int is_input)
1045{
1046 PmError err = pmNoError;
1047 int i;
1048 pm_hosterror = FALSE;
1049
1050 /* arg checking */
1051 if (!name) {
1052 err = pmInvalidDeviceId;
1053 goto error_return;
1054 }
1055
1056 Pm_Initialize(); /* just in case */
1057
1058 /* create the virtual device */
1059 if (pm_interf_list_len == 0) {
1060 return pmNotImplemented;
1061 }
1062 if (!interf) {
1063 /* default interface is the first one */
1064 interf = pm_interf_list[0].interf;
1065 }
1066 /* look up and call the create_fn for interf(ace), e.g. "CoreMIDI" */
1067 for (i = 0; i < pm_interf_list_len; i++) {
1068 if (strcmp(pm_interf_list[i].interf, interf) == 0) {
1069 int id = (*pm_interf_list[i].create_fn)(is_input,
1070 name, device_info);
1071 /* id could be pmNameConflict or an actual descriptor index */
1072 if (id >= 0) {
1073 pm_descriptors[id].pub.is_virtual = TRUE;
1074 }
1075 err = id;
1076 goto error_return;
1077 }
1078 }
1079 err = pmInterfaceNotSupported;
1080
1081error_return:
1082 /* note: if there is a pmHostError, it is the responsibility
1083 * of the system-dependent code (*midi->dictionary->open)()
1084 * to set pm_hosterror and pm_hosterror_text
1085 */
1086 return pm_errmsg(err);
1087}
1088
1089
1090PMEXPORT PmError Pm_CreateVirtualInput(const char *name,
1091 const char *interf,
1092 void *deviceInfo)
1093{
1094 return create_virtual_device(name, interf, deviceInfo, TRUE);
1095}
1096
1097PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf,
1098 void *deviceInfo)
1099{
1100 return create_virtual_device(name, interf, deviceInfo, FALSE);
1101}
1102
1103PmError Pm_DeleteVirtualDevice(PmDeviceID id)
1104{
1105 int i;
1106 const char *interf = pm_descriptors[id].pub.interf;
1107 PmError err = pmBadData; /* returned if we cannot find the interface-
1108 * specific delete function */
1109 /* arg checking */
1110 if (id < 0 || id >= pm_descriptor_len ||
1111 pm_descriptors[id].pub.opened || pm_descriptors[id].deleted) {
1112 return pmInvalidDeviceId;
1113 }
1114 /* delete function pointer is in interfaces list */
1115 for (i = 0; i < pm_interf_list_len; i++) {
1116 if (strcmp(pm_interf_list[i].interf, interf) == 0) {
1117 err = (*pm_interf_list[i].delete_fn)(id);
1118 break;
1119 }
1120 }
1121 pm_descriptors[id].deleted = TRUE;
1122 /* (pm_internal should already be NULL because !pub.opened) */
1123 pm_descriptors[id].pm_internal = NULL;
1124 pm_descriptors[id].descriptor = NULL;
1125 return err;
1126}
1127
1128PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)
1129{
1130 PmInternal *midi = (PmInternal *) stream;
1131 PmError err = pmNoError;
1132
1133 if (midi == NULL)
1134 err = pmBadPtr;
1135 else
1136 midi->channel_mask = mask;
1137
1138 return pm_errmsg(err);
1139}
1140
1141
1142PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters)
1143{
1144 PmInternal *midi = (PmInternal *) stream;
1145 PmError err = pmNoError;
1146
1147 /* arg checking */
1148 if (midi == NULL)
1149 err = pmBadPtr;
1150 else if (!pm_descriptors[midi->device_id].pub.opened)
1151 err = pmBadPtr;
1152 else
1153 midi->filters = filters;
1154 return pm_errmsg(err);
1155}
1156
1157
1158PMEXPORT PmError Pm_Close(PortMidiStream *stream)
1159{
1160 PmInternal *midi = (PmInternal *) stream;
1161 PmError err = pmNoError;
1162
1163 pm_hosterror = FALSE;
1164 /* arg checking */
1165 if (midi == NULL) /* midi must point to something */
1166 err = pmBadPtr;
1167 /* if it is an open device, the device_id will be valid */
1168 else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_len)
1169 err = pmBadPtr;
1170 /* and the device should be in the opened state */
1171 else if (!pm_descriptors[midi->device_id].pub.opened)
1172 err = pmBadPtr;
1173
1174 if (err != pmNoError)
1175 goto error_return;
1176
1177 /* close the device */
1178 err = (*midi->dictionary->close)(midi);
1179 /* even if an error occurred, continue with cleanup */
1180 pm_descriptors[midi->device_id].pm_internal = NULL;
1181 pm_descriptors[midi->device_id].pub.opened = FALSE;
1182 if (midi->queue) Pm_QueueDestroy(midi->queue);
1183 pm_free(midi);
1184error_return:
1185 /* system dependent code must set pm_hosterror and
1186 * pm_hosterror_text if a pmHostError occurs.
1187 */
1188 return pm_errmsg(err);
1189}
1190
1191PmError Pm_Synchronize(PortMidiStream* stream)
1192{
1193 PmInternal *midi = (PmInternal *) stream;
1194 PmError err = pmNoError;
1195 if (midi == NULL)
1196 err = pmBadPtr;
1197 else if (!pm_descriptors[midi->device_id].pub.output)
1198 err = pmBadPtr;
1199 else if (!pm_descriptors[midi->device_id].pub.opened)
1200 err = pmBadPtr;
1201 else
1202 midi->first_message = TRUE;
1203 return err;
1204}
1205
1206PMEXPORT PmError Pm_Abort(PortMidiStream* stream)
1207{
1208 PmInternal *midi = (PmInternal *) stream;
1209 PmError err;
1210 /* arg checking */
1211 if (midi == NULL)
1212 err = pmBadPtr;
1213 else if (!pm_descriptors[midi->device_id].pub.output)
1214 err = pmBadPtr;
1215 else if (!pm_descriptors[midi->device_id].pub.opened)
1216 err = pmBadPtr;
1217 else
1218 err = (*midi->dictionary->abort)(midi);
1219
1220 if (err == pmHostError) {
1221 midi->dictionary->check_host_error(midi);
1222 }
1223 return pm_errmsg(err);
1224}
1225
1226
1227
1228/* pm_channel_filtered returns non-zero if the channel mask is
1229 blocking the current channel */
1230#define pm_channel_filtered(status, mask) \
1231 ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask))))
1232
1233
1234/* The following two functions will checks to see if a MIDI message
1235 matches the filtering criteria. Since the sysex routines only want
1236 to filter realtime messages, we need to have separate routines.
1237 */
1238
1239
1240/* pm_realtime_filtered returns non-zero if the filter will kill the
1241 current message. Note that only realtime messages are checked here.
1242 */
1243#define pm_realtime_filtered(status, filters) \
1244 ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters)))
1245
1246/*
1247 return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
1248 || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
1249 || ((status == MIDI_START) && (filters & PM_FILT_PLAY))
1250 || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
1251 || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
1252 || ((status == MIDI_F9) && (filters & PM_FILT_F9))
1253 || ((status == MIDI_FD) && (filters & PM_FILT_FD))
1254 || ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
1255 || ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
1256 || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
1257 || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
1258 || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
1259}*/
1260
1261
1262/* pm_status_filtered returns non-zero if a filter will kill the
1263 current message, based on status. Note that sysex and real time are
1264 not checked. It is up to the subsystem (winmm, core midi, alsa) to
1265 filter sysex, as it is handled more easily and efficiently at that
1266 level. Realtime message are filtered in pm_realtime_filtered.
1267 */
1268#define pm_status_filtered(status, filters) \
1269 ((1 << (16 + ((status) >> 4))) & (filters))
1270
1271
1272/*
1273 return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
1274 || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
1275 || ((status == MIDI_CHANNEL_AT) &&
1276 (filters & PM_FILT_CHANNEL_AFTERTOUCH))
1277 || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
1278 || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
1279 || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
1280 || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
1281
1282}
1283*/
1284
1285static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
1286{
1287 PmEvent event;
1288
1289 /* there may be nothing in the buffer */
1290 if (midi->message_count == 0) return; /* nothing to flush */
1291
1292 event.message = midi->message;
1293 event.timestamp = timestamp;
1294 /* copied from pm_read_short, avoids filtering */
1295 if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
1296 midi->sysex_in_progress = FALSE;
1297 }
1298 midi->message_count = 0;
1299 midi->message = 0;
1300}
1301
1302
1303/* pm_read_short and pm_read_bytes
1304 are the interface between system-dependent MIDI input handlers
1305 and the system-independent PortMIDI code.
1306 The input handler MUST obey these rules:
1307 1) all short input messages must be sent to pm_read_short, which
1308 enqueues them to a FIFO for the application.
1309 2) each buffer of sysex bytes should be reported by calling pm_read_bytes
1310 (which sets midi->sysex_in_progress). After the eox byte,
1311 pm_read_bytes will clear sysex_in_progress
1312 */
1313
1314/* pm_read_short is the place where all input messages arrive from
1315 system-dependent code such as pmwinmm.c. Here, the messages
1316 are entered into the PortMidi input buffer.
1317 */
1318void pm_read_short(PmInternal *midi, PmEvent *event)
1319{
1320 int status;
1321 /* arg checking */
1322 assert(midi != NULL);
1323 /* midi filtering is applied here */
1324 status = Pm_MessageStatus(event->message);
1325 if (!pm_status_filtered(status, midi->filters)
1326 && (!is_real_time(status) ||
1327 !pm_realtime_filtered(status, midi->filters))
1328 && !pm_channel_filtered(status, midi->channel_mask)) {
1329 /* if sysex is in progress and we get a status byte, it had
1330 better be a realtime message or the starting SYSEX byte;
1331 otherwise, we exit the sysex_in_progress state
1332 */
1333 if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {
1334 /* two choices: real-time or not. If it's real-time, then
1335 * this should be delivered as a sysex byte because it is
1336 * embedded in a sysex message
1337 */
1338 if (is_real_time(status)) {
1339 midi->message |= (status << (8 * midi->message_count++));
1340 if (midi->message_count == 4) {
1341 pm_flush_sysex(midi, event->timestamp);
1342 }
1343 } else { /* otherwise, it's not real-time. This interrupts
1344 * a sysex message in progress */
1345 midi->sysex_in_progress = FALSE;
1346 }
1347 } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
1348 midi->sysex_in_progress = FALSE;
1349 }
1350 }
1351}
1352
1353
1354/* pm_read_bytes -- a sequence of bytes has been read from a device.
1355 * parse the bytes into PmEvents and put them in the queue.
1356 * midi - the midi device
1357 * data - the bytes
1358 * len - the number of bytes
1359 * timestamp - when were the bytes received?
1360 *
1361 * returns how many bytes processed, which is always the len parameter
1362 */
1363unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data,
1364 int len, PmTimestamp timestamp)
1365{
1366 int i = 0; /* index into data, must not be unsigned (!) */
1367 PmEvent event;
1368 event.timestamp = timestamp;
1369 assert(midi);
1370
1371 /* Since sysex messages may have embedded real-time messages, we
1372 * cannot simply send every consecutive group of 4 bytes as sysex
1373 * data. Instead, we insert each data byte into midi->message and
1374 * keep count using midi->message_count. If we encounter a
1375 * real-time message, it is sent immediately as a 1-byte PmEvent,
1376 * while sysex bytes are sent as PmEvents in groups of 4 bytes
1377 * until the sysex is either terminated by EOX (F7) or a
1378 * non-real-time message is encountered, indicating that the EOX
1379 * was dropped.
1380 */
1381
1382 /* This is a finite state machine so that we can accept any number
1383 * of bytes, even if they contain partial messages.
1384 *
1385 * midi->sysex_in_progress says we are expecting sysex message bytes
1386 * (otherwise, expect a short message or real-time message)
1387 * midi->message accumulates bytes to enqueue for application
1388 * midi->message_count is the number of bytes accumulated
1389 * midi->short_message_count is how many bytes we need in midi->message,
1390 * therefore midi->message_count, before we have a complete message
1391 * midi->running_status is running status or 0 if there is none
1392 *
1393 * Set running status when: A status byte < F0 is received.
1394 * Clear running status when: A status byte from F0 through F7 is
1395 * received.
1396 * Ignore (drop) data bytes when running status is 0.
1397 *
1398 * Our output buffer (the application input buffer) can overflow
1399 * at any time. If that occurs, we have to clear sysex_in_progress
1400 * (otherwise, the buffer could be flushed and we could resume
1401 * inserting sysex bytes into the buffer, resulting in a continuation
1402 * of a sysex message even though a buffer full of bytes was dropped.)
1403 *
1404 * Since we have to parse everything and form <=4-byte PmMessages,
1405 * we send all messages via pm_read_short, which filters messages
1406 * according to midi->filters and clears sysex_in_progress on
1407 * buffer overflow. This also provides a "short cut" for short
1408 * messages that are already parsed, allowing API-specific code
1409 * to bypass this more expensive state machine. What if we are
1410 * getting a sysex message, but it is interrupted by a short
1411 * message (status 80-EF) and a direct call to pm_read_short?
1412 * Without some care, the state machine would still be in
1413 * sysex_in_progress mode, and subsequent data bytes would be
1414 * accumulated as more sysex data, which is wrong since you
1415 * cannot have a short message in the middle of a sysex message.
1416 * To avoid this problem, pm_read_short clears sysex_in_progress
1417 * when a non-real-time short message arrives.
1418 */
1419
1420 while (i < len) {
1421 unsigned char byte = data[i++];
1422 if (is_real_time(byte)) {
1423 event.message = byte;
1424 pm_read_short(midi, &event);
1425 } else if (byte & MIDI_STATUS_MASK && byte != MIDI_EOX) {
1426 midi->message = byte;
1427 midi->message_count = 1;
1428 if (byte == MIDI_SYSEX) {
1429 midi->sysex_in_progress = TRUE;
1430 } else {
1431 midi->sysex_in_progress = FALSE;
1432 midi->short_message_count = pm_midi_length(midi->message);
1433 /* maybe we're done already with a 1-byte message: */
1434 if (midi->short_message_count == 1) {
1435 pm_read_short(midi, &event);
1436 midi->message_count = 0;
1437 }
1438 }
1439 } else if (midi->sysex_in_progress) { /* sysex data byte */
1440 /* accumulate sysex message data or EOX */
1441 midi->message |= (byte << (8 * midi->message_count++));
1442 if (midi->message_count == 4 || byte == MIDI_EOX) {
1443 event.message = midi->message;
1444 /* enqueue if not filtered, and then if there is overflow,
1445 stop sysex_in_progress */
1446 if (!(midi->filters & PM_FILT_SYSEX) &&
1447 Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
1448 midi->sysex_in_progress = FALSE;
1449 } else if (byte == MIDI_EOX) { /* continue unless EOX */
1450 midi->sysex_in_progress = FALSE;
1451 }
1452 midi->message_count = 0;
1453 midi->message = 0;
1454 }
1455 } else { /* no sysex in progress, must be short message */
1456 if (midi->message_count == 0) { /* need a running status */
1457 if (midi->running_status) {
1458 midi->message = midi->running_status;
1459 midi->message_count = 1;
1460 } else { /* drop data byte - not sysex and no status byte */
1461 continue;
1462 }
1463 }
1464 midi->message |= (byte << (8 * midi->message_count++));
1465 if (midi->message_count == midi->short_message_count) {
1466 event.message = midi->message;
1467 pm_read_short(midi, &event);
1468 }
1469 }
1470 }
1471 return i;
1472}
diff --git a/portmidi/pm_common/portmidi.h b/portmidi/pm_common/portmidi.h
new file mode 100755
index 0000000..8696a73
--- /dev/null
+++ b/portmidi/pm_common/portmidi.h
@@ -0,0 +1,974 @@
1#ifndef PORTMIDI_PORTMIDI_H
2#define PORTMIDI_PORTMIDI_H
3
4#ifdef __cplusplus
5extern "C" {
6#endif /* __cplusplus */
7
8/*
9 * PortMidi Portable Real-Time MIDI Library
10 * PortMidi API Header File
11 * Latest version available at: http://sourceforge.net/projects/portmedia
12 *
13 * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
14 * Copyright (c) 2001-2006 Roger B. Dannenberg
15 *
16 * Permission is hereby granted, free of charge, to any person obtaining
17 * a copy of this software and associated documentation files
18 * (the "Software"), to deal in the Software without restriction,
19 * including without limitation the rights to use, copy, modify, merge,
20 * publish, distribute, sublicense, and/or sell copies of the Software,
21 * and to permit persons to whom the Software is furnished to do so,
22 * subject to the following conditions:
23 *
24 * The above copyright notice and this permission notice shall be
25 * included in all copies or substantial portions of the Software.
26 *
27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
31 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
32 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 */
35
36/*
37 * The text above constitutes the entire PortMidi license; however,
38 * the PortMusic community also makes the following non-binding requests:
39 *
40 * Any person wishing to distribute modifications to the Software is
41 * requested to send the modifications to the original developer so that
42 * they can be incorporated into the canonical version. It is also
43 * requested that these non-binding requests be included along with the
44 * license above.
45 */
46
47/* CHANGELOG FOR PORTMIDI
48 * (see ../CHANGELOG.txt)
49 *
50 * NOTES ON HOST ERROR REPORTING:
51 *
52 * PortMidi errors (of type PmError) are generic,
53 * system-independent errors. When an error does not map to one of
54 * the more specific PmErrors, the catch-all code pmHostError is
55 * returned. This means that PortMidi has retained a more specific
56 * system-dependent error code. The caller can get more information
57 * by calling Pm_GetHostErrorText() to get a text string describing
58 * the error. Host errors can arise asynchronously from callbacks,
59 * * so there is no specific return code. Asynchronous errors are
60 * checked and reported by Pm_Poll. You can also check by calling
61 * Pm_HasHostError(). If this returns TRUE, Pm_GetHostErrorText()
62 * will return a text description of the error.
63 *
64 * NOTES ON COMPILE-TIME SWITCHES
65 *
66 * DEBUG assumes stdio and a console. Use this if you want
67 * automatic, simple error reporting, e.g. for prototyping. If
68 * you are using MFC or some other graphical interface with no
69 * console, DEBUG probably should be undefined.
70 * PM_CHECK_ERRORS more-or-less takes over error checking for
71 * return values, stopping your program and printing error
72 * messages when an error occurs. This also uses stdio for
73 * console text I/O. You can selectively disable this error
74 * checking by declaring extern int pm_check_errors; and
75 * setting pm_check_errors = FALSE; You can also reenable.
76 */
77/**
78 \defgroup grp_basics Basic Definitions
79 @{
80*/
81
82#include <stdint.h>
83
84#ifdef _WINDLL
85#define PMEXPORT __declspec(dllexport)
86#else
87#define PMEXPORT
88#endif
89
90#ifndef FALSE
91 #define FALSE 0
92#endif
93#ifndef TRUE
94 #define TRUE 1
95#endif
96
97/* default size of buffers for sysex transmission: */
98#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
99
100
101typedef enum {
102 pmNoError = 0, /**< Normal return value indicating no error. */
103 pmNoData = 0, /**< @brief No error, also indicates no data available.
104 * Use this constant where a value greater than zero would
105 * indicate data is available.
106 */
107 pmGotData = 1, /**< A "no error" return also indicating data available. */
108 pmHostError = -10000,
109 pmInvalidDeviceId, /**< Out of range or
110 * output device when input is requested or
111 * input device when output is requested or
112 * device is already opened.
113 */
114 pmInsufficientMemory,
115 pmBufferTooSmall,
116 pmBufferOverflow,
117 pmBadPtr, /**< #PortMidiStream parameter is NULL or
118 * stream is not opened or
119 * stream is output when input is required or
120 * stream is input when output is required. */
121 pmBadData, /**< Illegal midi data, e.g., missing EOX. */
122 pmInternalError,
123 pmBufferMaxSize, /**< Buffer is already as large as it can be. */
124 pmNotImplemented, /**< The function is not implemented, nothing was done. */
125 pmInterfaceNotSupported, /**< The requested interface is not supported. */
126 pmNameConflict, /**< Cannot create virtual device because name is taken. */
127 pmDeviceRemoved /**< Output attempted after (USB) device was removed. */
128 /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */
129} PmError; /**< @brief @enum PmError PortMidi error code; a common return type.
130 * No error is indicated by zero; errors are indicated by < 0.
131 */
132
133/**
134 Pm_Initialize() is the library initialization function - call this before
135 using the library.
136
137 *NOTE:* PortMidi scans for available devices when #Pm_Initialize
138 is called. To observe subsequent changes in the available
139 devices, you must shut down PortMidi by calling #Pm_Terminate and
140 then restart by calling #Pm_Initialize again. *IMPORTANT*: On
141 MacOS, #Pm_Initialize *must* always be called on the same
142 thread. Otherwise, changes in the available MIDI devices will
143 *not* be seen by PortMidi. As an example, if you start PortMidi in
144 a thread for processing MIDI, do not try to rescan devices by
145 calling #Pm_Initialize in a GUI thread. Instead, start PortMidi
146 the first time and every time in the GUI thread. Alternatively,
147 let the GUI request a restart in the MIDI thread. (These
148 restrictions only apply to macOS.) Speaking of threads, on all
149 platforms, you are allowed to call #Pm_Initialize in one thread,
150 yet send MIDI or poll for incoming MIDI in another
151 thread. However, PortMidi is not "thread safe," which means you
152 cannot allow threads to call PortMidi functions concurrently.
153
154 @return pmNoError.
155
156 PortMidi is designed to support multiple interfaces (such as ALSA,
157 CoreMIDI and WinMM). It is possible to return pmNoError because there
158 are no supported interfaces. In that case, zero devices will be
159 available.
160*/
161PMEXPORT PmError Pm_Initialize(void);
162
163/**
164 Pm_Terminate() is the library termination function - call this after
165 using the library.
166*/
167PMEXPORT PmError Pm_Terminate(void);
168
169/** Represents an open MIDI device. */
170typedef void PortMidiStream;
171
172/** A shorter form of #PortMidiStream. */
173#define PmStream PortMidiStream
174
175/** Test whether stream has a pending host error. Normally, the client
176 finds out about errors through returned error codes, but some
177 errors can occur asynchronously where the client does not
178 explicitly call a function, and therefore cannot receive an error
179 code. The client can test for a pending error using
180 Pm_HasHostError(). If true, the error can be accessed by calling
181 Pm_GetHostErrorText(). Pm_Poll() is similar to Pm_HasHostError(),
182 but if there is no error, it will return TRUE (1) if there is a
183 pending input message.
184*/
185PMEXPORT int Pm_HasHostError(PortMidiStream * stream);
186
187
188/** Translate portmidi error number into human readable message.
189 These strings are constants (set at compile time) so client has
190 no need to allocate storage.
191*/
192PMEXPORT const char *Pm_GetErrorText(PmError errnum);
193
194/** Translate portmidi host error into human readable message.
195 These strings are computed at run time, so client has to allocate storage.
196 After this routine executes, the host error is cleared.
197*/
198PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);
199
200/** Any host error msg has at most this many characters, including EOS. */
201#define PM_HOST_ERROR_MSG_LEN 256u
202
203/** Devices are represented as small integers. Device ids range from 0
204 to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information
205 about the device, and Pm_OpenInput() and PmOpenOutput() are used to
206 open the device.
207*/
208typedef int PmDeviceID;
209
210/** This PmDeviceID (constant) value represents no device and may be
211 returned by Pm_GetDefaultInputDeviceID() or
212 Pm_GetDefaultOutputDeviceID() if no default exists.
213*/
214#define pmNoDevice -1
215
216/** MIDI device information is returned in this structure, which is
217 owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo().
218*/
219#define PM_DEVICEINFO_VERS 200
220typedef struct {
221 int structVersion; /**< @brief this internal structure version */
222 const char *interf; /**< @brief underlying MIDI API, e.g.
223 "MMSystem" or "DirectX" */
224 char *name; /**< @brief device name, e.g. "USB MidiSport 1x1" */
225 int input; /**< @brief true iff input is available */
226 int output; /**< @brief true iff output is available */
227 int opened; /**< @brief used by generic PortMidi for error checking */
228 int is_virtual; /**< @brief true iff this is/was a virtual device */
229} PmDeviceInfo;
230
231/** MIDI system-dependent device or driver info is passed in this
232 structure, which is owned by the caller.
233*/
234#define PM_SYSDEPINFO_VERS 210
235
236enum PmSysDepPropertyKey {
237 pmKeyNone = 0, /**< a "noop" key value */
238 /** CoreMIDI Manufacturer name, value is string */
239 pmKeyCoreMidiManufacturer = 1,
240 /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be
241 passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening
242 a device. The created port will be named accordingly and will be
243 visible for externally made connections (subscriptions). (Linux ALSA
244 ports are always enabled for this, but only get application-specific
245 names if you give it one.) This key/value is ignored when opening
246 virtual ports, which are named when they are created.) */
247 pmKeyAlsaPortName = 2,
248 /** Linux ALSA snd_seq_set_client_name, value is a string.
249 Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput.
250 Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override
251 any previously set client name and applies to all ports. */
252 pmKeyAlsaClientName = 3
253 /* if system-dependent code introduces more options, register
254 the key here to avoid conflicts. */
255};
256
257/** System-dependent information can be passed when creating and opening
258 ports using this data structure, which stores alternating keys and
259 values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`,
260 and `pm_test/testio.c` for examples.
261 */
262typedef struct {
263 int structVersion; /**< @brief this structure version */
264 int length; /**< @brief number of properties in this structure */
265 struct {
266 enum PmSysDepPropertyKey key;
267 const void *value;
268 } properties[];
269} PmSysDepInfo;
270
271
272/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */
273PMEXPORT int Pm_CountDevices(void);
274
275/**
276 Return the default device ID or pmNoDevice if there are no devices.
277 The result (but not pmNoDevice) can be passed to Pm_OpenMidi().
278
279 The use of these functions is not recommended. There is no natural
280 "default device" on any system, so defaults must be set by users.
281 (Currently, PortMidi just returns the first device it finds as
282 "default", so if there *is* a default, implementors should use
283 pm_add_device to add system default input and output devices
284 first.)
285
286 The recommended solution is pass the burden to applications. It is
287 easy to scan devices with PortMidi and build a device menu, and to
288 save menu selections in application preferences for next
289 time. This is my recommendation for any GUI program. For simple
290 command-line applications and utilities, see pm_test where all the
291 test programs now accept device numbers on the command line and/or
292 prompt for their entry.
293
294 On linux, you can create virtual ports and use an external program
295 to set up inter-application and device connections.
296
297 Some advice for preferences: MIDI devices used to be built-in or
298 plug-in cards, so the numbers rarely changed. Now MIDI devices are
299 often plug-in USB devices, so device numbers change, and you
300 probably need to design to reinitialize PortMidi to rescan
301 devices. MIDI is pretty stateless, so this isn't a big problem,
302 although it means you cannot find a new device while playing or
303 recording MIDI.
304
305 Since device numbering can change whenever a USB device is plugged
306 in, preferences should record *names* of devices rather than
307 device numbers. It is simple enough to use string matching to find
308 a prefered device, so PortMidi does not provide any built-in
309 lookup function.
310*/
311PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void);
312
313/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */
314PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void);
315
316/** Find a device that matches a pattern.
317
318 @param pattern a substring of the device name, or if the pattern
319 contains the two-character separator ", ", then the first part of
320 the pattern represents a device interface substring and the second
321 part after the separator represents a device name substring.
322
323 @param is_input restricts the search to an input when true, or an
324 output when false.
325
326 @return the number of the first device whose device interface
327 contains the interface pattern (if any), whose device name
328 contains the name pattern, and whose direction (input or output)
329 matches the #is_input parameter. If no match is found, #pmNoDevice
330 (-1) is returned.
331*/
332PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input);
333
334
335/** Represents a millisecond clock with arbitrary start time.
336 This type is used for all MIDI timestamps and clocks.
337*/
338typedef int32_t PmTimestamp;
339typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
340
341/** TRUE if t1 before t2 */
342#define PmBefore(t1,t2) (((t1)-(t2)) < 0)
343/** @} */
344/**
345 \defgroup grp_device Input/Output Devices Handling
346 @{
347*/
348/** Get a PmDeviceInfo structure describing a MIDI device.
349
350 @param id the device to be queried.
351
352 If \p id is out of range or if the device designates a deleted
353 virtual device, the function returns NULL.
354
355 The returned structure is owned by the PortMidi implementation and
356 must not be manipulated or freed. The pointer is guaranteed to be
357 valid between calls to Pm_Initialize() and Pm_Terminate().
358*/
359PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id);
360
361/** Open a MIDI device for input.
362
363 @param stream the address of a #PortMidiStream pointer which will
364 receive a pointer to the newly opened stream.
365
366 @param inputDevice the ID of the device to be opened (see #PmDeviceID).
367
368 @param inputSysDepInfo a pointer to an optional system-dependent
369 data structure (a #PmSysDepInfo struct) containing additional
370 information for device setup or handle processing. This parameter
371 is never required for correct operation. If not used, specify
372 NULL. Declared `void *` here for backward compatibility. Note that
373 with Linux ALSA, you can use this parameter to specify a client name
374 and port name.
375
376 @param bufferSize the number of input events to be buffered
377 waiting to be read using Pm_Read(). Messages will be lost if the
378 number of unread messages exceeds this value.
379
380 @param time_proc (address of) a procedure that returns time in
381 milliseconds. It may be NULL, in which case a default millisecond
382 timebase (PortTime) is used. If the application wants to use
383 PortTime, it should start the timer (call Pt_Start) before calling
384 Pm_OpenInput or Pm_OpenOutput. If the application tries to start
385 the timer *after* Pm_OpenInput or Pm_OpenOutput, it may get a
386 ptAlreadyStarted error from Pt_Start, and the application's
387 preferred time resolution and callback function will be ignored.
388 \p time_proc result values are appended to incoming MIDI data,
389 normally by mapping system-provided timestamps to the \p time_proc
390 timestamps to maintain the precision of system-provided
391 timestamps.
392
393 @param time_info is a pointer passed to time_proc.
394
395 @return #pmNoError and places a pointer to a valid
396 #PortMidiStream in the stream argument. If the open operation
397 fails, a nonzero error code is returned (see #PMError) and
398 the value of stream is invalid.
399
400 Any stream that is successfully opened should eventually be closed
401 by calling Pm_Close().
402*/
403PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,
404 PmDeviceID inputDevice,
405 void *inputSysDepInfo,
406 int32_t bufferSize,
407 PmTimeProcPtr time_proc,
408 void *time_info);
409
410/** Open a MIDI device for output.
411
412 @param stream the address of a #PortMidiStream pointer which will
413 receive a pointer to the newly opened stream.
414
415 @param outputDevice the ID of the device to be opened (see #PmDeviceID).
416
417 @param inputSysDepInfo a pointer to an optional system-specific
418 data structure (a #PmSysDepInfo struct) containing additional
419 information for device setup or handle processing. This parameter
420 is never required for correct operation. If not used, specify
421 NULL. Declared `void *` here for backward compatibility. Note that
422 with Linux ALSA, you can use this parameter to specify a client name
423 and port name.
424
425 @param bufferSize the number of output events to be buffered
426 waiting for output. In some cases -- see below -- PortMidi does
427 not buffer output at all and merely passes data to a lower-level
428 API, in which case \p bufferSize is ignored. Since MIDI speeds now
429 vary from 1 to 50 or more messages per ms (over USB), put some
430 thought into this number. E.g. if latency is 20ms and you want to
431 burst 100 messages in that time (5000 messages per second), you
432 should set \p bufferSize to at least 100. The default on Windows
433 assumes an average rate of 500 messages per second and in this
434 example, output would be slowed waiting for free buffers.
435
436 @param latency the delay in milliseconds applied to timestamps
437 to determine when the output should actually occur. (If latency is
438 < 0, 0 is assumed.) If latency is zero, timestamps are ignored
439 and all output is delivered immediately. If latency is greater
440 than zero, output is delayed until the message timestamp plus the
441 latency. (NOTE: the time is measured relative to the time source
442 indicated by time_proc. Timestamps are absolute, not relative
443 delays or offsets.) In some cases, PortMidi can obtain better
444 timing than your application by passing timestamps along to the
445 device driver or hardware, so the best strategy to minimize jitter
446 is: wait until the real time to send the message, compute the
447 message, attach the *ideal* output time (not the current real
448 time, because some time may have elapsed), and send the
449 message. The \p latency will be added to the timestamp, and
450 provided the elapsed computation time has not exceeded \p latency,
451 the message will be delivered according to the timestamp. If the
452 real time is already past the timestamp, the message will be
453 delivered as soon as possible. Latency may also help you to
454 synchronize MIDI data to audio data by matching \p latency to the
455 audio buffer latency.
456
457 @param time_proc (address of) a pointer to a procedure that
458 returns time in milliseconds. It may be NULL, in which case a
459 default millisecond timebase (PortTime) is used. If the
460 application wants to use PortTime, it should start the timer (call
461 Pt_Start) before calling #Pm_OpenInput or #Pm_OpenOutput. If the
462 application tries to start the timer *after* #Pm_OpenInput or
463 #Pm_OpenOutput, it may get a #ptAlreadyStarted error from #Pt_Start,
464 and the application's preferred time resolution and callback
465 function will be ignored. \p time_proc times are used to schedule
466 outgoing MIDI data (when latency is non-zero), usually by mapping
467 from time_proc timestamps to internal system timestamps to
468 maintain the precision of system-supported timing.
469
470 @param time_info a pointer passed to time_proc.
471
472 @return #pmNoError and places a pointer to a valid #PortMidiStream
473 in the stream argument. If the operation fails, a nonzero error
474 code is returned (see PMError) and the value of \p stream is
475 invalid.
476
477 Note: ALSA appears to have a fixed-size priority queue for timed
478 output messages. Testing indicates the queue can hold a little
479 over 400 3-byte MIDI messages. Thus, you can send 10,000
480 messages/second if the latency is 30ms (30ms * 10000 msgs/sec *
481 0.001 sec/ms = 300 msgs), but not if the latency is 50ms
482 (resulting in about 500 pending messages, which is greater than
483 the 400 message limit). Since timestamps in ALSA are relative,
484 they are of less value than absolute timestamps in macOS and
485 Windows. This is a limitation of ALSA and apparently a design
486 flaw.
487
488 Example 1: If I provide a timestamp of 5000, latency is 1, and
489 time_proc returns 4990, then the desired output time will be when
490 time_proc returns timestamp+latency = 5001. This will be 5001-4990
491 = 11ms from now.
492
493 Example 2: If I want to send at exactly 5010, and latency is 10, I
494 should wait until 5000, compute the messages and provide a
495 timestamp of 5000. As long as computation takes less than 10ms,
496 the message will be delivered at time 5010.
497
498 Example 3 (recommended): It is often convenient to ignore latency.
499 E.g. if a sequence says to output at time 5010, just wait until
500 5010, compute the message and use 5010 for the timestamp. Delivery
501 will then be at 5010+latency, but unless you are synchronizing to
502 something else, the absolute delay by latency will not matter.
503
504 Any stream that is successfully opened should eventually be closed
505 by calling Pm_Close().
506*/
507PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
508 PmDeviceID outputDevice,
509 void *outputSysDepInfo,
510 int32_t bufferSize,
511 PmTimeProcPtr time_proc,
512 void *time_info,
513 int32_t latency);
514
515/** Create a virtual input device.
516
517 @param name gives the virtual device name, which is visible to
518 other applications.
519
520 @param interf is the interface (System API) used to create the
521 device Default interfaces are "MMSystem", "CoreMIDI" and
522 "ALSA". Currently, these are the only ones implemented, but future
523 implementations could support DirectMusic, Jack, sndio, or others.
524
525 @param sysDepInfo contains interface-dependent additional
526 information (a #PmSysDepInfo struct), e.g., hints or options. This
527 parameter is never required for correct operation. If not used,
528 specify NULL. Declared `void *` here for backward compatibility.
529
530 @return a device ID or #pmNameConflict (\p name is invalid or
531 already exists) or #pmInterfaceNotSupported (\p interf is does not
532 match a supported interface).
533
534 The created virtual device appears to other applications as if it
535 is an output device. The device must be opened to obtain a stream
536 and read from it.
537
538 Virtual devices are not supported by Windows (Multimedia API). Calls
539 on Windows do nothing except return #pmNotImplemented.
540*/
541PMEXPORT PmError Pm_CreateVirtualInput(const char *name,
542 const char *interf,
543 void *sysDepInfo);
544
545/** Create a virtual output device.
546
547 @param name gives the virtual device name, which is visible to
548 other applications.
549
550 @param interf is the interface (System API) used to create the
551 device Default interfaces are "MMSystem", "CoreMIDI" and
552 "ALSA". Currently, these are the only ones implemented, but future
553 implementations could support DirectMusic, Jack, sndio, or others.
554
555 @param sysDepInfo contains interface-dependent additional
556 information (a #PmSysDepInfo struct), e.g., hints or options. This
557 parameter is never required for correct operation. If not used,
558 specify NULL. Declared `void *` here for backward compatibility.
559
560 @return a device ID or #pmInvalidDeviceId (\p name is invalid or
561 already exists) or #pmInterfaceNotSupported (\p interf is does not
562 match a supported interface).
563
564 The created virtual device appears to other applications as if it
565 is an input device. The device must be opened to obtain a stream
566 and write to it.
567
568 Virtual devices are not supported by Windows (Multimedia API). Calls
569 on Windows do nothing except return #pmNotImplemented.
570*/
571PMEXPORT PmError Pm_CreateVirtualOutput(const char *name,
572 const char *interf,
573 void *sysDepInfo);
574
575/** Remove a virtual device.
576
577 @param device a device ID (small integer) designating the device.
578
579 The device is removed; other applications can no longer see or open
580 this virtual device, which may be either for input or output. The
581 device must not be open. The device ID may be reused, but existing
582 devices are not renumbered. This means that the device ID could be
583 in the range from 0 to #Pm_CountDevices(), yet the device ID does
584 not designate a device. In that case, passing the ID to
585 #Pm_GetDeviceInfo() will return NULL.
586
587 @return #pmNoError if the device was deleted or #pmInvalidDeviceId
588 if the device is open, already deleted, or \p device is out of
589 range.
590*/
591PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device);
592 /** @} */
593
594/**
595 @defgroup grp_events_filters Events and Filters Handling
596 @{
597*/
598
599/* Filter bit-mask definitions */
600/** filter active sensing messages (0xFE): */
601#define PM_FILT_ACTIVE (1 << 0x0E)
602/** filter system exclusive messages (0xF0): */
603#define PM_FILT_SYSEX (1 << 0x00)
604/** filter MIDI clock message (0xF8) */
605#define PM_FILT_CLOCK (1 << 0x08)
606/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
607#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
608/** filter tick messages (0xF9) */
609#define PM_FILT_TICK (1 << 0x09)
610/** filter undefined FD messages */
611#define PM_FILT_FD (1 << 0x0D)
612/** filter undefined real-time messages */
613#define PM_FILT_UNDEFINED PM_FILT_FD
614/** filter reset messages (0xFF) */
615#define PM_FILT_RESET (1 << 0x0F)
616/** filter all real-time messages */
617#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
618 PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
619/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
620#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
621/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
622#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
623/** per-note aftertouch (0xA0-0xAF) */
624#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
625/** filter both channel and poly aftertouch */
626#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | \
627 PM_FILT_POLY_AFTERTOUCH)
628/** Program changes (0xC0-0xCF) */
629#define PM_FILT_PROGRAM (1 << 0x1C)
630/** Control Changes (CC's) (0xB0-0xBF)*/
631#define PM_FILT_CONTROL (1 << 0x1B)
632/** Pitch Bender (0xE0-0xEF*/
633#define PM_FILT_PITCHBEND (1 << 0x1E)
634/** MIDI Time Code (0xF1)*/
635#define PM_FILT_MTC (1 << 0x01)
636/** Song Position (0xF2) */
637#define PM_FILT_SONG_POSITION (1 << 0x02)
638/** Song Select (0xF3)*/
639#define PM_FILT_SONG_SELECT (1 << 0x03)
640/** Tuning request (0xF6) */
641#define PM_FILT_TUNE (1 << 0x06)
642/** All System Common messages (mtc, song position, song select, tune request) */
643#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | \
644 PM_FILT_SONG_SELECT | PM_FILT_TUNE)
645
646
647/* Set filters on an open input stream to drop selected input types.
648
649 @param stream an open MIDI input stream.
650
651 @param filters indicate message types to filter (block).
652
653 @return #pmNoError or an error code.
654
655 By default, only active sensing messages are filtered.
656 To prohibit, say, active sensing and sysex messages, call
657 Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
658
659 Filtering is useful when midi routing or midi thru functionality
660 is being provided by the user application.
661 For example, you may want to exclude timing messages (clock, MTC,
662 start/stop/continue), while allowing note-related messages to pass.
663 Or you may be using a sequencer or drum-machine for MIDI clock
664 information but want to exclude any notes it may play.
665 */
666PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters);
667
668/** Create a mask that filters one channel. */
669#define Pm_Channel(channel) (1<<(channel))
670
671/** Filter incoming messages based on channel.
672
673 @param stream an open MIDI input stream.
674
675 @param mask indicates channels to be received.
676
677 @return #pmNoError or an error code.
678
679 The \p mask is a 16-bit bitfield corresponding to appropriate channels.
680 The #Pm_Channel macro can assist in calling this function.
681 I.e. to receive only input on channel 1, call with
682 Pm_SetChannelMask(Pm_Channel(1));
683 Multiple channels should be OR'd together, like
684 Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
685
686 Note that channels are numbered 0 to 15 (not 1 to 16). Most
687 synthesizer and interfaces number channels starting at 1, but
688 PortMidi numbers channels starting at 0.
689
690 All channels are allowed by default
691*/
692PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
693
694/** Terminate outgoing messages immediately.
695
696 @param stream an open MIDI output stream.
697
698 @result #pmNoError or an error code.
699
700 The caller should immediately close the output port; this call may
701 result in transmission of a partial MIDI message. There is no
702 abort for Midi input because the user can simply ignore messages
703 in the buffer and close an input device at any time. If the
704 specified behavior cannot be achieved through the system-level
705 interface (ALSA, CoreMIDI, etc.), the behavior may be that of
706 Pm_Close().
707 */
708PMEXPORT PmError Pm_Abort(PortMidiStream* stream);
709
710/** Close a midi stream, flush any pending buffers if possible.
711
712 @param stream an open MIDI input or output stream.
713
714 @result #pmNoError or an error code.
715
716 If the system-level interface (ALSA, CoreMIDI, etc.) does not
717 support flushing remaining messages, the behavior may be one of
718 the following (most preferred first): block until all pending
719 timestamped messages are delivered; deliver messages to a server
720 or kernel process for later delivery but return immediately; drop
721 messages (as in Pm_Abort()). Therefore, to be safe, applications
722 should wait until the output queue is empty before calling
723 Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a
724 100ms "cushion" beyond latency (if any) before closing.
725*/
726PMEXPORT PmError Pm_Close(PortMidiStream* stream);
727
728/** (re)synchronize to the time_proc passed when the stream was opened.
729
730 @param stream an open MIDI input or output stream.
731
732 @result #pmNoError or an error code.
733
734 Typically, this is used when the stream must be opened before the
735 time_proc reference is actually advancing. In this case, message
736 timing may be erratic, but since timestamps of zero mean "send
737 immediately," initialization messages with zero timestamps can be
738 written without a functioning time reference and without
739 problems. Before the first MIDI message with a non-zero timestamp
740 is written to the stream, the time reference must begin to advance
741 (for example, if the time_proc computes time based on audio
742 samples, time might begin to advance when an audio stream becomes
743 active). After time_proc return values become valid, and BEFORE
744 writing the first non-zero timestamped MIDI message, call
745 Pm_Synchronize() so that PortMidi can observe the difference
746 between the current time_proc value and its MIDI stream time.
747
748 In the more normal case where time_proc values advance
749 continuously, there is no need to call #Pm_Synchronize. PortMidi
750 will always synchronize at the first output message and
751 periodically thereafter.
752*/
753PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream);
754
755
756/** Encode a short Midi message into a 32-bit word. If data1
757 and/or data2 are not present, use zero.
758*/
759#define Pm_Message(status, data1, data2) \
760 ((((data2) << 16) & 0xFF0000) | \
761 (((data1) << 8) & 0xFF00) | \
762 ((status) & 0xFF))
763/** Extract the status field from a 32-bit midi message. */
764#define Pm_MessageStatus(msg) ((msg) & 0xFF)
765/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */
766#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
767/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */
768#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
769
770typedef uint32_t PmMessage; /**< @brief see #PmEvent */
771/**
772 All MIDI data comes in the form of PmEvent structures. A sysex
773 message is encoded as a sequence of PmEvent structures, with each
774 structure carrying 4 bytes of the message, i.e. only the first
775 PmEvent carries the status byte.
776
777 All other MIDI messages take 1 to 3 bytes and are encoded in a whole
778 PmMessage with status in the low-order byte and remaining bytes
779 unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes
780 of PmMessage, leaving the high-order byte unused.
781
782 Note that MIDI allows nested messages: the so-called "real-time" MIDI
783 messages can be inserted into the MIDI byte stream at any location,
784 including within a sysex message. MIDI real-time messages are one-byte
785 messages used mainly for timing (see the MIDI spec). PortMidi retains
786 the order of non-real-time MIDI messages on both input and output, but
787 it does not specify exactly how real-time messages are processed. This
788 is particulary problematic for MIDI input, because the input parser
789 must either prepare to buffer an unlimited number of sysex message
790 bytes or to buffer an unlimited number of real-time messages that
791 arrive embedded in a long sysex message. To simplify things, the input
792 parser is allowed to pass real-time MIDI messages embedded within a
793 sysex message, and it is up to the client to detect, process, and
794 remove these messages as they arrive.
795
796 When receiving sysex messages, the sysex message is terminated
797 by either an EOX status byte (anywhere in the 4 byte messages) or
798 by a non-real-time status byte in the low order byte of the message.
799 If you get a non-real-time status byte but there was no EOX byte, it
800 means the sysex message was somehow truncated. This is not
801 considered an error; e.g., a missing EOX can result from the user
802 disconnecting a MIDI cable during sysex transmission.
803
804 A real-time message can occur within a sysex message. A real-time
805 message will always occupy a full PmEvent with the status byte in
806 the low-order byte of the PmEvent message field. (This implies that
807 the byte-order of sysex bytes and real-time message bytes may not
808 be preserved -- for example, if a real-time message arrives after
809 3 bytes of a sysex message, the real-time message will be delivered
810 first. The first word of the sysex message will be delivered only
811 after the 4th byte arrives, filling the 4-byte PmEvent message field.
812
813 The timestamp field is observed when the output port is opened with
814 a non-zero latency. A timestamp of zero means "use the current time",
815 which in turn means to deliver the message with a delay of
816 latency (the latency parameter used when opening the output port.)
817 Do not expect PortMidi to sort data according to timestamps --
818 messages should be sent in the correct order, and timestamps MUST
819 be non-decreasing. See also "Example" for Pm_OpenOutput() above.
820
821 A sysex message will generally fill many #PmEvent structures. On
822 output to a #PortMidiStream with non-zero latency, the first timestamp
823 on sysex message data will determine the time to begin sending the
824 message. PortMidi implementations may ignore timestamps for the
825 remainder of the sysex message.
826
827 On input, the timestamp ideally denotes the arrival time of the
828 status byte of the message. The first timestamp on sysex message
829 data will be valid. Subsequent timestamps may denote
830 when message bytes were actually received, or they may be simply
831 copies of the first timestamp.
832
833 Timestamps for nested messages: If a real-time message arrives in
834 the middle of some other message, it is enqueued immediately with
835 the timestamp corresponding to its arrival time. The interrupted
836 non-real-time message or 4-byte packet of sysex data will be enqueued
837 later. The timestamp of interrupted data will be equal to that of
838 the interrupting real-time message to insure that timestamps are
839 non-decreasing.
840 */
841typedef struct {
842 PmMessage message;
843 PmTimestamp timestamp;
844} PmEvent;
845
846/** @} */
847
848/** \defgroup grp_io Reading and Writing Midi Messages
849 @{
850*/
851/** Retrieve midi data into a buffer.
852
853 @param stream the open input stream.
854
855 @return the number of events read, or, if the result is negative,
856 a #PmError value will be returned.
857
858 The Buffer Overflow Problem
859
860 The problem: if an input overflow occurs, data will be lost,
861 ultimately because there is no flow control all the way back to
862 the data source. When data is lost, the receiver should be
863 notified and some sort of graceful recovery should take place,
864 e.g. you shouldn't resume receiving in the middle of a long sysex
865 message.
866
867 With a lock-free fifo, which is pretty much what we're stuck with
868 to enable portability to the Mac, it's tricky for the producer and
869 consumer to synchronously reset the buffer and resume normal
870 operation.
871
872 Solution: the entire buffer managed by PortMidi will be flushed
873 when an overflow occurs. The consumer (Pm_Read()) gets an error
874 message (#pmBufferOverflow) and ordinary processing resumes as
875 soon as a new message arrives. The remainder of a partial sysex
876 message is not considered to be a "new message" and will be
877 flushed as well.
878*/
879PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length);
880
881/** Test whether input is available.
882
883 @param stream an open input stream.
884
885 @return TRUE, FALSE, or an error value.
886
887 If there was an asynchronous error, pmHostError is returned and you must
888 call again to determine if input is (also) available.
889
890 You should probably *not* use this function. Call Pm_Read()
891 instead. If it returns 0, then there is no data available. It is
892 possible for Pm_Poll() to return TRUE before the complete message
893 is available, so Pm_Read() could return 0 even after Pm_Poll()
894 returns TRUE. Only call Pm_Poll() if you want to know that data is
895 probably available even though you are not ready to receive data.
896*/
897PMEXPORT PmError Pm_Poll(PortMidiStream *stream);
898
899/** Write MIDI data from a buffer.
900
901 @param stream an open output stream.
902
903 @param buffer (address of) an array of MIDI event data.
904
905 @param length the length of the \p buffer.
906
907 @return TRUE, FALSE, or an error value.
908
909 \b buffer may contain:
910 - short messages
911 - sysex messages that are converted into a sequence of PmEvent
912 structures, e.g. sending data from a file or forwarding them
913 from midi input, with 4 SysEx bytes per PmEvent message,
914 low-order byte first, until the last message, which may
915 contain from 1 to 4 bytes ending in MIDI EOX (0xF7).
916 - PortMidi allows 1-byte real-time messages to be embedded
917 within SysEx messages, but only on 4-byte boundaries so
918 that SysEx data always uses a full 4 bytes (except possibly
919 at the end). Each real-time message always occupies a full
920 PmEvent (3 of the 4 bytes in the PmEvent's message are
921 ignored) even when embedded in a SysEx message.
922
923 Use Pm_WriteSysEx() to write a sysex message stored as a contiguous
924 array of bytes.
925
926 Sysex data may contain embedded real-time messages.
927
928 \p buffer is managed by the caller. The buffer may be destroyed
929 as soon as this call returns.
930*/
931PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,
932 int32_t length);
933
934/** Write a timestamped non-system-exclusive midi message.
935
936 @param stream an open output stream.
937
938 @param when timestamp for the event.
939
940 @param msg the data for the event.
941
942 @result #pmNoError or an error code.
943
944 Messages are delivered in order, and timestamps must be
945 non-decreasing. (But timestamps are ignored if the stream was
946 opened with latency = 0, and otherwise, non-decreasing timestamps
947 are "corrected" to the lowest valid value.)
948*/
949PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
950 PmMessage msg);
951
952/** Write a timestamped system-exclusive midi message.
953
954 @param stream an open output stream.
955
956 @param when timestamp for the event.
957
958 @param msg the sysex message, terminated with an EOX status byte.
959
960 @result #pmNoError or an error code.
961
962 \p msg is managed by the caller and may be destroyed when this
963 call returns.
964*/
965PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when,
966 unsigned char *msg);
967
968/** @} */
969
970#ifdef __cplusplus
971}
972#endif /* __cplusplus */
973
974#endif /* PORTMIDI_PORTMIDI_H */
diff --git a/portmidi/pm_haiku/pmhaiku.cpp b/portmidi/pm_haiku/pmhaiku.cpp
new file mode 100644
index 0000000..0c592f1
--- /dev/null
+++ b/portmidi/pm_haiku/pmhaiku.cpp
@@ -0,0 +1,473 @@
1/* pmhaiku.cpp -- PortMidi os-dependent code */
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <vector>
6#include <MidiConsumer.h>
7#include <MidiEndpoint.h>
8#include <MidiProducer.h>
9#include <MidiRoster.h>
10#include <MidiSynth.h>
11#include "portmidi.h"
12#include "pmutil.h"
13#include "pminternal.h"
14
15namespace {
16 struct PmInputConsumer : BMidiLocalConsumer {
17 PmInputConsumer(PmInternal *midi) :
18 BMidiLocalConsumer("PortMidi input consumer"),
19 midi(midi)
20 {
21 }
22
23
24 void Data(uchar *data, size_t length, bool atomic, bigtime_t time)
25 {
26 if (!atomic)
27 return; // should these be also supported?
28
29 if (data[0] == B_SYS_EX_START) {
30 pm_read_bytes(midi, data, length, time / 1000);
31 } else {
32 PmEvent event;
33 switch (length) {
34 case 1:
35 event.message = Pm_Message(data[0], 0, 0);
36 break;
37 case 2:
38 event.message = Pm_Message(data[0], data[1], 0);
39 break;
40 case 3:
41 event.message = Pm_Message(data[0], data[1], data[2]);
42 break;
43 default:
44 printf("Unexpected message length for short message, got %" B_PRIuSIZE "\n", length);
45 break;
46 }
47 event.timestamp = time / 1000;
48 pm_read_short(midi, &event);
49 }
50 }
51
52 private:
53 PmInternal *midi;
54 };
55
56 struct PmOutputInfo {
57 BMidiLocalProducer *producer;
58 std::vector<unsigned char> sysexBuffer;
59 };
60
61 struct PmSynthOutputInfo {
62 BMidiSynth midiSynth;
63 std::vector<unsigned char> sysexBuffer;
64 };
65
66
67 PmTimestamp synchronize(PmInternal *midi)
68 {
69 return 0;
70 }
71
72
73 PmError in_open(PmInternal *midi, void *driverInfo)
74 {
75 int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
76 BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
77 if (!producer)
78 return pmInvalidDeviceId;
79 PmInputConsumer *consumer = new PmInputConsumer(midi);
80 status_t status = producer->Connect(consumer);
81 if (status != B_OK) {
82 consumer->Release();
83 producer->Release();
84 strcpy(pm_hosterror_text, strerror(status));
85 pm_hosterror = TRUE;
86 return pmHostError;
87 }
88 midi->api_info = consumer;
89 producer->Release();
90 return pmNoError;
91 }
92
93
94 PmError in_abort(PmInternal *midi)
95 {
96 return pmNoError;
97 }
98
99
100 PmError in_close(PmInternal *midi)
101 {
102 int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
103 BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
104 if (!producer)
105 return pmInvalidDeviceId;
106 PmInputConsumer *consumer = (PmInputConsumer*)midi->api_info;
107 status_t status = producer->Disconnect(consumer);
108 if (status != B_OK) {
109 consumer->Release();
110 producer->Release();
111 strcpy(pm_hosterror_text, strerror(status));
112 pm_hosterror = TRUE;
113 return pmHostError;
114 }
115 consumer->Release();
116 midi->api_info = NULL;
117 producer->Release();
118 return pmNoError;
119 }
120
121
122 PmError out_open(PmInternal *midi, void *driverInfo)
123 {
124 int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
125 BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
126 if (!consumer)
127 return pmInvalidDeviceId;
128 BMidiLocalProducer *producer = new BMidiLocalProducer("PortMidi output producer");
129 status_t status = producer->Connect(consumer);
130 if (status != B_OK) {
131 consumer->Release();
132 producer->Release();
133 strcpy(pm_hosterror_text, strerror(status));
134 pm_hosterror = TRUE;
135 return pmHostError;
136 }
137 PmOutputInfo *info = new PmOutputInfo;
138 info->producer = producer;
139 midi->api_info = info;
140 consumer->Release();
141 return pmNoError;
142 }
143
144
145 PmError out_abort(PmInternal *midi)
146 {
147 return pmNoError;
148 }
149
150
151 PmError out_close(PmInternal *midi)
152 {
153 int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
154 BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
155 if (!consumer)
156 return pmInvalidDeviceId;
157 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
158 status_t status = info->producer->Disconnect(consumer);
159 if (status != B_OK) {
160 consumer->Release();
161 midi->api_info = NULL;
162 info->producer->Release();
163 delete info;
164 strcpy(pm_hosterror_text, strerror(status));
165 pm_hosterror = TRUE;
166 return pmHostError;
167 }
168 consumer->Release();
169 midi->api_info = NULL;
170 info->producer->Release();
171 delete info;
172 return pmNoError;
173 }
174
175
176 PmError write_short(PmInternal *midi, PmEvent *buffer)
177 {
178 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
179 uchar data[3];
180 data[0] = Pm_MessageStatus(buffer->message);
181 data[1] = Pm_MessageData1(buffer->message);
182 data[2] = Pm_MessageData2(buffer->message);
183 size_t length = pm_midi_length(data[0]);
184
185 info->producer->SprayData(data, length, true, buffer->timestamp * 1000);
186
187 // TODO: handle latency != 0
188 return pmNoError;
189 }
190
191
192 PmError begin_sysex(PmInternal *midi, PmTimestamp timestamp)
193 {
194 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
195 info->sysexBuffer.clear();
196 return pmNoError;
197 }
198
199
200 PmError end_sysex(PmInternal *midi, PmTimestamp timestamp)
201 {
202 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
203 info->producer->SpraySystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp * 1000);
204 info->sysexBuffer.clear();
205 return pmNoError;
206 }
207
208
209 PmError write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
210 {
211 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
212 info->sysexBuffer.push_back(byte);
213 return pmNoError;
214 }
215
216
217 PmError write_realtime(PmInternal *midi, PmEvent *buffer)
218 {
219 PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
220 info->producer->SpraySystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp * 1000);
221 return pmNoError;
222 }
223
224
225 PmError synth_open(PmInternal *midi, void *driverInfo)
226 {
227 PmSynthOutputInfo *info = new PmSynthOutputInfo;
228 info->midiSynth.EnableInput(true, true);
229 midi->api_info = info;
230 return pmNoError;
231 }
232
233
234 PmError synth_abort(PmInternal *midi)
235 {
236 return pmNoError;
237 }
238
239
240 PmError synth_close(PmInternal *midi)
241 {
242 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
243 delete info;
244 midi->api_info = NULL;
245 return pmNoError;
246 }
247
248
249 PmError write_short_synth(PmInternal *midi, PmEvent *buffer)
250 {
251 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
252 uchar data[3];
253 data[0] = Pm_MessageStatus(buffer->message);
254 data[1] = Pm_MessageData1(buffer->message);
255 data[2] = Pm_MessageData2(buffer->message);
256
257 switch(data[0] & 0xf0) {
258 case B_NOTE_OFF:
259 info->midiSynth.NoteOff((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
260 break;
261 case B_NOTE_ON:
262 info->midiSynth.NoteOn((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
263 break;
264 case B_KEY_PRESSURE:
265 info->midiSynth.KeyPressure((data[0] & 0x0f + 1), data[1], data[2], buffer->timestamp);
266 break;
267 case B_CONTROL_CHANGE:
268 info->midiSynth.ControlChange((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
269 break;
270 case B_PROGRAM_CHANGE:
271 info->midiSynth.ProgramChange((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
272 break;
273 case B_CHANNEL_PRESSURE:
274 info->midiSynth.ChannelPressure((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
275 break;
276 case B_PITCH_BEND:
277 info->midiSynth.PitchBend((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
278 break;
279 }
280
281 // TODO: handle latency != 0
282 return pmNoError;
283 }
284
285
286 PmError begin_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
287 {
288 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
289 info->sysexBuffer.clear();
290 return pmNoError;
291 }
292
293
294 PmError end_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
295 {
296 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
297 info->midiSynth.SystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp);
298 info->sysexBuffer.clear();
299 return pmNoError;
300 }
301
302
303 PmError write_byte_synth(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
304 {
305 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
306 info->sysexBuffer.push_back(byte);
307 return pmNoError;
308 }
309
310
311 PmError write_realtime_synth(PmInternal *midi, PmEvent *buffer)
312 {
313 PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
314 info->midiSynth.SystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp);
315 return pmNoError;
316 }
317
318
319 PmError write_flush(PmInternal *midi, PmTimestamp timestamp)
320 {
321 return pmNoError;
322 }
323
324
325 unsigned int check_host_error(PmInternal *midi)
326 {
327 return 0;
328 }
329
330
331 pm_fns_node pm_in_dictionary = {
332 none_write_short,
333 none_sysex,
334 none_sysex,
335 none_write_byte,
336 none_write_short,
337 none_write_flush,
338 synchronize,
339 in_open,
340 in_abort,
341 in_close,
342 success_poll,
343 check_host_error
344 };
345
346 pm_fns_node pm_out_dictionary = {
347 write_short,
348 begin_sysex,
349 end_sysex,
350 write_byte,
351 write_realtime,
352 write_flush,
353 synchronize,
354 out_open,
355 out_abort,
356 out_close,
357 none_poll,
358 check_host_error
359 };
360
361
362 pm_fns_node pm_synth_dictionary = {
363 write_short_synth,
364 begin_sysex_synth,
365 end_sysex_synth,
366 write_byte_synth,
367 write_realtime_synth,
368 write_flush,
369 synchronize,
370 synth_open,
371 synth_abort,
372 synth_close,
373 none_poll,
374 check_host_error
375 };
376
377
378 PmError create_virtual(int is_input, const char *name, void *driverInfo)
379 {
380 BMidiEndpoint *endpoint = is_input ? (BMidiEndpoint*)new BMidiLocalProducer(name) : new BMidiLocalConsumer(name);
381 if (!endpoint->IsValid()) {
382 endpoint->Release();
383 strcpy(pm_hosterror_text, "Endpoint could not be created");
384 pm_hosterror = TRUE;
385 return pmHostError;
386 }
387 status_t status = endpoint->Register();
388 if (status != B_OK) {
389 endpoint->Release();
390 strcpy(pm_hosterror_text, strerror(status));
391 pm_hosterror = TRUE;
392 return pmHostError;
393 }
394 return pm_add_device(const_cast<char*>("Haiku MIDI kit"), name, is_input, TRUE, (void*)(intptr_t)endpoint->ID(), is_input ? &pm_in_dictionary : &pm_out_dictionary);
395 }
396
397 PmError delete_virtual(PmDeviceID id)
398 {
399 int32 endpointID = (int32)(intptr_t)pm_descriptors[id].descriptor;
400 BMidiEndpoint *endpoint = BMidiRoster::FindEndpoint(endpointID);
401 //TODO: handle connected producers and consumers
402 status_t status = endpoint->Unregister();
403 // release twice to actually free the endpoint (FindEndpoint increases the ref-count)
404 endpoint->Release();
405 endpoint->Release();
406 if (status != B_OK) {
407 strcpy(pm_hosterror_text, strerror(status));
408 pm_hosterror = TRUE;
409 return pmHostError;
410 }
411 return pmNoError;
412 }
413}
414
415extern "C" {
416 void pm_init()
417 {
418 pm_add_interf(const_cast<char*>("Haiku MIDI kit"), create_virtual, delete_virtual);
419
420 pm_add_device(const_cast<char*>("Haiku MIDI kit"), "Soft Synth", FALSE, FALSE, NULL, &pm_synth_dictionary);
421
422 int32 id = 0;
423 BMidiEndpoint *endpoint;
424
425 while ((endpoint = BMidiRoster::NextEndpoint(&id)) != NULL) {
426 bool isInput = endpoint->IsProducer();
427 pm_add_device(const_cast<char*>("Haiku MIDI kit"), endpoint->Name(), isInput, FALSE, (void*)(intptr_t)id, isInput ? &pm_in_dictionary : &pm_out_dictionary);
428 endpoint->Release();
429 }
430 }
431
432
433 void pm_term()
434 {
435 int i;
436 for (i = 0; i < pm_descriptor_len; i++) {
437 PmInternal *midi = pm_descriptors[i].pm_internal;
438 if (midi && midi->api_info) {
439 // device is still open, close it
440 (*midi->dictionary->close)(midi);
441 }
442 if (pm_descriptors[i].pub.is_virtual && !pm_descriptors[i].deleted) {
443 delete_virtual(i);
444 }
445 }
446 }
447
448
449 PmDeviceID Pm_GetDefaultInputDeviceID()
450 {
451 Pm_Initialize();
452 return pm_default_input_device_id;
453 }
454
455
456 PmDeviceID Pm_GetDefaultOutputDeviceID()
457 {
458 Pm_Initialize();
459 return pm_default_output_device_id;
460 }
461
462
463 void *pm_alloc(size_t s)
464 {
465 return malloc(s);
466 }
467
468
469 void pm_free(void *ptr)
470 {
471 free(ptr);
472 }
473}
diff --git a/portmidi/pm_java/CMakeLists.txt b/portmidi/pm_java/CMakeLists.txt
new file mode 100644
index 0000000..55a20f4
--- /dev/null
+++ b/portmidi/pm_java/CMakeLists.txt
@@ -0,0 +1,56 @@
1# pm_java/CMakeLists.txt -- builds pmjni
2
3find_package(Java)
4message(STATUS "Java_JAVA_EXECUTABLE is " ${Java_JAVA_EXECUTABLE})
5
6# Build pmjni
7# this CMakeLists.txt is only loaded if BUILD_JAVA_NATIVE_INTERFACE
8# This jni library includes portmidi sources to give just
9# one library for JPortMidi users to manage rather than two.
10if(UNIX)
11 include(FindJNI)
12 # message(STATUS "JAVA_JVM_LIB_PATH is " ${JAVA_JVM_LIB_PATH})
13 # message(STATUS "JAVA_INCLUDE_PATH is " ${JAVA_INCLUDE_PATH})
14 # note: should use JAVA_JVM_LIB_PATH, but it is not set properly
15 # note: user might need to set JAVA_INCLUDE_PATH manually
16 #
17 # this will probably break on BSD and other Unix systems; the fix
18 # depends on whether FindJNI can find Java or not. If yes, then
19 # we should try to rely on automatically set JAVA_INCLUDE_PATH and
20 # JAVA_INCLUDE_PATH2; if no, then we need to make both JAVA_INCLUDE_PATH
21 # and JAVA_INCLUDE_PATH2 set by user (will need clear documentation
22 # because JAVA_INCLUDE_PATH2 is pretty obscure)
23 set(JAVA_INCLUDE_PATH ${JAVA_INCLUDE_PATH-UNKNOWN}
24 CACHE STRING "where to find Java SDK include directory")
25 # libjvm.so is found relative to JAVA_INCLUDE_PATH:
26 if (HAIKU)
27 set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/haiku)
28 else()
29 set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/linux)
30 endif()
31elseif(WIN32)
32 include(FindJNI)
33 # note: should use JAVA_JVM_LIB_PATH, but it is not set properly
34 set(JAVAVM_LIB ${JAVA_INCLUDE_PATH}/../lib/jvm.lib)
35
36 set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
37 # message(STATUS "JAVA_INCLUDE_PATHS: " ${JAVA_INCLUDE_PATHS})
38 # message(STATUS "JAVAVM_LIB: " ${JAVAVM_LIB})
39endif()
40
41add_library(pmjni SHARED pmjni/pmjni.c)
42target_sources(pmjni PRIVATE ${PM_LIB_PUBLIC_SRC} ${PM_LIB_PRIVATE_SRC})
43message(STATUS "Java paths ${JAVA_INCLUDE_PATHS}")
44# message(STATUS "Java pmjni src: pmjni/pmjni.c ${PM_LIB_SHARED_SRC} "
45# "${PM_LIB_PRIVATE_SRC}")
46target_include_directories(pmjni PUBLIC ${JAVA_INCLUDE_PATHS})
47target_link_libraries(pmjni ${PM_NEEDED_LIBS})
48set_target_properties(pmjni PROPERTIES
49 VERSION ${LIBRARY_VERSION}
50 SOVERSION ${LIBRARY_SOVERSION}
51 LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
52 RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
53 ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
54 EXECUTABLE_EXTENSION "jnilib"
55 MACOSX_RPATH ON)
56
diff --git a/portmidi/pm_java/README.txt b/portmidi/pm_java/README.txt
new file mode 100644
index 0000000..d1e5ad5
--- /dev/null
+++ b/portmidi/pm_java/README.txt
@@ -0,0 +1,62 @@
1README.txt
2Roger B. Dannenberg
316 Jun 2009
4updated 2021
5
6This directory implements a JNI library so that Java programs can use
7the PortMidi API. This was mainly created to implement PmDefaults, a
8program to set default input and output devices for PortMidi
9applications. Because it is rarely used, PmDefaults was dropped from
10PortMidi starting with v3. I recommend you implement per-application
11preferences and store default PortMidi device numbers for input and
12output there. (Or better yet, store device *names* since numbers can
13change if you plug in or remove USB devices.)
14
15Even without PmDefaults, a PortMidi API for Java is probably an
16improvement over other Java libraries, but there is very little MIDI
17development in Java, so I have not maintained this API. The only thing
18probably seriously wrong now is an interface to the
19Pm_CreateVirtualInput and Pm_CreateVirtualOutput functions, which are
20new additions.
21
22I will leave the code here, and if there is a demand, please either
23update it or let your needs be known. Perhaps I or someone can help.
24
25==================================================================
26
27BUILDING Java EXTERNAL LIBRARY
28
29You must have a JDK installed (Java development kit including javac
30(the Java compiler), jni.h, etc.
31
32Test java on the command line, e.g., type: javac -version
33
34Enable these options in the main CMakeLists.txt file (run CMake
35from your top-level repository directory):
36 BUILD_JAVA_NATIVE_INTERFACE
37In my Ubuntu linux with jdk-15, ccmake was unable to find my JDK, so
38I have to manually set CMake variables as follows (type 't' to see
39these in ccmake):
40 JAVA_AWT_INCLUDE_PATH /usr/lib/jvm/jdk-15/include
41 JAVA_AWT_LIBRARY /usr/lib/jvm/jdk-15/lib
42 JAVA_INCLUDE_PATH /usr/lib/jvm/jdk-15/include
43 JAVA_INCLUDE_PATH2 /usr/lib/jvm/jdk-15/include
44 JAVA_JVM_LIBRARY /usr/lib/jvm/jdk-15/lib
45Of course, your paths may differ.
46
47
48---- old implementation notes ----
49
50For Windows, we use the free software JavaExe.exe. The copy here was
51downloaded from
52
53http://software.techrepublic.com.com/abstract.aspx?kw=javaexe&docid=767485
54
55I found this page by visiting http://software.techrepublic.com.com and
56searching in the "Software" category for "JavaExe"
57
58JavaExe works by placing the JavaExe.exe file in the directory with the
59Java application jar file and then *renaming* JavaExe.exe to the name
60of the jar file, but keeping the .exe extension. (See make.bat for this
61step.) Documentation for JavaExe can be obtained by downloading the
62whole program from the URL(s) above.
diff --git a/portmidi/pm_java/jportmidi/JPortMidi.java b/portmidi/pm_java/jportmidi/JPortMidi.java
new file mode 100644
index 0000000..7116e19
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidi.java
@@ -0,0 +1,541 @@
1package jportmidi;
2
3/* PortMidi is a general class intended for any Java program using
4 the PortMidi library. It encapsulates JPortMidiApi with a more
5 object-oriented interface. A single PortMidi object can manage
6 up to one input stream and one output stream.
7
8 This class is not safely callable from multiple threads. It
9 is the client's responsibility to periodically call the Poll
10 method which checks for midi input and handles it.
11*/
12
13import jportmidi.*;
14import jportmidi.JPortMidiApi.*;
15
16public class JPortMidi {
17
18 // timecode to send message immediately
19 public final int NOW = 0;
20
21 // midi codes
22 public final int MIDI_NOTE_OFF = 0x80;
23 public final int MIDI_NOTE_ON = 0x90;
24 public final int CTRL_ALL_OFF = 123;
25 public final int MIDI_PITCH_BEND = 0xE0;
26 public final int MIDI_CLOCK = 0xF8;
27 public final int MIDI_CONTROL = 0xB0;
28 public final int MIDI_PROGRAM = 0xC0;
29 public final int MIDI_START = 0xFA;
30 public final int MIDI_STOP = 0xFC;
31 public final int MIDI_POLY_TOUCH = 0xA0;
32 public final int MIDI_TOUCH = 0xD0;
33
34 // error code -- cannot refresh device list while stream is open:
35 public final int pmStreamOpen = -5000;
36 public final int pmOutputNotOpen = -4999;
37
38 // access to JPortMidiApi is through a single, global instance
39 private static JPortMidiApi pm;
40 // a reference count tracks how many objects have it open
41 private static int pmRefCount = 0;
42 private static int openCount = 0;
43
44 public int error; // user can check here for error codes
45 private PortMidiStream input;
46 private PortMidiStream output;
47 private PmEvent buffer;
48 protected int timestamp; // remember timestamp from incoming messages
49 protected boolean trace = false; // used to print midi msgs for debugging
50
51
52 public JPortMidi() throws JPortMidiException {
53 if (pmRefCount == 0) {
54 pm = new JPortMidiApi();
55 pmRefCount++;
56 System.out.println("Calling Pm_Initialize");
57 checkError(pm.Pm_Initialize());
58 System.out.println("Called Pm_Initialize");
59 }
60 buffer = new PmEvent();
61 }
62
63 public boolean getTrace() { return trace; }
64
65 // set the trace flag and return previous value
66 public boolean setTrace(boolean flag) {
67 boolean previous = trace;
68 trace = flag;
69 return previous;
70 }
71
72 // WARNING: you must not call this if any devices are open
73 public void refreshDeviceLists()
74 throws JPortMidiException
75 {
76 if (openCount > 0) {
77 throw new JPortMidiException(pmStreamOpen,
78 "RefreshDeviceLists called while stream is open");
79 }
80 if (trace) System.out.println("Pm_Terminate");
81 checkError(pm.Pm_Terminate());
82 if (trace) System.out.println("Pm_Initialize");
83 checkError(pm.Pm_Initialize());
84 }
85
86 // there is no control over when/whether this is called, but it seems
87 // to be a good idea to close things when this object is collected
88 public void finalize() {
89 if (input != null) {
90 error = pm.Pm_Close(input);
91 }
92 if (input != null) {
93 int rslt = pm.Pm_Close(output);
94 // we may lose an error code from closing output, but don't
95 // lose any real error from closing input...
96 if (error == pm.pmNoError) error = rslt;
97 }
98 pmRefCount--;
99 if (pmRefCount == 0) {
100 error = pm.Pm_Terminate();
101 }
102 }
103
104 int checkError(int err) throws JPortMidiException
105 {
106 // note that Pm_Read and Pm_Write return positive result values
107 // which are not errors, so compare with >=
108 if (err >= pm.pmNoError) return err;
109 if (err == pm.pmHostError) {
110 throw new JPortMidiException(err, pm.Pm_GetHostErrorText());
111 } else {
112 throw new JPortMidiException(err, pm.Pm_GetErrorText(err));
113 }
114 }
115
116 // ******** ACCESS TO TIME ***********
117
118 public void timeStart(int resolution) throws JPortMidiException {
119 checkError(pm.Pt_TimeStart(resolution));
120 }
121
122 public void timeStop() throws JPortMidiException {
123 checkError(pm.Pt_TimeStop());
124 }
125
126 public int timeGet() {
127 return pm.Pt_Time();
128 }
129
130 public boolean timeStarted() {
131 return pm.Pt_TimeStarted();
132 }
133
134 // ******* QUERY DEVICE INFORMATION *********
135
136 public int countDevices() throws JPortMidiException {
137 return checkError(pm.Pm_CountDevices());
138 }
139
140 public int getDefaultInputDeviceID() throws JPortMidiException {
141 return checkError(pm.Pm_GetDefaultInputDeviceID());
142 }
143
144 public int getDefaultOutputDeviceID() throws JPortMidiException {
145 return checkError(pm.Pm_GetDefaultOutputDeviceID());
146 }
147
148 public String getDeviceInterf(int i) {
149 return pm.Pm_GetDeviceInterf(i);
150 }
151
152 public String getDeviceName(int i) {
153 return pm.Pm_GetDeviceName(i);
154 }
155
156 public boolean getDeviceInput(int i) {
157 return pm.Pm_GetDeviceInput(i);
158 }
159
160 public boolean getDeviceOutput(int i) {
161 return pm.Pm_GetDeviceOutput(i);
162 }
163
164 // ********** MIDI INTERFACE ************
165
166 public boolean isOpenInput() {
167 return input != null;
168 }
169
170 public void openInput(int inputDevice, int bufferSize)
171 throws JPortMidiException
172 {
173 openInput(inputDevice, "", bufferSize);
174 }
175
176 public void openInput(int inputDevice, String inputDriverInfo, int bufferSize)
177 throws JPortMidiException
178 {
179 if (isOpenInput()) pm.Pm_Close(input);
180 else input = new PortMidiStream();
181 if (trace) {
182 System.out.println("openInput " + getDeviceName(inputDevice));
183 }
184 checkError(pm.Pm_OpenInput(input, inputDevice,
185 inputDriverInfo, bufferSize));
186 // if no exception, then increase count of open streams
187 openCount++;
188 }
189
190 public boolean isOpenOutput() {
191 return output != null;
192 }
193
194 public void openOutput(int outputDevice, int bufferSize, int latency)
195 throws JPortMidiException
196 {
197 openOutput(outputDevice, "", bufferSize, latency);
198 }
199
200 public void openOutput(int outputDevice, String outputDriverInfo,
201 int bufferSize, int latency) throws JPortMidiException {
202 if (isOpenOutput()) pm.Pm_Close(output);
203 else output = new PortMidiStream();
204 if (trace) {
205 System.out.println("openOutput " + getDeviceName(outputDevice));
206 }
207 checkError(pm.Pm_OpenOutput(output, outputDevice, outputDriverInfo,
208 bufferSize, latency));
209 // if no exception, then increase count of open streams
210 openCount++;
211 }
212
213 public void setFilter(int filters) throws JPortMidiException {
214 if (input == null) return; // no effect if input not open
215 checkError(pm.Pm_SetFilter(input, filters));
216 }
217
218 public void setChannelMask(int mask) throws JPortMidiException {
219 if (input == null) return; // no effect if input not open
220 checkError(pm.Pm_SetChannelMask(input, mask));
221 }
222
223 public void abort() throws JPortMidiException {
224 if (output == null) return; // no effect if output not open
225 checkError(pm.Pm_Abort(output));
226 }
227
228 // In keeping with the idea that this class represents an input and output,
229 // there are separate Close methods for input and output streams, avoiding
230 // the need for clients to ever deal directly with a stream object
231 public void closeInput() throws JPortMidiException {
232 if (input == null) return; // no effect if input not open
233 checkError(pm.Pm_Close(input));
234 input = null;
235 openCount--;
236 }
237
238 public void closeOutput() throws JPortMidiException {
239 if (output == null) return; // no effect if output not open
240 checkError(pm.Pm_Close(output));
241 output = null;
242 openCount--;
243 }
244
245 // Poll should be called by client to process input messages (if any)
246 public void poll() throws JPortMidiException {
247 if (input == null) return; // does nothing until input is opened
248 while (true) {
249 int rslt = pm.Pm_Read(input, buffer);
250 checkError(rslt);
251 if (rslt == 0) return; // no more messages
252 handleMidiIn(buffer);
253 }
254 }
255
256 public void writeShort(int when, int msg) throws JPortMidiException {
257 if (output == null)
258 throw new JPortMidiException(pmOutputNotOpen,
259 "Output stream not open");
260 if (trace) {
261 System.out.println("writeShort: " + Integer.toHexString(msg));
262 }
263 checkError(pm.Pm_WriteShort(output, when, msg));
264 }
265
266 public void writeSysEx(int when, byte msg[]) throws JPortMidiException {
267 if (output == null)
268 throw new JPortMidiException(pmOutputNotOpen,
269 "Output stream not open");
270 if (trace) {
271 System.out.print("writeSysEx: ");
272 for (int i = 0; i < msg.length; i++) {
273 System.out.print(Integer.toHexString(msg[i]));
274 }
275 System.out.print("\n");
276 }
277 checkError(pm.Pm_WriteSysEx(output, when, msg));
278 }
279
280 public int midiChanMessage(int chan, int status, int data1, int data2) {
281 return (((data2 << 16) & 0xFF0000) |
282 ((data1 << 8) & 0xFF00) |
283 (status & 0xF0) |
284 (chan & 0xF));
285 }
286
287 public int midiMessage(int status, int data1, int data2) {
288 return ((((data2) << 16) & 0xFF0000) |
289 (((data1) << 8) & 0xFF00) |
290 ((status) & 0xFF));
291 }
292
293 public void midiAllOff(int channel) throws JPortMidiException {
294 midiAllOff(channel, NOW);
295 }
296
297 public void midiAllOff(int chan, int when) throws JPortMidiException {
298 writeShort(when, midiChanMessage(chan, MIDI_CONTROL, CTRL_ALL_OFF, 0));
299 }
300
301 public void midiPitchBend(int chan, int value) throws JPortMidiException {
302 midiPitchBend(chan, value, NOW);
303 }
304
305 public void midiPitchBend(int chan, int value, int when)
306 throws JPortMidiException {
307 writeShort(when,
308 midiChanMessage(chan, MIDI_PITCH_BEND, value, value >> 7));
309 }
310
311 public void midiClock() throws JPortMidiException {
312 midiClock(NOW);
313 }
314
315 public void midiClock(int when) throws JPortMidiException {
316 writeShort(when, midiMessage(MIDI_CLOCK, 0, 0));
317 }
318
319 public void midiControl(int chan, int control, int value)
320 throws JPortMidiException {
321 midiControl(chan, control, value, NOW);
322 }
323
324 public void midiControl(int chan, int control, int value, int when)
325 throws JPortMidiException {
326 writeShort(when, midiChanMessage(chan, MIDI_CONTROL, control, value));
327 }
328
329 public void midiNote(int chan, int pitch, int vel)
330 throws JPortMidiException {
331 midiNote(chan, pitch, vel, NOW);
332 }
333
334 public void midiNote(int chan, int pitch, int vel, int when)
335 throws JPortMidiException {
336 writeShort(when, midiChanMessage(chan, MIDI_NOTE_ON, pitch, vel));
337 }
338
339 public void midiProgram(int chan, int program)
340 throws JPortMidiException {
341 midiProgram(chan, program, NOW);
342 }
343
344 public void midiProgram(int chan, int program, int when)
345 throws JPortMidiException {
346 writeShort(when, midiChanMessage(chan, MIDI_PROGRAM, program, 0));
347 }
348
349 public void midiStart()
350 throws JPortMidiException {
351 midiStart(NOW);
352 }
353
354 public void midiStart(int when)
355 throws JPortMidiException {
356 writeShort(when, midiMessage(MIDI_START, 0, 0));
357 }
358
359 public void midiStop()
360 throws JPortMidiException {
361 midiStop(NOW);
362 }
363
364 public void midiStop(int when)
365 throws JPortMidiException {
366 writeShort(when, midiMessage(MIDI_STOP, 0, 0));
367 }
368
369 public void midiPolyTouch(int chan, int key, int value)
370 throws JPortMidiException {
371 midiPolyTouch(chan, key, value, NOW);
372 }
373
374 public void midiPolyTouch(int chan, int key, int value, int when)
375 throws JPortMidiException {
376 writeShort(when, midiChanMessage(chan, MIDI_POLY_TOUCH, key, value));
377 }
378
379 public void midiTouch(int chan, int value)
380 throws JPortMidiException {
381 midiTouch(chan, value, NOW);
382 }
383
384 public void midiTouch(int chan, int value, int when)
385 throws JPortMidiException {
386 writeShort(when, midiChanMessage(chan, MIDI_TOUCH, value, 0));
387 }
388
389 // ****** now we implement the message handlers ******
390
391 // an array for incoming sysex messages that can grow.
392 // The downside is that after getting a message, we
393
394 private byte sysexBuffer[] = null;
395 private int sysexBufferIndex = 0;
396
397 void sysexBufferReset() {
398 sysexBufferIndex = 0;
399 if (sysexBuffer == null) sysexBuffer = new byte[256];
400 }
401
402 void sysexBufferCheck() {
403 if (sysexBuffer.length < sysexBufferIndex + 4) {
404 byte bigger[] = new byte[sysexBuffer.length * 2];
405 for (int i = 0; i < sysexBufferIndex; i++) {
406 bigger[i] = sysexBuffer[i];
407 }
408 sysexBuffer = bigger;
409 }
410 // now we have space to write some bytes
411 }
412
413 // call this to insert Sysex and EOX status bytes
414 // call sysexBufferAppendBytes to insert anything else
415 void sysexBufferAppendStatus(byte status) {
416 sysexBuffer[sysexBufferIndex++] = status;
417 }
418
419 void sysexBufferAppendBytes(int msg, int len) {
420 for (int i = 0; i < len; i++) {
421 byte b = (byte) msg;
422 if ((msg & 0x80) != 0) {
423 if (b == 0xF7) { // end of sysex
424 sysexBufferAppendStatus(b);
425 sysex(sysexBuffer, sysexBufferIndex);
426 return;
427 }
428 // recursively handle embedded real-time messages
429 PmEvent buffer = new PmEvent();
430 buffer.timestamp = timestamp;
431 buffer.message = b;
432 handleMidiIn(buffer);
433 } else {
434 sysexBuffer[sysexBufferIndex++] = b;
435 }
436 msg = msg >> 8;
437 }
438 }
439
440 void sysexBegin(int msg) {
441 sysexBufferReset(); // start from 0, we have at least 256 bytes now
442 sysexBufferAppendStatus((byte) (msg & 0xFF)); // first byte is special
443 sysexBufferAppendBytes(msg >> 8, 3); // process remaining bytes
444 }
445
446 public void handleMidiIn(PmEvent buffer)
447 {
448 if (trace) {
449 System.out.println("handleMidiIn: " +
450 Integer.toHexString(buffer.message));
451 }
452 // rather than pass timestamps to every handler, where typically
453 // timestamps are ignored, just save the timestamp as a member
454 // variable where methods can access it if they want it
455 timestamp = buffer.timestamp;
456 int status = buffer.message & 0xFF;
457 if (status < 0x80) {
458 sysexBufferCheck(); // make enough space
459 sysexBufferAppendBytes(buffer.message, 4); // process 4 bytes
460 return;
461 }
462 int command = status & 0xF0;
463 int channel = status & 0x0F;
464 int data1 = (buffer.message >> 8) & 0xFF;
465 int data2 = (buffer.message >> 16) & 0xFF;
466 switch (command) {
467 case MIDI_NOTE_OFF:
468 noteOff(channel, data1, data2); break;
469 case MIDI_NOTE_ON:
470 if (data2 > 0) {
471 noteOn(channel, data1, data2); break;
472 } else {
473 noteOff(channel, data1);
474 }
475 break;
476 case MIDI_CONTROL:
477 control(channel, data1, data2); break;
478 case MIDI_POLY_TOUCH:
479 polyTouch(channel, data1, data2); break;
480 case MIDI_TOUCH:
481 touch(channel, data1); break;
482 case MIDI_PITCH_BEND:
483 pitchBend(channel, (data1 + (data2 << 7)) - 8192); break;
484 case MIDI_PROGRAM:
485 program(channel, data1); break;
486 case 0xF0:
487 switch (channel) {
488 case 0: sysexBegin(buffer.message); break;
489 case 1: mtcQuarterFrame(data1);
490 case 2: songPosition(data1 + (data2 << 7)); break;
491 case 3: songSelect(data1); break;
492 case 4: /* unused */ break;
493 case 5: /* unused */ break;
494 case 6: tuneRequest(); break;
495 case 7: sysexBufferAppendBytes(buffer.message, buffer.message); break;
496 case 8: clock(); break;
497 case 9: tick(); break;
498 case 0xA: clockStart(); break;
499 case 0xB: clockContinue(); break;
500 case 0xC: clockStop(); break;
501 case 0xD: /* unused */ break;
502 case 0xE: activeSense(); break;
503 case 0xF: reset(); break;
504 }
505 }
506 }
507
508 // the value ranges from +8181 to -8192. The interpretation is
509 // synthesizer dependent. Often the range is +/- one whole step
510 // (two semitones), but the range is usually adjustable within
511 // the synthesizer.
512 void pitchBend(int channel, int value) { return; }
513 void control(int channel, int control, int value) { return; }
514 void noteOn(int channel, int pitch, int velocity) { return; }
515 // you can handle velocity in note-off if you want, but the default
516 // is to drop the velocity and call the simpler NoteOff handler
517 void noteOff(int channel, int pitch, int velocity) {
518 noteOff(channel, pitch);
519 }
520 // if the subclass wants to implement NoteOff with velocity, it
521 // should override the following to make sure all NoteOffs are handled
522 void noteOff(int channel, int pitch) { return; }
523 void program(int channel, int program) { return; }
524 // the byte array may be bigger than the message, length tells how
525 // many bytes in the array are part of the message
526 void sysex(byte[] msg, int length) { return; }
527 void polyTouch(int channel, int key, int value) { return; }
528 void touch(int channel, int value) { return; }
529 void mtcQuarterFrame(int value) { return; }
530 // the value is a 14-bit integer representing 16th notes
531 void songPosition(int value) { return; }
532 void songSelect(int value) { return; }
533 void tuneRequest() { return; }
534 void clock() { return; } // represents 1/24th of a quarter note
535 void tick() { return; } // represents 10ms
536 void clockStart() { return; }
537 void clockStop() { return; }
538 void clockContinue() { return; }
539 void activeSense() { return; }
540 void reset() { return; }
541}
diff --git a/portmidi/pm_java/jportmidi/JPortMidiApi.java b/portmidi/pm_java/jportmidi/JPortMidiApi.java
new file mode 100644
index 0000000..45dd9d9
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidiApi.java
@@ -0,0 +1,117 @@
1package jportmidi;
2
3public 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}
diff --git a/portmidi/pm_java/jportmidi/JPortMidiException.java b/portmidi/pm_java/jportmidi/JPortMidiException.java
new file mode 100644
index 0000000..9be8aaf
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidiException.java
@@ -0,0 +1,12 @@
1// JPortMidiException -- thrown by JPortMidi methods
2
3package jportmidi;
4
5public 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
diff --git a/portmidi/pm_java/make.bat b/portmidi/pm_java/make.bat
new file mode 100644
index 0000000..ff15c2b
--- /dev/null
+++ b/portmidi/pm_java/make.bat
@@ -0,0 +1,50 @@
1@echo off
2
3rem This is an out-of-date script for Windows to build a Java application
4rem (PmDefaults) with PortMidi external library.xb
5
6rem Compile the java PortMidi interface classes.
7javac jportmidi/*.java
8
9rem Compile the pmdefaults application.
10javac -classpath . pmdefaults/*.java
11
12rem Temporarily copy the portmusic_logo.png file here to add to the jar file.
13copy pmdefaults\portmusic_logo.png . > nul
14
15rem Create a directory to hold the distribution.
16mkdir win32
17
18rem Attempt to copy the interface DLL to the distribution directory.
19
20if exist "..\release\pmjni.dll" goto have-dll
21
22echo "ERROR: pmjni.dll not found!"
23exit /b 1
24
25:have-dll
26copy "..\release\pmjni.dll" win32\pmjni.dll > nul
27
28rem Create a java archive (jar) file of the distribution.
29jar cmf pmdefaults\manifest.txt win32\pmdefaults.jar pmdefaults\*.class portmusic_logo.png jportmidi\*.class
30
31rem Clean up the temporary image file now that it is in the jar file.
32del portmusic_logo.png
33
34rem Copy the java execution code obtained from
35rem http://devwizard.free.fr/html/en/JavaExe.html to the distribution
36rem directory. The copy also renames the file to our desired executable
37rem name.
38copy JavaExe.exe win32\pmdefaults.exe > nul
39
40rem Integrate the icon into the executable using UpdateRsrcJavaExe from
41rem http://devwizard.free.fr
42UpdateRsrcJavaExe -run -exe=win32\pmdefaults.exe -ico=pmdefaults\pmdefaults.ico
43
44rem Copy the 32-bit windows read me file to the distribution directory.
45copy pmdefaults\readme-win32.txt win32\README.txt > nul
46
47rem Copy the license file to the distribution directory.
48copy pmdefaults\pmdefaults-license.txt win32\license.txt > nul
49
50echo "You can run pmdefaults.exe in win32"
diff --git a/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h b/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h
new file mode 100644
index 0000000..2208be6
--- /dev/null
+++ b/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h
@@ -0,0 +1,293 @@
1/* DO NOT EDIT THIS FILE - it is machine generated */
2#include <jni.h>
3/* Header for class jportmidi_JPortMidiApi */
4
5#ifndef _Included_jportmidi_JPortMidiApi
6#define _Included_jportmidi_JPortMidiApi
7#ifdef __cplusplus
8extern "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 */
57JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize
58 (JNIEnv *, jclass);
59
60/*
61 * Class: jportmidi_JPortMidiApi
62 * Method: Pm_Terminate
63 * Signature: ()I
64 */
65JNIEXPORT 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 */
73JNIEXPORT 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 */
81JNIEXPORT 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 */
89JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText
90 (JNIEnv *, jclass);
91
92/*
93 * Class: jportmidi_JPortMidiApi
94 * Method: Pm_CountDevices
95 * Signature: ()I
96 */
97JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices
98 (JNIEnv *, jclass);
99
100/*
101 * Class: jportmidi_JPortMidiApi
102 * Method: Pm_GetDefaultInputDeviceID
103 * Signature: ()I
104 */
105JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID
106 (JNIEnv *, jclass);
107
108/*
109 * Class: jportmidi_JPortMidiApi
110 * Method: Pm_GetDefaultOutputDeviceID
111 * Signature: ()I
112 */
113JNIEXPORT 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 */
121JNIEXPORT 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 */
129JNIEXPORT 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 */
137JNIEXPORT 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 */
145JNIEXPORT 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 */
153JNIEXPORT 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 */
161JNIEXPORT 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 */
169JNIEXPORT 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 */
177JNIEXPORT 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 */
185JNIEXPORT 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 */
193JNIEXPORT 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 */
201JNIEXPORT 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 */
209JNIEXPORT 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 */
217JNIEXPORT 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 */
225JNIEXPORT 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 */
233JNIEXPORT 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 */
241JNIEXPORT 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 */
249JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop
250 (JNIEnv *, jclass);
251
252/*
253 * Class: jportmidi_JPortMidiApi
254 * Method: Pt_Time
255 * Signature: ()I
256 */
257JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time
258 (JNIEnv *, jclass);
259
260/*
261 * Class: jportmidi_JPortMidiApi
262 * Method: Pt_TimeStarted
263 * Signature: ()Z
264 */
265JNIEXPORT 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
277extern "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
288extern "C" {
289#endif
290#ifdef __cplusplus
291}
292#endif
293#endif
diff --git a/portmidi/pm_java/pmjni/pmjni.c b/portmidi/pm_java/pmjni/pmjni.c
new file mode 100644
index 0000000..c60cffb
--- /dev/null
+++ b/portmidi/pm_java/pmjni/pmjni.c
@@ -0,0 +1,354 @@
1#include "portmidi.h"
2#include "porttime.h"
3#include "jportmidi_JportMidiApi.h"
4#include <stdio.h>
5
6// these macros assume JNIEnv *env is declared and valid:
7//
8#define CLASS(c, obj) jclass c = (*env)->GetObjectClass(env, obj)
9#define ADDRESS_FID(fid, c) \
10 jfieldID fid = (*env)->GetFieldID(env, c, "address", "J")
11// Uses Java Long (64-bit) to make sure there is room to store a
12// pointer. Cast this to a C long (either 32 or 64 bit) to match
13// the size of a pointer. Finally cast int to pointer. All this
14// is supposed to avoid C compiler warnings and (worse) losing
15// address bits.
16#define PMSTREAM(obj, fid) ((PmStream *) (intptr_t) (*env)->GetLongField(env, obj, fid))
17// Cast stream to long to convert integer to pointer, then expand
18// integer to 64-bit jlong. This avoids compiler warnings.
19#define SET_PMSTREAM(obj, fid, stream) \
20 (*env)->SetLongField(env, obj, fid, (jlong) (intptr_t) stream)
21
22
23/*
24 * Method: Pm_Initialize
25 */
26JNIEXPORT 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 */
36JNIEXPORT 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 */
46JNIEXPORT 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 */
58JNIEXPORT 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 */
68JNIEXPORT 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 */
80JNIEXPORT 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 */
90JNIEXPORT 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 */
100JNIEXPORT 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 */
110JNIEXPORT 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 */
122JNIEXPORT 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 */
134JNIEXPORT 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 */
146JNIEXPORT 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 */
158JNIEXPORT 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 */
175JNIEXPORT 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 */
192JNIEXPORT 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 */
204JNIEXPORT 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 */
216JNIEXPORT 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 */
228JNIEXPORT 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 */
240JNIEXPORT 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 */
261JNIEXPORT 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 */
273JNIEXPORT 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 */
294JNIEXPORT 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 */
306JNIEXPORT 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 */
321JNIEXPORT 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 */
330JNIEXPORT 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 */
339JNIEXPORT 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 */
348JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted
349 (JNIEnv *env, jclass c)
350{
351 return Pt_Started();
352}
353
354
diff --git a/portmidi/pm_java/pmjni/pmjni.rc b/portmidi/pm_java/pmjni/pmjni.rc
new file mode 100644
index 0000000..1b7522b
--- /dev/null
+++ b/portmidi/pm_java/pmjni/pmjni.rc
@@ -0,0 +1,63 @@
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
20LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21#pragma code_page(1252)
22#endif //_WIN32
23
24#ifdef APSTUDIO_INVOKED
25/////////////////////////////////////////////////////////////////////////////
26//
27// TEXTINCLUDE
28//
29
301 TEXTINCLUDE
31BEGIN
32 "resource.h\0"
33END
34
352 TEXTINCLUDE
36BEGIN
37 "#include ""afxres.h""\r\n"
38 "\0"
39END
40
413 TEXTINCLUDE
42BEGIN
43 "\r\n"
44 "\0"
45END
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
diff --git a/portmidi/pm_linux/README_LINUX.txt b/portmidi/pm_linux/README_LINUX.txt
new file mode 100755
index 0000000..cfbc43f
--- /dev/null
+++ b/portmidi/pm_linux/README_LINUX.txt
@@ -0,0 +1,99 @@
1README_LINUX.txt for PortMidi
2Roger Dannenberg
36 Dec 2012, revised May 2022
4
5Contents:
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
15See ../README.md for general instructions.
16
17THE pmdefaults PROGRAM
18
19(This may be obsolete. It is older than `../README.md` which
20also discusses pmdefaults, and Java support may be removed
21unless someone claims they use it... -RBD)
22
23You should install pmdefaults. It provides a graphical interface
24for selecting default MIDI IN and OUT devices so that you don't
25have to build device selection interfaces into all your programs
26and so users have a single place to set a preference.
27
28Follow the instructions above to run ccmake, making sure that
29CMAKE_BUILD_TYPE is Release. Run make as described above. Then:
30
31sudo make install
32
33This will install PortMidi libraries and the pmdefault program.
34You must alos have the environment variable LD_LIBRARY_PATH set
35to include /usr/local/lib (where libpmjni.so is installed).
36
37Now, you can run pmdefault.
38
39
40SETTING LD_LIBRARY_PATH
41
42pmdefaults will not work unless LD_LIBRARY_PATH includes a
43directory (normally /usr/local/lib) containing libpmjni.so,
44installed as described above.
45
46To set LD_LIBRARY_PATH, you might want to add this to your
47~/.profile (if you use the bash shell):
48
49LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
50export LD_LIBRARY_PATH
51
52
53A NOTE ABOUT AMD64:
54
55When compiling portmidi under linux on an AMD64, I had to add the -fPIC
56flag to the gcc flags.
57
58Reason: when trying to build John Harrison's pyPortMidi gcc bailed out
59with this error:
60
61./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
62./linux/libportmidi.a: could not read symbols: Bad value
63collect2: ld returned 1 exit status
64error: command 'gcc' failed with exit status 1
65
66What they said:
67http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3
68On certain architectures (AMD64 amongst them), shared libraries *must*
69be "PIC-enabled".
70
71CHANGELOG
72
7327-may-2022 Roger B. Dannenberg
74 Some updates to this file.
75
766-dec-2012 Roger B. Dannenberg
77 Copied notes on Autoconf from Audacity sources
78
7922-jan-2010 Roger B. Dannenberg
80 Updated instructions about Java paths
81
8214-oct-2009 Roger B. Dannenberg
83 Using CMake now for building and configuration
84
8529-aug-2006 Roger B. Dannenberg
86 Fixed PortTime to join with time thread for clean exit.
87
8828-aug-2006 Roger B. Dannenberg
89 Updated this documentation.
90
9108-Jun-2004 Roger B. Dannenberg
92 Updated code to use new system abstraction.
93
9412-Apr-2003 Roger B. Dannenberg
95 Fixed pm_test/test.c to filter clocks and active messages.
96 Integrated changes from Clemens Ladisch:
97 cleaned up pmlinuxalsa.c
98 record timestamp on sysex input
99 deallocate some resources previously left open
diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c
new file mode 100755
index 0000000..3766427
--- /dev/null
+++ b/portmidi/pm_linux/pmlinux.c
@@ -0,0 +1,68 @@
1/* pmlinux.c -- PortMidi os-dependent code */
2
3/* This file only needs to implement pm_init(), which calls various
4 routines to register the available midi devices. This file must
5 be separate from the main portmidi.c file because it is system
6 dependent, and it is separate from, pmlinuxalsa.c, because it
7 might need to register non-alsa devices as well.
8
9 NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
10 in pmlinuxalsa.c, which assumes all input devices are ALSA.
11 */
12
13#include "stdlib.h"
14#include "portmidi.h"
15#include "pmutil.h"
16#include "pminternal.h"
17
18#ifdef PMALSA
19 #include "pmlinuxalsa.h"
20#endif
21
22#ifdef PMNULL
23 #include "pmlinuxnull.h"
24#endif
25
26#if !(defined(PMALSA) || defined(PMNULL))
27#error One of PMALSA or PMNULL must be defined
28#endif
29
30void pm_init()
31{
32 /* Note: it is not an error for PMALSA to fail to initialize.
33 * It may be a design error that the client cannot query what subsystems
34 * are working properly other than by looking at the list of available
35 * devices.
36 */
37#ifdef PMALSA
38 pm_linuxalsa_init();
39#endif
40#ifdef PMNULL
41 pm_linuxnull_init();
42#endif
43}
44
45void pm_term(void)
46{
47 #ifdef PMALSA
48 pm_linuxalsa_term();
49 #endif
50 #ifdef PMNULL
51 pm_linuxnull_term();
52 #endif
53}
54
55PmDeviceID Pm_GetDefaultInputDeviceID() {
56 Pm_Initialize();
57 return pm_default_input_device_id;
58}
59
60PmDeviceID Pm_GetDefaultOutputDeviceID() {
61 Pm_Initialize();
62 return pm_default_output_device_id;
63}
64
65void *pm_alloc(size_t s) { return malloc(s); }
66
67void pm_free(void *ptr) { free(ptr); }
68
diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c
new file mode 100755
index 0000000..b2e43e1
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.c
@@ -0,0 +1,893 @@
1/*
2 * pmlinuxalsa.c -- system specific definitions
3 *
4 * written by:
5 * Roger Dannenberg (port to Alsa 0.9.x)
6 * Clemens Ladisch (provided code examples and invaluable consulting)
7 * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
8 */
9
10/* omit this code if PMALSA is not defined -- this mechanism allows
11 * selection of different MIDI interfaces (at compile time).
12 */
13#ifdef PMALSA
14
15#include "stdlib.h"
16#include "portmidi.h"
17#include "pmutil.h"
18#include "pminternal.h"
19#include "pmlinuxalsa.h"
20#include "string.h"
21#include "porttime.h"
22
23#include <alsa/asoundlib.h>
24
25/* I used many print statements to debug this code. I left them in the
26 * source, and you can turn them on by changing false to true below:
27 */
28#define VERBOSE_ON 0
29#define VERBOSE if (VERBOSE_ON)
30
31#define MIDI_SYSEX 0xf0
32#define MIDI_EOX 0xf7
33
34#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
35#error needs ALSA 0.9.0 or later
36#endif
37
38/* to store client/port in the device descriptor */
39#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port)))
40#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)
41#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)
42
43#define BYTE unsigned char
44
45extern pm_fns_node pm_linuxalsa_in_dictionary;
46extern pm_fns_node pm_linuxalsa_out_dictionary;
47
48static snd_seq_t *seq = NULL; // all input comes here,
49 // output queue allocated on seq
50static int queue, queue_used; /* one for all ports, reference counted */
51
52#define PORT_IS_CLOSED -999999
53
54typedef struct alsa_info_struct {
55 int is_virtual;
56 int client;
57 int port;
58 int this_port;
59 int in_sysex;
60 snd_midi_event_t *parser;
61} alsa_info_node, *alsa_info_type;
62
63
64/* get_alsa_error_text -- copy error text to potentially short string */
65/**/
66static void get_alsa_error_text(char *msg, int len, int err)
67{
68 int errlen = strlen(snd_strerror(err));
69 if (errlen > 0 && errlen < len) {
70 strcpy(msg, snd_strerror(err));
71 } else if (len > 20) {
72 sprintf(msg, "Alsa error %d", err);
73 } else {
74 msg[0] = 0;
75 }
76}
77
78
79static PmError check_hosterror(int err)
80{
81 if (err < 0) {
82 get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
83 pm_hosterror = TRUE;
84 return pmHostError;
85 }
86 return pmNoError;
87}
88
89
90/* queue is shared by both input and output, reference counted */
91static PmError alsa_use_queue(void)
92{
93 int err = 0;
94 if (queue_used == 0) {
95 snd_seq_queue_tempo_t *tempo;
96
97 queue = snd_seq_alloc_queue(seq);
98 if (queue < 0) {
99 return check_hosterror(queue);
100 }
101 snd_seq_queue_tempo_alloca(&tempo);
102 snd_seq_queue_tempo_set_tempo(tempo, 480000);
103 snd_seq_queue_tempo_set_ppq(tempo, 480);
104 err = snd_seq_set_queue_tempo(seq, queue, tempo);
105 if (err < 0) {
106 return check_hosterror(err);
107 }
108 snd_seq_start_queue(seq, queue, NULL);
109 snd_seq_drain_output(seq);
110 }
111 ++queue_used;
112 return pmNoError;
113}
114
115
116static void alsa_unuse_queue(void)
117{
118 if (--queue_used == 0) {
119 snd_seq_stop_queue(seq, queue, NULL);
120 snd_seq_drain_output(seq);
121 snd_seq_free_queue(seq, queue);
122 VERBOSE printf("queue freed\n");
123 }
124}
125
126
127/* midi_message_length -- how many bytes in a message? */
128static int midi_message_length(PmMessage message)
129{
130 message &= 0xff;
131 if (message < 0x80) {
132 return 0;
133 } else if (message < 0xf0) {
134 static const int length[] = {3, 3, 3, 3, 2, 2, 3};
135 return length[(message - 0x80) >> 4];
136 } else {
137 static const int length[] = {
138 -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
139 return length[message - 0xf0];
140 }
141}
142
143
144static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)
145{
146 alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));
147 info->is_virtual = is_virtual;
148 info->this_port = id;
149 info->client = GET_DESCRIPTOR_CLIENT(client_port);
150 info->port = GET_DESCRIPTOR_PORT(client_port);
151 info->in_sysex = 0;
152 return info;
153}
154
155
156/* search system dependent extra parameters for string */
157static const char *get_sysdep_name(enum PmSysDepPropertyKey key,
158 PmSysDepInfo *info)
159{
160 /* the version where all current properties were introduced is 210 */
161 if (info && info->structVersion >= 210) {
162 int i;
163 for (i = 0; i < info->length; i++) { /* search for key */
164 if (info->properties[i].key == key) {
165 return info->properties[i].value;
166 }
167 }
168 }
169 return NULL;
170}
171
172
173static void maybe_set_client_name(PmSysDepInfo *driverInfo)
174{
175 if (!seq) { // make sure seq is created and we have info
176 return;
177 }
178
179 const char *client_name = get_sysdep_name(pmKeyAlsaClientName,
180 (PmSysDepInfo *) driverInfo);
181 if (client_name) {
182 snd_seq_set_client_name(seq, client_name);
183 printf("maybe_set_client_name set client to %s\n", client_name);
184 }
185}
186
187
188static PmError alsa_out_open(PmInternal *midi, void *driverInfo)
189{
190 int id = midi->device_id;
191 void *client_port = pm_descriptors[id].descriptor;
192 alsa_info_type ainfo = alsa_info_create((long) client_port, id,
193 pm_descriptors[id].pub.is_virtual);
194 snd_seq_port_info_t *pinfo;
195 int err = 0;
196 int using_the_queue = 0;
197
198 if (!ainfo) return pmInsufficientMemory;
199 midi->api_info = ainfo;
200
201 snd_seq_port_info_alloca(&pinfo);
202 if (!ainfo->is_virtual) {
203 snd_seq_port_info_set_port(pinfo, id);
204 snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
205 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
206 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
207 SND_SEQ_PORT_TYPE_APPLICATION);
208 const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
209 (PmSysDepInfo *) driverInfo);
210 if (port_name) {
211 snd_seq_port_info_set_name(pinfo, port_name);
212 }
213 snd_seq_port_info_set_port_specified(pinfo, 1);
214
215 err = snd_seq_create_port(seq, pinfo);
216 if (err < 0) goto free_ainfo;
217
218 }
219
220 err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);
221 if (err < 0) goto free_this_port;
222
223 if (midi->latency > 0) { /* must delay output using a queue */
224 err = alsa_use_queue();
225 if (err < 0) goto free_parser;
226 using_the_queue++;
227 }
228
229 if (!ainfo->is_virtual) {
230 err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,
231 ainfo->port);
232 if (err < 0) goto unuse_queue; /* clean up and return on error */
233 }
234
235 maybe_set_client_name(driverInfo);
236
237 return pmNoError;
238
239 unuse_queue:
240 if (using_the_queue > 0) /* only for latency>0 case */
241 alsa_unuse_queue();
242 free_parser:
243 snd_midi_event_free(ainfo->parser);
244 free_this_port:
245 snd_seq_delete_port(seq, ainfo->this_port);
246 free_ainfo:
247 pm_free(ainfo);
248 return check_hosterror(err);
249}
250
251
252static PmError alsa_write_byte(PmInternal *midi, unsigned char byte,
253 PmTimestamp timestamp)
254{
255 alsa_info_type info = (alsa_info_type) midi->api_info;
256 snd_seq_event_t ev;
257 int err = 0;
258
259 snd_seq_ev_clear(&ev);
260 if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {
261 if (info->is_virtual) {
262 snd_seq_ev_set_subs(&ev);
263 } else {
264 snd_seq_ev_set_dest(&ev, info->client, info->port);
265 }
266 snd_seq_ev_set_source(&ev, info->this_port);
267 if (midi->latency > 0) {
268 /* compute relative time of event = timestamp - now + latency */
269 PmTimestamp now = (midi->time_proc ?
270 midi->time_proc(midi->time_info) :
271 Pt_Time());
272 int when = timestamp;
273 /* if timestamp is zero, send immediately */
274 /* otherwise compute time delay and use delay if positive */
275 if (when == 0) when = now;
276 when = (when - now) + midi->latency;
277 if (when < 0) when = 0;
278 VERBOSE printf("timestamp %d now %d latency %d, ",
279 (int) timestamp, (int) now, midi->latency);
280 VERBOSE printf("scheduling event after %d\n", when);
281 /* message is sent in relative ticks, where 1 tick = 1 ms */
282 snd_seq_ev_schedule_tick(&ev, queue, 1, when);
283 /* NOTE: for cases where the user does not supply a time function,
284 we could optimize the code by not starting Pt_Time and using
285 the alsa tick time instead. I didn't do this because it would
286 entail changing the queue management to start the queue tick
287 count when PortMidi is initialized and keep it running until
288 PortMidi is terminated. (This should be simple, but it's not
289 how the code works now.) -RBD */
290 } else { /* send event out without queueing */
291 VERBOSE printf("direct\n");
292 /* ev.queue = SND_SEQ_QUEUE_DIRECT;
293 ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
294 snd_seq_ev_set_direct(&ev);
295 }
296 VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n",
297 ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,
298 (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
299 (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
300 err = snd_seq_event_output(seq, &ev);
301 }
302 return check_hosterror(err);
303}
304
305
306static PmError alsa_out_close(PmInternal *midi)
307{
308 alsa_info_type info = (alsa_info_type) midi->api_info;
309 int err = 0;
310 int error2 = 0;
311 if (!info) return pmBadPtr;
312
313 if (info->this_port != PORT_IS_CLOSED) {
314 if (!info->is_virtual) {
315 err = snd_seq_disconnect_to(seq, info->this_port,
316 info->client, info->port);
317 /* even if there was an error, we still try to delete the port */
318 error2 = snd_seq_delete_port(seq, info->this_port);
319
320 if (!err) { /* retain original error if there was one */
321 err = error2; /* otherwise, use port delete status */
322 }
323 }
324 }
325 if (midi->latency > 0) alsa_unuse_queue();
326 snd_midi_event_free(info->parser);
327 midi->api_info = NULL; /* destroy the pointer to signify "closed" */
328 pm_free(info);
329 return check_hosterror(err);
330}
331
332
333static PmError alsa_create_virtual(int is_input, const char *name,
334 void *device_info)
335{
336 snd_seq_port_info_t *pinfo;
337 int err;
338 int client, port;
339
340 /* we need the id to set the port. */
341 PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,
342 (is_input ? &pm_linuxalsa_in_dictionary :
343 &pm_linuxalsa_out_dictionary));
344 if (id < 0) { /* error -- out of memory? */
345 return id;
346 }
347 snd_seq_port_info_alloca(&pinfo);
348 snd_seq_port_info_set_capability(pinfo,
349 (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :
350 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));
351 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
352 SND_SEQ_PORT_TYPE_APPLICATION);
353 snd_seq_port_info_set_name(pinfo, name);
354 snd_seq_port_info_set_port(pinfo, id);
355 snd_seq_port_info_set_port_specified(pinfo, 1);
356 /* next 3 lines needed to generate timestamp - PaulLiu */
357 snd_seq_port_info_set_timestamping(pinfo, 1);
358 snd_seq_port_info_set_timestamp_real(pinfo, 0);
359 snd_seq_port_info_set_timestamp_queue(pinfo, queue);
360
361 err = snd_seq_create_port(seq, pinfo);
362 if (err < 0) {
363 pm_undo_add_device(id);
364 return check_hosterror(err);
365 }
366
367 client = snd_seq_port_info_get_client(pinfo);
368 port = snd_seq_port_info_get_port(pinfo);
369 pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);
370 return id;
371}
372
373
374 static PmError alsa_delete_virtual(PmDeviceID id)
375 {
376 int err = snd_seq_delete_port(seq, id);
377 return check_hosterror(err);
378 }
379
380
381static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
382{
383 int id = midi->device_id;
384 void *client_port = pm_descriptors[id].descriptor;
385 alsa_info_type ainfo = alsa_info_create((long) client_port, id,
386 pm_descriptors[id].pub.is_virtual);
387 snd_seq_port_info_t *pinfo;
388 snd_seq_port_subscribe_t *sub;
389 snd_seq_addr_t addr;
390 int err = 0;
391 int is_virtual = pm_descriptors[id].pub.is_virtual;
392
393 if (!ainfo) return pmInsufficientMemory;
394 midi->api_info = ainfo;
395
396 err = alsa_use_queue();
397 if (err < 0) goto free_ainfo;
398
399 snd_seq_port_info_alloca(&pinfo);
400 if (is_virtual) {
401 ainfo->is_virtual = TRUE;
402 if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {
403 pinfo = NULL;
404 goto free_ainfo;
405 }
406 } else {
407 /* create a port for this alsa client (seq) where the port
408 number matches the portmidi device ID of the input device */
409 snd_seq_port_info_set_port(pinfo, id);
410 snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
411 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);
412
413 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
414 SND_SEQ_PORT_TYPE_APPLICATION);
415 snd_seq_port_info_set_port_specified(pinfo, 1);
416
417 const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
418 (PmSysDepInfo *) driverInfo);
419 if (port_name) {
420 snd_seq_port_info_set_name(pinfo, port_name);
421 }
422
423 err = snd_seq_create_port(seq, pinfo);
424 if (err < 0) goto free_queue;
425
426 /* forward messages from input to this alsa client, so this
427 * alsa client is the destination, and the destination port is the
428 * port we just created using the device ID as port number
429 */
430 snd_seq_port_subscribe_alloca(&sub);
431 addr.client = snd_seq_client_id(seq);
432 addr.port = ainfo->this_port;
433 snd_seq_port_subscribe_set_dest(sub, &addr);
434
435 /* forward from the sender which is the device named by
436 client and port */
437 addr.client = ainfo->client;
438 addr.port = ainfo->port;
439 snd_seq_port_subscribe_set_sender(sub, &addr);
440 snd_seq_port_subscribe_set_time_update(sub, 1);
441 /* this doesn't seem to work: messages come in with real timestamps */
442 snd_seq_port_subscribe_set_time_real(sub, 0);
443 err = snd_seq_subscribe_port(seq, sub);
444 if (err < 0) goto free_this_port; /* clean up and return on error */
445 }
446
447 maybe_set_client_name(driverInfo);
448
449 return pmNoError;
450 free_this_port:
451 snd_seq_delete_port(seq, ainfo->this_port);
452 free_queue:
453 alsa_unuse_queue();
454 free_ainfo:
455 pm_free(ainfo);
456 return check_hosterror(err);
457}
458
459static PmError alsa_in_close(PmInternal *midi)
460{
461 int err = 0;
462 alsa_info_type info = (alsa_info_type) midi->api_info;
463 if (!info) return pmBadPtr;
464 /* virtual ports stay open because the represent devices */
465 if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {
466 err = snd_seq_delete_port(seq, info->this_port);
467 }
468 alsa_unuse_queue();
469 midi->api_info = NULL;
470 pm_free(info);
471 return check_hosterror(err);
472}
473
474
475static PmError alsa_abort(PmInternal *midi)
476{
477 /* NOTE: ALSA documentation is vague. This is supposed to
478 * remove any pending output messages. If you can test and
479 * confirm this code is correct, please update this comment. -RBD
480 */
481 /* Unfortunately, I can't even compile it -- my ALSA version
482 * does not implement snd_seq_remove_events_t, so this does
483 * not compile. I'll try again, but it looks like I'll need to
484 * upgrade my entire Linux OS -RBD
485 */
486 /*
487 info_type info = (info_type) midi->api_info;
488 snd_seq_remove_events_t info;
489 snd_seq_addr_t addr;
490 addr.client = info->client;
491 addr.port = info->port;
492 snd_seq_remove_events_set_dest(&info, &addr);
493 snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
494 pm_hosterror = snd_seq_remove_events(seq, &info);
495 if (pm_hosterror) {
496 get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
497 pm_hosterror);
498 return pmHostError;
499 }
500 */
501 printf("WARNING: alsa_abort not implemented\n");
502 return pmNoError;
503}
504
505
506static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
507{
508 int err;
509 alsa_info_type info = (alsa_info_type) midi->api_info;
510 if (!info) return pmBadPtr;
511 VERBOSE printf("snd_seq_drain_output: %p\n", seq);
512 err = snd_seq_drain_output(seq);
513 return check_hosterror(err);
514}
515
516
517static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
518{
519 int bytes = midi_message_length(event->message);
520 PmMessage msg = event->message;
521 int i;
522 alsa_info_type info = (alsa_info_type) midi->api_info;
523 if (!info) return pmBadPtr;
524 for (i = 0; i < bytes; i++) {
525 unsigned char byte = msg;
526 VERBOSE printf("sending 0x%x\n", byte);
527 alsa_write_byte(midi, byte, event->timestamp);
528 if (pm_hosterror) break;
529 msg >>= 8; /* shift next byte into position */
530 }
531 if (pm_hosterror) return pmHostError;
532 return pmNoError;
533}
534
535
536/* alsa_sysex -- implements begin_sysex and end_sysex */
537PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
538 return pmNoError;
539}
540
541
542static PmTimestamp alsa_synchronize(PmInternal *midi)
543{
544 return 0; /* linux implementation does not use this synchronize function */
545 /* Apparently, Alsa data is relative to the time you send it, and there
546 is no reference. If this is true, this is a serious shortcoming of
547 Alsa. If not true, then PortMidi has a serious shortcoming -- it
548 should be scheduling relative to Alsa's time reference. */
549}
550
551
552static void handle_event(snd_seq_event_t *ev)
553{
554 int device_id = ev->dest.port;
555 PmInternal *midi = pm_descriptors[device_id].pm_internal;
556 // There is a race condition when closing a device and
557 // continuing to poll other open devices. The closed device may
558 // have outstanding events from before the close operation.
559 if (!midi) {
560 return;
561 }
562 PmEvent pm_ev;
563 PmTimestamp timestamp = midi->time_proc(midi->time_info);
564
565 /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
566 /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
567 * Currently, event timestamp is ignored. See long note below. */
568
569 VERBOSE {
570 /* translate time to time_proc basis */
571 snd_seq_queue_status_t *queue_status;
572 snd_seq_queue_status_alloca(&queue_status);
573 snd_seq_get_queue_status(seq, queue, queue_status);
574 printf("handle_event: alsa_now %d, "
575 "event timestamp %d (%d+%dns) (%s, %s)\n",
576 snd_seq_queue_status_get_tick_time(queue_status),
577 ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,
578 (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
579 (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
580 /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */
581 /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
582 snd_seq_queue_status_get_tick_time(queue_status); */
583 }
584 /* CURRENT: portmidi timestamp is "now". In a test, timestamps from
585 * hardware (MIDI over USB) were timestamped with the current ALSA
586 * time (snd_seq_queue_status_get_tick_time) and flags indicating
587 * absolute ticks, but timestamps from another application's virtual
588 * port, sent direct with 0 absolute ticks, were received with a
589 * large value that is apparently the time since the start time of
590 * the other application. Without any reference to our local time,
591 * this seems useless. PortMidi is supposed to return the local
592 * PortMidi time of the arrival of the message, so the best we can
593 * do is set the timestamp to our local clock. This seems to be a
594 * design flaw in ALSA -- I pointed this out a decade ago, but if
595 * there is a workaround, I'd still like to know. Maybe there is a
596 * way to use absolute real time and maybe that's sharable across
597 * applications by referencing the system time?
598 */
599 pm_ev.timestamp = timestamp;
600 switch (ev->type) {
601 case SND_SEQ_EVENT_NOTEON:
602 pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
603 ev->data.note.note & 0x7f,
604 ev->data.note.velocity & 0x7f);
605 pm_read_short(midi, &pm_ev);
606 break;
607 case SND_SEQ_EVENT_NOTEOFF:
608 pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
609 ev->data.note.note & 0x7f,
610 ev->data.note.velocity & 0x7f);
611 pm_read_short(midi, &pm_ev);
612 break;
613 case SND_SEQ_EVENT_KEYPRESS:
614 pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
615 ev->data.note.note & 0x7f,
616 ev->data.note.velocity & 0x7f);
617 pm_read_short(midi, &pm_ev);
618 break;
619 case SND_SEQ_EVENT_CONTROLLER:
620 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
621 ev->data.control.param & 0x7f,
622 ev->data.control.value & 0x7f);
623 pm_read_short(midi, &pm_ev);
624 break;
625 case SND_SEQ_EVENT_PGMCHANGE:
626 pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
627 ev->data.control.value & 0x7f, 0);
628 pm_read_short(midi, &pm_ev);
629 break;
630 case SND_SEQ_EVENT_CHANPRESS:
631 pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
632 ev->data.control.value & 0x7f, 0);
633 pm_read_short(midi, &pm_ev);
634 break;
635 case SND_SEQ_EVENT_PITCHBEND:
636 pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
637 (ev->data.control.value + 0x2000) & 0x7f,
638 ((ev->data.control.value + 0x2000) >> 7) & 0x7f);
639 pm_read_short(midi, &pm_ev);
640 break;
641 case SND_SEQ_EVENT_CONTROL14:
642 if (ev->data.control.param < 0x20) {
643 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
644 ev->data.control.param,
645 (ev->data.control.value >> 7) & 0x7f);
646 pm_read_short(midi, &pm_ev);
647 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
648 ev->data.control.param + 0x20,
649 ev->data.control.value & 0x7f);
650 pm_read_short(midi, &pm_ev);
651 } else {
652 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
653 ev->data.control.param & 0x7f,
654 ev->data.control.value & 0x7f);
655
656 pm_read_short(midi, &pm_ev);
657 }
658 break;
659 case SND_SEQ_EVENT_SONGPOS:
660 pm_ev.message = Pm_Message(0xf2,
661 ev->data.control.value & 0x7f,
662 (ev->data.control.value >> 7) & 0x7f);
663 pm_read_short(midi, &pm_ev);
664 break;
665 case SND_SEQ_EVENT_SONGSEL:
666 pm_ev.message = Pm_Message(0xf3,
667 ev->data.control.value & 0x7f, 0);
668 pm_read_short(midi, &pm_ev);
669 break;
670 case SND_SEQ_EVENT_QFRAME:
671 pm_ev.message = Pm_Message(0xf1,
672 ev->data.control.value & 0x7f, 0);
673 pm_read_short(midi, &pm_ev);
674 break;
675 case SND_SEQ_EVENT_START:
676 pm_ev.message = Pm_Message(0xfa, 0, 0);
677 pm_read_short(midi, &pm_ev);
678 break;
679 case SND_SEQ_EVENT_CONTINUE:
680 pm_ev.message = Pm_Message(0xfb, 0, 0);
681 pm_read_short(midi, &pm_ev);
682 break;
683 case SND_SEQ_EVENT_STOP:
684 pm_ev.message = Pm_Message(0xfc, 0, 0);
685 pm_read_short(midi, &pm_ev);
686 break;
687 case SND_SEQ_EVENT_CLOCK:
688 pm_ev.message = Pm_Message(0xf8, 0, 0);
689 pm_read_short(midi, &pm_ev);
690 break;
691 case SND_SEQ_EVENT_TUNE_REQUEST:
692 pm_ev.message = Pm_Message(0xf6, 0, 0);
693 pm_read_short(midi, &pm_ev);
694 break;
695 case SND_SEQ_EVENT_RESET:
696 pm_ev.message = Pm_Message(0xff, 0, 0);
697 pm_read_short(midi, &pm_ev);
698 break;
699 case SND_SEQ_EVENT_SENSING:
700 pm_ev.message = Pm_Message(0xfe, 0, 0);
701 pm_read_short(midi, &pm_ev);
702 break;
703 case SND_SEQ_EVENT_SYSEX: {
704 const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
705 /* assume there is one sysex byte to process */
706 pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
707 break;
708 }
709 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {
710 /* this happens if you have an input port open and the
711 * device or application with virtual ports closes. We
712 * mark the port as closed to avoid closing a 2nd time
713 * when Pm_Close() is called.
714 */
715 alsa_info_type info = (alsa_info_type) midi->api_info;
716 /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */
717 info->this_port = PORT_IS_CLOSED;
718 break;
719 }
720 case SND_SEQ_EVENT_PORT_SUBSCRIBED:
721 break; /* someone connected to a virtual output port, not reported */
722 default:
723 printf("portmidi handle_event: not handled type %x\n", ev->type);
724 break;
725 }
726}
727
728
729static PmError alsa_poll(PmInternal *midi)
730{
731 if (!midi) {
732 return pmBadPtr;
733 }
734 snd_seq_event_t *ev;
735 /* expensive check for input data, gets data from device: */
736 while (snd_seq_event_input_pending(seq, TRUE) > 0) {
737 /* cheap check on local input buffer */
738 while (snd_seq_event_input_pending(seq, FALSE) > 0) {
739 /* check for and ignore errors, e.g. input overflow */
740 /* note: if there's overflow, this should be reported
741 * all the way through to client. Since input from all
742 * devices is merged, we need to find all input devices
743 * and set all to the overflow state.
744 * NOTE: this assumes every input is ALSA based.
745 */
746 int rslt = snd_seq_event_input(seq, &ev);
747 if (rslt >= 0) {
748 handle_event(ev);
749 } else if (rslt == -ENOSPC) {
750 int i;
751 for (i = 0; i < pm_descriptor_len; i++) {
752 if (pm_descriptors[i].pub.input) {
753 PmInternal *midi_i = pm_descriptors[i].pm_internal;
754 /* careful, device may not be open! */
755 if (midi_i) Pm_SetOverflow(midi_i->queue);
756 }
757 }
758 }
759 }
760 }
761 return pmNoError;
762}
763
764
765static unsigned int alsa_check_host_error(PmInternal *midi)
766{
767 return FALSE;
768}
769
770
771pm_fns_node pm_linuxalsa_in_dictionary = {
772 none_write_short,
773 none_sysex,
774 none_sysex,
775 none_write_byte,
776 none_write_short,
777 none_write_flush,
778 alsa_synchronize,
779 alsa_in_open,
780 alsa_abort,
781 alsa_in_close,
782 alsa_poll,
783 alsa_check_host_error
784};
785
786pm_fns_node pm_linuxalsa_out_dictionary = {
787 alsa_write_short,
788 alsa_sysex,
789 alsa_sysex,
790 alsa_write_byte,
791 alsa_write_short, /* short realtime message */
792 alsa_write_flush,
793 alsa_synchronize,
794 alsa_out_open,
795 alsa_abort,
796 alsa_out_close,
797 none_poll,
798 alsa_check_host_error
799};
800
801
802/* pm_strdup -- copy a string to the heap. Use this rather than strdup so
803 * that we call pm_alloc, not malloc. This allows portmidi to avoid
804 * malloc which might cause priority inversion. Probably ALSA is going
805 * to call malloc anyway, so this extra work here may be pointless.
806 */
807char *pm_strdup(const char *s)
808{
809 int len = strlen(s);
810 char *dup = (char *) pm_alloc(len + 1);
811 strcpy(dup, s);
812 return dup;
813}
814
815
816PmError pm_linuxalsa_init(void)
817{
818 int err;
819 snd_seq_client_info_t *cinfo;
820 snd_seq_port_info_t *pinfo;
821 unsigned int caps;
822
823 /* Register interface ALSA with create_virtual fn */
824 pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);
825
826 /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this
827 * would cause messages to be dropped if the ALSA buffer fills up.
828 * The correct behavior is for writes to block until there is
829 * room to send all the data. The client should normally allocate
830 * a large enough buffer to avoid blocking on output.
831 * Now that blocking is enabled, the seq_event_input() will block
832 * if there is no input data. This is not what we want, so must
833 * call seq_event_input_pending() to avoid blocking.
834 */
835 err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
836 if (err < 0) goto error_return;
837
838 snd_seq_client_info_alloca(&cinfo);
839 snd_seq_port_info_alloca(&pinfo);
840
841 snd_seq_client_info_set_client(cinfo, -1);
842 while (snd_seq_query_next_client(seq, cinfo) == 0) {
843 snd_seq_port_info_set_client(pinfo,
844 snd_seq_client_info_get_client(cinfo));
845 snd_seq_port_info_set_port(pinfo, -1);
846 while (snd_seq_query_next_port(seq, pinfo) == 0) {
847 if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
848 continue; /* ignore Timer and Announce ports on client 0 */
849 caps = snd_seq_port_info_get_capability(pinfo);
850 if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ |
851 SND_SEQ_PORT_CAP_SUBS_WRITE)))
852 continue; /* ignore if you cannot read or write port */
853 if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
854 if (pm_default_output_device_id == -1)
855 pm_default_output_device_id = pm_descriptor_len;
856 pm_add_device("ALSA",
857 pm_strdup(snd_seq_port_info_get_name(pinfo)),
858 FALSE, FALSE,
859 MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
860 snd_seq_port_info_get_port(pinfo)),
861 &pm_linuxalsa_out_dictionary);
862 }
863 if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
864 if (pm_default_input_device_id == -1)
865 pm_default_input_device_id = pm_descriptor_len;
866 pm_add_device("ALSA",
867 pm_strdup(snd_seq_port_info_get_name(pinfo)),
868 TRUE, FALSE,
869 MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
870 snd_seq_port_info_get_port(pinfo)),
871 &pm_linuxalsa_in_dictionary);
872 }
873 }
874 }
875 return pmNoError;
876 error_return:
877 pm_linuxalsa_term(); /* clean up */
878 return check_hosterror(err);
879}
880
881
882void pm_linuxalsa_term(void)
883{
884 if (seq) {
885 snd_seq_close(seq);
886 pm_free(pm_descriptors);
887 pm_descriptors = NULL;
888 pm_descriptor_len = 0;
889 pm_descriptor_max = 0;
890 }
891}
892
893#endif
diff --git a/portmidi/pm_linux/pmlinuxalsa.h b/portmidi/pm_linux/pmlinuxalsa.h
new file mode 100755
index 0000000..d4bff16
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.h
@@ -0,0 +1,6 @@
1/* pmlinuxalsa.h -- system-specific definitions */
2
3PmError pm_linuxalsa_init(void);
4void pm_linuxalsa_term(void);
5
6
diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c
new file mode 100644
index 0000000..257f3d6
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.c
@@ -0,0 +1,31 @@
1/*
2 * pmlinuxnull.c -- system specific definitions
3 *
4 * written by:
5 * Roger Dannenberg
6 *
7 * If there is no ALSA, you can define PMNULL and build PortMidi. It will
8 * not report any devices, so you will not be able to open any, but if
9 * you wanted to disable MIDI from some application, this could be used.
10 * Mainly, this code shows the possibility of supporting multiple
11 * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.
12 * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.
13 */
14
15#ifdef PMNULL
16
17#include "portmidi.h"
18#include "pmlinuxnull.h"
19
20
21PmError pm_linuxnull_init(void)
22{
23 return pmNoError;
24}
25
26
27void pm_linuxnull_term(void)
28{
29}
30
31#endif
diff --git a/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h
new file mode 100644
index 0000000..9835825
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.h
@@ -0,0 +1,6 @@
1/* pmlinuxnull.h -- system-specific definitions */
2
3PmError pm_linuxnull_init(void);
4void pm_linuxnull_term(void);
5
6
diff --git a/portmidi/pm_mac/Makefile.osx b/portmidi/pm_mac/Makefile.osx
new file mode 100755
index 0000000..2044381
--- /dev/null
+++ b/portmidi/pm_mac/Makefile.osx
@@ -0,0 +1,125 @@
1# MAKEFILE FOR PORTMIDI
2
3# Roger B. Dannenberg
4# Sep 2009
5
6# NOTE: PortMidi is currently built and tested with CMake.
7# This makefile is probably broken, but if you want to
8# directly use make, start here, and please contribute any
9# fixes.
10
11# NOTE: you can use
12# make -f pm_mac/Makefile.osx configuration=Release
13# to override the default Debug configuration
14configuration=Release
15
16PF=/usr/local
17
18# For debugging, define PM_CHECK_ERRORS
19ifeq ($(configuration),Release)
20 CONFIG = Release
21else
22 CONFIG = Debug
23endif
24
25current: all
26
27all: $(CONFIG)/CMakeCache.txt
28 cd $(CONFIG); make
29
30$(CONFIG)/CMakeCache.txt:
31 rm -f $(CONFIG)/CMakeCache.txt
32 mkdir -p $(CONFIG)
33 cd $(CONFIG); cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(CONFIG)
34
35
36**** For instructions: make -f pm_mac/Makefile.osx help ****\n'
37
38help:
39 echo $$'\n\n\
40This is help for portmidi/pm_mac/Makefile.osx\n\n\
41Installation path for dylib is $(PF)\n\
42To build Release version libraries and test applications,\n \
43make -f pm_mac/Makefile.osx\n\
44To build Debug version libraries and test applications,\n \
45make -f pm_mac/Makefile.osx configuration=Debug\n\
46To install universal dynamic library,\n \
47sudo make -f pm_mac/Makefile.osx install\n\
48To install universal dynamic library with xcode,\n \
49make -f pm_mac/Makefile.osx install-with-xcode\n\
50To make PmDefaults Java application,\n \
51make -f pm_mac/Makefile.osx pmdefaults\n\n \
52configuration = $(configuration)\n'
53
54
55clean:
56 rm -f *.o *~ core* */*.o */*/*.o */*~ */core* pm_test/*/pm_dll.dll
57 rm -f *.opt *.ncb *.plg pm_win/Debug/pm_dll.lib pm_win/Release/pm_dll.lib
58 rm -f pm_test/*.opt pm_test/*.ncb
59 rm -f pm_java/pmjni/*.o pm_java/pmjni/*~ pm_java/*.h
60 rm -rf Release/CMakeFiles Debug/CMakeFiles
61 rm -rf pm_mac/pmdefaults/lib pm_mac/pmdefaults/src
62
63cleaner: clean
64 rm -rf pm_mac/build
65 rm -rf pm_mac/Debug pm_mac/Release pm_test/Debug pm_test/Release
66 rm -f Debug/*.dylib Release/*.dylib
67 rm -f pm_java/pmjni/Debug/*.jnilib
68 rm -f pm_java/pmjni/Release/*.jnilib
69
70cleanest: cleaner
71 rm -f Debug/CMakeCache.txt Release/CMakeCache.txt
72 rm -f CMakeCache.txt
73 rm -f Debug/libportmidi_s.a Release/libportmidi_s.a
74 rm -f pm_test/Debug/test pm_test/Debug/sysex pm_test/Debug/midithread
75 rm -f pm_test/Debug/latency pm_test/Debug/midithru
76 rm -f pm_test/Debug/qtest pm_test/Debug/mm
77 rm -f pm_test/Release/test pm_test/Release/sysex pm_test/Release/midithread
78 rm -f pm_test/Release/latency pm_test/Release/midithru
79 rm -f pm_test/Release/qtest pm_test/Release/mm
80 rm -f pm_java/*/*.class
81 rm -f pm_java/pmjni/jportmidi_JPortMidiApi_PortMidiStream.h
82
83backup: cleanest
84 cd ..; zip -r portmidi.zip portmidi
85
86install: porttime/porttime.h pm_common/portmidi.h \
87 $(CONFIG)/libportmidi.dylib
88 install porttime/porttime.h $(PF)/include/
89 install pm_common/portmidi.h $(PF)/include
90 install $(CONFIG)/libportmidi.dylib $(PF)/lib/
91
92# note - this uses xcode to build and install portmidi universal binaries
93install-with-xcode:
94 sudo xcodebuild -project pm_mac/pm_mac.xcodeproj \
95 -configuration Release install DSTROOT=/
96
97##### build pmdefault ######
98
99pm_java/pmjni/jportmidi_JPortMidiApi.h: pm_java/jportmidi/JPortMidiApi.class
100 cd pm_java; javah jportmidi.JPortMidiApi
101 mv pm_java/jportmidi_JportMidiApi.h pm_java/pmjni
102
103JAVASRC = pmdefaults/PmDefaultsFrame.java \
104 pmdefaults/PmDefaults.java \
105 jportmidi/JPortMidiApi.java jportmidi/JPortMidi.java \
106 jportmidi/JPortMidiException.java
107
108# this compiles ALL of the java code
109pm_java/jportmidi/JPortMidiApi.class: $(JAVASRC:%=pm_java/%)
110 cd pm_java; javac $(JAVASRC)
111
112$(CONFIG)/libpmjni.dylib:
113 mkdir -p $(CONFIG)
114 cd $(CONFIG); make -f ../pm_mac/$(MAKEFILE)
115
116pmdefaults: $(CONFIG)/libpmjni.dylib pm_java/jportmidi/JPortMidiApi.class
117ifeq ($(CONFIG),Debug)
118 echo "Error: you cannot build pmdefaults in a Debug configuration \n\
119 You should use configuration=Release in the Makefile command line. "
120 @exit 2
121endif
122 xcodebuild -project pm_mac/pm_mac.xcodeproj \
123 -configuration Release -target PmDefaults
124 echo "pmdefaults java application is made"
125
diff --git a/portmidi/pm_mac/README_MAC.txt b/portmidi/pm_mac/README_MAC.txt
new file mode 100644
index 0000000..41e8341
--- /dev/null
+++ b/portmidi/pm_mac/README_MAC.txt
@@ -0,0 +1,65 @@
1README_MAC.txt for PortMidi
2Roger Dannenberg
320 nov 2009
4
5revised Mar 2024 to remove pmdefaults references
6revised Jan 2022 for the PortMidi/portmidi repo on github.com
7revised 20 Sep 2010 for Xcode 4.3.2 and CMake 2.8.8
8
9This documents how I build PortMidi for macOS. It's not the only way,
10and command-line/scripting enthusiasts will say it's not even a good
11way. Feel free to contribute your approach if you are willing to
12describe it carefully and test it.
13
14Install Xcode and the CMake application, CMake.app. I use the GUI
15version of CMake which makes it easy to see/edit variables and
16options.
17
18==== USING CMAKE ====
19
20Run CMake.app and select your portmidi repo working directory as the
21location for source and build. (Yes, I use so called "in-tree"
22builds -- it doesn't hurt, but I don't think it is necessary.)
23
24Default settings should all be fine, but select options under BUILD if
25you wish:
26
27BUILD_NATIVE_JAVA_INTERFACE to build a Java interface (JNI) library.
28
29BUILD_PORTMIDI_TESTS to create some test programs. Of particular
30interest are test/mm, a handy command-line MIDI Input Monitor, and
31test/testio, a simple command-line program to send or receive some
32MIDI notes in case you need a quick test: What devices do I have? Does
33this input work? Does this output work?
34
35I disable BUILD_SHARED_LIBS and always link statically: Static linking only
36adds about 40KB to any application and then you don't have to worry
37about versions, instally, copying or finding the dynamic link library,
38etc.
39
40To make sure you link statically, I rename the library to
41libportmidi_static.a. To do this, set PM_STATIC_LIB_NAME (in CMake,
42under the "PM" group) to "portmidi_static", and of course your
43application will have to specify portmidi_static as the library to
44link to.
45
46If you are building simple command-line applications, you might want
47to enable PM_CHECK_ERRORS. If you do, then calls into the PortMidi
48library will print error messages and exit in the event of an error
49(such as trying to open a device that does not exist). This saves you
50from having to check for errors everytime you call a library function
51or getting confused when errors are detected but not reported. For
52high-quality applications, do NOT enable PM_CHECK_ERRORS -- any
53failure could immediately abort your whole application, which is not
54very friendly to users.
55
56Click on Configure (maybe a couple of times).
57
58Click on Generate and make an Xcode project.
59
60Open portmidi/portmidi.xcodeproj with Xcode and build what you
61need. The simplest thing is to build the ALL_BUILD target. Be careful
62to specify a Debug or Release depending on what you want. "ALL_BUILD"
63is a misnomer -- it only builds the version you select.
64
65
diff --git a/portmidi/pm_mac/pmmac.c b/portmidi/pm_mac/pmmac.c
new file mode 100755
index 0000000..48ac17a
--- /dev/null
+++ b/portmidi/pm_mac/pmmac.c
@@ -0,0 +1,44 @@
1/* pmmac.c -- PortMidi os-dependent code */
2
3/* This file only needs to implement:
4pm_init(), which calls various routines to register the
5available midi devices,
6Pm_GetDefaultInputDeviceID(), and
7Pm_GetDefaultOutputDeviceID().
8It is seperate from pmmacosxcm because we might want to register
9non-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
18void pm_init(void)
19{
20 pm_macosxcm_init();
21}
22
23
24void pm_term(void)
25{
26 pm_macosxcm_term();
27}
28
29PmDeviceID Pm_GetDefaultInputDeviceID(void)
30{
31 Pm_Initialize();
32 return pm_default_input_device_id;
33}
34
35PmDeviceID Pm_GetDefaultOutputDeviceID(void) {
36 Pm_Initialize();
37 return pm_default_output_device_id;
38}
39
40void *pm_alloc(size_t s) { return malloc(s); }
41
42void pm_free(void *ptr) { free(ptr); }
43
44
diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c
new file mode 100755
index 0000000..e8b196c
--- /dev/null
+++ b/portmidi/pm_mac/pmmacosxcm.c
@@ -0,0 +1,1179 @@
1/*
2 * Platform interface to the MacOS X CoreMIDI framework
3 *
4 * Jon Parise <jparise at cmu.edu>
5 * and subsequent work by Andrew Zeldis and Zico Kolter
6 * and Roger B. Dannenberg
7 *
8 * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
9 */
10
11/* Notes:
12
13 Since the input and output streams are represented by
14 MIDIEndpointRef values and almost no other state, we store the
15 MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.
16
17 OS X does not seem to have an error-code-to-text function, so we
18 will just use text messages instead of error codes.
19
20 Virtual device input synchronization: Once we create a virtual
21 device, it is always "on" and receiving messages, but it must drop
22 messages unless the device has been opened with Pm_OpenInput. To
23 open, the main thread should create all the data structures, then
24 call OSMemoryBarrier so that writes are observed, then set
25 is_opened = TRUE. To close without locks, we need to get the
26 callback to set is_opened to FALSE before we free data structures;
27 otherwise, there's a race condition where closing could delete
28 structures in use by the virtual_read_callback function. We send
29 8 MIDI resets (FF) in a single packet to our own port to signal
30 the virtual_read_callback to close it. Then, we wait for the
31 callback to recognize the "close" packet and reset is_opened.
32
33 Device scanning is done when you first open an application.
34 PortMIDI does not actively update the devices. Instead, you must
35 Pm_Terminate() and Pm_Initialize(), basically starting over. But
36 CoreMIDI does not have a way to shut down(!), and even
37 MIDIClientDispose() somehow retains state (and docs say do not
38 call it even if it worked). The solution, apparently, is to
39 call CFRunLoopRunInMode(), which somehow updates CoreMIDI
40 state.
41
42 But when do we call CFRunLoopRunInMode()? I tried calling it
43 in midi_in_poll() which is called when you call Pm_Read() since
44 that is called often. I observed that this caused the program
45 to block for as long as 50ms and fairly often for 2 or 3ms.
46 What was Apple thinking? Is it really OK to design systems that
47 can only function with a tricky multi-threaded, non-blocking
48 priority-based solution, and then not provide a proof of concept
49 or documentation? Or is Apple's design really flawed? If anyone
50 at Apple reads this, please let me know -- I'm curious.
51
52 But I digress... Here's the PortMidi approach: Since
53 CFRunLoopRunInMode() is potentially a non-realtime operation,
54 we only call it in Pm_Initialize(), where other calls to look
55 up devices and device names are quite slow to begin with. Again,
56 PortMidi does not actively scan for new or deleted devices, so
57 if devices change, you won't see it until the next Pm_Terminate
58 and Pm_Initialize.
59
60 Calling CFRunLoopRunInMode() once is probably not enough. There
61 might be better way, but it seems to work to just call it 100
62 times and insert 20 1ms delays (in case some inter-process
63 communication or synchronization is going on).
64 This adds 20ms to the wall time of Pm_Initialize(), but it
65 typically runs 30ms to much more (~4s), so this has little impact.
66 */
67
68#include <stdlib.h>
69
70/* turn on lots of debugging print statements */
71#define CM_DEBUG if (0)
72/* #define CM_DEBUG if (1) */
73
74#include "portmidi.h"
75#include "pmutil.h"
76#include "pminternal.h"
77#include "porttime.h"
78#include "pmmacosxcm.h"
79
80#include <stdio.h>
81#include <string.h>
82
83#include <CoreServices/CoreServices.h>
84#include <CoreMIDI/MIDIServices.h>
85#include <CoreAudio/HostTime.h>
86#include <unistd.h>
87#include <libkern/OSAtomic.h>
88
89#define PACKET_BUFFER_SIZE 1024
90/* maximum overall data rate (OS X limits MIDI rate in case there
91 * is a cycle among IAC ports.
92 */
93
94#define MAX_BYTES_PER_S 5400
95
96/* Apple reports that packets are dropped when the MIDI bytes/sec
97 exceeds 15000. This is computed by "tracking the number of MIDI
98 bytes scheduled into 1-second buckets over the last six seconds and
99 averaging these counts." This was confirmed in measurements
100 (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with
101 macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.
102 Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to
103 5400 based on testing (which shows 5700 is too high) to fix the
104 packet loss problem that showed up with macOS 12.
105
106 Experiments show this restriction applies to IAC bus MIDI, but not
107 to hardware interfaces. (I measured 0.5 Mbps each way over USB to a
108 Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe
109 it would get 1 Mbps one-way, which would make the CoreMIDI
110 restriction 18x slower than USB. Maybe other USB MIDI
111 implementations are faster -- USB top speed for other protocols is
112 certainly higher than 1 Mbps!)
113
114 This is apparently based on timestamps, not on real time, so we
115 have to avoid constructing packets that schedule high speed output
116 regardless of when writes occur. The solution is to alter
117 timestamps to limit data rates. This adds a slight time
118 distortion, e.g. an 11 note chord with all notes on the same
119 timestamp will be altered so that the last message is delayed by
120 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.
121 Note that this is about 2x MIDI speed, but at least 18x slower
122 than USB MIDI.
123
124 Altering timestamps creates another problem, which is that a sender
125 that exceeds the maximum rate can queue up an unbounded number of
126 messages. With non-USB MIDI devices, you could be writing 5x faster
127 to CoreMIDI than the hardware interface can send, causing an
128 unbounded backlog, not to mention that the output stream will be a
129 steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),
130 losing any original timing or rhythm. PortMidi does not guarantee
131 delivery if, over the long run, you write faster than the hardware
132 can send.
133
134 The LIMIT_RATE symbol, if defined (which is the default), enables
135 code to modify timestamps for output to an IAC device as follows:
136
137 Before a packet is formed, the message timestamp is set to the
138 maximum of the PortMidi timestamp (converted to CoreMIDI time)
139 and min_next_time. After each send, min_next_time is updated to
140 the packet time + packet length * delay_per_byte, which limits
141 the scheduled bytes-per-second. Also, after each packet list
142 flush, min_next_time is updated to the maximum of min_next_time
143 and the real time, which prevents many bytes to be scheduled in
144 the past. (We could more directly just say packets are never
145 scheduled in the past, but we prefer to get the current time -- a
146 system call -- only when we perform the more expensive operation
147 of flushing packets, so that's when we update min_next_time to
148 the current real time. If we are sending a lot, we have to flush
149 a lot, so the time will be updated frequently when it matters.)
150
151 This possible adjustment to timestamps can distort accurate
152 timestamps by up to 0.556 us per 3-byte MIDI message.
153
154 Nothing blocks the sender from queueing up an arbitrary number of
155 messages. Timestamps should be used for accurate timing by sending
156 timestamped messages a little ahead of real time, not for
157 scheduling an entire MIDI sequence at once!
158 */
159#define LIMIT_RATE 1
160
161#define SYSEX_BUFFER_SIZE 128
162/* What is the maximum PortMidi device number for an IAC device? A
163 * cleaner design would be to not use the endpoint as our device
164 * representation. Instead, we could have a private extensible struct
165 * to keep all device information, including whether the device is
166 * implemented with the AppleMIDIIACDriver, which we need because we
167 * have to limit the data rate to this particular driver to avoid
168 * dropping messages. Rather than rewrite a lot of code, I am just
169 * allocating 64 bytes to flag which devices are IAC ones. If an IAC
170 * device number is greater than 63, PortMidi will fail to limit
171 * writes to it, but will not complain and will not access memory
172 * outside the 64-element array of char.
173 */
174#define MAX_IAC_NUM 63
175
176#define VERBOSE_ON 1
177#define VERBOSE if (VERBOSE_ON)
178
179#define MIDI_SYSEX 0xf0
180#define MIDI_EOX 0xf7
181#define MIDI_CLOCK 0xf8
182#define MIDI_STATUS_MASK 0x80
183
184// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
185// NULL_REF is our representation of either 0 or NULL
186#ifdef __LP64__
187#define NULL_REF 0
188#else
189#define NULL_REF NULL
190#endif
191
192static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */
193static MIDIPortRef portIn = NULL_REF; /* Input port handle */
194static MIDIPortRef portOut = NULL_REF; /* Output port handle */
195static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */
196
197extern pm_fns_node pm_macosx_in_dictionary;
198extern pm_fns_node pm_macosx_out_dictionary;
199
200typedef struct coremidi_info_struct {
201 int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */
202 UInt64 delta; /* difference between stream time and real time in ns */
203 int sysex_mode; /* middle of sending sysex */
204 uint32_t sysex_word; /* accumulate data when receiving sysex */
205 uint32_t sysex_byte_count; /* count how many received */
206 char error[PM_HOST_ERROR_MSG_LEN];
207 char callback_error[PM_HOST_ERROR_MSG_LEN];
208 Byte packetBuffer[PACKET_BUFFER_SIZE];
209 MIDIPacketList *packetList; /* a pointer to packetBuffer */
210 MIDIPacket *packet;
211 Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
212 MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */
213 /* allow for running status (is running status possible here? -rbd): -cpr */
214 UInt64 min_next_time; /* when can the next send take place? (host time) */
215 int isIACdevice;
216 Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
217 UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
218} coremidi_info_node, *coremidi_info_type;
219
220/* private function declarations */
221MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time
222PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms
223
224char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);
225
226static PmError check_hosterror(OSStatus err, const char *msg)
227{
228 if (err != noErr) {
229 snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);
230 pm_hosterror = TRUE;
231 return pmHostError;
232 }
233 return pmNoError;
234}
235
236
237static PmTimestamp midi_synchronize(PmInternal *midi)
238{
239 coremidi_info_type info = (coremidi_info_type) midi->api_info;
240 UInt64 pm_stream_time_2 = // current time in ns
241 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
242 PmTimestamp real_time; // in ms
243 UInt64 pm_stream_time; // in ns
244 /* if latency is zero and this is an output, there is no
245 time reference and midi_synchronize should never be called */
246 assert(midi->time_proc);
247 assert(midi->is_input || midi->latency != 0);
248 do {
249 /* read real_time between two reads of stream time */
250 pm_stream_time = pm_stream_time_2;
251 real_time = (*midi->time_proc)(midi->time_info);
252 pm_stream_time_2 = AudioConvertHostTimeToNanos(
253 AudioGetCurrentHostTime());
254 /* repeat if more than 0.5 ms has elapsed */
255 } while (pm_stream_time_2 > pm_stream_time + 500000);
256 info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
257 midi->sync_time = real_time;
258 return real_time;
259}
260
261
262/* called when MIDI packets are received */
263static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)
264{
265 PmTimestamp timestamp;
266 MIDIPacket *packet;
267 unsigned int packetIndex;
268 uint32_t now;
269 /* Retrieve the context for this connection */
270 coremidi_info_type info = (coremidi_info_type) midi->api_info;
271 assert(info);
272
273 CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);
274
275 /* synchronize time references every 100ms */
276 now = (*midi->time_proc)(midi->time_info);
277 if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {
278 /* time to resync */
279 now = midi_synchronize(midi);
280 midi->first_message = FALSE;
281 }
282
283 packet = (MIDIPacket *) &newPackets->packet[0];
284 /* hardware devices get untimed messages and apply timestamps. We
285 * want to preserve them because they should be more accurate than
286 * applying the current time here. virtual devices just pass on the
287 * packet->timeStamp, which could be anything. PortMidi says the
288 * PortMidi timestamp is the time the message is received. We do not
289 * know if we are receiving from a device driver or a virtual device.
290 * PortMidi sends to virtual devices get a current timestamp, so we
291 * can treat them as the receive time. If the timestamp is zero,
292 * suggested by CoreMIDI as the value to use for immediate delivery,
293 * then we plug in `now` which is obtained above. If another
294 * application sends bogus non-zero timestamps, we will convert them
295 * to this port's reference time and pass them as event.timestamp.
296 * Receiver beware.
297 */
298 CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "
299 "status %x length %d\n",
300 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
301 AudioGetCurrentHostTime(),
302 packet->data[0], packet->length);
303 for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
304 /* Set the timestamp and dispatch this message */
305 CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n",
306 packet->timeStamp,
307 AudioConvertHostTimeToNanos(packet->timeStamp));
308 if (packet->timeStamp == 0) {
309 timestamp = now;
310 } else {
311 timestamp = (PmTimestamp) /* explicit conversion */ (
312 (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /
313 (UInt64) 1000000);
314 }
315 pm_read_bytes(midi, packet->data, packet->length, timestamp);
316 packet = MIDIPacketNext(packet);
317 }
318}
319
320/* callback for real devices - redirects to read_callback */
321static void device_read_callback(const MIDIPacketList *newPackets,
322 void *refCon, void *connRefCon)
323{
324 read_callback(newPackets, (PmInternal *) connRefCon);
325}
326
327
328/* callback for virtual devices - redirects to read_callback */
329static void virtual_read_callback(const MIDIPacketList *newPackets,
330 void *refCon, void *connRefCon)
331{
332 /* this refCon is the device ID -- if there is a valid ID and
333 the pm_descriptors table has a non-null pointer to a PmInternal,
334 then then device is open and should receive this data */
335 PmDeviceID id = (PmDeviceID) (size_t) refCon;
336 if (id >= 0 && id < pm_descriptor_len) {
337 if (pm_descriptors[id].pub.opened) {
338 /* check for close request (7 reset status bytes): */
339 if (newPackets->numPackets == 1 &&
340 newPackets->packet[0].length == 8 &&
341 /* CoreMIDI declares packets with 4-byte alignment, so we
342 * should be safe to test for 8 0xFF's as 2 32-bit values: */
343 *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&
344 *(SInt32 *) &newPackets->packet[0].data[4] == -1) {
345 CM_DEBUG printf("got close request packet\n");
346 pm_descriptors[id].pub.opened = FALSE;
347 return;
348 } else {
349 read_callback(newPackets, pm_descriptors[id].pm_internal);
350 }
351 }
352 }
353}
354
355
356/* allocate and initialize our internal coremidi connection info */
357static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)
358{
359 coremidi_info_type info = (coremidi_info_type)
360 pm_alloc(sizeof(coremidi_info_node));
361 if (!info) {
362 return NULL;
363 }
364 info->is_virtual = is_virtual;
365 info->delta = 0;
366 info->sysex_mode = FALSE;
367 info->sysex_word = 0;
368 info->sysex_byte_count = 0;
369 info->packet = NULL;
370 info->min_next_time = 0;
371 info->isIACdevice = FALSE;
372 info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
373 info->host_ticks_per_byte =
374 (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
375 info->packetList = (is_input ? NULL :
376 (MIDIPacketList *) info->packetBuffer);
377 return info;
378}
379
380
381static PmError midi_in_open(PmInternal *midi, void *driverInfo)
382{
383 MIDIEndpointRef endpoint;
384 coremidi_info_type info;
385 OSStatus macHostError;
386 int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
387
388 /* if this is an external device, descriptor is a MIDIEndpointRef.
389 * if this is a virtual device for this application, descriptor is NULL.
390 */
391 if (!is_virtual) {
392 endpoint = (MIDIEndpointRef) (intptr_t)
393 pm_descriptors[midi->device_id].descriptor;
394 if (endpoint == NULL_REF) {
395 return pmInvalidDeviceId;
396 }
397 }
398
399 info = create_macosxcm_info(is_virtual, TRUE);
400 midi->api_info = info;
401 if (!info) {
402 return pmInsufficientMemory;
403 }
404 if (!is_virtual) {
405 macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
406 if (macHostError != noErr) {
407 midi->api_info = NULL;
408 pm_free(info);
409 return check_hosterror(macHostError,
410 "MIDIPortConnectSource() in midi_in_open()");
411 }
412 }
413 return pmNoError;
414}
415
416static PmError midi_in_close(PmInternal *midi)
417{
418 MIDIEndpointRef endpoint;
419 OSStatus macHostError;
420 PmError err = pmNoError;
421
422 coremidi_info_type info = (coremidi_info_type) midi->api_info;
423
424 if (!info) return pmBadPtr;
425
426 endpoint = (MIDIEndpointRef) (intptr_t)
427 pm_descriptors[midi->device_id].descriptor;
428 if (endpoint == NULL_REF) {
429 return pmBadPtr;
430 }
431
432 if (!info->is_virtual) {
433 /* shut off the incoming messages before freeing data structures */
434 macHostError = MIDIPortDisconnectSource(portIn, endpoint);
435 /* If the source closes, you get paramErr == -50 here. It seems
436 * possible to monitor changes like sources closing by getting
437 * notifications ALL changes, but the CoreMIDI documentation is
438 * really terrible overall, and it seems easier to just ignore
439 * this host error.
440 */
441 if (macHostError != noErr && macHostError != -50) {
442 pm_hosterror = TRUE;
443 err = check_hosterror(macHostError,
444 "MIDIPortDisconnectSource() in midi_in_close()");
445 }
446 } else {
447 /* make "close virtual port" message */
448 SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;
449 /* memory requirements: packet count (4), timestamp (8), length (2),
450 * data (8). Total: 22, but we allocate plenty more:
451 */
452 Byte packetBuffer[64];
453 MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;
454 MIDIPacket *packet = MIDIPacketListInit(plist);
455 MIDIPacketListAdd(plist, 64, packet, 0, 8,
456 (const Byte *) &close_port_bytes);
457 macHostError = MIDISend(portOut, endpoint, plist);
458 if (macHostError != noErr) {
459 err = check_hosterror(macHostError, "MIDISend() (PortMidi close "
460 "port packet) in midi_in_close()");
461 }
462 /* when packet is delivered, callback thread will clear opened;
463 * we must wait for that before removing the input queues etc.
464 * Maybe this could use signals of some kind, but if signals use
465 * locks, locks can cause priority inversion problems, so we will
466 * just sleep as needed. On the MIDI timescale, inserting a 0.5ms
467 * latency should be OK, as the application has no business
468 * opening/closing devices during time-critical moments.
469 *
470 * We expect the MIDI thread to close the device quickly (<0.5ms),
471 * but we wait up to 50ms in case something terrible happens like
472 * getting paged out in the middle of deliving packets to this
473 * virtual device. If there is still no response, we time out and
474 * force the close without the MIDI thread (even this will probably
475 * succeed - the problem would be: this thread proceeds to delete
476 * the input queues, and the freed memory is reallocated and
477 * overwritten so that queues are no longer usable. Meanwhile,
478 * the MIDI thread has already begun to deliver packets, so the
479 * check for opened == TRUE passed, but MIDI thread does not insert
480 * into queue until queue is freed, reallocated and overwritten.
481 */
482 for (int i = 0; i < 100; i++) { /* up to 50ms delay */
483 if (!pm_descriptors[midi->device_id].pub.opened) {
484 break;
485 }
486 usleep(500); /* 0.5ms */
487 }
488 pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */
489 }
490 midi->api_info = NULL;
491 pm_free(info);
492 return err;
493}
494
495
496static PmError midi_out_open(PmInternal *midi, void *driverInfo)
497{
498 coremidi_info_type info;
499 int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
500
501 info = create_macosxcm_info(is_virtual, FALSE);
502 if (midi->device_id <= MAX_IAC_NUM) {
503 info->isIACdevice = isIAC[midi->device_id];
504 CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);
505 }
506 midi->api_info = info;
507 if (!info) {
508 return pmInsufficientMemory;
509 }
510 return pmNoError;
511}
512
513
514static PmError midi_out_close(PmInternal *midi)
515{
516 coremidi_info_type info = (coremidi_info_type) midi->api_info;
517 if (!info) return pmBadPtr;
518 midi->api_info = NULL;
519 pm_free(info);
520 return pmNoError;
521}
522
523
524/* MIDIDestinationCreate apparently cannot create a virtual device
525 * without a callback and a "refCon" parameter, but when we create
526 * a virtual device, we do not want a PortMidi stream yet -- that
527 * should wait for the user to open the stream. So, for the refCon,
528 * use the PortMidi device ID. The callback will check if the
529 * device is opened within PortMidi, and if so, use the pm_descriptors
530 * table to locate the corresponding PmStream.
531 */
532static PmError midi_create_virtual(int is_input, const char *name,
533 void *device_info)
534{
535 OSStatus macHostError;
536 MIDIEndpointRef endpoint;
537 CFStringRef nameRef;
538 PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,
539 (is_input ? &pm_macosx_in_dictionary :
540 &pm_macosx_out_dictionary));
541 if (id < 0) { /* error -- out of memory or name conflict? */
542 return id;
543 }
544
545 nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
546 if (is_input) {
547 macHostError = MIDIDestinationCreate(client, nameRef,
548 virtual_read_callback, (void *) (intptr_t) id, &endpoint);
549 } else {
550 macHostError = MIDISourceCreate(client, nameRef, &endpoint);
551 }
552 CFRelease(nameRef);
553
554 if (macHostError != noErr) {
555 /* undo the device we just allocated */
556 pm_undo_add_device(id);
557 return check_hosterror(macHostError, (is_input ?
558 "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :
559 "MIDISourceCreateWithProtocol() in midi_create_virtual()"));
560 }
561
562 /* Do we have a manufacturer name? If not, set to "PortMidi" */
563 const char *mfr_name = "PortMidi";
564 PmSysDepInfo *info = (PmSysDepInfo *) device_info;
565 /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */
566 if (info && info->structVersion >= 210) {
567 int i;
568 for (i = 0; i < info->length; i++) { /* search for key */
569 if (info->properties[i].key == pmKeyCoreMidiManufacturer) {
570 mfr_name = info->properties[i].value;
571 break;
572 } /* no other keys are recognized; they are ignored */
573 }
574 }
575 nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);
576 MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);
577 CFRelease(nameRef);
578
579 pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;
580 return id;
581}
582
583
584static PmError midi_delete_virtual(PmDeviceID id)
585{
586 MIDIEndpointRef endpoint;
587 OSStatus macHostError;
588
589 endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;
590 if (endpoint == NULL_REF) {
591 return pmBadPtr;
592 }
593 macHostError = MIDIEndpointDispose(endpoint);
594 return check_hosterror(macHostError,
595 "MIDIEndpointDispose() in midi_in_close()");
596}
597
598
599static PmError midi_abort(PmInternal *midi)
600{
601 OSStatus macHostError;
602 MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
603 pm_descriptors[midi->device_id].descriptor;
604 macHostError = MIDIFlushOutput(endpoint);
605 return check_hosterror(macHostError,
606 "MIDIFlushOutput() in midi_abort()");
607}
608
609
610static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
611{
612 OSStatus macHostError = 0;
613 coremidi_info_type info = (coremidi_info_type) midi->api_info;
614 MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
615 pm_descriptors[midi->device_id].descriptor;
616 assert(info);
617 assert(endpoint);
618 if (info->packet != NULL) {
619 /* out of space, send the buffer and start refilling it */
620 /* update min_next_time each flush to support rate limit */
621 UInt64 host_now = AudioGetCurrentHostTime();
622 if (host_now > info->min_next_time)
623 info->min_next_time = host_now;
624 if (info->is_virtual) {
625 macHostError = MIDIReceived(endpoint, info->packetList);
626 } else {
627 macHostError = MIDISend(portOut, endpoint, info->packetList);
628 }
629 info->packet = NULL; /* indicate no data in packetList now */
630 }
631 return check_hosterror(macHostError, (info->is_virtual ?
632 "MIDIReceived() in midi_write()" :
633 "MIDISend() in midi_write()"));
634}
635
636
637static PmError send_packet(PmInternal *midi, Byte *message,
638 unsigned int messageLength, MIDITimeStamp timestamp)
639{
640 PmError err;
641 coremidi_info_type info = (coremidi_info_type) midi->api_info;
642 assert(info);
643
644 CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "
645 "(host %lld)\n",
646 message[0], info->packet, messageLength, timestamp,
647 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
648 AudioGetCurrentHostTime());
649 info->packet = MIDIPacketListAdd(info->packetList,
650 sizeof(info->packetBuffer), info->packet,
651 timestamp, messageLength, message);
652#if LIMIT_SEND_RATE
653 info->byte_count += messageLength;
654#endif
655 if (info->packet == NULL) {
656 /* out of space, send the buffer and start refilling it */
657 /* make midi->packet non-null to fool midi_write_flush into sending */
658 info->packet = (MIDIPacket *) 4;
659 /* timestamp is 0 because midi_write_flush ignores timestamp since
660 * timestamps are already in packets. The timestamp parameter is here
661 * because other API's need it. midi_write_flush can be called
662 * from system-independent code that must be cross-API.
663 */
664 if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
665 info->packet = MIDIPacketListInit(info->packetList);
666 assert(info->packet); /* if this fails, it's a programming error */
667 info->packet = MIDIPacketListAdd(info->packetList,
668 sizeof(info->packetBuffer), info->packet,
669 timestamp, messageLength, message);
670 assert(info->packet); /* can't run out of space on first message */
671 }
672 return pmNoError;
673}
674
675
676static PmError midi_write_short(PmInternal *midi, PmEvent *event)
677{
678 PmTimestamp when = event->timestamp;
679 PmMessage what = event->message;
680 MIDITimeStamp timestamp;
681 coremidi_info_type info = (coremidi_info_type) midi->api_info;
682 Byte message[4];
683 unsigned int messageLength;
684
685 if (info->packet == NULL) {
686 info->packet = MIDIPacketListInit(info->packetList);
687 /* this can never fail, right? failure would indicate something
688 unrecoverable */
689 assert(info->packet);
690 }
691
692 /* PortMidi specifies that incoming timestamps are the receive
693 * time. Devices attach their receive times, but virtual devices
694 * do not. Instead, they pass along whatever timestamp was sent to
695 * them. We do not know if we are connected to real or virtual
696 * device. To avoid wild timestamps on the receiving end, we
697 * consider 2 cases: PortMidi timestamp is zero or latency is
698 * zero. Both mean send immediately, so we attach the current time
699 * which will go out immediately and arrive with a sensible
700 * timestamp (not zero and not zero mapped to the client's local
701 * time). Otherwise, we assume the timestamp is reasonable. It
702 * might be slighly in the past, but we pass it along after
703 * translation to MIDITimeStamp units.
704 *
705 * Compute timestamp: use current time if timestamp is zero or
706 * latency is zero. Both mean no timing and send immediately.
707 */
708 if (when == 0 || midi->latency == 0) {
709 timestamp = AudioGetCurrentHostTime();
710 } else { /* translate PortMidi time + latency to CoreMIDI time */
711 timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
712 info->delta;
713 timestamp = AudioConvertNanosToHostTime(timestamp);
714 }
715
716 message[0] = Pm_MessageStatus(what);
717 message[1] = Pm_MessageData1(what);
718 message[2] = Pm_MessageData2(what);
719 messageLength = pm_midi_length((int32_t) what);
720
721#ifdef LIMIT_RATE
722 /* Make sure we go forward in time. */
723 if (timestamp < info->min_next_time) {
724 timestamp = info->min_next_time;
725 }
726 /* Note that if application is way behind and slowly catching up, then
727 * timestamps could be increasing faster than real time, and since
728 * timestamps are used to estimate data rate, our estimate could be
729 * low, causing CoreMIDI to drop packets. This seems very unlikely.
730 */
731 if (info->isIACdevice || info->is_virtual) {
732 info->min_next_time = timestamp + messageLength *
733 info->host_ticks_per_byte;
734 }
735#endif
736 /* Add this message to the packet list */
737 return send_packet(midi, message, messageLength, timestamp);
738}
739
740
741static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
742{
743 UInt64 when_ns;
744 coremidi_info_type info = (coremidi_info_type) midi->api_info;
745 assert(info);
746 info->sysex_byte_count = 0;
747
748 /* compute timestamp */
749 if (when == 0) when = midi->now;
750 /* if latency == 0, midi->now is not valid. We will just set it to zero */
751 if (midi->latency == 0) when = 0;
752 when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
753 info->delta;
754 info->sysex_timestamp =
755 (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
756 UInt64 now; /* only make system time call when writing a virtual port */
757 if (info->is_virtual && info->sysex_timestamp <
758 (now = AudioGetCurrentHostTime())) {
759 info->sysex_timestamp = now;
760 }
761
762 if (info->packet == NULL) {
763 info->packet = MIDIPacketListInit(info->packetList);
764 /* this can never fail, right? failure would indicate something
765 unrecoverable */
766 assert(info->packet);
767 }
768 return pmNoError;
769}
770
771
772static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)
773{
774 PmError err;
775 coremidi_info_type info = (coremidi_info_type) midi->api_info;
776 assert(info);
777
778#ifdef LIMIT_RATE
779 /* make sure we go foreward in time */
780 if (info->sysex_timestamp < info->min_next_time)
781 info->sysex_timestamp = info->min_next_time;
782
783 if (info->isIACdevice) {
784 info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *
785 info->host_ticks_per_byte;
786 }
787#endif
788
789 /* now send what's in the buffer */
790 err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,
791 info->sysex_timestamp);
792 info->sysex_byte_count = 0;
793 if (err != pmNoError) {
794 info->packet = NULL; /* flush everything in the packet list */
795 }
796 return err;
797}
798
799
800static PmError midi_write_byte(PmInternal *midi, unsigned char byte,
801 PmTimestamp timestamp)
802{
803 coremidi_info_type info = (coremidi_info_type) midi->api_info;
804 assert(info);
805 if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
806 PmError err = midi_end_sysex(midi, timestamp);
807 if (err != pmNoError) return err;
808 }
809 info->sysex_buffer[info->sysex_byte_count++] = byte;
810 return pmNoError;
811}
812
813
814static PmError midi_write_realtime(PmInternal *midi, PmEvent *event)
815{
816 /* to send a realtime message during a sysex message, first
817 flush all pending sysex bytes into packet list */
818 PmError err = midi_end_sysex(midi, 0);
819 if (err != pmNoError) return err;
820 /* then we can just do a normal midi_write_short */
821 return midi_write_short(midi, event);
822}
823
824
825static unsigned int midi_check_host_error(PmInternal *midi)
826{
827 return FALSE;
828}
829
830
831MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
832{
833 UInt64 nanos;
834 if (timestamp <= 0) {
835 return (MIDITimeStamp)0;
836 } else {
837 nanos = (UInt64)timestamp * (UInt64)1000000;
838 return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
839 }
840}
841
842
843PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
844{
845 UInt64 nanos;
846 nanos = AudioConvertHostTimeToNanos(timestamp);
847 return (PmTimestamp)(nanos / (UInt64)1000000);
848}
849
850
851//
852// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
853//////////////////////////////////////
854// Obtain the name of an endpoint without regard for whether it has connections.
855// The result should be released by the caller.
856CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,
857 int *iac_flag)
858{
859 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
860 CFStringRef str;
861 *iac_flag = FALSE;
862
863 // begin with the endpoint's name
864 str = NULL;
865 MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
866 if (str != NULL) {
867 CFStringAppend(result, str);
868 CFRelease(str);
869 }
870 MIDIEntityRef entity = NULL_REF;
871 MIDIEndpointGetEntity(endpoint, &entity);
872 if (entity == NULL_REF) {
873 // probably virtual
874 return result;
875 }
876 if (!isExternal) { /* detect IAC devices */
877 //extern const CFStringRef kMIDIPropertyDriverOwner;
878 MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);
879 if (str != NULL) {
880 char s[32]; /* driver name may truncate, but that's OK */
881 CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);
882 s[31] = 0; /* make sure it is terminated just to be safe */
883 CM_DEBUG printf("driver %s\n", s);
884 *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);
885 }
886 }
887
888 if (CFStringGetLength(result) == 0) {
889 // endpoint name has zero length -- try the entity
890 str = NULL;
891 MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
892 if (str != NULL) {
893 CFStringAppend(result, str);
894 CFRelease(str);
895 }
896 }
897 // now consider the device's name
898 MIDIDeviceRef device = NULL_REF;
899 MIDIEntityGetDevice(entity, &device);
900 if (device == NULL_REF)
901 return result;
902
903 str = NULL;
904 MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
905 if (CFStringGetLength(result) == 0) {
906 CFRelease(result);
907 return str;
908 }
909 if (str != NULL) {
910 // if an external device has only one entity, throw away
911 // the endpoint name and just use the device name
912 if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
913 CFRelease(result);
914 return str;
915 } else {
916 if (CFStringGetLength(str) == 0) {
917 CFRelease(str);
918 return result;
919 }
920 // does the entity name already start with the device name?
921 // (some drivers do this though they shouldn't)
922 // if so, do not prepend
923 if (CFStringCompareWithOptions(result, /* endpoint name */
924 str, /* device name */
925 CFRangeMake(0, CFStringGetLength(str)), 0) !=
926 kCFCompareEqualTo) {
927 // prepend the device name to the entity name
928 if (CFStringGetLength(result) > 0)
929 CFStringInsert(result, 0, CFSTR(" "));
930 CFStringInsert(result, 0, str);
931 }
932 CFRelease(str);
933 }
934 }
935 return result;
936}
937
938
939// Obtain the name of an endpoint, following connections.
940// The result should be released by the caller.
941static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,
942 int *iac_flag)
943{
944 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
945 CFStringRef str;
946 OSStatus err;
947 long i;
948
949 // Does the endpoint have connections?
950 CFDataRef connections = NULL;
951 long nConnected = 0;
952 bool anyStrings = false;
953 err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,
954 &connections);
955 if (connections != NULL) {
956 // It has connections, follow them
957 // Concatenate the names of all connected devices
958 nConnected = CFDataGetLength(connections) /
959 (int32_t) sizeof(MIDIUniqueID);
960 if (nConnected) {
961 const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
962 for (i = 0; i < nConnected; ++i, ++pid) {
963 MIDIUniqueID id = EndianS32_BtoN(*pid);
964 MIDIObjectRef connObject;
965 MIDIObjectType connObjectType;
966 err = MIDIObjectFindByUniqueID(id, &connObject,
967 &connObjectType);
968 if (err == noErr) {
969 if (connObjectType == kMIDIObjectType_ExternalSource ||
970 connObjectType == kMIDIObjectType_ExternalDestination) {
971 // Connected to an external device's endpoint (>=10.3)
972 str = EndpointName((MIDIEndpointRef)(connObject), true,
973 iac_flag);
974 } else {
975 // Connected to an external device (10.2)
976 // (or something else, catch-all)
977 str = NULL;
978 MIDIObjectGetStringProperty(connObject,
979 kMIDIPropertyName, &str);
980 }
981 if (str != NULL) {
982 if (anyStrings)
983 CFStringAppend(result, CFSTR(", "));
984 else anyStrings = true;
985 CFStringAppend(result, str);
986 CFRelease(str);
987 }
988 }
989 }
990 }
991 CFRelease(connections);
992 }
993 if (anyStrings)
994 return result; // caller should release result
995
996 CFRelease(result);
997
998 // Here, either the endpoint had no connections, or we failed to
999 // obtain names for any of them.
1000 return EndpointName(endpoint, false, iac_flag);
1001}
1002
1003
1004char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)
1005{
1006 /* Thanks to Dan Wilcox for fixes for Unicode handling */
1007 CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);
1008 CFIndex utf16_len = CFStringGetLength(fullName) + 1;
1009 CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(
1010 utf16_len, kCFStringEncodingUTF8) + 1;
1011 char* pmname = (char *) pm_alloc(CFStringGetLength(fullName) + 1);
1012
1013 /* copy the string into our buffer; note that there may be some wasted
1014 space, but the total waste is not large */
1015 CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);
1016
1017 /* clean up */
1018 if (fullName) CFRelease(fullName);
1019 return pmname;
1020}
1021
1022
1023pm_fns_node pm_macosx_in_dictionary = {
1024 none_write_short,
1025 none_sysex,
1026 none_sysex,
1027 none_write_byte,
1028 none_write_short,
1029 none_write_flush,
1030 none_synchronize,
1031 midi_in_open,
1032 midi_abort,
1033 midi_in_close,
1034 success_poll,
1035 midi_check_host_error
1036};
1037
1038pm_fns_node pm_macosx_out_dictionary = {
1039 midi_write_short,
1040 midi_begin_sysex,
1041 midi_end_sysex,
1042 midi_write_byte,
1043 midi_write_realtime,
1044 midi_write_flush,
1045 midi_synchronize,
1046 midi_out_open,
1047 midi_abort,
1048 midi_out_close,
1049 success_poll,
1050 midi_check_host_error
1051};
1052
1053
1054/* We do nothing with callbacks, but generating the callbacks also
1055 * updates CoreMIDI state. Callback may not be essential, but calling
1056 * the CFRunLoopRunInMode is necessary.
1057 */
1058void cm_notify(const MIDINotification *msg, void *refCon)
1059{
1060 /* for debugging, trace change notifications:
1061 const char *descr[] = {
1062 "undefined (0)",
1063 "kMIDIMsgSetupChanged",
1064 "kMIDIMsgObjectAdded",
1065 "kMIDIMsgObjectRemoved",
1066 "kMIDIMsgPropertyChanged",
1067 "kMIDIMsgThruConnectionsChanged",
1068 "kMIDIMsgSerialPortOwnerChanged",
1069 "kMIDIMsgIOError"};
1070
1071 printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,
1072 descr[(int) msg->messageID]);
1073 */
1074 return;
1075}
1076
1077
1078PmError pm_macosxcm_init(void)
1079{
1080 ItemCount numInputs, numOutputs, numDevices;
1081 MIDIEndpointRef endpoint;
1082 OSStatus macHostError = noErr;
1083 char *error_text;
1084
1085 memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */
1086
1087 /* Register interface CoreMIDI with create_virtual fn */
1088 pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);
1089 /* no check for error return because this always succeeds */
1090
1091 /* Determine the number of MIDI devices on the system */
1092 numDevices = MIDIGetNumberOfDevices();
1093
1094 /* Return prematurely if no devices exist on the system
1095 Note that this is not an error. There may be no devices.
1096 Pm_CountDevices() will return zero, which is correct and
1097 useful information
1098 */
1099 if (numDevices <= 0) {
1100 return pmNoError;
1101 }
1102
1103 /* Initialize the client handle */
1104 if (client == NULL_REF) {
1105 macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,
1106 &client);
1107 } else { /* see notes above on device scanning */
1108 for (int i = 0; i < 100; i++) {
1109 // look for any changes before scanning for devices
1110 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
1111 if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */
1112 }
1113 }
1114 if (macHostError != noErr) {
1115 error_text = "MIDIClientCreate() in pm_macosxcm_init()";
1116 goto error_return;
1117 }
1118 numInputs = MIDIGetNumberOfSources();
1119 numOutputs = MIDIGetNumberOfDestinations();
1120
1121 /* Create the input port */
1122 macHostError = MIDIInputPortCreate(client, CFSTR("Input port"),
1123 device_read_callback, NULL, &portIn);
1124 if (macHostError != noErr) {
1125 error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
1126 goto error_return;
1127 }
1128
1129 /* Create the output port */
1130 macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
1131 if (macHostError != noErr) {
1132 error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
1133 goto error_return;
1134 }
1135
1136 /* Iterate over the MIDI input devices */
1137 for (int i = 0; i < numInputs; i++) {
1138 int iac_flag;
1139 endpoint = MIDIGetSource(i);
1140 if (endpoint == NULL_REF) {
1141 continue;
1142 }
1143 /* Register this device with PortMidi */
1144 pm_add_device("CoreMIDI",
1145 cm_get_full_endpoint_name(endpoint, &iac_flag), TRUE, FALSE,
1146 (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);
1147 }
1148
1149 /* Iterate over the MIDI output devices */
1150 for (int i = 0; i < numOutputs; i++) {
1151 int iac_flag;
1152 PmDeviceID id;
1153 endpoint = MIDIGetDestination(i);
1154 if (endpoint == NULL_REF) {
1155 continue;
1156 }
1157 /* Register this device with PortMidi */
1158 id = pm_add_device("CoreMIDI",
1159 cm_get_full_endpoint_name(endpoint, &iac_flag), FALSE, FALSE,
1160 (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);
1161 /* if this is an IAC device, tuck that info away for write functions */
1162 if (iac_flag && id <= MAX_IAC_NUM) {
1163 isIAC[id] = TRUE;
1164 }
1165 }
1166 return pmNoError;
1167
1168error_return:
1169 pm_macosxcm_term(); /* clear out any opened ports */
1170 return check_hosterror(macHostError, error_text);
1171}
1172
1173void pm_macosxcm_term(void)
1174{
1175 /* docs say do not explicitly dispose of client
1176 if (client != NULL_REF) MIDIClientDispose(client); */
1177 if (portIn != NULL_REF) MIDIPortDispose(portIn);
1178 if (portOut != NULL_REF) MIDIPortDispose(portOut);
1179}
diff --git a/portmidi/pm_mac/pmmacosxcm.h b/portmidi/pm_mac/pmmacosxcm.h
new file mode 100755
index 0000000..166a0b7
--- /dev/null
+++ b/portmidi/pm_mac/pmmacosxcm.h
@@ -0,0 +1,4 @@
1/* system-specific definitions */
2
3PmError pm_macosxcm_init(void);
4void pm_macosxcm_term(void);
diff --git a/portmidi/pm_sndio/pmsndio.c b/portmidi/pm_sndio/pmsndio.c
new file mode 100644
index 0000000..0c1ea11
--- /dev/null
+++ b/portmidi/pm_sndio/pmsndio.c
@@ -0,0 +1,365 @@
1/* pmsndio.c -- PortMidi os-dependent code */
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <sndio.h>
6#include <string.h>
7#include <poll.h>
8#include <errno.h>
9#include <pthread.h>
10#include "portmidi.h"
11#include "pmutil.h"
12#include "pminternal.h"
13#include "porttime.h"
14
15#define NDEVS 9
16#define SYSEX_MAXLEN 1024
17
18#define SYSEX_START 0xf0
19#define SYSEX_END 0xf7
20
21extern pm_fns_node pm_sndio_in_dictionary;
22extern pm_fns_node pm_sndio_out_dictionary;
23
24/* length of voice and common messages (status byte included) */
25unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
26unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
27
28struct mio_dev {
29 char name[16];
30 struct mio_hdl *hdl;
31 int mode;
32 char errmsg[PM_HOST_ERROR_MSG_LEN];
33 pthread_t thread;
34} devs[NDEVS];
35
36static void set_mode(struct mio_dev *, unsigned int);
37
38void pm_init()
39{
40 int i, j, k = 0;
41 char devices[][16] = {"midithru", "rmidi", "midi", "snd"};
42
43 /* default */
44 strcpy(devs[0].name, MIO_PORTANY);
45 pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k],
46 &pm_sndio_in_dictionary);
47 pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k],
48 &pm_sndio_out_dictionary);
49 k++;
50
51 for (i = 0; i < 4; i++) {
52 for (j = 0; j < 2; j++) {
53 sprintf(devs[k].name, "%s/%d", devices[i], j);
54 pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k],
55 &pm_sndio_in_dictionary);
56 pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k],
57 &pm_sndio_out_dictionary);
58 k++;
59 }
60 }
61
62 // this is set when we return to Pm_Initialize, but we need it
63 // now in order to (successfully) call Pm_CountDevices()
64 pm_initialized = TRUE;
65 pm_default_input_device_id = 0;
66 pm_default_output_device_id = 1;
67}
68
69void pm_term(void)
70{
71 int i;
72 for(i = 0; i < NDEVS; i++) {
73 if (devs[i].mode != 0) {
74 set_mode(&devs[i], 0);
75 if (devs[i].thread) {
76 pthread_join(devs[i].thread, NULL);
77 devs[i].thread = NULL;
78 }
79 }
80 }
81}
82
83PmDeviceID Pm_GetDefaultInputDeviceID() {
84 Pm_Initialize();
85 return pm_default_input_device_id;
86}
87
88PmDeviceID Pm_GetDefaultOutputDeviceID() {
89 Pm_Initialize();
90 return pm_default_output_device_id;
91}
92
93void *pm_alloc(size_t s) { return malloc(s); }
94
95void pm_free(void *ptr) { free(ptr); }
96
97/* midi_message_length -- how many bytes in a message? */
98static int midi_message_length(PmMessage message)
99{
100 unsigned char st = message & 0xff;
101 if (st >= 0xf8)
102 return 1;
103 else if (st >= 0xf0)
104 return common_len[st & 7];
105 else if (st >= 0x80)
106 return voice_len[(st >> 4) & 7];
107 else
108 return 0;
109}
110
111void* input_thread(void *param)
112{
113 PmInternal *midi = (PmInternal*)param;
114 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
115 struct pollfd pfd[1];
116 nfds_t nfds;
117 unsigned char st = 0, c = 0;
118 int rc, revents, idx = 0, len = 0;
119 size_t todo = 0;
120 unsigned char buf[0x200], *p;
121 PmEvent pm_ev, pm_ev_rt;
122 unsigned char sysex_data[SYSEX_MAXLEN];
123
124 while(dev->mode & MIO_IN) {
125 if (todo == 0) {
126 nfds = mio_pollfd(dev->hdl, pfd, POLLIN);
127 rc = poll(pfd, nfds, 100);
128 if (rc < 0) {
129 if (errno == EINTR)
130 continue;
131 break;
132 }
133 revents = mio_revents(dev->hdl, pfd);
134 if (!(revents & POLLIN))
135 continue;
136
137 todo = mio_read(dev->hdl, buf, sizeof(buf));
138 if (todo == 0)
139 continue;
140 p = buf;
141 }
142 c = *p++;
143 todo--;
144
145 if (c >= 0xf8) {
146 pm_ev_rt.message = c;
147 pm_ev_rt.timestamp = Pt_Time();
148 pm_read_short(midi, &pm_ev_rt);
149 } else if (c == SYSEX_END) {
150 /* note: PortMidi is designed to avoid the need for SYSEX_MAXLEN.
151 With the new implementation of pm_read_bytes, it would be
152 better to simply call pm_read_bytes() and let it parse buf,
153 which can contain any number of whole or partial messages with
154 interleaved realtime messages. I did not change the code because
155 I cannot test it. -RBD */
156 if (st == SYSEX_START) {
157 sysex_data[idx++] = c;
158 pm_read_bytes(midi, sysex_data, idx, Pt_Time());
159 }
160 st = 0;
161 idx = 0;
162 } else if (c == SYSEX_START) {
163 st = c;
164 idx = 0;
165 sysex_data[idx++] = c;
166 } else if (c >= 0xf0) {
167 pm_ev.message = c;
168 len = common_len[c & 7];
169 st = c;
170 idx = 1;
171 } else if (c >= 0x80) {
172 pm_ev.message = c;
173 len = voice_len[(c >> 4) & 7];
174 st = c;
175 idx = 1;
176 } else if (st == SYSEX_START) {
177 if (idx == SYSEX_MAXLEN) {
178 fprintf(stderr, "the message is too long\n");
179 idx = st = 0;
180 } else {
181 sysex_data[idx++] = c;
182 }
183 } else if (st) {
184 if (idx == 0 && st != SYSEX_START)
185 pm_ev.message |= (c << (8 * idx++));
186 pm_ev.message |= (c << (8 * idx++));
187 if (idx == len) {
188 pm_read_short(midi, &pm_ev);
189 if (st >= 0xf0)
190 st = 0;
191 idx = 0;
192 }
193 }
194 }
195
196 pthread_exit(NULL);
197 return NULL;
198}
199
200static void set_mode(struct mio_dev *dev, unsigned int mode) {
201 if (dev->mode != 0)
202 mio_close(dev->hdl);
203 dev->mode = 0;
204 if (mode != 0)
205 dev->hdl = mio_open(dev->name, mode, 0);
206 if (dev->hdl)
207 dev->mode = mode;
208}
209
210static PmError sndio_out_open(PmInternal *midi, void *driverInfo)
211{
212 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
213
214 if (dev->mode & MIO_OUT)
215 return pmNoError;
216
217 set_mode(dev, dev->mode | MIO_OUT);
218 if (!(dev->mode & MIO_OUT)) {
219 snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
220 "mio_open (output) failed: %s\n", dev->name);
221 return pmHostError;
222 }
223
224 return pmNoError;
225}
226
227static PmError sndio_in_open(PmInternal *midi, void *driverInfo)
228{
229 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
230
231 if (dev->mode & MIO_IN)
232 return pmNoError;
233
234 set_mode(dev, dev->mode | MIO_IN);
235 if (!(dev->mode & MIO_IN)) {
236 snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
237 "mio_open (input) failed: %s\n", dev->name);
238 return pmHostError;
239 }
240 pthread_attr_t attr;
241 pthread_attr_init(&attr);
242 pthread_create(&dev->thread, &attr, input_thread, ( void* )midi);
243 return pmNoError;
244}
245
246static PmError sndio_out_close(PmInternal *midi)
247{
248 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
249
250 if (dev->mode & MIO_OUT)
251 set_mode(dev, dev->mode & ~MIO_OUT);
252 return pmNoError;
253}
254
255static PmError sndio_in_close(PmInternal *midi)
256{
257 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
258
259 if (dev->mode & MIO_IN) {
260 set_mode(dev, dev->mode & ~MIO_IN);
261 pthread_join(dev->thread, NULL);
262 dev->thread = NULL;
263 }
264 return pmNoError;
265}
266
267static PmError sndio_abort(PmInternal *midi)
268{
269 return pmNoError;
270}
271
272static PmTimestamp sndio_synchronize(PmInternal *midi)
273{
274 return 0;
275}
276
277static PmError do_write(struct mio_dev *dev, const void *addr, size_t nbytes)
278{
279 size_t w = mio_write(dev->hdl, addr, nbytes);
280
281 if (w != nbytes) {
282 snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
283 "mio_write failed, bytes written:%zu\n", w);
284 return pmHostError;
285 }
286 return pmNoError;
287}
288
289static PmError sndio_write_byte(PmInternal *midi, unsigned char byte,
290 PmTimestamp timestamp)
291{
292 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
293
294 return do_write(dev, &byte, 1);
295}
296
297static PmError sndio_write_short(PmInternal *midi, PmEvent *event)
298{
299 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
300 int nbytes = midi_message_length(event->message);
301
302 if (midi->latency > 0) {
303 /* XXX the event should be queued for later playback */
304 return do_write(dev, &event->message, nbytes);
305 } else {
306 return do_write(dev, &event->message, nbytes);
307 }
308 return pmNoError;
309}
310
311static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp)
312{
313 return pmNoError;
314}
315
316PmError sndio_sysex(PmInternal *midi, PmTimestamp timestamp)
317{
318 return pmNoError;
319}
320
321static unsigned int sndio_has_host_error(PmInternal *midi)
322{
323 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
324
325 return (dev->errmsg[0] != '\0');
326}
327
328static void sndio_get_host_error(PmInternal *midi, char *msg, unsigned int len)
329{
330 struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
331
332 strlcpy(msg, dev->errmsg, len);
333 dev->errmsg[0] = '\0';
334}
335
336pm_fns_node pm_sndio_in_dictionary = {
337 none_write_short,
338 none_sysex,
339 none_sysex,
340 none_write_byte,
341 none_write_short,
342 none_write_flush,
343 sndio_synchronize,
344 sndio_in_open,
345 sndio_abort,
346 sndio_in_close,
347 success_poll,
348 sndio_has_host_error,
349};
350
351pm_fns_node pm_sndio_out_dictionary = {
352 sndio_write_short,
353 sndio_sysex,
354 sndio_sysex,
355 sndio_write_byte,
356 sndio_write_short,
357 sndio_write_flush,
358 sndio_synchronize,
359 sndio_out_open,
360 sndio_abort,
361 sndio_out_close,
362 none_poll,
363 sndio_has_host_error,
364};
365
diff --git a/portmidi/pm_sndio/pmsndio.h b/portmidi/pm_sndio/pmsndio.h
new file mode 100644
index 0000000..4096d9b
--- /dev/null
+++ b/portmidi/pm_sndio/pmsndio.h
@@ -0,0 +1,5 @@
1/* pmsndio.h */
2
3extern PmDeviceID pm_default_input_device_id;
4extern PmDeviceID pm_default_output_device_id;
5
diff --git a/portmidi/pm_test/CMakeLists.txt b/portmidi/pm_test/CMakeLists.txt
new file mode 100644
index 0000000..ae0fe48
--- /dev/null
+++ b/portmidi/pm_test/CMakeLists.txt
@@ -0,0 +1,46 @@
1# CMake file to build tests in this directory: pm_test
2
3# set the build directory to be in portmidi, not in portmidi/pm_test
4# this is required for Xcode:
5if(APPLE)
6set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
7endif(APPLE)
8
9# if(WIN32)
10# if(NOT BUILD_SHARED_LIBS)
11 # /MDd is multithread debug DLL, /MTd is multithread debug
12 # /MD is multithread DLL, /MT is multithread. Change to static:
13# include(../pm_win/static.cmake)
14# endif()
15# endif(WIN32)
16
17if(HAIKU)
18 add_compile_options(-fPIC) # Haiku x86_64 needs this explicitly
19endif()
20
21macro(add_test name)
22 add_executable(${name} ${name}.c)
23 target_link_libraries(${name} PRIVATE portmidi)
24 set_property(TARGET ${name} PROPERTY MSVC_RUNTIME_LIBRARY
25 "MultiThreaded$<$<CONFIG:Debug>:Debug>${MSVCRT_DLL}")
26endmacro(add_test)
27
28add_test(testio)
29add_test(midithread)
30add_test(midithru)
31add_test(sysex)
32add_test(latency)
33add_test(mm)
34add_test(midiclock)
35add_test(qtest)
36add_test(fast)
37add_test(fastrcv)
38add_test(pmlist)
39if(WIN32)
40# windows does not implement Pm_CreateVirtualInput or Pm_CreateVirtualOutput
41else(WIN32)
42add_test(recvvirtual)
43add_test(sendvirtual)
44add_test(multivirtual)
45add_test(virttest)
46endif(WIN32)
diff --git a/portmidi/pm_test/README.txt b/portmidi/pm_test/README.txt
new file mode 100644
index 0000000..6c0c7ab
--- /dev/null
+++ b/portmidi/pm_test/README.txt
@@ -0,0 +1,363 @@
1README.txt - for pm_test directory
2
3These are all test programs for PortMidi
4
5Because device numbers depend on the system, there is no automated
6script to run all tests on PortMidi.
7
8To run the full set of tests manually:
9
10Note: everything is run from the ../Debug or ../Release directory.
11Actual or example input is marked with >>, e.g., >>0 means type 0<ENTER>
12Comments are shown in square brackets [like this]
13
141. ./qtest -- output should show a bunch of tests and no error message.
15
162. ./testio [test input]
17Latency in ms: >>0
18enter your choice... >>1
19Type input number: >>6 [pick a working input device]
20[play some notes, look for note-on (0x90) with pitch and velocity data]
21
223. ./testio [test input (fail w/assert)]
23Latency in ms: >>0
24enter your choice... >>2
25Type input number: >>6 [pick a working input device]
26[play some notes, program will abort after 5 messages
27(this test only applies to a Debug build, otherwise
28the assert() macro is disabled.)]
29
304. ./testio [test input (fail w/NULL assign)]
31Latency in ms: >>0
32enter your choice... >>3
33Type input number: >>6 [pick a working input device]
34[play some notes, program will Segmentation fault after 5 messages
35(this test may not Segfault in the Release build; if not
36try testing with a Debug build.)]
37
385. ./testio [test output, no latency]
39Latency in ms: >>0
40enter your choice... >>4
41Type output number: >>2 [pick a working output device]
42>> [type ENTER when prompted (7 times)]
43[hear note on, note off, note on, note off, chord]
44
456. ./testio [test output, latency > 0]
46Latency in ms: >>300
47enter your choice... >>4
48Type output number: >>2 [pick a working output device]
49>> [type ENTER when prompted (7 times)]
50[hear note on, note off, note on, note off, arpeggiated chord
51 (delay of 300ms should be apparent)]
52
537. ./testio [for both, no latency]
54Latency in ms: >>0
55enter your choice... >>5
56Type input number: >>6 [pick a working input device]
57Type output number: >>2 [pick a working output device]
58[play notes on input, hear them on output]
59
608. ./testio [for both, latency > 0]
61Latency in ms: >>300
62enter your choice... >>5
63Type input number: >>6 [pick a working input device]
64Type output number: >>2 [pick a working output device]
65[play notes on input, hear them on output (delay of 300ms is apparent)]
66
679. ./testio [stream test]
68Latency in ms: >>0 [does not matter]
69enter your choice... >>6
70Type output number: >>2 [pick a working output device]
71>> [type ENTER to start]
72[hear 4 notes: C D E F# at one note per second, then all turn off]
73ready to close and terminate... (type ENTER) :>> [type ENTER (twice)]
74
7510. ./testio [isochronous out]
76Latency in ms: >>300
77enter your choice... >>7
78Type output number: >>2 [pick a working output device]
79ready to send program 1 change... (type ENTER): >> [type ENTER]
80[hear 80 notes, exactly 4 notes per second, no jitter]
81
8211. ./latency [no MIDI, histogram]
83Choose timer period (in ms, >= 1): >>1
84? >>1 [No MIDI traffic option]
85[wait about 10 seconds]
86>> [type ENTER]
87[output should be something like ... Maximum latency: 1 milliseconds]
88
8912. ./latency [MIDI input, histogram]
90Choose timer period (in ms, >= 1): >>1
91? >>2 [MIDI input option]
92Midi input device number: >>6 [pick a working input device]
93[wait about 5 seconds, play input for 10 seconds ]
94>> [type ENTER]
95[output should be something like ... Maximum latency: 3 milliseconds]
96
9713. ./latency [MIDI output, histogram]
98Choose timer period (in ms, >= 1): >>1
99? >>3 [MIDI output option]
100Midi output device number: >>2 [pick a working output device]
101Midi output should be sent every __ callback iterations: >>50
102[wait until you hear notes for 5 or 10 seconds]
103>> [type ENTER to stop]
104[output should be something like ... Maximum latency: 2 milliseconds]
105
10614. ./latency [MIDI input and output, histogram]
107Choose timer period (in ms, >= 1): >>1
108? >>4 [MIDI input and output option]
109Midi input device number: >>6 [pick a working input device]
110Midi output device number: >>2 [pick a working output device]
111Midi output should be sent every __ callback iterations: >>50
112[wait until you hear notes, simultaneously play notes for 5 or 10 seconds]
113>> [type ENTER to stop]
114[output should be something like ... Maximum latency: 1 milliseconds]
115
11615. ./mm [test with device input]
117Type input device number: >>6 [pick a working input device]
118[play some notes, see notes printed]
119>>q [Type q ENTER when finished to exit]
120
12116. ./midithread -i 6 -o 2 [use working input/output device numbers]
122>>5 [enter a transposition number]
123[play some notes, hear parallel 4ths]
124>>q [quit after ENTER a couple of times]
125
12617. ./midiclock [in one shell]
127 ./mm [in another shell]
128[Goal is send clock messages to MIDI monitor program. This requires
129 either a hardware loopback (MIDI cable from OUT to IN on interface)
130 or a software loopback (macOS IAC bus or ALSA MIDI Through Port)]
131[For midiclock application:]
132 Type output device number: >>0 [pick a device with loopback]
133 Type ENTER to start MIDI CLOCK: >> [type ENTER]
134[For mm application:]
135 Type input device number: >>1 [pick device with loopback]
136 [Wait a few seconds]
137 >>s [to get Clock Count]
138 >>s [expect to get a higher Clock Count]
139[For midiclock application:]
140 >>c [turn off clocks]
141[For mm application:]
142 >>s [to get Clock Count]
143 >>s [expect to Clock Count stays the same]
144[For midiclock application:]
145 >>t [turn on time code, see Time Code Quarter Frame messages from mm]
146 >>q [to quit]
147[For mm application:]
148 >>q [to quit]
149
15018. ./midithru -i 6 -o 2 [use working input/output device numbers]
151[Play notes on input evice; notes are sent immediately and also with a
152 2 sec delay to the output device; program terminates in 60 seconds or
153 when you play B3 (B below Middle C)]
154>> [ENTER to exit]
155
15619. ./recvvirtual -h [in one shell, macOS and Linux only]
157 ./recvvirtual -m vvv [for mac, or -c vvv -p vvvport for linux]
158 ./testio [in another shell]
159[For testio application:]
160 Latency in ms: >>0
161 enter your choice... >>4 [test output]
162 Type output number: >>9 [select the "portmidi (output)" device]
163 [type ENTER to each prompt, see that recvvirtual "Got message 0"
164 through "Got message 9"]
165 >> [ENTER to quit]
166[For recvvirtual application:]
167 >> [ENTER to quit]
168
16920. ./sendvirtual -h [in one shell, macOS and Linux only]
170 ./sendvirtual -m vvv [for mac, or -c vvv -p vvvport for linux]
171 ./mm [in another shell]
172[For mm application:]
173 Type input device number: >>10 [select the "portmidi" device]
174[For sendvirtual application:]
175 Type ENTER to send messages: >> [type ENTER]
176 [see NoteOn and off messages received by mm for Key 60-64]
177 >> [ENTER to quit]
178[For mm application:]
179 >>q [and ENTER twice to quit]
180
18121. ./sysex [no latency]
182[This requires either a hardware loopback (MIDI cable from OUT to IN
183 on interface) or a software loopback (macOS IAC bus or ALSA MIDI
184 Through Port)]
185>>l [for loopback test]
186Type output device number: >>0 [pick output device to loopback]
187Latency in milliseconds: >>0
188Type input device number: >>0 [pick input device for loopback]
189[Program will send 100,000 bytes. After awhile, program will quit.
190 You can read the Cummulative bytes/sec value.]
191
19222. ./sysex [latency > 0]
193[This requires either a hardware loopback (MIDI cable from OUT to IN
194 on interface) or a software loopback (macOS IAC bus or ALSA MIDI
195 Through Port)]
196>>l [for loopback test]
197Type output device number: >>0 [pick output device to loopback]
198Latency in milliseconds: >>100
199Type input device number: >>0 [pick input device for loopback]
200[Program will send 100,000 bytes. After awhile, program will quit. You
201 can read the Cummulative bytes/sec value; it is affected by latency.]
202
20323. ./fast [no latency]
204 ./fastrcv [in another shell]
205[This is a speed check, especially for macOSX IAC bus connections,
206 which are known to drop messages if you send messages too fast.
207 fast and fastrcv must use a loopback to function.]
208[In fastrcv:]
209 Input device number: >>1 [pick a non-hardware device if possible]
210[In fast:]
211 Latency in ms: >>0
212 Rate in messages per second: >>10000
213 Duration in seconds: >>10
214 Output device number: >>0 [pick a non-hardware device if possible]
215 sending output...
216[see message counts and times; on Linux you should get about 10000
217 messages/s; on macOS you should get about 1800 messages/s; Windows
218 does not have software ports, so data rate might be limited by the
219 loopback device you use.]
220
221Check output of fastrcv: there should be no errors, just msg/sec.]
222
22324. ./fast [latency > 0]
224 ./fastrcv [in another shell]
225[This is a speed check, especially for macOSX IAC bus connections,
226 which are known to drop messages if you send messages too fast.
227 fast and fastrcv must use a loopback to function.]
228[In fastrcv:]
229 Input device number: >>1 [pick a non-hardware device if possible]
230[In fast:]
231 Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
232 Rate in messages per second: >>10000
233 Duration in seconds: >>10
234 Output device number: >>0 [pick a non-hardware device if possible]
235 sending output...
236[see message counts and times; on Linux you should get about 10000
237 messages/s; on macOS you should get about 1800 messages/s; Windows
238 does not have software ports, so data rate might be limited by the
239 loopback device you use.]
240
241Check output of fastrcv: there should be no errors, just msg/sec.]
242
24325. ./fast [virtual output port, latency = 0, macOS and Linux only]
244 ./fastrcv [in another shell]
245[Start fast first:]
246 Latency in ms: >>0
247 Rate in messages per second: >>10000
248 Duration in seconds: >>10
249 Output device number: >>9 [enter number listed for "Create virtual
250 port named 'fast' (output)"]
251 Pausing so you can connect a receiver to the newly created
252 "fast" port. Type ENTER to proceed:
253[In fastrcv:]
254 Input device number: >>3 [pick the device named "fast (input)"]
255[In fast:]
256 >> [type ENTER to start]
257[see message counts and times as above ]
258
259Check output of fastrcv: there should be no errors, just msg/sec.]
260
26126. ./fast [virtual output port, latency > 0, macOS and Linux only]
262 ./fastrcv [in another shell]
263[Start fast first:]
264 Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
265 Rate in messages per second: >>10000
266 Duration in seconds: >>10
267 Output device number: >>9 [enter number listed for "Create virtual
268 port named 'fast' (output)"]
269 Pausing so you can connect a receiver to the newly created
270 "fast" port. Type ENTER to proceed:
271[In fastrcv:]
272 Input device number: >>3 [pick the device named "fast (input)"]
273[In fast:]
274 >> [type ENTER to start]
275[see message counts and times as above ]
276
277Check output of fastrcv: there should be no errors, just msg/sec.]
278
27927. ./fast [latency = 0, macOS and Linux only]
280 ./fastrcv [virtual input port, in another shell]
281[In fastrcv:]
282 Input device number: >>8 [enter number listed for "Create virtual
283 port named 'fastrcv' (input)"]
284[In fast:]
285 Latency in ms: >>0
286 Rate in messages per second: >>10000
287 Duration in seconds: >>10
288 Output device number: >>7 [pick the device named "fastrcv (output)"]
289 sending output...
290[see message counts and times as above ]
291
292Check output of fastrcv: there should be no errors, just msg/sec.]
293
29428. ./fast [latency > 0, macOS and Linux only]
295 ./fastrcv [virtual input port, in another shell]
296[In fastrcv:]
297 Input device number: >>8 [enter number listed for "Create virtual
298 port named 'fastrcv' (input)"]
299[In fast:]
300 Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
301 Rate in messages per second: >>10000
302 Duration in seconds: >>10
303 Output device number: >>7 [pick the device named "fastrcv (output)"]
304 sending output...
305[see message counts and times as above ]
306
307Check output of fastrcv: there should be no errors, just msg/sec.]
308
30929. ./midithru -v -n [virtual input and output, macOS and Linux only]
310 ./fast [latency = 0]
311 ./fastrcv [in another shell]
312[Start midithru first, it will run for 60 seconds]
313[In fastrcv:]
314 Input device number: >>3 [pick the device named
315 port named "midithru (input)"]
316[In fast:]
317 Latency in ms: >>0
318 Rate in messages per second: >>10000
319 Duration in seconds: >>10
320 Output device number: >>8 [pick the device named "midithru (output)"]
321 sending output...
322[see message counts and times as above, on Mac, output from fast to
323 midithru AND output from midithru to fastrcv are rate limited, so
324 as in other tests, it will take more than 10s to receive all the
325 messages and the receiving message rate will be about 1800 messages/second]
326
32730. ./multivirtual [macOS and Linux only]
328 ./testio
329 ./testio
330[Start multivirtual first]
331[In first testio:]
332 Latency in ms: >>0
333 enter your choice... >>5 [test both]
334 Type input number: >>1 [pick portmidi1 (input)
335 Type output number: >>4 [pick portmidi1 (output)
336[In second testio:]
337 Latency in ms: >>10
338 enter your choice... >>5 [test both]
339 Type input number: >>2 [pick portmidi2 (input)
340 Type output number: >>5 [pick portmidi2 (output)
341[In multivirtual:]
342 Type ENTER to send messages: >> [type ENTER to start]
343[see that each testio gets 11 messages (0 to 10) at reasonable times
344 (e.g. 2077 to 7580, and the "@" times (real times) should match the
345 timestamps). multivirtual should also report reasonable times and
346 line near the end of output should be "Got 11 messages from
347 portmidi1 and 11 from portmidi2; expected 11."]
348
34931. ./multivirtual [macOS and Linux only]
350 ./multivirtual
351[Second instance should report "PortMidi call failed...
352 PortMidi: Cannot create virtual device: name is taken"]
353
35432. pmlist
355 ./pmlist [check the output]
356 [plug in or remove a device]
357 >> [type RETURN]
358 [check for changes in device list]
359 >>q
360
361
362
363
diff --git a/portmidi/pm_test/fast.c b/portmidi/pm_test/fast.c
new file mode 100644
index 0000000..102697e
--- /dev/null
+++ b/portmidi/pm_test/fast.c
@@ -0,0 +1,290 @@
1/* fast.c -- send many MIDI messages very fast.
2 *
3 * This is a stress test created to explore reports of
4 * pm_write() call blocking (forever) on Linux when
5 * sending very dense MIDI sequences.
6 *
7 * Modified 8 Aug 2017 with -n to send expired timestamps
8 * to test a theory about why Linux ALSA hangs in Audacity.
9 *
10 * Modified 9 Aug 2017 with -m, -p to test when timestamps are
11 * wrapping from negative to positive or positive to negative.
12 *
13 * Roger B. Dannenberg, Aug 2017
14 */
15
16#include "portmidi.h"
17#include "porttime.h"
18#include "stdlib.h"
19#include "stdio.h"
20#include "string.h"
21#include "assert.h"
22
23#define DEVICE_INFO NULL
24#define DRIVER_INFO NULL
25#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
26
27#define STRING_MAX 80 /* used for console input */
28// need to get declaration for Sleep()
29#ifdef WIN32
30#include "windows.h"
31#else
32#include <unistd.h>
33#define Sleep(n) usleep(n * 1000)
34#endif
35
36
37int32_t latency = 0;
38int32_t msgrate = 0;
39int deviceno = -9999;
40int duration = 0;
41int expired_timestamps = FALSE;
42int use_timeoffset = 0;
43
44/* read a number from console */
45/**/
46int get_number(const char *prompt)
47{
48 int n = 0, i;
49 fputs(prompt, stdout);
50 while (n != 1) {
51 n = scanf("%d", &i);
52 while (getchar() != '\n') ;
53 }
54 return i;
55}
56
57
58/* get_time -- the time reference. Normally, this will be the default
59 * time, Pt_Time(), but if you use the -p or -m option, the time
60 * reference will start at an offset of -10s for -m, or
61 * maximum_time - 10s for -p, so that we can observe what happens
62 * with negative time or when time changes sign or wraps (by
63 * generating output for more than 10s).
64 */
65PmTimestamp get_time(void *info)
66{
67 PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset);
68 return now;
69}
70
71
72void 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 = pmNoError;
78 /* output buffer size should be a little more than
79 msgrate * latency / 1000. PortMidi will guarantee
80 a minimum of latency / 2 */
81 int buffer_size = msgrate * latency / 900;
82 PmTimestamp start, now;
83 int msgcnt = 0;
84 int polling_count = 0;
85 int pitch = 60;
86 int printtime = 1000;
87
88 /* It is recommended to start timer before PortMidi */
89 TIME_START;
90
91 /* open output device */
92 if (deviceno == Pm_CountDevices()) {
93 deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO);
94 if (deviceno >= 0) {
95 err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
96 get_time, NULL, latency);
97 pause = TRUE;
98 }
99 } else if (err >= pmNoError) {
100 err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
101 get_time, NULL, latency);
102 }
103 if (err == pmHostError) {
104 Pm_GetHostErrorText(line, STRING_MAX);
105 printf("PortMidi found host error...\n %s\n", line);
106 goto done;
107 } else if (err < 0) {
108 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
109 goto done;
110 }
111 printf("Midi Output opened with %ld ms latency.\n", (long) latency);
112 if (pause) {
113 printf("Pausing so you can connect a receiver to the newly created\n"
114 " \"fast\" port. Type ENTER to proceed: ");
115 while (getchar() != '\n') ;
116 }
117 /* wait a sec after printing previous line */
118 start = get_time(NULL) + 1000;
119 while (start > get_time(NULL)) {
120 Sleep(10);
121 }
122 printf("sending output...\n");
123 fflush(stdout); /* make sure message goes to console */
124
125 /* every 10ms send on/off pairs at timestamps set to current time */
126 now = get_time(NULL);
127 /* if expired_timestamps, we want to send timestamps that have
128 * expired. They should be sent immediately, but there's a suggestion
129 * that negative delay might cause problems in the ALSA implementation
130 * so this is something we can test using the -n flag.
131 */
132 if (expired_timestamps) {
133 now = now - 2 * latency;
134 }
135
136 while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) {
137 /* how many messages do we send? Total should be
138 * (elapsed * rate) / 1000
139 */
140 int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000;
141 /* always send until pitch would be 60 so if we run again, the
142 next pitch (60) will be expected */
143 if (msgcnt < send_total) {
144 if ((msgcnt & 1) == 0) {
145 Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100));
146 } else {
147 Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0));
148 /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */
149 pitch = (pitch - 59) % 12 + 60;
150 }
151 msgcnt += 1;
152 if (((PmTimestamp) (now - start)) >= printtime) {
153 printf("%d at %dms, polling count %d\n", msgcnt, now - start,
154 polling_count);
155 fflush(stdout); /* make sure message goes to console */
156 printtime += 1000; /* next msg in 1s */
157 }
158 }
159 now = get_time(NULL);
160 polling_count++;
161 }
162 /* close device (this not explicitly needed in most implementations) */
163 printf("ready to close and terminate... (type RETURN):");
164 while (getchar() != '\n') ;
165
166 Pm_Close(midi);
167 done:
168 Pm_Terminate();
169 printf("done closing and terminating...\n");
170}
171
172
173void show_usage()
174{
175 printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] "
176 "[-n] [-p] [-m]\n"
177 ", where latency is in ms,\n"
178 " rate is messages per second,\n"
179 " device is the PortMidi device number,\n"
180 " dur is the length of the test in seconds,\n"
181 " -n means send timestamps in the past,\n"
182 " -p means use a large positive time offset,\n"
183 " -m means use a large negative time offset, and\n"
184 " -h means help.\n");
185}
186
187int main(int argc, char *argv[])
188{
189 int default_in;
190 int default_out;
191 char *deflt;
192 int i = 0;
193 int latency_valid = FALSE;
194 int rate_valid = FALSE;
195 int device_valid = FALSE;
196 int dur_valid = FALSE;
197
198 if (sizeof(void *) == 8)
199 printf("Apparently this is a 64-bit machine.\n");
200 else if (sizeof(void *) == 4)
201 printf ("Apparently this is a 32-bit machine.\n");
202
203 if (argc <= 1) {
204 show_usage();
205 } else {
206 for (i = 1; i < argc; i++) {
207 if (strcmp(argv[i], "-h") == 0) {
208 show_usage();
209 } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
210 i = i + 1;
211 latency = atoi(argv[i]);
212 printf("Latency will be %ld\n", (long) latency);
213 latency_valid = TRUE;
214 } else if (strcmp(argv[i], "-r") == 0) {
215 i = i + 1;
216 msgrate = atoi(argv[i]);
217 printf("Rate will be %d messages/second\n", msgrate);
218 rate_valid = TRUE;
219 } else if (strcmp(argv[i], "-d") == 0) {
220 i = i + 1;
221 deviceno = atoi(argv[i]);
222 printf("Device will be %d\n", deviceno);
223 } else if (strcmp(argv[i], "-s") == 0) {
224 i = i + 1;
225 duration = atoi(argv[i]);
226 printf("Duration will be %d seconds\n", duration);
227 dur_valid = TRUE;
228 } else if (strcmp(argv[i], "-n") == 0) {
229 printf("Sending expired timestamps (-n)\n");
230 expired_timestamps = TRUE;
231 } else if (strcmp(argv[i], "-p") == 0) {
232 printf("Time offset set to 2147473648 (-p)\n");
233 use_timeoffset = 2147473648;
234 } else if (strcmp(argv[i], "-m") == 0) {
235 printf("Time offset set to -10000 (-m)\n");
236 use_timeoffset = -10000;
237 } else {
238 show_usage();
239 }
240 }
241 }
242
243 if (!latency_valid) {
244 // coerce to known size
245 latency = (int32_t) get_number("Latency in ms: ");
246 }
247
248 if (!rate_valid) {
249 // coerce from "%d" to known size
250 msgrate = (int32_t) get_number("Rate in messages per second: ");
251 }
252
253 if (!dur_valid) {
254 duration = get_number("Duration in seconds: ");
255 }
256
257 /* list device information */
258 default_in = Pm_GetDefaultInputDeviceID();
259 default_out = Pm_GetDefaultOutputDeviceID();
260 for (i = 0; i < Pm_CountDevices(); i++) {
261 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
262 if (info->output) {
263 printf("%d: %s, %s", i, info->interf, info->name);
264 if (i == deviceno) {
265 device_valid = TRUE;
266 deflt = "selected ";
267 } else if (i == default_out) {
268 deflt = "default ";
269 } else {
270 deflt = "";
271 }
272 printf(" (%soutput)\n", deflt);
273 }
274 }
275 printf("%d: Create virtual port named \"fast\"", i);
276 if (i == deviceno) {
277 device_valid = TRUE;
278 deflt = "selected ";
279 } else {
280 deflt = "";
281 }
282 printf(" (%soutput)\n", deflt);
283
284 if (!device_valid) {
285 deviceno = get_number("Output device number: ");
286 }
287
288 fast_test();
289 return 0;
290}
diff --git a/portmidi/pm_test/fastrcv.c b/portmidi/pm_test/fastrcv.c
new file mode 100644
index 0000000..dabf9fa
--- /dev/null
+++ b/portmidi/pm_test/fastrcv.c
@@ -0,0 +1,255 @@
1/* fastrcv.c -- send many MIDI messages very fast.
2 *
3 * This is a stress test created to explore reports of
4 * pm_write() call blocking (forever) on Linux when
5 * sending very dense MIDI sequences.
6 *
7 * Modified 8 Aug 2017 with -n to send expired timestamps
8 * to test a theory about why Linux ALSA hangs in Audacity.
9 *
10 * Roger B. Dannenberg, Aug 2017
11 */
12
13#include "portmidi.h"
14#include "porttime.h"
15#include "stdlib.h"
16#include "stdio.h"
17#include "string.h"
18#include "assert.h"
19
20#define INPUT_BUFFER_SIZE 1000 /* big to avoid losing any input */
21#define DEVICE_INFO NULL
22#define DRIVER_INFO NULL
23#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
24#define TIME_INFO NULL
25#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
26
27#define STRING_MAX 80 /* used for console input */
28// need to get declaration for Sleep()
29#ifdef WIN32
30#include "windows.h"
31#else
32#include <unistd.h>
33#define Sleep(n) usleep(n * 1000)
34#endif
35
36
37int deviceno = -9999;
38int verbose = FALSE;
39
40
41static void prompt_and_exit(void)
42{
43 printf("type ENTER...");
44 while (getchar() != '\n') ;
45 /* this will clean up open ports: */
46 exit(-1);
47}
48
49
50static PmError checkerror(PmError err)
51{
52 if (err == pmHostError) {
53 /* it seems pointless to allocate memory and copy the string,
54 * so I will do the work of Pm_GetHostErrorText directly
55 */
56 char errmsg[80];
57 Pm_GetHostErrorText(errmsg, 80);
58 printf("PortMidi found host error...\n %s\n", errmsg);
59 prompt_and_exit();
60 } else if (err < 0) {
61 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
62 prompt_and_exit();
63 }
64 return err;
65}
66
67
68/* read a number from console */
69/**/
70int get_number(const char *prompt)
71{
72 int n = 0, i;
73 fputs(prompt, stdout);
74 while (n != 1) {
75 n = scanf("%d", &i);
76 while (getchar() != '\n') ;
77 }
78 return i;
79}
80
81
82void fastrcv_test()
83{
84 PmStream * midi;
85 PmError status, length;
86 PmEvent buffer[1];
87 PmTimestamp start;
88 /* every 10ms read all messages, keep counts */
89 /* every 1000ms, print report */
90 int msgcnt = 0;
91 /* expect repeating sequence of 60 through 71, alternating on/off */
92 int expected_pitch = 60;
93 int expected_on = TRUE;
94 int report_time;
95 PmTimestamp last_timestamp = -1;
96 PmTimestamp last_delta = -1;
97
98 /* It is recommended to start timer before PortMidi */
99 TIME_START;
100
101 /* open output device */
102 if (deviceno == Pm_CountDevices()) {
103 int id = Pm_CreateVirtualInput("fastrcv", NULL, DEVICE_INFO);
104 if (id < 0) checkerror(id); /* error reporting */
105 checkerror(Pm_OpenInput(&midi, id, DRIVER_INFO,
106 INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO));
107 } else {
108 Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE,
109 TIME_PROC, TIME_INFO);
110 }
111 printf("Midi Input opened.\n");
112
113 /* wait a sec after printing previous line */
114 start = Pt_Time() + 1000;
115 while (start > Pt_Time()) {
116 Sleep(10);
117 }
118
119 report_time = Pt_Time() + 1000; /* report every 1s */
120 while (TRUE) {
121 PmTimestamp now = Pt_Time();
122 status = Pm_Poll(midi);
123 if (status == TRUE) {
124 length = Pm_Read(midi, buffer, 1);
125 if (length > 0) {
126 int status = Pm_MessageStatus(buffer[0].message);
127 if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */
128 status = 0x90;
129 buffer[0].message = Pm_Message(status,
130 Pm_MessageData1(buffer[0].message), 0);
131 }
132 /* only listen to NOTEON messages */
133 if (status == 0x90) {
134 int pitch = Pm_MessageData1(buffer[0].message);
135 int vel = Pm_MessageData2(buffer[0].message);
136 int is_on = (vel > 0);
137 if (verbose) {
138 printf("Note pitch %d vel %d\n", pitch, vel);
139 }
140 msgcnt++;
141 if (pitch != expected_pitch || expected_on != is_on) {
142 printf("Unexpected note-on: pitch %d vel %d, "
143 "expected: pitch %d Note%s\n", pitch, vel,
144 expected_pitch, (expected_on ? "On" : "Off"));
145 }
146 if (is_on) {
147 expected_on = FALSE;
148 expected_pitch = pitch;
149 } else {
150 expected_on = TRUE;
151 expected_pitch = (pitch + 1) % 72;
152 if (expected_pitch < 60) expected_pitch = 60;
153 }
154 if (last_timestamp >= 0) {
155 last_delta = buffer[0].timestamp - last_timestamp;
156 }
157 last_timestamp = buffer[0].timestamp;
158 }
159 }
160 }
161 if (now >= report_time) {
162 printf("%d msgs/sec", msgcnt);
163 /* if available, print the last timestamp and last delta time */
164 if (last_timestamp >= 0) {
165 printf(" last timestamp %d", (int) last_timestamp);
166 last_timestamp = -1;
167 }
168 if (last_delta >= 0) {
169 printf(" last delta time %d", (int) last_delta);
170 last_delta = -1;
171 }
172 printf("\n");
173 report_time += 1000;
174 msgcnt = 0;
175 }
176 }
177}
178
179
180void show_usage()
181{
182 printf("Usage: fastrcv [-h] [-v] [-d device], where\n"
183 "device is the PortMidi device number,\n"
184 "-h means help,\n"
185 "-v means verbose (print messages)\n");
186}
187
188int main(int argc, char *argv[])
189{
190 int default_in;
191 int default_out;
192 char *deflt;
193
194 int i = 0;
195 int test_input = 0, test_output = 0, test_both = 0;
196 int stream_test = 0;
197 int device_valid = FALSE;
198
199 if (sizeof(void *) == 8)
200 printf("Apparently this is a 64-bit machine.\n");
201 else if (sizeof(void *) == 4)
202 printf ("Apparently this is a 32-bit machine.\n");
203
204 if (argc <= 1) {
205 show_usage();
206 } else {
207 for (i = 1; i < argc; i++) {
208 if (strcmp(argv[i], "-h") == 0) {
209 show_usage();
210 } else if (strcmp(argv[i], "-v") == 0) {
211 verbose = TRUE;
212 } else if (strcmp(argv[i], "-d") == 0) {
213 i = i + 1;
214 deviceno = atoi(argv[i]);
215 printf("Device will be %d\n", deviceno);
216 } else {
217 show_usage();
218 }
219 }
220 }
221
222 /* list device information */
223 default_in = Pm_GetDefaultInputDeviceID();
224 default_out = Pm_GetDefaultOutputDeviceID();
225 for (i = 0; i < Pm_CountDevices(); i++) {
226 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
227 if (!info->output) {
228 printf("%d: %s, %s", i, info->interf, info->name);
229 if (i == deviceno) {
230 device_valid = TRUE;
231 deflt = "selected ";
232 } else if (i == default_out) {
233 deflt = "default ";
234 } else {
235 deflt = "";
236 }
237 printf(" (%sinput)\n", deflt);
238 }
239 }
240 printf("%d: Create virtual port named \"fastrcv\"", i);
241 if (i == deviceno) {
242 device_valid = TRUE;
243 deflt = "selected ";
244 } else {
245 deflt = "";
246 }
247 printf(" (%sinput)\n", deflt);
248
249 if (!device_valid) {
250 deviceno = get_number("Input device number: ");
251 }
252
253 fastrcv_test();
254 return 0;
255}
diff --git a/portmidi/pm_test/latency.c b/portmidi/pm_test/latency.c
new file mode 100755
index 0000000..06ea80d
--- /dev/null
+++ b/portmidi/pm_test/latency.c
@@ -0,0 +1,287 @@
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
70CHANGE LOG
71
7218-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
90int get_number(const char *prompt);
91
92PtTimestamp previous_callback_time = 0;
93
94int period; /* milliseconds per callback */
95
96int histogram[HIST_LEN];
97int max_latency = 0; /* worst latency observed */
98int out_of_range = 0; /* how many points outside of HIST_LEN? */
99
100int test_in, test_out; /* test MIDI in and/or out? */
101int output_period; /* output MIDI every __ iterations if test_out true */
102int iteration = 0;
103PmStream *in, *out;
104int note_on = 0; /* is the note currently on? */
105
106/* callback function for PortTime -- computes histogram */
107void pt_callback(PtTimestamp timestamp, void *userData)
108{
109 PtTimestamp difference = timestamp - previous_callback_time - period;
110 previous_callback_time = timestamp;
111
112 /* allow 5 seconds for the system to settle down */
113 if (timestamp < 5000) return;
114
115 iteration++;
116 /* send a note on/off if user requested it */
117 if (test_out && (iteration % output_period == 0)) {
118 PmEvent buffer[1];
119 buffer[0].timestamp = Pt_Time();
120 if (note_on) {
121 /* note off */
122 buffer[0].message = Pm_Message(0x90, 60, 0);
123 note_on = 0;
124 } else {
125 /* note on */
126 buffer[0].message = Pm_Message(0x90, 60, 100);
127 note_on = 1;
128 }
129 Pm_Write(out, buffer, 1);
130 iteration = 0;
131 }
132
133 /* read all waiting events (if user requested) */
134 if (test_in) {
135 PmError status;
136 PmEvent buffer[1];
137 do {
138 status = Pm_Poll(in);
139 if (status == TRUE) {
140 Pm_Read(in,buffer,1);
141 }
142 } while (status == TRUE);
143 }
144
145 if (difference < 0) return; /* ignore when system is "catching up" */
146
147 /* update the histogram */
148 if (difference < HIST_LEN) {
149 histogram[difference]++;
150 } else {
151 out_of_range++;
152 }
153
154 if (max_latency < difference) max_latency = difference;
155}
156
157
158int main()
159{
160 int i;
161 int len;
162 int choice;
163 PtTimestamp stop;
164 printf("Latency histogram.\n");
165 period = 0;
166 while (period < 1) {
167 period = get_number("Choose timer period (in ms, >= 1): ");
168 }
169 printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
170 "1. No MIDI traffic",
171 "2. MIDI input",
172 "3. MIDI output",
173 "4. MIDI input and output");
174 choice = get_number("? ");
175 switch (choice) {
176 case 1: test_in = 0; test_out = 0; break;
177 case 2: test_in = 1; test_out = 0; break;
178 case 3: test_in = 0; test_out = 1; break;
179 case 4: test_in = 1; test_out = 1; break;
180 default: assert(0);
181 }
182 if (test_in || test_out) {
183 /* list device information */
184 for (i = 0; i < Pm_CountDevices(); i++) {
185 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
186 if ((test_in && info->input) ||
187 (test_out && info->output)) {
188 printf("%d: %s, %s", i, info->interf, info->name);
189 if (info->input) printf(" (input)");
190 if (info->output) printf(" (output)");
191 printf("\n");
192 }
193 }
194 /* open stream(s) */
195 if (test_in) {
196 int i = get_number("MIDI input device number: ");
197 Pm_OpenInput(&in,
198 i,
199 NULL,
200 INPUT_BUFFER_SIZE,
201 (PmTimestamp (*)(void *)) Pt_Time,
202 NULL);
203 /* turn on filtering; otherwise, input might overflow in the
204 5-second period before timer callback starts reading midi */
205 Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
206 }
207 if (test_out) {
208 int i = get_number("MIDI output device number: ");
209 PmEvent buffer[1];
210 Pm_OpenOutput(&out,
211 i,
212 NULL,
213 OUTPUT_BUFFER_SIZE,
214 (PmTimestamp (*)(void *)) Pt_Time,
215 NULL,
216 0); /* no latency scheduling */
217
218 /* send a program change to force a status byte -- this fixes
219 a problem with a buggy linux MidiSport driver, and shouldn't
220 hurt anything else
221 */
222 buffer[0].timestamp = 0;
223 buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
224 Pm_Write(out, buffer, 1);
225
226 output_period = get_number(
227 "MIDI out should be sent every __ callback iterations: ");
228
229 assert(output_period >= 1);
230 }
231 }
232
233 printf("Latency measurements will start in 5 seconds. "
234 "Type return to stop: ");
235 Pt_Start(period, &pt_callback, 0);
236 while (getchar() != '\n') ;
237 stop = Pt_Time();
238 Pt_Stop();
239
240 /* courteously turn off the last note, if necessary */
241 if (note_on) {
242 PmEvent buffer[1];
243 buffer[0].timestamp = Pt_Time();
244 buffer[0].message = Pm_Message(0x90, 60, 0);
245 Pm_Write(out, buffer, 1);
246 }
247
248 /* print the histogram */
249 printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
250 printf("Latency(ms) Number of occurrences\n");
251 /* avoid printing beyond last non-zero histogram entry */
252 len = min(HIST_LEN, max_latency + 1);
253 for (i = 0; i < len; i++) {
254 printf("%2d %10d\n", i, histogram[i]);
255 }
256 printf("Number of points greater than %dms: %d\n",
257 HIST_LEN - 1, out_of_range);
258 printf("Maximum latency: %d milliseconds\n", max_latency);
259 printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
260 printf("than the numbers reported here.\n");
261 printf("Type return to exit...");
262 while (getchar() != '\n') ;
263
264 if(choice == 2)
265 Pm_Close(in);
266 else if(choice == 3)
267 Pm_Close(out);
268 else if(choice == 4)
269 {
270 Pm_Close(in);
271 Pm_Close(out);
272 }
273 return 0;
274}
275
276
277/* read a number from console */
278int get_number(const char *prompt)
279{
280 int n = 0, i;
281 fputs(prompt, stdout);
282 while (n != 1) {
283 n = scanf("%d", &i);
284 while (getchar() != '\n') ;
285 }
286 return i;
287}
diff --git a/portmidi/pm_test/midiclock.c b/portmidi/pm_test/midiclock.c
new file mode 100644
index 0000000..f0a6897
--- /dev/null
+++ b/portmidi/pm_test/midiclock.c
@@ -0,0 +1,282 @@
1/* miditime.c -- a test program that sends midi clock and MTC */
2
3#include "portmidi.h"
4#include "porttime.h"
5#include <stdlib.h>
6#include <stdio.h>
7#include <string.h>
8#include <assert.h>
9#include <ctype.h>
10
11#ifndef false
12#define false 0
13#define true 1
14#endif
15
16#define private static
17typedef 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
42boolean done = false;
43PmStream *midi;
44/* shared flags to control callback output generation: */
45boolean clock_running = false;
46boolean send_start_stop = false;
47boolean time_code_running = false;
48boolean active = false; /* tells callback to do its thing */
49float 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 */
64void 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/**/
166int get_number(const char *prompt)
167{
168 int n = 0, i;
169 fputs(prompt, stdout);
170 while (n != 1) {
171 n = scanf("%d", &i);
172 while (getchar() != '\n') ;
173 }
174 return i;
175}
176
177/****************************************************************************
178* showhelp
179* Effect: print help text
180****************************************************************************/
181
182private void showhelp()
183{
184 printf("\n");
185 printf("t toggles sending MIDI Time Code (MTC)\n");
186 printf("c toggles sending MIDI CLOCK (initially on)\n");
187 printf("m to set tempo (from 1bpm to 300bpm)\n");
188 printf("q quits\n");
189 printf("\n");
190}
191
192/****************************************************************************
193* doascii
194* Inputs:
195* char c: input character
196* Effect: interpret to control output
197****************************************************************************/
198
199private void doascii(char c)
200{
201 if (isupper(c)) c = tolower(c);
202 if (c == 'q') done = true;
203 else if (c == 'c') {
204 printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting"));
205 send_start_stop = true;
206 } else if (c == 't') {
207 printf("%s MIDI Time Code\n",
208 (time_code_running ? "Stopping" : "Starting"));
209 time_code_running = !time_code_running;
210 } else if (c == 'm') {
211 int input_tempo = get_number("Enter new tempo (bpm): ");
212 if (input_tempo >= 1 && input_tempo <= 300) {
213 printf("Changing tempo to %d\n", input_tempo);
214 tempo = (float) input_tempo;
215 } else {
216 printf("Tempo range is 1 to 300, current tempo is %g bpm\n",
217 tempo);
218 }
219 } else {
220 showhelp();
221 }
222}
223
224
225/* main - prompt for parameters, start processing */
226/*
227 * Prompt user to type return.
228 * Then send START and MIDI CLOCK for 60 beats/min.
229 * Commands:
230 * t - toggle sending MIDI Time Code (MTC)
231 * c - toggle sending MIDI CLOCK
232 * m - set tempo
233 * q - quit
234 */
235int main(int argc, char **argv)
236{
237 int outp;
238 PmError err;
239 int i;
240 if (argc > 1) {
241 printf("Warning: command line arguments ignored\n");
242 }
243 showhelp();
244 /* use porttime callback to send midi */
245 Pt_Start(1, timer_poll, 0);
246 /* list device information */
247 printf("MIDI output devices:\n");
248 for (i = 0; i < Pm_CountDevices(); i++) {
249 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
250 if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name);
251 }
252 outp = get_number("Type output device number: ");
253 err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
254 TIME_PROC, TIME_INFO, LATENCY);
255 if (err) {
256 puts(Pm_GetErrorText(err));
257 goto error_exit_no_device;
258 }
259 active = true;
260
261 printf("Type ENTER to start MIDI CLOCK:\n");
262 while (getchar() != '\n') ;
263 send_start_stop = true; /* send START and then CLOCKs */
264
265 while (!done) {
266 doascii(getchar());
267 while (getchar() != '\n') ;
268 }
269
270 active = false;
271 Pt_Sleep(2); /* this is to allow callback to complete -- it's
272 real time, so it's either ok and it runs on
273 time, or there's no point to synchronizing
274 with it */
275 /* now we "own" portmidi again */
276 Pm_Close(midi);
277 error_exit_no_device:
278 Pt_Stop();
279 Pm_Terminate();
280 exit(0);
281}
282
diff --git a/portmidi/pm_test/midithread.c b/portmidi/pm_test/midithread.c
new file mode 100755
index 0000000..ea0613b
--- /dev/null
+++ b/portmidi/pm_test/midithread.c
@@ -0,0 +1,343 @@
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
72int active = FALSE;
73int monitor = FALSE;
74int midi_thru = TRUE;
75
76int transpose;
77PmStream *midi_in;
78PmStream *midi_out;
79
80/****************************/
81/* END OF process_midi DATA */
82/****************************/
83
84/* shared queues */
85PmQueue *midi_to_main;
86PmQueue *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 */
93void 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
165void exit_with_message(char *msg)
166{
167 printf("%s\n", msg);
168 while (getchar() != '\n') ;
169 exit(1);
170}
171
172int main(int argc, char *argv[])
173{
174 int32_t n;
175 const PmDeviceInfo *info;
176 char line[STRING_MAX];
177 int spin;
178 int done = FALSE;
179 int i;
180 int input = -1, output = -1;
181
182 printf("Usage: midithread [-i input] [-o output]\n"
183 "where input and output are portmidi device numbers\n");
184 for (i = 1; i < argc; i++) {
185 if (strcmp(argv[i], "-i") == 0) {
186 i++;
187 input = atoi(argv[i]);
188 printf("Input device number: %d\n", input);
189 } else if (strcmp(argv[i], "-o") == 0) {
190 i++;
191 output = atoi(argv[i]);
192 printf("Output device number: %d\n", output);
193 } else {
194 return -1;
195 }
196 }
197 printf("begin PortMidi multithread test...\n");
198
199 /* note that it is safe to call PortMidi from the main thread for
200 initialization and opening devices. You should not make any
201 calls to PortMidi from this thread once the midi thread begins.
202 to make PortMidi calls.
203 */
204
205 /* make the message queues */
206 /* messages can be of any size and any type, but all messages in
207 * a given queue must have the same size. We'll just use int32_t's
208 * for our messages in this simple example
209 */
210 midi_to_main = Pm_QueueCreate(32, sizeof(int32_t));
211 assert(midi_to_main != NULL);
212 main_to_midi = Pm_QueueCreate(32, sizeof(int32_t));
213 assert(main_to_midi != NULL);
214
215 /* a little test of enqueue and dequeue operations. Ordinarily,
216 * you would call Pm_Enqueue from one thread and Pm_Dequeue from
217 * the other. Since the midi thread is not running, this is safe.
218 */
219 n = 1234567890;
220 Pm_Enqueue(midi_to_main, &n);
221 n = 987654321;
222 Pm_Enqueue(midi_to_main, &n);
223 Pm_Dequeue(midi_to_main, &n);
224 if (n != 1234567890) {
225 exit_with_message("Pm_Dequeue produced unexpected result.");
226 }
227 Pm_Dequeue(midi_to_main, &n);
228 if(n != 987654321) {
229 exit_with_message("Pm_Dequeue produced unexpected result.");
230 }
231
232 /* always start the timer before you start midi */
233 Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
234 /* the timer will call our function, process_midi() every millisecond */
235
236 Pm_Initialize();
237
238 output = (output < 0 ? Pm_GetDefaultOutputDeviceID() : output);
239 info = Pm_GetDeviceInfo(output);
240 if (info == NULL) {
241 printf("Could not open output device (%d).", output);
242 exit_with_message("");
243 }
244 printf("Opening output device %s %s\n", info->interf, info->name);
245
246 /* use zero latency because we want output to be immediate */
247 Pm_OpenOutput(&midi_out,
248 output,
249 DRIVER_INFO,
250 OUTPUT_BUFFER_SIZE,
251 TIME_PROC,
252 TIME_INFO,
253 LATENCY);
254
255 input = (input < 0 ? Pm_GetDefaultInputDeviceID() : input);
256 info = Pm_GetDeviceInfo(input);
257 if (info == NULL) {
258 printf("Could not open default input device (%d).", input);
259 exit_with_message("");
260 }
261 printf("Opening input device %s %s\n", info->interf, info->name);
262 Pm_OpenInput(&midi_in,
263 input,
264 DRIVER_INFO,
265 INPUT_BUFFER_SIZE,
266 TIME_PROC,
267 TIME_INFO);
268
269 active = TRUE; /* enable processing in the midi thread -- yes, this
270 is a shared variable without synchronization, but
271 this simple assignment is safe */
272
273 printf("Enter midi input; it will be transformed as specified by...\n");
274 printf("Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or\n"
275 "type a number to specify transposition.\n"
276 "Must terminate with [ENTER]\n");
277
278 while (!done) {
279 int32_t msg;
280 int input;
281 int len;
282 if (!fgets(line, STRING_MAX, stdin)) break; /* no stdin? */
283 /* remove the newline: */
284 len = (int) strlen(line);
285 if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
286 if (strcmp(line, "q") == 0) {
287 msg = QUIT_MSG;
288 Pm_Enqueue(main_to_midi, &msg);
289 /* wait for acknowlegement */
290 do {
291 spin = Pm_Dequeue(midi_to_main, &msg);
292 } while (spin == 0); /* spin */ ;
293 done = TRUE; /* leave the command loop and wrap up */
294 } else if (strcmp(line, "m") == 0) {
295 msg = MONITOR_MSG;
296 Pm_Enqueue(main_to_midi, &msg);
297 printf("Waiting for note...\n");
298 do {
299 spin = Pm_Dequeue(midi_to_main, &msg);
300 } while (spin == 0); /* spin */ ;
301 // convert int32_t to long for safe printing
302 printf("... pitch is %ld\n", (long) msg);
303 } else if (strcmp(line, "t") == 0) {
304 /* reading midi_thru asynchronously could give incorrect results,
305 e.g. if you type "t" twice before the midi thread responds to
306 the first one, but we'll do it this way anyway. Perhaps a more
307 correct way would be to wait for an acknowledgement message
308 containing the new state. */
309 printf("Setting THRU %s\n", (midi_thru ? "off" : "on"));
310 msg = THRU_MSG;
311 Pm_Enqueue(main_to_midi, &msg);
312 } else if (sscanf(line, "%d", &input) == 1) {
313 if (input >= -127 && input <= 127) {
314 /* send transposition value, make sur */
315 printf("Transposing by %d\n", input);
316 msg = (int32_t) input;
317 Pm_Enqueue(main_to_midi, &msg);
318 } else {
319 printf("Transposition must be within -127...127\n");
320 }
321 } else {
322 printf("%s\n%s\n",
323 "Type 'q[ENTER]' to quit, 'm[ENTER]' to monitor next pitch, or",
324 "enter a number to specify transposition.");
325 }
326 }
327
328 /* at this point, midi thread is inactive and we need to shut down
329 * the midi input and output
330 */
331 Pt_Stop(); /* stop the timer */
332 Pm_QueueDestroy(midi_to_main);
333 Pm_QueueDestroy(main_to_midi);
334
335 /* Belinda! if close fails here, some memory is deleted, right??? */
336 Pm_Close(midi_in);
337 Pm_Close(midi_out);
338
339 fputs("finished portMidi multithread test.\n"
340 "type ENTER to quit:", stdout);
341 while (getchar() != '\n') ;
342 return 0;
343}
diff --git a/portmidi/pm_test/midithru.c b/portmidi/pm_test/midithru.c
new file mode 100755
index 0000000..94b4f13
--- /dev/null
+++ b/portmidi/pm_test/midithru.c
@@ -0,0 +1,455 @@
1/* midithru.c -- example program implementing background thru processing */
2
3/* suppose you want low-latency midi-thru processing, but your
4 application wants to take advantage of the input buffer and
5 timestamped data so that it does not have to operate with very low
6 latency.
7
8 This program illustrates how to use a timer callback from PortTime
9 to implement a low-latency process that handles midi thru,
10 including correctly merging midi data from the application with
11 midi data from the input port.
12
13 The main application, which runs in the main program thread, will
14 use an interface similar to that of PortMidi, but since PortMidi
15 does not allow concurrent threads to share access to a stream, the
16 application will call private methods that transfer MIDI messages
17 to and from the timer thread using lock-free queues. All PortMidi
18 API calls are made from the timer thread.
19 */
20
21/* DESIGN
22
23All setup will be done by the main thread. Then, all direct access to
24PortMidi will be handed off to the timer callback thread.
25
26After this hand-off, the main thread will get/send messages via a queue.
27
28The goal is to send incoming messages to the midi output while merging
29any midi data generated by the application. Sysex is a problem here
30because you cannot insert (merge) a midi message while a sysex is in
31progress. There are at least three ways to implement midi thru with
32sysex messages:
33
341) Turn them off. If your application does not need them, turn them off
35 with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will
36 not receive sysex (or active sensing messages), so you will not have
37 to handle them.
38
392) Make them atomic. As you receive sysex messages, copy the data into
40 a (big) buffer. Ideally, expand the buffer as needed -- sysex messages
41 do not have any maximum length. Even more ideally, use a list structure
42 and real-time memory allocation to avoid latency in the timer thread.
43 When a full sysex message is received, send it to the midi output all
44 at once.
45
463) Process sysex incrementally. Send sysex data to midi output as it
47 arrives. Block any non-real-time messages from the application until
48 the sysex message completes. There is the risk that an incomplete
49 sysex message will block messages forever, so implement a 5-second
50 timeout: if no sysex data is seen for 5 seconds, release the block,
51 possibly losing the rest of the sysex message.
52
53 Application messages must be processed similarly: once started, a
54 sysex message will block MIDI THRU processing. We will assume that
55 the application will not abort a sysex message, so timeouts are not
56 necessary here.
57
58This code implements (3).
59
60Latency is also an issue. PortMidi requires timestamps to be in
61non-decreasing order. Since we'll be operating with a low-latency
62timer thread, we can just set the latency to zero meaning timestamps
63are ignored by PortMidi. This will allow thru to go through with
64minimal latency. The application, however, needs to use timestamps
65because we assume it is high latency (the whole purpose of this
66example is to illustrate how to get low-latency thru with a high-latency
67application.) So the callback thread will implement midi timing by
68observing timestamps. The current timestamp will be available in the
69global variable current_timestamp.
70
71*/
72
73
74#include "stdio.h"
75#include "stdlib.h"
76#include "string.h"
77#include "assert.h"
78#include "portmidi.h"
79#include "pmutil.h"
80#include "porttime.h"
81
82#define MIDI_SYSEX 0xf0
83#define MIDI_EOX 0xf7
84#define STRING_MAX 80 /* used for console input */
85
86/* active is set true when midi processing should start, must be
87 * volatile to force thread to check for updates by other thread */
88int active = FALSE;
89/* process_midi_exit_flag is set when the timer thread shuts down;
90 * must be volatile so it is re-read in the while loop that waits on it */
91volatile int process_midi_exit_flag;
92
93PmStream *midi_in;
94PmStream *midi_out;
95
96/* shared queues */
97#define IN_QUEUE_SIZE 1024
98#define OUT_QUEUE_SIZE 1024
99PmQueue *in_queue;
100PmQueue *out_queue;
101/* this is volatile because it is set in the process_midi callback and
102 * the main thread reads it to sense elapsed time. Without volatile, the
103 * optimizer can put it in a register and not see the updates.
104 */
105volatile PmTimestamp current_timestamp = 0;
106int thru_sysex_in_progress = FALSE;
107int app_sysex_in_progress = FALSE;
108PmTimestamp last_timestamp = 0;
109
110
111static void prompt_and_exit(void)
112{
113 printf("type ENTER...");
114 while (getchar() != '\n') ;
115 /* this will clean up open ports: */
116 exit(-1);
117}
118
119
120static PmError checkerror(PmError err)
121{
122 if (err == pmHostError) {
123 /* it seems pointless to allocate memory and copy the string,
124 * so I will do the work of Pm_GetHostErrorText directly
125 */
126 char errmsg[80];
127 Pm_GetHostErrorText(errmsg, 80);
128 printf("PortMidi found host error...\n %s\n", errmsg);
129 prompt_and_exit();
130 } else if (err < 0) {
131 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
132 prompt_and_exit();
133 }
134 return err;
135}
136
137
138/* time proc parameter for Pm_MidiOpen */
139PmTimestamp midithru_time_proc(void *info)
140{
141 return current_timestamp;
142}
143
144
145/* timer interrupt for processing midi data.
146 Incoming data is delivered to main program via in_queue.
147 Outgoing data from main program is delivered via out_queue.
148 Incoming data from midi_in is copied with low latency to midi_out.
149 Sysex messages from either source block messages from the other.
150 */
151void process_midi(PtTimestamp timestamp, void *userData)
152{
153 PmError result;
154 PmEvent buffer; /* just one message at a time */
155
156 current_timestamp++; /* update every millisecond */
157
158 /* do nothing until initialization completes */
159 if (!active) {
160 /* this flag signals that no more midi processing will be done */
161 process_midi_exit_flag = TRUE;
162 return;
163 }
164
165 /* see if there is any midi input to process */
166 if (!app_sysex_in_progress) {
167 do {
168 result = Pm_Poll(midi_in);
169 if (result) {
170 int status;
171 PmError rslt = Pm_Read(midi_in, &buffer, 1);
172 if (rslt == pmBufferOverflow)
173 continue;
174 assert(rslt == 1);
175
176 /* record timestamp of most recent data */
177 last_timestamp = current_timestamp;
178
179 /* the data might be the end of a sysex message that
180 has timed out, in which case we must ignore it.
181 It's a continuation of a sysex message if status
182 is actually a data byte (high-order bit is zero). */
183 status = Pm_MessageStatus(buffer.message);
184 if (((status & 0x80) == 0) && !thru_sysex_in_progress) {
185 continue; /* ignore this data */
186 }
187
188 /* implement midi thru */
189 /* note that you could output to multiple ports or do other
190 processing here if you wanted
191 */
192 /* printf("thru: %x\n", buffer.message); */
193 Pm_Write(midi_out, &buffer, 1);
194
195 /* send the message to the application */
196 /* you might want to filter clock or active sense messages here
197 to avoid sending a bunch of junk to the application even if
198 you want to send it to MIDI THRU
199 */
200 Pm_Enqueue(in_queue, &buffer);
201
202 /* sysex processing */
203 if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE;
204 else if ((status & 0xF8) != 0xF8) {
205 /* not MIDI_SYSEX and not real-time, so */
206 thru_sysex_in_progress = FALSE;
207 }
208 if (thru_sysex_in_progress && /* look for EOX */
209 (((buffer.message & 0xFF) == MIDI_EOX) ||
210 (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
211 (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
212 (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
213 thru_sysex_in_progress = FALSE;
214 }
215 }
216 } while (result);
217 }
218
219
220 /* see if there is application midi data to process */
221 while (!Pm_QueueEmpty(out_queue)) {
222 /* see if it is time to output the next message */
223 PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue);
224 assert(next); /* must be non-null because queue is not empty */
225 if (next->timestamp <= current_timestamp) {
226 /* time to send a message, first make sure it's not blocked */
227 int status = Pm_MessageStatus(next->message);
228 if ((status & 0xF8) == 0xF8) {
229 ; /* real-time messages are not blocked */
230 } else if (thru_sysex_in_progress) {
231 /* maybe sysex has timed out (output becomes unblocked) */
232 if (last_timestamp + 5000 < current_timestamp) {
233 thru_sysex_in_progress = FALSE;
234 } else break; /* output is blocked, so exit loop */
235 }
236 Pm_Dequeue(out_queue, &buffer);
237 Pm_Write(midi_out, &buffer, 1);
238
239 /* inspect message to update app_sysex_in_progress */
240 if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE;
241 else if ((status & 0xF8) != 0xF8) {
242 /* not MIDI_SYSEX and not real-time, so */
243 app_sysex_in_progress = FALSE;
244 }
245 if (app_sysex_in_progress && /* look for EOX */
246 (((buffer.message & 0xFF) == MIDI_EOX) ||
247 (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
248 (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
249 (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
250 app_sysex_in_progress = FALSE;
251 }
252 } else break; /* wait until indicated timestamp */
253 }
254}
255
256
257void exit_with_message(char *msg)
258{
259#define STRING_MAX 80
260 printf("%s\nType ENTER...", msg);
261 while (getchar() != '\n') ;
262 exit(1);
263}
264
265
266void initialize(int input, int output, int virtual)
267/* set up midi processing thread and open midi streams */
268{
269 /* note that it is safe to call PortMidi from the main thread for
270 initialization and opening devices. You should not make any
271 calls to PortMidi from this thread once the midi thread begins.
272 to make PortMidi calls.
273 */
274
275 /* note that this routine provides minimal error checking. If
276 you use the PortMidi library compiled with PM_CHECK_ERRORS,
277 then error messages will be printed and the program will exit
278 if an error is encountered. Otherwise, you should add some
279 error checking to this code.
280 */
281
282 const PmDeviceInfo *info;
283
284 /* make the message queues */
285 in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent));
286 assert(in_queue != NULL);
287 out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent));
288 assert(out_queue != NULL);
289
290 /* always start the timer before you start midi */
291 Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
292 /* the timer will call our function, process_midi() every millisecond */
293
294 Pm_Initialize();
295
296 if (output < 0) {
297 if (!virtual) {
298 output = Pm_GetDefaultOutputDeviceID();
299 }
300 }
301 if (output >= 0) {
302 info = Pm_GetDeviceInfo(output);
303 if (info == NULL) {
304 printf("Could not open default output device (%d).", output);
305 exit_with_message("");
306 }
307
308 printf("Opening output device %s %s\n", info->interf, info->name);
309
310 /* use zero latency because we want output to be immediate */
311 Pm_OpenOutput(&midi_out,
312 output,
313 NULL /* driver info */,
314 OUT_QUEUE_SIZE,
315 &midithru_time_proc,
316 NULL /* time info */,
317 0 /* Latency */);
318 } else { /* send to virtual port */
319 int id;
320 printf("Opening virtual output device \"midithru\"\n");
321 id = Pm_CreateVirtualOutput("midithru", NULL, NULL);
322 if (id < 0) checkerror(id); /* error reporting */
323 checkerror(Pm_OpenOutput(&midi_out, id, NULL, OUT_QUEUE_SIZE,
324 &midithru_time_proc, NULL, 0));
325 }
326 if (input < 0) {
327 if (!virtual) {
328 input = Pm_GetDefaultInputDeviceID();
329 }
330 }
331 if (input >= 0) {
332 info = Pm_GetDeviceInfo(input);
333 if (info == NULL) {
334 printf("Could not open default input device (%d).", input);
335 exit_with_message("");
336 }
337
338 printf("Opening input device %s %s\n", info->interf, info->name);
339 Pm_OpenInput(&midi_in,
340 input,
341 NULL /* driver info */,
342 0 /* use default input size */,
343 &midithru_time_proc,
344 NULL /* time info */);
345 } else { /* receive from virtual port */
346 int id;
347 printf("Opening virtual input device \"midithru\"\n");
348 id = Pm_CreateVirtualInput("midithru", NULL, NULL);
349 if (id < 0) checkerror(id); /* error reporting */
350 checkerror(Pm_OpenInput(&midi_in, id, NULL, 0,
351 &midithru_time_proc, NULL));
352 }
353 /* Note: if you set a filter here, then this will filter what goes
354 to the MIDI THRU port. You may not want to do this.
355 */
356 Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
357
358 active = TRUE; /* enable processing in the midi thread -- yes, this
359 is a shared variable without synchronization, but
360 this simple assignment is safe */
361
362}
363
364
365void finalize()
366{
367 /* the timer thread could be in the middle of accessing PortMidi stuff */
368 /* to detect that it is done, we first clear process_midi_exit_flag and
369 then wait for the timer thread to set it
370 */
371 process_midi_exit_flag = FALSE;
372 active = FALSE;
373 /* busy wait for flag from timer thread that it is done */
374 while (!process_midi_exit_flag) ;
375 /* at this point, midi thread is inactive and we need to shut down
376 * the midi input and output
377 */
378 Pt_Stop(); /* stop the timer */
379 Pm_QueueDestroy(in_queue);
380 Pm_QueueDestroy(out_queue);
381
382 Pm_Close(midi_in);
383 Pm_Close(midi_out);
384
385 Pm_Terminate();
386}
387
388
389int main(int argc, char *argv[])
390{
391 PmTimestamp last_time = 0;
392 PmEvent buffer;
393 int i;
394 int input = -1, output = -1;
395 int virtual = FALSE;
396 int delay_enable = TRUE;
397
398 printf("Usage: midithru [-i input] [-o output] [-v] [-n]\n"
399 "where input and output are portmidi device numbers\n"
400 "if -v and input and/or output are not specified,\n"
401 "then virtual ports are created and used instead.\n"
402 "-n turns off the default MIDI delay effect.\n");
403 for (i = 1; i < argc; i++) {
404 if (strcmp(argv[i], "-i") == 0) {
405 i++;
406 input = atoi(argv[i]);
407 printf("Input device number: %d\n", input);
408 } else if (strcmp(argv[i], "-o") == 0) {
409 i++;
410 output = atoi(argv[i]);
411 printf("Output device number: %d\n", output);
412 } else if (strcmp(argv[i], "-v") == 0) {
413 virtual = TRUE;
414 } else if (strcmp(argv[i], "-n") == 0) {
415 delay_enable = FALSE;
416 printf("delay_effect is disabled\n");
417 } else {
418 return -1;
419 }
420 }
421 printf("begin PortMidi midithru program...\n");
422
423 initialize(input, output, virtual); /* set up and start midi processing */
424
425 printf("This program will run for 60 seconds, "
426 "or until you play B below middle C,\n"
427 "All input is sent immediately, implementing software MIDI THRU.\n"
428 "Also, all input is echoed with a 2 second delay.\n");
429
430 while (current_timestamp < 60000) {
431 /* just to make the point that this is not a low-latency process,
432 spin until half a second has elapsed */
433 last_time = last_time + 500;
434 while (last_time > current_timestamp) ;
435
436 /* now read data and send it after changing timestamps */
437 while (Pm_Dequeue(in_queue, &buffer) == 1) {
438 /* printf("timestamp %d\n", buffer.timestamp); */
439 /* printf("message %x\n", buffer.message); */
440 if (delay_enable) {
441 buffer.timestamp = buffer.timestamp + 2000; /* delay */
442 Pm_Enqueue(out_queue, &buffer);
443 }
444 /* play B3 to break out of loop */
445 if (Pm_MessageStatus(buffer.message) == 0x90 &&
446 Pm_MessageData1(buffer.message) == 59) {
447 goto quit_now;
448 }
449 }
450 }
451quit_now:
452 finalize();
453 exit_with_message("finished PortMidi midithru program.");
454 return 0; /* never executed, but keeps the compiler happy */
455}
diff --git a/portmidi/pm_test/mm.c b/portmidi/pm_test/mm.c
new file mode 100755
index 0000000..ab9d32e
--- /dev/null
+++ b/portmidi/pm_test/mm.c
@@ -0,0 +1,595 @@
1/* mm.c -- midi monitor */
2
3/*****************************************************************************
4* Change Log
5* Date | Change
6*-----------+-----------------------------------------------------------------
7* 7-Apr-86 | Created changelog
8* 31-Jan-90 | GWL : use new cmdline
9* 5-Apr-91 | JDW : Further changes
10* 16-Feb-92 | GWL : eliminate label mmexit:; add error recovery
11* 18-May-92 | GWL : continuous clocks, etc.
12* 17-Jan-94 | GWL : option to display notes
13* 20-Nov-06 | RBD : port mm.c from CMU Midi Toolkit to PortMidi
14* | mm.c -- revealing MIDI secrets for over 20 years!
15*****************************************************************************/
16
17#include "stdlib.h"
18#include "ctype.h"
19#include "string.h"
20#include "stdio.h"
21#include "porttime.h"
22#include "portmidi.h"
23
24#define STRING_MAX 80
25
26#define MIDI_CODE_MASK 0xf0
27#define MIDI_CHN_MASK 0x0f
28/*#define MIDI_REALTIME 0xf8
29 #define MIDI_CHAN_MODE 0xfa */
30#define MIDI_OFF_NOTE 0x80
31#define MIDI_ON_NOTE 0x90
32#define MIDI_POLY_TOUCH 0xa0
33#define MIDI_CTRL 0xb0
34#define MIDI_CH_PROGRAM 0xc0
35#define MIDI_TOUCH 0xd0
36#define MIDI_BEND 0xe0
37
38#define MIDI_SYSEX 0xf0
39#define MIDI_Q_FRAME 0xf1
40#define MIDI_SONG_POINTER 0xf2
41#define MIDI_SONG_SELECT 0xf3
42#define MIDI_TUNE_REQ 0xf6
43#define MIDI_EOX 0xf7
44#define MIDI_TIME_CLOCK 0xf8
45#define MIDI_START 0xfa
46#define MIDI_CONTINUE 0xfb
47#define MIDI_STOP 0xfc
48#define MIDI_ACTIVE_SENSING 0xfe
49#define MIDI_SYS_RESET 0xff
50
51#define MIDI_ALL_SOUND_OFF 0x78
52#define MIDI_RESET_CONTROLLERS 0x79
53#define MIDI_LOCAL 0x7a
54#define MIDI_ALL_OFF 0x7b
55#define MIDI_OMNI_OFF 0x7c
56#define MIDI_OMNI_ON 0x7d
57#define MIDI_MONO_ON 0x7e
58#define MIDI_POLY_ON 0x7f
59
60
61#define private static
62
63#ifndef false
64#define false 0
65#define true 1
66#endif
67
68typedef int boolean;
69
70int debug = false; /* never set, but referenced by userio.c */
71PmStream *midi_in; /* midi input */
72boolean active = false; /* set when midi_in is ready for reading */
73boolean in_sysex = false; /* we are reading a sysex message */
74boolean inited = false; /* suppress printing during command line parsing */
75boolean done = false; /* when true, exit */
76boolean notes = true; /* show notes? */
77boolean controls = true; /* show continuous controllers */
78boolean bender = true; /* record pitch bend etc.? */
79boolean excldata = true; /* record system exclusive data? */
80boolean verbose = true; /* show text representation? */
81boolean realdata = true; /* record real time messages? */
82boolean clksencnt = true; /* clock and active sense count on */
83boolean chmode = true; /* show channel mode messages */
84boolean pgchanges = true; /* show program changes */
85boolean flush = false; /* flush all pending MIDI data */
86
87uint32_t filter = 0; /* remember state of midi filter */
88
89uint32_t clockcount = 0; /* count of clocks */
90uint32_t actsensecount = 0; /* cout of active sensing bytes */
91uint32_t notescount = 0; /* #notes since last request */
92uint32_t notestotal = 0; /* total #notes */
93
94char val_format[] = " Val %d\n";
95
96/*****************************************************************************
97* Imported variables
98*****************************************************************************/
99
100extern int abort_flag;
101
102/*****************************************************************************
103* Routines local to this module
104*****************************************************************************/
105
106private void mmexit(int code);
107private void output(PmMessage data);
108private int put_pitch(int p);
109private void showhelp();
110private void showbytes(PmMessage data, int len, boolean newline);
111private void showstatus(boolean flag);
112private void doascii(char c);
113private int get_number(const char *prompt);
114
115
116/* read a number from console */
117/**/
118int get_number(const char *prompt)
119{
120 int n = 0, i;
121 fputs(prompt, stdout);
122 while (n != 1) {
123 n = scanf("%d", &i);
124 while (getchar() != '\n') ;
125 }
126 return i;
127}
128
129
130void receive_poll(PtTimestamp timestamp, void *userData)
131{
132 PmEvent event;
133 int count;
134 if (!active) return;
135 while ((count = Pm_Read(midi_in, &event, 1))) {
136 if (count == 1) output(event.message);
137 else puts(Pm_GetErrorText(count));
138 }
139}
140
141
142/****************************************************************************
143* main
144* Effect: prompts for parameters, starts monitor
145****************************************************************************/
146
147int main(int argc, char **argv)
148{
149 char *argument;
150 int inp;
151 PmError err;
152 int i;
153 if (argc > 1) { /* first arg can change defaults */
154 argument = argv[1];
155 while (*argument) doascii(*argument++);
156 }
157 showhelp();
158 /* use porttime callback to empty midi queue and print */
159 Pt_Start(1, receive_poll, 0);
160 /* list device information */
161 puts("MIDI input devices:");
162 for (i = 0; i < Pm_CountDevices(); i++) {
163 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
164 if (info->input) printf("%d: %s, %s\n", i, info->interf, info->name);
165 }
166 inp = get_number("Type input device number: ");
167 err = Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
168 if (err) {
169 puts(Pm_GetErrorText(err));
170 Pt_Stop();
171 mmexit(1);
172 }
173 Pm_SetFilter(midi_in, filter);
174 inited = true; /* now can document changes, set filter */
175 printf("Midi Monitor ready.\n");
176 active = true;
177 while (!done) {
178 doascii(getchar());
179 while (getchar() != '\n') ;
180 }
181 active = false;
182 Pm_Close(midi_in);
183 Pt_Stop();
184 Pm_Terminate();
185 mmexit(0);
186 return 0; // make the compiler happy be returning a value
187}
188
189
190/****************************************************************************
191* doascii
192* Inputs:
193* char c: input character
194* Effect: interpret to revise flags
195****************************************************************************/
196
197private void doascii(char c)
198{
199 if (isupper(c)) c = tolower(c);
200 if (c == 'q') done = true;
201 else if (c == 'b') {
202 bender = !bender;
203 filter ^= PM_FILT_PITCHBEND;
204 if (inited)
205 printf("Pitch Bend, etc. %s\n", (bender ? "ON" : "OFF"));
206 } else if (c == 'c') {
207 controls = !controls;
208 filter ^= PM_FILT_CONTROL;
209 if (inited)
210 printf("Control Change %s\n", (controls ? "ON" : "OFF"));
211 } else if (c == 'h') {
212 pgchanges = !pgchanges;
213 filter ^= PM_FILT_PROGRAM;
214 if (inited)
215 printf("Program Changes %s\n", (pgchanges ? "ON" : "OFF"));
216 } else if (c == 'n') {
217 notes = !notes;
218 filter ^= PM_FILT_NOTE;
219 if (inited)
220 printf("Notes %s\n", (notes ? "ON" : "OFF"));
221 } else if (c == 'x') {
222 excldata = !excldata;
223 filter ^= PM_FILT_SYSEX;
224 if (inited)
225 printf("System Exclusive data %s\n", (excldata ? "ON" : "OFF"));
226 } else if (c == 'r') {
227 realdata = !realdata;
228 filter ^= (PM_FILT_PLAY | PM_FILT_RESET | PM_FILT_TICK | PM_FILT_UNDEFINED);
229 if (inited)
230 printf("Real Time messages %s\n", (realdata ? "ON" : "OFF"));
231 } else if (c == 'k') {
232 clksencnt = !clksencnt;
233 if (inited) {
234 printf("Clock and Active Sense Counting %s\n", (clksencnt ? "ON" : "OFF"));
235 printf("Resetting Clock and Active Sense counts.\n");
236 clockcount = actsensecount = 0;
237 }
238 } else if (c == 's') {
239 if (inited) {
240 printf("Clock Count %ld\nActive Sense Count %ld\n",
241 (long) clockcount, (long) actsensecount);
242 }
243 } else if (c == 't') {
244 notestotal+=notescount;
245 if (inited)
246 printf("This Note Count %ld\nTotal Note Count %ld\n",
247 (long) notescount, (long) notestotal);
248 notescount=0;
249 } else if (c == 'v') {
250 verbose = !verbose;
251 if (inited)
252 printf("Verbose %s\n", (verbose ? "ON" : "OFF"));
253 } else if (c == 'm') {
254 chmode = !chmode;
255 if (inited)
256 printf("Channel Mode Messages %s", (chmode ? "ON" : "OFF"));
257 } else {
258 if (inited) {
259 if (c == ' ') {
260 PmEvent event;
261 while (Pm_Read(midi_in, &event, 1)) ; /* flush midi input */
262 printf("...FLUSHED MIDI INPUT\n\n");
263 } else showhelp();
264 }
265 }
266 if (inited) Pm_SetFilter(midi_in, filter);
267}
268
269
270
271private void mmexit(int code)
272{
273 /* if this is not being run from a console, maybe we should wait for
274 * the user to read error messages before exiting
275 */
276 exit(code);
277}
278
279
280/****************************************************************************
281* output
282* Inputs:
283* data: midi message buffer holding one command or 4 bytes of sysex msg
284* Effect: format and print midi data
285****************************************************************************/
286
287char vel_format[] = " Vel %d\n";
288
289private void output(PmMessage data)
290{
291 int command; /* the current command */
292 int chan; /* the midi channel of the current event */
293 int len; /* used to get constant field width */
294
295 /* printf("output data %8x; ", data); */
296
297 command = Pm_MessageStatus(data) & MIDI_CODE_MASK;
298 chan = Pm_MessageStatus(data) & MIDI_CHN_MASK;
299
300 if (in_sysex || Pm_MessageStatus(data) == MIDI_SYSEX) {
301#define sysex_max 16
302 int i;
303 PmMessage data_copy = data;
304 in_sysex = true;
305 /* look for MIDI_EOX in first 3 bytes
306 * if realtime messages are embedded in sysex message, they will
307 * be printed as if they are part of the sysex message
308 */
309 for (i = 0; (i < 4) && ((data_copy & 0xFF) != MIDI_EOX); i++)
310 data_copy >>= 8;
311 if (i < 4) {
312 in_sysex = false;
313 i++; /* include the EOX byte in output */
314 }
315 showbytes(data, i, verbose);
316 if (verbose) printf("System Exclusive\n");
317 } else if (command == MIDI_ON_NOTE && Pm_MessageData2(data) != 0) {
318 notescount++;
319 if (notes) {
320 showbytes(data, 3, verbose);
321 if (verbose) {
322 printf("NoteOn Chan %2d Key %3d ", chan, Pm_MessageData1(data));
323 len = put_pitch(Pm_MessageData1(data));
324 printf(vel_format + len, Pm_MessageData2(data));
325 }
326 }
327 } else if ((command == MIDI_ON_NOTE /* && Pm_MessageData2(data) == 0 */ ||
328 command == MIDI_OFF_NOTE)) {
329 if (notes) {
330 showbytes(data, 3, verbose);
331 if (verbose) {
332 printf("NoteOff Chan %2d Key %3d ", chan,
333 Pm_MessageData1(data));
334 len = put_pitch(Pm_MessageData1(data));
335 printf(vel_format + len, Pm_MessageData2(data));
336 }
337 }
338 } else if (command == MIDI_CH_PROGRAM) {
339 if (pgchanges) {
340 showbytes(data, 2, verbose);
341 if (verbose) {
342 printf(" ProgChg Chan %2d Prog %2d\n", chan,
343 Pm_MessageData1(data) + 1);
344 }
345 }
346 } else if (command == MIDI_CTRL) {
347 /* controls 121 (MIDI_RESET_CONTROLLER) to 127 are channel
348 * mode messages. */
349 if (Pm_MessageData1(data) < MIDI_ALL_SOUND_OFF) {
350 if (controls) {
351 showbytes(data, 3, verbose);
352 if (verbose) {
353 printf("CtrlChg Chan %2d Ctrl %2d Val %2d\n",
354 chan, Pm_MessageData1(data), Pm_MessageData2(data));
355 }
356 } else /* channel mode */ if (chmode) {
357 showbytes(data, 3, verbose);
358 if (verbose) {
359 switch (Pm_MessageData1(data)) {
360 case MIDI_ALL_SOUND_OFF:
361 printf("All Sound Off, Chan %2d\n", chan);
362 break;
363 case MIDI_RESET_CONTROLLERS:
364 printf("Reset All Controllers, Chan %2d\n", chan);
365 break;
366 case MIDI_LOCAL:
367 printf("LocCtrl Chan %2d %s\n",
368 chan, Pm_MessageData2(data) ? "On" : "Off");
369 break;
370 case MIDI_ALL_OFF:
371 printf("All Off Chan %2d\n", chan);
372 break;
373 case MIDI_OMNI_OFF:
374 printf("OmniOff Chan %2d\n", chan);
375 break;
376 case MIDI_OMNI_ON:
377 printf("Omni On Chan %2d\n", chan);
378 break;
379 case MIDI_MONO_ON:
380 printf("Mono On Chan %2d\n", chan);
381 if (Pm_MessageData2(data))
382 printf(" to %d received channels\n",
383 Pm_MessageData2(data));
384 else
385 printf(" to all received channels\n");
386 break;
387 case MIDI_POLY_ON:
388 printf("Poly On Chan %2d\n", chan);
389 break;
390 }
391 }
392 }
393 }
394 } else if (command == MIDI_POLY_TOUCH) {
395 if (bender) {
396 showbytes(data, 3, verbose);
397 if (verbose) {
398 printf("P.Touch Chan %2d Key %2d ", chan,
399 Pm_MessageData1(data));
400 len = put_pitch(Pm_MessageData1(data));
401 printf(val_format + len, Pm_MessageData2(data));
402 }
403 }
404 } else if (command == MIDI_TOUCH) {
405 if (bender) {
406 showbytes(data, 2, verbose);
407 if (verbose) {
408 printf(" A.Touch Chan %2d Val %2d\n", chan,
409 Pm_MessageData1(data));
410 }
411 }
412 } else if (command == MIDI_BEND) {
413 if (bender) {
414 showbytes(data, 3, verbose);
415 if (verbose) {
416 printf("P.Bend Chan %2d Val %2d\n", chan,
417 (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
418 }
419 }
420 } else if (Pm_MessageStatus(data) == MIDI_SONG_POINTER) {
421 showbytes(data, 3, verbose);
422 if (verbose) {
423 printf(" Song Position %d\n",
424 (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
425 }
426 } else if (Pm_MessageStatus(data) == MIDI_SONG_SELECT) {
427 showbytes(data, 2, verbose);
428 if (verbose) {
429 printf(" Song Select %d\n", Pm_MessageData1(data));
430 }
431 } else if (Pm_MessageStatus(data) == MIDI_TUNE_REQ) {
432 showbytes(data, 1, verbose);
433 if (verbose) {
434 printf(" Tune Request\n");
435 }
436 } else if (Pm_MessageStatus(data) == MIDI_Q_FRAME) {
437 if (realdata) {
438 showbytes(data, 2, verbose);
439 if (verbose) {
440 printf(" Time Code Quarter Frame Type %d Values %d\n",
441 (Pm_MessageData1(data) & 0x70) >> 4,
442 Pm_MessageData1(data) & 0xf);
443 }
444 }
445 } else if (Pm_MessageStatus(data) == MIDI_START) {
446 if (realdata) {
447 showbytes(data, 1, verbose);
448 if (verbose) {
449 printf(" Start\n");
450 }
451 }
452 } else if (Pm_MessageStatus(data) == MIDI_CONTINUE) {
453 if (realdata) {
454 showbytes(data, 1, verbose);
455 if (verbose) {
456 printf(" Continue\n");
457 }
458 }
459 } else if (Pm_MessageStatus(data) == MIDI_STOP) {
460 if (realdata) {
461 showbytes(data, 1, verbose);
462 if (verbose) {
463 printf(" Stop\n");
464 }
465 }
466 } else if (Pm_MessageStatus(data) == MIDI_SYS_RESET) {
467 if (realdata) {
468 showbytes(data, 1, verbose);
469 if (verbose) {
470 printf(" System Reset\n");
471 }
472 }
473 } else if (Pm_MessageStatus(data) == MIDI_TIME_CLOCK) {
474 clockcount++;
475 if (clksencnt) {
476 showbytes(data, 1, verbose);
477 if (verbose) {
478 printf(" Clock\n");
479 }
480 }
481 } else if (Pm_MessageStatus(data) == MIDI_ACTIVE_SENSING) {
482 actsensecount++;
483 if (clksencnt) {
484 showbytes(data, 1, verbose);
485 if (verbose) {
486 printf(" Active Sensing\n");
487 }
488 }
489 } else showbytes(data, 3, verbose);
490 fflush(stdout);
491}
492
493
494/****************************************************************************
495* put_pitch
496* Inputs:
497* int p: pitch number
498* Effect: write out the pitch name for a given number
499****************************************************************************/
500
501private int put_pitch(int p)
502{
503 char result[8];
504 static char *ptos[] = {
505 "c", "cs", "d", "ef", "e", "f", "fs", "g",
506 "gs", "a", "bf", "b" };
507 /* note octave correction below */
508 sprintf(result, "%s%d", ptos[p % 12], (p / 12) - 1);
509 fputs(result, stdout);
510 return (int) strlen(result);
511}
512
513
514/****************************************************************************
515* showbytes
516* Effect: print hex data, precede with newline if asked
517****************************************************************************/
518
519char nib_to_hex[] = "0123456789ABCDEF";
520
521private void showbytes(PmMessage data, int len, boolean newline)
522{
523 int count = 0;
524 int i;
525
526/* if (newline) {
527 putchar('\n');
528 count++;
529 } */
530 for (i = 0; i < len; i++) {
531 putchar(nib_to_hex[(data >> 4) & 0xF]);
532 putchar(nib_to_hex[data & 0xF]);
533 count += 2;
534 if (count > 72) {
535 putchar('.');
536 putchar('.');
537 putchar('.');
538 break;
539 }
540 data >>= 8;
541 }
542 putchar(' ');
543}
544
545
546
547/****************************************************************************
548* showhelp
549* Effect: print help text
550****************************************************************************/
551
552private void showhelp()
553{
554 printf("\n");
555 printf(" Item Reported Range Item Reported Range\n");
556 printf(" ------------- ----- ------------- -----\n");
557 printf(" Channels 1 - 16 Programs 1 - 128\n");
558 printf(" Controllers 0 - 127 After Touch 0 - 127\n");
559 printf(" Loudness 0 - 127 Pitch Bend 0 - 16383, "
560 "center = 8192\n");
561 printf(" Pitches 0 - 127, 60 = c4 = middle C\n");
562 printf(" \n");
563 printf("n toggles notes");
564 showstatus(notes);
565 printf("t displays noteon count since last t\n");
566 printf("b toggles pitch bend, aftertouch");
567 showstatus(bender);
568 printf("c toggles continuous control");
569 showstatus(controls);
570 printf("h toggles program changes");
571 showstatus(pgchanges);
572 printf("x toggles system exclusive");
573 showstatus(excldata);
574 printf("k toggles clock and sense messages, clears counts");
575 showstatus(clksencnt);
576 printf("r toggles other real time messages & SMPTE");
577 showstatus(realdata);
578 printf("s displays clock and sense count since last k\n");
579 printf("m toggles channel mode messages");
580 showstatus(chmode);
581 printf("v toggles verbose text");
582 showstatus(verbose);
583 printf("q quits\n");
584 printf("\n");
585}
586
587/****************************************************************************
588* showstatus
589* Effect: print status of flag
590****************************************************************************/
591
592private void showstatus(boolean flag)
593{
594 printf(", now %s\n", flag ? "ON" : "OFF");
595}
diff --git a/portmidi/pm_test/multivirtual.c b/portmidi/pm_test/multivirtual.c
new file mode 100644
index 0000000..b90d860
--- /dev/null
+++ b/portmidi/pm_test/multivirtual.c
@@ -0,0 +1,223 @@
1/* multivirtual.c -- test for creating two input and two output virtual ports */
2/*
3 * Roger B. Dannenberg
4 * Oct 2021
5 */
6#include "portmidi.h"
7#include "porttime.h"
8#include "stdlib.h"
9#include "stdio.h"
10#include "string.h"
11#include "assert.h"
12
13#define OUTPUT_BUFFER_SIZE 0
14#define DEVICE_INFO NULL
15#define DRIVER_INFO NULL
16#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
17#define TIME_INFO NULL
18#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
19
20int latency = 0;
21
22static void prompt_and_exit(void)
23{
24 printf("type ENTER...");
25 while (getchar() != '\n') ;
26 /* this will clean up open ports: */
27 exit(-1);
28}
29
30
31static PmError checkerror(PmError err)
32{
33 if (err == pmHostError) {
34 /* it seems pointless to allocate memory and copy the string,
35 * so I will do the work of Pm_GetHostErrorText directly
36 */
37 char errmsg[80];
38 Pm_GetHostErrorText(errmsg, 80);
39 printf("PortMidi found host error...\n %s\n", errmsg);
40 prompt_and_exit();
41 } else if (err < 0) {
42 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
43 prompt_and_exit();
44 }
45 return err;
46}
47
48static int msg_count[2] = {0, 0};
49
50void poll_input(PmStream *in, int which)
51{
52 PmEvent buffer[1];
53 int pitch, expected, length;
54 PmError status = Pm_Poll(in);
55 if (status == TRUE) {
56 length = Pm_Read(in, buffer, 1);
57 if (length > 0) {
58 printf("Got message %d from portmidi%d: "
59 "time %ld, %2x %2x %2x\n",
60 msg_count[which], which + 1, (long) buffer[0].timestamp,
61 (status = Pm_MessageStatus(buffer[0].message)),
62 (pitch = Pm_MessageData1(buffer[0].message)),
63 Pm_MessageData2(buffer[0].message));
64 if (status == 0x90) { /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */
65 expected = (((msg_count[which] - 1) / 2) % 12) + 60 +
66 which * 12;
67 if (pitch != expected) {
68 printf("WARNING: expected pitch %d, got pitch %d\n",
69 expected, pitch);
70 }
71 }
72 msg_count[which]++;
73 } else {
74 assert(0);
75 }
76 }
77}
78
79
80void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2)
81{
82 while (when > Pt_Time()) {
83 poll_input(in1, 0);
84 poll_input(in2, 1);
85 Pt_Sleep(10);
86 }
87}
88
89
90/* create one virtual output device and one input device */
91void init(const char *name, PmStream **midi_out, PmStream **midi_in,
92 int *id_out, int *id_in)
93{
94 PmEvent buffer[1];
95
96 *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO));
97 checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
98 TIME_PROC, TIME_INFO, latency));
99 printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out);
100
101 *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO));
102 checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL));
103 printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in);
104 Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
105 /* empty the buffer after setting filter, just in case anything
106 got through */
107 while (Pm_Read(*midi_in, buffer, 1)) ;
108}
109
110
111void main_test(int num)
112{
113 PmStream *midi1_out;
114 PmStream *midi2_out;
115 PmStream *midi1_in;
116 PmStream *midi2_in;
117 int id1_out;
118 int id2_out;
119 int id1_in;
120 int id2_in;
121 int32_t next_time;
122 PmEvent buffer[1];
123 int pitch = 60;
124 int expected_count = num + 1; /* add 1 for MIDI Program message */
125
126 /* It is recommended to start timer before Midi; otherwise, PortMidi may
127 start the timer with its (default) parameters
128 */
129 TIME_START;
130
131 init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in);
132 init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in);
133
134 printf("Type ENTER to send messages: ");
135 while (getchar() != '\n') ;
136
137 buffer[0].timestamp = Pt_Time();
138#define PROGRAM 0
139 buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
140 Pm_Write(midi1_out, buffer, 1);
141 Pm_Write(midi2_out, buffer, 1);
142 next_time = Pt_Time() + 1000; /* wait 1s */
143 while (num > 0) {
144 wait_until(next_time, midi1_in, midi2_in);
145 Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100));
146 Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100));
147 printf("Note On pitch %d\n", pitch);
148 num--;
149 next_time += 500;
150
151 wait_until(next_time, midi1_in, midi2_in);
152 Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0));
153 Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0));
154 printf("Note Off pitch %d\n", pitch);
155 num--;
156 pitch = (pitch + 1) % 12 + 60;
157 next_time += 500;
158 }
159 wait_until(next_time, midi1_in, midi2_in); /* get final note-offs */
160
161 printf("Got %d messages from portmidi1 and %d from portmidi2; "
162 "expected %d.\n", msg_count[0], msg_count[1], expected_count);
163
164 /* close devices (this not explicitly needed in most implementations) */
165 printf("ready to close...");
166 checkerror(Pm_Close(midi1_out));
167 checkerror(Pm_Close(midi2_out));
168 checkerror(Pm_Close(midi1_in));
169 checkerror(Pm_Close(midi2_in));
170 printf("done closing.\nNow delete the virtual devices...");
171 checkerror(Pm_DeleteVirtualDevice(id1_out));
172 checkerror(Pm_DeleteVirtualDevice(id1_in));
173 checkerror(Pm_DeleteVirtualDevice(id2_out));
174 checkerror(Pm_DeleteVirtualDevice(id2_in));
175 printf("done deleting.\n");
176}
177
178
179void show_usage()
180{
181 printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n"
182 " -h for this message,\n"
183 " -l ms designates latency for precise timing (default 0),\n"
184 " n is number of message to send each output, not counting\n"
185 " initial program change.\n"
186 "sends change program to 1, then one note per second with 0.5s on,\n"
187 "0.5s off, for n/2 seconds to both output ports portmidi1 and\n"
188 "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n"
189 "pitches an octave higher. Latency >0 uses the device driver for \n"
190 "precise timing (see PortMidi documentation). Inputs print what\n"
191 "they get and print WARNING if they get something unexpected.\n"
192 "The expected test is use two instances of testio to loop\n"
193 "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n");
194 exit(0);
195}
196
197
198int main(int argc, char *argv[])
199{
200 int num = 10;
201 int i;
202 for (i = 1; i < argc; i++) {
203 if (strcmp(argv[i], "-h") == 0) {
204 show_usage();
205 } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
206 i = i + 1;
207 latency = atoi(argv[i]);
208 printf("Latency will be %d\n", latency);
209 } else {
210 num = atoi(argv[1]);
211 if (num <= 0) {
212 show_usage();
213 }
214 printf("Sending %d messages.\n", num);
215 }
216 }
217
218 main_test(num);
219
220 printf("finished sendvirtual test...type ENTER to quit...");
221 while (getchar() != '\n') ;
222 return 0;
223}
diff --git a/portmidi/pm_test/pmlist.c b/portmidi/pm_test/pmlist.c
new file mode 100644
index 0000000..5e3d1db
--- /dev/null
+++ b/portmidi/pm_test/pmlist.c
@@ -0,0 +1,63 @@
1/* pmlist.c -- list portmidi devices and numbers
2 *
3 * This program lists devices. When you type return, it
4 * restarts portmidi and lists devices again. It is mainly
5 * a test for shutting down and restarting.
6 *
7 * Roger B. Dannenberg, Feb 2022
8 */
9
10#include "portmidi.h"
11#include "porttime.h"
12#include "stdlib.h"
13#include "stdio.h"
14#include "string.h"
15#include "assert.h"
16
17#define DEVICE_INFO NULL
18#define DRIVER_INFO NULL
19#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
20
21#define STRING_MAX 80 /* used for console input */
22
23void show_usage()
24{
25 printf("Usage: pmlist [-h]\n -h means help.\n"
26 " Type return to rescan and list devices, q<ret> to quit\n");
27}
28
29
30int main(int argc, char *argv[])
31{
32 if (argc > 1) {
33 show_usage();
34 exit(0);
35 }
36
37 while (1) {
38 char input[STRING_MAX];
39 const char *deflt;
40 const char *in_or_out;
41 int default_in, default_out, i;
42
43 // Pm_Initialize();
44 /* list device information */
45 default_in = Pm_GetDefaultInputDeviceID();
46 default_out = Pm_GetDefaultOutputDeviceID();
47 for (i = 0; i < Pm_CountDevices(); i++) {
48 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
49 printf("%d: %s, %s", i, info->interf, info->name);
50 deflt = "";
51 if (i == default_out || i == default_in) {
52 deflt = "default ";
53 }
54 in_or_out = (info->input ? "input" : "output");
55 printf(" (%s%s)\n", deflt, in_or_out);
56 }
57 if (fgets(input, STRING_MAX, stdin) && input[0] == 'q') {
58 return 0;
59 }
60 Pm_Terminate();
61 }
62 return 0;
63}
diff --git a/portmidi/pm_test/qtest.c b/portmidi/pm_test/qtest.c
new file mode 100644
index 0000000..14d803e
--- /dev/null
+++ b/portmidi/pm_test/qtest.c
@@ -0,0 +1,174 @@
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 */
10void 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/**/
21void 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/**/
32int 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
47int 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}
diff --git a/portmidi/pm_test/recvvirtual.c b/portmidi/pm_test/recvvirtual.c
new file mode 100644
index 0000000..f8d9848
--- /dev/null
+++ b/portmidi/pm_test/recvvirtual.c
@@ -0,0 +1,175 @@
1#include "portmidi.h"
2#include "porttime.h"
3#include "stdlib.h"
4#include "stdio.h"
5#include "string.h"
6#include "assert.h"
7
8#define INPUT_BUFFER_SIZE 100
9#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
10#define TIME_INFO NULL
11#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
12
13#define STRING_MAX 80 /* used for console input */
14
15char *portname = "portmidi";
16
17PmSysDepInfo *sysdepinfo = NULL;
18char *port_name = "portmidi";
19
20static void set_sysdepinfo(char m_or_p, const char *name)
21{
22 if (!sysdepinfo) {
23 // allocate some space we will alias with open-ended PmDriverInfo:
24 // there is space for 4 parameters:
25 static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
26 sysdepinfo = (PmSysDepInfo *) dimem;
27 // build the driver info structure:
28 sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
29 sysdepinfo->length = 0;
30 }
31 if (sysdepinfo->length > 1) {
32 printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
33 exit(1);
34 }
35 int i = sysdepinfo->length++;
36 enum PmSysDepPropertyKey k = pmKeyNone;
37 if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
38 else if (m_or_p == 'p') k = pmKeyAlsaPortName;
39 else if (m_or_p == 'c') k = pmKeyAlsaClientName;
40 sysdepinfo->properties[i].key = k;
41 sysdepinfo->properties[i].value = name;
42}
43
44
45static void prompt_and_exit(void)
46{
47 printf("type ENTER...");
48 while (getchar() != '\n') ;
49 /* this will clean up open ports: */
50 exit(-1);
51}
52
53
54static PmError checkerror(PmError err)
55{
56 if (err == pmHostError) {
57 /* it seems pointless to allocate memory and copy the string,
58 * so I will do the work of Pm_GetHostErrorText directly
59 */
60 char errmsg[80];
61 Pm_GetHostErrorText(errmsg, 80);
62 printf("PortMidi found host error...\n %s\n", errmsg);
63 prompt_and_exit();
64 } else if (err < 0) {
65 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
66 prompt_and_exit();
67 }
68 return err;
69}
70
71
72void main_test_input(int num)
73{
74 PmStream *midi;
75 PmError status, length;
76 PmEvent buffer[1];
77 int id;
78 int i = 0; /* count messages as they arrive */
79 /* It is recommended to start timer before Midi; otherwise, PortMidi may
80 start the timer with its (default) parameters
81 */
82 TIME_START;
83
84 /* create a virtual input device */
85 id = checkerror(Pm_CreateVirtualInput(port_name, NULL, sysdepinfo));
86 checkerror(Pm_OpenInput(&midi, id, sysdepinfo, 0, NULL, NULL));
87
88 printf("Midi Input opened. Reading %d Midi messages...\n", num);
89 Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
90 /* empty the buffer after setting filter, just in case anything
91 got through */
92 while (Pm_Poll(midi)) {
93 Pm_Read(midi, buffer, 1);
94 }
95 /* now start paying attention to messages */
96 while (i < num) {
97 status = Pm_Poll(midi);
98 if (status == TRUE) {
99 length = Pm_Read(midi, buffer, 1);
100 if (length > 0) {
101 printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
102 i,
103 (long) buffer[0].timestamp,
104 (long) Pm_MessageStatus(buffer[0].message),
105 (long) Pm_MessageData1(buffer[0].message),
106 (long) Pm_MessageData2(buffer[0].message));
107 i++;
108 } else {
109 assert(0);
110 }
111 }
112 }
113
114 /* close device (this not explicitly needed in most implementations) */
115 printf("ready to close...");
116 Pm_Close(midi);
117 printf("done closing.\nNow delete the virtual device...");
118 checkerror(Pm_DeleteVirtualDevice(id));
119 printf("done deleting.\n");
120}
121
122
123void show_usage()
124{
125 printf("Usage: recvvirtual [-h] [-m manufacturer] [-c clientname] "
126 "[-p portname] [n]\n"
127 " -h for this message,\n"
128 " -m name designates a manufacturer name (macOS only),\n"
129 " -c name designates a client name (linux only),\n"
130 " -p name designates a port name (linux only),\n"
131 " n is number of message to wait for.\n");
132 exit(0);
133}
134
135
136int main(int argc, char *argv[])
137{
138 char line[STRING_MAX];
139 int num = 10;
140 int i;
141 if (argc <= 1) {
142 show_usage();
143 }
144 for (i = 1; i < argc; i++) {
145 if (strcmp(argv[i], "-h") == 0) {
146 show_usage();
147 } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) {
148 i = i + 1;
149 set_sysdepinfo('m', argv[i]);
150 printf("Manufacturer name will be %s\n", argv[i]);
151 } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
152 i = i + 1;
153 port_name = argv[i];
154 set_sysdepinfo('p', port_name);
155 printf("Port name will be %s\n", port_name);
156 } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
157 i = i + 1;
158 set_sysdepinfo('c', argv[i]);
159 printf("Client name will be %s\n", argv[i]);
160 } else {
161 num = atoi(argv[i]);
162 if (num <= 0) {
163 printf("Zero value or non-number for n\n");
164 show_usage();
165 }
166 printf("Waiting for %d messages.\n", num);
167 }
168 }
169
170 main_test_input(num);
171
172 printf("finished portMidi test...type ENTER to quit...");
173 while (getchar() != '\n') ;
174 return 0;
175}
diff --git a/portmidi/pm_test/sendvirtual.c b/portmidi/pm_test/sendvirtual.c
new file mode 100644
index 0000000..a60a48f
--- /dev/null
+++ b/portmidi/pm_test/sendvirtual.c
@@ -0,0 +1,194 @@
1/* sendvirtual.c -- test for creating a virtual device and sending to it */
2/*
3 * Roger B. Dannenberg
4 * Sep 2021
5 */
6#include "portmidi.h"
7#include "porttime.h"
8#include "stdlib.h"
9#include "stdio.h"
10#include "string.h"
11#include "assert.h"
12
13#define OUTPUT_BUFFER_SIZE 0
14#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
15#define TIME_INFO NULL
16#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
17
18int latency = 0;
19PmSysDepInfo *sysdepinfo = NULL;
20char *port_name = "portmidi";
21
22static void set_sysdepinfo(char m_or_p, const char *name)
23{
24 if (!sysdepinfo) {
25 // allocate some space we will alias with open-ended PmDriverInfo:
26 // there is space for 4 parameters:
27 static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
28 sysdepinfo = (PmSysDepInfo *) dimem;
29 // build the driver info structure:
30 sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
31 sysdepinfo->length = 0;
32 }
33 if (sysdepinfo->length > 1) {
34 printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
35 exit(1);
36 }
37 int i = sysdepinfo->length++;
38 enum PmSysDepPropertyKey k = pmKeyNone;
39 if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
40 else if (m_or_p == 'p') k = pmKeyAlsaPortName;
41 else if (m_or_p == 'c') k = pmKeyAlsaClientName;
42 sysdepinfo->properties[i].key = k;
43 sysdepinfo->properties[i].value = name;
44}
45
46
47static void prompt_and_exit(void)
48{
49 printf("type ENTER...");
50 while (getchar() != '\n') ;
51 /* this will clean up open ports: */
52 exit(-1);
53}
54
55
56static PmError checkerror(PmError err)
57{
58 if (err == pmHostError) {
59 /* it seems pointless to allocate memory and copy the string,
60 * so I will do the work of Pm_GetHostErrorText directly
61 */
62 char errmsg[80];
63 Pm_GetHostErrorText(errmsg, 80);
64 printf("PortMidi found host error...\n %s\n", errmsg);
65 prompt_and_exit();
66 } else if (err < 0) {
67 printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
68 prompt_and_exit();
69 }
70 return err;
71}
72
73
74void wait_until(PmTimestamp when)
75{
76 PtTimestamp now = Pt_Time();
77 if (when > now) {
78 Pt_Sleep(when - now);
79 }
80}
81
82
83void main_test_output(int num)
84{
85 PmStream *midi;
86 int32_t next_time;
87 PmEvent buffer[1];
88 PmTimestamp timestamp;
89 int pitch = 60;
90 int id;
91
92 /* It is recommended to start timer before Midi; otherwise, PortMidi may
93 start the timer with its (default) parameters
94 */
95 TIME_START;
96
97 /* create a virtual output device */
98 id = checkerror(Pm_CreateVirtualOutput(port_name, NULL, sysdepinfo));
99 checkerror(Pm_OpenOutput(&midi, id, sysdepinfo, OUTPUT_BUFFER_SIZE,
100 TIME_PROC, TIME_INFO, latency));
101
102 printf("Midi Output Virtual Device \"%s\" created.\n", port_name);
103 printf("Type ENTER to send messages: ");
104 while (getchar() != '\n') ;
105
106 buffer[0].timestamp = Pt_Time();
107#define PROGRAM 0
108 buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
109 Pm_Write(midi, buffer, 1);
110 next_time = Pt_Time() + 1000; /* wait 1s */
111 while (num > 0) {
112 wait_until(next_time);
113 Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100));
114 printf("Note On pitch %d\n", pitch);
115 num--;
116 next_time += 500;
117
118 wait_until(next_time);
119 Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0));
120 printf("Note Off pitch %d\n", pitch);
121 num--;
122 pitch = (pitch + 1) % 12 + 60;
123 next_time += 500;
124 }
125
126 /* close device (this not explicitly needed in most implementations) */
127 printf("ready to close...");
128 Pm_Close(midi);
129 printf("done closing.\nNow delete the virtual device...");
130 checkerror(Pm_DeleteVirtualDevice(id));
131 printf("done deleting.\n");
132}
133
134
135void show_usage()
136{
137 printf("Usage: sendvirtual [-h] [-l latency-in-ms] [-m manufacturer] "
138 "[-c clientname] [-p portname] [n]\n"
139 " -h for this message,\n"
140 " -l ms designates latency for precise timing (default 0),\n"
141 " -m name designates a manufacturer name (macOS only),\n"
142 " -c name designates a client name (linux only),\n"
143 " -p name designates a port name (linux only),\n"
144 " n is number of message to send.\n"
145 "sends change program to 1, then one note per second with 0.5s on,\n"
146 "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n"
147 "precise timing (see PortMidi documentation).\n");
148 exit(0);
149}
150
151
152int main(int argc, char *argv[])
153{
154 int num = 10;
155 int i;
156 if (argc <= 1) {
157 show_usage();
158 }
159 for (i = 1; i < argc; i++) {
160 if (strcmp(argv[i], "-h") == 0) {
161 show_usage();
162 } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
163 i = i + 1;
164 latency = atoi(argv[i]);
165 printf("Latency will be %d\n", latency);
166 } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) {
167 i = i + 1;
168 set_sysdepinfo('m', argv[i]);
169 printf("Manufacturer name will be %s\n", argv[i]);
170 } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
171 i = i + 1;
172 port_name = argv[i];
173 set_sysdepinfo('p', port_name);
174 printf("Port name will be %s\n", port_name);
175 } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
176 i = i + 1;
177 set_sysdepinfo('c', argv[i]);
178 printf("Client name will be %s\n", argv[i]);
179 } else {
180 num = atoi(argv[i]);
181 if (num <= 0) {
182 printf("Zero value or non-number for n\n");
183 show_usage();
184 }
185 printf("Sending %d messages.\n", num);
186 }
187 }
188
189 main_test_output(num);
190
191 printf("finished sendvirtual test...type ENTER to quit...");
192 while (getchar() != '\n') ;
193 return 0;
194}
diff --git a/portmidi/pm_test/sysex.c b/portmidi/pm_test/sysex.c
new file mode 100755
index 0000000..c2c7187
--- /dev/null
+++ b/portmidi/pm_test/sysex.c
@@ -0,0 +1,556 @@
1/* sysex.c -- example program showing how to send and receive sysex
2 messages
3
4 Messages are stored in a file using 2-digit hexadecimal numbers,
5 one per byte, separated by blanks, with up to 32 numbers per line:
6 F0 14 A7 4B ...
7
8 */
9
10#include "stdio.h"
11#include "stdlib.h"
12#include "assert.h"
13#include "portmidi.h"
14#include "porttime.h"
15#include "string.h"
16#ifdef WIN32
17// need to get declaration for Sleep()
18#include "windows.h"
19#else
20#include <unistd.h>
21#define Sleep(n) usleep(n * 1000)
22#endif
23
24// enable some extra printing
25#ifndef VERBOSE
26#define VERBOSE 0
27#endif
28
29#define MIDI_SYSEX 0xf0
30#define MIDI_EOX 0xf7
31
32#define STRING_MAX 80
33
34#ifndef true
35#define true 1
36#define false 0
37#endif
38
39int latency = 0;
40
41/* read a number from console */
42/**/
43int get_number(const char *prompt)
44{
45 int n = 0, i;
46 fputs(prompt, stdout);
47 while (n != 1) {
48 n = scanf("%d", &i);
49 while (getchar() != '\n') ;
50 }
51 return i;
52}
53
54
55/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */
56/**/
57void loopback_test()
58{
59 int outp;
60 int inp;
61 PmStream *midi_in;
62 PmStream *midi_out;
63 unsigned char msg[1024];
64 int32_t len;
65 int i;
66 int data;
67 PmEvent event;
68 int shift;
69 long total_bytes = 0;
70 int32_t begin_time;
71
72 Pt_Start(1, 0, 0);
73
74 printf("Connect a midi cable from an output port to an input port.\n");
75 printf("This test will send random data via sysex message from output\n");
76 printf("to input and check that the correct data was received.\n");
77 outp = get_number("Type output device number: ");
78 /* Open output with 1ms latency -- when latency is non-zero, the Win32
79 implementation supports sending sysex messages incrementally in a
80 series of buffers. This is nicer than allocating a big buffer for the
81 message, and it also seems to work better. Either way works.
82 */
83 while ((latency = get_number(
84 "Latency in milliseconds (0 to send data immediatedly,\n"
85 " >0 to send timestamped messages): ")) < 0);
86 Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
87 inp = get_number("Type input device number: ");
88 /* since we are going to send and then receive, make sure the input buffer
89 is large enough for the entire message */
90 Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
91
92 srand((unsigned int) Pt_Time()); /* seed for random numbers */
93
94 begin_time = Pt_Time();
95 while (total_bytes < 100000) {
96 PmError count;
97 int32_t start_time;
98 int error_position = -1; /* 0; -1; -1 for continuous */
99 int expected = 0;
100 int actual = 0;
101 /* this modification will run until an error is detected */
102 /* set error_position above to 0 for interactive, -1 for */
103 /* continuous */
104 if (error_position >= 0) {
105 int c;
106 printf("Type return to send message, q to quit: ");
107 while ((c = getchar()) != '\n') {
108 if (c == 'q') goto cleanup;
109 }
110 }
111
112 /* compose the message */
113 len = rand() % 998 + 2; /* len only counts data bytes */
114 msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */
115 /* data bytes go from 1 to len */
116 for (i = 0; i < len; i++) {
117/* pick whether data is sequential or random... (docs say random) */
118#define DATA_EXPR (i+1)
119// #define DATA_EXPR rand()
120 msg[i + 1] = DATA_EXPR & 0x7f; /* MIDI data */
121 }
122 /* final EOX goes in len+1, total of len+2 bytes in msg */
123 msg[len + 1] = (char) MIDI_EOX;
124
125 /* sanity check: before we send, there should be no queued data */
126 count = Pm_Read(midi_in, &event, 1);
127
128 if (count != 0) {
129 printf("Before sending anything, a MIDI message was found in\n");
130 printf("the input buffer. Please try again.\n");
131 break;
132 }
133
134 /* send the message two ways: 1) Pm_WriteSysEx, 2) Pm_Write */
135 if (total_bytes & 1) {
136 printf("Sending %d byte sysex msg via Pm_WriteSysEx.\n", len + 2);
137 Pm_WriteSysEx(midi_out, 0, msg);
138 } else {
139 PmEvent event = {0, 0};
140 int bits = 0;
141 printf("Sending %d byte sysex msg via Pm_Write(s).\n", len + 2);
142 for (i = 0; i < len + 2; i++) {
143 event.message |= (msg[i] << bits);
144 bits += 8;
145 if (bits == 32) { /* full message - send it */
146 Pm_Write(midi_out, &event, 1);
147 bits = 0;
148 event.message = 0;
149 }
150 }
151 if (bits > 0) { /* last message is partially full */
152 Pm_Write(midi_out, &event, 1);
153 }
154 }
155
156 /* receive the message and compare to msg[] */
157 data = 0;
158 shift = 0;
159 i = 0;
160 start_time = Pt_Time();
161 if (VERBOSE) {
162 printf("start_time %d\n", start_time);
163 }
164 error_position = -1;
165 /* allow up to 2 seconds for transmission */
166 while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) {
167 count = Pm_Read(midi_in, &event, 1);
168 if (count == 0) {
169 Sleep(1); /* be nice: give some CPU time to the system */
170 continue; /* continue polling for input */
171 }
172 if (VERBOSE) {
173 printf("read %08x ", event.message);
174 fflush(stdout);
175 }
176 /* compare 4 bytes of data until you reach an eox */
177 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
178 data = (event.message >> shift) & 0xFF;
179 if (data != msg[i] && error_position < 0) {
180 error_position = i;
181 expected = msg[i];
182 actual = data;
183 }
184 i++;
185 }
186 }
187 if (error_position >= 0) {
188 printf("Error at time %d byte %d: sent %x recd %x.\n", Pt_Time(),
189 error_position, expected, actual);
190 break;
191 } else if (i != len + 2) {
192 printf("Error at time %d: byte %d not received.\n", Pt_Time(), i);
193 break;
194 } else {
195 int seconds = (Pt_Time() - begin_time) / 1000;
196 if (seconds == 0) seconds = 1;
197 printf("Correctly received %d byte sysex message.\n", i);
198 total_bytes += i;
199 printf("Cummulative bytes/sec: %d, %d%% done.\n",
200 (int) (total_bytes / seconds),
201 (int) (100 * total_bytes / 100000));
202 }
203 }
204cleanup:
205 Pm_Close(midi_out);
206 Pm_Close(midi_in);
207 return;
208}
209
210
211/* send_multiple test -- send many sysex messages */
212/**/
213void send_multiple_test()
214{
215 int outp;
216 int length;
217 int num_msgs;
218 PmStream *midi_out;
219 unsigned char msg[1024];
220 int i;
221 PtTimestamp start_time;
222 PtTimestamp stop_time;
223
224 Pt_Start(1, 0, 0);
225
226 printf("This is for performance testing. You should be sending to this\n");
227 printf("program running the receive multiple test. Do NOT send to\n");
228 printf("a synthesizer or you risk reprogramming it\n");
229 outp = get_number("Type output device number: ");
230 while ((latency = get_number(
231 "Latency in milliseconds (0 to send data immediatedly,\n"
232 " >0 to send timestamped messages): ")) < 0);
233 Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
234 while ((length = get_number("Message length (7 - 1024): ")) < 7 ||
235 length > 1024) ;
236 while ((num_msgs = get_number("Number of messages: ")) < 1);
237 /* latency, length, and num_msgs should now all be valid */
238 /* compose the message except for sequence number in first 5 bytes */
239 msg[0] = (char) MIDI_SYSEX;
240 for (i = 6; i < length - 1; i++) {
241 msg[i] = i % 128; /* this is just filler */
242 }
243 msg[length - 1] = (char) MIDI_EOX;
244
245 start_time = Pt_Time();
246 /* send the messages */
247 for (i = num_msgs; i > 0; i--) {
248 /* insert sequence number into first 5 data bytes */
249 /* sequence counts down to zero */
250 int j;
251 int count = i;
252 /* 7 bits of message count i goes into each data byte */
253 for (j = 1; j <= 5; j++) {
254 msg[j] = count & 127;
255 count >>= 7;
256 }
257 /* send the message */
258 Pm_WriteSysEx(midi_out, 0, msg);
259 }
260 stop_time = Pt_Time();
261 Pm_Close(midi_out);
262 return;
263}
264
265#define MAX_MSG_LEN 1024
266static unsigned char receive_msg[MAX_MSG_LEN];
267static int receive_msg_index;
268static int receive_msg_length;
269static int receive_msg_count;
270static int receive_msg_error;
271static int receive_msg_messages;
272static PmStream *receive_msg_midi_in;
273static int receive_poll_running;
274
275/* receive_poll -- callback function to check for midi input */
276/**/
277void receive_poll(PtTimestamp timestamp, void *userData)
278{
279 PmError count;
280 PmEvent event;
281 int shift;
282 int data = 0;
283 int i;
284
285 if (!receive_poll_running) return; /* wait until midi device is opened */
286 shift = 0;
287 while (data != MIDI_EOX) {
288 count = Pm_Read(receive_msg_midi_in, &event, 1);
289 if (count == 0) return;
290
291 /* compare 4 bytes of data until you reach an eox */
292 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
293 receive_msg[receive_msg_index++] = data =
294 (event.message >> shift) & 0xFF;
295 if (receive_msg_index >= MAX_MSG_LEN) {
296 printf("error: incoming sysex too long\n");
297 goto error;
298 }
299 }
300 }
301 /* check the message */
302 if (receive_msg_length == 0) {
303 receive_msg_length = receive_msg_index;
304 }
305 if (receive_msg_length != receive_msg_index) {
306 printf("error: incoming sysex wrong length\n");
307 goto error;
308 }
309 if (receive_msg[0] != MIDI_SYSEX) {
310 printf("error: incoming sysex missing status byte\n");
311 goto error;
312 }
313 /* get and check the count */
314 count = 0;
315 for (i = 0; i < 5; i++) {
316 count += receive_msg[i + 1] << (7 * i);
317 }
318 if (receive_msg_count == -1) {
319 receive_msg_count = count;
320 receive_msg_messages = count;
321 }
322 if (receive_msg_count != count) {
323 printf("error: incoming sysex has wrong count\n");
324 goto error;
325 }
326 for (i = 6; i < receive_msg_index - 1; i++) {
327 if (receive_msg[i] != i % 128) {
328 printf("error: incoming sysex has bad data\n");
329 goto error;
330 }
331 }
332 if (receive_msg[receive_msg_length - 1] != MIDI_EOX) goto error;
333 receive_msg_index = 0; /* get ready for next message */
334 receive_msg_count--;
335 return;
336 error:
337 receive_msg_error = 1;
338 return;
339}
340
341
342/* receive_multiple_test -- send/rcv from 2 to 1000 bytes of random midi data */
343/**/
344void receive_multiple_test()
345{
346 PmError err;
347 int inp;
348
349 printf("This test expects to receive data sent by the send_multiple test\n");
350 printf("The test will check that correct data is received.\n");
351
352 /* Important: start PortTime first -- if it is not started first, it will
353 be started by PortMidi, and then our attempt to open again will fail */
354 receive_poll_running = false;
355 if ((err = Pt_Start(1, receive_poll, 0))) {
356 printf("PortTime error code: %d\n", err);
357 goto cleanup;
358 }
359 inp = get_number("Type input device number: ");
360 Pm_OpenInput(&receive_msg_midi_in, inp, NULL, 512, NULL, NULL);
361 receive_msg_index = 0;
362 receive_msg_length = 0;
363 receive_msg_count = -1;
364 receive_msg_error = 0;
365 receive_poll_running = true;
366 while ((!receive_msg_error) && (receive_msg_count != 0)) {
367#ifdef WIN32
368 Sleep(1000);
369#else
370 sleep(1); /* block and wait */
371#endif
372 }
373 if (receive_msg_error) {
374 printf("Receive_multiple test encountered an error\n");
375 } else {
376 printf("Receive_multiple test successfully received %d sysex messages\n",
377 receive_msg_messages);
378 }
379cleanup:
380 receive_poll_running = false;
381 Pm_Close(receive_msg_midi_in);
382 Pt_Stop();
383 return;
384}
385
386
387#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8)
388
389
390void receive_sysex()
391{
392 char line[80];
393 FILE *f;
394 PmStream *midi;
395 int shift = 0;
396 int data = 0;
397 int bytes_on_line = 0;
398 PmEvent msg;
399
400 /* determine which output device to use */
401 int i = get_number("Type input device number: ");
402
403 /* open input device */
404 Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL);
405 printf("Midi Input opened, type file for sysex data: ");
406
407 /* open file */
408 if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
409 /* remove the newline character */
410 if (strlen(line) > 0) line[strlen(line) - 1] = 0;
411 f = fopen(line, "w");
412 if (!f) {
413 printf("Could not open %s\n", line);
414 Pm_Close(midi);
415 return;
416 }
417
418 printf("Ready to receive a sysex message\n");
419
420 /* read data and write to file */
421 while (data != MIDI_EOX) {
422 PmError count;
423 count = Pm_Read(midi, &msg, 1);
424 /* CAUTION: this causes busy waiting. It would be better to
425 be in a polling loop to avoid being compute bound. PortMidi
426 does not support a blocking read since this is so seldom
427 useful.
428 */
429 if (count == 0) continue;
430 /* ignore real-time messages */
431 if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue;
432
433 /* write 4 bytes of data until you reach an eox */
434 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
435 data = (msg.message >> shift) & 0xFF;
436 /* if this is a status byte that's not MIDI_EOX, the sysex
437 message is incomplete and there is no more sysex data */
438 if (data & 0x80 && data != MIDI_EOX) break;
439 fprintf(f, "%2x ", data);
440 if (++bytes_on_line >= 16) {
441 fprintf(f, "\n");
442 bytes_on_line = 0;
443 }
444 }
445 }
446 fclose(f);
447 Pm_Close(midi);
448}
449
450
451void send_sysex()
452{
453 char line[80];
454 FILE *f;
455 PmStream *midi;
456 int data;
457 int shift = 0;
458 PmEvent msg;
459
460 /* determine which output device to use */
461 int i = get_number("Type output device number: ");
462 while ((latency = get_number(
463 "Latency in milliseconds (0 to send data immediatedly,\n"
464 " >0 to send timestamped messages): ")) < 0);
465
466 msg.timestamp = 0; /* no need for timestamp */
467
468 /* open output device */
469 Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency);
470 printf("Midi Output opened, type file with sysex data: ");
471
472 /* open file */
473 if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
474 /* remove the newline character */
475 if (strlen(line) > 0) line[strlen(line) - 1] = 0;
476 f = fopen(line, "r");
477 if (!f) {
478 printf("Could not open %s\n", line);
479 Pm_Close(midi);
480 return;
481 }
482
483 /* read file and send data */
484 msg.message = 0;
485 while (1) {
486 /* get next byte from file */
487
488 if (fscanf(f, "%x", &data) == 1) {
489 /* printf("read %x, ", data); */
490 /* OR byte into message at proper offset */
491 msg.message |= (data << shift);
492 shift += 8;
493 }
494 /* send the message if it's full (shift == 32) or if we are at end */
495 if (shift == 32 || data == MIDI_EOX) {
496 /* this will send sysex data 4 bytes at a time -- it would
497 be much more efficient to send multiple PmEvents at once
498 but this method is simpler. See Pm_WriteSysEx for a more
499 efficient code example.
500 */
501 Pm_Write(midi, &msg, 1);
502 msg.message = 0;
503 shift = 0;
504 }
505 if (data == MIDI_EOX) { /* end of message */
506 fclose(f);
507 Pm_Close(midi);
508 return;
509 }
510 }
511}
512
513
514int main()
515{
516 int i;
517
518 /* list device information */
519 for (i = 0; i < Pm_CountDevices(); i++) {
520 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
521 printf("%d: %s, %s", i, info->interf, info->name);
522 if (info->input) printf(" (input)");
523 if (info->output) printf(" (output)");
524 printf("\n");
525 }
526 while (1) {
527 char cmd;
528 printf("Type r to receive sysex, s to send,"
529 " l for loopback test, m to send multiple,"
530 " n to receive multiple, q to quit: ");
531 cmd = getchar();
532 while (getchar() != '\n') ;
533 switch (cmd) {
534 case 'r':
535 receive_sysex();
536 break;
537 case 's':
538 send_sysex();
539 break;
540 case 'l':
541 loopback_test();
542 break;
543 case 'm':
544 send_multiple_test();
545 break;
546 case 'n':
547 receive_multiple_test();
548 break;
549 case 'q':
550 exit(0);
551 default:
552 break;
553 }
554 }
555 return 0;
556}
diff --git a/portmidi/pm_test/testio.c b/portmidi/pm_test/testio.c
new file mode 100755
index 0000000..2711286
--- /dev/null
+++ b/portmidi/pm_test/testio.c
@@ -0,0 +1,594 @@
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 OUTPUT_BUFFER_SIZE 0
10#define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
11#define TIME_INFO NULL
12#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
13
14#define WAIT_FOR_ENTER while (getchar() != '\n') ;
15
16int32_t latency = 0;
17int verbose = FALSE;
18PmSysDepInfo *sysdepinfo = NULL;
19
20/* crash the program to test whether midi ports are closed */
21/**/
22void doSomethingReallyStupid() {
23 int * tmp = NULL;
24 *tmp = 5;
25}
26
27
28/* exit the program without any explicit cleanup */
29/**/
30void doSomethingStupid() {
31 assert(0);
32}
33
34
35/* read a number from console */
36/**/
37int get_number(const char *prompt)
38{
39 int n = 0, i;
40 fputs(prompt, stdout);
41 while (n != 1) {
42 n = scanf("%d", &i);
43 WAIT_FOR_ENTER
44 }
45 return i;
46}
47
48
49static void set_sysdepinfo(char m_or_p, const char *name)
50{
51 if (!sysdepinfo) {
52 // allocate some space we will alias with open-ended PmDriverInfo:
53 // there is space for 4 parameters:
54 static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
55 sysdepinfo = (PmSysDepInfo *) dimem;
56 // build the driver info structure:
57 sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
58 sysdepinfo->length = 0;
59 }
60 if (sysdepinfo->length > 1) {
61 printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
62 exit(1);
63 }
64 int i = sysdepinfo->length++;
65 enum PmSysDepPropertyKey k = pmKeyNone;
66 if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
67 else if (m_or_p == 'p') k = pmKeyAlsaPortName;
68 else if (m_or_p == 'c') k = pmKeyAlsaClientName;
69 sysdepinfo->properties[i].key = k;
70 sysdepinfo->properties[i].value = name;
71}
72
73
74/*
75 * the somethingStupid parameter can be set to simulate a program crash.
76 * We want PortMidi to close Midi ports automatically in the event of a
77 * crash because Windows does not (and this may cause an OS crash)
78 */
79void main_test_input(unsigned int somethingStupid) {
80 PmStream * midi;
81 PmError status, length;
82 PmEvent buffer[1];
83 int num = 10;
84 int i = get_number("Type input number: ");
85 /* It is recommended to start timer before Midi; otherwise, PortMidi may
86 start the timer with its (default) parameters
87 */
88 TIME_START;
89
90 /* open input device */
91 Pm_OpenInput(&midi,
92 i,
93 sysdepinfo,
94 INPUT_BUFFER_SIZE,
95 TIME_PROC,
96 TIME_INFO);
97
98 printf("Midi Input opened. Reading %d Midi messages...\n", num);
99 Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
100 /* empty the buffer after setting filter, just in case anything
101 got through */
102 while (Pm_Poll(midi)) {
103 Pm_Read(midi, buffer, 1);
104 }
105 /* now start paying attention to messages */
106 i = 0; /* count messages as they arrive */
107 while (i < num) {
108 status = Pm_Poll(midi);
109 if (status == TRUE) {
110 length = Pm_Read(midi, buffer, 1);
111 if (length > 0) {
112 printf("Got message %d @ time %ld: timestamp %ld, "
113 "%2lx %2lx %2lx\n", i, (long) Pt_Time(),
114 (long) buffer[0].timestamp,
115 (long) Pm_MessageStatus(buffer[0].message),
116 (long) Pm_MessageData1(buffer[0].message),
117 (long) Pm_MessageData2(buffer[0].message));
118 i++;
119 } else {
120 assert(0);
121 }
122 }
123 /* simulate crash if somethingStupid is 1 or 2 */
124 if ((i > (num/2)) && (somethingStupid == 1)) {
125 doSomethingStupid();
126 } else if ((i > (num/2)) && (somethingStupid == 2)) {
127 doSomethingReallyStupid();
128 }
129 }
130
131 /* close device (this not explicitly needed in most implementations) */
132 printf("ready to close...");
133
134 Pm_Close(midi);
135 printf("done closing...");
136}
137
138
139
140void main_test_output(int isochronous_test)
141{
142 PmStream * midi;
143 int32_t off_time;
144 int chord[] = { 60, 67, 76, 83, 90 };
145 #define chord_size 5
146 PmEvent buffer[chord_size];
147 PmTimestamp timestamp;
148
149 /* determine which output device to use */
150 int i = get_number("Type output number: ");
151
152 /* It is recommended to start timer before PortMidi */
153 TIME_START;
154
155 /* open output device -- since PortMidi avoids opening a timer
156 when latency is zero, we will pass in a NULL timer pointer
157 for that case. If PortMidi tries to access the time_proc,
158 we will crash, so this test will tell us something. */
159 Pm_OpenOutput(&midi,
160 i,
161 sysdepinfo,
162 OUTPUT_BUFFER_SIZE,
163 (latency == 0 ? NULL : TIME_PROC),
164 (latency == 0 ? NULL : TIME_INFO),
165 latency);
166 printf("Midi Output opened with %ld ms latency.\n", (long) latency);
167
168 /* output note on/off w/latency offset; hold until user prompts */
169 printf("ready to send program 1 change... (type ENTER):");
170 WAIT_FOR_ENTER
171 /* if we were writing midi for immediate output, we could always use
172 timestamps of zero, but since we may be writing with latency, we
173 will explicitly set the timestamp to "now" by getting the time.
174 The source of timestamps should always correspond to the TIME_PROC
175 and TIME_INFO parameters used in Pm_OpenOutput(). */
176 buffer[0].timestamp = Pt_Time();
177 /* Send a program change to increase the chances we will hear notes */
178 /* Program 0 is usually a piano, but you can change it here: */
179#define PROGRAM 0
180 buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
181 Pm_Write(midi, buffer, 1);
182
183 if (isochronous_test) { // play 4 notes per sec for 20s
184 int count;
185 PmTimestamp start;
186 if (latency < 100) {
187 printf("Warning: latency < 100, but this test sends messages"
188 " at times that are jittered by up to 100ms, so you"
189 " may hear uneven timing\n");
190 }
191 printf("Starting in 1s..."); fflush(stdout);
192 Pt_Sleep(1000);
193 start = Pt_Time();
194 for (count = 0; count < 80; count++) {
195 PmTimestamp next_time;
196 buffer[0].timestamp = start + count * 250;
197 buffer[0].message = Pm_Message(0x90, 69, 100);
198 buffer[1].timestamp = start + count * 250 + 200;
199 buffer[1].message = Pm_Message(0x90, 69, 0);
200 Pm_Write(midi, buffer, 2);
201 next_time = start + (count + 1) * 250;
202 // sleep for a random time up to 100ms to add jitter to
203 // the times at which we send messages. PortMidi timing
204 // should remove the jitter if latency > 100
205 while (Pt_Time() < next_time) {
206 Pt_Sleep(rand() % 100);
207 }
208 }
209 printf("Done sending 80 notes at 4 notes per second.\n");
210 } else {
211 PmError err = 0;
212 printf("ready to note-on... (type ENTER):");
213 WAIT_FOR_ENTER
214 buffer[0].timestamp = Pt_Time();
215 buffer[0].message = Pm_Message(0x90, 60, 100);
216 if ((err = Pm_Write(midi, buffer, 1))) {
217 printf("Pm_Write returns error: %d (%s)\n",
218 err, Pm_GetErrorText(err));
219 if (err == pmHostError) {
220 char errmsg[128];
221 Pm_GetHostErrorText(errmsg, 127);
222 printf(" Host error: %s\n", errmsg);
223 }
224 }
225 printf("ready to note-off... (type ENTER):");
226 WAIT_FOR_ENTER
227 buffer[0].timestamp = Pt_Time();
228 buffer[0].message = Pm_Message(0x90, 60, 0);
229 if ((err = Pm_Write(midi, buffer, 1))) {
230 printf("Pm_Write returns error: %d (%s)\n",
231 err, Pm_GetErrorText(err));
232 if (err == pmHostError) {
233 char errmsg[128];
234 Pm_GetHostErrorText(errmsg, 127);
235 printf(" Host error: %s\n", errmsg);
236 }
237 }
238
239 /* output short note on/off w/latency offset; hold until user prompts */
240 printf("ready to note-on (short form)... (type ENTER):");
241 WAIT_FOR_ENTER
242 Pm_WriteShort(midi, Pt_Time(),
243 Pm_Message(0x90, 60, 100));
244 printf("ready to note-off (short form)... (type ENTER):");
245 WAIT_FOR_ENTER
246 Pm_WriteShort(midi, Pt_Time(),
247 Pm_Message(0x90, 60, 0));
248
249 /* output several note on/offs to test timing.
250 Should be 1s between notes */
251 if (latency == 0) {
252 printf("chord should not arpeggiate, latency == 0\n");
253 } else {
254 printf("chord should arpeggiate (latency = %ld > 0\n",
255 (long) latency);
256 }
257 printf("ready to chord-on/chord-off... (type ENTER):");
258 WAIT_FOR_ENTER
259 timestamp = Pt_Time();
260 printf("starting timestamp %ld\n", (long) timestamp);
261 for (i = 0; i < chord_size; i++) {
262 buffer[i].timestamp = timestamp + 1000 * i;
263 buffer[i].message = Pm_Message(0x90, chord[i], 100);
264 }
265 Pm_Write(midi, buffer, chord_size);
266
267 off_time = timestamp + 1000 + chord_size * 1000;
268 while (Pt_Time() < off_time)
269 /* There was a report that Pm_Write with zero length sent last
270 * message again, so call Pm_Write here to see if note repeats
271 */
272 Pm_Write(midi, buffer, 0);
273 Pt_Sleep(20); /* wait */
274
275 for (i = 0; i < chord_size; i++) {
276 buffer[i].timestamp = timestamp + 1000 * i;
277 buffer[i].message = Pm_Message(0x90, chord[i], 0);
278 }
279 Pm_Write(midi, buffer, chord_size);
280 }
281
282 /* close device (this not explicitly needed in most implementations) */
283 printf("ready to close and terminate... (type ENTER):");
284 WAIT_FOR_ENTER
285
286 Pm_Close(midi);
287 Pm_Terminate();
288 printf("done closing and terminating...\n");
289}
290
291
292void main_test_both()
293{
294 int i = 0;
295 int in, out;
296 PmStream * midi, * midiOut;
297 PmEvent buffer[1];
298 PmError status, length;
299 int num = 11;
300
301 in = get_number("Type input number: ");
302 out = get_number("Type output number: ");
303
304 /* In is recommended to start timer before PortMidi */
305 TIME_START;
306
307 Pm_OpenOutput(&midiOut,
308 out,
309 sysdepinfo,
310 OUTPUT_BUFFER_SIZE,
311 TIME_PROC,
312 TIME_INFO,
313 latency);
314 printf("Midi Output opened with %ld ms latency.\n", (long) latency);
315 /* open input device */
316 Pm_OpenInput(&midi,
317 in,
318 sysdepinfo,
319 INPUT_BUFFER_SIZE,
320 TIME_PROC,
321 TIME_INFO);
322 printf("Midi Input opened. Reading %d Midi messages...\n", num);
323 Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
324 /* empty the buffer after setting filter, just in case anything
325 got through */
326 while (Pm_Poll(midi)) {
327 Pm_Read(midi, buffer, 1);
328 }
329 i = 0;
330 while (i < num) {
331 status = Pm_Poll(midi);
332 if (status == TRUE) {
333 length = Pm_Read(midi,buffer,1);
334 if (length > 0) {
335 Pm_Write(midiOut, buffer, 1);
336 printf("Got message %d @ time %ld: timestamp %ld, "
337 "%2lx %2lx %2lx\n", i, (long) Pt_Time(),
338 (long) buffer[0].timestamp,
339 (long) Pm_MessageStatus(buffer[0].message),
340 (long) Pm_MessageData1(buffer[0].message),
341 (long) Pm_MessageData2(buffer[0].message));
342 i++;
343 } else {
344 assert(0);
345 }
346 }
347 }
348 /* allow time for last message to go out */
349 Pt_Sleep(100 + latency);
350
351 /* close midi devices */
352 Pm_Close(midi);
353 Pm_Close(midiOut);
354 Pm_Terminate();
355}
356
357
358/* main_test_stream exercises windows winmm API's stream mode */
359/* The winmm stream mode is used for latency>0, and sends
360 timestamped messages. The timestamps are relative (delta)
361 times, whereas PortMidi times are absolute. Since peculiar
362 things happen when messages are not always sent in advance,
363 this function allows us to exercise the system and test it.
364 */
365void main_test_stream() {
366 PmStream * midi;
367 PmEvent buffer[16];
368
369 /* determine which output device to use */
370 int i = get_number("Type output number: ");
371
372 latency = 500; /* ignore LATENCY for this test and
373 fix the latency at 500ms */
374
375 /* It is recommended to start timer before PortMidi */
376 TIME_START;
377
378 /* open output device */
379 Pm_OpenOutput(&midi,
380 i,
381 sysdepinfo,
382 OUTPUT_BUFFER_SIZE,
383 TIME_PROC,
384 TIME_INFO,
385 latency);
386 printf("Midi Output opened with %ld ms latency.\n", (long) latency);
387
388 /* output note on/off w/latency offset; hold until user prompts */
389 printf("ready to send output... (type ENTER):");
390 WAIT_FOR_ENTER
391
392 /* if we were writing midi for immediate output, we could always use
393 timestamps of zero, but since we may be writing with latency, we
394 will explicitly set the timestamp to "now" by getting the time.
395 The source of timestamps should always correspond to the TIME_PROC
396 and TIME_INFO parameters used in Pm_OpenOutput(). */
397 buffer[0].timestamp = Pt_Time();
398 buffer[0].message = Pm_Message(0xC0, 0, 0);
399 buffer[1].timestamp = buffer[0].timestamp;
400 buffer[1].message = Pm_Message(0x90, 60, 100);
401 buffer[2].timestamp = buffer[0].timestamp + 1000;
402 buffer[2].message = Pm_Message(0x90, 62, 100);
403 buffer[3].timestamp = buffer[0].timestamp + 2000;
404 buffer[3].message = Pm_Message(0x90, 64, 100);
405 buffer[4].timestamp = buffer[0].timestamp + 3000;
406 buffer[4].message = Pm_Message(0x90, 66, 100);
407 buffer[5].timestamp = buffer[0].timestamp + 4000;
408 buffer[5].message = Pm_Message(0x90, 60, 0);
409 buffer[6].timestamp = buffer[0].timestamp + 4000;
410 buffer[6].message = Pm_Message(0x90, 62, 0);
411 buffer[7].timestamp = buffer[0].timestamp + 4000;
412 buffer[7].message = Pm_Message(0x90, 64, 0);
413 buffer[8].timestamp = buffer[0].timestamp + 4000;
414 buffer[8].message = Pm_Message(0x90, 66, 0);
415
416 Pm_Write(midi, buffer, 9);
417#ifdef SEND8
418 /* Now, we're ready for the real test.
419 Play 4 notes at now, now+500, now+1000, and now+1500
420 Then wait until now+2000.
421 Play 4 more notes as before.
422 We should hear 8 evenly spaced notes. */
423 now = Pt_Time();
424 for (i = 0; i < 4; i++) {
425 buffer[i * 2].timestamp = now + (i * 500);
426 buffer[i * 2].message = Pm_Message(0x90, 60, 100);
427 buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
428 buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
429 }
430 Pm_Write(midi, buffer, 8);
431
432 while (Pt_Time() < now + 2500)
433 Pt_Sleep(10);
434 /* now we are 500 ms behind schedule, but since the latency
435 is 500, the delay should not be audible */
436 now += 2000;
437 for (i = 0; i < 4; i++) {
438 buffer[i * 2].timestamp = now + (i * 500);
439 buffer[i * 2].message = Pm_Message(0x90, 60, 100);
440 buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
441 buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
442 }
443 Pm_Write(midi, buffer, 8);
444#endif
445 /* close device (this not explicitly needed in most implementations) */
446 printf("ready to close and terminate... (type ENTER):");
447 WAIT_FOR_ENTER
448
449 Pm_Close(midi);
450 Pm_Terminate();
451 printf("done closing and terminating...\n");
452}
453
454
455void show_usage()
456{
457 printf("Usage: test [-h] [-l latency-in-ms] [-c clientname] "
458 "[-p portname] [-v]\n"
459 " -h for this help message (only)\n"
460 " -l for latency\n"
461 " -c name designates a client name (linux only),\n"
462 " -p name designates a port name (linux only),\n"
463 " -v for verbose (enables more output)\n");
464}
465
466int main(int argc, char *argv[])
467{
468 int default_in;
469 int default_out;
470 int i = 0, n = 0;
471 int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
472 int isochronous_test = 0;
473 int stream_test = 0;
474 int latency_valid = FALSE;
475
476 show_usage();
477 if (sizeof(void *) == 8)
478 printf("Apparently this is a 64-bit machine.\n");
479 else if (sizeof(void *) == 4)
480 printf ("Apparently this is a 32-bit machine.\n");
481
482 for (i = 1; i < argc; i++) {
483 if (strcmp(argv[i], "-h") == 0) {
484 exit(0);
485 } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
486 i = i + 1;
487 const char *port_name = argv[i];
488 set_sysdepinfo('p', port_name);
489 printf("Port name will be %s\n", port_name);
490 } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
491 i = i + 1;
492 set_sysdepinfo('c', argv[i]);
493 printf("Client name will be %s\n", argv[i]);
494 } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
495 i = i + 1;
496 latency = atoi(argv[i]);
497 printf("Latency will be %ld\n", (long) latency);
498 latency_valid = TRUE;
499 } else if (strcmp(argv[i], "-v") == 0) {
500 printf("Verbose is now TRUE\n");
501 verbose = TRUE; /* not currently used for anything */
502 } else {
503 show_usage();
504 exit(0);
505 }
506 }
507
508 while (!latency_valid) {
509 int lat; // declared int to match "%d"
510 printf("Latency in ms: ");
511 if (scanf("%d", &lat) == 1) {
512 latency = (int32_t) lat; // coerce from "%d" to known size
513 latency_valid = TRUE;
514 }
515 }
516
517 /* determine what type of test to run */
518 printf("begin portMidi test...\n");
519 printf("enter your choice...\n 1: test input\n"
520 " 2: test input (fail w/assert)\n"
521 " 3: test input (fail w/NULL assign)\n"
522 " 4: test output\n 5: test both\n"
523 " 6: stream test (for WinMM)\n"
524 " 7. isochronous out\n");
525 while (n != 1) {
526 n = scanf("%d", &i);
527 WAIT_FOR_ENTER
528 switch(i) {
529 case 1:
530 test_input = 1;
531 break;
532 case 2:
533 test_input = 1;
534 somethingStupid = 1;
535 break;
536 case 3:
537 test_input = 1;
538 somethingStupid = 2;
539 break;
540 case 4:
541 test_output = 1;
542 break;
543 case 5:
544 test_both = 1;
545 break;
546 case 6:
547 stream_test = 1;
548 break;
549 case 7:
550 test_output = 1;
551 isochronous_test = 1;
552 break;
553 default:
554 printf("got %d (invalid input)\n", n);
555 break;
556 }
557 }
558
559 /* list device information */
560 default_in = Pm_GetDefaultInputDeviceID();
561 default_out = Pm_GetDefaultOutputDeviceID();
562 for (i = 0; i < Pm_CountDevices(); i++) {
563 char *deflt;
564 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
565 if (((test_input | test_both) & info->input) |
566 ((test_output | test_both | stream_test) & info->output)) {
567 printf("%d: %s, %s", i, info->interf, info->name);
568 if (info->input) {
569 deflt = (i == default_in ? "default " : "");
570 printf(" (%sinput)", deflt);
571 }
572 if (info->output) {
573 deflt = (i == default_out ? "default " : "");
574 printf(" (%soutput)", deflt);
575 }
576 printf("\n");
577 }
578 }
579
580 /* run test */
581 if (stream_test) {
582 main_test_stream();
583 } else if (test_input) {
584 main_test_input(somethingStupid);
585 } else if (test_output) {
586 main_test_output(isochronous_test);
587 } else if (test_both) {
588 main_test_both();
589 }
590
591 printf("finished portMidi test...type ENTER to quit...");
592 WAIT_FOR_ENTER
593 return 0;
594}
diff --git a/portmidi/pm_test/txdata.syx b/portmidi/pm_test/txdata.syx
new file mode 100755
index 0000000..1e06e5a
--- /dev/null
+++ b/portmidi/pm_test/txdata.syx
@@ -0,0 +1,257 @@
120 0 1d 4 c 6 0 34 1 4d 4 d 1f 7 3 6
2 c 5e 4 4d d b 18 5 3 6 0 3d 1 4a 16 18
31f 8 3 6 d 0 1 63 4 13 3a 23 0 0 0 2
4 c 2 4 0 63 32 0 0 0 32 0 47 72 61 6e 64
550 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0
610 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9 0 0 1f 9 9 f c 27 2 35 37 10 1f 4 3 4
10 d 19 4 56 5 16 1f f 8 d c 0 43 60 4 e
111f c 3 7 e 0 43 63 5 10 3c 14 8 2 1b 56
12 5 2 4 0 63 32 0 0 0 32 0 4c 6f 54 69 6e
1365 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0
1418 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
17 0 0 1f e f e 9 0 3 43 2d e 1f f 5 7
18 f 16 43 5a 0 0 1f 12 6 8 d 0 3 63 4 0
191f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e
2018 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d
2169 74 65 45 50 63 63 63 32 32 32 0 70 0 0 0
22 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
25 0 0 1f b 1 b 8 18 40 5f a e 1f 1f 0 a
26 f 0 40 5f 4 0 1f 1f 0 a f 0 40 63 5 6
271f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a
2818 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f
2972 67 61 6e 20 63 63 63 32 32 32 0 0 0 0 0
30 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
31 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
33 0 0 1f b 7 f 9 0 4 49 13 13 1f 8 7 5
34 e 0 2 58 0 c 1f 6 4 6 f 23 3 46 10 a
351f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52
3618 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20
3743 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0
3810 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
41 0 0 1f c 0 6 1 a 4 50 20 e 1f c 0 6
42 1 a 4 50 1f 8 1f b 9 5 e 0 2 63 5 e
431f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52
441d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65
4543 65 6c 73 74 63 63 63 32 32 32 0 20 0 26 0
46 1 0 8 4 0 0 0 0 0 0 0 0 0 0 0 0
47 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
48 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
49 0 0 f 1f 4 8 f 0 3a 51 4 b e 1f 0 8
50 f 0 22 4b 4 3 f 1a b 8 d 0 3b 36 9 3
5112 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52
5218 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70
5365 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0
5451 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
55 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
57 0 0 c 5 0 8 0 0 2 4a 4 b f 1f 0 8
58 f 0 2 3f 4 3 1f f 0 8 0 23 3 44 b 3
5910 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52
6018 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65
616c 68 6f 72 6e 63 63 63 32 32 32 0 0 0 0 0
62 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
65 0 0 10 1f 0 8 f 0 42 4a 0 3 11 1f 0 8
66 f a 43 51 0 3 11 9 0 8 d 0 42 2b 16 6
6710 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a
6824 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41
696c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0
7054 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0
71 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
72 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
73 0 0 10 9 2 6 d 0 41 3e 4 15 c b 2 3
74 e 0 41 4f 4 12 c e 2 8 d 0 42 4b a 1c
75 d b 1 9 e 0 3 63 a 14 0 23 f 2 1b 5e
7618 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f
776e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0
7850 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
79 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
81 0 0 1c 2 0 4 e 63 0 4e 4 3 d 5 0 6
82 e 63 1 56 a 8 12 7 0 6 9 63 2 47 1b e
83 a a 0 5 f 0 1 63 4 b 32 1a 8 d 0 52
84 c 4 4 0 63 32 0 0 0 32 0 44 6f 75 62 6c
8565 42 61 73 73 63 63 63 32 32 32 0 10 0 0 0
86 3 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
87 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
88 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
89 0 0 b 4 0 4 f 14 2 49 9 6 a 7 0 4
90 f 14 2 51 a 0 8 1f 0 5 f 0 1 63 9 6
91 a 1f 0 5 f 0 1 63 a 0 3c 1f 6 9 0 52
92 5 4 4 0 63 32 0 0 0 32 0 48 69 53 74 72
9369 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0
9432 0 10 5 0 0 0 0 0 0 0 0 0 0 0 0
95 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
96 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
97 0 0 10 13 f 4 a 0 3 3b 14 14 1f e 8 7
98 9 0 2 42 5 e 18 13 d 9 c 0 2 3c 13 8
991f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52
1001d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20
10120 20 20 20 20 63 63 63 32 32 32 8 0 0 21 0
102 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0
103 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
104 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
105 0 0 1f 6 6 4 f 0 40 48 5 0 c 8 7 5
106 f 5 0 52 4 0 f 7 3 7 e 8 3 63 4 6
107 f 8 4 5 f 0 3 63 4 6 7c 1f 0 6 0 4a
10811 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61
10972 54 70 74 73 63 63 63 32 32 32 6 1 0 38 0
110 8 0 48 0 0 0 0 0 0 0 0 0 0 0 0 0
111 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
112 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
113 0 0 d b 0 1 c 0 2 2c 3d 3 d 7 0 1
114 c 0 2 1f 3c 3 d 1f 0 5 f 0 2 63 5 6
115 d 1f 0 5 f 0 2 63 4 0 3c 63 0 2f 0 53
11611 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74
11768 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0
11811 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0
119 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
120 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
121 0 0 1f 9 0 6 0 27 2 51 19 b 1c 6 0 8
122 0 37 2 47 a 3 1f a 0 9 0 3d 2 4d a e
1231f 12 8 8 f 0 3 61 4 b 28 1f 0 3 0 52
124 c 3 4 0 63 32 1 32 0 32 0 4e 79 6c 6f 6e
12547 75 69 74 20 63 63 63 32 32 32 0 0 0 0 0
126 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
128 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
129 0 0 1f e e f f 0 3 48 2d 6 1f f 4 f
130 f 25 3 5b 0 0 1f 12 6 c e 1c 3 55 0 10
1311f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42
13218 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61
13372 20 23 31 20 63 63 63 32 32 32 0 0 0 0 0
134 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
135 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
136 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
137 0 0 1f 19 8 a 3 0 3 63 10 18 1f c 5 b
138 5 0 3 52 0 b 1f 19 6 b 5 0 3 63 a 16
1391f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42
14018 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79
14120 50 69 63 6b 63 63 63 32 32 32 0 30 0 0 0
142 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
143 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
144 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
145 0 0 1f 1 0 8 4 0 3 3d a 1e 1f 1 0 8
146 0 0 0 43 0 10 1f 9 6 8 c 1b 7 46 1c 1e
1471f 9 0 9 9 0 1 63 4 3 3a 1c 0 0 0 52
148 c 4 5 0 63 4b 0 0 0 32 0 45 6c 65 63 42
14961 73 73 20 31 63 63 63 32 32 32 0 0 0 0 0
150 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
151 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
152 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
153 0 0 1f f f e 9 0 3 46 1d 16 1f f 5 e
154 e d 3 63 0 b 1f 13 6 5 d 1c 3 63 0 0
1551f 13 6 8 f 0 4 63 4 6 3b 1f 0 0 0 42
156 c 4 4 0 63 32 0 32 0 32 0 53 79 6e 46 75
1576e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0
15870 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
159 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
160 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
161 0 0 1f 10 7 8 3 0 3 4f 4 3 1f 9 0 8
162 0 0 1 4a 0 b 1f 11 0 8 0 0 1 47 4 8
1631f 9 0 8 0 0 0 63 0 b 39 19 0 7 0 52
164 c 2 4 0 63 32 0 32 0 32 0 4c 61 74 65 6c
16579 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0
16640 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
167 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
168 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
169 0 0 13 12 0 9 d 22 0 51 0 b 1f 14 0 5
170 8 24 40 5c 0 3 1f 11 0 6 c 2c 0 53 9 0
17110 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e
17218 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20
1734c 65 61 64 20 63 63 63 32 32 32 0 70 0 40 0
174 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
175 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
176 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
177 0 0 13 1e 0 9 e 0 0 63 3f b 1f 14 0 5
178 e 24 1 51 4 3 1f 14 0 f 1 0 41 4d 8 3
179 f 1f 0 b f 0 2 63 4 b 3b 20 11 12 33 56
18018 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20
18146 6c 75 74 65 63 63 63 32 32 32 0 0 0 0 0
182 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
183 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
184 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
185 0 0 15 13 d 3 d 1e 2 50 18 e 15 14 9 4
186 c 1e 2 56 11 8 1b 1f f 7 f 0 1 63 4 6
1871a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62
18818 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20
1894a 69 76 65 20 63 63 63 32 32 32 0 0 0 0 0
190 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
191 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
192 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
193 0 0 1f 0 0 4 f 0 40 63 3c 0 b 8 7 7
194 f 5 0 63 4 6 f 5 3 7 f 8 0 3b 5 6
195 e 8 4 5 f 0 3 63 3 0 7e 1d 6 f 0 4a
19611 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42
19772 65 61 74 68 63 63 63 32 32 32 6 30 0 38 0
198 1 0 46 0 0 0 0 0 0 0 0 0 0 0 0 0
199 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
200 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
201 0 0 1f 0 0 4 f 0 40 47 2f 0 e 8 7 7
202 f 5 0 4c 0 6 13 1c d c 6 8 0 63 5 6
20314 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68
20417 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c
2054e 75 74 73 20 63 63 63 32 32 32 6 30 0 30 0
206 1 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
207 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
208 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
209 0 0 1f 1f 0 5 f 0 0 41 32 3 1f 14 10 5
210 5 1 2 63 7 3 1f b 12 8 f 0 1 63 c 3
2111f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62
21218 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72
21347 6c 61 73 73 63 63 63 32 32 32 0 0 0 0 0
214 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
215 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
216 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
217 0 0 16 2 0 4 6 9 1 4f 8 0 19 e 1 4
218 0 20 1 43 19 0 1f 12 10 6 7 0 0 54 3d 3
21916 d 6 6 2 1e 3 61 8 e 3a 20 1 14 0 42
220 c 2 4 2 63 63 63 0 0 32 0 46 75 7a 7a 79
22120 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b
22250 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
223 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
224 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
225 0 0 1c 8 0 3 e 0 1 55 12 3 1c 7 0 1
226 e 2e 1 58 27 b e 4 0 2 a 0 2 63 4 a
227 d 9 0 2 c 1 2 63 10 b 4 54 0 47 0 53
22818 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62
22965 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0
23040 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
231 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
232 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
233 0 0 1a 4 1 1 b 16 0 47 5 3 15 e 0 1
234 d 0 0 4c 5 16 1c 6 4 2 7 0 0 63 4 16
23518 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62
23624 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20
23742 65 6c 6c 73 63 63 63 32 32 32 0 0 0 0 0
238 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
239 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
240 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
241 0 0 1f 1f 13 3 0 0 0 5f 3d 6 1f 12 13 2
242 0 0 1 52 5 2 1f 14 13 3 0 0 1 56 28 5
2431e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73
24423 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65
24520 53 68 6f 74 63 63 63 32 32 32 8 0 0 0 8
246 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0
247 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
248 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
249 0 0 1f 16 0 3 7 0 1 50 0 3 1f 18 3 3
250 3 22 0 63 0 14 1d 7 6 3 6 0 1 3c 8 3
2511f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42
25218 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20
25344 72 75 6d 20 63 63 63 32 32 32 0 1 0 3 0
254 1 0 1 3 0 0 0 0 0 0 0 0 0 0 0 0
255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
256 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
257 0 0 7d f7 \ No newline at end of file
diff --git a/portmidi/pm_test/virttest.c b/portmidi/pm_test/virttest.c
new file mode 100644
index 0000000..1aeb09b
--- /dev/null
+++ b/portmidi/pm_test/virttest.c
@@ -0,0 +1,339 @@
1/* virttest.c -- test for creating/deleting virtual ports */
2/*
3 * Roger B. Dannenberg
4 * Oct 2021
5
6This test is performed by running 2 instances of the program. The
7first instance makes input and output ports named portmidi and waits
8for a message. The second tries to do the same, but will fail because
9portmidi already exists. It then opens portmidi (both input and
10output). In greater detail:
11
12FIRST INSTANCE SECOND INSTANCE
13-------------- ---------------
14
15initialize PortMidi initialize PortMidi
16create portmidi in
17create portmidi out
18wait for input
19 create portmidi in -> fails
20 open portmidi in/out
21 send to portmidi
22recv from portmidi
23send to portmidi
24wait 1s recv from portmidi
25 close portmidi in and out
26 terminate PortMidi
27list all devices:
28 - check for correct number
29 - check for good description of portmidi in port (open)
30 - check for good description of portmidi out port (open)
31close portmidi in
32list all devices:
33 - check for correct number
34 - check for good description of portmidi in port (closed)
35 - check for good description of portmidi out port (open)
36close portmidi out
37list all devices:
38 - check for correct number
39 - check for good description of portmidi in port (closed)
40 - check for good description of portmidi out port (closed)
41delete portmidi in
42 - check for correct number
43 - check for NULL description of portmidi in port
44 - check for good description of portmidi out port (closed)
45delete portmidi out
46 - check for correct number
47 - check for NULL description of portmidi in port
48 - check for NULL description of portmidi out port
49terminate portmidi
50REPEAT 3 TIMES wait 2 seconds to give head start to other instance
51 REPEAT 3 TIMES
52 */
53
54#include "portmidi.h"
55#include "porttime.h"
56#include "stdlib.h"
57#include "stdio.h"
58#include "string.h"
59#include "assert.h"
60
61#define OUTPUT_BUFFER_SIZE 0
62#define INPUT_BUFFER_SIZE 10
63#define DEVICE_INFO NULL
64#define DRIVER_INFO NULL
65#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
66#define TIME_INFO NULL
67#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
68
69
70static void prompt_and_exit(void)
71{
72 printf("type ENTER...");
73 while (getchar() != '\n') ;
74 /* this will clean up open ports: */
75 exit(-1);
76}
77
78
79static PmError printerror(PmError err, const char *msg)
80{
81 if (err == pmHostError) {
82 /* it seems pointless to allocate memory and copy the string,
83 * so I will do the work of Pm_GetHostErrorText directly
84 */
85 char errmsg[80];
86 Pm_GetHostErrorText(errmsg, 80);
87 printf("%s\n %s\n", msg, errmsg);
88 } else if (err < 0) {
89 printf("%s\n %s\n", msg, Pm_GetErrorText(err));
90 }
91 return err;
92}
93
94
95static PmError checkerror(PmError err)
96{
97 if (err < 0) {
98 printerror(err, "PortMidi call failed...");
99 prompt_and_exit();
100 }
101 return err;
102}
103
104
105void wait_until(PmTimestamp when)
106{
107 PtTimestamp now = Pt_Time();
108 if (when > now) {
109 Pt_Sleep(when - now);
110 }
111}
112
113
114void show_usage()
115{
116 printf("Usage: virttest\n"
117 " run two instances to test virtual port create/delete\n");
118}
119
120
121void check_info(int id, char stat, int input, int virtual)
122{
123 const PmDeviceInfo *info = Pm_GetDeviceInfo(id);
124 if (stat == 'd') {
125 if (info) {
126 printf("Expected device %d to be deleted.\n", id);
127 prompt_and_exit();
128 }
129 return;
130 }
131 if (!info) {
132 printf("Expected device %d to not be deleted.\n", id);
133 prompt_and_exit();
134 }
135 if (strcmp("portmidi", info->name) != 0) {
136 printf("Device %d name is %s, not \"portmidi\".\n", id, info->name);
137 prompt_and_exit();
138 }
139 if (info->input != input || (!info->output) != input) {
140 printf("Device %d input/output fields are wrong.\n", id);
141 prompt_and_exit();
142 }
143 if ((!info->opened && stat == 'o') || (info->opened && stat == 'c')) {
144 printf("Device %d opened==%d, status should be %c.\n", id,
145 info->opened, stat);
146 prompt_and_exit();
147 }
148 if (info->is_virtual != virtual) {
149 printf("Expected device %d to be virtual.\n", id);
150 prompt_and_exit();
151 }
152}
153
154
155/* stat is 'o' for open, 'c' for closed, 'd' for deleted device */
156void check_ports(int cnt, int in_id, char in_stat,
157 int out_id, char out_stat, int virtual)
158{
159 if (cnt != Pm_CountDevices()) {
160 printf("Device count changed from %d to %d.\n", cnt, Pm_CountDevices());
161 prompt_and_exit();
162 }
163 check_info(in_id, in_stat, TRUE, virtual);
164 check_info(out_id, out_stat, FALSE, virtual);
165}
166
167
168void devices_list()
169{
170 int i;
171 for (i = 0; i < Pm_CountDevices(); i++) {
172 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
173 if (info) {
174 printf("%d: %s %s %s %s\n", i, info->name,
175 (info->input ? "input" : "output"),
176 (info->is_virtual ? "virtual" : "real_device"),
177 (info->opened ? "opened" : "closed"));
178 }
179 }
180}
181
182
183void test2()
184{
185 PmStream *out = NULL;
186 PmStream *in = NULL;
187 int out_id;
188 int in_id;
189 PmEvent buffer[1];
190 PmTimestamp timestamp;
191 int pitch = 60;
192 int device_count = 0;
193 int i;
194
195 printf("This must be virttest instance #2\n");
196
197 /* find and open portmidi in and out */
198 device_count = Pm_CountDevices();
199 for (i = 0; i < device_count; i++) {
200 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
201 if (info && strcmp(info->name, "portmidi") == 0) {
202 if (info->input) {
203 checkerror(Pm_OpenInput(&in, i, DRIVER_INFO,
204 INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO));
205 in_id = i;
206 } else {
207 checkerror(Pm_OpenOutput(&out, i, DRIVER_INFO,
208 OUTPUT_BUFFER_SIZE, NULL, NULL, 0));
209 out_id = i;
210 }
211 }
212 }
213 if (!in) {
214 printf("Did not open portmidi as input (virtual output).\n");
215 prompt_and_exit();
216 }
217 if (!out) {
218 printf("Did not open portmidi as output (virtual input).\n");
219 prompt_and_exit();
220 }
221 printf("Input device %d and output device %d are open.\n", in_id, out_id);
222
223 /* send a message */
224 buffer[0].timestamp = 0;
225 buffer[0].message = Pm_Message(0x90, pitch, 100);
226 checkerror(Pm_Write(out, buffer, 1));
227
228 /* wait for reply */
229 printf("Sent message, waiting for reply...\n");
230 while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10);
231
232 printf("********** GOT THE MESSAGE, SHUTTING DOWN ************\n");
233
234 /* close in */
235 checkerror(Pm_Close(in));
236 check_ports(device_count, in_id, 'c', out_id, 'o', FALSE);
237 printf("Closed input %d\n", in_id);
238
239 /* close out */
240 checkerror(Pm_Close(out));
241 check_ports(device_count, in_id, 'c', out_id, 'c', FALSE);
242 printf("Closed output %d\n", out_id);
243
244 Pt_Sleep(1000);
245 /* wrap it up */
246 Pm_Terminate();
247 printf("Got reply and terminated...\n");
248 Pt_Sleep(2000); /* 2 seconds because other is waiting 1s. */
249 /* 1 more second to make sure other shuts down before test repeats. */
250}
251
252extern int pm_check_errors;
253
254void test()
255{
256 PmStream *out;
257 PmStream *in;
258 int out_id;
259 int in_id;
260 PmEvent buffer[1];
261 PmTimestamp timestamp;
262 int device_count = 0;
263
264 TIME_START;
265
266 printf("******** INITIALIZING PORTMIDI ***********\n");
267 timestamp = Pt_Time();
268 Pm_Initialize();
269 printf("Pm_Initialize took %dms\n", Pt_Time() - timestamp);
270 devices_list();
271
272 pm_check_errors = FALSE; /* otherwise, PM_CHECK_ERRORS, if defined, */
273 /* can cause this program to report an error and exit on pmNameConflict. */
274 in_id = Pm_CreateVirtualInput("portmidi", NULL, DEVICE_INFO);
275 pm_check_errors = TRUE; /* there should be no other errors */
276 if (in_id < 0) {
277 printerror(in_id, "Pm_CreateVirtualInput failed...");
278 test2();
279 return;
280 }
281 printf("Created portmidi virtual input; this is virttest instance #1\n");
282 out_id = checkerror(Pm_CreateVirtualOutput("portmidi", NULL, DRIVER_INFO));
283 device_count = Pm_CountDevices();
284
285 checkerror(Pm_OpenInput(&in, in_id, NULL, 0, NULL, NULL));
286 checkerror(Pm_OpenOutput(&out, out_id, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
287 TIME_PROC, TIME_INFO, 0));
288 printf("Created/Opened input %d and output %d\n", in_id, out_id);
289 Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
290 /* empty the buffer after setting filter, just in case anything
291 got through */
292 while (Pm_Read(in, buffer, 1)) ;
293
294 /* wait for input */
295 printf("Waiting for input...\n");
296 while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10);
297
298 /* send two replies (only one would be fine) */
299 checkerror(Pm_Write(out, buffer, 1));
300 printf("Received input, writing output...\n");
301
302 /* wait 1s so receiver can get the message before we shut down */
303 Pt_Sleep(1000);
304 printf("****** Closing everything and shutting down...\n");
305
306 /* expect 2 open ports */
307 check_ports(device_count, in_id, 'o', out_id, 'o', TRUE);
308 /* close in */
309 checkerror(Pm_Close(in));
310 check_ports(device_count, in_id, 'c', out_id, 'o', TRUE);
311
312 /* close out */
313 checkerror(Pm_Close(out));
314 check_ports(device_count, in_id, 'c', out_id, 'c', TRUE);
315
316 /* delete in */
317 checkerror(Pm_DeleteVirtualDevice(in_id));
318 check_ports(device_count, in_id, 'd', out_id, 'c', TRUE);
319
320 /* delete out */
321 checkerror(Pm_DeleteVirtualDevice(out_id));
322 check_ports(device_count, in_id, 'd', out_id, 'd', TRUE);
323
324 /* we are done */
325 Pm_Terminate();
326}
327
328
329int main(int argc, char *argv[])
330{
331 int i;
332 show_usage();
333 for (i = 0; i < 3; i++) {
334 test();
335 }
336 printf("finished virttest (SUCCESS). Type ENTER to quit...");
337 while (getchar() != '\n') ;
338 return 0;
339}
diff --git a/portmidi/pm_win/README_WIN.txt b/portmidi/pm_win/README_WIN.txt
new file mode 100755
index 0000000..8bfb467
--- /dev/null
+++ b/portmidi/pm_win/README_WIN.txt
@@ -0,0 +1,174 @@
1File: PortMidi Win32 Readme
2Author: Belinda Thom, June 16 2002
3Revised by: Roger Dannenberg, June 2002, May 2004, June 2007,
4 Umpei Kurokawa, June 2007
5 Roger Dannenberg Sep 2009, May 2022
6
7Contents:
8 Using Portmidi
9 To Install Portmidi
10 To Compile Portmidi
11 About Cmake
12 Using other versions of Visual C++
13 To Create Your Own Portmidi Client Application
14
15
16
17=============================================================================
18USING PORTMIDI:
19=============================================================================
20
21I recommend building a static library and linking with your
22application. PortMidi is not large. See ../README.md for
23basic compiling instructions.
24
25The Windows version has a couple of extra switches: You can define
26DEBUG and MMDEBUG for a few extra messages (see the code).
27
28If PM_CHECK_ERRORS is defined, PortMidi reports and exits on any
29error. This requires terminal output to see, and aborts your
30application, so it's only intended for quick command line programs
31where you do not care to check return values and handle errors
32more robustly.
33
34PortMidi is designed to run without a console and should work perfectly
35well within a graphical user interface application.
36
37Read the portmidi.h file for PortMidi API details on using the PortMidi API.
38See <...>\pm_test\testio.c and other files in pm_test for usage examples.
39
40There are many other programs in pm_test, including a MIDI monitor.
41
42
43============================================================================
44DESIGN NOTES
45============================================================================
46
47Orderly cleanup after errors are encountered is based on a fixed order of
48steps and state changes to reflect each step. Here's the order:
49
50To open input:
51 initialize return value to NULL
52 - allocate the PmInternal strucure (representation of PortMidiStream)
53 return value is (non-null) PmInternal structure
54 - allocate midi buffer
55 set buffer field of PmInternal structure
56 - call system-dependent open code
57 - allocate midiwinmm_type for winmm dependent data
58 set descriptor field of PmInternal structure
59 - open device
60 set handle field of midiwinmm_type structure
61 - allocate buffers
62 - start device
63 - return
64 - return
65
66SYSEX HANDLING
67
68There are three cases: simple output, stream output, input
69Each must deal with:
70 1. Buffer Initialization (creating buffers)
71 2. Buffer Allocation (finding a free buffer)
72 3. Buffer Fill (putting bytes in the buffer)
73 4. Buffer Preparation (midiOutPrepare, etc.)
74 5. Buffer Send (to Midi device)
75 6. Buffer Receive (in callback)
76 7. Buffer Empty (removing bytes from buffer)
77 8. Buffer Free (returning to the buffer pool)
78 9. Buffer Finalization (returning to heap)
79
80Here's how simple output handles sysex:
81 1. Buffer Initialization (creating buffers)
82 allocated when code tries to write first byte to a buffer
83 the test is "if (!m->sysex_buffers[0]) { ... }"
84 this field is initialized to NULL when device is opened
85 the size is SYSEX_BYTES_PER_BUFFER
86 allocate_sysex_buffers() does the initialization
87 note that the actual size of the allocation includes
88 additional space for a MIDIEVENT (3 longs) which are
89 not used in this case
90 2. Buffer Allocation (finding a free buffer)
91 see get_free_sysex_buffer()
92 cycle through m->sysex_buffers[] using m->next_sysex_buffer
93 to determine where to look next
94 if nothing is found, wait by blocking on m->sysex_buffer_signal
95 this is signaled by the callback every time a message is
96 received
97 3. Buffer Fill (putting bytes in the buffer)
98 essentially a state machine approach
99 hdr->dwBytesRecorded is a position in message pointed to by m->hdr
100 keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER
101 then send the message, reseting the state to initial values
102 4. Buffer Preparation (midiOutPrepare, etc.)
103 just before sending in winmm_end_sysex()
104 5. Buffer Send (to Midi device)
105 message is padded with zero at end (since extra space was allocated
106 this is ok) -- the zero works around a bug in (an old version of)
107 MIDI YOKE drivers
108 dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0
109 uses midiOutLongMsg()
110 6. Buffer Receive (in callback)
111 7. Buffer Empty (removing bytes from buffer)
112 not applicable for output
113 8. Buffer Free (returning to the buffer pool)
114 unprepare message to indicate that it is free
115 SetEvent on m->buffer_signal in case client is waiting
116 9. Buffer Finalization (returning to heap)
117 when device is closed, winmm_out_delete frees all sysex buffers
118
119Here's how stream output handles sysex:
120 1. Buffer Initialization (creating buffers)
121 same code as simple output (see above)
122 2. Buffer Allocation (finding a free buffer)
123 same code as simple output (see above)
124 3. Buffer Fill (putting bytes in the buffer)
125 essentially a state machine approach
126 m->dwBytesRecorded is a position in message
127 keep appending bytes until buffer is full (one byte to spare)
128 4. Buffer Preparation (midiOutPrepare, etc.)
129 done before sending message
130 dwBytesRecorded and dwBufferLength are set in winmm_end_sysex
131 5. Buffer Send (to Midi device)
132 uses midiStreamOutMsg()
133 6. Buffer Receive (in callback)
134 7. Buffer Empty (removing bytes from buffer)
135 not applicable for output
136 8. Buffer Free (returning to the buffer pool)
137 unprepare message to indicate that it is free
138 SetEvent on m->buffer_signal in case client is waiting
139 9. Buffer Finalization (returning to heap)
140 when device is closed, winmm_out_delete frees all sysex buffers
141
142
143Here's how input handles sysex:
144 1. Buffer Initialization (creating buffers)
145 two buffers are allocated in winmm_in_open
146 2. Buffer Allocation (finding a free buffer)
147 same code as simple output (see above)
148 3. Buffer Fill (putting bytes in the buffer)
149 not applicable for input
150 4. Buffer Preparation (midiOutPrepare, etc.)
151 done before sending message -- in winmm_in_open and in callback
152 5. Buffer Send (to Midi device)
153 uses midiInAddbuffer in allocate_sysex_input_buffer (called from
154 winmm_in_open) and callback
155 6. Buffer Receive (in callback)
156 7. Buffer Empty (removing bytes from buffer)
157 done without pause in loop in callback
158 8. Buffer Free (returning to the buffer pool)
159 done by midiInAddBuffer in callback, no pointer to buffers
160 is retained except by device
161 9. Buffer Finalization (returning to heap)
162 when device is closed, empty buffers are delivered to callback,
163 which frees them
164
165IMPORTANT: In addition to the above, PortMidi now has
166"shortcuts" to optimize the transfer of sysex data. To enable
167the optimization for sysex output, the system-dependent code
168sets fields in the pmInternal structure: fill_base, fill_offset_ptr,
169and fill_length. When fill_base is non-null, the system-independent
170part of PortMidi is allowed to directly copy sysex bytes to
171"fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches
172fill_length. See the code for details.
173
174
diff --git a/portmidi/pm_win/debugging_dlls.txt b/portmidi/pm_win/debugging_dlls.txt
new file mode 100755
index 0000000..82b81a5
--- /dev/null
+++ b/portmidi/pm_win/debugging_dlls.txt
@@ -0,0 +1,145 @@
1========================================================================================================================
2Methods for Debugging DLLs
3========================================================================================================================
4If you have the source for both the DLL and the calling program, open the project for the calling executable file and
5debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the
6Debug tab in the Project Settings dialog box.
7
8If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project
9Settings dialog box to specify the executable file that calls the DLL.
10
11You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you
12don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL
13file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After
14Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging.
15
16To debug a DLL using the project for the executable file
17
18From the Project menu, click Settings.
19The Project Settings dialog box appears.
20
21Choose the Debug tab.
22
23
24In the Category drop-down list box, select General.
25
26
27In the Program Arguments text box, type any command-line arguments required by the executable file.
28
29
30In the Category drop-down list box, select Additional DLLs.
31
32
33In the Local Name column, type the names of DLLs to debug.
34If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the
35remote module to map to the local module name.
36
37In the Preload column, select the check box if you want to load the module before debugging begins.
38
39
40Click OK to store the information in your project.
41
42
43From the Build menu, click Start Debug and Go to start the debugger.
44You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints
45in that file, even though it is not a part of the executable file’s project.
46
47To debug a DLL using the project for the DLL
48
49From the Project menu, click Settings.
50The Project Settings dialog box appears.
51
52Choose the Debug tab.
53
54
55In the Category drop-down list box, select General.
56
57
58In the Executable For Debug Session text box, type the name of the executable file that calls the DLL.
59
60
61In the Category list box, select Additional DLLs.
62
63
64In the Local Module Name column, type the name of the DLLs you want to debug.
65
66
67Click OK to store the information in your project.
68
69
70Set breakpoints as required in your DLL source files or on function symbols in the DLL.
71
72
73From the Build menu, click Start Debug and Go to start the debugger.
74To debug a DLL created with an external project
75
76From the Project menu, click Settings.
77The Project Settings dialog box appears.
78
79Choose the Debug tab.
80
81
82In the Category drop-down list box, select General.
83
84
85In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds.
86
87
88Click OK to store the information in your project.
89
90
91Build a debug version of the DLL with symbolic debugging information, if you don’t already have one.
92
93
94Follow one of the two procedures immediately preceding this one to debug the DLL.
95
96========================================================================================================================
97Why Don’t My DLL Breakpoints Work?
98========================================================================================================================
99Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each.
100If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics.
101Often breakpoint problems result from a combination of conditions.
102
103You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by
104the debugger.
105You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory
106by the debugger.
107Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep.
108
109When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep
110track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic
111information for all the code to be debugged and then walks through its breakpoint list, attempting to set the
112breakpoints.
113
114However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic
115information for the debugger to use when walking through its breakpoint list. Situations where this is likely to
116occur include:
117
118Attempts to set breakpoints in a DLL before the call to LoadLibrary.
119
120Setting a breakpoint in an ActiveX server before the container has started the server.
121
122Other similar cases.
123
124To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field
125in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for
126additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory
127will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical
128breakpoints. Make sure that these additional debugging processes are not already running when you start your
129debugging session. The debugging process and these additional processes must be sychronized at the same beginning
130point to work correctly, hitting all breakpoints.
131
132Breakpoints are missed when more than one copy of a DLL is on your hard disk.
133Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause
134debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the
135Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the
136DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to
137keep only one version of a DLL at a time in your path, current directory, and Windows directory.
138
139You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL.
140Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call
141to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet).
142Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at
143startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded
144into memory before this kind of breakpoint can be set. This means that the calling application's code must be
145executed to the point after its call to LoadLibrary before the debugger will allow this type of breakpoint to be set.
diff --git a/portmidi/pm_win/pmwin.c b/portmidi/pm_win/pmwin.c
new file mode 100755
index 0000000..5cb73b0
--- /dev/null
+++ b/portmidi/pm_win/pmwin.c
@@ -0,0 +1,98 @@
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 <windows.h>
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 */
29static void pm_exit(void) {
30 pm_term();
31}
32
33
34static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType)
35{
36 exit(1); /* invokes pm_exit() */
37 ExitProcess(1); /* probably never called */
38 return TRUE;
39}
40
41/* pm_init is the windows-dependent initialization.*/
42void pm_init(void)
43{
44 atexit(pm_exit);
45 SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
46#ifdef DEBUG
47 printf("registered pm_exit with atexit()\n");
48#endif
49 pm_winmm_init();
50 /* initialize other APIs (DirectX?) here */
51}
52
53
54void pm_term(void) {
55 pm_winmm_term();
56}
57
58
59static PmDeviceID pm_get_default_device_id(int is_input, char *key) {
60#define PATTERN_MAX 256
61 /* Find first input or device -- this is the default. */
62 PmDeviceID id = pmNoDevice;
63 int i;
64 Pm_Initialize(); /* make sure descriptors exist! */
65 for (i = 0; i < pm_descriptor_len; i++) {
66 if (pm_descriptors[i].pub.input == is_input) {
67 id = i;
68 break;
69 }
70 }
71 return id;
72}
73
74
75PmDeviceID Pm_GetDefaultInputDeviceID() {
76 return pm_get_default_device_id(TRUE,
77 "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");
78}
79
80
81PmDeviceID Pm_GetDefaultOutputDeviceID() {
82 return pm_get_default_device_id(FALSE,
83 "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");
84}
85
86
87#include "stdio.h"
88
89void *pm_alloc(size_t s) {
90 return malloc(s);
91}
92
93
94void pm_free(void *ptr) {
95 free(ptr);
96}
97
98
diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c
new file mode 100755
index 0000000..6f4b6f3
--- /dev/null
+++ b/portmidi/pm_win/pmwinmm.c
@@ -0,0 +1,1196 @@
1/* pmwinmm.c -- system specific definitions */
2
3#ifndef _WIN32_WINNT
4 /* without this define, InitializeCriticalSectionAndSpinCount is
5 * undefined. This version level means "Windows 2000 and higher"
6 */
7 #define _WIN32_WINNT 0x0500
8#endif
9
10#define UNICODE 1
11#include <wchar.h>
12#include "windows.h"
13#include "mmsystem.h"
14#include "portmidi.h"
15#include "pmutil.h"
16#include "pminternal.h"
17#include "pmwinmm.h"
18#include <string.h>
19#include "porttime.h"
20#ifndef UNICODE
21#error Expected UNICODE to be defined
22#endif
23
24
25/* asserts used to verify portMidi code logic is sound; later may want
26 something more graceful */
27#include <assert.h>
28#ifdef MMDEBUG
29/* this printf stuff really important for debugging client app w/host errors.
30 probably want to do something else besides read/write from/to console
31 for portability, however */
32#define STRING_MAX 80
33#include "stdio.h"
34#endif
35
36#define streql(x, y) (strcmp(x, y) == 0)
37
38#define MIDI_SYSEX 0xf0
39#define MIDI_EOX 0xf7
40
41/* callback routines */
42static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
43 UINT wMsg, DWORD_PTR dwInstance,
44 DWORD_PTR dwParam1, DWORD_PTR dwParam2);
45static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
46 DWORD_PTR dwInstance,
47 DWORD_PTR dwParam1,
48 DWORD_PTR dwParam2);
49
50extern pm_fns_node pm_winmm_in_dictionary;
51extern pm_fns_node pm_winmm_out_dictionary;
52
53static void winmm_out_delete(PmInternal *midi); /* forward reference */
54
55/*
56A note about buffers: WinMM seems to hold onto buffers longer than
57one would expect, e.g. when I tried using 2 small buffers to send
58long sysex messages, at some point WinMM held both buffers. This problem
59was fixed by making buffers bigger. Therefore, it seems that there should
60be enough buffer space to hold a whole sysex message.
61
62The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
63will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
64Call that the max_sysex_len = buffer_len * 4.
65
66For simple midi output (latency == 0), allocate 3 buffers, each with half
67the size of max_sysex_len, but each at least 256 bytes.
68
69For stream output, there will already be enough space in very short
70buffers, so use them, but make sure there are at least 16.
71
72For input, use many small buffers rather than 2 large ones so that when
73there are short sysex messages arriving frequently (as in control surfaces)
74there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
75but at least 16, of size 64 bytes each.
76
77The following constants help to represent these design parameters:
78*/
79#define NUM_SIMPLE_SYSEX_BUFFERS 3
80#define MIN_SIMPLE_SYSEX_LEN 256
81
82#define MIN_STREAM_BUFFERS 16
83#define STREAM_BUFFER_LEN 24
84
85#define INPUT_SYSEX_LEN 64
86#define MIN_INPUT_BUFFERS 16
87
88/* if we run out of space for output (assume this is due to a sysex msg,
89 expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
90 */
91#define NUM_EXPANSION_BUFFERS 128
92#define EXPANSION_BUFFER_LEN 1024
93
94/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
95#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
96/* A MIDIHDR with a sysex message is the buffer length plus the header size */
97#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
98
99/*
100==============================================================================
101win32 mmedia system specific structure passed to midi callbacks
102==============================================================================
103*/
104
105/* global winmm device info */
106MIDIINCAPS *midi_in_caps = NULL;
107MIDIINCAPS midi_in_mapper_caps;
108UINT midi_num_inputs = 0;
109MIDIOUTCAPS *midi_out_caps = NULL;
110MIDIOUTCAPS midi_out_mapper_caps;
111UINT midi_num_outputs = 0;
112
113/* per device info */
114typedef struct winmm_info_struct {
115 union {
116 HMIDISTRM stream; /* windows handle for stream */
117 HMIDIOUT out; /* windows handle for out calls */
118 HMIDIIN in; /* windows handle for in calls */
119 } handle;
120
121 /* midi output messages are sent in these buffers, which are allocated
122 * in a round-robin fashion, using next_buffer as an index
123 */
124 LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */
125 int max_buffers; /* length of buffers array */
126 int buffers_expanded; /* buffers array expanded for extra msgs? */
127 int num_buffers; /* how many buffers allocated in buffers array */
128 int next_buffer; /* index of next buffer to send */
129 HANDLE buffer_signal; /* used to wait for buffer to become free */
130 unsigned long last_time; /* last output time */
131 int first_message; /* flag: treat first message differently */
132 int sysex_mode; /* middle of sending sysex */
133 unsigned long sysex_word; /* accumulate data when receiving sysex */
134 unsigned int sysex_byte_count; /* count how many received */
135 LPMIDIHDR hdr; /* the message accumulating sysex to send */
136 unsigned long sync_time; /* when did we last determine delta? */
137 long delta; /* difference between stream time and
138 real time */
139 CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */
140} winmm_info_node, *winmm_info_type;
141
142
143/*
144=============================================================================
145general MIDI device queries
146=============================================================================
147*/
148
149/* add a device after converting device (product) name to UTF-8 */
150static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,
151 int is_virtual, void *descriptor, pm_fns_type dictionary)
152{
153 char utf8name[4 * MAXPNAMELEN];
154 WideCharToMultiByte(CP_UTF8, 0, device_name, -1,
155 utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);
156 /* ignore errors here -- if pm_descriptor_max is exceeded,
157 some devices will not be accessible. */
158 pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);
159}
160
161
162static void pm_winmm_general_inputs()
163{
164 UINT i;
165 WORD wRtn;
166 midi_num_inputs = midiInGetNumDevs();
167 midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) *
168 midi_num_inputs);
169 if (midi_in_caps == NULL) {
170 /* if you can't open a particular system-level midi interface
171 * (such as winmm), we just consider that system or API to be
172 * unavailable and move on without reporting an error.
173 */
174 return;
175 }
176
177 for (i = 0; i < midi_num_inputs; i++) {
178 wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
179 sizeof(MIDIINCAPS));
180 if (wRtn == MMSYSERR_NOERROR) {
181 pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,
182 (void *) (intptr_t) i, &pm_winmm_in_dictionary);
183 }
184 }
185}
186
187
188static void pm_winmm_mapper_input()
189{
190 WORD wRtn;
191 /* Note: if MIDIMAPPER opened as input (documentation implies you
192 can, but current system fails to retrieve input mapper
193 capabilities) then you still should retrieve some form of
194 setup info. */
195 wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
196 (LPMIDIINCAPS) & midi_in_mapper_caps,
197 sizeof(MIDIINCAPS));
198 if (wRtn == MMSYSERR_NOERROR) {
199 pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,
200 (void *) (intptr_t) MIDIMAPPER,
201 &pm_winmm_in_dictionary);
202 }
203}
204
205
206static void pm_winmm_general_outputs()
207{
208 UINT i;
209 DWORD wRtn;
210 midi_num_outputs = midiOutGetNumDevs();
211 midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs);
212
213 if (midi_out_caps == NULL) {
214 /* no error is reported -- see pm_winmm_general_inputs */
215 return ;
216 }
217
218 for (i = 0; i < midi_num_outputs; i++) {
219 wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],
220 sizeof(MIDIOUTCAPS));
221 if (wRtn == MMSYSERR_NOERROR) {
222 pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,
223 (void *) (intptr_t) i, &pm_winmm_out_dictionary);
224 }
225 }
226}
227
228
229static void pm_winmm_mapper_output()
230{
231 WORD wRtn;
232 /* Note: if MIDIMAPPER opened as output (pseudo MIDI device
233 maps device independent messages into device dependant ones,
234 via NT midimapper program) you still should get some setup info */
235 wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)
236 & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));
237 if (wRtn == MMSYSERR_NOERROR) {
238 pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,
239 (void *) (intptr_t) MIDIMAPPER,
240 &pm_winmm_out_dictionary);
241 }
242}
243
244
245/*
246============================================================================
247host error handling
248============================================================================
249*/
250
251static unsigned int winmm_check_host_error(PmInternal *midi)
252{
253 return FALSE;
254}
255
256
257/*
258=============================================================================
259buffer handling
260=============================================================================
261*/
262static MIDIHDR *allocate_buffer(long data_size)
263{
264 LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
265 MIDIEVENT *evt;
266 if (!hdr) return NULL;
267 evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
268 hdr->lpData = (LPSTR) evt;
269 hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
270 hdr->dwBytesRecorded = 0;
271 hdr->dwFlags = 0;
272 hdr->dwUser = hdr->dwBufferLength;
273 return hdr;
274}
275
276
277static PmError allocate_buffers(winmm_info_type info, long data_size,
278 long count)
279{
280 int i;
281 /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
282 info->num_buffers = 0; /* in case no memory can be allocated */
283 info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
284 if (!info->buffers) return pmInsufficientMemory;
285 info->max_buffers = count;
286 for (i = 0; i < count; i++) {
287 LPMIDIHDR hdr = allocate_buffer(data_size);
288 if (!hdr) { /* free everything allocated so far and return */
289 for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);
290 pm_free(info->buffers);
291 info->max_buffers = 0;
292 return pmInsufficientMemory;
293 }
294 info->buffers[i] = hdr; /* this may be NULL if allocation fails */
295 }
296 info->num_buffers = count;
297 return pmNoError;
298}
299
300
301static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
302{
303 LPMIDIHDR r = NULL;
304 winmm_info_type info = (winmm_info_type) midi->api_info;
305 while (TRUE) {
306 int i;
307 for (i = 0; i < info->num_buffers; i++) {
308 /* cycle through buffers, modulo info->num_buffers */
309 info->next_buffer++;
310 if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;
311 r = info->buffers[info->next_buffer];
312 if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
313 }
314 /* after scanning every buffer and not finding anything, block */
315 if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {
316#ifdef MMDEBUG
317 printf("PortMidi warning: get_free_output_buffer() "
318 "wait timed out after 1000ms\n");
319#endif
320 /* if we're trying to send a sysex message, maybe the
321 * message is too big and we need more message buffers.
322 * Expand the buffer pool by 128KB using 1024-byte buffers.
323 */
324 /* first, expand the buffers array if necessary */
325 if (!info->buffers_expanded) {
326 LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
327 (info->num_buffers + NUM_EXPANSION_BUFFERS) *
328 sizeof(LPMIDIHDR));
329 /* if no memory, we could return a no-memory error, but user
330 * probably will be unprepared to deal with it. Maybe the
331 * MIDI driver is temporarily hung so we should just wait.
332 * I don't know the right answer, but waiting is easier.
333 */
334 if (!new_buffers) continue;
335 /* copy buffers to new_buffers and replace buffers */
336 memcpy(new_buffers, info->buffers,
337 info->num_buffers * sizeof(LPMIDIHDR));
338 pm_free(info->buffers);
339 info->buffers = new_buffers;
340 info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;
341 info->buffers_expanded = TRUE;
342 }
343 /* next, add one buffer and return it */
344 if (info->num_buffers < info->max_buffers) {
345 r = allocate_buffer(EXPANSION_BUFFER_LEN);
346 /* again, if there's no memory, we may not really be
347 * dead -- maybe the system is temporarily hung and
348 * we can just wait longer for a message buffer */
349 if (!r) continue;
350 info->buffers[info->num_buffers++] = r;
351 goto found_buffer; /* break out of 2 loops */
352 }
353 /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
354 * and we have no free buffers to send. We'll just keep
355 * polling to see if any buffers show up.
356 */
357 }
358 }
359found_buffer:
360 r->dwBytesRecorded = 0;
361 /* actual buffer length is saved in dwUser field */
362 r->dwBufferLength = (DWORD) r->dwUser;
363 return r;
364}
365
366/*
367============================================================================
368begin midi input implementation
369============================================================================
370*/
371
372
373static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)
374{
375 LPMIDIHDR hdr = allocate_buffer(buffer_len);
376 if (!hdr) return pmInsufficientMemory;
377 /* note: pm_hosterror is normally a boolean, but here, we store Win
378 * error code. The caller must test the value for nonzero, set
379 * pm_hosterror_text, and then set pm_hosterror to TRUE */
380 pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
381 if (pm_hosterror) {
382 pm_free(hdr);
383 return pm_hosterror;
384 }
385 pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
386 return pm_hosterror;
387}
388
389
390static winmm_info_type winmm_info_create()
391{
392 winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));
393 info->handle.in = NULL;
394 info->handle.out = NULL;
395 info->buffers = NULL; /* not used for input */
396 info->num_buffers = 0; /* not used for input */
397 info->max_buffers = 0; /* not used for input */
398 info->buffers_expanded = FALSE; /* not used for input */
399 info->next_buffer = 0; /* not used for input */
400 info->buffer_signal = 0; /* not used for input */
401 info->last_time = 0;
402 info->first_message = TRUE; /* not used for input */
403 info->sysex_mode = FALSE;
404 info->sysex_word = 0;
405 info->sysex_byte_count = 0;
406 info->hdr = NULL; /* not used for input */
407 info->sync_time = 0;
408 info->delta = 0;
409 return info;
410}
411
412
413static void report_hosterror(LPWCH error_msg)
414{
415 WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,
416 sizeof(pm_hosterror_text), NULL, NULL);
417 if (pm_hosterror == MMSYSERR_NOMEM) {
418 /* add explanation to Window's confusing error message */
419 /* if there's room: */
420 if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {
421 strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
422 " Probably this MIDI device is open "
423 "in another application.");
424 }
425 }
426 pm_hosterror = TRUE;
427}
428
429
430static void report_hosterror_in()
431{
432 WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
433 int err = midiInGetErrorText(pm_hosterror, error_msg,
434 PM_HOST_ERROR_MSG_LEN);
435 assert(err == MMSYSERR_NOERROR);
436 report_hosterror(error_msg);
437}
438
439
440static void report_hosterror_out()
441{
442 WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
443 int err = midiOutGetErrorText(pm_hosterror, error_msg,
444 PM_HOST_ERROR_MSG_LEN);
445 assert(err == MMSYSERR_NOERROR);
446 report_hosterror(error_msg);
447}
448
449
450static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
451{
452 DWORD dwDevice;
453 int i = midi->device_id;
454 int max_sysex_len = midi->buffer_len * 4;
455 int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
456 winmm_info_type info;
457
458 dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
459
460 /* create system dependent device data */
461 info = winmm_info_create();
462 midi->api_info = info;
463 if (!info) goto no_memory;
464 /* 4000 is based on Windows documentation -- that's the value used
465 in the memory manager. It's small enough that it should not
466 hurt performance even if it's not optimal.
467 */
468 InitializeCriticalSectionAndSpinCount(&info->lock, 4000);
469 /* open device */
470 pm_hosterror = midiInOpen(
471 &(info->handle.in), /* input device handle */
472 dwDevice, /* device ID */
473 (DWORD_PTR) winmm_in_callback, /* callback address */
474 (DWORD_PTR) midi, /* callback instance data */
475 CALLBACK_FUNCTION); /* callback is a procedure */
476 if (pm_hosterror) goto free_descriptor;
477
478 if (num_input_buffers < MIN_INPUT_BUFFERS)
479 num_input_buffers = MIN_INPUT_BUFFERS;
480 for (i = 0; i < num_input_buffers; i++) {
481 if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {
482 /* either pm_hosterror was set, or the proper return code
483 is pmInsufficientMemory */
484 goto close_device;
485 }
486 }
487 /* start device */
488 pm_hosterror = midiInStart(info->handle.in);
489 if (!pm_hosterror) {
490 return pmNoError;
491 }
492
493 /* undo steps leading up to the detected error */
494
495 /* ignore return code (we already have an error to report) */
496 midiInReset(info->handle.in);
497close_device:
498 midiInClose(info->handle.in); /* ignore return code */
499free_descriptor:
500 midi->api_info = NULL;
501 pm_free(info);
502no_memory:
503 if (pm_hosterror) {
504 report_hosterror_in();
505 return pmHostError;
506 }
507 /* if !pm_hosterror, then the error must be pmInsufficientMemory */
508 return pmInsufficientMemory;
509 /* note: if we return an error code, the device will be
510 closed and memory will be freed. It's up to the caller
511 to free the parameter midi */
512}
513
514
515/* winmm_in_close -- close an open midi input device */
516/*
517 * assume midi is non-null (checked by caller)
518 */
519static PmError winmm_in_close(PmInternal *midi)
520{
521 winmm_info_type info = (winmm_info_type) midi->api_info;
522 if (!info) return pmBadPtr;
523 /* device to close */
524 if ((pm_hosterror = midiInStop(info->handle.in))) {
525 midiInReset(info->handle.in); /* try to reset and close port */
526 midiInClose(info->handle.in);
527 } else if ((pm_hosterror = midiInReset(info->handle.in))) {
528 midiInClose(info->handle.in); /* best effort to close midi port */
529 } else {
530 pm_hosterror = midiInClose(info->handle.in);
531 }
532 midi->api_info = NULL;
533 DeleteCriticalSection(&info->lock);
534 pm_free(info); /* delete */
535 if (pm_hosterror) {
536 report_hosterror_in();
537 return pmHostError;
538 }
539 return pmNoError;
540}
541
542
543/* Callback function executed via midiInput SW interrupt (via midiInOpen). */
544static void FAR PASCAL winmm_in_callback(
545 HMIDIIN hMidiIn, /* midiInput device Handle */
546 UINT wMsg, /* midi msg */
547 DWORD_PTR dwInstance, /* application data */
548 DWORD_PTR dwParam1, /* MIDI data */
549 DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */
550{
551 PmInternal *midi = (PmInternal *) dwInstance;
552 winmm_info_type info = (winmm_info_type) midi->api_info;
553
554 /* NOTE: we do not just EnterCriticalSection() here because an
555 * MIM_CLOSE message arrives when the port is closed, but then
556 * the info->lock has been destroyed.
557 */
558
559 switch (wMsg) {
560 case MIM_DATA: {
561 /* if this callback is reentered with data, we're in trouble.
562 * It's hard to imagine that Microsoft would allow callbacks
563 * to be reentrant -- isn't the model that this is like a
564 * hardware interrupt? -- but I've seen reentrant behavior
565 * using a debugger, so it happens.
566 */
567 EnterCriticalSection(&info->lock);
568
569 /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
570 message LOB;
571 dwParam2 is time message received by input device driver, specified
572 in [ms] from when midiInStart called.
573 each message is expanded to include the status byte */
574
575 if ((dwParam1 & 0x80) == 0) {
576 /* not a status byte -- ignore it. This happened running the
577 sysex.c test under Win2K with MidiMan USB 1x1 interface,
578 but I can't reproduce it. -RBD
579 */
580 /* printf("non-status byte found\n"); */
581 } else { /* data to process */
582 PmEvent event;
583 if (midi->time_proc)
584 dwParam2 = (*midi->time_proc)(midi->time_info);
585 event.timestamp = (PmTimestamp)dwParam2;
586 event.message = (PmMessage)dwParam1;
587 pm_read_short(midi, &event);
588 }
589 LeaveCriticalSection(&info->lock);
590 break;
591 }
592 case MIM_LONGDATA: {
593 MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
594 unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
595 unsigned int processed = 0;
596 int remaining = lpMidiHdr->dwBytesRecorded;
597
598 EnterCriticalSection(&info->lock);
599 /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n",
600 lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
601 if (midi->time_proc)
602 dwParam2 = (*midi->time_proc)(midi->time_info);
603 /* can there be more than one message in one buffer? */
604 /* assume yes and iterate through them */
605 pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);
606
607 /* when a device is closed, the pending MIM_LONGDATA buffers are
608 returned to this callback with dwBytesRecorded == 0. In this
609 case, we do not want to send them back to the interface (if
610 we do, the interface will not close, and Windows OS may hang). */
611 if (lpMidiHdr->dwBytesRecorded > 0) {
612 MMRESULT rslt;
613 lpMidiHdr->dwBytesRecorded = 0;
614 lpMidiHdr->dwFlags = 0;
615
616 /* note: no error checking -- can this actually fail? */
617 rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
618 assert(rslt == MMSYSERR_NOERROR);
619 /* note: I don't think this can fail except possibly for
620 * MMSYSERR_NOMEM, but the pain of reporting this
621 * unlikely but probably catastrophic error does not seem
622 * worth it.
623 */
624 rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
625 assert(rslt == MMSYSERR_NOERROR);
626 LeaveCriticalSection(&info->lock);
627 } else {
628 midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
629 LeaveCriticalSection(&info->lock);
630 pm_free(lpMidiHdr);
631 }
632 break;
633 }
634 case MIM_OPEN:
635 break;
636 case MIM_CLOSE:
637 break;
638 case MIM_ERROR:
639 /* printf("MIM_ERROR\n"); */
640 break;
641 case MIM_LONGERROR:
642 /* printf("MIM_LONGERROR\n"); */
643 break;
644 default:
645 break;
646 }
647}
648
649/*
650===========================================================================
651begin midi output implementation
652===========================================================================
653*/
654
655/* begin helper routines used by midiOutStream interface */
656
657/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */
658static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr,
659 unsigned long delta, unsigned long msg)
660{
661 unsigned long *ptr = (unsigned long *)
662 (hdr->lpData + hdr->dwBytesRecorded);
663 *ptr++ = delta; /* dwDeltaTime */
664 *ptr++ = 0; /* dwStream */
665 *ptr++ = msg; /* dwEvent */
666 hdr->dwBytesRecorded += 3 * sizeof(long);
667 /* if the addition of three more words (a message) would extend beyond
668 the buffer length, then return TRUE (full)
669 */
670 return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
671}
672
673
674static PmTimestamp pm_time_get(winmm_info_type info)
675{
676 MMTIME mmtime;
677 MMRESULT wRtn;
678 mmtime.wType = TIME_TICKS;
679 mmtime.u.ticks = 0;
680 wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));
681 assert(wRtn == MMSYSERR_NOERROR);
682 return mmtime.u.ticks;
683}
684
685
686/* end helper routines used by midiOutStream interface */
687
688
689static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
690{
691 DWORD dwDevice;
692 int i = midi->device_id;
693 winmm_info_type info;
694 MIDIPROPTEMPO propdata;
695 MIDIPROPTIMEDIV divdata;
696 int max_sysex_len = midi->buffer_len * 4;
697 int output_buffer_len;
698 int num_buffers;
699 dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
700
701 /* create system dependent device data */
702 info = winmm_info_create();
703 midi->api_info = info;
704 if (!info) goto no_memory;
705 /* create a signal */
706 info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
707 /* this should only fail when there are very serious problems */
708 assert(info->buffer_signal);
709 /* open device */
710 if (midi->latency == 0) {
711 /* use simple midi out calls */
712 pm_hosterror = midiOutOpen(
713 (LPHMIDIOUT) & info->handle.out, /* device Handle */
714 dwDevice, /* device ID */
715 /* note: same callback fn as for StreamOpen: */
716 (DWORD_PTR) winmm_streamout_callback, /* callback fn */
717 (DWORD_PTR) midi, /* callback instance data */
718 CALLBACK_FUNCTION); /* callback type */
719 } else {
720 /* use stream-based midi output (schedulable in future) */
721 pm_hosterror = midiStreamOpen(
722 &info->handle.stream, /* device Handle */
723 (LPUINT) & dwDevice, /* device ID pointer */
724 1, /* reserved, must be 1 */
725 (DWORD_PTR) winmm_streamout_callback,
726 (DWORD_PTR) midi, /* callback instance data */
727 CALLBACK_FUNCTION);
728 }
729 if (pm_hosterror != MMSYSERR_NOERROR) {
730 goto free_descriptor;
731 }
732
733 if (midi->latency == 0) {
734 num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
735 output_buffer_len = max_sysex_len / num_buffers;
736 if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
737 output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
738 } else {
739 num_buffers = max(midi->buffer_len, midi->latency / 2);
740 if (num_buffers < MIN_STREAM_BUFFERS)
741 num_buffers = MIN_STREAM_BUFFERS;
742 output_buffer_len = STREAM_BUFFER_LEN;
743
744 propdata.cbStruct = sizeof(MIDIPROPTEMPO);
745 propdata.dwTempo = 480000; /* microseconds per quarter */
746 pm_hosterror = midiStreamProperty(info->handle.stream,
747 (LPBYTE) & propdata,
748 MIDIPROP_SET | MIDIPROP_TEMPO);
749 if (pm_hosterror) goto close_device;
750
751 divdata.cbStruct = sizeof(MIDIPROPTEMPO);
752 divdata.dwTimeDiv = 480; /* divisions per quarter */
753 pm_hosterror = midiStreamProperty(info->handle.stream,
754 (LPBYTE) & divdata,
755 MIDIPROP_SET | MIDIPROP_TIMEDIV);
756 if (pm_hosterror) goto close_device;
757 }
758 /* allocate buffers */
759 if (allocate_buffers(info, output_buffer_len, num_buffers))
760 goto free_buffers;
761 /* start device */
762 if (midi->latency != 0) {
763 pm_hosterror = midiStreamRestart(info->handle.stream);
764 if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
765 }
766 return pmNoError;
767
768free_buffers:
769 /* buffers are freed below by winmm_out_delete */
770close_device:
771 midiOutClose(info->handle.out);
772free_descriptor:
773 midi->api_info = NULL;
774 winmm_out_delete(midi); /* frees buffers and m */
775no_memory:
776 if (pm_hosterror) {
777 report_hosterror_out();
778 return pmHostError;
779 }
780 return pmInsufficientMemory;
781}
782
783
784/* winmm_out_delete -- carefully free data associated with midi */
785/**/
786static void winmm_out_delete(PmInternal *midi)
787{
788 int i;
789 /* delete system dependent device data */
790 winmm_info_type info = (winmm_info_type) midi->api_info;
791 if (info) {
792 if (info->buffer_signal) {
793 /* don't report errors -- better not to stop cleanup */
794 CloseHandle(info->buffer_signal);
795 }
796 /* if using stream output, free buffers */
797 for (i = 0; i < info->num_buffers; i++) {
798 if (info->buffers[i]) pm_free(info->buffers[i]);
799 }
800 info->num_buffers = 0;
801 pm_free(info->buffers);
802 info->max_buffers = 0;
803 }
804 midi->api_info = NULL;
805 pm_free(info); /* delete */
806}
807
808
809/* see comments for winmm_in_close */
810static PmError winmm_out_close(PmInternal *midi)
811{
812 winmm_info_type info = (winmm_info_type) midi->api_info;
813 if (info->handle.out) {
814 /* device to close */
815 if (midi->latency == 0) {
816 pm_hosterror = midiOutClose(info->handle.out);
817 } else {
818 pm_hosterror = midiStreamClose(info->handle.stream);
819 }
820 /* regardless of outcome, free memory */
821 winmm_out_delete(midi);
822 }
823 if (pm_hosterror) {
824 report_hosterror_out();
825 return pmHostError;
826 }
827 return pmNoError;
828}
829
830
831static PmError winmm_out_abort(PmInternal *midi)
832{
833 winmm_info_type info = (winmm_info_type) midi->api_info;
834
835 /* only stop output streams */
836 if (midi->latency > 0) {
837 pm_hosterror = midiStreamStop(info->handle.stream);
838 if (pm_hosterror) {
839 report_hosterror_out();
840 return pmHostError;
841 }
842 }
843 return pmNoError;
844}
845
846
847static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
848{
849 winmm_info_type info = (winmm_info_type) midi->api_info;
850 assert(info);
851 if (info->hdr) {
852 pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr,
853 sizeof(MIDIHDR));
854 if (pm_hosterror) {
855 /* do not send message */
856 } else if (midi->latency == 0) {
857 /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
858 * should be zero. This is set in get_free_sysex_buffer().
859 * The msg length goes in dwBufferLength in spite of what
860 * Microsoft documentation says (or doesn't say). */
861 info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;
862 info->hdr->dwBytesRecorded = 0;
863 pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr,
864 sizeof(MIDIHDR));
865 } else {
866 pm_hosterror = midiStreamOut(info->handle.stream, info->hdr,
867 sizeof(MIDIHDR));
868 }
869 midi->fill_base = NULL;
870 info->hdr = NULL;
871 if (pm_hosterror) {
872 report_hosterror_out();
873 return pmHostError;
874 }
875 }
876 return pmNoError;
877}
878
879
880static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
881{
882 winmm_info_type info = (winmm_info_type) midi->api_info;
883 PmError rslt = pmNoError;
884 assert(info);
885
886 if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
887 pm_hosterror = midiOutShortMsg(info->handle.out, event->message);
888 if (pm_hosterror) {
889 if (info->hdr) { /* device disconnect may delete hdr */
890 info->hdr->dwFlags = 0; /* release the buffer */
891 }
892 report_hosterror_out();
893 return pmHostError;
894 }
895 } else { /* use midiStream interface -- pass data through buffers */
896 unsigned long when = event->timestamp;
897 unsigned long delta;
898 int full;
899 if (when == 0) when = midi->now;
900 /* when is in real_time; translate to intended stream time */
901 when = when + info->delta + midi->latency;
902 /* make sure we don't go backward in time */
903 if (when < info->last_time) when = info->last_time;
904 delta = when - info->last_time;
905 info->last_time = when;
906 /* before we insert any data, we must have a buffer */
907 if (info->hdr == NULL) {
908 /* stream interface: buffers allocated when stream is opened */
909 info->hdr = get_free_output_buffer(midi);
910 }
911 full = add_to_buffer(info, info->hdr, delta, event->message);
912 /* note: winmm_write_flush sets pm_hosterror etc. on host error */
913 if (full) rslt = winmm_write_flush(midi, when);
914 }
915 return rslt;
916}
917
918#define winmm_begin_sysex winmm_write_flush
919#ifndef winmm_begin_sysex
920static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
921{
922 winmm_info_type m = (winmm_info_type) midi->api_info;
923 PmError rslt = pmNoError;
924
925 if (midi->latency == 0) {
926 /* do nothing -- it's handled in winmm_write_byte */
927 } else {
928 /* sysex expects an empty sysex buffer, so send whatever is here */
929 rslt = winmm_write_flush(midi);
930 }
931 return rslt;
932}
933#endif
934
935static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
936{
937 /* could check for callback_error here, but I haven't checked
938 * what happens if we exit early and don't finish the sysex msg
939 * and clean up
940 */
941 winmm_info_type info = (winmm_info_type) midi->api_info;
942 PmError rslt = pmNoError;
943 LPMIDIHDR hdr = info->hdr;
944 if (!hdr) return rslt; /* something bad happened earlier,
945 do not report an error because it would have been
946 reported (at least) once already */
947 /* a(n old) version of MIDI YOKE requires a zero byte after
948 * the sysex message, but do not increment dwBytesRecorded: */
949 hdr->lpData[hdr->dwBytesRecorded] = 0;
950 if (midi->latency == 0) {
951#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
952 /* DEBUG CODE: */
953 { int i; int len = info->hdr->dwBufferLength;
954 printf("OutLongMsg %d ", len);
955 for (i = 0; i < len; i++) {
956 printf("%2x ", (unsigned char) (info->hdr->lpData[i]));
957 }
958 }
959#endif
960 } else {
961 /* Using stream interface. There are accumulated bytes in info->hdr
962 to send using midiStreamOut
963 */
964 /* add bytes recorded to MIDIEVENT length, but don't
965 count the MIDIEVENT data (3 longs) */
966 MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
967 evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
968 /* round up BytesRecorded to multiple of 4 */
969 hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
970 }
971 rslt = winmm_write_flush(midi, timestamp);
972 return rslt;
973}
974
975
976static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
977 PmTimestamp timestamp)
978{
979 /* write a sysex byte */
980 PmError rslt = pmNoError;
981 winmm_info_type info = (winmm_info_type) midi->api_info;
982 LPMIDIHDR hdr = info->hdr;
983 unsigned char *msg_buffer;
984 assert(info);
985 if (!hdr) {
986 info->hdr = hdr = get_free_output_buffer(midi);
987 assert(hdr);
988 midi->fill_base = (unsigned char *) info->hdr->lpData;
989 midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);
990 /* when buffer fills, Pm_WriteSysEx will revert to calling
991 * pmwin_write_byte, which expect to have space, so leave
992 * one byte free for pmwin_write_byte. Leave another byte
993 * of space for zero after message to make early version of
994 * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
995 midi->fill_length = hdr->dwBufferLength - 2;
996 if (midi->latency != 0) {
997 unsigned long when = (unsigned long) timestamp;
998 unsigned long delta;
999 unsigned long *ptr;
1000 if (when == 0) when = midi->now;
1001 /* when is in real_time; translate to intended stream time */
1002 when = when + info->delta + midi->latency;
1003 /* make sure we don't go backward in time */
1004 if (when < info->last_time) when = info->last_time;
1005 delta = when - info->last_time;
1006 info->last_time = when;
1007
1008 ptr = (unsigned long *) hdr->lpData;
1009 *ptr++ = delta;
1010 *ptr++ = 0;
1011 *ptr = MEVT_F_LONG;
1012 hdr->dwBytesRecorded = 3 * sizeof(long);
1013 /* data will be added at an offset of dwBytesRecorded ... */
1014 }
1015 }
1016 /* add the data byte */
1017 msg_buffer = (unsigned char *) (hdr->lpData);
1018 msg_buffer[hdr->dwBytesRecorded++] = byte;
1019
1020 /* see if buffer is full, leave one byte extra for pad */
1021 if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
1022 /* write what we've got and continue */
1023 rslt = winmm_end_sysex(midi, timestamp);
1024 }
1025 return rslt;
1026}
1027
1028
1029static PmTimestamp winmm_synchronize(PmInternal *midi)
1030{
1031 winmm_info_type info;
1032 unsigned long pm_stream_time_2;
1033 unsigned long real_time;
1034 unsigned long pm_stream_time;
1035
1036 /* only synchronize if we are using stream interface */
1037 if (midi->latency == 0) return 0;
1038
1039 /* figure out the time */
1040 info = (winmm_info_type) midi->api_info;
1041 pm_stream_time_2 = pm_time_get(info);
1042
1043 do {
1044 /* read real_time between two reads of stream time */
1045 pm_stream_time = pm_stream_time_2;
1046 real_time = (*midi->time_proc)(midi->time_info);
1047 pm_stream_time_2 = pm_time_get(info);
1048 /* repeat if more than 1ms elapsed */
1049 } while (pm_stream_time_2 > pm_stream_time + 1);
1050 info->delta = pm_stream_time - real_time;
1051 info->sync_time = real_time;
1052 return real_time;
1053}
1054
1055
1056/* winmm_streamout_callback -- unprepare (free) buffer header */
1057static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
1058 DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1059{
1060 PmInternal *midi = (PmInternal *) dwInstance;
1061 winmm_info_type info = (winmm_info_type) midi->api_info;
1062 LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
1063 int err;
1064
1065 /* Even if an error is pending, I think we should unprepare msgs and
1066 signal their arrival
1067 */
1068 /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n",
1069 hdr, wMsg, MOM_DONE); */
1070 if (wMsg == MOM_DONE) {
1071 MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr,
1072 sizeof(MIDIHDR));
1073 assert(ret == MMSYSERR_NOERROR);
1074 } else if (wMsg == MOM_CLOSE) {
1075 /* The streaming API gets a callback when the device is closed.
1076 * The non-streaming API gets a callback when the device is
1077 * removed or closed. It is misleading to set is_removed when
1078 * the device is closed normally, but in that case, midi itself
1079 * will be freed immediately, so there should be no way to
1080 * observe is_removed == TRUE. On the other hand, if the device
1081 * is removed, setting is_removed will cause PortMidi to return
1082 * the pmDeviceRemoved error on attempts to output to the device.
1083 * In the case of normal closing, due to midiOutClose(),
1084 * the call below is reentrant (!), but for some reason this does
1085 * not cause an error or infinite recursion, so we are not taking
1086 * any precautions to flag midi as "in the process of closing."
1087 */
1088 midi->is_removed = TRUE;
1089 midiOutClose(info->handle.out);
1090 }
1091 /* signal client in case it is blocked waiting for buffer */
1092 err = SetEvent(info->buffer_signal);
1093 assert(err); /* false -> error */
1094}
1095
1096
1097/*
1098===========================================================================
1099begin exported functions
1100===========================================================================
1101*/
1102
1103#define winmm_in_abort pm_fail_fn
1104pm_fns_node pm_winmm_in_dictionary = {
1105 none_write_short,
1106 none_sysex,
1107 none_sysex,
1108 none_write_byte,
1109 none_write_short,
1110 none_write_flush,
1111 winmm_synchronize,
1112 winmm_in_open,
1113 winmm_in_abort,
1114 winmm_in_close,
1115 success_poll,
1116 winmm_check_host_error
1117 };
1118
1119pm_fns_node pm_winmm_out_dictionary = {
1120 winmm_write_short,
1121 winmm_begin_sysex,
1122 winmm_end_sysex,
1123 winmm_write_byte,
1124 /* short realtime message: */ winmm_write_short,
1125 winmm_write_flush,
1126 winmm_synchronize,
1127 winmm_out_open,
1128 winmm_out_abort,
1129 winmm_out_close,
1130 none_poll,
1131 winmm_check_host_error
1132 };
1133
1134
1135/* initialize winmm interface. Note that if there is something wrong
1136 with winmm (e.g. it is not supported or installed), it is not an
1137 error. We should simply return without having added any devices to
1138 the table. Hence, no error code is returned. Furthermore, this init
1139 code is called along with every other supported interface, so the
1140 user would have a very hard time figuring out what hardware and API
1141 generated the error. Finally, it would add complexity to pmwin.c to
1142 remember where the error code came from in order to convert to text.
1143 */
1144void pm_winmm_init( void )
1145{
1146 pm_winmm_mapper_input();
1147 pm_winmm_mapper_output();
1148 pm_winmm_general_inputs();
1149 pm_winmm_general_outputs();
1150}
1151
1152
1153/* no error codes are returned, even if errors are encountered, because
1154 there is probably nothing the user could do (e.g. it would be an error
1155 to retry.
1156 */
1157void pm_winmm_term( void )
1158{
1159 int i;
1160#ifdef MMDEBUG
1161 int doneAny = 0;
1162 printf("pm_winmm_term called\n");
1163#endif
1164 for (i = 0; i < pm_descriptor_len; i++) {
1165 PmInternal *midi = pm_descriptors[i].pm_internal;
1166 if (midi) {
1167 winmm_info_type info = (winmm_info_type) midi->api_info;
1168 if (info->handle.out) {
1169 /* close next open device*/
1170#ifdef MMDEBUG
1171 if (doneAny == 0) {
1172 printf("begin closing open devices...\n");
1173 doneAny = 1;
1174 }
1175#endif
1176 /* close all open ports */
1177 (*midi->dictionary->close)(midi);
1178 }
1179 }
1180 }
1181 if (midi_in_caps) {
1182 pm_free(midi_in_caps);
1183 midi_in_caps = NULL;
1184 }
1185 if (midi_out_caps) {
1186 pm_free(midi_out_caps);
1187 midi_out_caps = NULL;
1188 }
1189#ifdef MMDEBUG
1190 if (doneAny) {
1191 printf("warning: devices were left open. They have been closed.\n");
1192 }
1193 printf("pm_winmm_term exiting\n");
1194#endif
1195 pm_descriptor_len = 0;
1196}
diff --git a/portmidi/pm_win/pmwinmm.h b/portmidi/pm_win/pmwinmm.h
new file mode 100755
index 0000000..4a353e2
--- /dev/null
+++ b/portmidi/pm_win/pmwinmm.h
@@ -0,0 +1,5 @@
1/* pmwinmm.h -- system-specific definitions for windows multimedia API */
2
3void pm_winmm_init( void );
4void pm_winmm_term( void );
5
diff --git a/portmidi/pm_win/static.cmake b/portmidi/pm_win/static.cmake
new file mode 100644
index 0000000..7fdad18
--- /dev/null
+++ b/portmidi/pm_win/static.cmake
@@ -0,0 +1,24 @@
1# static.cmake -- change flags to link with static runtime libraries
2#
3# Even when BUILD_SHARED_LIBS is OFF, CMake specifies linking wtih
4# multithread DLL, so you give inconsistent linking instructions
5# resulting in warning messages from MS Visual Studio. If you want
6# a static binary, I've found this approach works to eliminate
7# warnings and make everything static:
8#
9# Changes /MD (multithread DLL) to /MT (multithread static)
10
11if(MSVC)
12 foreach(flag_var
13 CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
14 CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO
15 CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
16 CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO)
17 if(${flag_var} MATCHES "/MD")
18 string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
19 endif(${flag_var} MATCHES "/MD")
20 endforeach(flag_var)
21
22 message(STATUS
23 "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library")
24endif(MSVC)
diff --git a/portmidi/portmusic_logo.png b/portmidi/portmusic_logo.png
new file mode 100644
index 0000000..17a063a
--- /dev/null
+++ b/portmidi/portmusic_logo.png
Binary files differ
diff --git a/portmidi/porttime/porttime.c b/portmidi/porttime/porttime.c
new file mode 100755
index 0000000..71b06f4
--- /dev/null
+++ b/portmidi/porttime/porttime.c
@@ -0,0 +1,3 @@
1/* porttime.c -- portable API for millisecond timer */
2
3/* There is no machine-independent implementation code to put here */
diff --git a/portmidi/porttime/porttime.h b/portmidi/porttime/porttime.h
new file mode 100755
index 0000000..0a61c5c
--- /dev/null
+++ b/portmidi/porttime/porttime.h
@@ -0,0 +1,103 @@
1/** @file porttime.h portable interface to millisecond timer. */
2
3/* CHANGE LOG FOR PORTTIME
4 10-Jun-03 Mark Nelson & RBD
5 boost priority of timer thread in ptlinux.c implementation
6 */
7
8#ifndef PORTMIDI_PORTTIME_H
9#define PORTMIDI_PORTTIME_H
10
11/* Should there be a way to choose the source of time here? */
12
13#ifdef WIN32
14#ifndef INT32_DEFINED
15// rather than having users install a special .h file for windows,
16// just put the required definitions inline here. portmidi.h uses
17// these too, so the definitions are (unfortunately) duplicated there
18typedef int int32_t;
19typedef unsigned int uint32_t;
20#define INT32_DEFINED
21#endif
22#else
23#include <stdint.h> // needed for int32_t
24#endif
25
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30#ifndef PMEXPORT
31#ifdef _WINDLL
32#define PMEXPORT __declspec(dllexport)
33#else
34#define PMEXPORT
35#endif
36#endif
37
38/** @defgroup grp_porttime PortTime: Millisecond Timer
39 @{
40*/
41
42typedef enum {
43 ptNoError = 0, /* success */
44 ptHostError = -10000, /* a system-specific error occurred */
45 ptAlreadyStarted, /* cannot start timer because it is already started */
46 ptAlreadyStopped, /* cannot stop timer because it is already stopped */
47 ptInsufficientMemory /* memory could not be allocated */
48} PtError; /**< @brief @enum PtError PortTime error code; a common return type.
49 * No error is indicated by zero; errors are indicated by < 0.
50 */
51
52/** real time or time offset in milliseconds. */
53typedef int32_t PtTimestamp;
54
55/** a function that gets a current time */
56typedef void (PtCallback)(PtTimestamp timestamp, void *userData);
57
58/** start a real-time clock service.
59
60 @param resolution the timer resolution in ms. The time will advance every
61 \p resolution ms.
62
63 @param callback a function pointer to be called every resolution ms.
64
65 @param userData is passed to \p callback as a parameter.
66
67 @return #ptNoError on success. See #PtError for other values.
68*/
69PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
70
71/** stop the timer.
72
73 @return #ptNoError on success. See #PtError for other values.
74*/
75PMEXPORT PtError Pt_Stop(void);
76
77/** test if the timer is running.
78
79 @return TRUE or FALSE
80*/
81PMEXPORT int Pt_Started(void);
82
83/** get the current time in ms.
84
85 @return the current time
86*/
87PMEXPORT PtTimestamp Pt_Time(void);
88
89/** pauses the current thread, allowing other threads to run.
90
91 @param duration the length of the pause in ms. The true duration
92 of the pause may be rounded to the nearest or next clock tick
93 as determined by resolution in #Pt_Start().
94*/
95PMEXPORT void Pt_Sleep(int32_t duration);
96
97/** @} */
98
99#ifdef __cplusplus
100}
101#endif
102
103#endif // PORTMIDI_PORTTIME_H
diff --git a/portmidi/porttime/pthaiku.cpp b/portmidi/porttime/pthaiku.cpp
new file mode 100644
index 0000000..9d8de14
--- /dev/null
+++ b/portmidi/porttime/pthaiku.cpp
@@ -0,0 +1,88 @@
1// pthaiku.cpp - portable timer implementation for Haiku
2
3#include "porttime.h"
4#include <Looper.h>
5#include <MessageRunner.h>
6#include <OS.h>
7
8namespace {
9 const uint32 timerMessage = 'PTTM';
10
11 struct TimerLooper : BLooper {
12 TimerLooper() : BLooper() {
13 }
14
15
16 virtual void MessageReceived(BMessage *message)
17 {
18 PtCallback *callback;
19 void *userData;
20 if (message->what == timerMessage && message->FindPointer("callback", (void**)&callback) == B_OK && message->FindPointer("userData", &userData) == B_OK) {
21 (*callback)(Pt_Time(), userData);
22 }
23 BLooper::MessageReceived(message);
24 }
25 };
26
27 bool time_started_flag = false;
28 bigtime_t time_offset;
29 TimerLooper *timerLooper;
30 BMessageRunner *timerRunner;
31}
32
33extern "C" {
34 PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
35 {
36 if (time_started_flag) return ptAlreadyStarted;
37 time_offset = system_time();
38 if (callback) {
39 timerLooper = new TimerLooper;
40 timerLooper->Run();
41 BMessenger target(timerLooper);
42 BMessage message(timerMessage);
43 message.AddPointer("callback", (void*)callback);
44 message.AddPointer("userData", userData);
45 bigtime_t interval = resolution * 1000;
46 timerRunner = new BMessageRunner(target, &message, interval);
47 if(timerRunner->InitCheck() != B_OK) {
48 delete timerRunner;
49 timerRunner = NULL;
50 timerLooper->PostMessage(B_QUIT_REQUESTED);
51 timerLooper = NULL;
52 return ptHostError;
53 }
54 }
55 time_started_flag = true;
56 return ptNoError;
57 }
58
59
60 PtError Pt_Stop()
61 {
62 if (!time_started_flag) return ptAlreadyStopped;
63 time_started_flag = false;
64 delete timerRunner;
65 timerRunner = NULL;
66 timerLooper->PostMessage(B_QUIT_REQUESTED);
67 timerLooper = NULL;
68 return ptNoError;
69 }
70
71
72 int Pt_Started()
73 {
74 return time_started_flag;
75 }
76
77
78 PtTimestamp Pt_Time()
79 {
80 return (system_time() - time_offset) / 1000;
81 }
82
83
84 void Pt_Sleep(int32_t duration)
85 {
86 snooze(duration * 1000);
87 }
88}
diff --git a/portmidi/porttime/ptlinux.c b/portmidi/porttime/ptlinux.c
new file mode 100755
index 0000000..c4af5c1
--- /dev/null
+++ b/portmidi/porttime/ptlinux.c
@@ -0,0 +1,139 @@
1/* ptlinux.c -- portable timer implementation for linux */
2
3
4/* IMPLEMENTATION NOTES (by Mark Nelson):
5
6Unlike Windows, Linux has no system call to request a periodic callback,
7so if Pt_Start() receives a callback parameter, it must create a thread
8that wakes up periodically and calls the provided callback function.
9If running as superuser, use setpriority() to renice thread to -20.
10One could also set the timer thread to a real-time priority (SCHED_FIFO
11and SCHED_RR), but this is dangerous for This is necessary because
12if the callback hangs it'll never return. A more serious reason
13is that the current scheduler implementation busy-waits instead
14of sleeping when realtime threads request a sleep of <=2ms (as a way
15to get around the 10ms granularity), which means the thread would never
16let anyone else on the CPU.
17
18CHANGE LOG
19
2018-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 <stdlib.h>
28#include <stdio.h>
29#include <unistd.h>
30#include <sys/types.h>
31#include "porttime.h"
32#include "time.h"
33#include "sys/resource.h"
34#include "pthread.h"
35
36#define TRUE 1
37#define FALSE 0
38
39#ifndef CLOCK_MONOTONIC_RAW
40#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
41#endif
42
43static int time_started_flag = FALSE;
44static struct timespec time_offset = {0, 0};
45static pthread_t pt_thread_pid;
46static int pt_thread_created = FALSE;
47
48/* note that this is static data -- we only need one copy */
49typedef struct {
50 int id;
51 int resolution;
52 PtCallback *callback;
53 void *userData;
54} pt_callback_parameters;
55
56static int pt_callback_proc_id = 0;
57
58static void *Pt_CallbackProc(void *p)
59{
60 pt_callback_parameters *parameters = (pt_callback_parameters *) p;
61 int mytime = 1;
62 /* to kill a process, just increment the pt_callback_proc_id */
63 /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
64 parameters->id); */
65 if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
66 while (pt_callback_proc_id == parameters->id) {
67 /* wait for a multiple of resolution ms */
68 struct timeval timeout;
69 int delay = mytime++ * parameters->resolution - Pt_Time();
70 if (delay < 0) delay = 0;
71 timeout.tv_sec = 0;
72 timeout.tv_usec = delay * 1000;
73 select(0, NULL, NULL, NULL, &timeout);
74 (*(parameters->callback))(Pt_Time(), parameters->userData);
75 }
76 /* printf("Pt_CallbackProc exiting\n"); */
77 // free(parameters);
78 return NULL;
79}
80
81
82PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
83{
84 if (time_started_flag) return ptNoError;
85 /* need this set before process runs: */
86 clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset);
87 if (callback) {
88 int res;
89 pt_callback_parameters *parms = (pt_callback_parameters *)
90 malloc(sizeof(pt_callback_parameters));
91 if (!parms) return ptInsufficientMemory;
92 parms->id = pt_callback_proc_id;
93 parms->resolution = resolution;
94 parms->callback = callback;
95 parms->userData = userData;
96 res = pthread_create(&pt_thread_pid, NULL,
97 Pt_CallbackProc, parms);
98 if (res != 0) return ptHostError;
99 pt_thread_created = TRUE;
100 }
101 time_started_flag = TRUE;
102 return ptNoError;
103}
104
105
106PtError Pt_Stop()
107{
108 /* printf("Pt_Stop called\n"); */
109 pt_callback_proc_id++;
110 if (pt_thread_created) {
111 pthread_join(pt_thread_pid, NULL);
112 pt_thread_created = FALSE;
113 }
114 time_started_flag = FALSE;
115 return ptNoError;
116}
117
118
119int Pt_Started()
120{
121 return time_started_flag;
122}
123
124
125PtTimestamp Pt_Time()
126{
127 long seconds, ms;
128 struct timespec now;
129 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
130 seconds = now.tv_sec - time_offset.tv_sec;
131 ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */
132 return seconds * 1000 + ms;
133}
134
135
136void Pt_Sleep(int32_t duration)
137{
138 usleep(duration * 1000);
139}
diff --git a/portmidi/porttime/ptmacosx_cf.c b/portmidi/porttime/ptmacosx_cf.c
new file mode 100755
index 0000000..a174c86
--- /dev/null
+++ b/portmidi/porttime/ptmacosx_cf.c
@@ -0,0 +1,140 @@
1/* ptmacosx.c -- portable timer implementation for mac os x */
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <pthread.h>
6#include <CoreFoundation/CoreFoundation.h>
7
8#import <mach/mach.h>
9#import <mach/mach_error.h>
10#import <mach/mach_time.h>
11#import <mach/clock.h>
12
13#include "porttime.h"
14
15#define THREAD_IMPORTANCE 30
16#define LONG_TIME 1000000000.0
17
18static int time_started_flag = FALSE;
19static CFAbsoluteTime startTime = 0.0;
20static CFRunLoopRef timerRunLoop;
21
22typedef struct {
23 int resolution;
24 PtCallback *callback;
25 void *userData;
26} PtThreadParams;
27
28
29void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)
30{
31 PtThreadParams *params = (PtThreadParams*)info;
32 (*params->callback)(Pt_Time(), params->userData);
33}
34
35static 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
89PtError 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
114PtError Pt_Stop()
115{
116 printf("Pt_Stop called\n");
117
118 CFRunLoopStop(timerRunLoop);
119 time_started_flag = FALSE;
120 return ptNoError;
121}
122
123
124int Pt_Started()
125{
126 return time_started_flag;
127}
128
129
130PtTimestamp Pt_Time()
131{
132 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
133 return (PtTimestamp) ((now - startTime) * 1000.0);
134}
135
136
137void Pt_Sleep(int32_t duration)
138{
139 usleep(duration * 1000);
140}
diff --git a/portmidi/porttime/ptmacosx_mach.c b/portmidi/porttime/ptmacosx_mach.c
new file mode 100755
index 0000000..1390af8
--- /dev/null
+++ b/portmidi/porttime/ptmacosx_mach.c
@@ -0,0 +1,204 @@
1/* ptmacosx.c -- portable timer implementation for mac os x */
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <CoreAudio/HostTime.h>
6
7#import <mach/mach.h>
8#import <mach/mach_error.h>
9#import <mach/mach_time.h>
10#import <mach/clock.h>
11#include <unistd.h>
12#include <AvailabilityMacros.h>
13
14#include "porttime.h"
15#include "sys/time.h"
16#include "pthread.h"
17
18#ifndef NSEC_PER_MSEC
19#define NSEC_PER_MSEC 1000000
20#endif
21#define THREAD_IMPORTANCE 63
22
23// QOS headers are available as of macOS 10.10
24#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
25#include "sys/qos.h"
26#define HAVE_APPLE_QOS 1
27#else
28#undef HAVE_APPLE_QOS
29#endif
30
31static int time_started_flag = FALSE;
32static UInt64 start_time;
33static pthread_t pt_thread_pid;
34
35/* note that this is static data -- we only need one copy */
36typedef struct {
37 int id;
38 int resolution;
39 PtCallback *callback;
40 void *userData;
41} pt_callback_parameters;
42
43static int pt_callback_proc_id = 0;
44
45static void *Pt_CallbackProc(void *p)
46{
47 pt_callback_parameters *parameters = (pt_callback_parameters *) p;
48 int mytime = 1;
49
50 kern_return_t error;
51 thread_extended_policy_data_t extendedPolicy;
52 thread_precedence_policy_data_t precedencePolicy;
53
54 extendedPolicy.timeshare = 0;
55 error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
56 (thread_policy_t)&extendedPolicy,
57 THREAD_EXTENDED_POLICY_COUNT);
58 if (error != KERN_SUCCESS) {
59 mach_error("Couldn't set thread timeshare policy", error);
60 }
61
62 precedencePolicy.importance = THREAD_IMPORTANCE;
63 error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
64 (thread_policy_t)&precedencePolicy,
65 THREAD_PRECEDENCE_POLICY_COUNT);
66 if (error != KERN_SUCCESS) {
67 mach_error("Couldn't set thread precedence policy", error);
68 }
69
70 // Most important, set real-time constraints.
71
72 // Define the guaranteed and max fraction of time for the audio thread.
73 // These "duty cycle" values can range from 0 to 1. A value of 0.5
74 // means the scheduler would give half the time to the thread.
75 // These values have empirically been found to yield good behavior.
76 // Good means that audio performance is high and other threads won't starve.
77 const double kGuaranteedAudioDutyCycle = 0.75;
78 const double kMaxAudioDutyCycle = 0.85;
79
80 // Define constants determining how much time the audio thread can
81 // use in a given time quantum. All times are in milliseconds.
82
83 // About 128 frames @44.1KHz
84 const double kTimeQuantum = 2.9;
85
86 // Time guaranteed each quantum.
87 const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
88
89 // Maximum time each quantum.
90 const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
91
92 // Get the conversion factor from milliseconds to absolute time
93 // which is what the time-constraints call needs.
94 mach_timebase_info_data_t tb_info;
95 mach_timebase_info(&tb_info);
96 double ms_to_abs_time =
97 ((double)tb_info.denom / (double)tb_info.numer) * 1000000;
98
99 thread_time_constraint_policy_data_t time_constraints;
100 time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time);
101 time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time);
102 time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time);
103 time_constraints.preemptible = 0;
104
105 error = thread_policy_set(mach_thread_self(),
106 THREAD_TIME_CONSTRAINT_POLICY,
107 (thread_policy_t)&time_constraints,
108 THREAD_TIME_CONSTRAINT_POLICY_COUNT);
109 if (error != KERN_SUCCESS) {
110 mach_error("Couldn't set thread precedence policy", error);
111 }
112
113 /* to kill a process, just increment the pt_callback_proc_id */
114 /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
115 parameters->id); */
116 while (pt_callback_proc_id == parameters->id) {
117 /* wait for a multiple of resolution ms */
118 UInt64 wait_time;
119 int delay = mytime++ * parameters->resolution - Pt_Time();
120 PtTimestamp timestamp;
121 if (delay < 0) delay = 0;
122 wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
123 wait_time += AudioGetCurrentHostTime();
124 mach_wait_until(wait_time);
125 timestamp = Pt_Time();
126 (*(parameters->callback))(timestamp, parameters->userData);
127 }
128 free(parameters);
129 return NULL;
130}
131
132
133PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
134{
135 if (time_started_flag) return ptAlreadyStarted;
136 start_time = AudioGetCurrentHostTime();
137
138 if (callback) {
139 int res;
140 pt_callback_parameters *parms;
141
142 parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
143 if (!parms) return ptInsufficientMemory;
144 parms->id = pt_callback_proc_id;
145 parms->resolution = resolution;
146 parms->callback = callback;
147 parms->userData = userData;
148
149#ifdef HAVE_APPLE_QOS
150 pthread_attr_t qosAttribute;
151 pthread_attr_init(&qosAttribute);
152 pthread_attr_set_qos_class_np(&qosAttribute,
153 QOS_CLASS_USER_INTERACTIVE, 0);
154
155 res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc,
156 parms);
157#else
158 res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
159#endif
160
161 struct sched_param sp;
162 memset(&sp, 0, sizeof(struct sched_param));
163 sp.sched_priority = sched_get_priority_max(SCHED_RR);
164 if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == -1) {
165 return ptHostError;
166 }
167
168 if (res != 0) return ptHostError;
169 }
170
171 time_started_flag = TRUE;
172 return ptNoError;
173}
174
175
176PtError Pt_Stop(void)
177{
178 /* printf("Pt_Stop called\n"); */
179 pt_callback_proc_id++;
180 pthread_join(pt_thread_pid, NULL);
181 time_started_flag = FALSE;
182 return ptNoError;
183}
184
185
186int Pt_Started(void)
187{
188 return time_started_flag;
189}
190
191
192PtTimestamp Pt_Time(void)
193{
194 UInt64 clock_time, nsec_time;
195 clock_time = AudioGetCurrentHostTime() - start_time;
196 nsec_time = AudioConvertHostTimeToNanos(clock_time);
197 return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
198}
199
200
201void Pt_Sleep(int32_t duration)
202{
203 usleep(duration * 1000);
204}
diff --git a/portmidi/porttime/ptwinmm.c b/portmidi/porttime/ptwinmm.c
new file mode 100755
index 0000000..5204659
--- /dev/null
+++ b/portmidi/porttime/ptwinmm.c
@@ -0,0 +1,70 @@
1/* ptwinmm.c -- portable timer implementation for win32 */
2
3
4#include "porttime.h"
5#include "windows.h"
6#include "time.h"
7
8
9TIMECAPS caps;
10
11static long time_offset = 0;
12static int time_started_flag = FALSE;
13static long time_resolution;
14static MMRESULT timer_id;
15static PtCallback *time_callback;
16
17void 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
24PMEXPORT 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
41PMEXPORT 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
55PMEXPORT int Pt_Started()
56{
57 return time_started_flag;
58}
59
60
61PMEXPORT PtTimestamp Pt_Time()
62{
63 return timeGetTime() - time_offset;
64}
65
66
67PMEXPORT void Pt_Sleep(int32_t duration)
68{
69 Sleep(duration);
70}
diff --git a/tml.h b/tml.h
new file mode 100644
index 0000000..3332873
--- /dev/null
+++ b/tml.h
@@ -0,0 +1,531 @@
1/* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont
2 no warranty implied; use at your own risk
3 Do this:
4 #define TML_IMPLEMENTATION
5 before you include this file in *one* C or C++ file to create the implementation.
6 // i.e. it should look like this:
7 #include ...
8 #include ...
9 #define TML_IMPLEMENTATION
10 #include "tml.h"
11
12 [OPTIONAL] #define TML_NO_STDIO to remove stdio dependency
13 [OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h
14 [OPTIONAL] #define TML_MEMCPY to avoid string.h
15
16 LICENSE (ZLIB)
17
18 Copyright (C) 2017, 2018, 2020 Bernhard Schelling
19
20 This software is provided 'as-is', without any express or implied
21 warranty. In no event will the authors be held liable for any damages
22 arising from the use of this software.
23
24 Permission is granted to anyone to use this software for any purpose,
25 including commercial applications, and to alter it and redistribute it
26 freely, subject to the following restrictions:
27
28 1. The origin of this software must not be misrepresented; you must not
29 claim that you wrote the original software. If you use this software
30 in a product, an acknowledgment in the product documentation would be
31 appreciated but is not required.
32 2. Altered source versions must be plainly marked as such, and must not be
33 misrepresented as being the original software.
34 3. This notice may not be removed or altered from any source distribution.
35
36*/
37
38#ifndef TML_INCLUDE_TML_INL
39#define TML_INCLUDE_TML_INL
40
41#ifdef __cplusplus
42extern "C" {
43#endif
44
45// Define this if you want the API functions to be static
46#ifdef TML_STATIC
47#define TMLDEF static
48#else
49#define TMLDEF extern
50#endif
51
52// Channel message type
53enum TMLMessageType
54{
55 TML_NOTE_OFF = 0x80, TML_NOTE_ON = 0x90, TML_KEY_PRESSURE = 0xA0, TML_CONTROL_CHANGE = 0xB0, TML_PROGRAM_CHANGE = 0xC0, TML_CHANNEL_PRESSURE = 0xD0, TML_PITCH_BEND = 0xE0, TML_SET_TEMPO = 0x51
56};
57
58// Midi controller numbers
59enum TMLController
60{
61 TML_BANK_SELECT_MSB, TML_MODULATIONWHEEL_MSB, TML_BREATH_MSB, TML_FOOT_MSB = 4, TML_PORTAMENTO_TIME_MSB, TML_DATA_ENTRY_MSB, TML_VOLUME_MSB,
62 TML_BALANCE_MSB, TML_PAN_MSB = 10, TML_EXPRESSION_MSB, TML_EFFECTS1_MSB, TML_EFFECTS2_MSB, TML_GPC1_MSB = 16, TML_GPC2_MSB, TML_GPC3_MSB, TML_GPC4_MSB,
63 TML_BANK_SELECT_LSB = 32, TML_MODULATIONWHEEL_LSB, TML_BREATH_LSB, TML_FOOT_LSB = 36, TML_PORTAMENTO_TIME_LSB, TML_DATA_ENTRY_LSB, TML_VOLUME_LSB,
64 TML_BALANCE_LSB, TML_PAN_LSB = 42, TML_EXPRESSION_LSB, TML_EFFECTS1_LSB, TML_EFFECTS2_LSB, TML_GPC1_LSB = 48, TML_GPC2_LSB, TML_GPC3_LSB, TML_GPC4_LSB,
65 TML_SUSTAIN_SWITCH = 64, TML_PORTAMENTO_SWITCH, TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH, TML_HOLD2_SWITCH,
66 TML_SOUND_CTRL1, TML_SOUND_CTRL2, TML_SOUND_CTRL3, TML_SOUND_CTRL4, TML_SOUND_CTRL5, TML_SOUND_CTRL6,
67 TML_SOUND_CTRL7, TML_SOUND_CTRL8, TML_SOUND_CTRL9, TML_SOUND_CTRL10, TML_GPC5, TML_GPC6, TML_GPC7, TML_GPC8,
68 TML_PORTAMENTO_CTRL, TML_FX_REVERB = 91, TML_FX_TREMOLO, TML_FX_CHORUS, TML_FX_CELESTE_DETUNE, TML_FX_PHASER,
69 TML_DATA_ENTRY_INCR, TML_DATA_ENTRY_DECR, TML_NRPN_LSB, TML_NRPN_MSB, TML_RPN_LSB, TML_RPN_MSB,
70 TML_ALL_SOUND_OFF = 120, TML_ALL_CTRL_OFF, TML_LOCAL_CONTROL, TML_ALL_NOTES_OFF, TML_OMNI_OFF, TML_OMNI_ON, TML_POLY_OFF, TML_POLY_ON
71};
72
73// A single MIDI message linked to the next message in time
74typedef struct tml_message
75{
76 // Time of the message in milliseconds
77 unsigned int time;
78
79 // Type (see TMLMessageType) and channel number
80 unsigned char type, channel;
81
82 // 2 byte of parameter data based on the type:
83 // - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages
84 // - key, key_pressure for TML_KEY_PRESSURE messages
85 // - control, control_value for TML_CONTROL_CHANGE messages (see TMLController)
86 // - program for TML_PROGRAM_CHANGE messages
87 // - channel_pressure for TML_CHANNEL_PRESSURE messages
88 // - pitch_bend for TML_PITCH_BEND messages
89 union
90 {
91 #ifdef _MSC_VER
92 #pragma warning(push)
93 #pragma warning(disable:4201) //nonstandard extension used: nameless struct/union
94 #elif defined(__GNUC__)
95 #pragma GCC diagnostic push
96 #pragma GCC diagnostic ignored "-Wpedantic" //ISO C++ prohibits anonymous structs
97 #endif
98
99 struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; };
100 struct { unsigned short pitch_bend; };
101
102 #ifdef _MSC_VER
103 #pragma warning( pop )
104 #elif defined(__GNUC__)
105 #pragma GCC diagnostic pop
106 #endif
107 };
108
109 // The pointer to the next message in time following this event
110 struct tml_message* next;
111} tml_message;
112
113// The load functions will return a pointer to a struct tml_message.
114// Normally the linked list gets traversed by following the next pointers.
115// Make sure to keep the pointer to the first message to free the memory.
116// On error the tml_load* functions will return NULL most likely due to an
117// invalid MIDI stream (or if the file did not exist in tml_load_filename).
118
119#ifndef TML_NO_STDIO
120// Directly load a MIDI file from a .mid file path
121TMLDEF tml_message* tml_load_filename(const char* filename);
122#endif
123
124// Load a MIDI file from a block of memory
125TMLDEF tml_message* tml_load_memory(const void* buffer, int size);
126
127// Get infos about this loaded MIDI file, returns the note count
128// NULL can be passed for any output value pointer if not needed.
129// used_channels: Will be set to how many channels play notes
130// (i.e. 1 if channel 15 is used but no other)
131// used_programs: Will be set to how many different programs are used
132// total_notes: Will be set to the total number of note on messages
133// time_first_note: Will be set to the time of the first note on message
134// time_length: Will be set to the total time in milliseconds
135TMLDEF int tml_get_info(tml_message* first_message, int* used_channels, int* used_programs, int* total_notes, unsigned int* time_first_note, unsigned int* time_length);
136
137// Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO
138TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message);
139
140// Free all the memory of the linked message list (can also call free() manually)
141TMLDEF void tml_free(tml_message* f);
142
143// Stream structure for the generic loading
144struct tml_stream
145{
146 // Custom data given to the functions as the first parameter
147 void* data;
148
149 // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes)
150 int (*read)(void* data, void* ptr, unsigned int size);
151};
152
153// Generic Midi loading method using the stream structure above
154TMLDEF tml_message* tml_load(struct tml_stream* stream);
155
156// If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used
157struct tsf_stream;
158TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream);
159
160#ifdef __cplusplus
161}
162#endif
163
164// end header
165// ---------------------------------------------------------------------------------------------------------
166#endif //TML_INCLUDE_TML_INL
167
168#ifdef TML_IMPLEMENTATION
169
170#if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC)
171# include <stdlib.h>
172# define TML_MALLOC malloc
173# define TML_FREE free
174# define TML_REALLOC realloc
175#endif
176
177#if !defined(TML_MEMCPY)
178# include <string.h>
179# define TML_MEMCPY memcpy
180#endif
181
182#ifndef TML_NO_STDIO
183# include <stdio.h>
184#endif
185
186#define TML_NULL 0
187
188////crash on errors and warnings to find broken midi files while debugging
189//#define TML_ERROR(msg) *(int*)0 = 0xbad;
190//#define TML_WARN(msg) *(int*)0 = 0xf00d;
191
192////print errors and warnings
193//#define TML_ERROR(msg) printf("ERROR: %s\n", msg);
194//#define TML_WARN(msg) printf("WARNING: %s\n", msg);
195
196#ifndef TML_ERROR
197#define TML_ERROR(msg)
198#endif
199
200#ifndef TML_WARN
201#define TML_WARN(msg)
202#endif
203
204#ifdef __cplusplus
205extern "C" {
206#endif
207
208#ifndef TML_NO_STDIO
209static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); }
210TMLDEF tml_message* tml_load_filename(const char* filename)
211{
212 struct tml_message* res;
213 struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read };
214 #if __STDC_WANT_SECURE_LIB__
215 FILE* f = TML_NULL; fopen_s(&f, filename, "rb");
216 #else
217 FILE* f = fopen(filename, "rb");
218 #endif
219 if (!f) { TML_ERROR("File not found"); return 0; }
220 stream.data = f;
221 res = tml_load(&stream);
222 fclose(f);
223 return res;
224}
225#endif
226
227struct tml_stream_memory { const char* buffer; unsigned int total, pos; };
228static int tml_stream_memory_read(struct tml_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TML_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; }
229TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size)
230{
231 struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read };
232 struct tml_stream_memory f = { 0, 0, 0 };
233 f.buffer = (const char*)buffer;
234 f.total = size;
235 stream.data = &f;
236 return tml_load(&stream);
237}
238
239struct tml_track
240{
241 unsigned int Idx, End, Ticks;
242};
243
244struct tml_tempomsg
245{
246 unsigned int time;
247 unsigned char type, Tempo[3];
248 tml_message* next;
249};
250
251struct tml_parser
252{
253 unsigned char *buf, *buf_end;
254 int last_status, message_array_size, message_count;
255};
256
257enum TMLSystemType
258{
259 TML_TEXT = 0x01, TML_COPYRIGHT = 0x02, TML_TRACK_NAME = 0x03, TML_INST_NAME = 0x04, TML_LYRIC = 0x05, TML_MARKER = 0x06, TML_CUE_POINT = 0x07,
260 TML_EOT = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f,
261 TML_SYSEX = 0xf0, TML_TIME_CODE = 0xf1, TML_SONG_POSITION = 0xf2, TML_SONG_SELECT = 0xf3, TML_TUNE_REQUEST = 0xf6, TML_EOX = 0xf7, TML_SYNC = 0xf8,
262 TML_TICK = 0xf9, TML_START = 0xfa, TML_CONTINUE = 0xfb, TML_STOP = 0xfc, TML_ACTIVE_SENSING = 0xfe, TML_SYSTEM_RESET = 0xff
263};
264
265static int tml_readbyte(struct tml_parser* p)
266{
267 return (p->buf == p->buf_end ? -1 : *(p->buf++));
268}
269
270static int tml_readvariablelength(struct tml_parser* p)
271{
272 unsigned int res = 0, i = 0;
273 unsigned char c;
274 for (; i != 4; i++)
275 {
276 if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; }
277 c = *(p->buf++);
278 if (c & 0x80) res = ((res | (c & 0x7F)) << 7);
279 else return (int)(res | c);
280 }
281 TML_WARN("Invalid variable length byte count"); return -1;
282}
283
284static int tml_parsemessage(tml_message** f, struct tml_parser* p)
285{
286 int deltatime = tml_readvariablelength(p), status = tml_readbyte(p);
287 tml_message* evt;
288
289 if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis
290 if (status < 0) { TML_WARN("Unexpected end of file"); return -1; }
291 if ((status & 0x80) == 0)
292 {
293 // Invalid, use same status as before
294 if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; }
295 p->buf--;
296 status = p->last_status;
297 }
298 else p->last_status = status;
299
300 if (p->message_array_size == p->message_count)
301 {
302 //start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done
303 p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size));
304 *f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message));
305 if (!*f) { TML_ERROR("Out of memory"); return -1; }
306 }
307 evt = *f + p->message_count;
308
309 //check what message we have
310 if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex
311 {
312 //sysex messages are not handled
313 p->buf += tml_readvariablelength(p);
314 if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
315 evt->type = 0;
316 }
317 else if (status == 0xFF) //meta events
318 {
319 int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p);
320 unsigned char* metadata = p->buf;
321 if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; }
322 if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
323
324 switch (meta_type)
325 {
326 case TML_EOT:
327 if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; }
328 if (!deltatime) return TML_EOT; //no need to store this message
329 evt->type = TML_EOT;
330 break;
331
332 case TML_SET_TEMPO:
333 if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; }
334 evt->type = TML_SET_TEMPO;
335 ((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0];
336 ((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1];
337 ((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2];
338 break;
339
340 default:
341 evt->type = 0;
342 }
343 }
344 else //channel message
345 {
346 int param;
347 if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
348 evt->key = (param & 0x7f);
349 evt->channel = (status & 0x0f);
350 switch (evt->type = (status & 0xf0))
351 {
352 case TML_NOTE_OFF:
353 case TML_NOTE_ON:
354 case TML_KEY_PRESSURE:
355 case TML_CONTROL_CHANGE:
356 if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
357 evt->velocity = (param & 0x7f);
358 break;
359
360 case TML_PITCH_BEND:
361 if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
362 evt->pitch_bend = ((param & 0x7f) << 7) | evt->key;
363 break;
364
365 case TML_PROGRAM_CHANGE:
366 case TML_CHANNEL_PRESSURE:
367 evt->velocity = 0;
368 break;
369
370 default: //ignore system/manufacture messages
371 evt->type = 0;
372 break;
373 }
374 }
375
376 if (deltatime || evt->type)
377 {
378 evt->time = deltatime;
379 p->message_count++;
380 }
381 return evt->type;
382}
383
384TMLDEF tml_message* tml_load(struct tml_stream* stream)
385{
386 int num_tracks, division, trackbufsize = 0;
387 unsigned char midi_header[14], *trackbuf = TML_NULL;
388 struct tml_message* messages = TML_NULL;
389 struct tml_track *tracks, *t, *tracksEnd;
390 struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 };
391
392 // Parse MIDI header
393 if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; }
394 if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' ||
395 midi_header[7] != 6 || midi_header[9] > 2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; }
396 if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; }
397 num_tracks = (int)(midi_header[10] << 8) | midi_header[11];
398 division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note)
399 if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; }
400
401 // Allocate temporary tracks array for parsing
402 tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks);
403 tracksEnd = &tracks[num_tracks];
404 for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0;
405
406 // Read all messages for all tracks
407 for (t = tracks; t != tracksEnd; t++)
408 {
409 unsigned char track_header[8];
410 int track_length;
411 if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; }
412 if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k')
413 { TML_WARN("Invalid MTrk header"); break; }
414
415 // Get size of track data and read into buffer (allocate bigger buffer if needed)
416 track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24);
417 if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; }
418 if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); }
419 if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; }
420
421 t->Idx = p.message_count;
422 for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;)
423 {
424 int type = tml_parsemessage(&messages, &p);
425 if (type == TML_EOT || type < 0) break; //file end or illegal data encountered
426 }
427 if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); }
428 t->End = p.message_count;
429 }
430 TML_FREE(trackbuf);
431
432 // Change message time signature from delta ticks to actual msec values and link messages ordered by time
433 if (p.message_count)
434 {
435 tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap;
436 unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change
437 int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change
438 double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick
439
440 // Loop through all messages over all tracks ordered by time
441 for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest)
442 {
443 step_smallest = 0x7fffffff;
444 msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time);
445 for (t = tracks; t != tracksEnd; t++)
446 {
447 if (t->Idx == t->End) continue;
448 for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++)
449 {
450 t->Ticks += Msg->time;
451 if (Msg->type == TML_SET_TEMPO)
452 {
453 unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo;
454 ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division);
455 tempo_msec = msec;
456 tempo_ticks = ticks;
457 }
458 if (Msg->type)
459 {
460 Msg->time = msec;
461 if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; }
462 else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; }
463 }
464 }
465 if (Msg != MsgEnd && t->Ticks + Msg->time > ticks)
466 {
467 int step = (int)(t->Ticks + Msg->time - ticks);
468 if (step < step_smallest) step_smallest = step;
469 }
470 }
471 }
472 if (PrevMessage) PrevMessage->next = TML_NULL;
473 else p.message_count = 0;
474 }
475 TML_FREE(tracks);
476
477 if (p.message_count == 0)
478 {
479 TML_FREE(messages);
480 messages = TML_NULL;
481 }
482
483 return messages;
484}
485
486TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream)
487{
488 return tml_load((struct tml_stream*)stream);
489}
490
491TMLDEF int tml_get_info(tml_message* Msg, int* out_used_channels, int* out_used_programs, int* out_total_notes, unsigned int* out_time_first_note, unsigned int* out_time_length)
492{
493 int used_programs = 0, used_channels = 0, total_notes = 0;
494 unsigned int time_first_note = 0xffffffff, time_length = 0;
495 unsigned char channels[16] = { 0 }, programs[128] = { 0 };
496 for (;Msg; Msg = Msg->next)
497 {
498 time_length = Msg->time;
499 if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; }
500 if (Msg->type != TML_NOTE_ON) continue;
501 if (time_first_note == 0xffffffff) time_first_note = time_length;
502 if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; }
503 total_notes++;
504 }
505 if (time_first_note == 0xffffffff) time_first_note = 0;
506 if (out_used_channels ) *out_used_channels = used_channels;
507 if (out_used_programs ) *out_used_programs = used_programs;
508 if (out_total_notes ) *out_total_notes = total_notes;
509 if (out_time_first_note) *out_time_first_note = time_first_note;
510 if (out_time_length ) *out_time_length = time_length;
511 return total_notes;
512}
513
514TMLDEF int tml_get_tempo_value(tml_message* msg)
515{
516 unsigned char* Tempo;
517 if (!msg || msg->type != TML_SET_TEMPO) return 0;
518 Tempo = ((struct tml_tempomsg*)msg)->Tempo;
519 return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]);
520}
521
522TMLDEF void tml_free(tml_message* f)
523{
524 TML_FREE(f);
525}
526
527#ifdef __cplusplus
528}
529#endif
530
531#endif //TML_IMPLEMENTATION