1 /*
2     Drumstick RT Backend using the ALSA Sequencer
3     Copyright (C) 2009-2021 Pedro Lopez-Cabanillas <plcl@users.sf.net>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "alsamidiinput.h"
20 #include <QMap>
21 #include <QStringList>
22 #include <drumstick/alsaclient.h>
23 #include <drumstick/alsaevent.h>
24 #include <drumstick/alsaport.h>
25 #include <drumstick/rtmidioutput.h>
26 
27 namespace drumstick { namespace rt {
28 
29     using namespace ALSA;
30 
31     const QString ALSAMIDIInput::DEFAULT_PUBLIC_NAME = QStringLiteral("MIDI In");
32 
33     class ALSAMIDIInput::ALSAMIDIInputPrivate : public SequencerEventHandler
34     {
35     public:
36 
37         ALSAMIDIInput *m_inp;
38         MIDIOutput *m_out;
39         MidiClient *m_client;
40         MidiPort *m_port;
41         int m_portId;
42         int m_clientId;
43         bool m_thruEnabled;
44         bool m_clientFilter;
45         int m_runtimeAlsaNum;
46         QString m_publicName;
47         MIDIConnection m_currentInput;
48         QList<MIDIConnection> m_inputDevices;
49         QStringList m_excludedNames;
50         bool m_initialized;
51         bool m_status;
52         QStringList m_diagnostics;
53 
ALSAMIDIInputPrivate(ALSAMIDIInput * inp)54         explicit ALSAMIDIInputPrivate(ALSAMIDIInput *inp) :
55             m_inp(inp),
56             m_out(nullptr),
57             m_client(nullptr),
58             m_port(nullptr),
59             m_portId(-1),
60             m_clientId(-1),
61             m_thruEnabled(false),
62             m_clientFilter(false),
63             m_publicName(ALSAMIDIInput::DEFAULT_PUBLIC_NAME),
64             m_initialized(false),
65             m_status(false)
66         {
67             m_runtimeAlsaNum = getRuntimeALSALibraryNumber();
68         }
69 
~ALSAMIDIInputPrivate()70         virtual ~ALSAMIDIInputPrivate()
71         {
72             if (m_initialized) {
73                 clearSubscription();
74                 uninitialize();
75             }
76         }
77 
initialize()78         void initialize() {
79             //qDebug() << Q_FUNC_INFO << m_initialized;
80             if (!m_initialized) {
81                 m_client = new MidiClient(m_inp);
82                 m_client->open();
83                 m_client->setClientName(m_publicName);
84                 m_port = m_client->createPort();
85                 m_port->setPortName("in");
86                 m_port->setCapability( SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE );
87                 m_port->setPortType( SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC );
88                 m_clientId = m_client->getClientId();
89                 m_portId = m_port->getPortId();
90                 m_port->setTimestamping(false);
91                 m_port->setTimestampReal(false);
92                 m_client->setHandler(this);
93                 m_initialized = true;
94                 m_status = true;
95                 m_diagnostics.clear();
96                 m_client->startSequencerInput();
97             }
98         }
99 
uninitialize()100         void uninitialize() {
101             //qDebug() << Q_FUNC_INFO << m_initialized;
102             if (m_initialized) {
103                 if (m_port != nullptr) {
104                     m_port->detach();
105                     delete m_port;
106                     m_port = nullptr;
107                 }
108                 if (m_client != nullptr) {
109                     m_client->close();
110                     delete m_client;
111                     m_client = nullptr;
112                 }
113                 m_initialized = false;
114                 m_status = false;
115                 m_diagnostics.clear();
116             }
117         }
118 
clientIsAdvanced(int clientId)119         bool clientIsAdvanced(int clientId)
120         {
121             // asking for runtime version instead of SND_LIB_VERSION
122             if (m_runtimeAlsaNum < 0x01000B)
123                 // ALSA <= 1.0.10
124                 return (clientId < 64);
125             else
126                 // ALSA >= 1.0.11
127                 return (clientId < 16);
128         }
129 
reloadDeviceList(bool advanced)130         void reloadDeviceList(bool advanced)
131         {
132             QStringList clientNames;
133             QMap<QString,int> namesMap;
134             if (!m_initialized) {
135                 initialize();
136             }
137             auto inputs = m_client->getAvailableInputs();
138             m_clientFilter = !advanced;
139             m_inputDevices.clear();
140             m_inputDevices << MIDIConnection();
141             for (const PortInfo& p : qAsConst(inputs)) {
142                 QString name = p.getClientName();
143                 clientNames << name;
144                 if (namesMap.contains(name)) {
145                     namesMap[name]++;
146                 } else {
147                     namesMap.insert(name, 1);
148                 }
149             }
150             for (PortInfo& p : inputs) {
151                 bool excluded = false;
152                 QString name = p.getClientName();
153                 if (m_clientFilter && clientIsAdvanced(p.getClient()))
154                     continue;
155                 if ( m_clientFilter && name.startsWith(QStringLiteral("Virtual Raw MIDI")) )
156                     continue;
157                 if ( name.startsWith(m_publicName) )
158                     continue;
159                 for (const QString& n : qAsConst(m_excludedNames)) {
160                     if (name.startsWith(n)) {
161                         excluded = true;
162                         break;
163                     }
164                 }
165                 if (!excluded) {
166                     int k = clientNames.count(name) + 1;
167                     QString addr = QString("%1:%2").arg(p.getClient()).arg(p.getPort());
168                     if (k > 2) {
169                         m_inputDevices << MIDIConnection(QString("%1 (%2)").arg(name).arg(k - namesMap[name]--), addr);
170                     } else {
171                         m_inputDevices << MIDIConnection(name, addr);
172                     }
173                 }
174             }
175             if (!m_currentInput.first.isEmpty() &&
176                 !m_inputDevices.contains(m_currentInput)) {
177                 m_currentInput = MIDIConnection();
178             }
179         }
180 
setSubscription(const MIDIConnection & newDevice)181         bool setSubscription(const MIDIConnection &newDevice)
182         {
183             if (!m_initialized) {
184                 initialize();
185             }
186             if (m_inputDevices.contains(newDevice)) {
187                 m_currentInput = newDevice;
188                 m_port->unsubscribeAll();
189                 m_port->subscribeFrom(newDevice.second.toString());
190                 return true;
191             }
192             return false;
193         }
194 
clearSubscription()195         void clearSubscription()
196         {
197             if (!m_currentInput.first.isEmpty() && m_initialized) {
198                 m_client->stopSequencerInput();
199                 m_port->unsubscribeAll();
200                 m_currentInput = MIDIConnection();
201             }
202         }
203 
setPublicName(QString newName)204         void setPublicName(QString newName)
205         {
206             if (newName != m_publicName) {
207                 m_publicName = newName;
208                 if(m_initialized) {
209                     m_client->setClientName(newName);
210                 }
211             }
212         }
213 
handleSequencerEvent(SequencerEvent * ev)214         void handleSequencerEvent(SequencerEvent* ev) override
215         {
216             if ( !SequencerEvent::isConnectionChange(ev) && m_initialized)
217                 switch(ev->getSequencerType()) {
218                 case SND_SEQ_EVENT_NOTEOFF: {
219                         const NoteOffEvent* n = static_cast<const NoteOffEvent*>(ev);
220                         if(m_out != nullptr && m_thruEnabled) {
221                             m_out->sendNoteOff(n->getChannel(), n->getKey(), n->getVelocity());
222                         }
223                         emit m_inp->midiNoteOff(n->getChannel(), n->getKey(), n->getVelocity());
224                     }
225                     break;
226                 case SND_SEQ_EVENT_NOTEON: {
227                         const NoteOnEvent* n = static_cast<const NoteOnEvent*>(ev);
228                         if(m_out != nullptr && m_thruEnabled) {
229                             m_out->sendNoteOn(n->getChannel(), n->getKey(), n->getVelocity());
230                         }
231                         emit m_inp->midiNoteOn(n->getChannel(), n->getKey(), n->getVelocity());
232                     }
233                     break;
234                 case SND_SEQ_EVENT_KEYPRESS: {
235                         const KeyPressEvent* n = static_cast<const KeyPressEvent*>(ev);
236                         if(m_out != nullptr && m_thruEnabled) {
237                             m_out->sendKeyPressure(n->getChannel(), n->getKey(), n->getVelocity());
238                         }
239                         emit m_inp->midiKeyPressure(n->getChannel(), n->getKey(), n->getVelocity());
240                     }
241                     break;
242                 case SND_SEQ_EVENT_CONTROLLER:
243                 case SND_SEQ_EVENT_CONTROL14: {
244                         const ControllerEvent* n = static_cast<const ControllerEvent*>(ev);
245                         if(m_out != nullptr && m_thruEnabled) {
246                             m_out->sendController(n->getChannel(), n->getParam(), n->getValue());
247                         }
248                         emit m_inp->midiController(n->getChannel(), n->getParam(), n->getValue());
249                     }
250                     break;
251                 case SND_SEQ_EVENT_PGMCHANGE: {
252                         const ProgramChangeEvent* p = static_cast<const ProgramChangeEvent*>(ev);
253                         if(m_out != nullptr && m_thruEnabled) {
254                             m_out->sendProgram(p->getChannel(), p->getValue());
255                         }
256                         emit m_inp->midiProgram(p->getChannel(), p->getValue());
257                     }
258                     break;
259                 case SND_SEQ_EVENT_CHANPRESS: {
260                         const ChanPressEvent* n = static_cast<const ChanPressEvent*>(ev);
261                         if(m_out != nullptr && m_thruEnabled) {
262                             m_out->sendChannelPressure(n->getChannel(), n->getValue());
263                         }
264                         emit m_inp->midiChannelPressure(n->getChannel(), n->getValue());
265                     }
266                     break;
267                 case SND_SEQ_EVENT_PITCHBEND: {
268                         const PitchBendEvent* n = static_cast<const PitchBendEvent*>(ev);
269                         if(m_out != nullptr && m_thruEnabled) {
270                             m_out->sendPitchBend(n->getChannel(), n->getValue());
271                         }
272                         emit m_inp->midiPitchBend(n->getChannel(), n->getValue());
273                     }
274                     break;
275                 case SND_SEQ_EVENT_SYSEX: {
276                         const SysExEvent* n = static_cast<const SysExEvent*>(ev);
277                         QByteArray data(n->getData(), n->getLength());
278                         if(m_out != nullptr && m_thruEnabled) {
279                             m_out->sendSysex(data);
280                         }
281                         emit m_inp->midiSysex(data);
282                     }
283                     break;
284                 case SND_SEQ_EVENT_SYSTEM: {
285                         const SystemEvent* n = static_cast<const SystemEvent*>(ev);
286                         int status = (int) n->getRaw8(0);
287                         if(m_out != nullptr && m_thruEnabled) {
288                             m_out->sendSystemMsg(status);
289                         }
290                         if (status < 0xF7)
291                             emit m_inp->midiSystemCommon(status);
292                         else if (status > 0xF7)
293                             emit m_inp->midiSystemRealtime(status);
294                     }
295                     break;
296                 default:
297                     break;
298                 }
299             delete ev;
300         }
301     };
302 
ALSAMIDIInput(QObject * parent)303     ALSAMIDIInput::ALSAMIDIInput(QObject *parent) : MIDIInput(parent),
304         d(new ALSAMIDIInputPrivate(this))
305     { }
306 
~ALSAMIDIInput()307     ALSAMIDIInput::~ALSAMIDIInput()
308     {
309         delete d;
310     }
311 
initialize(QSettings * settings)312     void ALSAMIDIInput::initialize(QSettings* settings)
313     {
314         Q_UNUSED(settings)
315         d->initialize();
316     }
317 
backendName()318     QString ALSAMIDIInput::backendName()
319     {
320         return QStringLiteral("ALSA");
321     }
322 
publicName()323     QString ALSAMIDIInput::publicName()
324     {
325         return d->m_publicName;
326     }
327 
setPublicName(QString name)328     void ALSAMIDIInput::setPublicName(QString name)
329     {
330         d->setPublicName(name);
331     }
332 
connections(bool advanced)333     QList<MIDIConnection> ALSAMIDIInput::connections(bool advanced)
334     {
335         d->reloadDeviceList(advanced);
336         return d->m_inputDevices;
337     }
338 
setExcludedConnections(QStringList conns)339     void ALSAMIDIInput::setExcludedConnections(QStringList conns)
340     {
341         d->m_excludedNames = conns;
342     }
343 
open(const MIDIConnection & name)344     void ALSAMIDIInput::open(const MIDIConnection& name)
345     {
346         auto b = d->setSubscription(name);
347         if (!b) {
348             d->m_diagnostics << "failed subscription to " + name.first;
349         }
350     }
351 
close()352     void ALSAMIDIInput::close()
353     {
354         d->clearSubscription();
355         d->uninitialize();
356     }
357 
currentConnection()358     MIDIConnection ALSAMIDIInput::currentConnection()
359     {
360         return d->m_currentInput;
361     }
362 
setMIDIThruDevice(MIDIOutput * device)363     void ALSAMIDIInput::setMIDIThruDevice(MIDIOutput *device)
364     {
365         d->m_out = device;
366     }
367 
enableMIDIThru(bool enable)368     void ALSAMIDIInput::enableMIDIThru(bool enable)
369     {
370         d->m_thruEnabled = enable;
371     }
372 
isEnabledMIDIThru()373     bool ALSAMIDIInput::isEnabledMIDIThru()
374     {
375         return d->m_thruEnabled && (d->m_out != nullptr);
376     }
377 
getDiagnostics()378     QStringList ALSAMIDIInput::getDiagnostics()
379     {
380         return d->m_diagnostics;
381     }
382 
getStatus()383     bool ALSAMIDIInput::getStatus()
384     {
385         return d->m_status;
386     }
387 
388 } // namespace rt
389 } // namespace drumstick
390