mirror of
				https://github.com/alsa-project/alsa-lib.git
				synced 2025-10-29 05:40:25 -04:00 
			
		
		
		
	 8c0b17bca3
			
		
	
	
		8c0b17bca3
		
	
	
	
	
		
			
			Events sent by external clients subscribed to the input port are not
timestamped.
This inconsistent behavior may surprise newbies who look at seq-decoder as
a reference example.
See the example below using "vkeybd --addr 128:0" to connect to seq-decoder,
the events sent by vkeybd are on a different queue with no timestamps:
  ...
  EVENT>>> Type = 66, flags = 0x0, time = 0 ticks
           Source = 0.1, dest = 128.0, queue = 253
           Event = Port Subscribed; 129:0 -> 128:0
  EVENT>>> Type = 66, flags = 0x1, time = 4.829712627
           Source = 0.1, dest = 128.0, queue = 0
           Event = Port Subscribed; 129:0 -> 128:0
  EVENT>>> Type = 10, flags = 0x0, time = 0 ticks
           Source = 129.0, dest = 128.0, queue = 253
           Event = Controller; ch=0, param=0, value=0
  EVENT>>> Type = 11, flags = 0x0, time = 0 ticks
           Source = 129.0, dest = 128.0, queue = 253
           Event = Program Change; ch=0, program=0
  ...
After the change events are on the main queue and are timestamped:
  ...
  EVENT>>> Type = 66, flags = 0x1, time = 4.280907223
           Source = 0.1, dest = 128.0, queue = 0
           Event = Port Subscribed; 129:0 -> 128:0
  EVENT>>> Type = 66, flags = 0x1, time = 4.280912063
           Source = 0.1, dest = 128.0, queue = 0
           Event = Port Subscribed; 129:0 -> 128:0
  EVENT>>> Type = 10, flags = 0x1, time = 4.280990702
           Source = 129.0, dest = 128.0, queue = 0
           Event = Controller; ch=0, param=0, value=0
  EVENT>>> Type = 11, flags = 0x1, time = 4.280994862
           Source = 129.0, dest = 128.0, queue = 0
           Event = Program Change; ch=0, program=0
  ...
Signed-off-by: Antonio Ospite <ao2@ao2.it>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
		
	
			
		
			
				
	
	
		
			359 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  Simple event decoder
 | |
|  */
 | |
| 
 | |
| static char *event_names[256] = {
 | |
| 	[SND_SEQ_EVENT_SYSTEM]=	"System",
 | |
| 	[SND_SEQ_EVENT_RESULT]=	"Result",
 | |
| 	[SND_SEQ_EVENT_NOTE]=	"Note",
 | |
| 	[SND_SEQ_EVENT_NOTEON]=	"Note On",
 | |
| 	[SND_SEQ_EVENT_NOTEOFF]=	"Note Off",
 | |
| 	[SND_SEQ_EVENT_KEYPRESS]=	"Key Pressure",
 | |
| 	[SND_SEQ_EVENT_CONTROLLER]=	"Controller",
 | |
| 	[SND_SEQ_EVENT_PGMCHANGE]=	"Program Change",
 | |
| 	[SND_SEQ_EVENT_CHANPRESS]=	"Channel Pressure",
 | |
| 	[SND_SEQ_EVENT_PITCHBEND]=	"Pitchbend",
 | |
| 	[SND_SEQ_EVENT_CONTROL14]=	"Control14",
 | |
| 	[SND_SEQ_EVENT_NONREGPARAM]=	"Nonregparam",
 | |
| 	[SND_SEQ_EVENT_REGPARAM]=		"Regparam",
 | |
| 	[SND_SEQ_EVENT_SONGPOS]=	"Song Position",
 | |
| 	[SND_SEQ_EVENT_SONGSEL]=	"Song Select",
 | |
| 	[SND_SEQ_EVENT_QFRAME]=	"Qframe",
 | |
| 	[SND_SEQ_EVENT_TIMESIGN]=	"SMF Time Signature",
 | |
| 	[SND_SEQ_EVENT_KEYSIGN]=	"SMF Key Signature",
 | |
| 	[SND_SEQ_EVENT_START]=	"Start",
 | |
| 	[SND_SEQ_EVENT_CONTINUE]=	"Continue",
 | |
| 	[SND_SEQ_EVENT_STOP]=	"Stop",
 | |
| 	[SND_SEQ_EVENT_SETPOS_TICK]=	"Set Position Tick",
 | |
| 	[SND_SEQ_EVENT_SETPOS_TIME]=	"Set Position Time",
 | |
| 	[SND_SEQ_EVENT_TEMPO]=	"Tempo",
 | |
| 	[SND_SEQ_EVENT_CLOCK]=	"Clock",
 | |
| 	[SND_SEQ_EVENT_TICK]=	"Tick",
 | |
| 	[SND_SEQ_EVENT_TUNE_REQUEST]=	"Tune Request",
 | |
| 	[SND_SEQ_EVENT_RESET]=	"Reset",
 | |
| 	[SND_SEQ_EVENT_SENSING]=	"Active Sensing",
 | |
| 	[SND_SEQ_EVENT_ECHO]=	"Echo",
 | |
| 	[SND_SEQ_EVENT_OSS]=	"OSS",
 | |
| 	[SND_SEQ_EVENT_CLIENT_START]=	"Client Start",
 | |
| 	[SND_SEQ_EVENT_CLIENT_EXIT]=	"Client Exit",
 | |
| 	[SND_SEQ_EVENT_CLIENT_CHANGE]=	"Client Change",
 | |
| 	[SND_SEQ_EVENT_PORT_START]=	"Port Start",
 | |
| 	[SND_SEQ_EVENT_PORT_EXIT]=	"Port Exit",
 | |
| 	[SND_SEQ_EVENT_PORT_CHANGE]=	"Port Change",
 | |
| 	[SND_SEQ_EVENT_PORT_SUBSCRIBED]=	"Port Subscribed",
 | |
| 	[SND_SEQ_EVENT_PORT_UNSUBSCRIBED]=	"Port Unsubscribed",
 | |
| #if 0
 | |
| 	[SND_SEQ_EVENT_SAMPLE]=	"Sample",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_CLUSTER]=	"Sample Cluster",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_START]=	"Sample Start",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_STOP]=	"Sample Stop",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_FREQ]=	"Sample Freq",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_VOLUME]=	"Sample Volume",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_LOOP]=	"Sample Loop",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_POSITION]=	"Sample Position",
 | |
| 	[SND_SEQ_EVENT_SAMPLE_PRIVATE1]=	"Sample Private1",
 | |
| #endif
 | |
| 	[SND_SEQ_EVENT_USR0]=	"User 0",
 | |
| 	[SND_SEQ_EVENT_USR1]=	"User 1",
 | |
| 	[SND_SEQ_EVENT_USR2]=	"User 2",
 | |
| 	[SND_SEQ_EVENT_USR3]=	"User 3",
 | |
| 	[SND_SEQ_EVENT_USR4]=	"User 4",
 | |
| 	[SND_SEQ_EVENT_USR5]=	"User 5",
 | |
| 	[SND_SEQ_EVENT_USR6]=	"User 6",
 | |
| 	[SND_SEQ_EVENT_USR7]=	"User 7",
 | |
| 	[SND_SEQ_EVENT_USR8]=	"User 8",
 | |
| 	[SND_SEQ_EVENT_USR9]=	"User 9",
 | |
| #if 0
 | |
| 	[SND_SEQ_EVENT_INSTR_BEGIN]=	"Instr Begin",
 | |
| 	[SND_SEQ_EVENT_INSTR_END]=	"Instr End",
 | |
| 	[SND_SEQ_EVENT_INSTR_INFO]=	"Instr Info",
 | |
| 	[SND_SEQ_EVENT_INSTR_INFO_RESULT]=	"Instr Info Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_FINFO]=	"Instr Font Info",
 | |
| 	[SND_SEQ_EVENT_INSTR_FINFO_RESULT]=	"Instr Font Info Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_RESET]=	"Instr Reset",
 | |
| 	[SND_SEQ_EVENT_INSTR_STATUS]=	"Instr Status",
 | |
| 	[SND_SEQ_EVENT_INSTR_STATUS_RESULT]=	"Instr Status Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_PUT]=	"Instr Put",
 | |
| 	[SND_SEQ_EVENT_INSTR_GET]=	"Instr Get",
 | |
| 	[SND_SEQ_EVENT_INSTR_GET_RESULT]=	"Instr Get Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_FREE]=	"Instr Free",
 | |
| 	[SND_SEQ_EVENT_INSTR_LIST]=	"Instr List",
 | |
| 	[SND_SEQ_EVENT_INSTR_LIST_RESULT]=	"Instr List Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_CLUSTER]=	"Instr Cluster",
 | |
| 	[SND_SEQ_EVENT_INSTR_CLUSTER_GET]=	"Instr Cluster Get",
 | |
| 	[SND_SEQ_EVENT_INSTR_CLUSTER_RESULT]=	"Instr Cluster Result",
 | |
| 	[SND_SEQ_EVENT_INSTR_CHANGE]=	"Instr Change",
 | |
| #endif
 | |
| 	[SND_SEQ_EVENT_SYSEX]=	"Sysex",
 | |
| 	[SND_SEQ_EVENT_BOUNCE]=	"Bounce",
 | |
| 	[SND_SEQ_EVENT_USR_VAR0]=	"User Var0",
 | |
| 	[SND_SEQ_EVENT_USR_VAR1]=	"User Var1",
 | |
| 	[SND_SEQ_EVENT_USR_VAR2]=	"User Var2",
 | |
| 	[SND_SEQ_EVENT_USR_VAR3]=	"User Var3",
 | |
| 	[SND_SEQ_EVENT_USR_VAR4]=	"User Var4",
 | |
| #if 0
 | |
| 	[SND_SEQ_EVENT_IPCSHM]=	"IPC Shm",
 | |
| 	[SND_SEQ_EVENT_USR_VARIPC0]=	"User IPC0",
 | |
| 	[SND_SEQ_EVENT_USR_VARIPC1]=	"User IPC1",
 | |
| 	[SND_SEQ_EVENT_USR_VARIPC2]=	"User IPC2",
 | |
| 	[SND_SEQ_EVENT_USR_VARIPC3]=	"User IPC3",
 | |
| 	[SND_SEQ_EVENT_USR_VARIPC4]=	"User IPC4",
 | |
| #endif
 | |
| 	[SND_SEQ_EVENT_NONE]=	"None",
 | |
| };
 | |
| 
 | |
| int decode_event(snd_seq_event_t * ev)
 | |
| {
 | |
| 	char *space = "         ";
 | |
| 
 | |
| 	printf("EVENT>>> Type = %d, flags = 0x%x", ev->type, ev->flags);
 | |
| 	switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) {
 | |
| 	case SND_SEQ_TIME_STAMP_TICK:
 | |
| 		printf(", time = %d ticks",
 | |
| 		       ev->time.tick);
 | |
| 		break;
 | |
| 	case SND_SEQ_TIME_STAMP_REAL:
 | |
| 		printf(", time = %d.%09d",
 | |
| 		       (int)ev->time.time.tv_sec,
 | |
| 		       (int)ev->time.time.tv_nsec);
 | |
| 		break;
 | |
| 	}
 | |
| 	printf("\n%sSource = %d.%d, dest = %d.%d, queue = %d\n",
 | |
| 	       space,
 | |
| 	       ev->source.client,
 | |
| 	       ev->source.port,
 | |
| 	       ev->dest.client,
 | |
| 	       ev->dest.port,
 | |
| 	       ev->queue);
 | |
| 
 | |
| 	if (event_names[ev->type])
 | |
| 		printf("%sEvent = %s", space, event_names[ev->type]);
 | |
| 	else
 | |
| 		printf("%sEvent = Reserved %d\n", space, ev->type);
 | |
| 	/* decode the actual event data... */
 | |
| 	switch (ev->type) {
 | |
| 	case SND_SEQ_EVENT_NOTE:
 | |
| 		printf("; ch=%d, note=%d, velocity=%d, off_velocity=%d, duration=%d\n",
 | |
| 		       ev->data.note.channel,
 | |
| 		       ev->data.note.note,
 | |
| 		       ev->data.note.velocity,
 | |
| 		       ev->data.note.off_velocity,
 | |
| 		       ev->data.note.duration);
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_NOTEON:
 | |
| 	case SND_SEQ_EVENT_NOTEOFF:
 | |
| 	case SND_SEQ_EVENT_KEYPRESS:
 | |
| 		printf("; ch=%d, note=%d, velocity=%d\n",
 | |
| 		       ev->data.note.channel,
 | |
| 		       ev->data.note.note,
 | |
| 		       ev->data.note.velocity);
 | |
| 		break;
 | |
| 		
 | |
| 	case SND_SEQ_EVENT_CONTROLLER:
 | |
| 		printf("; ch=%d, param=%i, value=%i\n",
 | |
| 		       ev->data.control.channel,
 | |
| 		       ev->data.control.param,
 | |
| 		       ev->data.control.value);
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_PGMCHANGE:
 | |
| 		printf("; ch=%d, program=%i\n",
 | |
| 		       ev->data.control.channel,
 | |
| 		       ev->data.control.value);
 | |
| 		break;
 | |
| 			
 | |
| 	case SND_SEQ_EVENT_CHANPRESS:
 | |
| 	case SND_SEQ_EVENT_PITCHBEND:
 | |
| 		printf("; ch=%d, value=%i\n",
 | |
| 		       ev->data.control.channel,
 | |
| 		       ev->data.control.value);
 | |
| 		break;
 | |
| 			
 | |
| 	case SND_SEQ_EVENT_SYSEX:
 | |
| 		{
 | |
| 			unsigned char *sysex = (unsigned char *) ev + sizeof(snd_seq_event_t);
 | |
| 			unsigned int c;
 | |
| 			
 | |
| 			printf("; len=%d [", ev->data.ext.len);
 | |
| 			
 | |
| 			for (c = 0; c < ev->data.ext.len; c++) {
 | |
| 				printf("%02x%s", sysex[c], c < ev->data.ext.len - 1 ? ":" : "");
 | |
| 			}
 | |
| 			printf("]\n");
 | |
| 		}
 | |
| 		break;
 | |
| 			
 | |
| 	case SND_SEQ_EVENT_QFRAME:
 | |
| 		printf("; frame=0x%02x\n", ev->data.control.value);
 | |
| 		break;
 | |
| 		
 | |
| 	case SND_SEQ_EVENT_CLOCK:
 | |
| 	case SND_SEQ_EVENT_START:
 | |
| 	case SND_SEQ_EVENT_CONTINUE:
 | |
| 	case SND_SEQ_EVENT_STOP:
 | |
| 		printf("; queue = %i\n", ev->data.queue.queue);
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_SENSING:
 | |
| 		printf("\n");
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_ECHO:
 | |
| 		{
 | |
| 			int i;
 | |
| 				
 | |
| 			printf("; ");
 | |
| 			for (i = 0; i < 8; i++) {
 | |
| 				printf("%02i%s", ev->data.raw8.d[i], i < 7 ? ":" : "\n");
 | |
| 			}
 | |
| 		}
 | |
| 		break;
 | |
| 			
 | |
| 	case SND_SEQ_EVENT_CLIENT_START:
 | |
| 	case SND_SEQ_EVENT_CLIENT_EXIT:
 | |
| 	case SND_SEQ_EVENT_CLIENT_CHANGE:
 | |
| 		printf("; client=%i\n", ev->data.addr.client);
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_PORT_START:
 | |
| 	case SND_SEQ_EVENT_PORT_EXIT:
 | |
| 	case SND_SEQ_EVENT_PORT_CHANGE:
 | |
| 		printf("; client=%i, port = %i\n", ev->data.addr.client, ev->data.addr.port);
 | |
| 		break;
 | |
| 
 | |
| 	case SND_SEQ_EVENT_PORT_SUBSCRIBED:
 | |
| 	case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
 | |
| 		printf("; %i:%i -> %i:%i\n",
 | |
| 		       ev->data.connect.sender.client, ev->data.connect.sender.port,
 | |
| 		       ev->data.connect.dest.client, ev->data.connect.dest.port);
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		printf("; not implemented\n");
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	switch (ev->flags & SND_SEQ_EVENT_LENGTH_MASK) {
 | |
| 	case SND_SEQ_EVENT_LENGTH_FIXED:
 | |
| 		return sizeof(snd_seq_event_t);
 | |
| 
 | |
| 	case SND_SEQ_EVENT_LENGTH_VARIABLE:
 | |
| 		return sizeof(snd_seq_event_t) + ev->data.ext.len;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void event_decoder_start_timer(snd_seq_t *handle, int queue,
 | |
| 			       int client ATTRIBUTE_UNUSED,
 | |
| 			       int port ATTRIBUTE_UNUSED)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	if ((err = snd_seq_start_queue(handle, queue, NULL))<0)
 | |
| 		fprintf(stderr, "Timer event output error: %s\n", snd_strerror(err));
 | |
| 	while (snd_seq_drain_output(handle)>0)
 | |
| 		sleep(1);
 | |
| }
 | |
| 
 | |
| void event_decoder(snd_seq_t *handle, int argc, char *argv[])
 | |
| {
 | |
| 	snd_seq_event_t *ev;
 | |
| 	snd_seq_port_info_t *pinfo;
 | |
| 	snd_seq_port_subscribe_t *sub;
 | |
| 	snd_seq_addr_t addr;
 | |
| 	int client, port, queue, max, err, v1, v2;
 | |
| 	char *ptr;
 | |
| 	struct pollfd *pfds;
 | |
| 
 | |
| 	if ((client = snd_seq_client_id(handle))<0) {
 | |
| 		fprintf(stderr, "Cannot determine client number: %s\n", snd_strerror(client));
 | |
| 		return;
 | |
| 	}
 | |
| 	printf("Client ID = %i\n", client);
 | |
| 	if ((queue = snd_seq_alloc_queue(handle))<0) {
 | |
| 		fprintf(stderr, "Cannot allocate queue: %s\n", snd_strerror(queue));
 | |
| 		return;
 | |
| 	}
 | |
| 	printf("Queue ID = %i\n", queue);
 | |
| 	if ((err = snd_seq_nonblock(handle, 1))<0)
 | |
| 		fprintf(stderr, "Cannot set nonblock mode: %s\n", snd_strerror(err));
 | |
| 	snd_seq_port_info_alloca(&pinfo);
 | |
| 	snd_seq_port_info_set_name(pinfo, "Input");
 | |
| 	snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC);
 | |
| 	snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);
 | |
| 
 | |
| 	/* Enable timestamping for events sent by external subscribers. */
 | |
| 	snd_seq_port_info_set_timestamping(pinfo, 1);
 | |
| 	snd_seq_port_info_set_timestamp_real(pinfo, 1);
 | |
| 	snd_seq_port_info_set_timestamp_queue(pinfo, queue);
 | |
| 
 | |
| 	if ((err = snd_seq_create_port(handle, pinfo)) < 0) {
 | |
| 		fprintf(stderr, "Cannot create input port: %s\n", snd_strerror(err));
 | |
| 		return;
 | |
| 	}
 | |
| 	port = snd_seq_port_info_get_port(pinfo);
 | |
| 	event_decoder_start_timer(handle, queue, client, port);
 | |
| 
 | |
| 	snd_seq_port_subscribe_alloca(&sub);
 | |
| 	addr.client = SND_SEQ_CLIENT_SYSTEM;
 | |
| 	addr.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
 | |
| 	snd_seq_port_subscribe_set_sender(sub, &addr);
 | |
| 	addr.client = client;
 | |
| 	addr.port = port;
 | |
| 	snd_seq_port_subscribe_set_dest(sub, &addr);
 | |
| 	snd_seq_port_subscribe_set_queue(sub, queue);
 | |
| 	snd_seq_port_subscribe_set_time_update(sub, 1);
 | |
| 	snd_seq_port_subscribe_set_time_real(sub, 1);
 | |
| 	if ((err = snd_seq_subscribe_port(handle, sub))<0) {
 | |
| 		fprintf(stderr, "Cannot subscribe announce port: %s\n", snd_strerror(err));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	addr.client = SND_SEQ_CLIENT_SYSTEM;
 | |
| 	addr.port = SND_SEQ_PORT_SYSTEM_TIMER;
 | |
| 	snd_seq_port_subscribe_set_sender(sub, &addr);
 | |
| 	if ((err = snd_seq_subscribe_port(handle, sub))<0) {
 | |
| 		fprintf(stderr, "Cannot subscribe timer port: %s\n", snd_strerror(err));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (max = 0; max < argc; max++) {
 | |
| 		ptr = argv[max];
 | |
| 		if (!ptr)
 | |
| 			continue;
 | |
| 		snd_seq_port_subscribe_set_time_real(sub, 0);
 | |
| 		if (tolower(*ptr) == 'r') {
 | |
| 			snd_seq_port_subscribe_set_time_real(sub, 1);
 | |
| 			ptr++;
 | |
| 		}
 | |
| 		if (sscanf(ptr, "%i.%i", &v1, &v2) != 2) {
 | |
| 			fprintf(stderr, "Wrong argument '%s'...\n", argv[max]);
 | |
| 			return;
 | |
| 		}
 | |
| 		addr.client = v1;
 | |
| 		addr.port = v2;
 | |
| 		snd_seq_port_subscribe_set_sender(sub, &addr);
 | |
| 		if ((err = snd_seq_subscribe_port(handle, sub))<0) {
 | |
| 			fprintf(stderr, "Cannot subscribe port %i from client %i: %s\n", v2, v1, snd_strerror(err));
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	max = snd_seq_poll_descriptors_count(handle, POLLIN);
 | |
| 	pfds = alloca(sizeof(*pfds) * max);
 | |
| 	while (1) {
 | |
| 		snd_seq_poll_descriptors(handle, pfds, max, POLLIN);
 | |
| 		if (poll(pfds, max, -1) < 0)
 | |
| 			break;
 | |
| 		do {
 | |
| 			if ((err = snd_seq_event_input(handle, &ev))<0)
 | |
| 				break;
 | |
| 			if (!ev)
 | |
| 				continue;
 | |
| 			decode_event(ev);
 | |
| 			snd_seq_free_event(ev);
 | |
| 		} while (err > 0);
 | |
| 	}
 | |
| }
 |