aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_linux
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_linux')
-rwxr-xr-xportmidi/pm_linux/README_LINUX.txt99
-rwxr-xr-xportmidi/pm_linux/pmlinux.c68
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.c893
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.h6
-rw-r--r--portmidi/pm_linux/pmlinuxnull.c31
-rw-r--r--portmidi/pm_linux/pmlinuxnull.h6
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 @@
1README_LINUX.txt for PortMidi
2Roger Dannenberg
36 Dec 2012, revised May 2022
4
5Contents:
6 To make PortMidi
7 The pmdefaults program
8 Setting LD_LIBRARY_PATH
9 A note about amd64
10 Using autoconf
11 Using configure
12 Changelog
13
14
15See ../README.md for general instructions.
16
17THE pmdefaults PROGRAM
18
19(This may be obsolete. It is older than `../README.md` which
20also discusses pmdefaults, and Java support may be removed
21unless someone claims they use it... -RBD)
22
23You should install pmdefaults. It provides a graphical interface
24for selecting default MIDI IN and OUT devices so that you don't
25have to build device selection interfaces into all your programs
26and so users have a single place to set a preference.
27
28Follow the instructions above to run ccmake, making sure that
29CMAKE_BUILD_TYPE is Release. Run make as described above. Then:
30
31sudo make install
32
33This will install PortMidi libraries and the pmdefault program.
34You must alos have the environment variable LD_LIBRARY_PATH set
35to include /usr/local/lib (where libpmjni.so is installed).
36
37Now, you can run pmdefault.
38
39
40SETTING LD_LIBRARY_PATH
41
42pmdefaults will not work unless LD_LIBRARY_PATH includes a
43directory (normally /usr/local/lib) containing libpmjni.so,
44installed as described above.
45
46To set LD_LIBRARY_PATH, you might want to add this to your
47~/.profile (if you use the bash shell):
48
49LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
50export LD_LIBRARY_PATH
51
52
53A NOTE ABOUT AMD64:
54
55When compiling portmidi under linux on an AMD64, I had to add the -fPIC
56flag to the gcc flags.
57
58Reason: when trying to build John Harrison's pyPortMidi gcc bailed out
59with this error:
60
61./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
62./linux/libportmidi.a: could not read symbols: Bad value
63collect2: ld returned 1 exit status
64error: command 'gcc' failed with exit status 1
65
66What they said:
67http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3
68On certain architectures (AMD64 amongst them), shared libraries *must*
69be "PIC-enabled".
70
71CHANGELOG
72
7327-may-2022 Roger B. Dannenberg
74 Some updates to this file.
75
766-dec-2012 Roger B. Dannenberg
77 Copied notes on Autoconf from Audacity sources
78
7922-jan-2010 Roger B. Dannenberg
80 Updated instructions about Java paths
81
8214-oct-2009 Roger B. Dannenberg
83 Using CMake now for building and configuration
84
8529-aug-2006 Roger B. Dannenberg
86 Fixed PortTime to join with time thread for clean exit.
87
8828-aug-2006 Roger B. Dannenberg
89 Updated this documentation.
90
9108-Jun-2004 Roger B. Dannenberg
92 Updated code to use new system abstraction.
93
9412-Apr-2003 Roger B. Dannenberg
95 Fixed pm_test/test.c to filter clocks and active messages.
96 Integrated changes from Clemens Ladisch:
97 cleaned up pmlinuxalsa.c
98 record timestamp on sysex input
99 deallocate some resources previously left open
diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c
new file mode 100755
index 0000000..3766427
--- /dev/null
+++ b/portmidi/pm_linux/pmlinux.c
@@ -0,0 +1,68 @@
1/* pmlinux.c -- PortMidi os-dependent code */
2
3/* This file only needs to implement pm_init(), which calls various
4 routines to register the available midi devices. This file must
5 be separate from the main portmidi.c file because it is system
6 dependent, and it is separate from, pmlinuxalsa.c, because it
7 might need to register non-alsa devices as well.
8
9 NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
10 in pmlinuxalsa.c, which assumes all input devices are ALSA.
11 */
12
13#include "stdlib.h"
14#include "portmidi.h"
15#include "pmutil.h"
16#include "pminternal.h"
17
18#ifdef PMALSA
19 #include "pmlinuxalsa.h"
20#endif
21
22#ifdef PMNULL
23 #include "pmlinuxnull.h"
24#endif
25
26#if !(defined(PMALSA) || defined(PMNULL))
27#error One of PMALSA or PMNULL must be defined
28#endif
29
30void pm_init()
31{
32 /* Note: it is not an error for PMALSA to fail to initialize.
33 * It may be a design error that the client cannot query what subsystems
34 * are working properly other than by looking at the list of available
35 * devices.
36 */
37#ifdef PMALSA
38 pm_linuxalsa_init();
39#endif
40#ifdef PMNULL
41 pm_linuxnull_init();
42#endif
43}
44
45void pm_term(void)
46{
47 #ifdef PMALSA
48 pm_linuxalsa_term();
49 #endif
50 #ifdef PMNULL
51 pm_linuxnull_term();
52 #endif
53}
54
55PmDeviceID Pm_GetDefaultInputDeviceID() {
56 Pm_Initialize();
57 return pm_default_input_device_id;
58}
59
60PmDeviceID Pm_GetDefaultOutputDeviceID() {
61 Pm_Initialize();
62 return pm_default_output_device_id;
63}
64
65void *pm_alloc(size_t s) { return malloc(s); }
66
67void pm_free(void *ptr) { free(ptr); }
68
diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c
new file mode 100755
index 0000000..b2e43e1
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.c
@@ -0,0 +1,893 @@
1/*
2 * pmlinuxalsa.c -- system specific definitions
3 *
4 * written by:
5 * Roger Dannenberg (port to Alsa 0.9.x)
6 * Clemens Ladisch (provided code examples and invaluable consulting)
7 * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
8 */
9
10/* omit this code if PMALSA is not defined -- this mechanism allows
11 * selection of different MIDI interfaces (at compile time).
12 */
13#ifdef PMALSA
14
15#include "stdlib.h"
16#include "portmidi.h"
17#include "pmutil.h"
18#include "pminternal.h"
19#include "pmlinuxalsa.h"
20#include "string.h"
21#include "porttime.h"
22
23#include <alsa/asoundlib.h>
24
25/* I used many print statements to debug this code. I left them in the
26 * source, and you can turn them on by changing false to true below:
27 */
28#define VERBOSE_ON 0
29#define VERBOSE if (VERBOSE_ON)
30
31#define MIDI_SYSEX 0xf0
32#define MIDI_EOX 0xf7
33
34#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
35#error needs ALSA 0.9.0 or later
36#endif
37
38/* to store client/port in the device descriptor */
39#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port)))
40#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)
41#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)
42
43#define BYTE unsigned char
44
45extern pm_fns_node pm_linuxalsa_in_dictionary;
46extern pm_fns_node pm_linuxalsa_out_dictionary;
47
48static snd_seq_t *seq = NULL; // all input comes here,
49 // output queue allocated on seq
50static int queue, queue_used; /* one for all ports, reference counted */
51
52#define PORT_IS_CLOSED -999999
53
54typedef struct alsa_info_struct {
55 int is_virtual;
56 int client;
57 int port;
58 int this_port;
59 int in_sysex;
60 snd_midi_event_t *parser;
61} alsa_info_node, *alsa_info_type;
62
63
64/* get_alsa_error_text -- copy error text to potentially short string */
65/**/
66static void get_alsa_error_text(char *msg, int len, int err)
67{
68 int errlen = strlen(snd_strerror(err));
69 if (errlen > 0 && errlen < len) {
70 strcpy(msg, snd_strerror(err));
71 } else if (len > 20) {
72 sprintf(msg, "Alsa error %d", err);
73 } else {
74 msg[0] = 0;
75 }
76}
77
78
79static PmError check_hosterror(int err)
80{
81 if (err < 0) {
82 get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
83 pm_hosterror = TRUE;
84 return pmHostError;
85 }
86 return pmNoError;
87}
88
89
90/* queue is shared by both input and output, reference counted */
91static PmError alsa_use_queue(void)
92{
93 int err = 0;
94 if (queue_used == 0) {
95 snd_seq_queue_tempo_t *tempo;
96
97 queue = snd_seq_alloc_queue(seq);
98 if (queue < 0) {
99 return check_hosterror(queue);
100 }
101 snd_seq_queue_tempo_alloca(&tempo);
102 snd_seq_queue_tempo_set_tempo(tempo, 480000);
103 snd_seq_queue_tempo_set_ppq(tempo, 480);
104 err = snd_seq_set_queue_tempo(seq, queue, tempo);
105 if (err < 0) {
106 return check_hosterror(err);
107 }
108 snd_seq_start_queue(seq, queue, NULL);
109 snd_seq_drain_output(seq);
110 }
111 ++queue_used;
112 return pmNoError;
113}
114
115
116static void alsa_unuse_queue(void)
117{
118 if (--queue_used == 0) {
119 snd_seq_stop_queue(seq, queue, NULL);
120 snd_seq_drain_output(seq);
121 snd_seq_free_queue(seq, queue);
122 VERBOSE printf("queue freed\n");
123 }
124}
125
126
127/* midi_message_length -- how many bytes in a message? */
128static int midi_message_length(PmMessage message)
129{
130 message &= 0xff;
131 if (message < 0x80) {
132 return 0;
133 } else if (message < 0xf0) {
134 static const int length[] = {3, 3, 3, 3, 2, 2, 3};
135 return length[(message - 0x80) >> 4];
136 } else {
137 static const int length[] = {
138 -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
139 return length[message - 0xf0];
140 }
141}
142
143
144static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)
145{
146 alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));
147 info->is_virtual = is_virtual;
148 info->this_port = id;
149 info->client = GET_DESCRIPTOR_CLIENT(client_port);
150 info->port = GET_DESCRIPTOR_PORT(client_port);
151 info->in_sysex = 0;
152 return info;
153}
154
155
156/* search system dependent extra parameters for string */
157static const char *get_sysdep_name(enum PmSysDepPropertyKey key,
158 PmSysDepInfo *info)
159{
160 /* the version where all current properties were introduced is 210 */
161 if (info && info->structVersion >= 210) {
162 int i;
163 for (i = 0; i < info->length; i++) { /* search for key */
164 if (info->properties[i].key == key) {
165 return info->properties[i].value;
166 }
167 }
168 }
169 return NULL;
170}
171
172
173static void maybe_set_client_name(PmSysDepInfo *driverInfo)
174{
175 if (!seq) { // make sure seq is created and we have info
176 return;
177 }
178
179 const char *client_name = get_sysdep_name(pmKeyAlsaClientName,
180 (PmSysDepInfo *) driverInfo);
181 if (client_name) {
182 snd_seq_set_client_name(seq, client_name);
183 printf("maybe_set_client_name set client to %s\n", client_name);
184 }
185}
186
187
188static PmError alsa_out_open(PmInternal *midi, void *driverInfo)
189{
190 int id = midi->device_id;
191 void *client_port = pm_descriptors[id].descriptor;
192 alsa_info_type ainfo = alsa_info_create((long) client_port, id,
193 pm_descriptors[id].pub.is_virtual);
194 snd_seq_port_info_t *pinfo;
195 int err = 0;
196 int using_the_queue = 0;
197
198 if (!ainfo) return pmInsufficientMemory;
199 midi->api_info = ainfo;
200
201 snd_seq_port_info_alloca(&pinfo);
202 if (!ainfo->is_virtual) {
203 snd_seq_port_info_set_port(pinfo, id);
204 snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
205 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
206 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
207 SND_SEQ_PORT_TYPE_APPLICATION);
208 const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
209 (PmSysDepInfo *) driverInfo);
210 if (port_name) {
211 snd_seq_port_info_set_name(pinfo, port_name);
212 }
213 snd_seq_port_info_set_port_specified(pinfo, 1);
214
215 err = snd_seq_create_port(seq, pinfo);
216 if (err < 0) goto free_ainfo;
217
218 }
219
220 err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);
221 if (err < 0) goto free_this_port;
222
223 if (midi->latency > 0) { /* must delay output using a queue */
224 err = alsa_use_queue();
225 if (err < 0) goto free_parser;
226 using_the_queue++;
227 }
228
229 if (!ainfo->is_virtual) {
230 err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,
231 ainfo->port);
232 if (err < 0) goto unuse_queue; /* clean up and return on error */
233 }
234
235 maybe_set_client_name(driverInfo);
236
237 return pmNoError;
238
239 unuse_queue:
240 if (using_the_queue > 0) /* only for latency>0 case */
241 alsa_unuse_queue();
242 free_parser:
243 snd_midi_event_free(ainfo->parser);
244 free_this_port:
245 snd_seq_delete_port(seq, ainfo->this_port);
246 free_ainfo:
247 pm_free(ainfo);
248 return check_hosterror(err);
249}
250
251
252static PmError alsa_write_byte(PmInternal *midi, unsigned char byte,
253 PmTimestamp timestamp)
254{
255 alsa_info_type info = (alsa_info_type) midi->api_info;
256 snd_seq_event_t ev;
257 int err = 0;
258
259 snd_seq_ev_clear(&ev);
260 if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {
261 if (info->is_virtual) {
262 snd_seq_ev_set_subs(&ev);
263 } else {
264 snd_seq_ev_set_dest(&ev, info->client, info->port);
265 }
266 snd_seq_ev_set_source(&ev, info->this_port);
267 if (midi->latency > 0) {
268 /* compute relative time of event = timestamp - now + latency */
269 PmTimestamp now = (midi->time_proc ?
270 midi->time_proc(midi->time_info) :
271 Pt_Time());
272 int when = timestamp;
273 /* if timestamp is zero, send immediately */
274 /* otherwise compute time delay and use delay if positive */
275 if (when == 0) when = now;
276 when = (when - now) + midi->latency;
277 if (when < 0) when = 0;
278 VERBOSE printf("timestamp %d now %d latency %d, ",
279 (int) timestamp, (int) now, midi->latency);
280 VERBOSE printf("scheduling event after %d\n", when);
281 /* message is sent in relative ticks, where 1 tick = 1 ms */
282 snd_seq_ev_schedule_tick(&ev, queue, 1, when);
283 /* NOTE: for cases where the user does not supply a time function,
284 we could optimize the code by not starting Pt_Time and using
285 the alsa tick time instead. I didn't do this because it would
286 entail changing the queue management to start the queue tick
287 count when PortMidi is initialized and keep it running until
288 PortMidi is terminated. (This should be simple, but it's not
289 how the code works now.) -RBD */
290 } else { /* send event out without queueing */
291 VERBOSE printf("direct\n");
292 /* ev.queue = SND_SEQ_QUEUE_DIRECT;
293 ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
294 snd_seq_ev_set_direct(&ev);
295 }
296 VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n",
297 ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,
298 (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
299 (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
300 err = snd_seq_event_output(seq, &ev);
301 }
302 return check_hosterror(err);
303}
304
305
306static PmError alsa_out_close(PmInternal *midi)
307{
308 alsa_info_type info = (alsa_info_type) midi->api_info;
309 int err = 0;
310 int error2 = 0;
311 if (!info) return pmBadPtr;
312
313 if (info->this_port != PORT_IS_CLOSED) {
314 if (!info->is_virtual) {
315 err = snd_seq_disconnect_to(seq, info->this_port,
316 info->client, info->port);
317 /* even if there was an error, we still try to delete the port */
318 error2 = snd_seq_delete_port(seq, info->this_port);
319
320 if (!err) { /* retain original error if there was one */
321 err = error2; /* otherwise, use port delete status */
322 }
323 }
324 }
325 if (midi->latency > 0) alsa_unuse_queue();
326 snd_midi_event_free(info->parser);
327 midi->api_info = NULL; /* destroy the pointer to signify "closed" */
328 pm_free(info);
329 return check_hosterror(err);
330}
331
332
333static PmError alsa_create_virtual(int is_input, const char *name,
334 void *device_info)
335{
336 snd_seq_port_info_t *pinfo;
337 int err;
338 int client, port;
339
340 /* we need the id to set the port. */
341 PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,
342 (is_input ? &pm_linuxalsa_in_dictionary :
343 &pm_linuxalsa_out_dictionary));
344 if (id < 0) { /* error -- out of memory? */
345 return id;
346 }
347 snd_seq_port_info_alloca(&pinfo);
348 snd_seq_port_info_set_capability(pinfo,
349 (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :
350 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));
351 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
352 SND_SEQ_PORT_TYPE_APPLICATION);
353 snd_seq_port_info_set_name(pinfo, name);
354 snd_seq_port_info_set_port(pinfo, id);
355 snd_seq_port_info_set_port_specified(pinfo, 1);
356 /* next 3 lines needed to generate timestamp - PaulLiu */
357 snd_seq_port_info_set_timestamping(pinfo, 1);
358 snd_seq_port_info_set_timestamp_real(pinfo, 0);
359 snd_seq_port_info_set_timestamp_queue(pinfo, queue);
360
361 err = snd_seq_create_port(seq, pinfo);
362 if (err < 0) {
363 pm_undo_add_device(id);
364 return check_hosterror(err);
365 }
366
367 client = snd_seq_port_info_get_client(pinfo);
368 port = snd_seq_port_info_get_port(pinfo);
369 pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);
370 return id;
371}
372
373
374 static PmError alsa_delete_virtual(PmDeviceID id)
375 {
376 int err = snd_seq_delete_port(seq, id);
377 return check_hosterror(err);
378 }
379
380
381static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
382{
383 int id = midi->device_id;
384 void *client_port = pm_descriptors[id].descriptor;
385 alsa_info_type ainfo = alsa_info_create((long) client_port, id,
386 pm_descriptors[id].pub.is_virtual);
387 snd_seq_port_info_t *pinfo;
388 snd_seq_port_subscribe_t *sub;
389 snd_seq_addr_t addr;
390 int err = 0;
391 int is_virtual = pm_descriptors[id].pub.is_virtual;
392
393 if (!ainfo) return pmInsufficientMemory;
394 midi->api_info = ainfo;
395
396 err = alsa_use_queue();
397 if (err < 0) goto free_ainfo;
398
399 snd_seq_port_info_alloca(&pinfo);
400 if (is_virtual) {
401 ainfo->is_virtual = TRUE;
402 if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {
403 pinfo = NULL;
404 goto free_ainfo;
405 }
406 } else {
407 /* create a port for this alsa client (seq) where the port
408 number matches the portmidi device ID of the input device */
409 snd_seq_port_info_set_port(pinfo, id);
410 snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
411 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);
412
413 snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
414 SND_SEQ_PORT_TYPE_APPLICATION);
415 snd_seq_port_info_set_port_specified(pinfo, 1);
416
417 const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
418 (PmSysDepInfo *) driverInfo);
419 if (port_name) {
420 snd_seq_port_info_set_name(pinfo, port_name);
421 }
422
423 err = snd_seq_create_port(seq, pinfo);
424 if (err < 0) goto free_queue;
425
426 /* forward messages from input to this alsa client, so this
427 * alsa client is the destination, and the destination port is the
428 * port we just created using the device ID as port number
429 */
430 snd_seq_port_subscribe_alloca(&sub);
431 addr.client = snd_seq_client_id(seq);
432 addr.port = ainfo->this_port;
433 snd_seq_port_subscribe_set_dest(sub, &addr);
434
435 /* forward from the sender which is the device named by
436 client and port */
437 addr.client = ainfo->client;
438 addr.port = ainfo->port;
439 snd_seq_port_subscribe_set_sender(sub, &addr);
440 snd_seq_port_subscribe_set_time_update(sub, 1);
441 /* this doesn't seem to work: messages come in with real timestamps */
442 snd_seq_port_subscribe_set_time_real(sub, 0);
443 err = snd_seq_subscribe_port(seq, sub);
444 if (err < 0) goto free_this_port; /* clean up and return on error */
445 }
446
447 maybe_set_client_name(driverInfo);
448
449 return pmNoError;
450 free_this_port:
451 snd_seq_delete_port(seq, ainfo->this_port);
452 free_queue:
453 alsa_unuse_queue();
454 free_ainfo:
455 pm_free(ainfo);
456 return check_hosterror(err);
457}
458
459static PmError alsa_in_close(PmInternal *midi)
460{
461 int err = 0;
462 alsa_info_type info = (alsa_info_type) midi->api_info;
463 if (!info) return pmBadPtr;
464 /* virtual ports stay open because the represent devices */
465 if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {
466 err = snd_seq_delete_port(seq, info->this_port);
467 }
468 alsa_unuse_queue();
469 midi->api_info = NULL;
470 pm_free(info);
471 return check_hosterror(err);
472}
473
474
475static PmError alsa_abort(PmInternal *midi)
476{
477 /* NOTE: ALSA documentation is vague. This is supposed to
478 * remove any pending output messages. If you can test and
479 * confirm this code is correct, please update this comment. -RBD
480 */
481 /* Unfortunately, I can't even compile it -- my ALSA version
482 * does not implement snd_seq_remove_events_t, so this does
483 * not compile. I'll try again, but it looks like I'll need to
484 * upgrade my entire Linux OS -RBD
485 */
486 /*
487 info_type info = (info_type) midi->api_info;
488 snd_seq_remove_events_t info;
489 snd_seq_addr_t addr;
490 addr.client = info->client;
491 addr.port = info->port;
492 snd_seq_remove_events_set_dest(&info, &addr);
493 snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
494 pm_hosterror = snd_seq_remove_events(seq, &info);
495 if (pm_hosterror) {
496 get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
497 pm_hosterror);
498 return pmHostError;
499 }
500 */
501 printf("WARNING: alsa_abort not implemented\n");
502 return pmNoError;
503}
504
505
506static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
507{
508 int err;
509 alsa_info_type info = (alsa_info_type) midi->api_info;
510 if (!info) return pmBadPtr;
511 VERBOSE printf("snd_seq_drain_output: %p\n", seq);
512 err = snd_seq_drain_output(seq);
513 return check_hosterror(err);
514}
515
516
517static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
518{
519 int bytes = midi_message_length(event->message);
520 PmMessage msg = event->message;
521 int i;
522 alsa_info_type info = (alsa_info_type) midi->api_info;
523 if (!info) return pmBadPtr;
524 for (i = 0; i < bytes; i++) {
525 unsigned char byte = msg;
526 VERBOSE printf("sending 0x%x\n", byte);
527 alsa_write_byte(midi, byte, event->timestamp);
528 if (pm_hosterror) break;
529 msg >>= 8; /* shift next byte into position */
530 }
531 if (pm_hosterror) return pmHostError;
532 return pmNoError;
533}
534
535
536/* alsa_sysex -- implements begin_sysex and end_sysex */
537PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
538 return pmNoError;
539}
540
541
542static PmTimestamp alsa_synchronize(PmInternal *midi)
543{
544 return 0; /* linux implementation does not use this synchronize function */
545 /* Apparently, Alsa data is relative to the time you send it, and there
546 is no reference. If this is true, this is a serious shortcoming of
547 Alsa. If not true, then PortMidi has a serious shortcoming -- it
548 should be scheduling relative to Alsa's time reference. */
549}
550
551
552static void handle_event(snd_seq_event_t *ev)
553{
554 int device_id = ev->dest.port;
555 PmInternal *midi = pm_descriptors[device_id].pm_internal;
556 // There is a race condition when closing a device and
557 // continuing to poll other open devices. The closed device may
558 // have outstanding events from before the close operation.
559 if (!midi) {
560 return;
561 }
562 PmEvent pm_ev;
563 PmTimestamp timestamp = midi->time_proc(midi->time_info);
564
565 /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
566 /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
567 * Currently, event timestamp is ignored. See long note below. */
568
569 VERBOSE {
570 /* translate time to time_proc basis */
571 snd_seq_queue_status_t *queue_status;
572 snd_seq_queue_status_alloca(&queue_status);
573 snd_seq_get_queue_status(seq, queue, queue_status);
574 printf("handle_event: alsa_now %d, "
575 "event timestamp %d (%d+%dns) (%s, %s)\n",
576 snd_seq_queue_status_get_tick_time(queue_status),
577 ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,
578 (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
579 (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
580 /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */
581 /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
582 snd_seq_queue_status_get_tick_time(queue_status); */
583 }
584 /* CURRENT: portmidi timestamp is "now". In a test, timestamps from
585 * hardware (MIDI over USB) were timestamped with the current ALSA
586 * time (snd_seq_queue_status_get_tick_time) and flags indicating
587 * absolute ticks, but timestamps from another application's virtual
588 * port, sent direct with 0 absolute ticks, were received with a
589 * large value that is apparently the time since the start time of
590 * the other application. Without any reference to our local time,
591 * this seems useless. PortMidi is supposed to return the local
592 * PortMidi time of the arrival of the message, so the best we can
593 * do is set the timestamp to our local clock. This seems to be a
594 * design flaw in ALSA -- I pointed this out a decade ago, but if
595 * there is a workaround, I'd still like to know. Maybe there is a
596 * way to use absolute real time and maybe that's sharable across
597 * applications by referencing the system time?
598 */
599 pm_ev.timestamp = timestamp;
600 switch (ev->type) {
601 case SND_SEQ_EVENT_NOTEON:
602 pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
603 ev->data.note.note & 0x7f,
604 ev->data.note.velocity & 0x7f);
605 pm_read_short(midi, &pm_ev);
606 break;
607 case SND_SEQ_EVENT_NOTEOFF:
608 pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
609 ev->data.note.note & 0x7f,
610 ev->data.note.velocity & 0x7f);
611 pm_read_short(midi, &pm_ev);
612 break;
613 case SND_SEQ_EVENT_KEYPRESS:
614 pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
615 ev->data.note.note & 0x7f,
616 ev->data.note.velocity & 0x7f);
617 pm_read_short(midi, &pm_ev);
618 break;
619 case SND_SEQ_EVENT_CONTROLLER:
620 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
621 ev->data.control.param & 0x7f,
622 ev->data.control.value & 0x7f);
623 pm_read_short(midi, &pm_ev);
624 break;
625 case SND_SEQ_EVENT_PGMCHANGE:
626 pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
627 ev->data.control.value & 0x7f, 0);
628 pm_read_short(midi, &pm_ev);
629 break;
630 case SND_SEQ_EVENT_CHANPRESS:
631 pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
632 ev->data.control.value & 0x7f, 0);
633 pm_read_short(midi, &pm_ev);
634 break;
635 case SND_SEQ_EVENT_PITCHBEND:
636 pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
637 (ev->data.control.value + 0x2000) & 0x7f,
638 ((ev->data.control.value + 0x2000) >> 7) & 0x7f);
639 pm_read_short(midi, &pm_ev);
640 break;
641 case SND_SEQ_EVENT_CONTROL14:
642 if (ev->data.control.param < 0x20) {
643 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
644 ev->data.control.param,
645 (ev->data.control.value >> 7) & 0x7f);
646 pm_read_short(midi, &pm_ev);
647 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
648 ev->data.control.param + 0x20,
649 ev->data.control.value & 0x7f);
650 pm_read_short(midi, &pm_ev);
651 } else {
652 pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
653 ev->data.control.param & 0x7f,
654 ev->data.control.value & 0x7f);
655
656 pm_read_short(midi, &pm_ev);
657 }
658 break;
659 case SND_SEQ_EVENT_SONGPOS:
660 pm_ev.message = Pm_Message(0xf2,
661 ev->data.control.value & 0x7f,
662 (ev->data.control.value >> 7) & 0x7f);
663 pm_read_short(midi, &pm_ev);
664 break;
665 case SND_SEQ_EVENT_SONGSEL:
666 pm_ev.message = Pm_Message(0xf3,
667 ev->data.control.value & 0x7f, 0);
668 pm_read_short(midi, &pm_ev);
669 break;
670 case SND_SEQ_EVENT_QFRAME:
671 pm_ev.message = Pm_Message(0xf1,
672 ev->data.control.value & 0x7f, 0);
673 pm_read_short(midi, &pm_ev);
674 break;
675 case SND_SEQ_EVENT_START:
676 pm_ev.message = Pm_Message(0xfa, 0, 0);
677 pm_read_short(midi, &pm_ev);
678 break;
679 case SND_SEQ_EVENT_CONTINUE:
680 pm_ev.message = Pm_Message(0xfb, 0, 0);
681 pm_read_short(midi, &pm_ev);
682 break;
683 case SND_SEQ_EVENT_STOP:
684 pm_ev.message = Pm_Message(0xfc, 0, 0);
685 pm_read_short(midi, &pm_ev);
686 break;
687 case SND_SEQ_EVENT_CLOCK:
688 pm_ev.message = Pm_Message(0xf8, 0, 0);
689 pm_read_short(midi, &pm_ev);
690 break;
691 case SND_SEQ_EVENT_TUNE_REQUEST:
692 pm_ev.message = Pm_Message(0xf6, 0, 0);
693 pm_read_short(midi, &pm_ev);
694 break;
695 case SND_SEQ_EVENT_RESET:
696 pm_ev.message = Pm_Message(0xff, 0, 0);
697 pm_read_short(midi, &pm_ev);
698 break;
699 case SND_SEQ_EVENT_SENSING:
700 pm_ev.message = Pm_Message(0xfe, 0, 0);
701 pm_read_short(midi, &pm_ev);
702 break;
703 case SND_SEQ_EVENT_SYSEX: {
704 const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
705 /* assume there is one sysex byte to process */
706 pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
707 break;
708 }
709 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {
710 /* this happens if you have an input port open and the
711 * device or application with virtual ports closes. We
712 * mark the port as closed to avoid closing a 2nd time
713 * when Pm_Close() is called.
714 */
715 alsa_info_type info = (alsa_info_type) midi->api_info;
716 /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */
717 info->this_port = PORT_IS_CLOSED;
718 break;
719 }
720 case SND_SEQ_EVENT_PORT_SUBSCRIBED:
721 break; /* someone connected to a virtual output port, not reported */
722 default:
723 printf("portmidi handle_event: not handled type %x\n", ev->type);
724 break;
725 }
726}
727
728
729static PmError alsa_poll(PmInternal *midi)
730{
731 if (!midi) {
732 return pmBadPtr;
733 }
734 snd_seq_event_t *ev;
735 /* expensive check for input data, gets data from device: */
736 while (snd_seq_event_input_pending(seq, TRUE) > 0) {
737 /* cheap check on local input buffer */
738 while (snd_seq_event_input_pending(seq, FALSE) > 0) {
739 /* check for and ignore errors, e.g. input overflow */
740 /* note: if there's overflow, this should be reported
741 * all the way through to client. Since input from all
742 * devices is merged, we need to find all input devices
743 * and set all to the overflow state.
744 * NOTE: this assumes every input is ALSA based.
745 */
746 int rslt = snd_seq_event_input(seq, &ev);
747 if (rslt >= 0) {
748 handle_event(ev);
749 } else if (rslt == -ENOSPC) {
750 int i;
751 for (i = 0; i < pm_descriptor_len; i++) {
752 if (pm_descriptors[i].pub.input) {
753 PmInternal *midi_i = pm_descriptors[i].pm_internal;
754 /* careful, device may not be open! */
755 if (midi_i) Pm_SetOverflow(midi_i->queue);
756 }
757 }
758 }
759 }
760 }
761 return pmNoError;
762}
763
764
765static unsigned int alsa_check_host_error(PmInternal *midi)
766{
767 return FALSE;
768}
769
770
771pm_fns_node pm_linuxalsa_in_dictionary = {
772 none_write_short,
773 none_sysex,
774 none_sysex,
775 none_write_byte,
776 none_write_short,
777 none_write_flush,
778 alsa_synchronize,
779 alsa_in_open,
780 alsa_abort,
781 alsa_in_close,
782 alsa_poll,
783 alsa_check_host_error
784};
785
786pm_fns_node pm_linuxalsa_out_dictionary = {
787 alsa_write_short,
788 alsa_sysex,
789 alsa_sysex,
790 alsa_write_byte,
791 alsa_write_short, /* short realtime message */
792 alsa_write_flush,
793 alsa_synchronize,
794 alsa_out_open,
795 alsa_abort,
796 alsa_out_close,
797 none_poll,
798 alsa_check_host_error
799};
800
801
802/* pm_strdup -- copy a string to the heap. Use this rather than strdup so
803 * that we call pm_alloc, not malloc. This allows portmidi to avoid
804 * malloc which might cause priority inversion. Probably ALSA is going
805 * to call malloc anyway, so this extra work here may be pointless.
806 */
807char *pm_strdup(const char *s)
808{
809 int len = strlen(s);
810 char *dup = (char *) pm_alloc(len + 1);
811 strcpy(dup, s);
812 return dup;
813}
814
815
816PmError pm_linuxalsa_init(void)
817{
818 int err;
819 snd_seq_client_info_t *cinfo;
820 snd_seq_port_info_t *pinfo;
821 unsigned int caps;
822
823 /* Register interface ALSA with create_virtual fn */
824 pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);
825
826 /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this
827 * would cause messages to be dropped if the ALSA buffer fills up.
828 * The correct behavior is for writes to block until there is
829 * room to send all the data. The client should normally allocate
830 * a large enough buffer to avoid blocking on output.
831 * Now that blocking is enabled, the seq_event_input() will block
832 * if there is no input data. This is not what we want, so must
833 * call seq_event_input_pending() to avoid blocking.
834 */
835 err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
836 if (err < 0) goto error_return;
837
838 snd_seq_client_info_alloca(&cinfo);
839 snd_seq_port_info_alloca(&pinfo);
840
841 snd_seq_client_info_set_client(cinfo, -1);
842 while (snd_seq_query_next_client(seq, cinfo) == 0) {
843 snd_seq_port_info_set_client(pinfo,
844 snd_seq_client_info_get_client(cinfo));
845 snd_seq_port_info_set_port(pinfo, -1);
846 while (snd_seq_query_next_port(seq, pinfo) == 0) {
847 if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
848 continue; /* ignore Timer and Announce ports on client 0 */
849 caps = snd_seq_port_info_get_capability(pinfo);
850 if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ |
851 SND_SEQ_PORT_CAP_SUBS_WRITE)))
852 continue; /* ignore if you cannot read or write port */
853 if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
854 if (pm_default_output_device_id == -1)
855 pm_default_output_device_id = pm_descriptor_len;
856 pm_add_device("ALSA",
857 pm_strdup(snd_seq_port_info_get_name(pinfo)),
858 FALSE, FALSE,
859 MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
860 snd_seq_port_info_get_port(pinfo)),
861 &pm_linuxalsa_out_dictionary);
862 }
863 if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
864 if (pm_default_input_device_id == -1)
865 pm_default_input_device_id = pm_descriptor_len;
866 pm_add_device("ALSA",
867 pm_strdup(snd_seq_port_info_get_name(pinfo)),
868 TRUE, FALSE,
869 MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
870 snd_seq_port_info_get_port(pinfo)),
871 &pm_linuxalsa_in_dictionary);
872 }
873 }
874 }
875 return pmNoError;
876 error_return:
877 pm_linuxalsa_term(); /* clean up */
878 return check_hosterror(err);
879}
880
881
882void pm_linuxalsa_term(void)
883{
884 if (seq) {
885 snd_seq_close(seq);
886 pm_free(pm_descriptors);
887 pm_descriptors = NULL;
888 pm_descriptor_len = 0;
889 pm_descriptor_max = 0;
890 }
891}
892
893#endif
diff --git a/portmidi/pm_linux/pmlinuxalsa.h b/portmidi/pm_linux/pmlinuxalsa.h
new file mode 100755
index 0000000..d4bff16
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.h
@@ -0,0 +1,6 @@
1/* pmlinuxalsa.h -- system-specific definitions */
2
3PmError pm_linuxalsa_init(void);
4void pm_linuxalsa_term(void);
5
6
diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c
new file mode 100644
index 0000000..257f3d6
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.c
@@ -0,0 +1,31 @@
1/*
2 * pmlinuxnull.c -- system specific definitions
3 *
4 * written by:
5 * Roger Dannenberg
6 *
7 * If there is no ALSA, you can define PMNULL and build PortMidi. It will
8 * not report any devices, so you will not be able to open any, but if
9 * you wanted to disable MIDI from some application, this could be used.
10 * Mainly, this code shows the possibility of supporting multiple
11 * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.
12 * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.
13 */
14
15#ifdef PMNULL
16
17#include "portmidi.h"
18#include "pmlinuxnull.h"
19
20
21PmError pm_linuxnull_init(void)
22{
23 return pmNoError;
24}
25
26
27void pm_linuxnull_term(void)
28{
29}
30
31#endif
diff --git a/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h
new file mode 100644
index 0000000..9835825
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.h
@@ -0,0 +1,6 @@
1/* pmlinuxnull.h -- system-specific definitions */
2
3PmError pm_linuxnull_init(void);
4void pm_linuxnull_term(void);
5
6