diff options
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 @@ | |||
| 1 | example1 | ||
| 2 | example2 | ||
| 3 | example3 | ||
| 4 | |||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..51e4b2d --- /dev/null +++ b/Makefile | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | tests: 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 | |||
| 6 | run-example3: | ||
| 7 | LD_LIBRARY_PATH=./portmidi ./example3 | ||
| 8 | |||
| 9 | build-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 | |||
| @@ -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) |
| 7 | const static unsigned char MinimalSoundFont[] = | 7 | const 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 |
| 23 | static tsf* g_TinySoundFont; | 22 | static tsf* g_TinySoundFont; |
| 24 | 23 | ||
| 25 | // Callback function called by the audio thread | 24 | // Callback function called by the audio thread |
| 26 | static void AudioCallback(void* data, Uint8 *stream, int len) | 25 | static 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 | ||
| 36 | int main(int argc, char *argv[]) | 34 | int 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 | |||
| @@ -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 |
| 10 | static SDL_mutex* g_Mutex; | 10 | static SDL_mutex* g_Mutex; |
| 11 | 11 | ||
| 12 | static void AudioCallback(void* data, Uint8 *stream, int len) | 12 | static 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 | ||
| 21 | int main(int argc, char *argv[]) | 20 | int 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 | |||
| 9 | int 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 | ||
| 5 | extern "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 | |||
| 101 | typedef 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 | */ | ||
| 161 | PMEXPORT PmError Pm_Initialize(void); | ||
| 162 | |||
| 163 | /** | ||
| 164 | Pm_Terminate() is the library termination function - call this after | ||
| 165 | using the library. | ||
| 166 | */ | ||
| 167 | PMEXPORT PmError Pm_Terminate(void); | ||
| 168 | |||
| 169 | /** Represents an open MIDI device. */ | ||
| 170 | typedef 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 | */ | ||
| 185 | PMEXPORT 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 | */ | ||
| 192 | PMEXPORT 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 | */ | ||
| 198 | PMEXPORT 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 | */ | ||
| 208 | typedef 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 | ||
| 220 | typedef 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 | |||
| 236 | enum 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 | */ | ||
| 262 | typedef 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. */ | ||
| 273 | PMEXPORT 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 | */ | ||
| 311 | PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void); | ||
| 312 | |||
| 313 | /** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */ | ||
| 314 | PMEXPORT 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 | */ | ||
| 332 | PMEXPORT 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 | */ | ||
| 338 | typedef int32_t PmTimestamp; | ||
| 339 | typedef 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 | */ | ||
| 359 | PMEXPORT 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 | */ | ||
| 403 | PMEXPORT 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 | */ | ||
| 507 | PMEXPORT 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 | */ | ||
| 541 | PMEXPORT 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 | */ | ||
| 571 | PMEXPORT 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 | */ | ||
| 591 | PMEXPORT 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 | */ | ||
| 666 | PMEXPORT 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 | */ | ||
| 692 | PMEXPORT 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 | */ | ||
| 708 | PMEXPORT 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 | */ | ||
| 726 | PMEXPORT 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 | */ | ||
| 753 | PMEXPORT 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 | |||
| 770 | typedef 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 | */ | ||
| 841 | typedef 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 | */ | ||
| 879 | PMEXPORT 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 | */ | ||
| 897 | PMEXPORT 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 | */ | ||
| 931 | PMEXPORT 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 | */ | ||
| 949 | PMEXPORT 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 | */ | ||
| 965 | PMEXPORT 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 @@ | |||
| 1 | name: build | ||
| 2 | |||
| 3 | on: | ||
| 4 | push: | ||
| 5 | pull_request: | ||
| 6 | |||
| 7 | jobs: | ||
| 8 | build: | ||
| 9 | strategy: | ||
| 10 | fail-fast: false | ||
| 11 | matrix: | ||
| 12 | include: | ||
| 13 | - name: Ubuntu | ||
| 14 | os: ubuntu-latest | ||
| 15 | install_dir: ~/portmidi | ||
| 16 | cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||
| 17 | - name: macOS | ||
| 18 | os: macos-latest | ||
| 19 | install_dir: ~/portmidi | ||
| 20 | cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||
| 21 | - name: Windows | ||
| 22 | os: windows-latest | ||
| 23 | install_dir: C:\portmidi | ||
| 24 | cmake_config: --config RelWithDebInfo | ||
| 25 | |||
| 26 | name: ${{ matrix.name }} | ||
| 27 | runs-on: ${{ matrix.os }} | ||
| 28 | steps: | ||
| 29 | - name: Check out Git repository | ||
| 30 | uses: actions/checkout@v2 | ||
| 31 | - name: "[Ubuntu] Install dependencies" | ||
| 32 | run: sudo apt install -y libasound2-dev | ||
| 33 | if: runner.os == 'Linux' | ||
| 34 | - name: Configure | ||
| 35 | run: 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 @@ | |||
| 1 | name: Generate Docs | ||
| 2 | |||
| 3 | on: | ||
| 4 | push: | ||
| 5 | branches: | ||
| 6 | - main | ||
| 7 | workflow_dispatch: | ||
| 8 | |||
| 9 | jobs: | ||
| 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 | ||
| 2 | build*/ | ||
| 3 | *~ | ||
| 4 | CMakeCache.txt | ||
| 5 | CMakeFiles/ | ||
| 6 | CMakeScripts/ | ||
| 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/ | ||
| 15 | portmidi.build/ | ||
| 16 | cmake_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 | |||
| 5 | cmake_minimum_required(VERSION 3.21) | ||
| 6 | # (ALSA::ALSA new in 3.12 and used in pm_common/CMakeLists.txt) | ||
| 7 | # Some Java stuff failed on 3.17 but works with 3.20+ | ||
| 8 | |||
| 9 | cmake_policy(SET CMP0091 NEW) # enables MSVC_RUNTIME_LIBRARY target property | ||
| 10 | |||
| 11 | # Previously, PortMidi versions were simply SVN commit version numbers. | ||
| 12 | # Versions are now in the form x.y.z | ||
| 13 | # Changed 1.0 to 2.0 because API is extended with virtual ports: | ||
| 14 | set(SOVERSION "2") | ||
| 15 | set(VERSION "2.0.4") | ||
| 16 | |||
| 17 | project(portmidi VERSION "${VERSION}" | ||
| 18 | DESCRIPTION "Cross-Platform MIDI IO") | ||
| 19 | |||
| 20 | set(LIBRARY_SOVERSION "${SOVERSION}") | ||
| 21 | set(LIBRARY_VERSION "${VERSION}") | ||
| 22 | |||
| 23 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) | ||
| 24 | |||
| 25 | option(PM_USE_STATIC_RUNTIME | ||
| 26 | "Use MSVC static runtime. Only applies when BUILD_SHARED_LIBS is OFF" | ||
| 27 | ON) | ||
| 28 | |||
| 29 | option(USE_SNDIO "Use sndio" OFF) | ||
| 30 | |||
| 31 | # MSVCRT_DLL is used to construct the MSVC_RUNTIME_LIBRARY property | ||
| 32 | # (see pm_common/CMakeLists.txt and pm_test/CMakeLists.txt) | ||
| 33 | if(PM_USE_STATIC_RUNTIME AND NOT BUILD_SHARED_LIBS) | ||
| 34 | set(MSVCRT_DLL "") | ||
| 35 | else() | ||
| 36 | set(MSVCRT_DLL "DLL") | ||
| 37 | endif() | ||
| 38 | |||
| 39 | # Always build with position-independent code (-fPIC) | ||
| 40 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) | ||
| 41 | |||
| 42 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING | ||
| 43 | "make for this OS version or higher") | ||
| 44 | |||
| 45 | # PM_ACTUAL_LIB_NAME is in this scope -- see pm_common/CMakeLists.txt | ||
| 46 | # PM_NEEDED_LIBS is in this scope -- see pm_common/CMakeLists.txt | ||
| 47 | |||
| 48 | include(GNUInstallDirs) | ||
| 49 | |||
| 50 | # Build Types | ||
| 51 | # credit: http://cliutils.gitlab.io/modern-cmake/chapters/features.html | ||
| 52 | set(DEFAULT_BUILD_TYPE "Release") | ||
| 53 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) | ||
| 54 | message(STATUS | ||
| 55 | "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") | ||
| 56 | set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE | ||
| 57 | STRING "Choose the type of build." FORCE) | ||
| 58 | # Set the possible values of build type for cmake-gui | ||
| 59 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS | ||
| 60 | "Debug" "Release" "MinSizeRel" "RelWithDebInfo") | ||
| 61 | endif() | ||
| 62 | |||
| 63 | # where to put libraries? Everything goes here in this directory | ||
| 64 | # (or Debug or Release, depending on the OS) | ||
| 65 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) | ||
| 66 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) | ||
| 67 | |||
| 68 | option(BUILD_JAVA_NATIVE_INTERFACE | ||
| 69 | "build the Java PortMidi interface library" OFF) | ||
| 70 | |||
| 71 | # Defines are used in both portmidi (in pm_common/) and pmjni (in pm_java), | ||
| 72 | # so define them here to be inherited by both libraries. | ||
| 73 | # | ||
| 74 | # PortMidi software architecture supports multiple system API's to lower- | ||
| 75 | # level MIDI drivers, e.g. PMNULL (no drivers), Jack (but not supported yet), | ||
| 76 | # and sndio (BSD, not supported yet). Interfaces are selected by defining, | ||
| 77 | # e.g., PMALSA. (In principle, we should require PMCOREMIDI (for macOS) | ||
| 78 | # and PMWINMM (for windows), but these are assumed. | ||
| 79 | # | ||
| 80 | if(APPLE OR WIN32) | ||
| 81 | else(APPLE_OR_WIN32) | ||
| 82 | set(LINUX_DEFINES "PMALSA" CACHE STRING "must define either PMALSA or PMNULL") | ||
| 83 | add_compile_definitions(${LINUX_DEFINES}) | ||
| 84 | endif(APPLE OR WIN32) | ||
| 85 | |||
| 86 | if(BUILD_JAVA_NATIVE_INTERFACE) | ||
| 87 | message(WARNING | ||
| 88 | "Java API and PmDefaults program updated 2021, but support has " | ||
| 89 | "been discontinued. If you need/use this, let developers know.") | ||
| 90 | set(PMJNI_IF_EXISTS "pmjni") # used by INSTALL below | ||
| 91 | else(BUILD_JAVA_NATIVE_INTERFACE) | ||
| 92 | set(PMJNI_IF_EXISTS "") # used by INSTALL below | ||
| 93 | endif(BUILD_JAVA_NATIVE_INTERFACE) | ||
| 94 | |||
| 95 | |||
| 96 | # Something like this might help if you need to build for a specific cpu type: | ||
| 97 | # set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING | ||
| 98 | # "change to support other architectures" FORCE) | ||
| 99 | |||
| 100 | include_directories(pm_common porttime) | ||
| 101 | add_subdirectory(pm_common) | ||
| 102 | |||
| 103 | option(BUILD_PORTMIDI_TESTS | ||
| 104 | "Build test programs, including midi monitor (mm)" OFF) | ||
| 105 | if(BUILD_PORTMIDI_TESTS) | ||
| 106 | add_subdirectory(pm_test) | ||
| 107 | endif(BUILD_PORTMIDI_TESTS) | ||
| 108 | |||
| 109 | # See note above about Java support (probably) discontinued | ||
| 110 | if(BUILD_JAVA_NATIVE_INTERFACE) | ||
| 111 | add_subdirectory(pm_java) | ||
| 112 | endif(BUILD_JAVA_NATIVE_INTERFACE) | ||
| 113 | |||
| 114 | # Install the libraries and headers (Linux and Mac OS X command line) | ||
| 115 | INSTALL(TARGETS portmidi ${PMJNI_IF_EXISTS} | ||
| 116 | EXPORT PortMidiTargets | ||
| 117 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" | ||
| 118 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" | ||
| 119 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" | ||
| 120 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") | ||
| 121 | |||
| 122 | INSTALL(FILES | ||
| 123 | pm_common/portmidi.h | ||
| 124 | pm_common/pmutil.h | ||
| 125 | porttime/porttime.h | ||
| 126 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) | ||
| 127 | |||
| 128 | # pkgconfig - generate pc file | ||
| 129 | # See https://cmake.org/cmake/help/latest/command/configure_file.html | ||
| 130 | if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") | ||
| 131 | set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") | ||
| 132 | else() | ||
| 133 | set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") | ||
| 134 | endif() | ||
| 135 | if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") | ||
| 136 | set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") | ||
| 137 | else() | ||
| 138 | set(PKGCONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") | ||
| 139 | endif() | ||
| 140 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/portmidi.pc.in | ||
| 141 | ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc @ONLY) | ||
| 142 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc | ||
| 143 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) | ||
| 144 | |||
| 145 | # CMake config | ||
| 146 | set(PORTMIDI_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/PortMidi") | ||
| 147 | install( | ||
| 148 | EXPORT PortMidiTargets | ||
| 149 | FILE PortMidiTargets.cmake | ||
| 150 | NAMESPACE PortMidi:: | ||
| 151 | DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" | ||
| 152 | ) | ||
| 153 | include(CMakePackageConfigHelpers) | ||
| 154 | configure_package_config_file(packaging/PortMidiConfig.cmake.in | ||
| 155 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake" | ||
| 156 | INSTALL_DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}" | ||
| 157 | ) | ||
| 158 | write_basic_package_version_file( | ||
| 159 | "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake" | ||
| 160 | VERSION "${CMAKE_PROJECT_VERSION}" | ||
| 161 | COMPATIBILITY SameMajorVersion | ||
| 162 | ) | ||
| 163 | install( | ||
| 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... | ||
| 176 | MESSAGE(STATUS "PortMidi Library name: " ${PM_ACTUAL_LIB_NAME}) | ||
| 177 | MESSAGE(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) | ||
| 178 | MESSAGE(STATUS "Library Type: " ${LIB_TYPE}) | ||
| 179 | MESSAGE(STATUS "Compiler flags: " ${CMAKE_CXX_COMPILE_FLAGS}) | ||
| 180 | get_directory_property(prop COMPILE_DEFINITIONS) | ||
| 181 | MESSAGE(STATUS "Compile definitions: " ${prop}) | ||
| 182 | get_directory_property(prop COMPILE_OPTIONS) | ||
| 183 | MESSAGE(STATUS "Compile options: " ${prop}) | ||
| 184 | MESSAGE(STATUS "Compiler cxx debug flags: " ${CMAKE_CXX_FLAGS_DEBUG}) | ||
| 185 | MESSAGE(STATUS "Compiler cxx release flags: " ${CMAKE_CXX_FLAGS_RELEASE}) | ||
| 186 | MESSAGE(STATUS "Compiler cxx min size flags: " ${CMAKE_CXX_FLAGS_MINSIZEREL}) | ||
| 187 | MESSAGE(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 | |||
| 27 | DOXYFILE_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 | |||
| 35 | PROJECT_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 | |||
| 41 | PROJECT_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 | |||
| 47 | PROJECT_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 | |||
| 54 | PROJECT_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 | |||
| 61 | OUTPUT_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 | |||
| 71 | CREATE_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 | |||
| 79 | ALLOW_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 | |||
| 94 | OUTPUT_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 | |||
| 101 | BRIEF_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 | |||
| 110 | REPEAT_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 | |||
| 121 | ABBREVIATE_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 | |||
| 138 | ALWAYS_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 | |||
| 146 | INLINE_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 | |||
| 153 | FULL_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 | |||
| 165 | STRIP_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 | |||
| 174 | STRIP_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 | |||
| 181 | SHORT_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 | |||
| 190 | JAVADOC_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 | |||
| 200 | JAVADOC_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 | |||
| 208 | QT_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 | |||
| 220 | MULTILINE_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 | |||
| 228 | PYTHON_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 | |||
| 234 | INHERIT_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 | |||
| 241 | SEPARATE_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 | |||
| 247 | TAB_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 | |||
| 264 | ALIASES = | ||
| 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 | |||
| 272 | OPTIMIZE_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 | |||
| 280 | OPTIMIZE_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 | |||
| 286 | OPTIMIZE_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 | |||
| 292 | OPTIMIZE_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 | |||
| 300 | OPTIMIZE_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 | |||
| 323 | EXTENSION_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 | |||
| 333 | MARKDOWN_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 | |||
| 342 | TOC_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 | |||
| 350 | AUTOLINK_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 | |||
| 360 | BUILTIN_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 | |||
| 366 | CPP_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 | |||
| 374 | SIP_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 | |||
| 384 | IDL_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 | |||
| 392 | DISTRIBUTE_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 | |||
| 399 | GROUP_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 | |||
| 408 | SUBGROUPING = 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 | |||
| 419 | INLINE_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 | |||
| 429 | INLINE_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 | |||
| 440 | TYPEDEF_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 | |||
| 453 | LOOKUP_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 | |||
| 466 | NUM_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 | |||
| 480 | EXTRACT_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 | |||
| 486 | EXTRACT_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 | |||
| 492 | EXTRACT_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 | |||
| 498 | EXTRACT_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 | |||
| 504 | EXTRACT_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 | |||
| 512 | EXTRACT_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 | |||
| 520 | EXTRACT_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 | |||
| 529 | EXTRACT_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 | |||
| 536 | RESOLVE_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 | |||
| 544 | HIDE_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 | |||
| 552 | HIDE_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 | |||
| 559 | HIDE_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 | |||
| 566 | HIDE_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 | |||
| 573 | INTERNAL_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 | |||
| 589 | CASE_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 | |||
| 596 | HIDE_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 | |||
| 603 | HIDE_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 | |||
| 609 | SHOW_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 | |||
| 615 | SHOW_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 | |||
| 622 | SHOW_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 | |||
| 628 | FORCE_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 | |||
| 634 | INLINE_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 | |||
| 641 | SORT_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 | |||
| 649 | SORT_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 | |||
| 661 | SORT_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 | |||
| 668 | SORT_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 | |||
| 678 | SORT_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 | |||
| 688 | STRICT_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 | |||
| 694 | GENERATE_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 | |||
| 700 | GENERATE_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 | |||
| 706 | GENERATE_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 | |||
| 713 | GENERATE_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 | |||
| 719 | ENABLED_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 | |||
| 730 | MAX_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 | |||
| 737 | SHOW_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 | |||
| 744 | SHOW_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 | |||
| 751 | SHOW_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 | |||
| 761 | FILE_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 | |||
| 775 | LAYOUT_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 | |||
| 785 | CITE_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 | |||
| 796 | QUIET = 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 | |||
| 805 | WARNINGS = 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 | |||
| 812 | WARN_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 | |||
| 820 | WARN_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 | |||
| 827 | WARN_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 | |||
| 837 | WARN_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 | |||
| 846 | WARN_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 | |||
| 856 | WARN_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 | |||
| 862 | WARN_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 | |||
| 874 | INPUT =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 | |||
| 883 | INPUT_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 | |||
| 903 | FILE_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 | |||
| 954 | RECURSIVE = 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 | |||
| 963 | EXCLUDE = | ||
| 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 | |||
| 970 | EXCLUDE_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 | |||
| 979 | EXCLUDE_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 | |||
| 990 | EXCLUDE_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 | |||
| 996 | EXAMPLE_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 | |||
| 1003 | EXAMPLE_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 | |||
| 1010 | EXAMPLE_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 | |||
| 1016 | IMAGE_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 | |||
| 1037 | INPUT_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 | |||
| 1050 | FILTER_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 | |||
| 1057 | FILTER_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 | |||
| 1065 | FILTER_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 | |||
| 1072 | USE_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 | |||
| 1085 | SOURCE_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 | |||
| 1091 | INLINE_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 | |||
| 1098 | STRIP_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 | |||
| 1104 | REFERENCED_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 | |||
| 1110 | REFERENCES_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 | |||
| 1118 | REFERENCES_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 | |||
| 1128 | SOURCE_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 | |||
| 1150 | USE_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 | |||
| 1158 | VERBATIM_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 | |||
| 1169 | CLANG_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 | |||
| 1177 | CLANG_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 | |||
| 1185 | CLANG_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 | |||
| 1198 | CLANG_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 | |||
| 1209 | ALPHABETICAL_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 | |||
| 1217 | IGNORE_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 | |||
| 1226 | GENERATE_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 | |||
| 1234 | HTML_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 | |||
| 1241 | HTML_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 | |||
| 1261 | HTML_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 | |||
| 1271 | HTML_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 | |||
| 1283 | HTML_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 | |||
| 1296 | HTML_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 | |||
| 1306 | HTML_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 | |||
| 1317 | HTML_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 | |||
| 1325 | HTML_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 | |||
| 1336 | HTML_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 | |||
| 1345 | HTML_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 | |||
| 1356 | HTML_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 | |||
| 1364 | HTML_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 | |||
| 1377 | HTML_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 | |||
| 1392 | GENERATE_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 | |||
| 1400 | DOCSET_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 | |||
| 1408 | DOCSET_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 | |||
| 1416 | DOCSET_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 | |||
| 1422 | DOCSET_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 | |||
| 1444 | GENERATE_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 | |||
| 1451 | CHM_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 | |||
| 1459 | HHC_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 | |||
| 1466 | GENERATE_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 | |||
| 1472 | CHM_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 | |||
| 1480 | BINARY_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 | |||
| 1487 | TOC_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 | |||
| 1496 | GENERATE_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 | |||
| 1503 | QCH_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 | |||
| 1512 | QHP_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 | |||
| 1521 | QHP_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 | |||
| 1529 | QHP_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 | |||
| 1537 | QHP_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 | |||
| 1544 | QHP_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 | |||
| 1551 | QHG_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 | |||
| 1563 | GENERATE_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 | |||
| 1571 | ECLIPSE_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 | |||
| 1582 | DISABLE_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 | |||
| 1599 | GENERATE_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 | |||
| 1611 | FULL_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 | |||
| 1621 | ENUM_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 | |||
| 1628 | TREEVIEW_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 | |||
| 1635 | EXT_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 | |||
| 1646 | HTML_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 | |||
| 1655 | FORMULA_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 | |||
| 1666 | FORMULA_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 | |||
| 1672 | FORMULA_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 | |||
| 1683 | USE_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 | |||
| 1694 | MATHJAX_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 | |||
| 1711 | MATHJAX_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 | |||
| 1725 | MATHJAX_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 | |||
| 1737 | MATHJAX_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 | |||
| 1746 | MATHJAX_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 | |||
| 1767 | SEARCHENGINE = 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 | |||
| 1779 | SERVER_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 | |||
| 1796 | EXTERNAL_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 | |||
| 1808 | SEARCHENGINE_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 | |||
| 1816 | SEARCHDATA_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 | |||
| 1824 | EXTERNAL_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 | |||
| 1834 | EXTRA_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 | |||
| 1843 | GENERATE_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 | |||
| 1851 | LATEX_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 | |||
| 1863 | LATEX_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 | |||
| 1873 | MAKEINDEX_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 | |||
| 1883 | LATEX_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 | |||
| 1891 | COMPACT_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 | |||
| 1900 | PAPER_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 | |||
| 1912 | EXTRA_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 | |||
| 1929 | LATEX_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 | |||
| 1941 | LATEX_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 | |||
| 1952 | LATEX_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 | |||
| 1960 | LATEX_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 | |||
| 1969 | PDF_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 | |||
| 1979 | USE_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 | |||
| 1987 | LATEX_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 | |||
| 1994 | LATEX_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 | |||
| 2002 | LATEX_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 | |||
| 2010 | LATEX_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 | |||
| 2018 | LATEX_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 | |||
| 2029 | GENERATE_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 | |||
| 2037 | RTF_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 | |||
| 2045 | COMPACT_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 | |||
| 2057 | RTF_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 | |||
| 2067 | RTF_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 | |||
| 2074 | RTF_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 | |||
| 2084 | GENERATE_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 | |||
| 2093 | MAN_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 | |||
| 2102 | MAN_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 | |||
| 2109 | MAN_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 | |||
| 2118 | MAN_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 | |||
| 2128 | GENERATE_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 | |||
| 2136 | XML_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 | |||
| 2145 | XML_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 | |||
| 2152 | XML_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 | |||
| 2162 | GENERATE_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 | |||
| 2170 | DOCBOOK_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 | |||
| 2182 | GENERATE_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 | |||
| 2198 | GENERATE_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 | |||
| 2206 | PERLMOD_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 | |||
| 2216 | PERLMOD_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 | |||
| 2224 | PERLMOD_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 | |||
| 2234 | ENABLE_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 | |||
| 2243 | MACRO_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 | |||
| 2251 | EXPAND_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 | |||
| 2258 | SEARCH_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 | |||
| 2265 | INCLUDE_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 | |||
| 2273 | INCLUDE_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 | |||
| 2283 | PREDEFINED = | ||
| 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 | |||
| 2292 | EXPAND_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 | |||
| 2302 | SKIP_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 | |||
| 2321 | TAGFILES = | ||
| 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 | |||
| 2327 | GENERATE_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 | |||
| 2334 | ALLEXTERNALS = 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 | |||
| 2341 | EXTERNAL_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 | |||
| 2348 | EXTERNAL_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 | |||
| 2361 | CLASS_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 | |||
| 2368 | DIA_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 | |||
| 2374 | HIDE_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 | |||
| 2383 | HAVE_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 | |||
| 2393 | DOT_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 | |||
| 2403 | DOT_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 | |||
| 2410 | DOT_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 | |||
| 2417 | DOT_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 | |||
| 2425 | CLASS_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 | |||
| 2434 | COLLABORATION_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 | |||
| 2441 | GROUP_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 | |||
| 2449 | UML_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 | |||
| 2462 | UML_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 | |||
| 2475 | DOT_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 | |||
| 2484 | DOT_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 | |||
| 2492 | TEMPLATE_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 | |||
| 2501 | INCLUDE_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 | |||
| 2510 | INCLUDED_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 | |||
| 2522 | CALL_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 | |||
| 2534 | CALLER_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 | |||
| 2541 | GRAPHICAL_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 | |||
| 2550 | DIRECTORY_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 | |||
| 2565 | DOT_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 | |||
| 2577 | INTERACTIVE_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 | |||
| 2583 | DOT_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 | |||
| 2590 | DOTFILE_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 | |||
| 2596 | MSCFILE_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 | |||
| 2602 | DIAFILE_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 | |||
| 2610 | PLANTUML_JAR_PATH = | ||
| 2611 | |||
| 2612 | # When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a | ||
| 2613 | # configuration file for plantuml. | ||
| 2614 | |||
| 2615 | PLANTUML_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 | |||
| 2620 | PLANTUML_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 | |||
| 2632 | DOT_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 | |||
| 2644 | MAX_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 | |||
| 2656 | DOT_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 | |||
| 2665 | DOT_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 | |||
| 2673 | GENERATE_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 | |||
| 2682 | DOT_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 | |||
| 3 | This is the canonical release of PortMidi. | ||
| 4 | |||
| 5 | See other repositories within [PortMidi](https://github.com/PortMidi) | ||
| 6 | for related code and bindings (although currently, not much is here). | ||
| 7 | |||
| 8 | ## [Full C API documentation is here.](https://portmidi.github.io/portmidi_docs/) | ||
| 9 | |||
| 10 | ## Compiling and Using PortMidi | ||
| 11 | |||
| 12 | Use CMake (or ccmake) to create a Makefile for Linux/BSD or a | ||
| 13 | project file for Xcode or MS Visual Studio. Use make or an IDE to compile: | ||
| 14 | ``` | ||
| 15 | sudo apt install libasound2-dev # on Linux, you need ALSA | ||
| 16 | cd portmidi # start in the top-level portmidi directory | ||
| 17 | ccmake . # 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) | ||
| 22 | make # compile sources and build PortMidi library | ||
| 23 | # (alternatively, open project file with your IDE) | ||
| 24 | sudo make install # if you want to install to your system | ||
| 25 | ``` | ||
| 26 | |||
| 27 | ## Installation | ||
| 28 | |||
| 29 | My advice is to build PortMidi and statically link it to your | ||
| 30 | application. This gives you more control over versions. However, | ||
| 31 | installing PortMidi to your system is preferred by some, and the | ||
| 32 | following should do it: | ||
| 33 | ``` | ||
| 34 | cmake . | ||
| 35 | make | ||
| 36 | sudo make install | ||
| 37 | ``` | ||
| 38 | |||
| 39 | ## Language Bindings | ||
| 40 | |||
| 41 | Here is a guide to some other projects using PortMidi. There is not | ||
| 42 | much coordination, so let us know if there are better or alternative | ||
| 43 | bindings 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, | ||
| 65 | which enabled a graphical interface to select default MIDI devices, | ||
| 66 | has been removed for lack of interest. This allowed us to also remove | ||
| 67 | C code to read and parse Java preference files on various systems, | ||
| 68 | simplifying the library. **PmDefaults** allowed simple command-line | ||
| 69 | programs to use `Pm_DefaultInputDeviceID()` and | ||
| 70 | `Pm_DefaultOutputDeviceID()` rather than creating device selection | ||
| 71 | interfaces. Now, you should either pass devices on the command line or | ||
| 72 | create your own selection interface when building a GUI | ||
| 73 | application. (See pm_tests for examples.) `Pm_DefaultInputDeviceID()` | ||
| 74 | and `Pm_DefaultOutputDeviceID()` now return a valid device if | ||
| 75 | possible, but they may not actually reflect any user preference. | ||
| 76 | |||
| 77 | Haiku support in a minimal implementation. See TODO's in source. | ||
| 78 | |||
| 79 | sndio is also minimally supported, allowing basic PortMidi functions | ||
| 80 | in OpenBSD, FreeBSD, and NetBSD by setting USE_SNDIO for CMake, but | ||
| 81 | not delayed/timestamped output and virtual devices. | ||
| 82 | |||
| 83 | # Other Repositories | ||
| 84 | |||
| 85 | PortMidi used to be part of the PortMedia suite, but this repo has | ||
| 86 | been reduced to mostly just C/C++ code for PortMidi. You will find | ||
| 87 | some other repositories in this PortMidi project set up for language | ||
| 88 | bindings (volunteers and contributors are invited!). Other code | ||
| 89 | removed from previous releases of PortMedia include: | ||
| 90 | |||
| 91 | ## PortSMF | ||
| 92 | |||
| 93 | A Standard MIDI File (SMF) (and more) library is in the [portsmf | ||
| 94 | repository](https://github.com/rbdannenberg/portsmf). | ||
| 95 | |||
| 96 | PortSMF is a library for reading/writing/editing Standard MIDI | ||
| 97 | Files. It is actually much more, with a general representation of | ||
| 98 | events and updates with properties consisting of attributes and typed | ||
| 99 | values. Familiar properties of pitch, time, duration, and channel are | ||
| 100 | built into events and updates to make them faster to access and more | ||
| 101 | compact. | ||
| 102 | |||
| 103 | To my knowledge, PortSMF has the most complete and useful handling of | ||
| 104 | MIDI tempo tracks. E.g., you can edit notes according to either beat | ||
| 105 | or time, and you can edit tempo tracks, for example, flattening the | ||
| 106 | tempo while preserving the beat alignment, preserving the real time | ||
| 107 | while changing the tempo or stretching the tempo over some interval. | ||
| 108 | |||
| 109 | In addition to Standard MIDI Files, PortSMF supports an ASCII | ||
| 110 | representation called Allegro. PortSMF and Allegro are used for | ||
| 111 | Audacity Note Tracks. | ||
| 112 | |||
| 113 | ## scorealign | ||
| 114 | |||
| 115 | Scorealign used to be part of the PortMedia suite. It is now at the | ||
| 116 | [scorealign repository](https://github.com/rbdannenberg/scorealign). | ||
| 117 | |||
| 118 | Scorealign aligns audio-to-audio, audio-to-MIDI or MIDI-to-MIDI using | ||
| 119 | dynamic time warping (DTW) of a computed chromagram | ||
| 120 | representation. There are some added smoothing tricks to improve | ||
| 121 | performance. This library is written in C and runs substantially | ||
| 122 | faster than most other implementations, especially those written in | ||
| 123 | MATLAB, due to the core DTW algorithm. Users should be warned that | ||
| 124 | while chromagrams are robust features for alignment, they achieve | ||
| 125 | robustness by operating at fairly high granularity, e.g., durations of | ||
| 126 | around 100ms, which limits time precision. Other more recent | ||
| 127 | algorithms can doubtless do better, but be cautious of claims, since | ||
| 128 | it 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 @@ | |||
| 1 | README for PortMidi | ||
| 2 | |||
| 3 | Roger B. Dannenberg | ||
| 4 | |||
| 5 | Documentation for PortMidi is found in pm_common/portmidi.h. | ||
| 6 | Documentation in HTML is available at portmidi.github.io/portmidi_docs/ | ||
| 7 | |||
| 8 | Additional documentation: | ||
| 9 | - README.md (overview, how to build, what's new) | ||
| 10 | - Windows: see pm_win/README_WIN.txt and pm_win/debugging_dlls.txt | ||
| 11 | - Linux: see pm_linux/README_LINUX.txt | ||
| 12 | - Mac OSX: see pm_mac/README_MAC.txt | ||
| 13 | - Other Languages: look for other repos at github.com/PortMidi, | ||
| 14 | and search README.md for pointers to other projects. | ||
| 15 | |||
| 16 | ---------- some notes on the design of PortMidi ---------- | ||
| 17 | |||
| 18 | POINTERS VS DEVICE NUMBERS | ||
| 19 | |||
| 20 | When you open a MIDI port, PortMidi allocates a structure to | ||
| 21 | maintain the state of the open device. Since every device is | ||
| 22 | also listed in a table, you might think it would be simpler to | ||
| 23 | use the table index rather than a pointer to identify a device. | ||
| 24 | This would also help with error checking (it's hard to make | ||
| 25 | sure a pointer is valid). PortMidi's design parallels that of | ||
| 26 | PortAudio. | ||
| 27 | |||
| 28 | ERROR HANDLING | ||
| 29 | |||
| 30 | Error handling turned out to be much more complicated than expected. | ||
| 31 | PortMidi functions return error codes that the caller can check. | ||
| 32 | In addition, errors may occur asynchronously due to MIDI input. | ||
| 33 | However, for Windows, there are virtually no errors that can | ||
| 34 | occur if the code is correct and not passing bogus values. One | ||
| 35 | exception is an error that the system is out of memory, but my | ||
| 36 | guess is that one is unlikely to recover gracefully from that. | ||
| 37 | Therefore, all errors in callbacks are guarded by assert(), which | ||
| 38 | means not guarded at all in release configurations. | ||
| 39 | |||
| 40 | Ordinarily, the caller checks for an error code. If the error is | ||
| 41 | system-dependent, pmHostError is returned and the caller can | ||
| 42 | call Pm_GetHostErrorText to get a text description of the error. | ||
| 43 | |||
| 44 | Host error codes are system-specific and are recorded in the | ||
| 45 | system-specific data allocated for each open MIDI port. | ||
| 46 | However, if an error occurs on open or close, | ||
| 47 | we cannot store the error with the device because there will be | ||
| 48 | no device data (assuming PortMidi cleans up after devices that | ||
| 49 | are not open). For open and close, we will convert the error | ||
| 50 | to text, copy it to a global string, and set pm_hosterror, a | ||
| 51 | global flag. | ||
| 52 | |||
| 53 | Similarly, whenever a Read or Write operation returns pmHostError, | ||
| 54 | the corresponding error string is copied to a global string | ||
| 55 | and pm_hosterror is set. This makes getting error strings | ||
| 56 | simple and uniform, although it does cost a string copy and some | ||
| 57 | overhead even if the user does not want to look at the error data. | ||
| 58 | |||
| 59 | The system-specific Read, Write, Poll, etc. implementations should | ||
| 60 | check for asynchronous errors and return immediately if one is | ||
| 61 | found so that these get reported. This happens in the Mac OS X | ||
| 62 | code, where lots of things are happening in callbacks, but again, | ||
| 63 | in Windows, there are no error codes recorded in callbacks. | ||
| 64 | |||
| 65 | DEBUGGING | ||
| 66 | |||
| 67 | If you are building a console application for research, we suggest | ||
| 68 | compiling with the option PM_CHECK_ERRORS. This will insert a | ||
| 69 | check for error return values at the end of each PortMidi | ||
| 70 | function. If an error is encountered, a text message is printed | ||
| 71 | using printf(), the user is asked to type ENTER, and then exit(-1) | ||
| 72 | is called to clean up and terminate the program. | ||
| 73 | |||
| 74 | You should not use PM_CHECK_ERRORS if printf() does not work | ||
| 75 | (e.g. this is not a console application under Windows, or there | ||
| 76 | is no visible console on some other OS), and you should not use | ||
| 77 | PM_CHECK_ERRORS if you intend to recover from errors rather than | ||
| 78 | abruptly terminate the program. | ||
| 79 | |||
| 80 | The Windows version (and perhaps others) also offers a DEBUG | ||
| 81 | compile-time option. See README_WIN.txt. | ||
| 82 | |||
| 83 | RELEASE | ||
| 84 | |||
| 85 | To make a new release, update VERSION variable in CMakeLists.txt. | ||
| 86 | |||
| 87 | Update CHANGELOG.txt. What's new? | ||
| 88 | |||
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 | |||
| 3 | include(CMakeFindDependencyMacro) | ||
| 4 | if(UNIX AND NOT APPLE AND NOT HAIKU AND (@LINUX_DEFINES@ MATCHES ".*PMALSA.*")) | ||
| 5 | find_dependency(ALSA) | ||
| 6 | endif() | ||
| 7 | |||
| 8 | if(NOT WIN32) | ||
| 9 | set(THREADS_PREFER_PTHREAD_FLAG ON) | ||
| 10 | find_package(Threads REQUIRED) | ||
| 11 | endif() | ||
| 12 | |||
| 13 | include("${CMAKE_CURRENT_LIST_DIR}/PortMidiTargets.cmake") | ||
| 14 | |||
| 15 | check_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 @@ | |||
| 1 | prefix=@CMAKE_INSTALL_PREFIX@ | ||
| 2 | exec_prefix=${prefix} | ||
| 3 | libdir=@PKGCONFIG_LIBDIR@ | ||
| 4 | includedir=@PKGCONFIG_INCLUDEDIR@ | ||
| 5 | |||
| 6 | Name: @CMAKE_PROJECT_NAME@ | ||
| 7 | Description: @CMAKE_PROJECT_DESCRIPTION@ | ||
| 8 | Version: @CMAKE_PROJECT_VERSION@ | ||
| 9 | Cflags: -I${includedir} | ||
| 10 | Libs: -L${libdir} -l@CMAKE_PROJECT_NAME@ | ||
| 11 | Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@ | ||
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"): | ||
| 16 | set(PM_STATIC_LIB_NAME "portmidi" CACHE STRING | ||
| 17 | "For static builds, the PortMidi library name, e.g. portmidi-static. | ||
| 18 | Default is portmidi") | ||
| 19 | set(PM_ACTUAL_LIB_NAME "portmidi") | ||
| 20 | if(NOT BUILD_SHARED_LIBS) | ||
| 21 | set(PM_ACTUAL_LIB_NAME ${PM_STATIC_LIB_NAME}) | ||
| 22 | endif() | ||
| 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. | ||
| 26 | if(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}) | ||
| 31 | endif(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: | ||
| 35 | get_filename_component(PMDIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) | ||
| 36 | set(PM_LIB_PUBLIC_SRC ${PMDIR}/pm_common/portmidi.c | ||
| 37 | ${PMDIR}/pm_common/pmutil.c | ||
| 38 | ${PMDIR}/porttime/porttime.c) | ||
| 39 | add_library(portmidi ${PM_LIB_PUBLIC_SRC}) | ||
| 40 | |||
| 41 | # MSVCRT_DLL is "DLL" for shared runtime library, and "" for static: | ||
| 42 | set_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) | ||
| 49 | target_include_directories(portmidi PUBLIC | ||
| 50 | $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> | ||
| 51 | $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>) | ||
| 52 | |||
| 53 | |||
| 54 | option(PM_CHECK_ERRORS | ||
| 55 | "Insert a check for error return values at the end of each PortMidi function. | ||
| 56 | If an error is encountered, a text message is printed using printf(), the user | ||
| 57 | is asked to type ENTER, and then exit(-1) is called to clean up and terminate | ||
| 58 | the program. | ||
| 59 | |||
| 60 | You should not use PM_CHECK_ERRORS if printf() does not work (e.g. this is not | ||
| 61 | a console application under Windows, or there is no visible console on some | ||
| 62 | other OS), and you should not use PM_CHECK_ERRORS if you intend to recover | ||
| 63 | from errors rather than abruptly terminate the program." OFF) | ||
| 64 | if(PM_CHECK_ERRORS) | ||
| 65 | target_compile_definitions(portmidi PRIVATE PM_CHECK_ERRORS) | ||
| 66 | endif(PM_CHECK_ERRORS) | ||
| 67 | |||
| 68 | macro(prepend_path RESULT PATH) | ||
| 69 | set(${RESULT}) | ||
| 70 | foreach(FILE ${ARGN}) | ||
| 71 | list(APPEND ${RESULT} "${PATH}${FILE}") | ||
| 72 | endforeach(FILE) | ||
| 73 | endmacro(prepend_path) | ||
| 74 | |||
| 75 | # UNIX needs pthread library | ||
| 76 | if(NOT WIN32) | ||
| 77 | set(THREADS_PREFER_PTHREAD_FLAG ON) | ||
| 78 | find_package(Threads REQUIRED) | ||
| 79 | endif() | ||
| 80 | |||
| 81 | # Check for sndio | ||
| 82 | if(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) | ||
| 88 | endif(USE_SNDIO) | ||
| 89 | |||
| 90 | # first include the appropriate system-dependent file: | ||
| 91 | if(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}) | ||
| 98 | elseif(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) | ||
| 120 | elseif(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) | ||
| 126 | elseif(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() | ||
| 147 | elseif(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() | ||
| 159 | else() | ||
| 160 | message(FATAL_ERROR "Operating system not supported.") | ||
| 161 | endif() | ||
| 162 | |||
| 163 | set(PM_LIB_PUBLIC_SRC ${PM_LIB_PUBLIC_SRC} PARENT_SCOPE) # export to parent | ||
| 164 | set(PM_LIB_PRIVATE_SRC ${PM_LIB_PRIVATE_SRC} PARENT_SCOPE) # export to parent | ||
| 165 | |||
| 166 | target_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 | ||
| 23 | extern "C" { | ||
| 24 | #endif | ||
| 25 | |||
| 26 | extern int pm_initialized; /* see note in portmidi.c */ | ||
| 27 | extern PmDeviceID pm_default_input_device_id; | ||
| 28 | extern PmDeviceID pm_default_output_device_id; | ||
| 29 | |||
| 30 | /* these are defined in system-specific file */ | ||
| 31 | void *pm_alloc(size_t s); | ||
| 32 | void 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: */ | ||
| 37 | extern int pm_hosterror; /* boolean */ | ||
| 38 | extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; | ||
| 39 | |||
| 40 | struct pm_internal_struct; | ||
| 41 | |||
| 42 | /* these do not use PmInternal because it is not defined yet... */ | ||
| 43 | typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, | ||
| 44 | PmEvent *buffer); | ||
| 45 | typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, | ||
| 46 | PmTimestamp timestamp); | ||
| 47 | typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, | ||
| 48 | PmTimestamp timestamp); | ||
| 49 | typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, | ||
| 50 | unsigned char byte, PmTimestamp timestamp); | ||
| 51 | typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, | ||
| 52 | PmEvent *buffer); | ||
| 53 | typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi, | ||
| 54 | PmTimestamp timestamp); | ||
| 55 | typedef 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 */ | ||
| 58 | typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, | ||
| 59 | void *driverInfo); | ||
| 60 | typedef PmError (*pm_create_fn)(int is_input, const char *name, | ||
| 61 | void *driverInfo); | ||
| 62 | typedef PmError (*pm_delete_fn)(PmDeviceID id); | ||
| 63 | typedef 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. */ | ||
| 66 | typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); | ||
| 67 | typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); | ||
| 68 | typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi); | ||
| 69 | |||
| 70 | typedef 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: */ | ||
| 88 | extern pm_fns_node pm_none_dictionary; | ||
| 89 | |||
| 90 | typedef 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 | |||
| 101 | extern int pm_descriptor_max; | ||
| 102 | extern descriptor_type pm_descriptors; | ||
| 103 | extern int pm_descriptor_len; | ||
| 104 | |||
| 105 | typedef uint32_t (*time_get_proc_type)(void *time_info); | ||
| 106 | |||
| 107 | typedef 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? */ | ||
| 153 | int pm_midi_length(PmMessage msg); | ||
| 154 | |||
| 155 | /* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ | ||
| 156 | void pm_init(void); | ||
| 157 | void pm_term(void); | ||
| 158 | |||
| 159 | /* defined by portMidi, used by pmwinmm */ | ||
| 160 | PmError none_write_short(PmInternal *midi, PmEvent *buffer); | ||
| 161 | PmError none_write_byte(PmInternal *midi, unsigned char byte, | ||
| 162 | PmTimestamp timestamp); | ||
| 163 | PmTimestamp none_synchronize(PmInternal *midi); | ||
| 164 | |||
| 165 | PmError pm_fail_fn(PmInternal *midi); | ||
| 166 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); | ||
| 167 | PmError pm_success_fn(PmInternal *midi); | ||
| 168 | PmError pm_add_interf(char *interf, pm_create_fn create_fn, | ||
| 169 | pm_delete_fn delete_fn); | ||
| 170 | PmError pm_add_device(char *interf, const char *name, int is_input, | ||
| 171 | int is_virtual, void *descriptor, pm_fns_type dictionary); | ||
| 172 | void pm_undo_add_device(int id); | ||
| 173 | uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, | ||
| 174 | PmTimestamp timestamp); | ||
| 175 | void 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 | |||
| 20 | typedef struct { | ||
| 21 | long head; | ||
| 22 | long tail; | ||
| 23 | long len; | ||
| 24 | long overflow; | ||
| 25 | int32_t msg_size; /* number of int32_t in a message including extra word */ | ||
| 26 | int32_t peek_overflow; | ||
| 27 | int32_t *buffer; | ||
| 28 | int32_t *peek; | ||
| 29 | int32_t peek_flag; | ||
| 30 | } PmQueueRep; | ||
| 31 | |||
| 32 | |||
| 33 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) | ||
| 34 | { | ||
| 35 | int32_t int32s_per_msg = | ||
| 36 | (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & | ||
| 37 | ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); | ||
| 38 | PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); | ||
| 39 | if (!queue) /* memory allocation failed */ | ||
| 40 | return NULL; | ||
| 41 | |||
| 42 | /* need extra word per message for non-zero encoding */ | ||
| 43 | queue->len = num_msgs * (int32s_per_msg + 1); | ||
| 44 | queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); | ||
| 45 | bzero(queue->buffer, queue->len * sizeof(int32_t)); | ||
| 46 | if (!queue->buffer) { | ||
| 47 | pm_free(queue); | ||
| 48 | return NULL; | ||
| 49 | } else { /* allocate the "peek" buffer */ | ||
| 50 | queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); | ||
| 51 | if (!queue->peek) { | ||
| 52 | /* free everything allocated so far and return */ | ||
| 53 | pm_free(queue->buffer); | ||
| 54 | pm_free(queue); | ||
| 55 | return NULL; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | bzero(queue->buffer, queue->len * sizeof(int32_t)); | ||
| 59 | queue->head = 0; | ||
| 60 | queue->tail = 0; | ||
| 61 | /* msg_size is in words */ | ||
| 62 | queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ | ||
| 63 | queue->overflow = FALSE; | ||
| 64 | queue->peek_overflow = FALSE; | ||
| 65 | queue->peek_flag = FALSE; | ||
| 66 | return queue; | ||
| 67 | } | ||
| 68 | |||
| 69 | |||
| 70 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) | ||
| 71 | { | ||
| 72 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 73 | |||
| 74 | /* arg checking */ | ||
| 75 | if (!queue || !queue->buffer || !queue->peek) | ||
| 76 | return pmBadPtr; | ||
| 77 | |||
| 78 | pm_free(queue->peek); | ||
| 79 | pm_free(queue->buffer); | ||
| 80 | pm_free(queue); | ||
| 81 | return pmNoError; | ||
| 82 | } | ||
| 83 | |||
| 84 | |||
| 85 | PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) | ||
| 86 | { | ||
| 87 | long head; | ||
| 88 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 89 | int i; | ||
| 90 | int32_t *msg_as_int32 = (int32_t *) msg; | ||
| 91 | |||
| 92 | /* arg checking */ | ||
| 93 | if (!queue) | ||
| 94 | return pmBadPtr; | ||
| 95 | /* a previous peek operation encountered an overflow, but the overflow | ||
| 96 | * has not yet been reported to client, so do it now. No message is | ||
| 97 | * returned, but on the next call, we will return the peek buffer. | ||
| 98 | */ | ||
| 99 | if (queue->peek_overflow) { | ||
| 100 | queue->peek_overflow = FALSE; | ||
| 101 | return pmBufferOverflow; | ||
| 102 | } | ||
| 103 | if (queue->peek_flag) { | ||
| 104 | memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); | ||
| 105 | queue->peek_flag = FALSE; | ||
| 106 | return pmGotData; | ||
| 107 | } | ||
| 108 | |||
| 109 | head = queue->head; | ||
| 110 | /* if writer overflows, it writes queue->overflow = tail+1 so that | ||
| 111 | * when the reader gets to that position in the buffer, it can | ||
| 112 | * return the overflow condition to the reader. The problem is that | ||
| 113 | * at overflow, things have wrapped around, so tail == head, and the | ||
| 114 | * reader will detect overflow immediately instead of waiting until | ||
| 115 | * it reads everything in the buffer, wrapping around again to the | ||
| 116 | * point where tail == head. So the condition also checks that | ||
| 117 | * queue->buffer[head] is zero -- if so, then the buffer is now | ||
| 118 | * empty, and we're at the point in the msg stream where overflow | ||
| 119 | * occurred. It's time to signal overflow to the reader. If | ||
| 120 | * queue->buffer[head] is non-zero, there's a message there and we | ||
| 121 | * should read all the way around the buffer before signalling overflow. | ||
| 122 | * There is a write-order dependency here, but to fail, the overflow | ||
| 123 | * field would have to be written while an entire buffer full of | ||
| 124 | * writes are still pending. I'm assuming out-of-order writes are | ||
| 125 | * possible, but not that many. | ||
| 126 | */ | ||
| 127 | if (queue->overflow == head + 1 && !queue->buffer[head]) { | ||
| 128 | queue->overflow = 0; /* non-overflow condition */ | ||
| 129 | return pmBufferOverflow; | ||
| 130 | } | ||
| 131 | |||
| 132 | /* test to see if there is data in the queue -- test from back | ||
| 133 | * to front so if writer is simultaneously writing, we don't | ||
| 134 | * waste time discovering the write is not finished | ||
| 135 | */ | ||
| 136 | for (i = queue->msg_size - 1; i >= 0; i--) { | ||
| 137 | if (!queue->buffer[head + i]) { | ||
| 138 | return pmNoData; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | memcpy(msg, (char *) &queue->buffer[head + 1], | ||
| 142 | sizeof(int32_t) * (queue->msg_size - 1)); | ||
| 143 | /* fix up zeros */ | ||
| 144 | i = queue->buffer[head]; | ||
| 145 | while (i < queue->msg_size) { | ||
| 146 | int32_t j; | ||
| 147 | i--; /* msg does not have extra word so shift down */ | ||
| 148 | j = msg_as_int32[i]; | ||
| 149 | msg_as_int32[i] = 0; | ||
| 150 | i = j; | ||
| 151 | } | ||
| 152 | /* signal that data has been removed by zeroing: */ | ||
| 153 | bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); | ||
| 154 | |||
| 155 | /* update head */ | ||
| 156 | head += queue->msg_size; | ||
| 157 | if (head == queue->len) head = 0; | ||
| 158 | queue->head = head; | ||
| 159 | return pmGotData; /* success */ | ||
| 160 | } | ||
| 161 | |||
| 162 | |||
| 163 | |||
| 164 | PMEXPORT PmError Pm_SetOverflow(PmQueue *q) | ||
| 165 | { | ||
| 166 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 167 | long tail; | ||
| 168 | /* arg checking */ | ||
| 169 | if (!queue) | ||
| 170 | return pmBadPtr; | ||
| 171 | /* no more enqueue until receiver acknowledges overflow */ | ||
| 172 | if (queue->overflow) return pmBufferOverflow; | ||
| 173 | tail = queue->tail; | ||
| 174 | queue->overflow = tail + 1; | ||
| 175 | return pmBufferOverflow; | ||
| 176 | } | ||
| 177 | |||
| 178 | |||
| 179 | PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) | ||
| 180 | { | ||
| 181 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 182 | long tail; | ||
| 183 | int i; | ||
| 184 | int32_t *src = (int32_t *) msg; | ||
| 185 | int32_t *ptr; | ||
| 186 | int32_t *dest; | ||
| 187 | int rslt; | ||
| 188 | if (!queue) | ||
| 189 | return pmBadPtr; | ||
| 190 | /* no more enqueue until receiver acknowledges overflow */ | ||
| 191 | if (queue->overflow) return pmBufferOverflow; | ||
| 192 | rslt = Pm_QueueFull(q); | ||
| 193 | /* already checked above: if (rslt == pmBadPtr) return rslt; */ | ||
| 194 | tail = queue->tail; | ||
| 195 | if (rslt) { | ||
| 196 | queue->overflow = tail + 1; | ||
| 197 | return pmBufferOverflow; | ||
| 198 | } | ||
| 199 | |||
| 200 | /* queue is has room for message, and overflow flag is cleared */ | ||
| 201 | ptr = &queue->buffer[tail]; | ||
| 202 | dest = ptr + 1; | ||
| 203 | for (i = 1; i < queue->msg_size; i++) { | ||
| 204 | int32_t j = src[i - 1]; | ||
| 205 | if (!j) { | ||
| 206 | *ptr = i; | ||
| 207 | ptr = dest; | ||
| 208 | } else { | ||
| 209 | *dest = j; | ||
| 210 | } | ||
| 211 | dest++; | ||
| 212 | } | ||
| 213 | *ptr = i; | ||
| 214 | tail += queue->msg_size; | ||
| 215 | if (tail == queue->len) tail = 0; | ||
| 216 | queue->tail = tail; | ||
| 217 | return pmNoError; | ||
| 218 | } | ||
| 219 | |||
| 220 | |||
| 221 | PMEXPORT int Pm_QueueEmpty(PmQueue *q) | ||
| 222 | { | ||
| 223 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 224 | return (!queue) || /* null pointer -> return "empty" */ | ||
| 225 | (queue->buffer[queue->head] == 0 && !queue->peek_flag); | ||
| 226 | } | ||
| 227 | |||
| 228 | |||
| 229 | PMEXPORT int Pm_QueueFull(PmQueue *q) | ||
| 230 | { | ||
| 231 | long tail; | ||
| 232 | int i; | ||
| 233 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 234 | /* arg checking */ | ||
| 235 | if (!queue) | ||
| 236 | return pmBadPtr; | ||
| 237 | tail = queue->tail; | ||
| 238 | /* test to see if there is space in the queue */ | ||
| 239 | for (i = 0; i < queue->msg_size; i++) { | ||
| 240 | if (queue->buffer[tail + i]) { | ||
| 241 | return TRUE; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | return FALSE; | ||
| 245 | } | ||
| 246 | |||
| 247 | |||
| 248 | PMEXPORT void *Pm_QueuePeek(PmQueue *q) | ||
| 249 | { | ||
| 250 | PmError rslt; | ||
| 251 | int32_t temp; | ||
| 252 | PmQueueRep *queue = (PmQueueRep *) q; | ||
| 253 | /* arg checking */ | ||
| 254 | if (!queue) | ||
| 255 | return NULL; | ||
| 256 | |||
| 257 | if (queue->peek_flag) { | ||
| 258 | return queue->peek; | ||
| 259 | } | ||
| 260 | /* this is ugly: if peek_overflow is set, then Pm_Dequeue() | ||
| 261 | * returns immediately with pmBufferOverflow, but here, we | ||
| 262 | * want Pm_Dequeue() to really check for data. If data is | ||
| 263 | * there, we can return it | ||
| 264 | */ | ||
| 265 | temp = queue->peek_overflow; | ||
| 266 | queue->peek_overflow = FALSE; | ||
| 267 | rslt = Pm_Dequeue(q, queue->peek); | ||
| 268 | queue->peek_overflow = temp; | ||
| 269 | |||
| 270 | if (rslt == 1) { | ||
| 271 | queue->peek_flag = TRUE; | ||
| 272 | return queue->peek; | ||
| 273 | } else if (rslt == pmBufferOverflow) { | ||
| 274 | /* when overflow is indicated, the queue is empty and the | ||
| 275 | * first message that was dropped by Enqueue (signalling | ||
| 276 | * pmBufferOverflow to its caller) would have been the next | ||
| 277 | * message in the queue. Pm_QueuePeek will return NULL, but | ||
| 278 | * remember that an overflow occurred. (see Pm_Dequeue) | ||
| 279 | */ | ||
| 280 | queue->peek_overflow = TRUE; | ||
| 281 | } | ||
| 282 | return NULL; | ||
| 283 | } | ||
| 284 | |||
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 | ||
| 16 | extern "C" { | ||
| 17 | #endif /* __cplusplus */ | ||
| 18 | |||
| 19 | /** @defgroup grp_pmutil Lock-free Queue | ||
| 20 | @{ | ||
| 21 | */ | ||
| 22 | |||
| 23 | /** The queue representation is opaque. Declare a queue as PmQueue * */ | ||
| 24 | typedef void PmQueue; | ||
| 25 | |||
| 26 | /** create a single-reader, single-writer queue. | ||
| 27 | |||
| 28 | @param num_msgs the number of messages the queue can hold | ||
| 29 | |||
| 30 | @param 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 | */ | ||
| 64 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); | ||
| 65 | |||
| 66 | /** destroy a queue and free its storage. | ||
| 67 | |||
| 68 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 69 | |||
| 70 | @return pmNoError or an error code. | ||
| 71 | |||
| 72 | Uses #pm_free(). | ||
| 73 | |||
| 74 | */ | ||
| 75 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); | ||
| 76 | |||
| 77 | /** remove one message from the queue, copying it into \p msg. | ||
| 78 | |||
| 79 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 80 | |||
| 81 | @param msg address to which the message, if any, is copied. | ||
| 82 | |||
| 83 | @return 1 if successful, and 0 if the queue is empty. Returns | ||
| 84 | #pmBufferOverflow if what would have been the next thing in the | ||
| 85 | queue was dropped due to overflow. (So when overflow occurs, the | ||
| 86 | receiver can receive a queue full of messages before getting the | ||
| 87 | overflow report. This protocol ensures that the reader will be | ||
| 88 | notified when data is lost due to overflow. | ||
| 89 | */ | ||
| 90 | PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); | ||
| 91 | |||
| 92 | /** insert one message into the queue, copying it from \p msg. | ||
| 93 | |||
| 94 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 95 | |||
| 96 | @param msg address of the message to be enqueued. | ||
| 97 | |||
| 98 | @return #pmNoError if successful and #pmBufferOverflow if the | ||
| 99 | queue was already full. If #pmBufferOverflow is returned, the | ||
| 100 | overflow flag is set. | ||
| 101 | */ | ||
| 102 | PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); | ||
| 103 | |||
| 104 | /** test if the queue is full. | ||
| 105 | |||
| 106 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 107 | |||
| 108 | @return non-zero iff the queue is empty, and @pmBadPtr if \p queue | ||
| 109 | is NULL. | ||
| 110 | |||
| 111 | The full condition may change immediately because a parallel | ||
| 112 | dequeue operation could be in progress. The result is | ||
| 113 | pessimistic: if it returns false (zero) to the single writer, then | ||
| 114 | #Pm_Enqueue() is guaranteed to succeed. | ||
| 115 | */ | ||
| 116 | PMEXPORT int Pm_QueueFull(PmQueue *queue); | ||
| 117 | |||
| 118 | /** test if the queue is empty. | ||
| 119 | |||
| 120 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 121 | |||
| 122 | @return zero iff the queue is either empty or NULL. | ||
| 123 | |||
| 124 | The empty condition may change immediately because a parallel | ||
| 125 | enqueue operation could be in progress. Furthermore, the | ||
| 126 | result is optimistic: it may say false, when due to | ||
| 127 | out-of-order writes, the full message has not arrived. Therefore, | ||
| 128 | #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns | ||
| 129 | false. | ||
| 130 | */ | ||
| 131 | PMEXPORT int Pm_QueueEmpty(PmQueue *queue); | ||
| 132 | |||
| 133 | /** get a pointer to the item at the head of the queue. | ||
| 134 | |||
| 135 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 136 | |||
| 137 | @result a pointer to the head message or NULL if the queue is empty. | ||
| 138 | |||
| 139 | The message is not removed from the queue. #Pm_QueuePeek() will | ||
| 140 | not indicate when an overflow occurs. If you want to get and check | ||
| 141 | #pmBufferOverflow messages, use the return value of | ||
| 142 | #Pm_QueuePeek() *only* as an indication that you should call | ||
| 143 | #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue() | ||
| 144 | would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL, | ||
| 145 | but internally clear the #pmBufferOverflow flag, enabling | ||
| 146 | #Pm_Enqueue() to resume enqueuing messages. A subsequent call to | ||
| 147 | #Pm_QueuePeek() will return a pointer to the first message *after* | ||
| 148 | the overflow. Using this as an indication to call #Pm_Dequeue(), | ||
| 149 | the first call to #Pm_Dequeue() will return #pmBufferOverflow. The | ||
| 150 | second call will return success, copying the same message pointed | ||
| 151 | to by the previous #Pm_QueuePeek(). | ||
| 152 | |||
| 153 | When to use #Pm_QueuePeek(): (1) when you need to look at the message | ||
| 154 | data to decide who should be called to receive it. (2) when you need | ||
| 155 | to know a message is ready but cannot accept the message. | ||
| 156 | |||
| 157 | Note that #Pm_QueuePeek() is not a fast check, so if possible, you | ||
| 158 | might as well just call #Pm_Dequeue() and accept the data if it is there. | ||
| 159 | */ | ||
| 160 | PMEXPORT void *Pm_QueuePeek(PmQueue *queue); | ||
| 161 | |||
| 162 | /** allows the writer (enqueuer) to signal an overflow | ||
| 163 | condition to the reader (dequeuer). | ||
| 164 | |||
| 165 | @param queue a queue created by #Pm_QueueCreate(). | ||
| 166 | |||
| 167 | @return #pmNoError if overflow is set, or #pmBadPtr if queue is | ||
| 168 | NULL, or #pmBufferOverflow if buffer is already in an overflow | ||
| 169 | state. | ||
| 170 | |||
| 171 | E.g., when transfering data from the OS to an application, if the | ||
| 172 | OS indicates a buffer overrun, #Pm_SetOverflow() can be used to | ||
| 173 | insure that the reader receives a #pmBufferOverflow result from | ||
| 174 | #Pm_Dequeue(). | ||
| 175 | */ | ||
| 176 | PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); | ||
| 177 | |||
| 178 | /** @} */ | ||
| 179 | |||
| 180 | #ifdef __cplusplus | ||
| 181 | } | ||
| 182 | #endif /* __cplusplus */ | ||
| 183 | |||
| 184 | #endif // PORTMIDI_PMUTIL_H | ||
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 | */ | ||
| 41 | PmDeviceID pm_default_input_device_id = -1; | ||
| 42 | PmDeviceID 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 | */ | ||
| 47 | int pm_initialized = FALSE; | ||
| 48 | |||
| 49 | int 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 | */ | ||
| 57 | int pm_check_errors = TRUE; | ||
| 58 | |||
| 59 | char 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 | |||
| 67 | static 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 | |||
| 76 | static 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 | |||
| 99 | int 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 | ==================================================================== | ||
| 121 | system implementation of portmidi interface | ||
| 122 | ==================================================================== | ||
| 123 | */ | ||
| 124 | |||
| 125 | int pm_descriptor_max = 0; | ||
| 126 | int pm_descriptor_len = 0; | ||
| 127 | descriptor_type pm_descriptors = NULL; | ||
| 128 | |||
| 129 | /* interface pm_descriptors are simple: an array of string/fnptr pairs: */ | ||
| 130 | #define MAX_INTERF 4 | ||
| 131 | static struct { | ||
| 132 | const char *interf; | ||
| 133 | pm_create_fn create_fn; | ||
| 134 | pm_delete_fn delete_fn; | ||
| 135 | } pm_interf_list[MAX_INTERF]; | ||
| 136 | |||
| 137 | static 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 | */ | ||
| 152 | PmError 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 | |||
| 166 | PmError 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 | */ | ||
| 210 | PmError 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 | */ | ||
| 311 | void 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 | */ | ||
| 328 | int 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 | ==================================================================== | ||
| 358 | portmidi implementation | ||
| 359 | ==================================================================== | ||
| 360 | */ | ||
| 361 | |||
| 362 | PMEXPORT 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 | |||
| 370 | PMEXPORT 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 */ | ||
| 380 | PmError pm_success_fn(PmInternal *midi) | ||
| 381 | { | ||
| 382 | return pmNoError; | ||
| 383 | } | ||
| 384 | |||
| 385 | /* none_write -- returns an error if called */ | ||
| 386 | PmError none_write_short(PmInternal *midi, PmEvent *buffer) | ||
| 387 | { | ||
| 388 | return pmBadPtr; | ||
| 389 | } | ||
| 390 | |||
| 391 | /* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ | ||
| 392 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) | ||
| 393 | { | ||
| 394 | return pmBadPtr; | ||
| 395 | } | ||
| 396 | |||
| 397 | PmError 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 */ | ||
| 404 | PmError pm_fail_fn(PmInternal *midi) | ||
| 405 | { | ||
| 406 | return pmBadPtr; | ||
| 407 | } | ||
| 408 | |||
| 409 | static PmError none_open(PmInternal *midi, void *driverInfo) | ||
| 410 | { | ||
| 411 | return pmBadPtr; | ||
| 412 | } | ||
| 413 | |||
| 414 | static unsigned int none_check_host_error(PmInternal * midi) | ||
| 415 | { | ||
| 416 | return FALSE; | ||
| 417 | } | ||
| 418 | |||
| 419 | PmTimestamp 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 | |||
| 427 | pm_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 | |||
| 443 | PMEXPORT 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 | */ | ||
| 503 | PMEXPORT 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 | |||
| 522 | PMEXPORT 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 | |||
| 533 | PMEXPORT 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 | |||
| 547 | PMEXPORT 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 | */ | ||
| 575 | PMEXPORT 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 | |||
| 615 | PMEXPORT 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 | */ | ||
| 643 | static 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 | |||
| 655 | PMEXPORT 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); | ||
| 780 | pm_write_error: | ||
| 781 | if (err == pmHostError) { | ||
| 782 | midi->dictionary->check_host_error(midi); | ||
| 783 | } | ||
| 784 | error_exit: | ||
| 785 | return pm_errmsg(err); | ||
| 786 | } | ||
| 787 | |||
| 788 | |||
| 789 | PMEXPORT 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 | |||
| 800 | PMEXPORT 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 | } | ||
| 864 | end_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 | |||
| 878 | PmError 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 | |||
| 944 | PMEXPORT 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 | } | ||
| 984 | error_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 | |||
| 993 | PMEXPORT 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 | } | ||
| 1035 | error_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 | |||
| 1043 | static 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 | |||
| 1081 | error_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 | |||
| 1090 | PMEXPORT 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 | |||
| 1097 | PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf, | ||
| 1098 | void *deviceInfo) | ||
| 1099 | { | ||
| 1100 | return create_virtual_device(name, interf, deviceInfo, FALSE); | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | PmError 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 | |||
| 1128 | PMEXPORT 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 | |||
| 1142 | PMEXPORT 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 | |||
| 1158 | PMEXPORT 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); | ||
| 1184 | error_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 | |||
| 1191 | PmError 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 | |||
| 1206 | PMEXPORT 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 | |||
| 1285 | static 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 | */ | ||
| 1318 | void 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 | */ | ||
| 1363 | unsigned 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 | ||
| 5 | extern "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 | |||
| 101 | typedef 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 | */ | ||
| 161 | PMEXPORT PmError Pm_Initialize(void); | ||
| 162 | |||
| 163 | /** | ||
| 164 | Pm_Terminate() is the library termination function - call this after | ||
| 165 | using the library. | ||
| 166 | */ | ||
| 167 | PMEXPORT PmError Pm_Terminate(void); | ||
| 168 | |||
| 169 | /** Represents an open MIDI device. */ | ||
| 170 | typedef 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 | */ | ||
| 185 | PMEXPORT 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 | */ | ||
| 192 | PMEXPORT 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 | */ | ||
| 198 | PMEXPORT 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 | */ | ||
| 208 | typedef 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 | ||
| 220 | typedef 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 | |||
| 236 | enum 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 | */ | ||
| 262 | typedef 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. */ | ||
| 273 | PMEXPORT 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 | */ | ||
| 311 | PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void); | ||
| 312 | |||
| 313 | /** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */ | ||
| 314 | PMEXPORT 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 | */ | ||
| 332 | PMEXPORT 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 | */ | ||
| 338 | typedef int32_t PmTimestamp; | ||
| 339 | typedef 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 | */ | ||
| 359 | PMEXPORT 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 | */ | ||
| 403 | PMEXPORT 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 | */ | ||
| 507 | PMEXPORT 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 | */ | ||
| 541 | PMEXPORT 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 | */ | ||
| 571 | PMEXPORT 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 | */ | ||
| 591 | PMEXPORT 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 | */ | ||
| 666 | PMEXPORT 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 | */ | ||
| 692 | PMEXPORT 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 | */ | ||
| 708 | PMEXPORT 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 | */ | ||
| 726 | PMEXPORT 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 | */ | ||
| 753 | PMEXPORT 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 | |||
| 770 | typedef 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 | */ | ||
| 841 | typedef 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 | */ | ||
| 879 | PMEXPORT 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 | */ | ||
| 897 | PMEXPORT 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 | */ | ||
| 931 | PMEXPORT 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 | */ | ||
| 949 | PMEXPORT 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 | */ | ||
| 965 | PMEXPORT 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 | |||
| 15 | namespace { | ||
| 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 | |||
| 415 | extern "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 | |||
| 3 | find_package(Java) | ||
| 4 | message(STATUS "Java_JAVA_EXECUTABLE is " ${Java_JAVA_EXECUTABLE}) | ||
| 5 | |||
| 6 | # Build pmjni | ||
| 7 | # this CMakeLists.txt is only loaded if BUILD_JAVA_NATIVE_INTERFACE | ||
| 8 | # This jni library includes portmidi sources to give just | ||
| 9 | # one library for JPortMidi users to manage rather than two. | ||
| 10 | if(UNIX) | ||
| 11 | include(FindJNI) | ||
| 12 | # message(STATUS "JAVA_JVM_LIB_PATH is " ${JAVA_JVM_LIB_PATH}) | ||
| 13 | # message(STATUS "JAVA_INCLUDE_PATH is " ${JAVA_INCLUDE_PATH}) | ||
| 14 | # note: should use JAVA_JVM_LIB_PATH, but it is not set properly | ||
| 15 | # note: user might need to set JAVA_INCLUDE_PATH manually | ||
| 16 | # | ||
| 17 | # this will probably break on BSD and other Unix systems; the fix | ||
| 18 | # depends on whether FindJNI can find Java or not. If yes, then | ||
| 19 | # we should try to rely on automatically set JAVA_INCLUDE_PATH and | ||
| 20 | # JAVA_INCLUDE_PATH2; if no, then we need to make both JAVA_INCLUDE_PATH | ||
| 21 | # and JAVA_INCLUDE_PATH2 set by user (will need clear documentation | ||
| 22 | # because JAVA_INCLUDE_PATH2 is pretty obscure) | ||
| 23 | set(JAVA_INCLUDE_PATH ${JAVA_INCLUDE_PATH-UNKNOWN} | ||
| 24 | CACHE STRING "where to find Java SDK include directory") | ||
| 25 | # libjvm.so is found relative to JAVA_INCLUDE_PATH: | ||
| 26 | if (HAIKU) | ||
| 27 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/haiku) | ||
| 28 | else() | ||
| 29 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/linux) | ||
| 30 | endif() | ||
| 31 | elseif(WIN32) | ||
| 32 | include(FindJNI) | ||
| 33 | # note: should use JAVA_JVM_LIB_PATH, but it is not set properly | ||
| 34 | set(JAVAVM_LIB ${JAVA_INCLUDE_PATH}/../lib/jvm.lib) | ||
| 35 | |||
| 36 | set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) | ||
| 37 | # message(STATUS "JAVA_INCLUDE_PATHS: " ${JAVA_INCLUDE_PATHS}) | ||
| 38 | # message(STATUS "JAVAVM_LIB: " ${JAVAVM_LIB}) | ||
| 39 | endif() | ||
| 40 | |||
| 41 | add_library(pmjni SHARED pmjni/pmjni.c) | ||
| 42 | target_sources(pmjni PRIVATE ${PM_LIB_PUBLIC_SRC} ${PM_LIB_PRIVATE_SRC}) | ||
| 43 | message(STATUS "Java paths ${JAVA_INCLUDE_PATHS}") | ||
| 44 | # message(STATUS "Java pmjni src: pmjni/pmjni.c ${PM_LIB_SHARED_SRC} " | ||
| 45 | # "${PM_LIB_PRIVATE_SRC}") | ||
| 46 | target_include_directories(pmjni PUBLIC ${JAVA_INCLUDE_PATHS}) | ||
| 47 | target_link_libraries(pmjni ${PM_NEEDED_LIBS}) | ||
| 48 | set_target_properties(pmjni PROPERTIES | ||
| 49 | VERSION ${LIBRARY_VERSION} | ||
| 50 | SOVERSION ${LIBRARY_SOVERSION} | ||
| 51 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 52 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 53 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" | ||
| 54 | EXECUTABLE_EXTENSION "jnilib" | ||
| 55 | MACOSX_RPATH ON) | ||
| 56 | |||
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 @@ | |||
| 1 | README.txt | ||
| 2 | Roger B. Dannenberg | ||
| 3 | 16 Jun 2009 | ||
| 4 | updated 2021 | ||
| 5 | |||
| 6 | This directory implements a JNI library so that Java programs can use | ||
| 7 | the PortMidi API. This was mainly created to implement PmDefaults, a | ||
| 8 | program to set default input and output devices for PortMidi | ||
| 9 | applications. Because it is rarely used, PmDefaults was dropped from | ||
| 10 | PortMidi starting with v3. I recommend you implement per-application | ||
| 11 | preferences and store default PortMidi device numbers for input and | ||
| 12 | output there. (Or better yet, store device *names* since numbers can | ||
| 13 | change if you plug in or remove USB devices.) | ||
| 14 | |||
| 15 | Even without PmDefaults, a PortMidi API for Java is probably an | ||
| 16 | improvement over other Java libraries, but there is very little MIDI | ||
| 17 | development in Java, so I have not maintained this API. The only thing | ||
| 18 | probably seriously wrong now is an interface to the | ||
| 19 | Pm_CreateVirtualInput and Pm_CreateVirtualOutput functions, which are | ||
| 20 | new additions. | ||
| 21 | |||
| 22 | I will leave the code here, and if there is a demand, please either | ||
| 23 | update it or let your needs be known. Perhaps I or someone can help. | ||
| 24 | |||
| 25 | ================================================================== | ||
| 26 | |||
| 27 | BUILDING Java EXTERNAL LIBRARY | ||
| 28 | |||
| 29 | You must have a JDK installed (Java development kit including javac | ||
| 30 | (the Java compiler), jni.h, etc. | ||
| 31 | |||
| 32 | Test java on the command line, e.g., type: javac -version | ||
| 33 | |||
| 34 | Enable these options in the main CMakeLists.txt file (run CMake | ||
| 35 | from your top-level repository directory): | ||
| 36 | BUILD_JAVA_NATIVE_INTERFACE | ||
| 37 | In my Ubuntu linux with jdk-15, ccmake was unable to find my JDK, so | ||
| 38 | I have to manually set CMake variables as follows (type 't' to see | ||
| 39 | these in ccmake): | ||
| 40 | JAVA_AWT_INCLUDE_PATH /usr/lib/jvm/jdk-15/include | ||
| 41 | JAVA_AWT_LIBRARY /usr/lib/jvm/jdk-15/lib | ||
| 42 | JAVA_INCLUDE_PATH /usr/lib/jvm/jdk-15/include | ||
| 43 | JAVA_INCLUDE_PATH2 /usr/lib/jvm/jdk-15/include | ||
| 44 | JAVA_JVM_LIBRARY /usr/lib/jvm/jdk-15/lib | ||
| 45 | Of course, your paths may differ. | ||
| 46 | |||
| 47 | |||
| 48 | ---- old implementation notes ---- | ||
| 49 | |||
| 50 | For Windows, we use the free software JavaExe.exe. The copy here was | ||
| 51 | downloaded from | ||
| 52 | |||
| 53 | http://software.techrepublic.com.com/abstract.aspx?kw=javaexe&docid=767485 | ||
| 54 | |||
| 55 | I found this page by visiting http://software.techrepublic.com.com and | ||
| 56 | searching in the "Software" category for "JavaExe" | ||
| 57 | |||
| 58 | JavaExe works by placing the JavaExe.exe file in the directory with the | ||
| 59 | Java application jar file and then *renaming* JavaExe.exe to the name | ||
| 60 | of the jar file, but keeping the .exe extension. (See make.bat for this | ||
| 61 | step.) Documentation for JavaExe can be obtained by downloading the | ||
| 62 | whole program from the URL(s) above. | ||
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 @@ | |||
| 1 | package 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 | |||
| 13 | import jportmidi.*; | ||
| 14 | import jportmidi.JPortMidiApi.*; | ||
| 15 | |||
| 16 | public 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 @@ | |||
| 1 | package jportmidi; | ||
| 2 | |||
| 3 | public class JPortMidiApi { | ||
| 4 | public static class PortMidiStream { | ||
| 5 | private long address; | ||
| 6 | } | ||
| 7 | public static class PmEvent { | ||
| 8 | public int message; | ||
| 9 | public int timestamp; | ||
| 10 | } | ||
| 11 | |||
| 12 | // PmError bindings | ||
| 13 | public final int pmNoError = 0; | ||
| 14 | public final int pmNoData = 0; | ||
| 15 | public final int pmGotData = -1; | ||
| 16 | public final int pmHostError = -10000; | ||
| 17 | public final int pmInvalidDeviceId = -9999; | ||
| 18 | public final int pmInsufficientMemory = -9998; | ||
| 19 | public final int pmBufferTooSmall = -9997; | ||
| 20 | public final int pmBufferOverflow = -9996; | ||
| 21 | public final int pmBadPtr = -9995; | ||
| 22 | public final int pmBadData = -9994; | ||
| 23 | public final int pmInternalError = -9993; | ||
| 24 | public final int pmBufferMaxSize = -9992; | ||
| 25 | |||
| 26 | static public native int Pm_Initialize(); | ||
| 27 | static public native int Pm_Terminate(); | ||
| 28 | static public native int Pm_HasHostError(PortMidiStream stream); | ||
| 29 | static public native String Pm_GetErrorText(int errnum); | ||
| 30 | static public native String Pm_GetHostErrorText(); | ||
| 31 | final int pmNoDevice = -1; | ||
| 32 | static public native int Pm_CountDevices(); | ||
| 33 | static public native int Pm_GetDefaultInputDeviceID(); | ||
| 34 | static public native int Pm_GetDefaultOutputDeviceID(); | ||
| 35 | static public native String Pm_GetDeviceInterf(int i); | ||
| 36 | static public native String Pm_GetDeviceName(int i); | ||
| 37 | static public native boolean Pm_GetDeviceInput(int i); | ||
| 38 | static public native boolean Pm_GetDeviceOutput(int i); | ||
| 39 | static public native int Pm_OpenInput(PortMidiStream stream, | ||
| 40 | int inputDevice, | ||
| 41 | String inputDriverInfo, | ||
| 42 | int bufferSize); | ||
| 43 | static public native int Pm_OpenOutput(PortMidiStream stream, | ||
| 44 | int outputDevice, | ||
| 45 | String outnputDriverInfo, | ||
| 46 | int bufferSize, | ||
| 47 | int latency); | ||
| 48 | final static public int PM_FILT_ACTIVE = (1 << 0x0E); | ||
| 49 | final static public int PM_FILT_SYSEX = (1 << 0x00); | ||
| 50 | final static public int PM_FILT_CLOCK = (1 << 0x08); | ||
| 51 | final static public int PM_FILT_PLAY = | ||
| 52 | (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B); | ||
| 53 | final static public int PM_FILT_TICK = (1 << 0x09); | ||
| 54 | final static public int PM_FILT_FD = (1 << 0x0D); | ||
| 55 | final static public int PM_FILT_UNDEFINED = PM_FILT_FD; | ||
| 56 | final static public int PM_FILT_RESET = (1 << 0x0F); | ||
| 57 | final static public int PM_FILT_REALTIME = | ||
| 58 | PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK; | ||
| 59 | final static public int PM_FILT_NOTE = (1 << 0x19) | (1 << 0x18); | ||
| 60 | final static public int PM_FILT_CHANNEL_AFTERTOUCH = (1 << 0x1D); | ||
| 61 | final static public int PM_FILT_POLY_AFTERTOUCH = (1 << 0x1A); | ||
| 62 | final static public int PM_FILT_AFTERTOUCH = | ||
| 63 | (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH); | ||
| 64 | final static public int PM_FILT_PROGRAM = (1 << 0x1C); | ||
| 65 | final static public int PM_FILT_CONTROL = (1 << 0x1B); | ||
| 66 | final static public int PM_FILT_PITCHBEND = (1 << 0x1E); | ||
| 67 | final static public int PM_FILT_MTC = (1 << 0x01); | ||
| 68 | final static public int PM_FILT_SONG_POSITION = (1 << 0x02); | ||
| 69 | final static public int PM_FILT_SONG_SELECT = (1 << 0x03); | ||
| 70 | final static public int PM_FILT_TUNE = (1 << 0x06); | ||
| 71 | final static public int PM_FILT_SYSTEMCOMMON = | ||
| 72 | (PM_FILT_MTC | PM_FILT_SONG_POSITION | | ||
| 73 | PM_FILT_SONG_SELECT | PM_FILT_TUNE); | ||
| 74 | static public native int Pm_SetFilter(PortMidiStream stream, int filters); | ||
| 75 | static public int Pm_Channel(int channel) { return 1 << channel; } | ||
| 76 | final static public native int Pm_SetChannelMask(PortMidiStream stream, | ||
| 77 | int mask); | ||
| 78 | final static public native int Pm_Abort(PortMidiStream stream); | ||
| 79 | final static public native int Pm_Close(PortMidiStream stream); | ||
| 80 | static public int Pm_Message(int status, int data1, int data2) { | ||
| 81 | return (((data2 << 16) & 0xFF0000) | | ||
| 82 | ((data1 << 8) & 0xFF00) | | ||
| 83 | (status & 0xFF)); | ||
| 84 | } | ||
| 85 | static public int Pm_MessageStatus(int msg) { | ||
| 86 | return msg & 0xFF; | ||
| 87 | } | ||
| 88 | static public int Pm_MessageData1(int msg) { | ||
| 89 | return (msg >> 8) & 0xFF; | ||
| 90 | } | ||
| 91 | static public int Pm_MessageData2(int msg) { | ||
| 92 | return (msg >> 16) & 0xFF; | ||
| 93 | } | ||
| 94 | // only supports reading one buffer at a time | ||
| 95 | static public native int Pm_Read(PortMidiStream stream, PmEvent buffer); | ||
| 96 | static public native int Pm_Poll(PortMidiStream stream); | ||
| 97 | // only supports writing one buffer at a time | ||
| 98 | static public native int Pm_Write(PortMidiStream stream, PmEvent buffer); | ||
| 99 | static public native int Pm_WriteShort(PortMidiStream stream, | ||
| 100 | int when, int msg); | ||
| 101 | static public native int Pm_WriteSysEx(PortMidiStream stream, | ||
| 102 | int when, byte msg[]); | ||
| 103 | |||
| 104 | public final int ptNoError = 0; | ||
| 105 | public final int ptAlreadyStarted = -10000; | ||
| 106 | public final int ptAlreadyStopped = -9999; | ||
| 107 | public final int PtInsufficientMemory = -9998; | ||
| 108 | static public native int Pt_TimeStart(int resolution); | ||
| 109 | static public native int Pt_TimeStop(); | ||
| 110 | static public native int Pt_Time(); | ||
| 111 | static public native boolean Pt_TimeStarted(); | ||
| 112 | static { | ||
| 113 | System.out.println("Loading pmjni"); | ||
| 114 | System.loadLibrary("pmjni"); | ||
| 115 | System.out.println("done loading pmjni"); | ||
| 116 | } | ||
| 117 | } | ||
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 | |||
| 3 | package jportmidi; | ||
| 4 | |||
| 5 | public class JPortMidiException extends Exception { | ||
| 6 | public int error = 0; | ||
| 7 | public JPortMidiException(int err, String msg) { | ||
| 8 | super(msg); | ||
| 9 | error = err; | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
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 | |||
| 3 | rem This is an out-of-date script for Windows to build a Java application | ||
| 4 | rem (PmDefaults) with PortMidi external library.xb | ||
| 5 | |||
| 6 | rem Compile the java PortMidi interface classes. | ||
| 7 | javac jportmidi/*.java | ||
| 8 | |||
| 9 | rem Compile the pmdefaults application. | ||
| 10 | javac -classpath . pmdefaults/*.java | ||
| 11 | |||
| 12 | rem Temporarily copy the portmusic_logo.png file here to add to the jar file. | ||
| 13 | copy pmdefaults\portmusic_logo.png . > nul | ||
| 14 | |||
| 15 | rem Create a directory to hold the distribution. | ||
| 16 | mkdir win32 | ||
| 17 | |||
| 18 | rem Attempt to copy the interface DLL to the distribution directory. | ||
| 19 | |||
| 20 | if exist "..\release\pmjni.dll" goto have-dll | ||
| 21 | |||
| 22 | echo "ERROR: pmjni.dll not found!" | ||
| 23 | exit /b 1 | ||
| 24 | |||
| 25 | :have-dll | ||
| 26 | copy "..\release\pmjni.dll" win32\pmjni.dll > nul | ||
| 27 | |||
| 28 | rem Create a java archive (jar) file of the distribution. | ||
| 29 | jar cmf pmdefaults\manifest.txt win32\pmdefaults.jar pmdefaults\*.class portmusic_logo.png jportmidi\*.class | ||
| 30 | |||
| 31 | rem Clean up the temporary image file now that it is in the jar file. | ||
| 32 | del portmusic_logo.png | ||
| 33 | |||
| 34 | rem Copy the java execution code obtained from | ||
| 35 | rem http://devwizard.free.fr/html/en/JavaExe.html to the distribution | ||
| 36 | rem directory. The copy also renames the file to our desired executable | ||
| 37 | rem name. | ||
| 38 | copy JavaExe.exe win32\pmdefaults.exe > nul | ||
| 39 | |||
| 40 | rem Integrate the icon into the executable using UpdateRsrcJavaExe from | ||
| 41 | rem http://devwizard.free.fr | ||
| 42 | UpdateRsrcJavaExe -run -exe=win32\pmdefaults.exe -ico=pmdefaults\pmdefaults.ico | ||
| 43 | |||
| 44 | rem Copy the 32-bit windows read me file to the distribution directory. | ||
| 45 | copy pmdefaults\readme-win32.txt win32\README.txt > nul | ||
| 46 | |||
| 47 | rem Copy the license file to the distribution directory. | ||
| 48 | copy pmdefaults\pmdefaults-license.txt win32\license.txt > nul | ||
| 49 | |||
| 50 | echo "You can run pmdefaults.exe in win32" | ||
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 | ||
| 8 | extern "C" { | ||
| 9 | #endif | ||
| 10 | #undef jportmidi_JPortMidiApi_PM_FILT_ACTIVE | ||
| 11 | #define jportmidi_JPortMidiApi_PM_FILT_ACTIVE 16384L | ||
| 12 | #undef jportmidi_JPortMidiApi_PM_FILT_SYSEX | ||
| 13 | #define jportmidi_JPortMidiApi_PM_FILT_SYSEX 1L | ||
| 14 | #undef jportmidi_JPortMidiApi_PM_FILT_CLOCK | ||
| 15 | #define jportmidi_JPortMidiApi_PM_FILT_CLOCK 256L | ||
| 16 | #undef jportmidi_JPortMidiApi_PM_FILT_PLAY | ||
| 17 | #define jportmidi_JPortMidiApi_PM_FILT_PLAY 7168L | ||
| 18 | #undef jportmidi_JPortMidiApi_PM_FILT_TICK | ||
| 19 | #define jportmidi_JPortMidiApi_PM_FILT_TICK 512L | ||
| 20 | #undef jportmidi_JPortMidiApi_PM_FILT_FD | ||
| 21 | #define jportmidi_JPortMidiApi_PM_FILT_FD 8192L | ||
| 22 | #undef jportmidi_JPortMidiApi_PM_FILT_UNDEFINED | ||
| 23 | #define jportmidi_JPortMidiApi_PM_FILT_UNDEFINED 8192L | ||
| 24 | #undef jportmidi_JPortMidiApi_PM_FILT_RESET | ||
| 25 | #define jportmidi_JPortMidiApi_PM_FILT_RESET 32768L | ||
| 26 | #undef jportmidi_JPortMidiApi_PM_FILT_REALTIME | ||
| 27 | #define jportmidi_JPortMidiApi_PM_FILT_REALTIME 16641L | ||
| 28 | #undef jportmidi_JPortMidiApi_PM_FILT_NOTE | ||
| 29 | #define jportmidi_JPortMidiApi_PM_FILT_NOTE 50331648L | ||
| 30 | #undef jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH | ||
| 31 | #define jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH 536870912L | ||
| 32 | #undef jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH | ||
| 33 | #define jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH 67108864L | ||
| 34 | #undef jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH | ||
| 35 | #define jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH 603979776L | ||
| 36 | #undef jportmidi_JPortMidiApi_PM_FILT_PROGRAM | ||
| 37 | #define jportmidi_JPortMidiApi_PM_FILT_PROGRAM 268435456L | ||
| 38 | #undef jportmidi_JPortMidiApi_PM_FILT_CONTROL | ||
| 39 | #define jportmidi_JPortMidiApi_PM_FILT_CONTROL 134217728L | ||
| 40 | #undef jportmidi_JPortMidiApi_PM_FILT_PITCHBEND | ||
| 41 | #define jportmidi_JPortMidiApi_PM_FILT_PITCHBEND 1073741824L | ||
| 42 | #undef jportmidi_JPortMidiApi_PM_FILT_MTC | ||
| 43 | #define jportmidi_JPortMidiApi_PM_FILT_MTC 2L | ||
| 44 | #undef jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION | ||
| 45 | #define jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION 4L | ||
| 46 | #undef jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT | ||
| 47 | #define jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT 8L | ||
| 48 | #undef jportmidi_JPortMidiApi_PM_FILT_TUNE | ||
| 49 | #define jportmidi_JPortMidiApi_PM_FILT_TUNE 64L | ||
| 50 | #undef jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON | ||
| 51 | #define jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON 78L | ||
| 52 | /* | ||
| 53 | * Class: jportmidi_JPortMidiApi | ||
| 54 | * Method: Pm_Initialize | ||
| 55 | * Signature: ()I | ||
| 56 | */ | ||
| 57 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize | ||
| 58 | (JNIEnv *, jclass); | ||
| 59 | |||
| 60 | /* | ||
| 61 | * Class: jportmidi_JPortMidiApi | ||
| 62 | * Method: Pm_Terminate | ||
| 63 | * Signature: ()I | ||
| 64 | */ | ||
| 65 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate | ||
| 66 | (JNIEnv *, jclass); | ||
| 67 | |||
| 68 | /* | ||
| 69 | * Class: jportmidi_JPortMidiApi | ||
| 70 | * Method: Pm_HasHostError | ||
| 71 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I | ||
| 72 | */ | ||
| 73 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError | ||
| 74 | (JNIEnv *, jclass, jobject); | ||
| 75 | |||
| 76 | /* | ||
| 77 | * Class: jportmidi_JPortMidiApi | ||
| 78 | * Method: Pm_GetErrorText | ||
| 79 | * Signature: (I)Ljava/lang/String; | ||
| 80 | */ | ||
| 81 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText | ||
| 82 | (JNIEnv *, jclass, jint); | ||
| 83 | |||
| 84 | /* | ||
| 85 | * Class: jportmidi_JPortMidiApi | ||
| 86 | * Method: Pm_GetHostErrorText | ||
| 87 | * Signature: ()Ljava/lang/String; | ||
| 88 | */ | ||
| 89 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText | ||
| 90 | (JNIEnv *, jclass); | ||
| 91 | |||
| 92 | /* | ||
| 93 | * Class: jportmidi_JPortMidiApi | ||
| 94 | * Method: Pm_CountDevices | ||
| 95 | * Signature: ()I | ||
| 96 | */ | ||
| 97 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices | ||
| 98 | (JNIEnv *, jclass); | ||
| 99 | |||
| 100 | /* | ||
| 101 | * Class: jportmidi_JPortMidiApi | ||
| 102 | * Method: Pm_GetDefaultInputDeviceID | ||
| 103 | * Signature: ()I | ||
| 104 | */ | ||
| 105 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID | ||
| 106 | (JNIEnv *, jclass); | ||
| 107 | |||
| 108 | /* | ||
| 109 | * Class: jportmidi_JPortMidiApi | ||
| 110 | * Method: Pm_GetDefaultOutputDeviceID | ||
| 111 | * Signature: ()I | ||
| 112 | */ | ||
| 113 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID | ||
| 114 | (JNIEnv *, jclass); | ||
| 115 | |||
| 116 | /* | ||
| 117 | * Class: jportmidi_JPortMidiApi | ||
| 118 | * Method: Pm_GetDeviceInterf | ||
| 119 | * Signature: (I)Ljava/lang/String; | ||
| 120 | */ | ||
| 121 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf | ||
| 122 | (JNIEnv *, jclass, jint); | ||
| 123 | |||
| 124 | /* | ||
| 125 | * Class: jportmidi_JPortMidiApi | ||
| 126 | * Method: Pm_GetDeviceName | ||
| 127 | * Signature: (I)Ljava/lang/String; | ||
| 128 | */ | ||
| 129 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName | ||
| 130 | (JNIEnv *, jclass, jint); | ||
| 131 | |||
| 132 | /* | ||
| 133 | * Class: jportmidi_JPortMidiApi | ||
| 134 | * Method: Pm_GetDeviceInput | ||
| 135 | * Signature: (I)Z | ||
| 136 | */ | ||
| 137 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput | ||
| 138 | (JNIEnv *, jclass, jint); | ||
| 139 | |||
| 140 | /* | ||
| 141 | * Class: jportmidi_JPortMidiApi | ||
| 142 | * Method: Pm_GetDeviceOutput | ||
| 143 | * Signature: (I)Z | ||
| 144 | */ | ||
| 145 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput | ||
| 146 | (JNIEnv *, jclass, jint); | ||
| 147 | |||
| 148 | /* | ||
| 149 | * Class: jportmidi_JPortMidiApi | ||
| 150 | * Method: Pm_OpenInput | ||
| 151 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;I)I | ||
| 152 | */ | ||
| 153 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput | ||
| 154 | (JNIEnv *, jclass, jobject, jint, jstring, jint); | ||
| 155 | |||
| 156 | /* | ||
| 157 | * Class: jportmidi_JPortMidiApi | ||
| 158 | * Method: Pm_OpenOutput | ||
| 159 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;II)I | ||
| 160 | */ | ||
| 161 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput | ||
| 162 | (JNIEnv *, jclass, jobject, jint, jstring, jint, jint); | ||
| 163 | |||
| 164 | /* | ||
| 165 | * Class: jportmidi_JPortMidiApi | ||
| 166 | * Method: Pm_SetFilter | ||
| 167 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I | ||
| 168 | */ | ||
| 169 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter | ||
| 170 | (JNIEnv *, jclass, jobject, jint); | ||
| 171 | |||
| 172 | /* | ||
| 173 | * Class: jportmidi_JPortMidiApi | ||
| 174 | * Method: Pm_SetChannelMask | ||
| 175 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I | ||
| 176 | */ | ||
| 177 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask | ||
| 178 | (JNIEnv *, jclass, jobject, jint); | ||
| 179 | |||
| 180 | /* | ||
| 181 | * Class: jportmidi_JPortMidiApi | ||
| 182 | * Method: Pm_Abort | ||
| 183 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I | ||
| 184 | */ | ||
| 185 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort | ||
| 186 | (JNIEnv *, jclass, jobject); | ||
| 187 | |||
| 188 | /* | ||
| 189 | * Class: jportmidi_JPortMidiApi | ||
| 190 | * Method: Pm_Close | ||
| 191 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I | ||
| 192 | */ | ||
| 193 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close | ||
| 194 | (JNIEnv *, jclass, jobject); | ||
| 195 | |||
| 196 | /* | ||
| 197 | * Class: jportmidi_JPortMidiApi | ||
| 198 | * Method: Pm_Read | ||
| 199 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I | ||
| 200 | */ | ||
| 201 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read | ||
| 202 | (JNIEnv *, jclass, jobject, jobject); | ||
| 203 | |||
| 204 | /* | ||
| 205 | * Class: jportmidi_JPortMidiApi | ||
| 206 | * Method: Pm_Poll | ||
| 207 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I | ||
| 208 | */ | ||
| 209 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll | ||
| 210 | (JNIEnv *, jclass, jobject); | ||
| 211 | |||
| 212 | /* | ||
| 213 | * Class: jportmidi_JPortMidiApi | ||
| 214 | * Method: Pm_Write | ||
| 215 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I | ||
| 216 | */ | ||
| 217 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write | ||
| 218 | (JNIEnv *, jclass, jobject, jobject); | ||
| 219 | |||
| 220 | /* | ||
| 221 | * Class: jportmidi_JPortMidiApi | ||
| 222 | * Method: Pm_WriteShort | ||
| 223 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;II)I | ||
| 224 | */ | ||
| 225 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort | ||
| 226 | (JNIEnv *, jclass, jobject, jint, jint); | ||
| 227 | |||
| 228 | /* | ||
| 229 | * Class: jportmidi_JPortMidiApi | ||
| 230 | * Method: Pm_WriteSysEx | ||
| 231 | * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I[B)I | ||
| 232 | */ | ||
| 233 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx | ||
| 234 | (JNIEnv *, jclass, jobject, jint, jbyteArray); | ||
| 235 | |||
| 236 | /* | ||
| 237 | * Class: jportmidi_JPortMidiApi | ||
| 238 | * Method: Pt_TimeStart | ||
| 239 | * Signature: (I)I | ||
| 240 | */ | ||
| 241 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart | ||
| 242 | (JNIEnv *, jclass, jint); | ||
| 243 | |||
| 244 | /* | ||
| 245 | * Class: jportmidi_JPortMidiApi | ||
| 246 | * Method: Pt_TimeStop | ||
| 247 | * Signature: ()I | ||
| 248 | */ | ||
| 249 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop | ||
| 250 | (JNIEnv *, jclass); | ||
| 251 | |||
| 252 | /* | ||
| 253 | * Class: jportmidi_JPortMidiApi | ||
| 254 | * Method: Pt_Time | ||
| 255 | * Signature: ()I | ||
| 256 | */ | ||
| 257 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time | ||
| 258 | (JNIEnv *, jclass); | ||
| 259 | |||
| 260 | /* | ||
| 261 | * Class: jportmidi_JPortMidiApi | ||
| 262 | * Method: Pt_TimeStarted | ||
| 263 | * Signature: ()Z | ||
| 264 | */ | ||
| 265 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted | ||
| 266 | (JNIEnv *, jclass); | ||
| 267 | |||
| 268 | #ifdef __cplusplus | ||
| 269 | } | ||
| 270 | #endif | ||
| 271 | #endif | ||
| 272 | /* Header for class jportmidi_JPortMidiApi_PmEvent */ | ||
| 273 | |||
| 274 | #ifndef _Included_jportmidi_JPortMidiApi_PmEvent | ||
| 275 | #define _Included_jportmidi_JPortMidiApi_PmEvent | ||
| 276 | #ifdef __cplusplus | ||
| 277 | extern "C" { | ||
| 278 | #endif | ||
| 279 | #ifdef __cplusplus | ||
| 280 | } | ||
| 281 | #endif | ||
| 282 | #endif | ||
| 283 | /* Header for class jportmidi_JPortMidiApi_PortMidiStream */ | ||
| 284 | |||
| 285 | #ifndef _Included_jportmidi_JPortMidiApi_PortMidiStream | ||
| 286 | #define _Included_jportmidi_JPortMidiApi_PortMidiStream | ||
| 287 | #ifdef __cplusplus | ||
| 288 | extern "C" { | ||
| 289 | #endif | ||
| 290 | #ifdef __cplusplus | ||
| 291 | } | ||
| 292 | #endif | ||
| 293 | #endif | ||
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 | */ | ||
| 26 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize | ||
| 27 | (JNIEnv *env, jclass cl) | ||
| 28 | { | ||
| 29 | return Pm_Initialize(); | ||
| 30 | } | ||
| 31 | |||
| 32 | |||
| 33 | /* | ||
| 34 | * Method: Pm_Terminate | ||
| 35 | */ | ||
| 36 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate | ||
| 37 | (JNIEnv *env, jclass cl) | ||
| 38 | { | ||
| 39 | return Pm_Terminate(); | ||
| 40 | } | ||
| 41 | |||
| 42 | |||
| 43 | /* | ||
| 44 | * Method: Pm_HasHostError | ||
| 45 | */ | ||
| 46 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError | ||
| 47 | (JNIEnv *env, jclass cl, jobject jstream) | ||
| 48 | { | ||
| 49 | CLASS(c, jstream); | ||
| 50 | ADDRESS_FID(fid, c); | ||
| 51 | return Pm_HasHostError(PMSTREAM(jstream, fid)); | ||
| 52 | } | ||
| 53 | |||
| 54 | |||
| 55 | /* | ||
| 56 | * Method: Pm_GetErrorText | ||
| 57 | */ | ||
| 58 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText | ||
| 59 | (JNIEnv *env, jclass cl, jint i) | ||
| 60 | { | ||
| 61 | return (*env)->NewStringUTF(env, Pm_GetErrorText(i)); | ||
| 62 | } | ||
| 63 | |||
| 64 | |||
| 65 | /* | ||
| 66 | * Method: Pm_GetHostErrorText | ||
| 67 | */ | ||
| 68 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText | ||
| 69 | (JNIEnv *env, jclass cl) | ||
| 70 | { | ||
| 71 | char msg[PM_HOST_ERROR_MSG_LEN]; | ||
| 72 | Pm_GetHostErrorText(msg, PM_HOST_ERROR_MSG_LEN); | ||
| 73 | return (*env)->NewStringUTF(env, msg); | ||
| 74 | } | ||
| 75 | |||
| 76 | |||
| 77 | /* | ||
| 78 | * Method: Pm_CountDevices | ||
| 79 | */ | ||
| 80 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices | ||
| 81 | (JNIEnv *env, jclass cl) | ||
| 82 | { | ||
| 83 | return Pm_CountDevices(); | ||
| 84 | } | ||
| 85 | |||
| 86 | |||
| 87 | /* | ||
| 88 | * Method: Pm_GetDefaultInputDeviceID | ||
| 89 | */ | ||
| 90 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID | ||
| 91 | (JNIEnv *env, jclass cl) | ||
| 92 | { | ||
| 93 | return Pm_GetDefaultInputDeviceID(); | ||
| 94 | } | ||
| 95 | |||
| 96 | |||
| 97 | /* | ||
| 98 | * Method: Pm_GetDefaultOutputDeviceID | ||
| 99 | */ | ||
| 100 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID | ||
| 101 | (JNIEnv *env, jclass cl) | ||
| 102 | { | ||
| 103 | return Pm_GetDefaultOutputDeviceID(); | ||
| 104 | } | ||
| 105 | |||
| 106 | |||
| 107 | /* | ||
| 108 | * Method: Pm_GetDeviceInterf | ||
| 109 | */ | ||
| 110 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf | ||
| 111 | (JNIEnv *env, jclass cl, jint i) | ||
| 112 | { | ||
| 113 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 114 | if (!info) return NULL; | ||
| 115 | return (*env)->NewStringUTF(env, info->interf); | ||
| 116 | } | ||
| 117 | |||
| 118 | |||
| 119 | /* | ||
| 120 | * Method: Pm_GetDeviceName | ||
| 121 | */ | ||
| 122 | JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName | ||
| 123 | (JNIEnv *env, jclass cl, jint i) | ||
| 124 | { | ||
| 125 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 126 | if (!info) return NULL; | ||
| 127 | return (*env)->NewStringUTF(env, info->name); | ||
| 128 | } | ||
| 129 | |||
| 130 | |||
| 131 | /* | ||
| 132 | * Method: Pm_GetDeviceInput | ||
| 133 | */ | ||
| 134 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput | ||
| 135 | (JNIEnv *env, jclass cl, jint i) | ||
| 136 | { | ||
| 137 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 138 | if (!info) return (jboolean) 0; | ||
| 139 | return (jboolean) info->input; | ||
| 140 | } | ||
| 141 | |||
| 142 | |||
| 143 | /* | ||
| 144 | * Method: Pm_GetDeviceOutput | ||
| 145 | */ | ||
| 146 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput | ||
| 147 | (JNIEnv *env, jclass cl, jint i) | ||
| 148 | { | ||
| 149 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 150 | if (!info) return (jboolean) 0; | ||
| 151 | return (jboolean) info->output; | ||
| 152 | } | ||
| 153 | |||
| 154 | |||
| 155 | /* | ||
| 156 | * Method: Pm_OpenInput | ||
| 157 | */ | ||
| 158 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput | ||
| 159 | (JNIEnv *env, jclass cl, | ||
| 160 | jobject jstream, jint index, jstring extras, jint bufsiz) | ||
| 161 | { | ||
| 162 | PmError rslt; | ||
| 163 | PortMidiStream *stream; | ||
| 164 | CLASS(c, jstream); | ||
| 165 | ADDRESS_FID(fid, c); | ||
| 166 | rslt = Pm_OpenInput(&stream, index, NULL, bufsiz, NULL, NULL); | ||
| 167 | SET_PMSTREAM(jstream, fid, stream); | ||
| 168 | return rslt; | ||
| 169 | } | ||
| 170 | |||
| 171 | |||
| 172 | /* | ||
| 173 | * Method: Pm_OpenOutput | ||
| 174 | */ | ||
| 175 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput | ||
| 176 | (JNIEnv *env, jclass cl, jobject jstream, jint index, jstring extras, | ||
| 177 | jint bufsiz, jint latency) | ||
| 178 | { | ||
| 179 | PmError rslt; | ||
| 180 | PortMidiStream *stream; | ||
| 181 | CLASS(c, jstream); | ||
| 182 | ADDRESS_FID(fid, c); | ||
| 183 | rslt = Pm_OpenOutput(&stream, index, NULL, bufsiz, NULL, NULL, latency); | ||
| 184 | SET_PMSTREAM(jstream, fid, stream); | ||
| 185 | return rslt; | ||
| 186 | } | ||
| 187 | |||
| 188 | |||
| 189 | /* | ||
| 190 | * Method: Pm_SetFilter | ||
| 191 | */ | ||
| 192 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter | ||
| 193 | (JNIEnv *env, jclass cl, jobject jstream, jint filters) | ||
| 194 | { | ||
| 195 | CLASS(c, jstream); | ||
| 196 | ADDRESS_FID(fid, c); | ||
| 197 | return Pm_SetFilter(PMSTREAM(jstream, fid), filters); | ||
| 198 | } | ||
| 199 | |||
| 200 | |||
| 201 | /* | ||
| 202 | * Method: Pm_SetChannelMask | ||
| 203 | */ | ||
| 204 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask | ||
| 205 | (JNIEnv *env, jclass cl, jobject jstream, jint mask) | ||
| 206 | { | ||
| 207 | CLASS(c, jstream); | ||
| 208 | ADDRESS_FID(fid, c); | ||
| 209 | return Pm_SetChannelMask(PMSTREAM(jstream, fid), mask); | ||
| 210 | } | ||
| 211 | |||
| 212 | |||
| 213 | /* | ||
| 214 | * Method: Pm_Abort | ||
| 215 | */ | ||
| 216 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort | ||
| 217 | (JNIEnv *env, jclass cl, jobject jstream) | ||
| 218 | { | ||
| 219 | CLASS(c, jstream); | ||
| 220 | ADDRESS_FID(fid, c); | ||
| 221 | return Pm_Abort(PMSTREAM(jstream, fid)); | ||
| 222 | } | ||
| 223 | |||
| 224 | |||
| 225 | /* | ||
| 226 | * Method: Pm_Close | ||
| 227 | */ | ||
| 228 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close | ||
| 229 | (JNIEnv *env, jclass cl, jobject jstream) | ||
| 230 | { | ||
| 231 | CLASS(c, jstream); | ||
| 232 | ADDRESS_FID(fid, c); | ||
| 233 | return Pm_Close(PMSTREAM(jstream, fid)); | ||
| 234 | } | ||
| 235 | |||
| 236 | |||
| 237 | /* | ||
| 238 | * Method: Pm_Read | ||
| 239 | */ | ||
| 240 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read | ||
| 241 | (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent) | ||
| 242 | { | ||
| 243 | CLASS(jstream_class, jstream); | ||
| 244 | ADDRESS_FID(address_fid, jstream_class); | ||
| 245 | jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent); | ||
| 246 | jfieldID message_fid = | ||
| 247 | (*env)->GetFieldID(env, jpmevent_class, "message", "I"); | ||
| 248 | jfieldID timestamp_fid = | ||
| 249 | (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I"); | ||
| 250 | PmEvent buffer; | ||
| 251 | PmError rslt = Pm_Read(PMSTREAM(jstream, address_fid), &buffer, 1); | ||
| 252 | (*env)->SetIntField(env, jpmevent, message_fid, buffer.message); | ||
| 253 | (*env)->SetIntField(env, jpmevent, timestamp_fid, buffer.timestamp); | ||
| 254 | return rslt; | ||
| 255 | } | ||
| 256 | |||
| 257 | |||
| 258 | /* | ||
| 259 | * Method: Pm_Poll | ||
| 260 | */ | ||
| 261 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll | ||
| 262 | (JNIEnv *env, jclass cl, jobject jstream) | ||
| 263 | { | ||
| 264 | CLASS(c, jstream); | ||
| 265 | ADDRESS_FID(fid, c); | ||
| 266 | return Pm_Poll(PMSTREAM(jstream, fid)); | ||
| 267 | } | ||
| 268 | |||
| 269 | |||
| 270 | /* | ||
| 271 | * Method: Pm_Write | ||
| 272 | */ | ||
| 273 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write | ||
| 274 | (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent) | ||
| 275 | { | ||
| 276 | CLASS(jstream_class, jstream); | ||
| 277 | ADDRESS_FID(address_fid, jstream_class); | ||
| 278 | jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent); | ||
| 279 | jfieldID message_fid = | ||
| 280 | (*env)->GetFieldID(env, jpmevent_class, "message", "I"); | ||
| 281 | jfieldID timestamp_fid = | ||
| 282 | (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I"); | ||
| 283 | // note that we call WriteShort because it's simpler than constructing | ||
| 284 | // a buffer and passing it to Pm_Write | ||
| 285 | return Pm_WriteShort(PMSTREAM(jstream, address_fid), | ||
| 286 | (*env)->GetIntField(env, jpmevent, timestamp_fid), | ||
| 287 | (*env)->GetIntField(env, jpmevent, message_fid)); | ||
| 288 | } | ||
| 289 | |||
| 290 | |||
| 291 | /* | ||
| 292 | * Method: Pm_WriteShort | ||
| 293 | */ | ||
| 294 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort | ||
| 295 | (JNIEnv *env, jclass cl, jobject jstream, jint when, jint msg) | ||
| 296 | { | ||
| 297 | CLASS(c, jstream); | ||
| 298 | ADDRESS_FID(fid, c); | ||
| 299 | return Pm_WriteShort(PMSTREAM(jstream, fid), when, msg); | ||
| 300 | } | ||
| 301 | |||
| 302 | |||
| 303 | /* | ||
| 304 | * Method: Pm_WriteSysEx | ||
| 305 | */ | ||
| 306 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx | ||
| 307 | (JNIEnv *env, jclass cl, jobject jstream, jint when, jbyteArray jmsg) | ||
| 308 | { | ||
| 309 | CLASS(c, jstream); | ||
| 310 | ADDRESS_FID(fid, c); | ||
| 311 | jbyte *bytes = (*env)->GetByteArrayElements(env, jmsg, 0); | ||
| 312 | PmError rslt = Pm_WriteSysEx(PMSTREAM(jstream, fid), when, | ||
| 313 | (unsigned char *) bytes); | ||
| 314 | (*env)->ReleaseByteArrayElements(env, jmsg, bytes, 0); | ||
| 315 | return rslt; | ||
| 316 | } | ||
| 317 | |||
| 318 | /* | ||
| 319 | * Method: Pt_TimeStart | ||
| 320 | */ | ||
| 321 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart | ||
| 322 | (JNIEnv *env, jclass c, jint resolution) | ||
| 323 | { | ||
| 324 | return Pt_Start(resolution, NULL, NULL); | ||
| 325 | } | ||
| 326 | |||
| 327 | /* | ||
| 328 | * Method: Pt_TimeStop | ||
| 329 | */ | ||
| 330 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop | ||
| 331 | (JNIEnv *env, jclass c) | ||
| 332 | { | ||
| 333 | return Pt_Stop(); | ||
| 334 | } | ||
| 335 | |||
| 336 | /* | ||
| 337 | * Method: Pt_Time | ||
| 338 | */ | ||
| 339 | JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time | ||
| 340 | (JNIEnv *env, jclass c) | ||
| 341 | { | ||
| 342 | return Pt_Time(); | ||
| 343 | } | ||
| 344 | |||
| 345 | /* | ||
| 346 | * Method: Pt_TimeStarted | ||
| 347 | */ | ||
| 348 | JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted | ||
| 349 | (JNIEnv *env, jclass c) | ||
| 350 | { | ||
| 351 | return Pt_Started(); | ||
| 352 | } | ||
| 353 | |||
| 354 | |||
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 | ||
| 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US | ||
| 21 | #pragma code_page(1252) | ||
| 22 | #endif //_WIN32 | ||
| 23 | |||
| 24 | #ifdef APSTUDIO_INVOKED | ||
| 25 | ///////////////////////////////////////////////////////////////////////////// | ||
| 26 | // | ||
| 27 | // TEXTINCLUDE | ||
| 28 | // | ||
| 29 | |||
| 30 | 1 TEXTINCLUDE | ||
| 31 | BEGIN | ||
| 32 | "resource.h\0" | ||
| 33 | END | ||
| 34 | |||
| 35 | 2 TEXTINCLUDE | ||
| 36 | BEGIN | ||
| 37 | "#include ""afxres.h""\r\n" | ||
| 38 | "\0" | ||
| 39 | END | ||
| 40 | |||
| 41 | 3 TEXTINCLUDE | ||
| 42 | BEGIN | ||
| 43 | "\r\n" | ||
| 44 | "\0" | ||
| 45 | END | ||
| 46 | |||
| 47 | #endif // APSTUDIO_INVOKED | ||
| 48 | |||
| 49 | #endif // English (U.S.) resources | ||
| 50 | ///////////////////////////////////////////////////////////////////////////// | ||
| 51 | |||
| 52 | |||
| 53 | |||
| 54 | #ifndef APSTUDIO_INVOKED | ||
| 55 | ///////////////////////////////////////////////////////////////////////////// | ||
| 56 | // | ||
| 57 | // Generated from the TEXTINCLUDE 3 resource. | ||
| 58 | // | ||
| 59 | |||
| 60 | |||
| 61 | ///////////////////////////////////////////////////////////////////////////// | ||
| 62 | #endif // not APSTUDIO_INVOKED | ||
| 63 | |||
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 @@ | |||
| 1 | README_LINUX.txt for PortMidi | ||
| 2 | Roger Dannenberg | ||
| 3 | 6 Dec 2012, revised May 2022 | ||
| 4 | |||
| 5 | Contents: | ||
| 6 | To make PortMidi | ||
| 7 | The pmdefaults program | ||
| 8 | Setting LD_LIBRARY_PATH | ||
| 9 | A note about amd64 | ||
| 10 | Using autoconf | ||
| 11 | Using configure | ||
| 12 | Changelog | ||
| 13 | |||
| 14 | |||
| 15 | See ../README.md for general instructions. | ||
| 16 | |||
| 17 | THE pmdefaults PROGRAM | ||
| 18 | |||
| 19 | (This may be obsolete. It is older than `../README.md` which | ||
| 20 | also discusses pmdefaults, and Java support may be removed | ||
| 21 | unless someone claims they use it... -RBD) | ||
| 22 | |||
| 23 | You should install pmdefaults. It provides a graphical interface | ||
| 24 | for selecting default MIDI IN and OUT devices so that you don't | ||
| 25 | have to build device selection interfaces into all your programs | ||
| 26 | and so users have a single place to set a preference. | ||
| 27 | |||
| 28 | Follow the instructions above to run ccmake, making sure that | ||
| 29 | CMAKE_BUILD_TYPE is Release. Run make as described above. Then: | ||
| 30 | |||
| 31 | sudo make install | ||
| 32 | |||
| 33 | This will install PortMidi libraries and the pmdefault program. | ||
| 34 | You must alos have the environment variable LD_LIBRARY_PATH set | ||
| 35 | to include /usr/local/lib (where libpmjni.so is installed). | ||
| 36 | |||
| 37 | Now, you can run pmdefault. | ||
| 38 | |||
| 39 | |||
| 40 | SETTING LD_LIBRARY_PATH | ||
| 41 | |||
| 42 | pmdefaults will not work unless LD_LIBRARY_PATH includes a | ||
| 43 | directory (normally /usr/local/lib) containing libpmjni.so, | ||
| 44 | installed as described above. | ||
| 45 | |||
| 46 | To set LD_LIBRARY_PATH, you might want to add this to your | ||
| 47 | ~/.profile (if you use the bash shell): | ||
| 48 | |||
| 49 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib | ||
| 50 | export LD_LIBRARY_PATH | ||
| 51 | |||
| 52 | |||
| 53 | A NOTE ABOUT AMD64: | ||
| 54 | |||
| 55 | When compiling portmidi under linux on an AMD64, I had to add the -fPIC | ||
| 56 | flag to the gcc flags. | ||
| 57 | |||
| 58 | Reason: when trying to build John Harrison's pyPortMidi gcc bailed out | ||
| 59 | with this error: | ||
| 60 | |||
| 61 | ./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC | ||
| 62 | ./linux/libportmidi.a: could not read symbols: Bad value | ||
| 63 | collect2: ld returned 1 exit status | ||
| 64 | error: command 'gcc' failed with exit status 1 | ||
| 65 | |||
| 66 | What they said: | ||
| 67 | http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3 | ||
| 68 | On certain architectures (AMD64 amongst them), shared libraries *must* | ||
| 69 | be "PIC-enabled". | ||
| 70 | |||
| 71 | CHANGELOG | ||
| 72 | |||
| 73 | 27-may-2022 Roger B. Dannenberg | ||
| 74 | Some updates to this file. | ||
| 75 | |||
| 76 | 6-dec-2012 Roger B. Dannenberg | ||
| 77 | Copied notes on Autoconf from Audacity sources | ||
| 78 | |||
| 79 | 22-jan-2010 Roger B. Dannenberg | ||
| 80 | Updated instructions about Java paths | ||
| 81 | |||
| 82 | 14-oct-2009 Roger B. Dannenberg | ||
| 83 | Using CMake now for building and configuration | ||
| 84 | |||
| 85 | 29-aug-2006 Roger B. Dannenberg | ||
| 86 | Fixed PortTime to join with time thread for clean exit. | ||
| 87 | |||
| 88 | 28-aug-2006 Roger B. Dannenberg | ||
| 89 | Updated this documentation. | ||
| 90 | |||
| 91 | 08-Jun-2004 Roger B. Dannenberg | ||
| 92 | Updated code to use new system abstraction. | ||
| 93 | |||
| 94 | 12-Apr-2003 Roger B. Dannenberg | ||
| 95 | Fixed pm_test/test.c to filter clocks and active messages. | ||
| 96 | Integrated changes from Clemens Ladisch: | ||
| 97 | cleaned up pmlinuxalsa.c | ||
| 98 | record timestamp on sysex input | ||
| 99 | deallocate some resources previously left open | ||
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 | |||
| 30 | void 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 | |||
| 45 | void pm_term(void) | ||
| 46 | { | ||
| 47 | #ifdef PMALSA | ||
| 48 | pm_linuxalsa_term(); | ||
| 49 | #endif | ||
| 50 | #ifdef PMNULL | ||
| 51 | pm_linuxnull_term(); | ||
| 52 | #endif | ||
| 53 | } | ||
| 54 | |||
| 55 | PmDeviceID Pm_GetDefaultInputDeviceID() { | ||
| 56 | Pm_Initialize(); | ||
| 57 | return pm_default_input_device_id; | ||
| 58 | } | ||
| 59 | |||
| 60 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | ||
| 61 | Pm_Initialize(); | ||
| 62 | return pm_default_output_device_id; | ||
| 63 | } | ||
| 64 | |||
| 65 | void *pm_alloc(size_t s) { return malloc(s); } | ||
| 66 | |||
| 67 | void pm_free(void *ptr) { free(ptr); } | ||
| 68 | |||
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 | |||
| 45 | extern pm_fns_node pm_linuxalsa_in_dictionary; | ||
| 46 | extern pm_fns_node pm_linuxalsa_out_dictionary; | ||
| 47 | |||
| 48 | static snd_seq_t *seq = NULL; // all input comes here, | ||
| 49 | // output queue allocated on seq | ||
| 50 | static int queue, queue_used; /* one for all ports, reference counted */ | ||
| 51 | |||
| 52 | #define PORT_IS_CLOSED -999999 | ||
| 53 | |||
| 54 | typedef 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 | /**/ | ||
| 66 | static 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 | |||
| 79 | static 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 */ | ||
| 91 | static 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 | |||
| 116 | static 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? */ | ||
| 128 | static 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 | |||
| 144 | static 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 */ | ||
| 157 | static 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 | |||
| 173 | static 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 | |||
| 188 | static 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 | |||
| 252 | static 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 | |||
| 306 | static 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 | |||
| 333 | static 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 | |||
| 381 | static 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 | |||
| 459 | static 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 | |||
| 475 | static 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 | |||
| 506 | static 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 | |||
| 517 | static 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 */ | ||
| 537 | PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) { | ||
| 538 | return pmNoError; | ||
| 539 | } | ||
| 540 | |||
| 541 | |||
| 542 | static 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 | |||
| 552 | static 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 | |||
| 729 | static 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 | |||
| 765 | static unsigned int alsa_check_host_error(PmInternal *midi) | ||
| 766 | { | ||
| 767 | return FALSE; | ||
| 768 | } | ||
| 769 | |||
| 770 | |||
| 771 | pm_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 | |||
| 786 | pm_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 | */ | ||
| 807 | char *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 | |||
| 816 | PmError 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 | |||
| 882 | void 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 | |||
| 3 | PmError pm_linuxalsa_init(void); | ||
| 4 | void 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 | |||
| 21 | PmError pm_linuxnull_init(void) | ||
| 22 | { | ||
| 23 | return pmNoError; | ||
| 24 | } | ||
| 25 | |||
| 26 | |||
| 27 | void 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 | |||
| 3 | PmError pm_linuxnull_init(void); | ||
| 4 | void 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 | ||
| 14 | configuration=Release | ||
| 15 | |||
| 16 | PF=/usr/local | ||
| 17 | |||
| 18 | # For debugging, define PM_CHECK_ERRORS | ||
| 19 | ifeq ($(configuration),Release) | ||
| 20 | CONFIG = Release | ||
| 21 | else | ||
| 22 | CONFIG = Debug | ||
| 23 | endif | ||
| 24 | |||
| 25 | current: all | ||
| 26 | |||
| 27 | all: $(CONFIG)/CMakeCache.txt | ||
| 28 | cd $(CONFIG); make | ||
| 29 | |||
| 30 | $(CONFIG)/CMakeCache.txt: | ||
| 31 | rm -f $(CONFIG)/CMakeCache.txt | ||
| 32 | mkdir -p $(CONFIG) | ||
| 33 | cd $(CONFIG); cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(CONFIG) | ||
| 34 | |||
| 35 | |||
| 36 | **** For instructions: make -f pm_mac/Makefile.osx help ****\n' | ||
| 37 | |||
| 38 | help: | ||
| 39 | echo $$'\n\n\ | ||
| 40 | This is help for portmidi/pm_mac/Makefile.osx\n\n\ | ||
| 41 | Installation path for dylib is $(PF)\n\ | ||
| 42 | To build Release version libraries and test applications,\n \ | ||
| 43 | make -f pm_mac/Makefile.osx\n\ | ||
| 44 | To build Debug version libraries and test applications,\n \ | ||
| 45 | make -f pm_mac/Makefile.osx configuration=Debug\n\ | ||
| 46 | To install universal dynamic library,\n \ | ||
| 47 | sudo make -f pm_mac/Makefile.osx install\n\ | ||
| 48 | To install universal dynamic library with xcode,\n \ | ||
| 49 | make -f pm_mac/Makefile.osx install-with-xcode\n\ | ||
| 50 | To make PmDefaults Java application,\n \ | ||
| 51 | make -f pm_mac/Makefile.osx pmdefaults\n\n \ | ||
| 52 | configuration = $(configuration)\n' | ||
| 53 | |||
| 54 | |||
| 55 | clean: | ||
| 56 | rm -f *.o *~ core* */*.o */*/*.o */*~ */core* pm_test/*/pm_dll.dll | ||
| 57 | rm -f *.opt *.ncb *.plg pm_win/Debug/pm_dll.lib pm_win/Release/pm_dll.lib | ||
| 58 | rm -f pm_test/*.opt pm_test/*.ncb | ||
| 59 | rm -f pm_java/pmjni/*.o pm_java/pmjni/*~ pm_java/*.h | ||
| 60 | rm -rf Release/CMakeFiles Debug/CMakeFiles | ||
| 61 | rm -rf pm_mac/pmdefaults/lib pm_mac/pmdefaults/src | ||
| 62 | |||
| 63 | cleaner: clean | ||
| 64 | rm -rf pm_mac/build | ||
| 65 | rm -rf pm_mac/Debug pm_mac/Release pm_test/Debug pm_test/Release | ||
| 66 | rm -f Debug/*.dylib Release/*.dylib | ||
| 67 | rm -f pm_java/pmjni/Debug/*.jnilib | ||
| 68 | rm -f pm_java/pmjni/Release/*.jnilib | ||
| 69 | |||
| 70 | cleanest: cleaner | ||
| 71 | rm -f Debug/CMakeCache.txt Release/CMakeCache.txt | ||
| 72 | rm -f CMakeCache.txt | ||
| 73 | rm -f Debug/libportmidi_s.a Release/libportmidi_s.a | ||
| 74 | rm -f pm_test/Debug/test pm_test/Debug/sysex pm_test/Debug/midithread | ||
| 75 | rm -f pm_test/Debug/latency pm_test/Debug/midithru | ||
| 76 | rm -f pm_test/Debug/qtest pm_test/Debug/mm | ||
| 77 | rm -f pm_test/Release/test pm_test/Release/sysex pm_test/Release/midithread | ||
| 78 | rm -f pm_test/Release/latency pm_test/Release/midithru | ||
| 79 | rm -f pm_test/Release/qtest pm_test/Release/mm | ||
| 80 | rm -f pm_java/*/*.class | ||
| 81 | rm -f pm_java/pmjni/jportmidi_JPortMidiApi_PortMidiStream.h | ||
| 82 | |||
| 83 | backup: cleanest | ||
| 84 | cd ..; zip -r portmidi.zip portmidi | ||
| 85 | |||
| 86 | install: porttime/porttime.h pm_common/portmidi.h \ | ||
| 87 | $(CONFIG)/libportmidi.dylib | ||
| 88 | install porttime/porttime.h $(PF)/include/ | ||
| 89 | install pm_common/portmidi.h $(PF)/include | ||
| 90 | install $(CONFIG)/libportmidi.dylib $(PF)/lib/ | ||
| 91 | |||
| 92 | # note - this uses xcode to build and install portmidi universal binaries | ||
| 93 | install-with-xcode: | ||
| 94 | sudo xcodebuild -project pm_mac/pm_mac.xcodeproj \ | ||
| 95 | -configuration Release install DSTROOT=/ | ||
| 96 | |||
| 97 | ##### build pmdefault ###### | ||
| 98 | |||
| 99 | pm_java/pmjni/jportmidi_JPortMidiApi.h: pm_java/jportmidi/JPortMidiApi.class | ||
| 100 | cd pm_java; javah jportmidi.JPortMidiApi | ||
| 101 | mv pm_java/jportmidi_JportMidiApi.h pm_java/pmjni | ||
| 102 | |||
| 103 | JAVASRC = pmdefaults/PmDefaultsFrame.java \ | ||
| 104 | pmdefaults/PmDefaults.java \ | ||
| 105 | jportmidi/JPortMidiApi.java jportmidi/JPortMidi.java \ | ||
| 106 | jportmidi/JPortMidiException.java | ||
| 107 | |||
| 108 | # this compiles ALL of the java code | ||
| 109 | pm_java/jportmidi/JPortMidiApi.class: $(JAVASRC:%=pm_java/%) | ||
| 110 | cd pm_java; javac $(JAVASRC) | ||
| 111 | |||
| 112 | $(CONFIG)/libpmjni.dylib: | ||
| 113 | mkdir -p $(CONFIG) | ||
| 114 | cd $(CONFIG); make -f ../pm_mac/$(MAKEFILE) | ||
| 115 | |||
| 116 | pmdefaults: $(CONFIG)/libpmjni.dylib pm_java/jportmidi/JPortMidiApi.class | ||
| 117 | ifeq ($(CONFIG),Debug) | ||
| 118 | echo "Error: you cannot build pmdefaults in a Debug configuration \n\ | ||
| 119 | You should use configuration=Release in the Makefile command line. " | ||
| 120 | @exit 2 | ||
| 121 | endif | ||
| 122 | xcodebuild -project pm_mac/pm_mac.xcodeproj \ | ||
| 123 | -configuration Release -target PmDefaults | ||
| 124 | echo "pmdefaults java application is made" | ||
| 125 | |||
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 @@ | |||
| 1 | README_MAC.txt for PortMidi | ||
| 2 | Roger Dannenberg | ||
| 3 | 20 nov 2009 | ||
| 4 | |||
| 5 | revised Mar 2024 to remove pmdefaults references | ||
| 6 | revised Jan 2022 for the PortMidi/portmidi repo on github.com | ||
| 7 | revised 20 Sep 2010 for Xcode 4.3.2 and CMake 2.8.8 | ||
| 8 | |||
| 9 | This documents how I build PortMidi for macOS. It's not the only way, | ||
| 10 | and command-line/scripting enthusiasts will say it's not even a good | ||
| 11 | way. Feel free to contribute your approach if you are willing to | ||
| 12 | describe it carefully and test it. | ||
| 13 | |||
| 14 | Install Xcode and the CMake application, CMake.app. I use the GUI | ||
| 15 | version of CMake which makes it easy to see/edit variables and | ||
| 16 | options. | ||
| 17 | |||
| 18 | ==== USING CMAKE ==== | ||
| 19 | |||
| 20 | Run CMake.app and select your portmidi repo working directory as the | ||
| 21 | location for source and build. (Yes, I use so called "in-tree" | ||
| 22 | builds -- it doesn't hurt, but I don't think it is necessary.) | ||
| 23 | |||
| 24 | Default settings should all be fine, but select options under BUILD if | ||
| 25 | you wish: | ||
| 26 | |||
| 27 | BUILD_NATIVE_JAVA_INTERFACE to build a Java interface (JNI) library. | ||
| 28 | |||
| 29 | BUILD_PORTMIDI_TESTS to create some test programs. Of particular | ||
| 30 | interest are test/mm, a handy command-line MIDI Input Monitor, and | ||
| 31 | test/testio, a simple command-line program to send or receive some | ||
| 32 | MIDI notes in case you need a quick test: What devices do I have? Does | ||
| 33 | this input work? Does this output work? | ||
| 34 | |||
| 35 | I disable BUILD_SHARED_LIBS and always link statically: Static linking only | ||
| 36 | adds about 40KB to any application and then you don't have to worry | ||
| 37 | about versions, instally, copying or finding the dynamic link library, | ||
| 38 | etc. | ||
| 39 | |||
| 40 | To make sure you link statically, I rename the library to | ||
| 41 | libportmidi_static.a. To do this, set PM_STATIC_LIB_NAME (in CMake, | ||
| 42 | under the "PM" group) to "portmidi_static", and of course your | ||
| 43 | application will have to specify portmidi_static as the library to | ||
| 44 | link to. | ||
| 45 | |||
| 46 | If you are building simple command-line applications, you might want | ||
| 47 | to enable PM_CHECK_ERRORS. If you do, then calls into the PortMidi | ||
| 48 | library will print error messages and exit in the event of an error | ||
| 49 | (such as trying to open a device that does not exist). This saves you | ||
| 50 | from having to check for errors everytime you call a library function | ||
| 51 | or getting confused when errors are detected but not reported. For | ||
| 52 | high-quality applications, do NOT enable PM_CHECK_ERRORS -- any | ||
| 53 | failure could immediately abort your whole application, which is not | ||
| 54 | very friendly to users. | ||
| 55 | |||
| 56 | Click on Configure (maybe a couple of times). | ||
| 57 | |||
| 58 | Click on Generate and make an Xcode project. | ||
| 59 | |||
| 60 | Open portmidi/portmidi.xcodeproj with Xcode and build what you | ||
| 61 | need. The simplest thing is to build the ALL_BUILD target. Be careful | ||
| 62 | to specify a Debug or Release depending on what you want. "ALL_BUILD" | ||
| 63 | is a misnomer -- it only builds the version you select. | ||
| 64 | |||
| 65 | |||
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: | ||
| 4 | pm_init(), which calls various routines to register the | ||
| 5 | available midi devices, | ||
| 6 | Pm_GetDefaultInputDeviceID(), and | ||
| 7 | Pm_GetDefaultOutputDeviceID(). | ||
| 8 | It is seperate from pmmacosxcm because we might want to register | ||
| 9 | non-CoreMIDI devices. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include "stdlib.h" | ||
| 13 | #include "portmidi.h" | ||
| 14 | #include "pmutil.h" | ||
| 15 | #include "pminternal.h" | ||
| 16 | #include "pmmacosxcm.h" | ||
| 17 | |||
| 18 | void pm_init(void) | ||
| 19 | { | ||
| 20 | pm_macosxcm_init(); | ||
| 21 | } | ||
| 22 | |||
| 23 | |||
| 24 | void pm_term(void) | ||
| 25 | { | ||
| 26 | pm_macosxcm_term(); | ||
| 27 | } | ||
| 28 | |||
| 29 | PmDeviceID Pm_GetDefaultInputDeviceID(void) | ||
| 30 | { | ||
| 31 | Pm_Initialize(); | ||
| 32 | return pm_default_input_device_id; | ||
| 33 | } | ||
| 34 | |||
| 35 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { | ||
| 36 | Pm_Initialize(); | ||
| 37 | return pm_default_output_device_id; | ||
| 38 | } | ||
| 39 | |||
| 40 | void *pm_alloc(size_t s) { return malloc(s); } | ||
| 41 | |||
| 42 | void pm_free(void *ptr) { free(ptr); } | ||
| 43 | |||
| 44 | |||
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 | |||
| 192 | static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ | ||
| 193 | static MIDIPortRef portIn = NULL_REF; /* Input port handle */ | ||
| 194 | static MIDIPortRef portOut = NULL_REF; /* Output port handle */ | ||
| 195 | static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */ | ||
| 196 | |||
| 197 | extern pm_fns_node pm_macosx_in_dictionary; | ||
| 198 | extern pm_fns_node pm_macosx_out_dictionary; | ||
| 199 | |||
| 200 | typedef 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 */ | ||
| 221 | MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time | ||
| 222 | PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms | ||
| 223 | |||
| 224 | char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag); | ||
| 225 | |||
| 226 | static 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 | |||
| 237 | static 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 */ | ||
| 263 | static 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 */ | ||
| 321 | static 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 */ | ||
| 329 | static 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 */ | ||
| 357 | static 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 | |||
| 381 | static 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 | |||
| 416 | static 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 | |||
| 496 | static 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 | |||
| 514 | static 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 | */ | ||
| 532 | static 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 | |||
| 584 | static 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 | |||
| 599 | static 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 | |||
| 610 | static 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 | |||
| 637 | static 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 | |||
| 676 | static 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 | |||
| 741 | static 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 | |||
| 772 | static 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 | |||
| 800 | static 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 | |||
| 814 | static 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 | |||
| 825 | static unsigned int midi_check_host_error(PmInternal *midi) | ||
| 826 | { | ||
| 827 | return FALSE; | ||
| 828 | } | ||
| 829 | |||
| 830 | |||
| 831 | MIDITimeStamp 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 | |||
| 843 | PmTimestamp 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. | ||
| 856 | CFStringRef 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. | ||
| 941 | static 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 | |||
| 1004 | char *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 | |||
| 1023 | pm_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 | |||
| 1038 | pm_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 | */ | ||
| 1058 | void 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 | |||
| 1078 | PmError 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 | |||
| 1168 | error_return: | ||
| 1169 | pm_macosxcm_term(); /* clear out any opened ports */ | ||
| 1170 | return check_hosterror(macHostError, error_text); | ||
| 1171 | } | ||
| 1172 | |||
| 1173 | void 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 | |||
| 3 | PmError pm_macosxcm_init(void); | ||
| 4 | void 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 | |||
| 21 | extern pm_fns_node pm_sndio_in_dictionary; | ||
| 22 | extern pm_fns_node pm_sndio_out_dictionary; | ||
| 23 | |||
| 24 | /* length of voice and common messages (status byte included) */ | ||
| 25 | unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; | ||
| 26 | unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; | ||
| 27 | |||
| 28 | struct mio_dev { | ||
| 29 | char name[16]; | ||
| 30 | struct mio_hdl *hdl; | ||
| 31 | int mode; | ||
| 32 | char errmsg[PM_HOST_ERROR_MSG_LEN]; | ||
| 33 | pthread_t thread; | ||
| 34 | } devs[NDEVS]; | ||
| 35 | |||
| 36 | static void set_mode(struct mio_dev *, unsigned int); | ||
| 37 | |||
| 38 | void pm_init() | ||
| 39 | { | ||
| 40 | int i, j, k = 0; | ||
| 41 | char devices[][16] = {"midithru", "rmidi", "midi", "snd"}; | ||
| 42 | |||
| 43 | /* default */ | ||
| 44 | strcpy(devs[0].name, MIO_PORTANY); | ||
| 45 | pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k], | ||
| 46 | &pm_sndio_in_dictionary); | ||
| 47 | pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k], | ||
| 48 | &pm_sndio_out_dictionary); | ||
| 49 | k++; | ||
| 50 | |||
| 51 | for (i = 0; i < 4; i++) { | ||
| 52 | for (j = 0; j < 2; j++) { | ||
| 53 | sprintf(devs[k].name, "%s/%d", devices[i], j); | ||
| 54 | pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k], | ||
| 55 | &pm_sndio_in_dictionary); | ||
| 56 | pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k], | ||
| 57 | &pm_sndio_out_dictionary); | ||
| 58 | k++; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | // this is set when we return to Pm_Initialize, but we need it | ||
| 63 | // now in order to (successfully) call Pm_CountDevices() | ||
| 64 | pm_initialized = TRUE; | ||
| 65 | pm_default_input_device_id = 0; | ||
| 66 | pm_default_output_device_id = 1; | ||
| 67 | } | ||
| 68 | |||
| 69 | void pm_term(void) | ||
| 70 | { | ||
| 71 | int i; | ||
| 72 | for(i = 0; i < NDEVS; i++) { | ||
| 73 | if (devs[i].mode != 0) { | ||
| 74 | set_mode(&devs[i], 0); | ||
| 75 | if (devs[i].thread) { | ||
| 76 | pthread_join(devs[i].thread, NULL); | ||
| 77 | devs[i].thread = NULL; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | PmDeviceID Pm_GetDefaultInputDeviceID() { | ||
| 84 | Pm_Initialize(); | ||
| 85 | return pm_default_input_device_id; | ||
| 86 | } | ||
| 87 | |||
| 88 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | ||
| 89 | Pm_Initialize(); | ||
| 90 | return pm_default_output_device_id; | ||
| 91 | } | ||
| 92 | |||
| 93 | void *pm_alloc(size_t s) { return malloc(s); } | ||
| 94 | |||
| 95 | void pm_free(void *ptr) { free(ptr); } | ||
| 96 | |||
| 97 | /* midi_message_length -- how many bytes in a message? */ | ||
| 98 | static int midi_message_length(PmMessage message) | ||
| 99 | { | ||
| 100 | unsigned char st = message & 0xff; | ||
| 101 | if (st >= 0xf8) | ||
| 102 | return 1; | ||
| 103 | else if (st >= 0xf0) | ||
| 104 | return common_len[st & 7]; | ||
| 105 | else if (st >= 0x80) | ||
| 106 | return voice_len[(st >> 4) & 7]; | ||
| 107 | else | ||
| 108 | return 0; | ||
| 109 | } | ||
| 110 | |||
| 111 | void* input_thread(void *param) | ||
| 112 | { | ||
| 113 | PmInternal *midi = (PmInternal*)param; | ||
| 114 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 115 | struct pollfd pfd[1]; | ||
| 116 | nfds_t nfds; | ||
| 117 | unsigned char st = 0, c = 0; | ||
| 118 | int rc, revents, idx = 0, len = 0; | ||
| 119 | size_t todo = 0; | ||
| 120 | unsigned char buf[0x200], *p; | ||
| 121 | PmEvent pm_ev, pm_ev_rt; | ||
| 122 | unsigned char sysex_data[SYSEX_MAXLEN]; | ||
| 123 | |||
| 124 | while(dev->mode & MIO_IN) { | ||
| 125 | if (todo == 0) { | ||
| 126 | nfds = mio_pollfd(dev->hdl, pfd, POLLIN); | ||
| 127 | rc = poll(pfd, nfds, 100); | ||
| 128 | if (rc < 0) { | ||
| 129 | if (errno == EINTR) | ||
| 130 | continue; | ||
| 131 | break; | ||
| 132 | } | ||
| 133 | revents = mio_revents(dev->hdl, pfd); | ||
| 134 | if (!(revents & POLLIN)) | ||
| 135 | continue; | ||
| 136 | |||
| 137 | todo = mio_read(dev->hdl, buf, sizeof(buf)); | ||
| 138 | if (todo == 0) | ||
| 139 | continue; | ||
| 140 | p = buf; | ||
| 141 | } | ||
| 142 | c = *p++; | ||
| 143 | todo--; | ||
| 144 | |||
| 145 | if (c >= 0xf8) { | ||
| 146 | pm_ev_rt.message = c; | ||
| 147 | pm_ev_rt.timestamp = Pt_Time(); | ||
| 148 | pm_read_short(midi, &pm_ev_rt); | ||
| 149 | } else if (c == SYSEX_END) { | ||
| 150 | /* note: PortMidi is designed to avoid the need for SYSEX_MAXLEN. | ||
| 151 | With the new implementation of pm_read_bytes, it would be | ||
| 152 | better to simply call pm_read_bytes() and let it parse buf, | ||
| 153 | which can contain any number of whole or partial messages with | ||
| 154 | interleaved realtime messages. I did not change the code because | ||
| 155 | I cannot test it. -RBD */ | ||
| 156 | if (st == SYSEX_START) { | ||
| 157 | sysex_data[idx++] = c; | ||
| 158 | pm_read_bytes(midi, sysex_data, idx, Pt_Time()); | ||
| 159 | } | ||
| 160 | st = 0; | ||
| 161 | idx = 0; | ||
| 162 | } else if (c == SYSEX_START) { | ||
| 163 | st = c; | ||
| 164 | idx = 0; | ||
| 165 | sysex_data[idx++] = c; | ||
| 166 | } else if (c >= 0xf0) { | ||
| 167 | pm_ev.message = c; | ||
| 168 | len = common_len[c & 7]; | ||
| 169 | st = c; | ||
| 170 | idx = 1; | ||
| 171 | } else if (c >= 0x80) { | ||
| 172 | pm_ev.message = c; | ||
| 173 | len = voice_len[(c >> 4) & 7]; | ||
| 174 | st = c; | ||
| 175 | idx = 1; | ||
| 176 | } else if (st == SYSEX_START) { | ||
| 177 | if (idx == SYSEX_MAXLEN) { | ||
| 178 | fprintf(stderr, "the message is too long\n"); | ||
| 179 | idx = st = 0; | ||
| 180 | } else { | ||
| 181 | sysex_data[idx++] = c; | ||
| 182 | } | ||
| 183 | } else if (st) { | ||
| 184 | if (idx == 0 && st != SYSEX_START) | ||
| 185 | pm_ev.message |= (c << (8 * idx++)); | ||
| 186 | pm_ev.message |= (c << (8 * idx++)); | ||
| 187 | if (idx == len) { | ||
| 188 | pm_read_short(midi, &pm_ev); | ||
| 189 | if (st >= 0xf0) | ||
| 190 | st = 0; | ||
| 191 | idx = 0; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | pthread_exit(NULL); | ||
| 197 | return NULL; | ||
| 198 | } | ||
| 199 | |||
| 200 | static void set_mode(struct mio_dev *dev, unsigned int mode) { | ||
| 201 | if (dev->mode != 0) | ||
| 202 | mio_close(dev->hdl); | ||
| 203 | dev->mode = 0; | ||
| 204 | if (mode != 0) | ||
| 205 | dev->hdl = mio_open(dev->name, mode, 0); | ||
| 206 | if (dev->hdl) | ||
| 207 | dev->mode = mode; | ||
| 208 | } | ||
| 209 | |||
| 210 | static PmError sndio_out_open(PmInternal *midi, void *driverInfo) | ||
| 211 | { | ||
| 212 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 213 | |||
| 214 | if (dev->mode & MIO_OUT) | ||
| 215 | return pmNoError; | ||
| 216 | |||
| 217 | set_mode(dev, dev->mode | MIO_OUT); | ||
| 218 | if (!(dev->mode & MIO_OUT)) { | ||
| 219 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, | ||
| 220 | "mio_open (output) failed: %s\n", dev->name); | ||
| 221 | return pmHostError; | ||
| 222 | } | ||
| 223 | |||
| 224 | return pmNoError; | ||
| 225 | } | ||
| 226 | |||
| 227 | static PmError sndio_in_open(PmInternal *midi, void *driverInfo) | ||
| 228 | { | ||
| 229 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 230 | |||
| 231 | if (dev->mode & MIO_IN) | ||
| 232 | return pmNoError; | ||
| 233 | |||
| 234 | set_mode(dev, dev->mode | MIO_IN); | ||
| 235 | if (!(dev->mode & MIO_IN)) { | ||
| 236 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, | ||
| 237 | "mio_open (input) failed: %s\n", dev->name); | ||
| 238 | return pmHostError; | ||
| 239 | } | ||
| 240 | pthread_attr_t attr; | ||
| 241 | pthread_attr_init(&attr); | ||
| 242 | pthread_create(&dev->thread, &attr, input_thread, ( void* )midi); | ||
| 243 | return pmNoError; | ||
| 244 | } | ||
| 245 | |||
| 246 | static PmError sndio_out_close(PmInternal *midi) | ||
| 247 | { | ||
| 248 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 249 | |||
| 250 | if (dev->mode & MIO_OUT) | ||
| 251 | set_mode(dev, dev->mode & ~MIO_OUT); | ||
| 252 | return pmNoError; | ||
| 253 | } | ||
| 254 | |||
| 255 | static PmError sndio_in_close(PmInternal *midi) | ||
| 256 | { | ||
| 257 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 258 | |||
| 259 | if (dev->mode & MIO_IN) { | ||
| 260 | set_mode(dev, dev->mode & ~MIO_IN); | ||
| 261 | pthread_join(dev->thread, NULL); | ||
| 262 | dev->thread = NULL; | ||
| 263 | } | ||
| 264 | return pmNoError; | ||
| 265 | } | ||
| 266 | |||
| 267 | static PmError sndio_abort(PmInternal *midi) | ||
| 268 | { | ||
| 269 | return pmNoError; | ||
| 270 | } | ||
| 271 | |||
| 272 | static PmTimestamp sndio_synchronize(PmInternal *midi) | ||
| 273 | { | ||
| 274 | return 0; | ||
| 275 | } | ||
| 276 | |||
| 277 | static PmError do_write(struct mio_dev *dev, const void *addr, size_t nbytes) | ||
| 278 | { | ||
| 279 | size_t w = mio_write(dev->hdl, addr, nbytes); | ||
| 280 | |||
| 281 | if (w != nbytes) { | ||
| 282 | snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, | ||
| 283 | "mio_write failed, bytes written:%zu\n", w); | ||
| 284 | return pmHostError; | ||
| 285 | } | ||
| 286 | return pmNoError; | ||
| 287 | } | ||
| 288 | |||
| 289 | static PmError sndio_write_byte(PmInternal *midi, unsigned char byte, | ||
| 290 | PmTimestamp timestamp) | ||
| 291 | { | ||
| 292 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 293 | |||
| 294 | return do_write(dev, &byte, 1); | ||
| 295 | } | ||
| 296 | |||
| 297 | static PmError sndio_write_short(PmInternal *midi, PmEvent *event) | ||
| 298 | { | ||
| 299 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 300 | int nbytes = midi_message_length(event->message); | ||
| 301 | |||
| 302 | if (midi->latency > 0) { | ||
| 303 | /* XXX the event should be queued for later playback */ | ||
| 304 | return do_write(dev, &event->message, nbytes); | ||
| 305 | } else { | ||
| 306 | return do_write(dev, &event->message, nbytes); | ||
| 307 | } | ||
| 308 | return pmNoError; | ||
| 309 | } | ||
| 310 | |||
| 311 | static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp) | ||
| 312 | { | ||
| 313 | return pmNoError; | ||
| 314 | } | ||
| 315 | |||
| 316 | PmError sndio_sysex(PmInternal *midi, PmTimestamp timestamp) | ||
| 317 | { | ||
| 318 | return pmNoError; | ||
| 319 | } | ||
| 320 | |||
| 321 | static unsigned int sndio_has_host_error(PmInternal *midi) | ||
| 322 | { | ||
| 323 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 324 | |||
| 325 | return (dev->errmsg[0] != '\0'); | ||
| 326 | } | ||
| 327 | |||
| 328 | static void sndio_get_host_error(PmInternal *midi, char *msg, unsigned int len) | ||
| 329 | { | ||
| 330 | struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor; | ||
| 331 | |||
| 332 | strlcpy(msg, dev->errmsg, len); | ||
| 333 | dev->errmsg[0] = '\0'; | ||
| 334 | } | ||
| 335 | |||
| 336 | pm_fns_node pm_sndio_in_dictionary = { | ||
| 337 | none_write_short, | ||
| 338 | none_sysex, | ||
| 339 | none_sysex, | ||
| 340 | none_write_byte, | ||
| 341 | none_write_short, | ||
| 342 | none_write_flush, | ||
| 343 | sndio_synchronize, | ||
| 344 | sndio_in_open, | ||
| 345 | sndio_abort, | ||
| 346 | sndio_in_close, | ||
| 347 | success_poll, | ||
| 348 | sndio_has_host_error, | ||
| 349 | }; | ||
| 350 | |||
| 351 | pm_fns_node pm_sndio_out_dictionary = { | ||
| 352 | sndio_write_short, | ||
| 353 | sndio_sysex, | ||
| 354 | sndio_sysex, | ||
| 355 | sndio_write_byte, | ||
| 356 | sndio_write_short, | ||
| 357 | sndio_write_flush, | ||
| 358 | sndio_synchronize, | ||
| 359 | sndio_out_open, | ||
| 360 | sndio_abort, | ||
| 361 | sndio_out_close, | ||
| 362 | none_poll, | ||
| 363 | sndio_has_host_error, | ||
| 364 | }; | ||
| 365 | |||
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 | |||
| 3 | extern PmDeviceID pm_default_input_device_id; | ||
| 4 | extern 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: | ||
| 5 | if(APPLE) | ||
| 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) | ||
| 7 | endif(APPLE) | ||
| 8 | |||
| 9 | # if(WIN32) | ||
| 10 | # if(NOT BUILD_SHARED_LIBS) | ||
| 11 | # /MDd is multithread debug DLL, /MTd is multithread debug | ||
| 12 | # /MD is multithread DLL, /MT is multithread. Change to static: | ||
| 13 | # include(../pm_win/static.cmake) | ||
| 14 | # endif() | ||
| 15 | # endif(WIN32) | ||
| 16 | |||
| 17 | if(HAIKU) | ||
| 18 | add_compile_options(-fPIC) # Haiku x86_64 needs this explicitly | ||
| 19 | endif() | ||
| 20 | |||
| 21 | macro(add_test name) | ||
| 22 | add_executable(${name} ${name}.c) | ||
| 23 | target_link_libraries(${name} PRIVATE portmidi) | ||
| 24 | set_property(TARGET ${name} PROPERTY MSVC_RUNTIME_LIBRARY | ||
| 25 | "MultiThreaded$<$<CONFIG:Debug>:Debug>${MSVCRT_DLL}") | ||
| 26 | endmacro(add_test) | ||
| 27 | |||
| 28 | add_test(testio) | ||
| 29 | add_test(midithread) | ||
| 30 | add_test(midithru) | ||
| 31 | add_test(sysex) | ||
| 32 | add_test(latency) | ||
| 33 | add_test(mm) | ||
| 34 | add_test(midiclock) | ||
| 35 | add_test(qtest) | ||
| 36 | add_test(fast) | ||
| 37 | add_test(fastrcv) | ||
| 38 | add_test(pmlist) | ||
| 39 | if(WIN32) | ||
| 40 | # windows does not implement Pm_CreateVirtualInput or Pm_CreateVirtualOutput | ||
| 41 | else(WIN32) | ||
| 42 | add_test(recvvirtual) | ||
| 43 | add_test(sendvirtual) | ||
| 44 | add_test(multivirtual) | ||
| 45 | add_test(virttest) | ||
| 46 | endif(WIN32) | ||
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 @@ | |||
| 1 | README.txt - for pm_test directory | ||
| 2 | |||
| 3 | These are all test programs for PortMidi | ||
| 4 | |||
| 5 | Because device numbers depend on the system, there is no automated | ||
| 6 | script to run all tests on PortMidi. | ||
| 7 | |||
| 8 | To run the full set of tests manually: | ||
| 9 | |||
| 10 | Note: everything is run from the ../Debug or ../Release directory. | ||
| 11 | Actual or example input is marked with >>, e.g., >>0 means type 0<ENTER> | ||
| 12 | Comments are shown in square brackets [like this] | ||
| 13 | |||
| 14 | 1. ./qtest -- output should show a bunch of tests and no error message. | ||
| 15 | |||
| 16 | 2. ./testio [test input] | ||
| 17 | Latency in ms: >>0 | ||
| 18 | enter your choice... >>1 | ||
| 19 | Type input number: >>6 [pick a working input device] | ||
| 20 | [play some notes, look for note-on (0x90) with pitch and velocity data] | ||
| 21 | |||
| 22 | 3. ./testio [test input (fail w/assert)] | ||
| 23 | Latency in ms: >>0 | ||
| 24 | enter your choice... >>2 | ||
| 25 | Type 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 | ||
| 28 | the assert() macro is disabled.)] | ||
| 29 | |||
| 30 | 4. ./testio [test input (fail w/NULL assign)] | ||
| 31 | Latency in ms: >>0 | ||
| 32 | enter your choice... >>3 | ||
| 33 | Type 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 | ||
| 36 | try testing with a Debug build.)] | ||
| 37 | |||
| 38 | 5. ./testio [test output, no latency] | ||
| 39 | Latency in ms: >>0 | ||
| 40 | enter your choice... >>4 | ||
| 41 | Type 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 | |||
| 45 | 6. ./testio [test output, latency > 0] | ||
| 46 | Latency in ms: >>300 | ||
| 47 | enter your choice... >>4 | ||
| 48 | Type 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 | |||
| 53 | 7. ./testio [for both, no latency] | ||
| 54 | Latency in ms: >>0 | ||
| 55 | enter your choice... >>5 | ||
| 56 | Type input number: >>6 [pick a working input device] | ||
| 57 | Type output number: >>2 [pick a working output device] | ||
| 58 | [play notes on input, hear them on output] | ||
| 59 | |||
| 60 | 8. ./testio [for both, latency > 0] | ||
| 61 | Latency in ms: >>300 | ||
| 62 | enter your choice... >>5 | ||
| 63 | Type input number: >>6 [pick a working input device] | ||
| 64 | Type output number: >>2 [pick a working output device] | ||
| 65 | [play notes on input, hear them on output (delay of 300ms is apparent)] | ||
| 66 | |||
| 67 | 9. ./testio [stream test] | ||
| 68 | Latency in ms: >>0 [does not matter] | ||
| 69 | enter your choice... >>6 | ||
| 70 | Type 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] | ||
| 73 | ready to close and terminate... (type ENTER) :>> [type ENTER (twice)] | ||
| 74 | |||
| 75 | 10. ./testio [isochronous out] | ||
| 76 | Latency in ms: >>300 | ||
| 77 | enter your choice... >>7 | ||
| 78 | Type output number: >>2 [pick a working output device] | ||
| 79 | ready to send program 1 change... (type ENTER): >> [type ENTER] | ||
| 80 | [hear 80 notes, exactly 4 notes per second, no jitter] | ||
| 81 | |||
| 82 | 11. ./latency [no MIDI, histogram] | ||
| 83 | Choose 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 | |||
| 89 | 12. ./latency [MIDI input, histogram] | ||
| 90 | Choose timer period (in ms, >= 1): >>1 | ||
| 91 | ? >>2 [MIDI input option] | ||
| 92 | Midi 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 | |||
| 97 | 13. ./latency [MIDI output, histogram] | ||
| 98 | Choose timer period (in ms, >= 1): >>1 | ||
| 99 | ? >>3 [MIDI output option] | ||
| 100 | Midi output device number: >>2 [pick a working output device] | ||
| 101 | Midi 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 | |||
| 106 | 14. ./latency [MIDI input and output, histogram] | ||
| 107 | Choose timer period (in ms, >= 1): >>1 | ||
| 108 | ? >>4 [MIDI input and output option] | ||
| 109 | Midi input device number: >>6 [pick a working input device] | ||
| 110 | Midi output device number: >>2 [pick a working output device] | ||
| 111 | Midi 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 | |||
| 116 | 15. ./mm [test with device input] | ||
| 117 | Type 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 | |||
| 121 | 16. ./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 | |||
| 126 | 17. ./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 | |||
| 150 | 18. ./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 | |||
| 156 | 19. ./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 | |||
| 169 | 20. ./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 | |||
| 181 | 21. ./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] | ||
| 186 | Type output device number: >>0 [pick output device to loopback] | ||
| 187 | Latency in milliseconds: >>0 | ||
| 188 | Type 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 | |||
| 192 | 22. ./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] | ||
| 197 | Type output device number: >>0 [pick output device to loopback] | ||
| 198 | Latency in milliseconds: >>100 | ||
| 199 | Type 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 | |||
| 203 | 23. ./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 | |||
| 221 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 222 | |||
| 223 | 24. ./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 | |||
| 241 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 242 | |||
| 243 | 25. ./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 | |||
| 259 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 260 | |||
| 261 | 26. ./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 | |||
| 277 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 278 | |||
| 279 | 27. ./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 | |||
| 292 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 293 | |||
| 294 | 28. ./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 | |||
| 307 | Check output of fastrcv: there should be no errors, just msg/sec.] | ||
| 308 | |||
| 309 | 29. ./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 | |||
| 327 | 30. ./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 | |||
| 349 | 31. ./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 | |||
| 354 | 32. 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 | |||
| 37 | int32_t latency = 0; | ||
| 38 | int32_t msgrate = 0; | ||
| 39 | int deviceno = -9999; | ||
| 40 | int duration = 0; | ||
| 41 | int expired_timestamps = FALSE; | ||
| 42 | int use_timeoffset = 0; | ||
| 43 | |||
| 44 | /* read a number from console */ | ||
| 45 | /**/ | ||
| 46 | int get_number(const char *prompt) | ||
| 47 | { | ||
| 48 | int n = 0, i; | ||
| 49 | fputs(prompt, stdout); | ||
| 50 | while (n != 1) { | ||
| 51 | n = scanf("%d", &i); | ||
| 52 | while (getchar() != '\n') ; | ||
| 53 | } | ||
| 54 | return i; | ||
| 55 | } | ||
| 56 | |||
| 57 | |||
| 58 | /* get_time -- the time reference. Normally, this will be the default | ||
| 59 | * time, Pt_Time(), but if you use the -p or -m option, the time | ||
| 60 | * reference will start at an offset of -10s for -m, or | ||
| 61 | * maximum_time - 10s for -p, so that we can observe what happens | ||
| 62 | * with negative time or when time changes sign or wraps (by | ||
| 63 | * generating output for more than 10s). | ||
| 64 | */ | ||
| 65 | PmTimestamp get_time(void *info) | ||
| 66 | { | ||
| 67 | PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset); | ||
| 68 | return now; | ||
| 69 | } | ||
| 70 | |||
| 71 | |||
| 72 | void fast_test() | ||
| 73 | { | ||
| 74 | PmStream *midi; | ||
| 75 | char line[STRING_MAX]; | ||
| 76 | int pause = FALSE; /* pause if this is a virtual output port */ | ||
| 77 | PmError err = pmNoError; | ||
| 78 | /* output buffer size should be a little more than | ||
| 79 | msgrate * latency / 1000. PortMidi will guarantee | ||
| 80 | a minimum of latency / 2 */ | ||
| 81 | int buffer_size = msgrate * latency / 900; | ||
| 82 | PmTimestamp start, now; | ||
| 83 | int msgcnt = 0; | ||
| 84 | int polling_count = 0; | ||
| 85 | int pitch = 60; | ||
| 86 | int printtime = 1000; | ||
| 87 | |||
| 88 | /* It is recommended to start timer before PortMidi */ | ||
| 89 | TIME_START; | ||
| 90 | |||
| 91 | /* open output device */ | ||
| 92 | if (deviceno == Pm_CountDevices()) { | ||
| 93 | deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO); | ||
| 94 | if (deviceno >= 0) { | ||
| 95 | err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, | ||
| 96 | get_time, NULL, latency); | ||
| 97 | pause = TRUE; | ||
| 98 | } | ||
| 99 | } else if (err >= pmNoError) { | ||
| 100 | err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, | ||
| 101 | get_time, NULL, latency); | ||
| 102 | } | ||
| 103 | if (err == pmHostError) { | ||
| 104 | Pm_GetHostErrorText(line, STRING_MAX); | ||
| 105 | printf("PortMidi found host error...\n %s\n", line); | ||
| 106 | goto done; | ||
| 107 | } else if (err < 0) { | ||
| 108 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 109 | goto done; | ||
| 110 | } | ||
| 111 | printf("Midi Output opened with %ld ms latency.\n", (long) latency); | ||
| 112 | if (pause) { | ||
| 113 | printf("Pausing so you can connect a receiver to the newly created\n" | ||
| 114 | " \"fast\" port. Type ENTER to proceed: "); | ||
| 115 | while (getchar() != '\n') ; | ||
| 116 | } | ||
| 117 | /* wait a sec after printing previous line */ | ||
| 118 | start = get_time(NULL) + 1000; | ||
| 119 | while (start > get_time(NULL)) { | ||
| 120 | Sleep(10); | ||
| 121 | } | ||
| 122 | printf("sending output...\n"); | ||
| 123 | fflush(stdout); /* make sure message goes to console */ | ||
| 124 | |||
| 125 | /* every 10ms send on/off pairs at timestamps set to current time */ | ||
| 126 | now = get_time(NULL); | ||
| 127 | /* if expired_timestamps, we want to send timestamps that have | ||
| 128 | * expired. They should be sent immediately, but there's a suggestion | ||
| 129 | * that negative delay might cause problems in the ALSA implementation | ||
| 130 | * so this is something we can test using the -n flag. | ||
| 131 | */ | ||
| 132 | if (expired_timestamps) { | ||
| 133 | now = now - 2 * latency; | ||
| 134 | } | ||
| 135 | |||
| 136 | while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) { | ||
| 137 | /* how many messages do we send? Total should be | ||
| 138 | * (elapsed * rate) / 1000 | ||
| 139 | */ | ||
| 140 | int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000; | ||
| 141 | /* always send until pitch would be 60 so if we run again, the | ||
| 142 | next pitch (60) will be expected */ | ||
| 143 | if (msgcnt < send_total) { | ||
| 144 | if ((msgcnt & 1) == 0) { | ||
| 145 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100)); | ||
| 146 | } else { | ||
| 147 | Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0)); | ||
| 148 | /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */ | ||
| 149 | pitch = (pitch - 59) % 12 + 60; | ||
| 150 | } | ||
| 151 | msgcnt += 1; | ||
| 152 | if (((PmTimestamp) (now - start)) >= printtime) { | ||
| 153 | printf("%d at %dms, polling count %d\n", msgcnt, now - start, | ||
| 154 | polling_count); | ||
| 155 | fflush(stdout); /* make sure message goes to console */ | ||
| 156 | printtime += 1000; /* next msg in 1s */ | ||
| 157 | } | ||
| 158 | } | ||
| 159 | now = get_time(NULL); | ||
| 160 | polling_count++; | ||
| 161 | } | ||
| 162 | /* close device (this not explicitly needed in most implementations) */ | ||
| 163 | printf("ready to close and terminate... (type RETURN):"); | ||
| 164 | while (getchar() != '\n') ; | ||
| 165 | |||
| 166 | Pm_Close(midi); | ||
| 167 | done: | ||
| 168 | Pm_Terminate(); | ||
| 169 | printf("done closing and terminating...\n"); | ||
| 170 | } | ||
| 171 | |||
| 172 | |||
| 173 | void show_usage() | ||
| 174 | { | ||
| 175 | printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] " | ||
| 176 | "[-n] [-p] [-m]\n" | ||
| 177 | ", where latency is in ms,\n" | ||
| 178 | " rate is messages per second,\n" | ||
| 179 | " device is the PortMidi device number,\n" | ||
| 180 | " dur is the length of the test in seconds,\n" | ||
| 181 | " -n means send timestamps in the past,\n" | ||
| 182 | " -p means use a large positive time offset,\n" | ||
| 183 | " -m means use a large negative time offset, and\n" | ||
| 184 | " -h means help.\n"); | ||
| 185 | } | ||
| 186 | |||
| 187 | int main(int argc, char *argv[]) | ||
| 188 | { | ||
| 189 | int default_in; | ||
| 190 | int default_out; | ||
| 191 | char *deflt; | ||
| 192 | int i = 0; | ||
| 193 | int latency_valid = FALSE; | ||
| 194 | int rate_valid = FALSE; | ||
| 195 | int device_valid = FALSE; | ||
| 196 | int dur_valid = FALSE; | ||
| 197 | |||
| 198 | if (sizeof(void *) == 8) | ||
| 199 | printf("Apparently this is a 64-bit machine.\n"); | ||
| 200 | else if (sizeof(void *) == 4) | ||
| 201 | printf ("Apparently this is a 32-bit machine.\n"); | ||
| 202 | |||
| 203 | if (argc <= 1) { | ||
| 204 | show_usage(); | ||
| 205 | } else { | ||
| 206 | for (i = 1; i < argc; i++) { | ||
| 207 | if (strcmp(argv[i], "-h") == 0) { | ||
| 208 | show_usage(); | ||
| 209 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { | ||
| 210 | i = i + 1; | ||
| 211 | latency = atoi(argv[i]); | ||
| 212 | printf("Latency will be %ld\n", (long) latency); | ||
| 213 | latency_valid = TRUE; | ||
| 214 | } else if (strcmp(argv[i], "-r") == 0) { | ||
| 215 | i = i + 1; | ||
| 216 | msgrate = atoi(argv[i]); | ||
| 217 | printf("Rate will be %d messages/second\n", msgrate); | ||
| 218 | rate_valid = TRUE; | ||
| 219 | } else if (strcmp(argv[i], "-d") == 0) { | ||
| 220 | i = i + 1; | ||
| 221 | deviceno = atoi(argv[i]); | ||
| 222 | printf("Device will be %d\n", deviceno); | ||
| 223 | } else if (strcmp(argv[i], "-s") == 0) { | ||
| 224 | i = i + 1; | ||
| 225 | duration = atoi(argv[i]); | ||
| 226 | printf("Duration will be %d seconds\n", duration); | ||
| 227 | dur_valid = TRUE; | ||
| 228 | } else if (strcmp(argv[i], "-n") == 0) { | ||
| 229 | printf("Sending expired timestamps (-n)\n"); | ||
| 230 | expired_timestamps = TRUE; | ||
| 231 | } else if (strcmp(argv[i], "-p") == 0) { | ||
| 232 | printf("Time offset set to 2147473648 (-p)\n"); | ||
| 233 | use_timeoffset = 2147473648; | ||
| 234 | } else if (strcmp(argv[i], "-m") == 0) { | ||
| 235 | printf("Time offset set to -10000 (-m)\n"); | ||
| 236 | use_timeoffset = -10000; | ||
| 237 | } else { | ||
| 238 | show_usage(); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | if (!latency_valid) { | ||
| 244 | // coerce to known size | ||
| 245 | latency = (int32_t) get_number("Latency in ms: "); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (!rate_valid) { | ||
| 249 | // coerce from "%d" to known size | ||
| 250 | msgrate = (int32_t) get_number("Rate in messages per second: "); | ||
| 251 | } | ||
| 252 | |||
| 253 | if (!dur_valid) { | ||
| 254 | duration = get_number("Duration in seconds: "); | ||
| 255 | } | ||
| 256 | |||
| 257 | /* list device information */ | ||
| 258 | default_in = Pm_GetDefaultInputDeviceID(); | ||
| 259 | default_out = Pm_GetDefaultOutputDeviceID(); | ||
| 260 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 261 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 262 | if (info->output) { | ||
| 263 | printf("%d: %s, %s", i, info->interf, info->name); | ||
| 264 | if (i == deviceno) { | ||
| 265 | device_valid = TRUE; | ||
| 266 | deflt = "selected "; | ||
| 267 | } else if (i == default_out) { | ||
| 268 | deflt = "default "; | ||
| 269 | } else { | ||
| 270 | deflt = ""; | ||
| 271 | } | ||
| 272 | printf(" (%soutput)\n", deflt); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | printf("%d: Create virtual port named \"fast\"", i); | ||
| 276 | if (i == deviceno) { | ||
| 277 | device_valid = TRUE; | ||
| 278 | deflt = "selected "; | ||
| 279 | } else { | ||
| 280 | deflt = ""; | ||
| 281 | } | ||
| 282 | printf(" (%soutput)\n", deflt); | ||
| 283 | |||
| 284 | if (!device_valid) { | ||
| 285 | deviceno = get_number("Output device number: "); | ||
| 286 | } | ||
| 287 | |||
| 288 | fast_test(); | ||
| 289 | return 0; | ||
| 290 | } | ||
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 | |||
| 37 | int deviceno = -9999; | ||
| 38 | int verbose = FALSE; | ||
| 39 | |||
| 40 | |||
| 41 | static void prompt_and_exit(void) | ||
| 42 | { | ||
| 43 | printf("type ENTER..."); | ||
| 44 | while (getchar() != '\n') ; | ||
| 45 | /* this will clean up open ports: */ | ||
| 46 | exit(-1); | ||
| 47 | } | ||
| 48 | |||
| 49 | |||
| 50 | static PmError checkerror(PmError err) | ||
| 51 | { | ||
| 52 | if (err == pmHostError) { | ||
| 53 | /* it seems pointless to allocate memory and copy the string, | ||
| 54 | * so I will do the work of Pm_GetHostErrorText directly | ||
| 55 | */ | ||
| 56 | char errmsg[80]; | ||
| 57 | Pm_GetHostErrorText(errmsg, 80); | ||
| 58 | printf("PortMidi found host error...\n %s\n", errmsg); | ||
| 59 | prompt_and_exit(); | ||
| 60 | } else if (err < 0) { | ||
| 61 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 62 | prompt_and_exit(); | ||
| 63 | } | ||
| 64 | return err; | ||
| 65 | } | ||
| 66 | |||
| 67 | |||
| 68 | /* read a number from console */ | ||
| 69 | /**/ | ||
| 70 | int get_number(const char *prompt) | ||
| 71 | { | ||
| 72 | int n = 0, i; | ||
| 73 | fputs(prompt, stdout); | ||
| 74 | while (n != 1) { | ||
| 75 | n = scanf("%d", &i); | ||
| 76 | while (getchar() != '\n') ; | ||
| 77 | } | ||
| 78 | return i; | ||
| 79 | } | ||
| 80 | |||
| 81 | |||
| 82 | void fastrcv_test() | ||
| 83 | { | ||
| 84 | PmStream * midi; | ||
| 85 | PmError status, length; | ||
| 86 | PmEvent buffer[1]; | ||
| 87 | PmTimestamp start; | ||
| 88 | /* every 10ms read all messages, keep counts */ | ||
| 89 | /* every 1000ms, print report */ | ||
| 90 | int msgcnt = 0; | ||
| 91 | /* expect repeating sequence of 60 through 71, alternating on/off */ | ||
| 92 | int expected_pitch = 60; | ||
| 93 | int expected_on = TRUE; | ||
| 94 | int report_time; | ||
| 95 | PmTimestamp last_timestamp = -1; | ||
| 96 | PmTimestamp last_delta = -1; | ||
| 97 | |||
| 98 | /* It is recommended to start timer before PortMidi */ | ||
| 99 | TIME_START; | ||
| 100 | |||
| 101 | /* open output device */ | ||
| 102 | if (deviceno == Pm_CountDevices()) { | ||
| 103 | int id = Pm_CreateVirtualInput("fastrcv", NULL, DEVICE_INFO); | ||
| 104 | if (id < 0) checkerror(id); /* error reporting */ | ||
| 105 | checkerror(Pm_OpenInput(&midi, id, DRIVER_INFO, | ||
| 106 | INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO)); | ||
| 107 | } else { | ||
| 108 | Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE, | ||
| 109 | TIME_PROC, TIME_INFO); | ||
| 110 | } | ||
| 111 | printf("Midi Input opened.\n"); | ||
| 112 | |||
| 113 | /* wait a sec after printing previous line */ | ||
| 114 | start = Pt_Time() + 1000; | ||
| 115 | while (start > Pt_Time()) { | ||
| 116 | Sleep(10); | ||
| 117 | } | ||
| 118 | |||
| 119 | report_time = Pt_Time() + 1000; /* report every 1s */ | ||
| 120 | while (TRUE) { | ||
| 121 | PmTimestamp now = Pt_Time(); | ||
| 122 | status = Pm_Poll(midi); | ||
| 123 | if (status == TRUE) { | ||
| 124 | length = Pm_Read(midi, buffer, 1); | ||
| 125 | if (length > 0) { | ||
| 126 | int status = Pm_MessageStatus(buffer[0].message); | ||
| 127 | if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */ | ||
| 128 | status = 0x90; | ||
| 129 | buffer[0].message = Pm_Message(status, | ||
| 130 | Pm_MessageData1(buffer[0].message), 0); | ||
| 131 | } | ||
| 132 | /* only listen to NOTEON messages */ | ||
| 133 | if (status == 0x90) { | ||
| 134 | int pitch = Pm_MessageData1(buffer[0].message); | ||
| 135 | int vel = Pm_MessageData2(buffer[0].message); | ||
| 136 | int is_on = (vel > 0); | ||
| 137 | if (verbose) { | ||
| 138 | printf("Note pitch %d vel %d\n", pitch, vel); | ||
| 139 | } | ||
| 140 | msgcnt++; | ||
| 141 | if (pitch != expected_pitch || expected_on != is_on) { | ||
| 142 | printf("Unexpected note-on: pitch %d vel %d, " | ||
| 143 | "expected: pitch %d Note%s\n", pitch, vel, | ||
| 144 | expected_pitch, (expected_on ? "On" : "Off")); | ||
| 145 | } | ||
| 146 | if (is_on) { | ||
| 147 | expected_on = FALSE; | ||
| 148 | expected_pitch = pitch; | ||
| 149 | } else { | ||
| 150 | expected_on = TRUE; | ||
| 151 | expected_pitch = (pitch + 1) % 72; | ||
| 152 | if (expected_pitch < 60) expected_pitch = 60; | ||
| 153 | } | ||
| 154 | if (last_timestamp >= 0) { | ||
| 155 | last_delta = buffer[0].timestamp - last_timestamp; | ||
| 156 | } | ||
| 157 | last_timestamp = buffer[0].timestamp; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | if (now >= report_time) { | ||
| 162 | printf("%d msgs/sec", msgcnt); | ||
| 163 | /* if available, print the last timestamp and last delta time */ | ||
| 164 | if (last_timestamp >= 0) { | ||
| 165 | printf(" last timestamp %d", (int) last_timestamp); | ||
| 166 | last_timestamp = -1; | ||
| 167 | } | ||
| 168 | if (last_delta >= 0) { | ||
| 169 | printf(" last delta time %d", (int) last_delta); | ||
| 170 | last_delta = -1; | ||
| 171 | } | ||
| 172 | printf("\n"); | ||
| 173 | report_time += 1000; | ||
| 174 | msgcnt = 0; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | |||
| 180 | void show_usage() | ||
| 181 | { | ||
| 182 | printf("Usage: fastrcv [-h] [-v] [-d device], where\n" | ||
| 183 | "device is the PortMidi device number,\n" | ||
| 184 | "-h means help,\n" | ||
| 185 | "-v means verbose (print messages)\n"); | ||
| 186 | } | ||
| 187 | |||
| 188 | int main(int argc, char *argv[]) | ||
| 189 | { | ||
| 190 | int default_in; | ||
| 191 | int default_out; | ||
| 192 | char *deflt; | ||
| 193 | |||
| 194 | int i = 0; | ||
| 195 | int test_input = 0, test_output = 0, test_both = 0; | ||
| 196 | int stream_test = 0; | ||
| 197 | int device_valid = FALSE; | ||
| 198 | |||
| 199 | if (sizeof(void *) == 8) | ||
| 200 | printf("Apparently this is a 64-bit machine.\n"); | ||
| 201 | else if (sizeof(void *) == 4) | ||
| 202 | printf ("Apparently this is a 32-bit machine.\n"); | ||
| 203 | |||
| 204 | if (argc <= 1) { | ||
| 205 | show_usage(); | ||
| 206 | } else { | ||
| 207 | for (i = 1; i < argc; i++) { | ||
| 208 | if (strcmp(argv[i], "-h") == 0) { | ||
| 209 | show_usage(); | ||
| 210 | } else if (strcmp(argv[i], "-v") == 0) { | ||
| 211 | verbose = TRUE; | ||
| 212 | } else if (strcmp(argv[i], "-d") == 0) { | ||
| 213 | i = i + 1; | ||
| 214 | deviceno = atoi(argv[i]); | ||
| 215 | printf("Device will be %d\n", deviceno); | ||
| 216 | } else { | ||
| 217 | show_usage(); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | /* list device information */ | ||
| 223 | default_in = Pm_GetDefaultInputDeviceID(); | ||
| 224 | default_out = Pm_GetDefaultOutputDeviceID(); | ||
| 225 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 226 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 227 | if (!info->output) { | ||
| 228 | printf("%d: %s, %s", i, info->interf, info->name); | ||
| 229 | if (i == deviceno) { | ||
| 230 | device_valid = TRUE; | ||
| 231 | deflt = "selected "; | ||
| 232 | } else if (i == default_out) { | ||
| 233 | deflt = "default "; | ||
| 234 | } else { | ||
| 235 | deflt = ""; | ||
| 236 | } | ||
| 237 | printf(" (%sinput)\n", deflt); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | printf("%d: Create virtual port named \"fastrcv\"", i); | ||
| 241 | if (i == deviceno) { | ||
| 242 | device_valid = TRUE; | ||
| 243 | deflt = "selected "; | ||
| 244 | } else { | ||
| 245 | deflt = ""; | ||
| 246 | } | ||
| 247 | printf(" (%sinput)\n", deflt); | ||
| 248 | |||
| 249 | if (!device_valid) { | ||
| 250 | deviceno = get_number("Input device number: "); | ||
| 251 | } | ||
| 252 | |||
| 253 | fastrcv_test(); | ||
| 254 | return 0; | ||
| 255 | } | ||
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 | |||
| 70 | CHANGE LOG | ||
| 71 | |||
| 72 | 18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive | ||
| 73 | MIDI during test, and made period user-settable. | ||
| 74 | */ | ||
| 75 | |||
| 76 | #define HIST_LEN 21 /* how many 1ms bins in the histogram */ | ||
| 77 | |||
| 78 | #define STRING_MAX 80 /* used for console input */ | ||
| 79 | |||
| 80 | #define INPUT_BUFFER_SIZE 100 | ||
| 81 | #define OUTPUT_BUFFER_SIZE 0 | ||
| 82 | |||
| 83 | #ifndef max | ||
| 84 | #define max(a, b) ((a) > (b) ? (a) : (b)) | ||
| 85 | #endif | ||
| 86 | #ifndef min | ||
| 87 | #define min(a, b) ((a) <= (b) ? (a) : (b)) | ||
| 88 | #endif | ||
| 89 | |||
| 90 | int get_number(const char *prompt); | ||
| 91 | |||
| 92 | PtTimestamp previous_callback_time = 0; | ||
| 93 | |||
| 94 | int period; /* milliseconds per callback */ | ||
| 95 | |||
| 96 | int histogram[HIST_LEN]; | ||
| 97 | int max_latency = 0; /* worst latency observed */ | ||
| 98 | int out_of_range = 0; /* how many points outside of HIST_LEN? */ | ||
| 99 | |||
| 100 | int test_in, test_out; /* test MIDI in and/or out? */ | ||
| 101 | int output_period; /* output MIDI every __ iterations if test_out true */ | ||
| 102 | int iteration = 0; | ||
| 103 | PmStream *in, *out; | ||
| 104 | int note_on = 0; /* is the note currently on? */ | ||
| 105 | |||
| 106 | /* callback function for PortTime -- computes histogram */ | ||
| 107 | void pt_callback(PtTimestamp timestamp, void *userData) | ||
| 108 | { | ||
| 109 | PtTimestamp difference = timestamp - previous_callback_time - period; | ||
| 110 | previous_callback_time = timestamp; | ||
| 111 | |||
| 112 | /* allow 5 seconds for the system to settle down */ | ||
| 113 | if (timestamp < 5000) return; | ||
| 114 | |||
| 115 | iteration++; | ||
| 116 | /* send a note on/off if user requested it */ | ||
| 117 | if (test_out && (iteration % output_period == 0)) { | ||
| 118 | PmEvent buffer[1]; | ||
| 119 | buffer[0].timestamp = Pt_Time(); | ||
| 120 | if (note_on) { | ||
| 121 | /* note off */ | ||
| 122 | buffer[0].message = Pm_Message(0x90, 60, 0); | ||
| 123 | note_on = 0; | ||
| 124 | } else { | ||
| 125 | /* note on */ | ||
| 126 | buffer[0].message = Pm_Message(0x90, 60, 100); | ||
| 127 | note_on = 1; | ||
| 128 | } | ||
| 129 | Pm_Write(out, buffer, 1); | ||
| 130 | iteration = 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | /* read all waiting events (if user requested) */ | ||
| 134 | if (test_in) { | ||
| 135 | PmError status; | ||
| 136 | PmEvent buffer[1]; | ||
| 137 | do { | ||
| 138 | status = Pm_Poll(in); | ||
| 139 | if (status == TRUE) { | ||
| 140 | Pm_Read(in,buffer,1); | ||
| 141 | } | ||
| 142 | } while (status == TRUE); | ||
| 143 | } | ||
| 144 | |||
| 145 | if (difference < 0) return; /* ignore when system is "catching up" */ | ||
| 146 | |||
| 147 | /* update the histogram */ | ||
| 148 | if (difference < HIST_LEN) { | ||
| 149 | histogram[difference]++; | ||
| 150 | } else { | ||
| 151 | out_of_range++; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (max_latency < difference) max_latency = difference; | ||
| 155 | } | ||
| 156 | |||
| 157 | |||
| 158 | int main() | ||
| 159 | { | ||
| 160 | int i; | ||
| 161 | int len; | ||
| 162 | int choice; | ||
| 163 | PtTimestamp stop; | ||
| 164 | printf("Latency histogram.\n"); | ||
| 165 | period = 0; | ||
| 166 | while (period < 1) { | ||
| 167 | period = get_number("Choose timer period (in ms, >= 1): "); | ||
| 168 | } | ||
| 169 | printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", | ||
| 170 | "1. No MIDI traffic", | ||
| 171 | "2. MIDI input", | ||
| 172 | "3. MIDI output", | ||
| 173 | "4. MIDI input and output"); | ||
| 174 | choice = get_number("? "); | ||
| 175 | switch (choice) { | ||
| 176 | case 1: test_in = 0; test_out = 0; break; | ||
| 177 | case 2: test_in = 1; test_out = 0; break; | ||
| 178 | case 3: test_in = 0; test_out = 1; break; | ||
| 179 | case 4: test_in = 1; test_out = 1; break; | ||
| 180 | default: assert(0); | ||
| 181 | } | ||
| 182 | if (test_in || test_out) { | ||
| 183 | /* list device information */ | ||
| 184 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 185 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 186 | if ((test_in && info->input) || | ||
| 187 | (test_out && info->output)) { | ||
| 188 | printf("%d: %s, %s", i, info->interf, info->name); | ||
| 189 | if (info->input) printf(" (input)"); | ||
| 190 | if (info->output) printf(" (output)"); | ||
| 191 | printf("\n"); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | /* open stream(s) */ | ||
| 195 | if (test_in) { | ||
| 196 | int i = get_number("MIDI input device number: "); | ||
| 197 | Pm_OpenInput(&in, | ||
| 198 | i, | ||
| 199 | NULL, | ||
| 200 | INPUT_BUFFER_SIZE, | ||
| 201 | (PmTimestamp (*)(void *)) Pt_Time, | ||
| 202 | NULL); | ||
| 203 | /* turn on filtering; otherwise, input might overflow in the | ||
| 204 | 5-second period before timer callback starts reading midi */ | ||
| 205 | Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); | ||
| 206 | } | ||
| 207 | if (test_out) { | ||
| 208 | int i = get_number("MIDI output device number: "); | ||
| 209 | PmEvent buffer[1]; | ||
| 210 | Pm_OpenOutput(&out, | ||
| 211 | i, | ||
| 212 | NULL, | ||
| 213 | OUTPUT_BUFFER_SIZE, | ||
| 214 | (PmTimestamp (*)(void *)) Pt_Time, | ||
| 215 | NULL, | ||
| 216 | 0); /* no latency scheduling */ | ||
| 217 | |||
| 218 | /* send a program change to force a status byte -- this fixes | ||
| 219 | a problem with a buggy linux MidiSport driver, and shouldn't | ||
| 220 | hurt anything else | ||
| 221 | */ | ||
| 222 | buffer[0].timestamp = 0; | ||
| 223 | buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ | ||
| 224 | Pm_Write(out, buffer, 1); | ||
| 225 | |||
| 226 | output_period = get_number( | ||
| 227 | "MIDI out should be sent every __ callback iterations: "); | ||
| 228 | |||
| 229 | assert(output_period >= 1); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | printf("Latency measurements will start in 5 seconds. " | ||
| 234 | "Type return to stop: "); | ||
| 235 | Pt_Start(period, &pt_callback, 0); | ||
| 236 | while (getchar() != '\n') ; | ||
| 237 | stop = Pt_Time(); | ||
| 238 | Pt_Stop(); | ||
| 239 | |||
| 240 | /* courteously turn off the last note, if necessary */ | ||
| 241 | if (note_on) { | ||
| 242 | PmEvent buffer[1]; | ||
| 243 | buffer[0].timestamp = Pt_Time(); | ||
| 244 | buffer[0].message = Pm_Message(0x90, 60, 0); | ||
| 245 | Pm_Write(out, buffer, 1); | ||
| 246 | } | ||
| 247 | |||
| 248 | /* print the histogram */ | ||
| 249 | printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); | ||
| 250 | printf("Latency(ms) Number of occurrences\n"); | ||
| 251 | /* avoid printing beyond last non-zero histogram entry */ | ||
| 252 | len = min(HIST_LEN, max_latency + 1); | ||
| 253 | for (i = 0; i < len; i++) { | ||
| 254 | printf("%2d %10d\n", i, histogram[i]); | ||
| 255 | } | ||
| 256 | printf("Number of points greater than %dms: %d\n", | ||
| 257 | HIST_LEN - 1, out_of_range); | ||
| 258 | printf("Maximum latency: %d milliseconds\n", max_latency); | ||
| 259 | printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); | ||
| 260 | printf("than the numbers reported here.\n"); | ||
| 261 | printf("Type return to exit..."); | ||
| 262 | while (getchar() != '\n') ; | ||
| 263 | |||
| 264 | if(choice == 2) | ||
| 265 | Pm_Close(in); | ||
| 266 | else if(choice == 3) | ||
| 267 | Pm_Close(out); | ||
| 268 | else if(choice == 4) | ||
| 269 | { | ||
| 270 | Pm_Close(in); | ||
| 271 | Pm_Close(out); | ||
| 272 | } | ||
| 273 | return 0; | ||
| 274 | } | ||
| 275 | |||
| 276 | |||
| 277 | /* read a number from console */ | ||
| 278 | int get_number(const char *prompt) | ||
| 279 | { | ||
| 280 | int n = 0, i; | ||
| 281 | fputs(prompt, stdout); | ||
| 282 | while (n != 1) { | ||
| 283 | n = scanf("%d", &i); | ||
| 284 | while (getchar() != '\n') ; | ||
| 285 | } | ||
| 286 | return i; | ||
| 287 | } | ||
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 | ||
| 17 | typedef int boolean; | ||
| 18 | |||
| 19 | #define MIDI_TIME_CLOCK 0xf8 | ||
| 20 | #define MIDI_START 0xfa | ||
| 21 | #define MIDI_CONTINUE 0xfb | ||
| 22 | #define MIDI_STOP 0xfc | ||
| 23 | #define MIDI_Q_FRAME 0xf1 | ||
| 24 | |||
| 25 | #define OUTPUT_BUFFER_SIZE 0 | ||
| 26 | #define DRIVER_INFO NULL | ||
| 27 | #define TIME_PROC ((PmTimeProcPtr) Pt_Time) | ||
| 28 | #define TIME_INFO NULL | ||
| 29 | #define LATENCY 0 | ||
| 30 | #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ | ||
| 31 | |||
| 32 | #define STRING_MAX 80 /* used for console input */ | ||
| 33 | |||
| 34 | /* to determine ms per clock: | ||
| 35 | * time per beat in seconds = 60 / tempo | ||
| 36 | * multiply by 1000 to get time per beat in ms: 60000 / tempo | ||
| 37 | * divide by 24 CLOCKs per beat: (60000/24) / tempo | ||
| 38 | * simplify: 2500 / tempo | ||
| 39 | */ | ||
| 40 | #define TEMPO_TO_CLOCK 2500.0 | ||
| 41 | |||
| 42 | boolean done = false; | ||
| 43 | PmStream *midi; | ||
| 44 | /* shared flags to control callback output generation: */ | ||
| 45 | boolean clock_running = false; | ||
| 46 | boolean send_start_stop = false; | ||
| 47 | boolean time_code_running = false; | ||
| 48 | boolean active = false; /* tells callback to do its thing */ | ||
| 49 | float tempo = 60.0F; | ||
| 50 | /* protocol for handing off portmidi to callback thread: | ||
| 51 | main owns portmidi | ||
| 52 | main sets active = true: ownership transfers to callback | ||
| 53 | main sets active = false: main requests ownership | ||
| 54 | callback sees active == false, yields ownership back to main | ||
| 55 | main waits 2ms to make sure callback has a chance to yield | ||
| 56 | (stop making PortMidi calls), then assumes it can close | ||
| 57 | PortMidi | ||
| 58 | */ | ||
| 59 | |||
| 60 | /* timer_poll -- the timer callback function */ | ||
| 61 | /* | ||
| 62 | * All MIDI sends take place here | ||
| 63 | */ | ||
| 64 | void timer_poll(PtTimestamp timestamp, void *userData) | ||
| 65 | { | ||
| 66 | static int callback_owns_portmidi = false; | ||
| 67 | static PmTimestamp clock_start_time = 0; | ||
| 68 | static double next_clock_time = 0; | ||
| 69 | /* SMPTE time */ | ||
| 70 | static int frames = 0; | ||
| 71 | static int seconds = 0; | ||
| 72 | static int minutes = 0; | ||
| 73 | static int hours = 0; | ||
| 74 | static int mtc_count = 0; /* where are we in quarter frame sequence? */ | ||
| 75 | static int smpte_start_time = 0; | ||
| 76 | static double next_smpte_time = 0; | ||
| 77 | #define QUARTER_FRAME_PERIOD (1.0 / 120.0) /* 30fps, 1/4 frame */ | ||
| 78 | |||
| 79 | if (callback_owns_portmidi && !active) { | ||
| 80 | /* main is requesting (by setting active to false) that we shut down */ | ||
| 81 | callback_owns_portmidi = false; | ||
| 82 | return; | ||
| 83 | } | ||
| 84 | if (!active) return; /* main still getting ready or it's closing down */ | ||
| 85 | callback_owns_portmidi = true; /* main is ready, we have portmidi */ | ||
| 86 | if (send_start_stop) { | ||
| 87 | if (clock_running) { | ||
| 88 | Pm_WriteShort(midi, 0, MIDI_STOP); | ||
| 89 | } else { | ||
| 90 | Pm_WriteShort(midi, 0, MIDI_START); | ||
| 91 | clock_start_time = timestamp; | ||
| 92 | next_clock_time = TEMPO_TO_CLOCK / tempo; | ||
| 93 | } | ||
| 94 | clock_running = !clock_running; | ||
| 95 | send_start_stop = false; /* until main sets it again */ | ||
| 96 | /* note that there's a slight race condition here: main could | ||
| 97 | set send_start_stop asynchronously, but we assume user is | ||
| 98 | typing slower than the clock rate */ | ||
| 99 | } | ||
| 100 | if (clock_running) { | ||
| 101 | if ((timestamp - clock_start_time) > next_clock_time) { | ||
| 102 | Pm_WriteShort(midi, 0, MIDI_TIME_CLOCK); | ||
| 103 | next_clock_time += TEMPO_TO_CLOCK / tempo; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | if (time_code_running) { | ||
| 107 | int data = 0; // initialization avoids compiler warning | ||
| 108 | if ((timestamp - smpte_start_time) < next_smpte_time) | ||
| 109 | return; | ||
| 110 | switch (mtc_count) { | ||
| 111 | case 0: /* frames low nibble */ | ||
| 112 | data = frames; | ||
| 113 | break; | ||
| 114 | case 1: /* frames high nibble */ | ||
| 115 | data = frames >> 4; | ||
| 116 | break; | ||
| 117 | case 2: /* frames seconds low nibble */ | ||
| 118 | data = seconds; | ||
| 119 | break; | ||
| 120 | case 3: /* frames seconds high nibble */ | ||
| 121 | data = seconds >> 4; | ||
| 122 | break; | ||
| 123 | case 4: /* frames minutes low nibble */ | ||
| 124 | data = minutes; | ||
| 125 | break; | ||
| 126 | case 5: /* frames minutes high nibble */ | ||
| 127 | data = minutes >> 4; | ||
| 128 | break; | ||
| 129 | case 6: /* hours low nibble */ | ||
| 130 | data = hours; | ||
| 131 | break; | ||
| 132 | case 7: /* hours high nibble */ | ||
| 133 | data = hours >> 4; | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | data &= 0xF; /* take only 4 bits */ | ||
| 137 | Pm_WriteShort(midi, 0, | ||
| 138 | Pm_Message(MIDI_Q_FRAME, (mtc_count << 4) + data, 0)); | ||
| 139 | mtc_count = (mtc_count + 1) & 7; /* wrap around */ | ||
| 140 | if (mtc_count == 0) { /* update time by two frames */ | ||
| 141 | frames += 2; | ||
| 142 | if (frames >= 30) { | ||
| 143 | frames = 0; | ||
| 144 | seconds++; | ||
| 145 | if (seconds >= 60) { | ||
| 146 | seconds = 0; | ||
| 147 | minutes++; | ||
| 148 | if (minutes >= 60) { | ||
| 149 | minutes = 0; | ||
| 150 | hours++; | ||
| 151 | /* just let hours wrap if it gets that far */ | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | next_smpte_time += QUARTER_FRAME_PERIOD; | ||
| 157 | } else { /* time_code_running is false */ | ||
| 158 | smpte_start_time = timestamp; | ||
| 159 | /* so that when it finally starts, we'll be in sync */ | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | |||
| 164 | /* read a number from console */ | ||
| 165 | /**/ | ||
| 166 | int get_number(const char *prompt) | ||
| 167 | { | ||
| 168 | int n = 0, i; | ||
| 169 | fputs(prompt, stdout); | ||
| 170 | while (n != 1) { | ||
| 171 | n = scanf("%d", &i); | ||
| 172 | while (getchar() != '\n') ; | ||
| 173 | } | ||
| 174 | return i; | ||
| 175 | } | ||
| 176 | |||
| 177 | /**************************************************************************** | ||
| 178 | * showhelp | ||
| 179 | * Effect: print help text | ||
| 180 | ****************************************************************************/ | ||
| 181 | |||
| 182 | private void showhelp() | ||
| 183 | { | ||
| 184 | printf("\n"); | ||
| 185 | printf("t toggles sending MIDI Time Code (MTC)\n"); | ||
| 186 | printf("c toggles sending MIDI CLOCK (initially on)\n"); | ||
| 187 | printf("m to set tempo (from 1bpm to 300bpm)\n"); | ||
| 188 | printf("q quits\n"); | ||
| 189 | printf("\n"); | ||
| 190 | } | ||
| 191 | |||
| 192 | /**************************************************************************** | ||
| 193 | * doascii | ||
| 194 | * Inputs: | ||
| 195 | * char c: input character | ||
| 196 | * Effect: interpret to control output | ||
| 197 | ****************************************************************************/ | ||
| 198 | |||
| 199 | private void doascii(char c) | ||
| 200 | { | ||
| 201 | if (isupper(c)) c = tolower(c); | ||
| 202 | if (c == 'q') done = true; | ||
| 203 | else if (c == 'c') { | ||
| 204 | printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting")); | ||
| 205 | send_start_stop = true; | ||
| 206 | } else if (c == 't') { | ||
| 207 | printf("%s MIDI Time Code\n", | ||
| 208 | (time_code_running ? "Stopping" : "Starting")); | ||
| 209 | time_code_running = !time_code_running; | ||
| 210 | } else if (c == 'm') { | ||
| 211 | int input_tempo = get_number("Enter new tempo (bpm): "); | ||
| 212 | if (input_tempo >= 1 && input_tempo <= 300) { | ||
| 213 | printf("Changing tempo to %d\n", input_tempo); | ||
| 214 | tempo = (float) input_tempo; | ||
| 215 | } else { | ||
| 216 | printf("Tempo range is 1 to 300, current tempo is %g bpm\n", | ||
| 217 | tempo); | ||
| 218 | } | ||
| 219 | } else { | ||
| 220 | showhelp(); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | |||
| 225 | /* main - prompt for parameters, start processing */ | ||
| 226 | /* | ||
| 227 | * Prompt user to type return. | ||
| 228 | * Then send START and MIDI CLOCK for 60 beats/min. | ||
| 229 | * Commands: | ||
| 230 | * t - toggle sending MIDI Time Code (MTC) | ||
| 231 | * c - toggle sending MIDI CLOCK | ||
| 232 | * m - set tempo | ||
| 233 | * q - quit | ||
| 234 | */ | ||
| 235 | int main(int argc, char **argv) | ||
| 236 | { | ||
| 237 | int outp; | ||
| 238 | PmError err; | ||
| 239 | int i; | ||
| 240 | if (argc > 1) { | ||
| 241 | printf("Warning: command line arguments ignored\n"); | ||
| 242 | } | ||
| 243 | showhelp(); | ||
| 244 | /* use porttime callback to send midi */ | ||
| 245 | Pt_Start(1, timer_poll, 0); | ||
| 246 | /* list device information */ | ||
| 247 | printf("MIDI output devices:\n"); | ||
| 248 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 249 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 250 | if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name); | ||
| 251 | } | ||
| 252 | outp = get_number("Type output device number: "); | ||
| 253 | err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE, | ||
| 254 | TIME_PROC, TIME_INFO, LATENCY); | ||
| 255 | if (err) { | ||
| 256 | puts(Pm_GetErrorText(err)); | ||
| 257 | goto error_exit_no_device; | ||
| 258 | } | ||
| 259 | active = true; | ||
| 260 | |||
| 261 | printf("Type ENTER to start MIDI CLOCK:\n"); | ||
| 262 | while (getchar() != '\n') ; | ||
| 263 | send_start_stop = true; /* send START and then CLOCKs */ | ||
| 264 | |||
| 265 | while (!done) { | ||
| 266 | doascii(getchar()); | ||
| 267 | while (getchar() != '\n') ; | ||
| 268 | } | ||
| 269 | |||
| 270 | active = false; | ||
| 271 | Pt_Sleep(2); /* this is to allow callback to complete -- it's | ||
| 272 | real time, so it's either ok and it runs on | ||
| 273 | time, or there's no point to synchronizing | ||
| 274 | with it */ | ||
| 275 | /* now we "own" portmidi again */ | ||
| 276 | Pm_Close(midi); | ||
| 277 | error_exit_no_device: | ||
| 278 | Pt_Stop(); | ||
| 279 | Pm_Terminate(); | ||
| 280 | exit(0); | ||
| 281 | } | ||
| 282 | |||
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 | |||
| 72 | int active = FALSE; | ||
| 73 | int monitor = FALSE; | ||
| 74 | int midi_thru = TRUE; | ||
| 75 | |||
| 76 | int transpose; | ||
| 77 | PmStream *midi_in; | ||
| 78 | PmStream *midi_out; | ||
| 79 | |||
| 80 | /****************************/ | ||
| 81 | /* END OF process_midi DATA */ | ||
| 82 | /****************************/ | ||
| 83 | |||
| 84 | /* shared queues */ | ||
| 85 | PmQueue *midi_to_main; | ||
| 86 | PmQueue *main_to_midi; | ||
| 87 | |||
| 88 | #define QUIT_MSG 1000 | ||
| 89 | #define MONITOR_MSG 1001 | ||
| 90 | #define THRU_MSG 1002 | ||
| 91 | |||
| 92 | /* timer interrupt for processing midi data */ | ||
| 93 | void process_midi(PtTimestamp timestamp, void *userData) | ||
| 94 | { | ||
| 95 | PmError result; | ||
| 96 | PmEvent buffer; /* just one message at a time */ | ||
| 97 | int32_t msg; | ||
| 98 | |||
| 99 | /* do nothing until initialization completes */ | ||
| 100 | if (!active) | ||
| 101 | return; | ||
| 102 | |||
| 103 | /* check for messages */ | ||
| 104 | do { | ||
| 105 | result = Pm_Dequeue(main_to_midi, &msg); | ||
| 106 | if (result) { | ||
| 107 | if (msg >= -127 && msg <= 127) | ||
| 108 | transpose = msg; | ||
| 109 | else if (msg == QUIT_MSG) { | ||
| 110 | /* acknowledge receipt of quit message */ | ||
| 111 | Pm_Enqueue(midi_to_main, &msg); | ||
| 112 | active = FALSE; | ||
| 113 | return; | ||
| 114 | } else if (msg == MONITOR_MSG) { | ||
| 115 | /* main has requested a pitch. monitor is a flag that | ||
| 116 | * records the request: | ||
| 117 | */ | ||
| 118 | monitor = TRUE; | ||
| 119 | } else if (msg == THRU_MSG) { | ||
| 120 | /* toggle Thru on or off */ | ||
| 121 | midi_thru = !midi_thru; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } while (result); | ||
| 125 | |||
| 126 | /* see if there is any midi input to process */ | ||
| 127 | do { | ||
| 128 | result = Pm_Poll(midi_in); | ||
| 129 | if (result) { | ||
| 130 | int status, data1, data2; | ||
| 131 | if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) | ||
| 132 | continue; | ||
| 133 | if (midi_thru) | ||
| 134 | Pm_Write(midi_out, &buffer, 1); | ||
| 135 | /* unless there was overflow, we should have a message now */ | ||
| 136 | status = Pm_MessageStatus(buffer.message); | ||
| 137 | data1 = Pm_MessageData1(buffer.message); | ||
| 138 | data2 = Pm_MessageData2(buffer.message); | ||
| 139 | if ((status & 0xF0) == 0x90 || | ||
| 140 | (status & 0xF0) == 0x80) { | ||
| 141 | |||
| 142 | /* this is a note-on or note-off, so transpose and send */ | ||
| 143 | data1 += transpose; | ||
| 144 | |||
| 145 | /* keep within midi pitch range, keep proper pitch class */ | ||
| 146 | while (data1 > 127) | ||
| 147 | data1 -= 12; | ||
| 148 | while (data1 < 0) | ||
| 149 | data1 += 12; | ||
| 150 | |||
| 151 | /* send the message */ | ||
| 152 | buffer.message = Pm_Message(status, data1, data2); | ||
| 153 | Pm_Write(midi_out, &buffer, 1); | ||
| 154 | |||
| 155 | /* if monitor is set, send the pitch to the main thread */ | ||
| 156 | if (monitor) { | ||
| 157 | Pm_Enqueue(midi_to_main, &data1); | ||
| 158 | monitor = FALSE; /* only send one pitch per request */ | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | } while (result); | ||
| 163 | } | ||
| 164 | |||
| 165 | void exit_with_message(char *msg) | ||
| 166 | { | ||
| 167 | printf("%s\n", msg); | ||
| 168 | while (getchar() != '\n') ; | ||
| 169 | exit(1); | ||
| 170 | } | ||
| 171 | |||
| 172 | int 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 | |||
| 23 | All setup will be done by the main thread. Then, all direct access to | ||
| 24 | PortMidi will be handed off to the timer callback thread. | ||
| 25 | |||
| 26 | After this hand-off, the main thread will get/send messages via a queue. | ||
| 27 | |||
| 28 | The goal is to send incoming messages to the midi output while merging | ||
| 29 | any midi data generated by the application. Sysex is a problem here | ||
| 30 | because you cannot insert (merge) a midi message while a sysex is in | ||
| 31 | progress. There are at least three ways to implement midi thru with | ||
| 32 | sysex messages: | ||
| 33 | |||
| 34 | 1) 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 | |||
| 39 | 2) 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 | |||
| 46 | 3) 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 | |||
| 58 | This code implements (3). | ||
| 59 | |||
| 60 | Latency is also an issue. PortMidi requires timestamps to be in | ||
| 61 | non-decreasing order. Since we'll be operating with a low-latency | ||
| 62 | timer thread, we can just set the latency to zero meaning timestamps | ||
| 63 | are ignored by PortMidi. This will allow thru to go through with | ||
| 64 | minimal latency. The application, however, needs to use timestamps | ||
| 65 | because we assume it is high latency (the whole purpose of this | ||
| 66 | example is to illustrate how to get low-latency thru with a high-latency | ||
| 67 | application.) So the callback thread will implement midi timing by | ||
| 68 | observing timestamps. The current timestamp will be available in the | ||
| 69 | global 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 */ | ||
| 88 | int 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 */ | ||
| 91 | volatile int process_midi_exit_flag; | ||
| 92 | |||
| 93 | PmStream *midi_in; | ||
| 94 | PmStream *midi_out; | ||
| 95 | |||
| 96 | /* shared queues */ | ||
| 97 | #define IN_QUEUE_SIZE 1024 | ||
| 98 | #define OUT_QUEUE_SIZE 1024 | ||
| 99 | PmQueue *in_queue; | ||
| 100 | PmQueue *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 | */ | ||
| 105 | volatile PmTimestamp current_timestamp = 0; | ||
| 106 | int thru_sysex_in_progress = FALSE; | ||
| 107 | int app_sysex_in_progress = FALSE; | ||
| 108 | PmTimestamp last_timestamp = 0; | ||
| 109 | |||
| 110 | |||
| 111 | static 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 | |||
| 120 | static 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 */ | ||
| 139 | PmTimestamp 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 | */ | ||
| 151 | void 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 | |||
| 257 | void 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 | |||
| 266 | void 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 | |||
| 365 | void 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 | |||
| 389 | int 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 | } | ||
| 451 | quit_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 | |||
| 68 | typedef int boolean; | ||
| 69 | |||
| 70 | int debug = false; /* never set, but referenced by userio.c */ | ||
| 71 | PmStream *midi_in; /* midi input */ | ||
| 72 | boolean active = false; /* set when midi_in is ready for reading */ | ||
| 73 | boolean in_sysex = false; /* we are reading a sysex message */ | ||
| 74 | boolean inited = false; /* suppress printing during command line parsing */ | ||
| 75 | boolean done = false; /* when true, exit */ | ||
| 76 | boolean notes = true; /* show notes? */ | ||
| 77 | boolean controls = true; /* show continuous controllers */ | ||
| 78 | boolean bender = true; /* record pitch bend etc.? */ | ||
| 79 | boolean excldata = true; /* record system exclusive data? */ | ||
| 80 | boolean verbose = true; /* show text representation? */ | ||
| 81 | boolean realdata = true; /* record real time messages? */ | ||
| 82 | boolean clksencnt = true; /* clock and active sense count on */ | ||
| 83 | boolean chmode = true; /* show channel mode messages */ | ||
| 84 | boolean pgchanges = true; /* show program changes */ | ||
| 85 | boolean flush = false; /* flush all pending MIDI data */ | ||
| 86 | |||
| 87 | uint32_t filter = 0; /* remember state of midi filter */ | ||
| 88 | |||
| 89 | uint32_t clockcount = 0; /* count of clocks */ | ||
| 90 | uint32_t actsensecount = 0; /* cout of active sensing bytes */ | ||
| 91 | uint32_t notescount = 0; /* #notes since last request */ | ||
| 92 | uint32_t notestotal = 0; /* total #notes */ | ||
| 93 | |||
| 94 | char val_format[] = " Val %d\n"; | ||
| 95 | |||
| 96 | /***************************************************************************** | ||
| 97 | * Imported variables | ||
| 98 | *****************************************************************************/ | ||
| 99 | |||
| 100 | extern int abort_flag; | ||
| 101 | |||
| 102 | /***************************************************************************** | ||
| 103 | * Routines local to this module | ||
| 104 | *****************************************************************************/ | ||
| 105 | |||
| 106 | private void mmexit(int code); | ||
| 107 | private void output(PmMessage data); | ||
| 108 | private int put_pitch(int p); | ||
| 109 | private void showhelp(); | ||
| 110 | private void showbytes(PmMessage data, int len, boolean newline); | ||
| 111 | private void showstatus(boolean flag); | ||
| 112 | private void doascii(char c); | ||
| 113 | private int get_number(const char *prompt); | ||
| 114 | |||
| 115 | |||
| 116 | /* read a number from console */ | ||
| 117 | /**/ | ||
| 118 | int 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 | |||
| 130 | void 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 | |||
| 147 | int 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 | |||
| 197 | private 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 | |||
| 271 | private 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 | |||
| 287 | char vel_format[] = " Vel %d\n"; | ||
| 288 | |||
| 289 | private 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 | |||
| 501 | private 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 | |||
| 519 | char nib_to_hex[] = "0123456789ABCDEF"; | ||
| 520 | |||
| 521 | private 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 | |||
| 552 | private 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 | |||
| 592 | private 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 | |||
| 20 | int latency = 0; | ||
| 21 | |||
| 22 | static void prompt_and_exit(void) | ||
| 23 | { | ||
| 24 | printf("type ENTER..."); | ||
| 25 | while (getchar() != '\n') ; | ||
| 26 | /* this will clean up open ports: */ | ||
| 27 | exit(-1); | ||
| 28 | } | ||
| 29 | |||
| 30 | |||
| 31 | static PmError checkerror(PmError err) | ||
| 32 | { | ||
| 33 | if (err == pmHostError) { | ||
| 34 | /* it seems pointless to allocate memory and copy the string, | ||
| 35 | * so I will do the work of Pm_GetHostErrorText directly | ||
| 36 | */ | ||
| 37 | char errmsg[80]; | ||
| 38 | Pm_GetHostErrorText(errmsg, 80); | ||
| 39 | printf("PortMidi found host error...\n %s\n", errmsg); | ||
| 40 | prompt_and_exit(); | ||
| 41 | } else if (err < 0) { | ||
| 42 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 43 | prompt_and_exit(); | ||
| 44 | } | ||
| 45 | return err; | ||
| 46 | } | ||
| 47 | |||
| 48 | static int msg_count[2] = {0, 0}; | ||
| 49 | |||
| 50 | void poll_input(PmStream *in, int which) | ||
| 51 | { | ||
| 52 | PmEvent buffer[1]; | ||
| 53 | int pitch, expected, length; | ||
| 54 | PmError status = Pm_Poll(in); | ||
| 55 | if (status == TRUE) { | ||
| 56 | length = Pm_Read(in, buffer, 1); | ||
| 57 | if (length > 0) { | ||
| 58 | printf("Got message %d from portmidi%d: " | ||
| 59 | "time %ld, %2x %2x %2x\n", | ||
| 60 | msg_count[which], which + 1, (long) buffer[0].timestamp, | ||
| 61 | (status = Pm_MessageStatus(buffer[0].message)), | ||
| 62 | (pitch = Pm_MessageData1(buffer[0].message)), | ||
| 63 | Pm_MessageData2(buffer[0].message)); | ||
| 64 | if (status == 0x90) { /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */ | ||
| 65 | expected = (((msg_count[which] - 1) / 2) % 12) + 60 + | ||
| 66 | which * 12; | ||
| 67 | if (pitch != expected) { | ||
| 68 | printf("WARNING: expected pitch %d, got pitch %d\n", | ||
| 69 | expected, pitch); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | msg_count[which]++; | ||
| 73 | } else { | ||
| 74 | assert(0); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | |||
| 80 | void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2) | ||
| 81 | { | ||
| 82 | while (when > Pt_Time()) { | ||
| 83 | poll_input(in1, 0); | ||
| 84 | poll_input(in2, 1); | ||
| 85 | Pt_Sleep(10); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | |||
| 90 | /* create one virtual output device and one input device */ | ||
| 91 | void init(const char *name, PmStream **midi_out, PmStream **midi_in, | ||
| 92 | int *id_out, int *id_in) | ||
| 93 | { | ||
| 94 | PmEvent buffer[1]; | ||
| 95 | |||
| 96 | *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO)); | ||
| 97 | checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE, | ||
| 98 | TIME_PROC, TIME_INFO, latency)); | ||
| 99 | printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out); | ||
| 100 | |||
| 101 | *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO)); | ||
| 102 | checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL)); | ||
| 103 | printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in); | ||
| 104 | Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); | ||
| 105 | /* empty the buffer after setting filter, just in case anything | ||
| 106 | got through */ | ||
| 107 | while (Pm_Read(*midi_in, buffer, 1)) ; | ||
| 108 | } | ||
| 109 | |||
| 110 | |||
| 111 | void main_test(int num) | ||
| 112 | { | ||
| 113 | PmStream *midi1_out; | ||
| 114 | PmStream *midi2_out; | ||
| 115 | PmStream *midi1_in; | ||
| 116 | PmStream *midi2_in; | ||
| 117 | int id1_out; | ||
| 118 | int id2_out; | ||
| 119 | int id1_in; | ||
| 120 | int id2_in; | ||
| 121 | int32_t next_time; | ||
| 122 | PmEvent buffer[1]; | ||
| 123 | int pitch = 60; | ||
| 124 | int expected_count = num + 1; /* add 1 for MIDI Program message */ | ||
| 125 | |||
| 126 | /* It is recommended to start timer before Midi; otherwise, PortMidi may | ||
| 127 | start the timer with its (default) parameters | ||
| 128 | */ | ||
| 129 | TIME_START; | ||
| 130 | |||
| 131 | init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in); | ||
| 132 | init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in); | ||
| 133 | |||
| 134 | printf("Type ENTER to send messages: "); | ||
| 135 | while (getchar() != '\n') ; | ||
| 136 | |||
| 137 | buffer[0].timestamp = Pt_Time(); | ||
| 138 | #define PROGRAM 0 | ||
| 139 | buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); | ||
| 140 | Pm_Write(midi1_out, buffer, 1); | ||
| 141 | Pm_Write(midi2_out, buffer, 1); | ||
| 142 | next_time = Pt_Time() + 1000; /* wait 1s */ | ||
| 143 | while (num > 0) { | ||
| 144 | wait_until(next_time, midi1_in, midi2_in); | ||
| 145 | Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100)); | ||
| 146 | Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100)); | ||
| 147 | printf("Note On pitch %d\n", pitch); | ||
| 148 | num--; | ||
| 149 | next_time += 500; | ||
| 150 | |||
| 151 | wait_until(next_time, midi1_in, midi2_in); | ||
| 152 | Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0)); | ||
| 153 | Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0)); | ||
| 154 | printf("Note Off pitch %d\n", pitch); | ||
| 155 | num--; | ||
| 156 | pitch = (pitch + 1) % 12 + 60; | ||
| 157 | next_time += 500; | ||
| 158 | } | ||
| 159 | wait_until(next_time, midi1_in, midi2_in); /* get final note-offs */ | ||
| 160 | |||
| 161 | printf("Got %d messages from portmidi1 and %d from portmidi2; " | ||
| 162 | "expected %d.\n", msg_count[0], msg_count[1], expected_count); | ||
| 163 | |||
| 164 | /* close devices (this not explicitly needed in most implementations) */ | ||
| 165 | printf("ready to close..."); | ||
| 166 | checkerror(Pm_Close(midi1_out)); | ||
| 167 | checkerror(Pm_Close(midi2_out)); | ||
| 168 | checkerror(Pm_Close(midi1_in)); | ||
| 169 | checkerror(Pm_Close(midi2_in)); | ||
| 170 | printf("done closing.\nNow delete the virtual devices..."); | ||
| 171 | checkerror(Pm_DeleteVirtualDevice(id1_out)); | ||
| 172 | checkerror(Pm_DeleteVirtualDevice(id1_in)); | ||
| 173 | checkerror(Pm_DeleteVirtualDevice(id2_out)); | ||
| 174 | checkerror(Pm_DeleteVirtualDevice(id2_in)); | ||
| 175 | printf("done deleting.\n"); | ||
| 176 | } | ||
| 177 | |||
| 178 | |||
| 179 | void show_usage() | ||
| 180 | { | ||
| 181 | printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n" | ||
| 182 | " -h for this message,\n" | ||
| 183 | " -l ms designates latency for precise timing (default 0),\n" | ||
| 184 | " n is number of message to send each output, not counting\n" | ||
| 185 | " initial program change.\n" | ||
| 186 | "sends change program to 1, then one note per second with 0.5s on,\n" | ||
| 187 | "0.5s off, for n/2 seconds to both output ports portmidi1 and\n" | ||
| 188 | "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n" | ||
| 189 | "pitches an octave higher. Latency >0 uses the device driver for \n" | ||
| 190 | "precise timing (see PortMidi documentation). Inputs print what\n" | ||
| 191 | "they get and print WARNING if they get something unexpected.\n" | ||
| 192 | "The expected test is use two instances of testio to loop\n" | ||
| 193 | "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n"); | ||
| 194 | exit(0); | ||
| 195 | } | ||
| 196 | |||
| 197 | |||
| 198 | int main(int argc, char *argv[]) | ||
| 199 | { | ||
| 200 | int num = 10; | ||
| 201 | int i; | ||
| 202 | for (i = 1; i < argc; i++) { | ||
| 203 | if (strcmp(argv[i], "-h") == 0) { | ||
| 204 | show_usage(); | ||
| 205 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { | ||
| 206 | i = i + 1; | ||
| 207 | latency = atoi(argv[i]); | ||
| 208 | printf("Latency will be %d\n", latency); | ||
| 209 | } else { | ||
| 210 | num = atoi(argv[1]); | ||
| 211 | if (num <= 0) { | ||
| 212 | show_usage(); | ||
| 213 | } | ||
| 214 | printf("Sending %d messages.\n", num); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | main_test(num); | ||
| 219 | |||
| 220 | printf("finished sendvirtual test...type ENTER to quit..."); | ||
| 221 | while (getchar() != '\n') ; | ||
| 222 | return 0; | ||
| 223 | } | ||
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 | |||
| 23 | void 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 | |||
| 30 | int main(int argc, char *argv[]) | ||
| 31 | { | ||
| 32 | if (argc > 1) { | ||
| 33 | show_usage(); | ||
| 34 | exit(0); | ||
| 35 | } | ||
| 36 | |||
| 37 | while (1) { | ||
| 38 | char input[STRING_MAX]; | ||
| 39 | const char *deflt; | ||
| 40 | const char *in_or_out; | ||
| 41 | int default_in, default_out, i; | ||
| 42 | |||
| 43 | // Pm_Initialize(); | ||
| 44 | /* list device information */ | ||
| 45 | default_in = Pm_GetDefaultInputDeviceID(); | ||
| 46 | default_out = Pm_GetDefaultOutputDeviceID(); | ||
| 47 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 48 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 49 | printf("%d: %s, %s", i, info->interf, info->name); | ||
| 50 | deflt = ""; | ||
| 51 | if (i == default_out || i == default_in) { | ||
| 52 | deflt = "default "; | ||
| 53 | } | ||
| 54 | in_or_out = (info->input ? "input" : "output"); | ||
| 55 | printf(" (%s%s)\n", deflt, in_or_out); | ||
| 56 | } | ||
| 57 | if (fgets(input, STRING_MAX, stdin) && input[0] == 'q') { | ||
| 58 | return 0; | ||
| 59 | } | ||
| 60 | Pm_Terminate(); | ||
| 61 | } | ||
| 62 | return 0; | ||
| 63 | } | ||
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 | */ | ||
| 10 | void make_msg(long msg[], int n, int i) | ||
| 11 | { | ||
| 12 | int j; | ||
| 13 | for (j = 0; j < n; j++) { | ||
| 14 | msg[j] = i % (j + 5); | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | |||
| 19 | /* print_msg -- print the content of msg of length n to stdout */ | ||
| 20 | /**/ | ||
| 21 | void print_msg(long msg[], int n) | ||
| 22 | { | ||
| 23 | int i; | ||
| 24 | for (i = 0; i < n; i++) { | ||
| 25 | printf(" %li", msg[i]); | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | |||
| 30 | /* cmp_msg -- compare two messages of length n */ | ||
| 31 | /**/ | ||
| 32 | int cmp_msg(long msg[], long msg2[], int n, int i) | ||
| 33 | { | ||
| 34 | int j; | ||
| 35 | for (j = 0; j < n; j++) { | ||
| 36 | if (msg[j] != msg2[j]) { | ||
| 37 | printf("Received message %d doesn't match sent message\n", i); | ||
| 38 | printf("in: "); print_msg(msg, n); printf("\n"); | ||
| 39 | printf("out:"); print_msg(msg2, n); printf("\n"); | ||
| 40 | return FALSE; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | return TRUE; | ||
| 44 | } | ||
| 45 | |||
| 46 | |||
| 47 | int main() | ||
| 48 | { | ||
| 49 | int msg_len; | ||
| 50 | for (msg_len = 4; msg_len < 100; msg_len += 5) { | ||
| 51 | PmQueue *queue = Pm_QueueCreate(100, msg_len * sizeof(long)); | ||
| 52 | int i; | ||
| 53 | long msg[100]; | ||
| 54 | long msg2[100]; | ||
| 55 | |||
| 56 | printf("msg_len = %d\n", msg_len); | ||
| 57 | if (!queue) { | ||
| 58 | printf("Could not allocate queue\n"); | ||
| 59 | return 1; | ||
| 60 | } | ||
| 61 | |||
| 62 | /* insert/remove 1000 messages */ | ||
| 63 | printf("test 1\n"); | ||
| 64 | for (i = 0; i < 1357; i++) { | ||
| 65 | make_msg(msg, msg_len, i); | ||
| 66 | if (Pm_Enqueue(queue, msg)) { | ||
| 67 | printf("Pm_Enqueue error\n"); | ||
| 68 | return 1; | ||
| 69 | } | ||
| 70 | if (Pm_Dequeue(queue, msg2) != 1) { | ||
| 71 | printf("Pm_Dequeue error\n"); | ||
| 72 | return 1; | ||
| 73 | } | ||
| 74 | if (!cmp_msg(msg, msg2, msg_len, i)) { | ||
| 75 | return 1; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | /* make full */ | ||
| 80 | printf("test 2\n"); | ||
| 81 | for (i = 0; i < 100; i++) { | ||
| 82 | make_msg(msg, msg_len, i); | ||
| 83 | if (Pm_Enqueue(queue, msg)) { | ||
| 84 | printf("Pm_Enqueue error\n"); | ||
| 85 | return 1; | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | /* alternately remove and insert */ | ||
| 90 | for (i = 100; i < 1234; i++) { | ||
| 91 | make_msg(msg, msg_len, i - 100); /* what we expect */ | ||
| 92 | if (Pm_Dequeue(queue, msg2) != 1) { | ||
| 93 | printf("Pm_Dequeue error\n"); | ||
| 94 | return 1; | ||
| 95 | } | ||
| 96 | if (!cmp_msg(msg, msg2, msg_len, i)) { | ||
| 97 | return 1; | ||
| 98 | } | ||
| 99 | make_msg(msg, msg_len, i); | ||
| 100 | if (Pm_Enqueue(queue, msg)) { | ||
| 101 | printf("Pm_Enqueue error\n"); | ||
| 102 | return 1; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | /* remove all */ | ||
| 107 | while (!Pm_QueueEmpty(queue)) { | ||
| 108 | make_msg(msg, msg_len, i - 100); /* what we expect */ | ||
| 109 | if (Pm_Dequeue(queue, msg2) != 1) { | ||
| 110 | printf("Pm_Dequeue error\n"); | ||
| 111 | return 1; | ||
| 112 | } | ||
| 113 | if (!cmp_msg(msg, msg2, msg_len, i)) { | ||
| 114 | return 1; | ||
| 115 | } | ||
| 116 | i++; | ||
| 117 | } | ||
| 118 | if (i != 1334) { | ||
| 119 | printf("Message count error\n"); | ||
| 120 | return 1; | ||
| 121 | } | ||
| 122 | |||
| 123 | /* now test overflow */ | ||
| 124 | printf("test 3\n"); | ||
| 125 | for (i = 0; i < 110; i++) { | ||
| 126 | make_msg(msg, msg_len, i); | ||
| 127 | if (Pm_Enqueue(queue, msg) == pmBufferOverflow) { | ||
| 128 | break; /* this is supposed to execute after 100 messages */ | ||
| 129 | } | ||
| 130 | } | ||
| 131 | for (i = 0; i < 100; i++) { | ||
| 132 | make_msg(msg, msg_len, i); | ||
| 133 | if (Pm_Dequeue(queue, msg2) != 1) { | ||
| 134 | printf("Pm_Dequeue error\n"); | ||
| 135 | return 1; | ||
| 136 | } | ||
| 137 | if (!cmp_msg(msg, msg2, msg_len, i)) { | ||
| 138 | return 1; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | /* we should detect overflow after removing 100 messages */ | ||
| 142 | if (Pm_Dequeue(queue, msg2) != pmBufferOverflow) { | ||
| 143 | printf("Pm_Dequeue overflow expected\n"); | ||
| 144 | return 1; | ||
| 145 | } | ||
| 146 | |||
| 147 | /* after overflow is detected (and cleared), sender can | ||
| 148 | * send again | ||
| 149 | */ | ||
| 150 | /* see if function is restored, also test peek */ | ||
| 151 | printf("test 4\n"); | ||
| 152 | for (i = 0; i < 1357; i++) { | ||
| 153 | long *peek; | ||
| 154 | make_msg(msg, msg_len, i); | ||
| 155 | if (Pm_Enqueue(queue, msg)) { | ||
| 156 | printf("Pm_Enqueue error\n"); | ||
| 157 | return 1; | ||
| 158 | } | ||
| 159 | peek = (long *) Pm_QueuePeek(queue); | ||
| 160 | if (!cmp_msg(msg, peek, msg_len, i)) { | ||
| 161 | return 1; | ||
| 162 | } | ||
| 163 | if (Pm_Dequeue(queue, msg2) != 1) { | ||
| 164 | printf("Pm_Dequeue error\n"); | ||
| 165 | return 1; | ||
| 166 | } | ||
| 167 | if (!cmp_msg(msg, msg2, msg_len, i)) { | ||
| 168 | return 1; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | Pm_QueueDestroy(queue); | ||
| 172 | } | ||
| 173 | return 0; | ||
| 174 | } | ||
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 | |||
| 15 | char *portname = "portmidi"; | ||
| 16 | |||
| 17 | PmSysDepInfo *sysdepinfo = NULL; | ||
| 18 | char *port_name = "portmidi"; | ||
| 19 | |||
| 20 | static void set_sysdepinfo(char m_or_p, const char *name) | ||
| 21 | { | ||
| 22 | if (!sysdepinfo) { | ||
| 23 | // allocate some space we will alias with open-ended PmDriverInfo: | ||
| 24 | // there is space for 4 parameters: | ||
| 25 | static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; | ||
| 26 | sysdepinfo = (PmSysDepInfo *) dimem; | ||
| 27 | // build the driver info structure: | ||
| 28 | sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; | ||
| 29 | sysdepinfo->length = 0; | ||
| 30 | } | ||
| 31 | if (sysdepinfo->length > 1) { | ||
| 32 | printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); | ||
| 33 | exit(1); | ||
| 34 | } | ||
| 35 | int i = sysdepinfo->length++; | ||
| 36 | enum PmSysDepPropertyKey k = pmKeyNone; | ||
| 37 | if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; | ||
| 38 | else if (m_or_p == 'p') k = pmKeyAlsaPortName; | ||
| 39 | else if (m_or_p == 'c') k = pmKeyAlsaClientName; | ||
| 40 | sysdepinfo->properties[i].key = k; | ||
| 41 | sysdepinfo->properties[i].value = name; | ||
| 42 | } | ||
| 43 | |||
| 44 | |||
| 45 | static void prompt_and_exit(void) | ||
| 46 | { | ||
| 47 | printf("type ENTER..."); | ||
| 48 | while (getchar() != '\n') ; | ||
| 49 | /* this will clean up open ports: */ | ||
| 50 | exit(-1); | ||
| 51 | } | ||
| 52 | |||
| 53 | |||
| 54 | static PmError checkerror(PmError err) | ||
| 55 | { | ||
| 56 | if (err == pmHostError) { | ||
| 57 | /* it seems pointless to allocate memory and copy the string, | ||
| 58 | * so I will do the work of Pm_GetHostErrorText directly | ||
| 59 | */ | ||
| 60 | char errmsg[80]; | ||
| 61 | Pm_GetHostErrorText(errmsg, 80); | ||
| 62 | printf("PortMidi found host error...\n %s\n", errmsg); | ||
| 63 | prompt_and_exit(); | ||
| 64 | } else if (err < 0) { | ||
| 65 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 66 | prompt_and_exit(); | ||
| 67 | } | ||
| 68 | return err; | ||
| 69 | } | ||
| 70 | |||
| 71 | |||
| 72 | void main_test_input(int num) | ||
| 73 | { | ||
| 74 | PmStream *midi; | ||
| 75 | PmError status, length; | ||
| 76 | PmEvent buffer[1]; | ||
| 77 | int id; | ||
| 78 | int i = 0; /* count messages as they arrive */ | ||
| 79 | /* It is recommended to start timer before Midi; otherwise, PortMidi may | ||
| 80 | start the timer with its (default) parameters | ||
| 81 | */ | ||
| 82 | TIME_START; | ||
| 83 | |||
| 84 | /* create a virtual input device */ | ||
| 85 | id = checkerror(Pm_CreateVirtualInput(port_name, NULL, sysdepinfo)); | ||
| 86 | checkerror(Pm_OpenInput(&midi, id, sysdepinfo, 0, NULL, NULL)); | ||
| 87 | |||
| 88 | printf("Midi Input opened. Reading %d Midi messages...\n", num); | ||
| 89 | Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); | ||
| 90 | /* empty the buffer after setting filter, just in case anything | ||
| 91 | got through */ | ||
| 92 | while (Pm_Poll(midi)) { | ||
| 93 | Pm_Read(midi, buffer, 1); | ||
| 94 | } | ||
| 95 | /* now start paying attention to messages */ | ||
| 96 | while (i < num) { | ||
| 97 | status = Pm_Poll(midi); | ||
| 98 | if (status == TRUE) { | ||
| 99 | length = Pm_Read(midi, buffer, 1); | ||
| 100 | if (length > 0) { | ||
| 101 | printf("Got message %d: time %ld, %2lx %2lx %2lx\n", | ||
| 102 | i, | ||
| 103 | (long) buffer[0].timestamp, | ||
| 104 | (long) Pm_MessageStatus(buffer[0].message), | ||
| 105 | (long) Pm_MessageData1(buffer[0].message), | ||
| 106 | (long) Pm_MessageData2(buffer[0].message)); | ||
| 107 | i++; | ||
| 108 | } else { | ||
| 109 | assert(0); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | /* close device (this not explicitly needed in most implementations) */ | ||
| 115 | printf("ready to close..."); | ||
| 116 | Pm_Close(midi); | ||
| 117 | printf("done closing.\nNow delete the virtual device..."); | ||
| 118 | checkerror(Pm_DeleteVirtualDevice(id)); | ||
| 119 | printf("done deleting.\n"); | ||
| 120 | } | ||
| 121 | |||
| 122 | |||
| 123 | void show_usage() | ||
| 124 | { | ||
| 125 | printf("Usage: recvvirtual [-h] [-m manufacturer] [-c clientname] " | ||
| 126 | "[-p portname] [n]\n" | ||
| 127 | " -h for this message,\n" | ||
| 128 | " -m name designates a manufacturer name (macOS only),\n" | ||
| 129 | " -c name designates a client name (linux only),\n" | ||
| 130 | " -p name designates a port name (linux only),\n" | ||
| 131 | " n is number of message to wait for.\n"); | ||
| 132 | exit(0); | ||
| 133 | } | ||
| 134 | |||
| 135 | |||
| 136 | int main(int argc, char *argv[]) | ||
| 137 | { | ||
| 138 | char line[STRING_MAX]; | ||
| 139 | int num = 10; | ||
| 140 | int i; | ||
| 141 | if (argc <= 1) { | ||
| 142 | show_usage(); | ||
| 143 | } | ||
| 144 | for (i = 1; i < argc; i++) { | ||
| 145 | if (strcmp(argv[i], "-h") == 0) { | ||
| 146 | show_usage(); | ||
| 147 | } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { | ||
| 148 | i = i + 1; | ||
| 149 | set_sysdepinfo('m', argv[i]); | ||
| 150 | printf("Manufacturer name will be %s\n", argv[i]); | ||
| 151 | } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { | ||
| 152 | i = i + 1; | ||
| 153 | port_name = argv[i]; | ||
| 154 | set_sysdepinfo('p', port_name); | ||
| 155 | printf("Port name will be %s\n", port_name); | ||
| 156 | } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { | ||
| 157 | i = i + 1; | ||
| 158 | set_sysdepinfo('c', argv[i]); | ||
| 159 | printf("Client name will be %s\n", argv[i]); | ||
| 160 | } else { | ||
| 161 | num = atoi(argv[i]); | ||
| 162 | if (num <= 0) { | ||
| 163 | printf("Zero value or non-number for n\n"); | ||
| 164 | show_usage(); | ||
| 165 | } | ||
| 166 | printf("Waiting for %d messages.\n", num); | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | main_test_input(num); | ||
| 171 | |||
| 172 | printf("finished portMidi test...type ENTER to quit..."); | ||
| 173 | while (getchar() != '\n') ; | ||
| 174 | return 0; | ||
| 175 | } | ||
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 | |||
| 18 | int latency = 0; | ||
| 19 | PmSysDepInfo *sysdepinfo = NULL; | ||
| 20 | char *port_name = "portmidi"; | ||
| 21 | |||
| 22 | static void set_sysdepinfo(char m_or_p, const char *name) | ||
| 23 | { | ||
| 24 | if (!sysdepinfo) { | ||
| 25 | // allocate some space we will alias with open-ended PmDriverInfo: | ||
| 26 | // there is space for 4 parameters: | ||
| 27 | static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; | ||
| 28 | sysdepinfo = (PmSysDepInfo *) dimem; | ||
| 29 | // build the driver info structure: | ||
| 30 | sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; | ||
| 31 | sysdepinfo->length = 0; | ||
| 32 | } | ||
| 33 | if (sysdepinfo->length > 1) { | ||
| 34 | printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); | ||
| 35 | exit(1); | ||
| 36 | } | ||
| 37 | int i = sysdepinfo->length++; | ||
| 38 | enum PmSysDepPropertyKey k = pmKeyNone; | ||
| 39 | if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; | ||
| 40 | else if (m_or_p == 'p') k = pmKeyAlsaPortName; | ||
| 41 | else if (m_or_p == 'c') k = pmKeyAlsaClientName; | ||
| 42 | sysdepinfo->properties[i].key = k; | ||
| 43 | sysdepinfo->properties[i].value = name; | ||
| 44 | } | ||
| 45 | |||
| 46 | |||
| 47 | static void prompt_and_exit(void) | ||
| 48 | { | ||
| 49 | printf("type ENTER..."); | ||
| 50 | while (getchar() != '\n') ; | ||
| 51 | /* this will clean up open ports: */ | ||
| 52 | exit(-1); | ||
| 53 | } | ||
| 54 | |||
| 55 | |||
| 56 | static PmError checkerror(PmError err) | ||
| 57 | { | ||
| 58 | if (err == pmHostError) { | ||
| 59 | /* it seems pointless to allocate memory and copy the string, | ||
| 60 | * so I will do the work of Pm_GetHostErrorText directly | ||
| 61 | */ | ||
| 62 | char errmsg[80]; | ||
| 63 | Pm_GetHostErrorText(errmsg, 80); | ||
| 64 | printf("PortMidi found host error...\n %s\n", errmsg); | ||
| 65 | prompt_and_exit(); | ||
| 66 | } else if (err < 0) { | ||
| 67 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 68 | prompt_and_exit(); | ||
| 69 | } | ||
| 70 | return err; | ||
| 71 | } | ||
| 72 | |||
| 73 | |||
| 74 | void wait_until(PmTimestamp when) | ||
| 75 | { | ||
| 76 | PtTimestamp now = Pt_Time(); | ||
| 77 | if (when > now) { | ||
| 78 | Pt_Sleep(when - now); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | |||
| 83 | void main_test_output(int num) | ||
| 84 | { | ||
| 85 | PmStream *midi; | ||
| 86 | int32_t next_time; | ||
| 87 | PmEvent buffer[1]; | ||
| 88 | PmTimestamp timestamp; | ||
| 89 | int pitch = 60; | ||
| 90 | int id; | ||
| 91 | |||
| 92 | /* It is recommended to start timer before Midi; otherwise, PortMidi may | ||
| 93 | start the timer with its (default) parameters | ||
| 94 | */ | ||
| 95 | TIME_START; | ||
| 96 | |||
| 97 | /* create a virtual output device */ | ||
| 98 | id = checkerror(Pm_CreateVirtualOutput(port_name, NULL, sysdepinfo)); | ||
| 99 | checkerror(Pm_OpenOutput(&midi, id, sysdepinfo, OUTPUT_BUFFER_SIZE, | ||
| 100 | TIME_PROC, TIME_INFO, latency)); | ||
| 101 | |||
| 102 | printf("Midi Output Virtual Device \"%s\" created.\n", port_name); | ||
| 103 | printf("Type ENTER to send messages: "); | ||
| 104 | while (getchar() != '\n') ; | ||
| 105 | |||
| 106 | buffer[0].timestamp = Pt_Time(); | ||
| 107 | #define PROGRAM 0 | ||
| 108 | buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); | ||
| 109 | Pm_Write(midi, buffer, 1); | ||
| 110 | next_time = Pt_Time() + 1000; /* wait 1s */ | ||
| 111 | while (num > 0) { | ||
| 112 | wait_until(next_time); | ||
| 113 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100)); | ||
| 114 | printf("Note On pitch %d\n", pitch); | ||
| 115 | num--; | ||
| 116 | next_time += 500; | ||
| 117 | |||
| 118 | wait_until(next_time); | ||
| 119 | Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0)); | ||
| 120 | printf("Note Off pitch %d\n", pitch); | ||
| 121 | num--; | ||
| 122 | pitch = (pitch + 1) % 12 + 60; | ||
| 123 | next_time += 500; | ||
| 124 | } | ||
| 125 | |||
| 126 | /* close device (this not explicitly needed in most implementations) */ | ||
| 127 | printf("ready to close..."); | ||
| 128 | Pm_Close(midi); | ||
| 129 | printf("done closing.\nNow delete the virtual device..."); | ||
| 130 | checkerror(Pm_DeleteVirtualDevice(id)); | ||
| 131 | printf("done deleting.\n"); | ||
| 132 | } | ||
| 133 | |||
| 134 | |||
| 135 | void show_usage() | ||
| 136 | { | ||
| 137 | printf("Usage: sendvirtual [-h] [-l latency-in-ms] [-m manufacturer] " | ||
| 138 | "[-c clientname] [-p portname] [n]\n" | ||
| 139 | " -h for this message,\n" | ||
| 140 | " -l ms designates latency for precise timing (default 0),\n" | ||
| 141 | " -m name designates a manufacturer name (macOS only),\n" | ||
| 142 | " -c name designates a client name (linux only),\n" | ||
| 143 | " -p name designates a port name (linux only),\n" | ||
| 144 | " n is number of message to send.\n" | ||
| 145 | "sends change program to 1, then one note per second with 0.5s on,\n" | ||
| 146 | "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n" | ||
| 147 | "precise timing (see PortMidi documentation).\n"); | ||
| 148 | exit(0); | ||
| 149 | } | ||
| 150 | |||
| 151 | |||
| 152 | int main(int argc, char *argv[]) | ||
| 153 | { | ||
| 154 | int num = 10; | ||
| 155 | int i; | ||
| 156 | if (argc <= 1) { | ||
| 157 | show_usage(); | ||
| 158 | } | ||
| 159 | for (i = 1; i < argc; i++) { | ||
| 160 | if (strcmp(argv[i], "-h") == 0) { | ||
| 161 | show_usage(); | ||
| 162 | } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { | ||
| 163 | i = i + 1; | ||
| 164 | latency = atoi(argv[i]); | ||
| 165 | printf("Latency will be %d\n", latency); | ||
| 166 | } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { | ||
| 167 | i = i + 1; | ||
| 168 | set_sysdepinfo('m', argv[i]); | ||
| 169 | printf("Manufacturer name will be %s\n", argv[i]); | ||
| 170 | } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { | ||
| 171 | i = i + 1; | ||
| 172 | port_name = argv[i]; | ||
| 173 | set_sysdepinfo('p', port_name); | ||
| 174 | printf("Port name will be %s\n", port_name); | ||
| 175 | } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { | ||
| 176 | i = i + 1; | ||
| 177 | set_sysdepinfo('c', argv[i]); | ||
| 178 | printf("Client name will be %s\n", argv[i]); | ||
| 179 | } else { | ||
| 180 | num = atoi(argv[i]); | ||
| 181 | if (num <= 0) { | ||
| 182 | printf("Zero value or non-number for n\n"); | ||
| 183 | show_usage(); | ||
| 184 | } | ||
| 185 | printf("Sending %d messages.\n", num); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | main_test_output(num); | ||
| 190 | |||
| 191 | printf("finished sendvirtual test...type ENTER to quit..."); | ||
| 192 | while (getchar() != '\n') ; | ||
| 193 | return 0; | ||
| 194 | } | ||
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 | |||
| 39 | int latency = 0; | ||
| 40 | |||
| 41 | /* read a number from console */ | ||
| 42 | /**/ | ||
| 43 | int 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 | /**/ | ||
| 57 | void 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 | } | ||
| 204 | cleanup: | ||
| 205 | Pm_Close(midi_out); | ||
| 206 | Pm_Close(midi_in); | ||
| 207 | return; | ||
| 208 | } | ||
| 209 | |||
| 210 | |||
| 211 | /* send_multiple test -- send many sysex messages */ | ||
| 212 | /**/ | ||
| 213 | void 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 | ||
| 266 | static unsigned char receive_msg[MAX_MSG_LEN]; | ||
| 267 | static int receive_msg_index; | ||
| 268 | static int receive_msg_length; | ||
| 269 | static int receive_msg_count; | ||
| 270 | static int receive_msg_error; | ||
| 271 | static int receive_msg_messages; | ||
| 272 | static PmStream *receive_msg_midi_in; | ||
| 273 | static int receive_poll_running; | ||
| 274 | |||
| 275 | /* receive_poll -- callback function to check for midi input */ | ||
| 276 | /**/ | ||
| 277 | void 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 | /**/ | ||
| 344 | void 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 | } | ||
| 379 | cleanup: | ||
| 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 | |||
| 390 | void 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 | |||
| 451 | void 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 | |||
| 514 | int 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 | |||
| 16 | int32_t latency = 0; | ||
| 17 | int verbose = FALSE; | ||
| 18 | PmSysDepInfo *sysdepinfo = NULL; | ||
| 19 | |||
| 20 | /* crash the program to test whether midi ports are closed */ | ||
| 21 | /**/ | ||
| 22 | void doSomethingReallyStupid() { | ||
| 23 | int * tmp = NULL; | ||
| 24 | *tmp = 5; | ||
| 25 | } | ||
| 26 | |||
| 27 | |||
| 28 | /* exit the program without any explicit cleanup */ | ||
| 29 | /**/ | ||
| 30 | void doSomethingStupid() { | ||
| 31 | assert(0); | ||
| 32 | } | ||
| 33 | |||
| 34 | |||
| 35 | /* read a number from console */ | ||
| 36 | /**/ | ||
| 37 | int 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 | |||
| 49 | static 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 | */ | ||
| 79 | void 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 | |||
| 140 | void 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 | |||
| 292 | void 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 | */ | ||
| 365 | void 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 | |||
| 455 | void 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 | |||
| 466 | int 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 @@ | |||
| 1 | 20 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 | ||
| 3 | 1f 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 | ||
| 5 | 50 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0 | ||
| 6 | 10 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 | ||
| 11 | 1f 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 | ||
| 13 | 65 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0 | ||
| 14 | 18 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 | ||
| 19 | 1f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e | ||
| 20 | 18 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d | ||
| 21 | 69 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 | ||
| 27 | 1f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a | ||
| 28 | 18 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f | ||
| 29 | 72 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 | ||
| 35 | 1f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52 | ||
| 36 | 18 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20 | ||
| 37 | 43 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0 | ||
| 38 | 10 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 | ||
| 43 | 1f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52 | ||
| 44 | 1d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65 | ||
| 45 | 43 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 | ||
| 51 | 12 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52 | ||
| 52 | 18 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70 | ||
| 53 | 65 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0 | ||
| 54 | 51 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 | ||
| 59 | 10 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52 | ||
| 60 | 18 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65 | ||
| 61 | 6c 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 | ||
| 67 | 10 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a | ||
| 68 | 24 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41 | ||
| 69 | 6c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0 | ||
| 70 | 54 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 | ||
| 76 | 18 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f | ||
| 77 | 6e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0 | ||
| 78 | 50 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 | ||
| 85 | 65 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 | ||
| 93 | 69 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0 | ||
| 94 | 32 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 | ||
| 99 | 1f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52 | ||
| 100 | 1d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20 | ||
| 101 | 20 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 | ||
| 108 | 11 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61 | ||
| 109 | 72 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 | ||
| 116 | 11 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74 | ||
| 117 | 68 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0 | ||
| 118 | 11 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 | ||
| 123 | 1f 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 | ||
| 125 | 47 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 | ||
| 131 | 1f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42 | ||
| 132 | 18 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61 | ||
| 133 | 72 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 | ||
| 139 | 1f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42 | ||
| 140 | 18 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79 | ||
| 141 | 20 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 | ||
| 147 | 1f 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 | ||
| 149 | 61 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 | ||
| 155 | 1f 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 | ||
| 157 | 6e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0 | ||
| 158 | 70 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 | ||
| 163 | 1f 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 | ||
| 165 | 79 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0 | ||
| 166 | 40 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 | ||
| 171 | 10 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e | ||
| 172 | 18 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20 | ||
| 173 | 4c 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 | ||
| 180 | 18 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20 | ||
| 181 | 46 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 | ||
| 187 | 1a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62 | ||
| 188 | 18 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20 | ||
| 189 | 4a 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 | ||
| 196 | 11 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42 | ||
| 197 | 72 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 | ||
| 203 | 14 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68 | ||
| 204 | 17 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c | ||
| 205 | 4e 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 | ||
| 211 | 1f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62 | ||
| 212 | 18 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72 | ||
| 213 | 47 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 | ||
| 219 | 16 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 | ||
| 221 | 20 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b | ||
| 222 | 50 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 | ||
| 228 | 18 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62 | ||
| 229 | 65 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0 | ||
| 230 | 40 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 | ||
| 235 | 18 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62 | ||
| 236 | 24 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20 | ||
| 237 | 42 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 | ||
| 243 | 1e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73 | ||
| 244 | 23 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65 | ||
| 245 | 20 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 | ||
| 251 | 1f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42 | ||
| 252 | 18 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20 | ||
| 253 | 44 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 | |||
| 6 | This test is performed by running 2 instances of the program. The | ||
| 7 | first instance makes input and output ports named portmidi and waits | ||
| 8 | for a message. The second tries to do the same, but will fail because | ||
| 9 | portmidi already exists. It then opens portmidi (both input and | ||
| 10 | output). In greater detail: | ||
| 11 | |||
| 12 | FIRST INSTANCE SECOND INSTANCE | ||
| 13 | -------------- --------------- | ||
| 14 | |||
| 15 | initialize PortMidi initialize PortMidi | ||
| 16 | create portmidi in | ||
| 17 | create portmidi out | ||
| 18 | wait for input | ||
| 19 | create portmidi in -> fails | ||
| 20 | open portmidi in/out | ||
| 21 | send to portmidi | ||
| 22 | recv from portmidi | ||
| 23 | send to portmidi | ||
| 24 | wait 1s recv from portmidi | ||
| 25 | close portmidi in and out | ||
| 26 | terminate PortMidi | ||
| 27 | list 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) | ||
| 31 | close portmidi in | ||
| 32 | list 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) | ||
| 36 | close portmidi out | ||
| 37 | list 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) | ||
| 41 | delete 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) | ||
| 45 | delete 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 | ||
| 49 | terminate portmidi | ||
| 50 | REPEAT 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 | |||
| 70 | static 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 | |||
| 79 | static 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 | |||
| 95 | static 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 | |||
| 105 | void wait_until(PmTimestamp when) | ||
| 106 | { | ||
| 107 | PtTimestamp now = Pt_Time(); | ||
| 108 | if (when > now) { | ||
| 109 | Pt_Sleep(when - now); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | |||
| 114 | void show_usage() | ||
| 115 | { | ||
| 116 | printf("Usage: virttest\n" | ||
| 117 | " run two instances to test virtual port create/delete\n"); | ||
| 118 | } | ||
| 119 | |||
| 120 | |||
| 121 | void 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 */ | ||
| 156 | void 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 | |||
| 168 | void 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 | |||
| 183 | void 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 | |||
| 252 | extern int pm_check_errors; | ||
| 253 | |||
| 254 | void 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 | |||
| 329 | int 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 @@ | |||
| 1 | File: PortMidi Win32 Readme | ||
| 2 | Author: Belinda Thom, June 16 2002 | ||
| 3 | Revised by: Roger Dannenberg, June 2002, May 2004, June 2007, | ||
| 4 | Umpei Kurokawa, June 2007 | ||
| 5 | Roger Dannenberg Sep 2009, May 2022 | ||
| 6 | |||
| 7 | Contents: | ||
| 8 | Using Portmidi | ||
| 9 | To Install Portmidi | ||
| 10 | To Compile Portmidi | ||
| 11 | About Cmake | ||
| 12 | Using other versions of Visual C++ | ||
| 13 | To Create Your Own Portmidi Client Application | ||
| 14 | |||
| 15 | |||
| 16 | |||
| 17 | ============================================================================= | ||
| 18 | USING PORTMIDI: | ||
| 19 | ============================================================================= | ||
| 20 | |||
| 21 | I recommend building a static library and linking with your | ||
| 22 | application. PortMidi is not large. See ../README.md for | ||
| 23 | basic compiling instructions. | ||
| 24 | |||
| 25 | The Windows version has a couple of extra switches: You can define | ||
| 26 | DEBUG and MMDEBUG for a few extra messages (see the code). | ||
| 27 | |||
| 28 | If PM_CHECK_ERRORS is defined, PortMidi reports and exits on any | ||
| 29 | error. This requires terminal output to see, and aborts your | ||
| 30 | application, so it's only intended for quick command line programs | ||
| 31 | where you do not care to check return values and handle errors | ||
| 32 | more robustly. | ||
| 33 | |||
| 34 | PortMidi is designed to run without a console and should work perfectly | ||
| 35 | well within a graphical user interface application. | ||
| 36 | |||
| 37 | Read the portmidi.h file for PortMidi API details on using the PortMidi API. | ||
| 38 | See <...>\pm_test\testio.c and other files in pm_test for usage examples. | ||
| 39 | |||
| 40 | There are many other programs in pm_test, including a MIDI monitor. | ||
| 41 | |||
| 42 | |||
| 43 | ============================================================================ | ||
| 44 | DESIGN NOTES | ||
| 45 | ============================================================================ | ||
| 46 | |||
| 47 | Orderly cleanup after errors are encountered is based on a fixed order of | ||
| 48 | steps and state changes to reflect each step. Here's the order: | ||
| 49 | |||
| 50 | To open input: | ||
| 51 | initialize return value to NULL | ||
| 52 | - allocate the PmInternal strucure (representation of PortMidiStream) | ||
| 53 | return value is (non-null) PmInternal structure | ||
| 54 | - allocate midi buffer | ||
| 55 | set buffer field of PmInternal structure | ||
| 56 | - call system-dependent open code | ||
| 57 | - allocate midiwinmm_type for winmm dependent data | ||
| 58 | set descriptor field of PmInternal structure | ||
| 59 | - open device | ||
| 60 | set handle field of midiwinmm_type structure | ||
| 61 | - allocate buffers | ||
| 62 | - start device | ||
| 63 | - return | ||
| 64 | - return | ||
| 65 | |||
| 66 | SYSEX HANDLING | ||
| 67 | |||
| 68 | There are three cases: simple output, stream output, input | ||
| 69 | Each must deal with: | ||
| 70 | 1. Buffer Initialization (creating buffers) | ||
| 71 | 2. Buffer Allocation (finding a free buffer) | ||
| 72 | 3. Buffer Fill (putting bytes in the buffer) | ||
| 73 | 4. Buffer Preparation (midiOutPrepare, etc.) | ||
| 74 | 5. Buffer Send (to Midi device) | ||
| 75 | 6. Buffer Receive (in callback) | ||
| 76 | 7. Buffer Empty (removing bytes from buffer) | ||
| 77 | 8. Buffer Free (returning to the buffer pool) | ||
| 78 | 9. Buffer Finalization (returning to heap) | ||
| 79 | |||
| 80 | Here's how simple output handles sysex: | ||
| 81 | 1. Buffer Initialization (creating buffers) | ||
| 82 | allocated when code tries to write first byte to a buffer | ||
| 83 | the test is "if (!m->sysex_buffers[0]) { ... }" | ||
| 84 | this field is initialized to NULL when device is opened | ||
| 85 | the size is SYSEX_BYTES_PER_BUFFER | ||
| 86 | allocate_sysex_buffers() does the initialization | ||
| 87 | note that the actual size of the allocation includes | ||
| 88 | additional space for a MIDIEVENT (3 longs) which are | ||
| 89 | not used in this case | ||
| 90 | 2. Buffer Allocation (finding a free buffer) | ||
| 91 | see get_free_sysex_buffer() | ||
| 92 | cycle through m->sysex_buffers[] using m->next_sysex_buffer | ||
| 93 | to determine where to look next | ||
| 94 | if nothing is found, wait by blocking on m->sysex_buffer_signal | ||
| 95 | this is signaled by the callback every time a message is | ||
| 96 | received | ||
| 97 | 3. Buffer Fill (putting bytes in the buffer) | ||
| 98 | essentially a state machine approach | ||
| 99 | hdr->dwBytesRecorded is a position in message pointed to by m->hdr | ||
| 100 | keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER | ||
| 101 | then send the message, reseting the state to initial values | ||
| 102 | 4. Buffer Preparation (midiOutPrepare, etc.) | ||
| 103 | just before sending in winmm_end_sysex() | ||
| 104 | 5. Buffer Send (to Midi device) | ||
| 105 | message is padded with zero at end (since extra space was allocated | ||
| 106 | this is ok) -- the zero works around a bug in (an old version of) | ||
| 107 | MIDI YOKE drivers | ||
| 108 | dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0 | ||
| 109 | uses midiOutLongMsg() | ||
| 110 | 6. Buffer Receive (in callback) | ||
| 111 | 7. Buffer Empty (removing bytes from buffer) | ||
| 112 | not applicable for output | ||
| 113 | 8. Buffer Free (returning to the buffer pool) | ||
| 114 | unprepare message to indicate that it is free | ||
| 115 | SetEvent on m->buffer_signal in case client is waiting | ||
| 116 | 9. Buffer Finalization (returning to heap) | ||
| 117 | when device is closed, winmm_out_delete frees all sysex buffers | ||
| 118 | |||
| 119 | Here's how stream output handles sysex: | ||
| 120 | 1. Buffer Initialization (creating buffers) | ||
| 121 | same code as simple output (see above) | ||
| 122 | 2. Buffer Allocation (finding a free buffer) | ||
| 123 | same code as simple output (see above) | ||
| 124 | 3. Buffer Fill (putting bytes in the buffer) | ||
| 125 | essentially a state machine approach | ||
| 126 | m->dwBytesRecorded is a position in message | ||
| 127 | keep appending bytes until buffer is full (one byte to spare) | ||
| 128 | 4. Buffer Preparation (midiOutPrepare, etc.) | ||
| 129 | done before sending message | ||
| 130 | dwBytesRecorded and dwBufferLength are set in winmm_end_sysex | ||
| 131 | 5. Buffer Send (to Midi device) | ||
| 132 | uses midiStreamOutMsg() | ||
| 133 | 6. Buffer Receive (in callback) | ||
| 134 | 7. Buffer Empty (removing bytes from buffer) | ||
| 135 | not applicable for output | ||
| 136 | 8. Buffer Free (returning to the buffer pool) | ||
| 137 | unprepare message to indicate that it is free | ||
| 138 | SetEvent on m->buffer_signal in case client is waiting | ||
| 139 | 9. Buffer Finalization (returning to heap) | ||
| 140 | when device is closed, winmm_out_delete frees all sysex buffers | ||
| 141 | |||
| 142 | |||
| 143 | Here's how input handles sysex: | ||
| 144 | 1. Buffer Initialization (creating buffers) | ||
| 145 | two buffers are allocated in winmm_in_open | ||
| 146 | 2. Buffer Allocation (finding a free buffer) | ||
| 147 | same code as simple output (see above) | ||
| 148 | 3. Buffer Fill (putting bytes in the buffer) | ||
| 149 | not applicable for input | ||
| 150 | 4. Buffer Preparation (midiOutPrepare, etc.) | ||
| 151 | done before sending message -- in winmm_in_open and in callback | ||
| 152 | 5. Buffer Send (to Midi device) | ||
| 153 | uses midiInAddbuffer in allocate_sysex_input_buffer (called from | ||
| 154 | winmm_in_open) and callback | ||
| 155 | 6. Buffer Receive (in callback) | ||
| 156 | 7. Buffer Empty (removing bytes from buffer) | ||
| 157 | done without pause in loop in callback | ||
| 158 | 8. Buffer Free (returning to the buffer pool) | ||
| 159 | done by midiInAddBuffer in callback, no pointer to buffers | ||
| 160 | is retained except by device | ||
| 161 | 9. Buffer Finalization (returning to heap) | ||
| 162 | when device is closed, empty buffers are delivered to callback, | ||
| 163 | which frees them | ||
| 164 | |||
| 165 | IMPORTANT: In addition to the above, PortMidi now has | ||
| 166 | "shortcuts" to optimize the transfer of sysex data. To enable | ||
| 167 | the optimization for sysex output, the system-dependent code | ||
| 168 | sets fields in the pmInternal structure: fill_base, fill_offset_ptr, | ||
| 169 | and fill_length. When fill_base is non-null, the system-independent | ||
| 170 | part of PortMidi is allowed to directly copy sysex bytes to | ||
| 171 | "fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches | ||
| 172 | fill_length. See the code for details. | ||
| 173 | |||
| 174 | |||
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 | ======================================================================================================================== | ||
| 2 | Methods for Debugging DLLs | ||
| 3 | ======================================================================================================================== | ||
| 4 | If you have the source for both the DLL and the calling program, open the project for the calling executable file and | ||
| 5 | debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the | ||
| 6 | Debug tab in the Project Settings dialog box. | ||
| 7 | |||
| 8 | If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project | ||
| 9 | Settings dialog box to specify the executable file that calls the DLL. | ||
| 10 | |||
| 11 | You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you | ||
| 12 | don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL | ||
| 13 | file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After | ||
| 14 | Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging. | ||
| 15 | |||
| 16 | To debug a DLL using the project for the executable file | ||
| 17 | |||
| 18 | From the Project menu, click Settings. | ||
| 19 | The Project Settings dialog box appears. | ||
| 20 | |||
| 21 | Choose the Debug tab. | ||
| 22 | |||
| 23 | |||
| 24 | In the Category drop-down list box, select General. | ||
| 25 | |||
| 26 | |||
| 27 | In the Program Arguments text box, type any command-line arguments required by the executable file. | ||
| 28 | |||
| 29 | |||
| 30 | In the Category drop-down list box, select Additional DLLs. | ||
| 31 | |||
| 32 | |||
| 33 | In the Local Name column, type the names of DLLs to debug. | ||
| 34 | If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the | ||
| 35 | remote module to map to the local module name. | ||
| 36 | |||
| 37 | In the Preload column, select the check box if you want to load the module before debugging begins. | ||
| 38 | |||
| 39 | |||
| 40 | Click OK to store the information in your project. | ||
| 41 | |||
| 42 | |||
| 43 | From the Build menu, click Start Debug and Go to start the debugger. | ||
| 44 | You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints | ||
| 45 | in that file, even though it is not a part of the executable file’s project. | ||
| 46 | |||
| 47 | To debug a DLL using the project for the DLL | ||
| 48 | |||
| 49 | From the Project menu, click Settings. | ||
| 50 | The Project Settings dialog box appears. | ||
| 51 | |||
| 52 | Choose the Debug tab. | ||
| 53 | |||
| 54 | |||
| 55 | In the Category drop-down list box, select General. | ||
| 56 | |||
| 57 | |||
| 58 | In the Executable For Debug Session text box, type the name of the executable file that calls the DLL. | ||
| 59 | |||
| 60 | |||
| 61 | In the Category list box, select Additional DLLs. | ||
| 62 | |||
| 63 | |||
| 64 | In the Local Module Name column, type the name of the DLLs you want to debug. | ||
| 65 | |||
| 66 | |||
| 67 | Click OK to store the information in your project. | ||
| 68 | |||
| 69 | |||
| 70 | Set breakpoints as required in your DLL source files or on function symbols in the DLL. | ||
| 71 | |||
| 72 | |||
| 73 | From the Build menu, click Start Debug and Go to start the debugger. | ||
| 74 | To debug a DLL created with an external project | ||
| 75 | |||
| 76 | From the Project menu, click Settings. | ||
| 77 | The Project Settings dialog box appears. | ||
| 78 | |||
| 79 | Choose the Debug tab. | ||
| 80 | |||
| 81 | |||
| 82 | In the Category drop-down list box, select General. | ||
| 83 | |||
| 84 | |||
| 85 | In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds. | ||
| 86 | |||
| 87 | |||
| 88 | Click OK to store the information in your project. | ||
| 89 | |||
| 90 | |||
| 91 | Build a debug version of the DLL with symbolic debugging information, if you don’t already have one. | ||
| 92 | |||
| 93 | |||
| 94 | Follow one of the two procedures immediately preceding this one to debug the DLL. | ||
| 95 | |||
| 96 | ======================================================================================================================== | ||
| 97 | Why Don’t My DLL Breakpoints Work? | ||
| 98 | ======================================================================================================================== | ||
| 99 | Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each. | ||
| 100 | If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics. | ||
| 101 | Often breakpoint problems result from a combination of conditions. | ||
| 102 | |||
| 103 | You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by | ||
| 104 | the debugger. | ||
| 105 | You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory | ||
| 106 | by the debugger. | ||
| 107 | Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep. | ||
| 108 | |||
| 109 | When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep | ||
| 110 | track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic | ||
| 111 | information for all the code to be debugged and then walks through its breakpoint list, attempting to set the | ||
| 112 | breakpoints. | ||
| 113 | |||
| 114 | However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic | ||
| 115 | information for the debugger to use when walking through its breakpoint list. Situations where this is likely to | ||
| 116 | occur include: | ||
| 117 | |||
| 118 | Attempts to set breakpoints in a DLL before the call to LoadLibrary. | ||
| 119 | |||
| 120 | Setting a breakpoint in an ActiveX server before the container has started the server. | ||
| 121 | |||
| 122 | Other similar cases. | ||
| 123 | |||
| 124 | To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field | ||
| 125 | in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for | ||
| 126 | additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory | ||
| 127 | will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical | ||
| 128 | breakpoints. Make sure that these additional debugging processes are not already running when you start your | ||
| 129 | debugging session. The debugging process and these additional processes must be sychronized at the same beginning | ||
| 130 | point to work correctly, hitting all breakpoints. | ||
| 131 | |||
| 132 | Breakpoints are missed when more than one copy of a DLL is on your hard disk. | ||
| 133 | Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause | ||
| 134 | debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the | ||
| 135 | Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the | ||
| 136 | DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to | ||
| 137 | keep only one version of a DLL at a time in your path, current directory, and Windows directory. | ||
| 138 | |||
| 139 | You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL. | ||
| 140 | Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call | ||
| 141 | to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet). | ||
| 142 | Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at | ||
| 143 | startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded | ||
| 144 | into memory before this kind of breakpoint can be set. This means that the calling application's code must be | ||
| 145 | executed 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 | */ | ||
| 29 | static void pm_exit(void) { | ||
| 30 | pm_term(); | ||
| 31 | } | ||
| 32 | |||
| 33 | |||
| 34 | static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType) | ||
| 35 | { | ||
| 36 | exit(1); /* invokes pm_exit() */ | ||
| 37 | ExitProcess(1); /* probably never called */ | ||
| 38 | return TRUE; | ||
| 39 | } | ||
| 40 | |||
| 41 | /* pm_init is the windows-dependent initialization.*/ | ||
| 42 | void pm_init(void) | ||
| 43 | { | ||
| 44 | atexit(pm_exit); | ||
| 45 | SetConsoleCtrlHandler(ctrl_c_handler, TRUE); | ||
| 46 | #ifdef DEBUG | ||
| 47 | printf("registered pm_exit with atexit()\n"); | ||
| 48 | #endif | ||
| 49 | pm_winmm_init(); | ||
| 50 | /* initialize other APIs (DirectX?) here */ | ||
| 51 | } | ||
| 52 | |||
| 53 | |||
| 54 | void pm_term(void) { | ||
| 55 | pm_winmm_term(); | ||
| 56 | } | ||
| 57 | |||
| 58 | |||
| 59 | static PmDeviceID pm_get_default_device_id(int is_input, char *key) { | ||
| 60 | #define PATTERN_MAX 256 | ||
| 61 | /* Find first input or device -- this is the default. */ | ||
| 62 | PmDeviceID id = pmNoDevice; | ||
| 63 | int i; | ||
| 64 | Pm_Initialize(); /* make sure descriptors exist! */ | ||
| 65 | for (i = 0; i < pm_descriptor_len; i++) { | ||
| 66 | if (pm_descriptors[i].pub.input == is_input) { | ||
| 67 | id = i; | ||
| 68 | break; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | return id; | ||
| 72 | } | ||
| 73 | |||
| 74 | |||
| 75 | PmDeviceID Pm_GetDefaultInputDeviceID() { | ||
| 76 | return pm_get_default_device_id(TRUE, | ||
| 77 | "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E"); | ||
| 78 | } | ||
| 79 | |||
| 80 | |||
| 81 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | ||
| 82 | return pm_get_default_device_id(FALSE, | ||
| 83 | "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E"); | ||
| 84 | } | ||
| 85 | |||
| 86 | |||
| 87 | #include "stdio.h" | ||
| 88 | |||
| 89 | void *pm_alloc(size_t s) { | ||
| 90 | return malloc(s); | ||
| 91 | } | ||
| 92 | |||
| 93 | |||
| 94 | void pm_free(void *ptr) { | ||
| 95 | free(ptr); | ||
| 96 | } | ||
| 97 | |||
| 98 | |||
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 */ | ||
| 42 | static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, | ||
| 43 | UINT wMsg, DWORD_PTR dwInstance, | ||
| 44 | DWORD_PTR dwParam1, DWORD_PTR dwParam2); | ||
| 45 | static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, | ||
| 46 | DWORD_PTR dwInstance, | ||
| 47 | DWORD_PTR dwParam1, | ||
| 48 | DWORD_PTR dwParam2); | ||
| 49 | |||
| 50 | extern pm_fns_node pm_winmm_in_dictionary; | ||
| 51 | extern pm_fns_node pm_winmm_out_dictionary; | ||
| 52 | |||
| 53 | static void winmm_out_delete(PmInternal *midi); /* forward reference */ | ||
| 54 | |||
| 55 | /* | ||
| 56 | A note about buffers: WinMM seems to hold onto buffers longer than | ||
| 57 | one would expect, e.g. when I tried using 2 small buffers to send | ||
| 58 | long sysex messages, at some point WinMM held both buffers. This problem | ||
| 59 | was fixed by making buffers bigger. Therefore, it seems that there should | ||
| 60 | be enough buffer space to hold a whole sysex message. | ||
| 61 | |||
| 62 | The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) | ||
| 63 | will be used to estimate the largest sysex message (= buffer_len * 4 bytes). | ||
| 64 | Call that the max_sysex_len = buffer_len * 4. | ||
| 65 | |||
| 66 | For simple midi output (latency == 0), allocate 3 buffers, each with half | ||
| 67 | the size of max_sysex_len, but each at least 256 bytes. | ||
| 68 | |||
| 69 | For stream output, there will already be enough space in very short | ||
| 70 | buffers, so use them, but make sure there are at least 16. | ||
| 71 | |||
| 72 | For input, use many small buffers rather than 2 large ones so that when | ||
| 73 | there are short sysex messages arriving frequently (as in control surfaces) | ||
| 74 | there will be more free buffers to fill. Use max_sysex_len / 64 buffers, | ||
| 75 | but at least 16, of size 64 bytes each. | ||
| 76 | |||
| 77 | The 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 | ============================================================================== | ||
| 101 | win32 mmedia system specific structure passed to midi callbacks | ||
| 102 | ============================================================================== | ||
| 103 | */ | ||
| 104 | |||
| 105 | /* global winmm device info */ | ||
| 106 | MIDIINCAPS *midi_in_caps = NULL; | ||
| 107 | MIDIINCAPS midi_in_mapper_caps; | ||
| 108 | UINT midi_num_inputs = 0; | ||
| 109 | MIDIOUTCAPS *midi_out_caps = NULL; | ||
| 110 | MIDIOUTCAPS midi_out_mapper_caps; | ||
| 111 | UINT midi_num_outputs = 0; | ||
| 112 | |||
| 113 | /* per device info */ | ||
| 114 | typedef 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 | ============================================================================= | ||
| 145 | general MIDI device queries | ||
| 146 | ============================================================================= | ||
| 147 | */ | ||
| 148 | |||
| 149 | /* add a device after converting device (product) name to UTF-8 */ | ||
| 150 | static 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 | |||
| 162 | static 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 | |||
| 188 | static 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 | |||
| 206 | static 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 | |||
| 229 | static 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 | ============================================================================ | ||
| 247 | host error handling | ||
| 248 | ============================================================================ | ||
| 249 | */ | ||
| 250 | |||
| 251 | static unsigned int winmm_check_host_error(PmInternal *midi) | ||
| 252 | { | ||
| 253 | return FALSE; | ||
| 254 | } | ||
| 255 | |||
| 256 | |||
| 257 | /* | ||
| 258 | ============================================================================= | ||
| 259 | buffer handling | ||
| 260 | ============================================================================= | ||
| 261 | */ | ||
| 262 | static 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 | |||
| 277 | static 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 | |||
| 301 | static 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 | } | ||
| 359 | found_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 | ============================================================================ | ||
| 368 | begin midi input implementation | ||
| 369 | ============================================================================ | ||
| 370 | */ | ||
| 371 | |||
| 372 | |||
| 373 | static 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 | |||
| 390 | static 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 | |||
| 413 | static 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 | |||
| 430 | static 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 | |||
| 440 | static 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 | |||
| 450 | static 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); | ||
| 497 | close_device: | ||
| 498 | midiInClose(info->handle.in); /* ignore return code */ | ||
| 499 | free_descriptor: | ||
| 500 | midi->api_info = NULL; | ||
| 501 | pm_free(info); | ||
| 502 | no_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 | */ | ||
| 519 | static 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). */ | ||
| 544 | static 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 | =========================================================================== | ||
| 651 | begin 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 */ | ||
| 658 | static 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 | |||
| 674 | static 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 | |||
| 689 | static 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 | |||
| 768 | free_buffers: | ||
| 769 | /* buffers are freed below by winmm_out_delete */ | ||
| 770 | close_device: | ||
| 771 | midiOutClose(info->handle.out); | ||
| 772 | free_descriptor: | ||
| 773 | midi->api_info = NULL; | ||
| 774 | winmm_out_delete(midi); /* frees buffers and m */ | ||
| 775 | no_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 | /**/ | ||
| 786 | static 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 */ | ||
| 810 | static 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 | |||
| 831 | static 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 | |||
| 847 | static 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 | |||
| 880 | static 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 | ||
| 920 | static 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 | |||
| 935 | static 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 | |||
| 976 | static 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 | |||
| 1029 | static 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 */ | ||
| 1057 | static 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 | =========================================================================== | ||
| 1099 | begin exported functions | ||
| 1100 | =========================================================================== | ||
| 1101 | */ | ||
| 1102 | |||
| 1103 | #define winmm_in_abort pm_fail_fn | ||
| 1104 | pm_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 | |||
| 1119 | pm_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 | */ | ||
| 1144 | void 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 | */ | ||
| 1157 | void 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 | |||
| 3 | void pm_winmm_init( void ); | ||
| 4 | void 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 | |||
| 11 | if(MSVC) | ||
| 12 | foreach(flag_var | ||
| 13 | CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE | ||
| 14 | CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO | ||
| 15 | CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE | ||
| 16 | CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) | ||
| 17 | if(${flag_var} MATCHES "/MD") | ||
| 18 | string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") | ||
| 19 | endif(${flag_var} MATCHES "/MD") | ||
| 20 | endforeach(flag_var) | ||
| 21 | |||
| 22 | message(STATUS | ||
| 23 | "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library") | ||
| 24 | endif(MSVC) | ||
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 | ||
| 18 | typedef int int32_t; | ||
| 19 | typedef 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 | ||
| 27 | extern "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 | |||
| 42 | typedef 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. */ | ||
| 53 | typedef int32_t PtTimestamp; | ||
| 54 | |||
| 55 | /** a function that gets a current time */ | ||
| 56 | typedef 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 | */ | ||
| 69 | PMEXPORT 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 | */ | ||
| 75 | PMEXPORT PtError Pt_Stop(void); | ||
| 76 | |||
| 77 | /** test if the timer is running. | ||
| 78 | |||
| 79 | @return TRUE or FALSE | ||
| 80 | */ | ||
| 81 | PMEXPORT int Pt_Started(void); | ||
| 82 | |||
| 83 | /** get the current time in ms. | ||
| 84 | |||
| 85 | @return the current time | ||
| 86 | */ | ||
| 87 | PMEXPORT 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 | */ | ||
| 95 | PMEXPORT 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 | |||
| 8 | namespace { | ||
| 9 | const uint32 timerMessage = 'PTTM'; | ||
| 10 | |||
| 11 | struct TimerLooper : BLooper { | ||
| 12 | TimerLooper() : BLooper() { | ||
| 13 | } | ||
| 14 | |||
| 15 | |||
| 16 | virtual void MessageReceived(BMessage *message) | ||
| 17 | { | ||
| 18 | PtCallback *callback; | ||
| 19 | void *userData; | ||
| 20 | if (message->what == timerMessage && message->FindPointer("callback", (void**)&callback) == B_OK && message->FindPointer("userData", &userData) == B_OK) { | ||
| 21 | (*callback)(Pt_Time(), userData); | ||
| 22 | } | ||
| 23 | BLooper::MessageReceived(message); | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | |||
| 27 | bool time_started_flag = false; | ||
| 28 | bigtime_t time_offset; | ||
| 29 | TimerLooper *timerLooper; | ||
| 30 | BMessageRunner *timerRunner; | ||
| 31 | } | ||
| 32 | |||
| 33 | extern "C" { | ||
| 34 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | ||
| 35 | { | ||
| 36 | if (time_started_flag) return ptAlreadyStarted; | ||
| 37 | time_offset = system_time(); | ||
| 38 | if (callback) { | ||
| 39 | timerLooper = new TimerLooper; | ||
| 40 | timerLooper->Run(); | ||
| 41 | BMessenger target(timerLooper); | ||
| 42 | BMessage message(timerMessage); | ||
| 43 | message.AddPointer("callback", (void*)callback); | ||
| 44 | message.AddPointer("userData", userData); | ||
| 45 | bigtime_t interval = resolution * 1000; | ||
| 46 | timerRunner = new BMessageRunner(target, &message, interval); | ||
| 47 | if(timerRunner->InitCheck() != B_OK) { | ||
| 48 | delete timerRunner; | ||
| 49 | timerRunner = NULL; | ||
| 50 | timerLooper->PostMessage(B_QUIT_REQUESTED); | ||
| 51 | timerLooper = NULL; | ||
| 52 | return ptHostError; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | time_started_flag = true; | ||
| 56 | return ptNoError; | ||
| 57 | } | ||
| 58 | |||
| 59 | |||
| 60 | PtError Pt_Stop() | ||
| 61 | { | ||
| 62 | if (!time_started_flag) return ptAlreadyStopped; | ||
| 63 | time_started_flag = false; | ||
| 64 | delete timerRunner; | ||
| 65 | timerRunner = NULL; | ||
| 66 | timerLooper->PostMessage(B_QUIT_REQUESTED); | ||
| 67 | timerLooper = NULL; | ||
| 68 | return ptNoError; | ||
| 69 | } | ||
| 70 | |||
| 71 | |||
| 72 | int Pt_Started() | ||
| 73 | { | ||
| 74 | return time_started_flag; | ||
| 75 | } | ||
| 76 | |||
| 77 | |||
| 78 | PtTimestamp Pt_Time() | ||
| 79 | { | ||
| 80 | return (system_time() - time_offset) / 1000; | ||
| 81 | } | ||
| 82 | |||
| 83 | |||
| 84 | void Pt_Sleep(int32_t duration) | ||
| 85 | { | ||
| 86 | snooze(duration * 1000); | ||
| 87 | } | ||
| 88 | } | ||
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 | |||
| 6 | Unlike Windows, Linux has no system call to request a periodic callback, | ||
| 7 | so if Pt_Start() receives a callback parameter, it must create a thread | ||
| 8 | that wakes up periodically and calls the provided callback function. | ||
| 9 | If running as superuser, use setpriority() to renice thread to -20. | ||
| 10 | One could also set the timer thread to a real-time priority (SCHED_FIFO | ||
| 11 | and SCHED_RR), but this is dangerous for This is necessary because | ||
| 12 | if the callback hangs it'll never return. A more serious reason | ||
| 13 | is that the current scheduler implementation busy-waits instead | ||
| 14 | of sleeping when realtime threads request a sleep of <=2ms (as a way | ||
| 15 | to get around the 10ms granularity), which means the thread would never | ||
| 16 | let anyone else on the CPU. | ||
| 17 | |||
| 18 | CHANGE LOG | ||
| 19 | |||
| 20 | 18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer | ||
| 21 | thread. Simplified implementation notes. | ||
| 22 | |||
| 23 | */ | ||
| 24 | /* stdlib, stdio, unistd, and sys/types were added because they appeared | ||
| 25 | * in a Gentoo patch, but I'm not sure why they are needed. -RBD | ||
| 26 | */ | ||
| 27 | #include <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 | |||
| 43 | static int time_started_flag = FALSE; | ||
| 44 | static struct timespec time_offset = {0, 0}; | ||
| 45 | static pthread_t pt_thread_pid; | ||
| 46 | static int pt_thread_created = FALSE; | ||
| 47 | |||
| 48 | /* note that this is static data -- we only need one copy */ | ||
| 49 | typedef struct { | ||
| 50 | int id; | ||
| 51 | int resolution; | ||
| 52 | PtCallback *callback; | ||
| 53 | void *userData; | ||
| 54 | } pt_callback_parameters; | ||
| 55 | |||
| 56 | static int pt_callback_proc_id = 0; | ||
| 57 | |||
| 58 | static void *Pt_CallbackProc(void *p) | ||
| 59 | { | ||
| 60 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; | ||
| 61 | int mytime = 1; | ||
| 62 | /* to kill a process, just increment the pt_callback_proc_id */ | ||
| 63 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, | ||
| 64 | parameters->id); */ | ||
| 65 | if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); | ||
| 66 | while (pt_callback_proc_id == parameters->id) { | ||
| 67 | /* wait for a multiple of resolution ms */ | ||
| 68 | struct timeval timeout; | ||
| 69 | int delay = mytime++ * parameters->resolution - Pt_Time(); | ||
| 70 | if (delay < 0) delay = 0; | ||
| 71 | timeout.tv_sec = 0; | ||
| 72 | timeout.tv_usec = delay * 1000; | ||
| 73 | select(0, NULL, NULL, NULL, &timeout); | ||
| 74 | (*(parameters->callback))(Pt_Time(), parameters->userData); | ||
| 75 | } | ||
| 76 | /* printf("Pt_CallbackProc exiting\n"); */ | ||
| 77 | // free(parameters); | ||
| 78 | return NULL; | ||
| 79 | } | ||
| 80 | |||
| 81 | |||
| 82 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | ||
| 83 | { | ||
| 84 | if (time_started_flag) return ptNoError; | ||
| 85 | /* need this set before process runs: */ | ||
| 86 | clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset); | ||
| 87 | if (callback) { | ||
| 88 | int res; | ||
| 89 | pt_callback_parameters *parms = (pt_callback_parameters *) | ||
| 90 | malloc(sizeof(pt_callback_parameters)); | ||
| 91 | if (!parms) return ptInsufficientMemory; | ||
| 92 | parms->id = pt_callback_proc_id; | ||
| 93 | parms->resolution = resolution; | ||
| 94 | parms->callback = callback; | ||
| 95 | parms->userData = userData; | ||
| 96 | res = pthread_create(&pt_thread_pid, NULL, | ||
| 97 | Pt_CallbackProc, parms); | ||
| 98 | if (res != 0) return ptHostError; | ||
| 99 | pt_thread_created = TRUE; | ||
| 100 | } | ||
| 101 | time_started_flag = TRUE; | ||
| 102 | return ptNoError; | ||
| 103 | } | ||
| 104 | |||
| 105 | |||
| 106 | PtError Pt_Stop() | ||
| 107 | { | ||
| 108 | /* printf("Pt_Stop called\n"); */ | ||
| 109 | pt_callback_proc_id++; | ||
| 110 | if (pt_thread_created) { | ||
| 111 | pthread_join(pt_thread_pid, NULL); | ||
| 112 | pt_thread_created = FALSE; | ||
| 113 | } | ||
| 114 | time_started_flag = FALSE; | ||
| 115 | return ptNoError; | ||
| 116 | } | ||
| 117 | |||
| 118 | |||
| 119 | int Pt_Started() | ||
| 120 | { | ||
| 121 | return time_started_flag; | ||
| 122 | } | ||
| 123 | |||
| 124 | |||
| 125 | PtTimestamp 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 | |||
| 136 | void 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 | |||
| 18 | static int time_started_flag = FALSE; | ||
| 19 | static CFAbsoluteTime startTime = 0.0; | ||
| 20 | static CFRunLoopRef timerRunLoop; | ||
| 21 | |||
| 22 | typedef struct { | ||
| 23 | int resolution; | ||
| 24 | PtCallback *callback; | ||
| 25 | void *userData; | ||
| 26 | } PtThreadParams; | ||
| 27 | |||
| 28 | |||
| 29 | void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) | ||
| 30 | { | ||
| 31 | PtThreadParams *params = (PtThreadParams*)info; | ||
| 32 | (*params->callback)(Pt_Time(), params->userData); | ||
| 33 | } | ||
| 34 | |||
| 35 | static void* Pt_Thread(void *p) | ||
| 36 | { | ||
| 37 | CFTimeInterval timerInterval; | ||
| 38 | CFRunLoopTimerContext timerContext; | ||
| 39 | CFRunLoopTimerRef timer; | ||
| 40 | PtThreadParams *params = (PtThreadParams*)p; | ||
| 41 | //CFTimeInterval timeout; | ||
| 42 | |||
| 43 | /* raise the thread's priority */ | ||
| 44 | kern_return_t error; | ||
| 45 | thread_extended_policy_data_t extendedPolicy; | ||
| 46 | thread_precedence_policy_data_t precedencePolicy; | ||
| 47 | |||
| 48 | extendedPolicy.timeshare = 0; | ||
| 49 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, | ||
| 50 | (thread_policy_t)&extendedPolicy, | ||
| 51 | THREAD_EXTENDED_POLICY_COUNT); | ||
| 52 | if (error != KERN_SUCCESS) { | ||
| 53 | mach_error("Couldn't set thread timeshare policy", error); | ||
| 54 | } | ||
| 55 | |||
| 56 | precedencePolicy.importance = THREAD_IMPORTANCE; | ||
| 57 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, | ||
| 58 | (thread_policy_t)&precedencePolicy, | ||
| 59 | THREAD_PRECEDENCE_POLICY_COUNT); | ||
| 60 | if (error != KERN_SUCCESS) { | ||
| 61 | mach_error("Couldn't set thread precedence policy", error); | ||
| 62 | } | ||
| 63 | |||
| 64 | /* set up the timer context */ | ||
| 65 | timerContext.version = 0; | ||
| 66 | timerContext.info = params; | ||
| 67 | timerContext.retain = NULL; | ||
| 68 | timerContext.release = NULL; | ||
| 69 | timerContext.copyDescription = NULL; | ||
| 70 | |||
| 71 | /* create a new timer */ | ||
| 72 | timerInterval = (double)params->resolution / 1000.0; | ||
| 73 | timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, | ||
| 74 | 0, 0, Pt_CFTimerCallback, &timerContext); | ||
| 75 | |||
| 76 | timerRunLoop = CFRunLoopGetCurrent(); | ||
| 77 | CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); | ||
| 78 | |||
| 79 | /* run until we're told to stop by Pt_Stop() */ | ||
| 80 | CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); | ||
| 81 | |||
| 82 | CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); | ||
| 83 | CFRelease(timer); | ||
| 84 | free(params); | ||
| 85 | |||
| 86 | return NULL; | ||
| 87 | } | ||
| 88 | |||
| 89 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | ||
| 90 | { | ||
| 91 | PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); | ||
| 92 | pthread_t pthread_id; | ||
| 93 | |||
| 94 | printf("Pt_Start() called\n"); | ||
| 95 | |||
| 96 | // /* make sure we're not already playing */ | ||
| 97 | if (time_started_flag) return ptAlreadyStarted; | ||
| 98 | startTime = CFAbsoluteTimeGetCurrent(); | ||
| 99 | |||
| 100 | if (callback) { | ||
| 101 | |||
| 102 | params->resolution = resolution; | ||
| 103 | params->callback = callback; | ||
| 104 | params->userData = userData; | ||
| 105 | |||
| 106 | pthread_create(&pthread_id, NULL, Pt_Thread, params); | ||
| 107 | } | ||
| 108 | |||
| 109 | time_started_flag = TRUE; | ||
| 110 | return ptNoError; | ||
| 111 | } | ||
| 112 | |||
| 113 | |||
| 114 | PtError Pt_Stop() | ||
| 115 | { | ||
| 116 | printf("Pt_Stop called\n"); | ||
| 117 | |||
| 118 | CFRunLoopStop(timerRunLoop); | ||
| 119 | time_started_flag = FALSE; | ||
| 120 | return ptNoError; | ||
| 121 | } | ||
| 122 | |||
| 123 | |||
| 124 | int Pt_Started() | ||
| 125 | { | ||
| 126 | return time_started_flag; | ||
| 127 | } | ||
| 128 | |||
| 129 | |||
| 130 | PtTimestamp Pt_Time() | ||
| 131 | { | ||
| 132 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); | ||
| 133 | return (PtTimestamp) ((now - startTime) * 1000.0); | ||
| 134 | } | ||
| 135 | |||
| 136 | |||
| 137 | void Pt_Sleep(int32_t duration) | ||
| 138 | { | ||
| 139 | usleep(duration * 1000); | ||
| 140 | } | ||
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 | |||
| 31 | static int time_started_flag = FALSE; | ||
| 32 | static UInt64 start_time; | ||
| 33 | static pthread_t pt_thread_pid; | ||
| 34 | |||
| 35 | /* note that this is static data -- we only need one copy */ | ||
| 36 | typedef struct { | ||
| 37 | int id; | ||
| 38 | int resolution; | ||
| 39 | PtCallback *callback; | ||
| 40 | void *userData; | ||
| 41 | } pt_callback_parameters; | ||
| 42 | |||
| 43 | static int pt_callback_proc_id = 0; | ||
| 44 | |||
| 45 | static void *Pt_CallbackProc(void *p) | ||
| 46 | { | ||
| 47 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; | ||
| 48 | int mytime = 1; | ||
| 49 | |||
| 50 | kern_return_t error; | ||
| 51 | thread_extended_policy_data_t extendedPolicy; | ||
| 52 | thread_precedence_policy_data_t precedencePolicy; | ||
| 53 | |||
| 54 | extendedPolicy.timeshare = 0; | ||
| 55 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, | ||
| 56 | (thread_policy_t)&extendedPolicy, | ||
| 57 | THREAD_EXTENDED_POLICY_COUNT); | ||
| 58 | if (error != KERN_SUCCESS) { | ||
| 59 | mach_error("Couldn't set thread timeshare policy", error); | ||
| 60 | } | ||
| 61 | |||
| 62 | precedencePolicy.importance = THREAD_IMPORTANCE; | ||
| 63 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, | ||
| 64 | (thread_policy_t)&precedencePolicy, | ||
| 65 | THREAD_PRECEDENCE_POLICY_COUNT); | ||
| 66 | if (error != KERN_SUCCESS) { | ||
| 67 | mach_error("Couldn't set thread precedence policy", error); | ||
| 68 | } | ||
| 69 | |||
| 70 | // Most important, set real-time constraints. | ||
| 71 | |||
| 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 | |||
| 133 | PtError 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 | |||
| 176 | PtError 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 | |||
| 186 | int Pt_Started(void) | ||
| 187 | { | ||
| 188 | return time_started_flag; | ||
| 189 | } | ||
| 190 | |||
| 191 | |||
| 192 | PtTimestamp 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 | |||
| 201 | void 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 | |||
| 9 | TIMECAPS caps; | ||
| 10 | |||
| 11 | static long time_offset = 0; | ||
| 12 | static int time_started_flag = FALSE; | ||
| 13 | static long time_resolution; | ||
| 14 | static MMRESULT timer_id; | ||
| 15 | static PtCallback *time_callback; | ||
| 16 | |||
| 17 | void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, | ||
| 18 | DWORD_PTR dw1, DWORD_PTR dw2) | ||
| 19 | { | ||
| 20 | (*time_callback)(Pt_Time(), (void *) dwUser); | ||
| 21 | } | ||
| 22 | |||
| 23 | |||
| 24 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | ||
| 25 | { | ||
| 26 | if (time_started_flag) return ptAlreadyStarted; | ||
| 27 | timeBeginPeriod(resolution); | ||
| 28 | time_resolution = resolution; | ||
| 29 | time_offset = timeGetTime(); | ||
| 30 | time_started_flag = TRUE; | ||
| 31 | time_callback = callback; | ||
| 32 | if (callback) { | ||
| 33 | timer_id = timeSetEvent(resolution, 1, winmm_time_callback, | ||
| 34 | (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); | ||
| 35 | if (!timer_id) return ptHostError; | ||
| 36 | } | ||
| 37 | return ptNoError; | ||
| 38 | } | ||
| 39 | |||
| 40 | |||
| 41 | PMEXPORT PtError Pt_Stop() | ||
| 42 | { | ||
| 43 | if (!time_started_flag) return ptAlreadyStopped; | ||
| 44 | if (time_callback && timer_id) { | ||
| 45 | timeKillEvent(timer_id); | ||
| 46 | time_callback = NULL; | ||
| 47 | timer_id = 0; | ||
| 48 | } | ||
| 49 | time_started_flag = FALSE; | ||
| 50 | timeEndPeriod(time_resolution); | ||
| 51 | return ptNoError; | ||
| 52 | } | ||
| 53 | |||
| 54 | |||
| 55 | PMEXPORT int Pt_Started() | ||
| 56 | { | ||
| 57 | return time_started_flag; | ||
| 58 | } | ||
| 59 | |||
| 60 | |||
| 61 | PMEXPORT PtTimestamp Pt_Time() | ||
| 62 | { | ||
| 63 | return timeGetTime() - time_offset; | ||
| 64 | } | ||
| 65 | |||
| 66 | |||
| 67 | PMEXPORT void Pt_Sleep(int32_t duration) | ||
| 68 | { | ||
| 69 | Sleep(duration); | ||
| 70 | } | ||
| @@ -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 | ||
| 42 | extern "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 | ||
| 53 | enum 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 | ||
| 59 | enum 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 | ||
| 74 | typedef 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 | ||
| 121 | TMLDEF tml_message* tml_load_filename(const char* filename); | ||
| 122 | #endif | ||
| 123 | |||
| 124 | // Load a MIDI file from a block of memory | ||
| 125 | TMLDEF 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 | ||
| 135 | TMLDEF 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 | ||
| 138 | TMLDEF 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) | ||
| 141 | TMLDEF void tml_free(tml_message* f); | ||
| 142 | |||
| 143 | // Stream structure for the generic loading | ||
| 144 | struct 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 | ||
| 154 | TMLDEF 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 | ||
| 157 | struct tsf_stream; | ||
| 158 | TMLDEF 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 | ||
| 205 | extern "C" { | ||
| 206 | #endif | ||
| 207 | |||
| 208 | #ifndef TML_NO_STDIO | ||
| 209 | static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } | ||
| 210 | TMLDEF 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 | |||
| 227 | struct tml_stream_memory { const char* buffer; unsigned int total, pos; }; | ||
| 228 | static 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; } | ||
| 229 | TMLDEF 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 | |||
| 239 | struct tml_track | ||
| 240 | { | ||
| 241 | unsigned int Idx, End, Ticks; | ||
| 242 | }; | ||
| 243 | |||
| 244 | struct tml_tempomsg | ||
| 245 | { | ||
| 246 | unsigned int time; | ||
| 247 | unsigned char type, Tempo[3]; | ||
| 248 | tml_message* next; | ||
| 249 | }; | ||
| 250 | |||
| 251 | struct tml_parser | ||
| 252 | { | ||
| 253 | unsigned char *buf, *buf_end; | ||
| 254 | int last_status, message_array_size, message_count; | ||
| 255 | }; | ||
| 256 | |||
| 257 | enum 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 | |||
| 265 | static int tml_readbyte(struct tml_parser* p) | ||
| 266 | { | ||
| 267 | return (p->buf == p->buf_end ? -1 : *(p->buf++)); | ||
| 268 | } | ||
| 269 | |||
| 270 | static 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 | |||
| 284 | static 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 | |||
| 384 | TMLDEF 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 | |||
| 486 | TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream) | ||
| 487 | { | ||
| 488 | return tml_load((struct tml_stream*)stream); | ||
| 489 | } | ||
| 490 | |||
| 491 | TMLDEF 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 | |||
| 514 | TMLDEF 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 | |||
| 522 | TMLDEF void tml_free(tml_message* f) | ||
| 523 | { | ||
| 524 | TML_FREE(f); | ||
| 525 | } | ||
| 526 | |||
| 527 | #ifdef __cplusplus | ||
| 528 | } | ||
| 529 | #endif | ||
| 530 | |||
| 531 | #endif //TML_IMPLEMENTATION | ||
