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