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