diff options
Diffstat (limited to 'portmidi/pm_linux')
| -rwxr-xr-x | portmidi/pm_linux/README_LINUX.txt | 99 | ||||
| -rwxr-xr-x | portmidi/pm_linux/pmlinux.c | 68 | ||||
| -rwxr-xr-x | portmidi/pm_linux/pmlinuxalsa.c | 893 | ||||
| -rwxr-xr-x | portmidi/pm_linux/pmlinuxalsa.h | 6 | ||||
| -rw-r--r-- | portmidi/pm_linux/pmlinuxnull.c | 31 | ||||
| -rw-r--r-- | portmidi/pm_linux/pmlinuxnull.h | 6 |
6 files changed, 1103 insertions, 0 deletions
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 | |||
