1 /*
2 Copyright (C) 2004-2005  The Pentagram Team
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 */
18 
19 /*
20   ALSA MIDI driver
21   Adapted from ScummVM's backends/midi/alsa.cpp
22   (which is in turn
23    "Mostly cut'n'pasted from Virtual Tiny Keyboard (vkeybd) by Takashi Iwai" )
24 */
25 
26 #include "pent_include.h"
27 #include "ALSAMidiDriver.h"
28 
29 #ifdef USE_ALSA_MIDI
30 
31 const MidiDriver::MidiDriverDesc ALSAMidiDriver::desc =
32 		MidiDriver::MidiDriverDesc ("alsa", createInstance);
33 
34 
35 
36 
37 #if SND_LIB_MAJOR >= 1 || SND_LIB_MINOR >= 6
38 #define snd_seq_flush_output(x) snd_seq_drain_output(x)
39 #define snd_seq_set_client_group(x,name)	/*nop */
40 #define my_snd_seq_open(seqp) snd_seq_open(seqp, "hw", SND_SEQ_OPEN_OUTPUT, 0)
41 #else
42 /* SND_SEQ_OPEN_OUT causes oops on early version of ALSA */
43 #define my_snd_seq_open(seqp) snd_seq_open(seqp, SND_SEQ_OPEN)
44 #endif
45 
46 #define ALSA_PORT "65:0"
47 #define ADDR_DELIM ".:"
48 
49 
ALSAMidiDriver()50 ALSAMidiDriver::ALSAMidiDriver()
51  : isOpen(false), seq_handle(nullptr), seq_client(0), seq_port(0),
52    my_client(0), my_port(0)
53 {
54 
55 }
56 
open()57 int ALSAMidiDriver::open() {
58 	std::string arg;
59 	unsigned int caps;
60 
61 	if (isOpen)
62 		return -1;
63 
64 	arg = getConfigSetting("alsa_port", ALSA_PORT);
65 
66 	if (parse_addr(arg, &seq_client, &seq_port) < 0) {
67 		perr << "ALSAMidiDriver: Invalid port: " << arg << std::endl;
68 		return -1;
69 	}
70 
71 	if (my_snd_seq_open(&seq_handle)) {
72 		perr << "ALSAMidiDriver: Can't open sequencer" << std::endl;
73 		return -1;
74 	}
75 
76 	isOpen = true;
77 
78 	my_client = snd_seq_client_id(seq_handle);
79 	snd_seq_set_client_name(seq_handle, "PENTAGRAM");
80 	snd_seq_set_client_group(seq_handle, "input");
81 
82 	caps = SND_SEQ_PORT_CAP_READ;
83 	if (seq_client == SND_SEQ_ADDRESS_SUBSCRIBERS)
84 		caps = ~SND_SEQ_PORT_CAP_SUBS_READ;
85 	my_port =
86 		snd_seq_create_simple_port(seq_handle, "PENTAGRAM", caps,
87 								   SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
88 	if (my_port < 0) {
89 		snd_seq_close(seq_handle);
90 		isOpen = false;
91 		perr << "ALSAMidiDriver: Can't create port" << std::endl;
92 		return -1;
93 	}
94 
95 	if (seq_client != SND_SEQ_ADDRESS_SUBSCRIBERS) {
96 		/* subscribe to MIDI port */
97 		if (snd_seq_connect_to(seq_handle, my_port, seq_client, seq_port) < 0) {
98 			snd_seq_close(seq_handle);
99 			isOpen = false;
100 			perr << "ALSAMidiDriver: "
101 				 << "Can't subscribe to MIDI port (" << seq_client
102 				 << ":" << seq_port << ")" << std::endl;
103 			return -1;
104 		}
105 	}
106 
107 	pout << "ALSA client initialised [" << seq_client << ":"
108 		 << seq_port << "]" << std::endl;
109 
110 	return 0;
111 }
112 
close()113 void ALSAMidiDriver::close() {
114 	isOpen = false;
115 	if (seq_handle)
116 		snd_seq_close(seq_handle);
117 }
118 
send(uint32 b)119 void ALSAMidiDriver::send(uint32 b) {
120 	unsigned int midiCmd[4];
121 	ev.type = SND_SEQ_EVENT_OSS;
122 
123 	midiCmd[3] = (b & 0xFF000000) >> 24;
124 	midiCmd[2] = (b & 0x00FF0000) >> 16;
125 	midiCmd[1] = (b & 0x0000FF00) >> 8;
126 	midiCmd[0] = (b & 0x000000FF);
127 	ev.data.raw32.d[0] = midiCmd[0];
128 	ev.data.raw32.d[1] = midiCmd[1];
129 	ev.data.raw32.d[2] = midiCmd[2];
130 
131 	unsigned char chanID = midiCmd[0] & 0x0F;
132 	switch (midiCmd[0] & 0xF0) {
133 	case 0x80:
134 		snd_seq_ev_set_noteoff(&ev, chanID, midiCmd[1], midiCmd[2]);
135 		send_event(1);
136 		break;
137 	case 0x90:
138 		snd_seq_ev_set_noteon(&ev, chanID, midiCmd[1], midiCmd[2]);
139 		send_event(1);
140 		break;
141 	case 0xB0:
142 		/* is it this simple ? Wow... */
143 		snd_seq_ev_set_controller(&ev, chanID, midiCmd[1], midiCmd[2]);
144 		send_event(1);
145 		break;
146 	case 0xC0:
147 		snd_seq_ev_set_pgmchange(&ev, chanID, midiCmd[1]);
148 		send_event(0);
149 		break;
150 	case 0xD0:
151 		snd_seq_ev_set_chanpress(&ev, chanID, midiCmd[1]);
152 		send_event(0);
153 		break;
154 	case 0xE0:{
155 			// long theBend = ((((long)midiCmd[1] + (long)(midiCmd[2] << 7))) - 0x2000) / 4;
156 			// snd_seq_ev_set_pitchbend(&ev, chanID, theBend);
157 			long theBend = (static_cast<long>(midiCmd[1]) + static_cast<long>(midiCmd[2] << 7)) - 0x2000;
158 			snd_seq_ev_set_pitchbend(&ev, chanID, theBend);
159 			send_event(1);
160 		}
161 		break;
162 
163 	default:
164 		perr << "ALSAMidiDriver: Unknown Command: "
165 			 << std::hex << static_cast<int>(b) << std::dec << std::endl;
166 		/* I don't know if this works but, well... */
167 		send_event(1);
168 		break;
169 	}
170 }
171 
send_sysex(uint8 status,const uint8 * msg,uint16 length)172 void ALSAMidiDriver::send_sysex(uint8 status,const uint8 *msg,uint16 length) {
173 	unsigned char buf[1024];
174 
175 	if (length > 511) {
176 		perr << "ALSAMidiDriver: "
177 			 << "Cannot send SysEx block - data too large" << std::endl;
178 		return;
179 	}
180 	buf[0] = status;
181 	memcpy(buf + 1, msg, length);
182 	snd_seq_ev_set_sysex(&ev, length + 1, &buf);
183 	send_event(1);
184 }
185 
186 // static
parse_addr(const std::string & _arg,int * client,int * port)187 int ALSAMidiDriver::parse_addr(const std::string& _arg, int *client, int *port) {
188 	const char* arg = _arg.c_str();
189 
190 	if (isdigit(static_cast<unsigned char>(*arg))) {
191 		const char *p;
192 		if ((p = strpbrk(arg, ADDR_DELIM)) == nullptr)
193 			return -1;
194 		*client = atoi(arg);
195 		*port = atoi(p + 1);
196 	} else {
197 		if (*arg == 's' || *arg == 'S') {
198 			*client = SND_SEQ_ADDRESS_SUBSCRIBERS;
199 			*port = 0;
200 		} else
201 			return -1;
202 	}
203 	return 0;
204 }
205 
send_event(int do_flush)206 void ALSAMidiDriver::send_event(int do_flush) {
207 	snd_seq_ev_set_direct(&ev);
208 	snd_seq_ev_set_source(&ev, my_port);
209 	snd_seq_ev_set_dest(&ev, seq_client, seq_port);
210 
211 	snd_seq_event_output(seq_handle, &ev);
212 	if (do_flush)
213 		snd_seq_flush_output(seq_handle);
214 }
215 
216 #endif
217