1 /*************************************************************************** 2 * * 3 * LinuxSampler - modular, streaming capable sampler * 4 * * 5 * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * 6 * Copyright (C) 2005 - 2020 Christian Schoenebeck * 7 * * 8 * This program is free software; you can redistribute it and/or modify * 9 * it under the terms of the GNU General Public License as published by * 10 * the Free Software Foundation; either version 2 of the License, or * 11 * (at your option) any later version. * 12 * * 13 * This program is distributed in the hope that it will be useful, * 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 16 * GNU General Public License for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License * 19 * along with this program; if not, write to the Free Software * 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * 21 * MA 02111-1307 USA * 22 ***************************************************************************/ 23 24 #include "MidiInputDeviceAlsa.h" 25 #include "MidiInputDeviceFactory.h" 26 27 #define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) 28 29 namespace LinuxSampler { 30 31 /// number of currently existing ALSA midi input devices in LinuxSampler 32 static int existingAlsaDevices = 0; 33 34 // *************** ParameterName *************** 35 // * 36 ParameterName(MidiInputPort * pPort)37 MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterName::ParameterName(MidiInputPort* pPort) throw (Exception) : MidiInputPort::ParameterName(pPort, "Port " + ToString(pPort->GetPortNumber())) { 38 OnSetValue(ValueAsString()); // initialize port name 39 } 40 OnSetValue(String s)41 void MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterName::OnSetValue(String s) throw (Exception) { 42 if (s.size() > 16) throw Exception("Name too long for ALSA MIDI input port (max. 16 characters)"); 43 snd_seq_port_info_t* hInfo; 44 snd_seq_port_info_malloc(&hInfo); 45 snd_seq_get_port_info(((MidiInputDeviceAlsa*)pPort->GetDevice())->hAlsaSeq, pPort->GetPortNumber(), hInfo); 46 snd_seq_port_info_set_name(hInfo, s.c_str()); 47 snd_seq_set_port_info(((MidiInputDeviceAlsa*)pPort->GetDevice())->hAlsaSeq, pPort->GetPortNumber(), hInfo); 48 snd_seq_port_info_free(hInfo); 49 } 50 51 52 53 // *************** ParameterAlsaSeqBindings *************** 54 // * 55 56 ParameterAlsaSeqBindings(MidiInputPortAlsa * pPort)57 MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqBindings::ParameterAlsaSeqBindings(MidiInputPortAlsa* pPort) : DeviceRuntimeParameterStrings( std::vector<String>() ) { 58 this->pPort = pPort; 59 } 60 Description()61 String MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqBindings::Description() { 62 return "Bindings to other Alsa sequencer clients"; 63 } Fix()64 bool MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqBindings::Fix() { 65 return false; 66 } 67 PossibilitiesAsString()68 std::vector<String> MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqBindings::PossibilitiesAsString() { 69 std::vector<String> res; 70 snd_seq_client_info_t* cinfo; 71 snd_seq_port_info_t* pinfo; 72 73 snd_seq_client_info_alloca(&cinfo); 74 snd_seq_port_info_alloca(&pinfo); 75 snd_seq_client_info_set_client(cinfo, -1); 76 while (snd_seq_query_next_client(pPort->pDevice->hAlsaSeq, cinfo) >= 0) { 77 snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); 78 snd_seq_port_info_set_port(pinfo, -1); 79 while (snd_seq_query_next_port(pPort->pDevice->hAlsaSeq, pinfo) >= 0) { 80 if (perm_ok(pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ)) { 81 String seq_id = ToString(snd_seq_client_info_get_client(cinfo)) + ":" + 82 ToString(snd_seq_port_info_get_port(pinfo)); 83 res.push_back(seq_id); 84 } 85 } 86 } 87 88 return res; 89 } 90 OnSetValue(std::vector<String> vS)91 void MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqBindings::OnSetValue(std::vector<String> vS) throw (Exception) { 92 pPort->UnsubscribeAll(); 93 94 std::vector<String>::iterator iter = vS.begin(); 95 for (; iter != vS.end(); iter++) pPort->ConnectToAlsaMidiSource((*iter).c_str()); 96 } 97 98 99 100 // *************** ParameterAlsaSeqId *************** 101 // * 102 ParameterAlsaSeqId(MidiInputPortAlsa * pPort)103 MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqId::ParameterAlsaSeqId(MidiInputPortAlsa* pPort) 104 : DeviceRuntimeParameterString(ToString(pPort->pDevice->hAlsaSeqClient) + ":" + ToString(pPort->portNumber)) { 105 } 106 Description()107 String MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqId::Description() { 108 return "ALSA Sequencer ID"; 109 } 110 Fix()111 bool MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqId::Fix() { 112 return true; 113 } 114 PossibilitiesAsString()115 std::vector<String> MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqId::PossibilitiesAsString() { 116 return std::vector<String>(); // nothing 117 } 118 OnSetValue(String s)119 void MidiInputDeviceAlsa::MidiInputPortAlsa::ParameterAlsaSeqId::OnSetValue(String s) { 120 // not possible as parameter is 'fix' 121 } 122 123 124 125 // *************** MidiInputDeviceAlsa::ParameterName *************** 126 // * 127 ParameterName()128 MidiInputDeviceAlsa::ParameterName::ParameterName() : DeviceCreationParameterString() { 129 InitWithDefault(); // use default name 130 } 131 ParameterName(String s)132 MidiInputDeviceAlsa::ParameterName::ParameterName(String s) : DeviceCreationParameterString(s) { 133 } 134 Description()135 String MidiInputDeviceAlsa::ParameterName::Description() { 136 return "Arbitrary ALSA client name"; 137 } 138 Fix()139 bool MidiInputDeviceAlsa::ParameterName::Fix() { 140 return true; 141 } 142 Mandatory()143 bool MidiInputDeviceAlsa::ParameterName::Mandatory() { 144 return false; 145 } 146 DependsAsParameters()147 std::map<String,DeviceCreationParameter*> MidiInputDeviceAlsa::ParameterName::DependsAsParameters() { 148 return std::map<String,DeviceCreationParameter*>(); // no dependencies 149 } 150 PossibilitiesAsString(std::map<String,String> Parameters)151 std::vector<String> MidiInputDeviceAlsa::ParameterName::PossibilitiesAsString(std::map<String,String> Parameters) { 152 return std::vector<String>(); 153 } 154 DefaultAsString(std::map<String,String> Parameters)155 optional<String> MidiInputDeviceAlsa::ParameterName::DefaultAsString(std::map<String,String> Parameters) { 156 return (existingAlsaDevices) ? "LinuxSampler" + ToString(existingAlsaDevices) : "LinuxSampler"; 157 } 158 OnSetValue(String s)159 void MidiInputDeviceAlsa::ParameterName::OnSetValue(String s) throw (Exception) { 160 // not possible, as parameter is fix 161 } 162 Name()163 String MidiInputDeviceAlsa::ParameterName::Name() { 164 return "NAME"; 165 } 166 167 168 169 // *************** MidiInputPortAlsa *************** 170 // * 171 MidiInputPortAlsa(MidiInputDeviceAlsa * pDevice)172 MidiInputDeviceAlsa::MidiInputPortAlsa::MidiInputPortAlsa(MidiInputDeviceAlsa* pDevice) throw (MidiInputException) : MidiInputPort(pDevice, -1) { 173 this->pDevice = pDevice; 174 175 // create Alsa sequencer port 176 int alsaPort = snd_seq_create_simple_port(pDevice->hAlsaSeq, "unnamed port", 177 SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, 178 SND_SEQ_PORT_TYPE_APPLICATION); 179 if (alsaPort < 0) throw MidiInputException("Error creating sequencer port"); 180 this->portNumber = alsaPort; 181 182 delete Parameters["NAME"]; 183 Parameters["NAME"] = new ParameterName(this); 184 Parameters["ALSA_SEQ_BINDINGS"] = new ParameterAlsaSeqBindings(this); 185 Parameters["ALSA_SEQ_ID"] = new ParameterAlsaSeqId(this); 186 } 187 ~MidiInputPortAlsa()188 MidiInputDeviceAlsa::MidiInputPortAlsa::~MidiInputPortAlsa() { 189 UnsubscribeAll(); 190 snd_seq_delete_simple_port(pDevice->hAlsaSeq, portNumber); 191 } 192 193 /** 194 * Connects this Alsa midi input device with an Alsa MIDI source. 195 * 196 * @param Client - Alsa sequencer client and port ID of a MIDI source 197 * (e.g. "64:0") 198 * @throws MidiInputException if connection cannot be established 199 */ ConnectToAlsaMidiSource(const char * MidiSource)200 void MidiInputDeviceAlsa::MidiInputPortAlsa::ConnectToAlsaMidiSource(const char* MidiSource) { 201 snd_seq_addr_t sender, dest; 202 snd_seq_port_subscribe_t* subs; 203 int hExtClient, hExtPort; 204 205 sscanf(MidiSource, "%d:%d", &hExtClient, &hExtPort); 206 sender.client = (char) hExtClient; 207 sender.port = (char) hExtPort; 208 dest.client = (char) pDevice->hAlsaSeqClient; 209 dest.port = (char) portNumber; 210 snd_seq_port_subscribe_malloc(&subs); 211 snd_seq_port_subscribe_set_sender(subs, &sender); 212 snd_seq_port_subscribe_set_dest(subs, &dest); 213 snd_seq_port_subscribe_set_queue(subs, 1); 214 snd_seq_port_subscribe_set_time_update(subs, 1); 215 snd_seq_port_subscribe_set_time_real(subs, 1); 216 if (snd_seq_subscribe_port(pDevice->hAlsaSeq, subs) < 0) { 217 snd_seq_port_subscribe_free(subs); 218 throw MidiInputException(String("Unable to connect to Alsa seq client \'") + MidiSource + "\' (" + snd_strerror(errno) + ")"); 219 } 220 221 subscriptions.push_back(subs); 222 } 223 UnsubscribeAll()224 void MidiInputDeviceAlsa::MidiInputPortAlsa::UnsubscribeAll() { 225 for (std::vector<snd_seq_port_subscribe_t*>::iterator it = subscriptions.begin(); 226 it != subscriptions.end(); it++) { 227 if (snd_seq_unsubscribe_port(pDevice->hAlsaSeq, *it)) { 228 dmsg(1,("MidiInputPortAlsa::UnsubscribeAll: Can't unsubscribe port connection!.\n")); 229 } 230 snd_seq_port_subscribe_free(*it); 231 } 232 subscriptions.clear(); 233 } 234 235 // *************** MidiInputDeviceAlsa *************** 236 // * 237 MidiInputDeviceAlsa(std::map<String,DeviceCreationParameter * > Parameters,void * pSampler)238 MidiInputDeviceAlsa::MidiInputDeviceAlsa(std::map<String,DeviceCreationParameter*> Parameters, void* pSampler) : MidiInputDevice(Parameters, pSampler), Thread(true, true, 1, -1) { 239 if (snd_seq_open(&hAlsaSeq, "default", SND_SEQ_OPEN_INPUT, 0) < 0) { 240 throw MidiInputException("Error opening ALSA sequencer"); 241 } 242 existingAlsaDevices++; 243 this->hAlsaSeqClient = snd_seq_client_id(hAlsaSeq); 244 snd_seq_set_client_name(hAlsaSeq, ((DeviceCreationParameterString*)Parameters["NAME"])->ValueAsString().c_str()); 245 AcquirePorts(((DeviceCreationParameterInt*)Parameters["PORTS"])->ValueAsInt()); 246 if (((DeviceCreationParameterBool*)Parameters["ACTIVE"])->ValueAsBool()) { 247 Listen(); 248 } 249 } 250 ~MidiInputDeviceAlsa()251 MidiInputDeviceAlsa::~MidiInputDeviceAlsa() { 252 // free the midi ports (we can't let the base class do this, 253 // as the MidiInputPortAlsa destructors need access to 254 // hAlsaSeq) 255 for (std::map<int,MidiInputPort*>::iterator iter = Ports.begin(); iter != Ports.end() ; iter++) { 256 delete static_cast<MidiInputPortAlsa*>(iter->second); 257 } 258 Ports.clear(); 259 260 snd_seq_close(hAlsaSeq); 261 existingAlsaDevices--; //FIXME: this is too simple, can lead to multiple clients with the same name 262 } 263 CreateMidiPort()264 MidiInputDeviceAlsa::MidiInputPortAlsa* MidiInputDeviceAlsa::CreateMidiPort() { 265 return new MidiInputPortAlsa(this); 266 } 267 Name()268 String MidiInputDeviceAlsa::Name() { 269 return "ALSA"; 270 } 271 Driver()272 String MidiInputDeviceAlsa::Driver() { 273 return Name(); 274 } 275 Listen()276 void MidiInputDeviceAlsa::Listen() { 277 StartThread(); 278 } 279 StopListen()280 void MidiInputDeviceAlsa::StopListen() { 281 StopThread(); 282 } 283 Description()284 String MidiInputDeviceAlsa::Description() { 285 return "Advanced Linux Sound Architecture"; 286 } 287 Version()288 String MidiInputDeviceAlsa::Version() { 289 String s = "$Revision: 3766 $"; 290 return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword 291 } 292 Main()293 int MidiInputDeviceAlsa::Main() { 294 295 #if DEBUG 296 Thread::setNameOfCaller("AlsaMidi"); 297 #endif 298 299 int npfd; 300 struct pollfd* pfd; 301 snd_seq_event_t* ev; 302 303 npfd = snd_seq_poll_descriptors_count(hAlsaSeq, POLLIN); 304 pfd = (struct pollfd*) alloca(npfd * sizeof(struct pollfd)); 305 snd_seq_poll_descriptors(hAlsaSeq, pfd, npfd, POLLIN); 306 while (true) { 307 // poll() is defined as thread cancelation point by POSIX 308 if (poll(pfd, npfd, 100000) > 0) { 309 do { 310 TestCancel(); 311 312 // prevent thread from being cancelled 313 // (e.g. to prevent deadlocks while holding mutex lock(s)) 314 pushCancelable(false); 315 316 snd_seq_event_input(hAlsaSeq, &ev); 317 int port = (int) ev->dest.port; 318 MidiInputPort* pMidiInputPort = Ports[port]; 319 320 switch (ev->type) { 321 case SND_SEQ_EVENT_CONTROLLER: 322 if (ev->data.control.param == 0) 323 pMidiInputPort->DispatchBankSelectMsb(ev->data.control.value, ev->data.control.channel); 324 else if (ev->data.control.param == 32) 325 pMidiInputPort->DispatchBankSelectLsb(ev->data.control.value, ev->data.control.channel); 326 pMidiInputPort->DispatchControlChange(ev->data.control.param, ev->data.control.value, ev->data.control.channel); 327 break; 328 329 case SND_SEQ_EVENT_CHANPRESS: 330 pMidiInputPort->DispatchChannelPressure(ev->data.control.value, ev->data.control.channel); 331 break; 332 333 case SND_SEQ_EVENT_KEYPRESS: 334 pMidiInputPort->DispatchPolyphonicKeyPressure(ev->data.note.note, ev->data.note.velocity, ev->data.control.channel); 335 break; 336 337 case SND_SEQ_EVENT_PITCHBEND: 338 pMidiInputPort->DispatchPitchbend(ev->data.control.value, ev->data.control.channel); 339 break; 340 341 case SND_SEQ_EVENT_NOTEON: 342 if (ev->data.note.velocity != 0) { 343 pMidiInputPort->DispatchNoteOn(ev->data.note.note, ev->data.note.velocity, ev->data.control.channel); 344 } 345 else { 346 pMidiInputPort->DispatchNoteOff(ev->data.note.note, 0, ev->data.control.channel); 347 } 348 break; 349 350 case SND_SEQ_EVENT_NOTEOFF: 351 pMidiInputPort->DispatchNoteOff(ev->data.note.note, ev->data.note.velocity, ev->data.control.channel); 352 break; 353 354 case SND_SEQ_EVENT_SYSEX: 355 pMidiInputPort->DispatchSysex(ev->data.ext.ptr, ev->data.ext.len); 356 break; 357 358 case SND_SEQ_EVENT_PGMCHANGE: 359 pMidiInputPort->DispatchProgramChange(ev->data.control.value, ev->data.control.channel); 360 break; 361 } 362 snd_seq_free_event(ev); 363 364 // now allow thread being cancelled again 365 // (since all mutexes are now unlocked) 366 popCancelable(); 367 368 } while (snd_seq_event_input_pending(hAlsaSeq, 0) > 0); 369 } 370 } 371 // just to avoid a compiler warning 372 return EXIT_FAILURE; 373 } 374 375 } // namespace LinuxSampler 376