drumstick  1.0.1
macmidiinput.cpp
1 /*
2  Drumstick RT Backend
3  Copyright (C) 2009-2015 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 2 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 along
16  with this program; if not, write to the Free Software Foundation, Inc.,
17  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19 
20 #include "macmidiinput.h"
21 #include "rtmidioutput.h"
22 #include "maccommon.h"
23 
24 #include <QDebug>
25 #include <QStringList>
26 #include <QMutex>
27 #include <QTextCodec>
28 #include <QObject>
29 
30 #include <CoreFoundation/CoreFoundation.h>
31 #include <CoreMIDI/CoreMIDI.h>
32 
33 namespace drumstick {
34 namespace rt {
35 
36  static CFStringRef DEFAULT_PUBLIC_NAME CFSTR("MIDI In");
37 
38  void MacMIDIReadProc( const MIDIPacketList *pktlist,
39  void *refCon, void *connRefCon );
40 
41  class MacMIDIInputPrivate {
42  public:
43  MacMIDIInput *m_inp;
44  MIDIOutput *m_out;
45  MIDIClientRef m_client;
46  MIDIPortRef m_port;
47  MIDIEndpointRef m_endpoint;
48  MIDIEndpointRef m_source;
49  bool m_thruEnabled;
50  bool m_clientFilter;
51  QString m_publicName;
52  QString m_currentInput;
53  QStringList m_excludedNames;
54  QStringList m_inputDevices;
55 
56  MacMIDIInputPrivate(MacMIDIInput *inp) :
57  m_inp(inp),
58  m_out(0),
59  m_client(0),
60  m_port(0),
61  m_endpoint(0),
62  m_source(0),
63  m_thruEnabled(false),
64  m_clientFilter(true),
65  m_publicName(QString::fromCFString(DEFAULT_PUBLIC_NAME))
66  {
67  internalCreate( DEFAULT_PUBLIC_NAME );
68  }
69 
70  void internalCreate(CFStringRef name)
71  {
72  OSStatus result = noErr;
73  result = MIDIClientCreate( name , NULL, NULL, &m_client );
74  if (result != noErr) {
75  qDebug() << "MIDIClientCreate() err:" << result;
76  return;
77  }
78  result = MIDIDestinationCreate ( m_client, name, MacMIDIReadProc, (void*) this, &m_endpoint );
79  if (result != noErr) {
80  qDebug() << "MIDIDestinationCreate() err:" << result;
81  return;
82  }
83  result = MIDIInputPortCreate( m_client, name, MacMIDIReadProc, (void *) this, &m_port );
84  if (result != noErr) {
85  qDebug() << "MIDIInputPortCreate() error:" << result;
86  return;
87  }
88  reloadDeviceList(true);
89  }
90 
91  virtual ~MacMIDIInputPrivate()
92  {
93  internalDispose();
94  }
95 
96  void internalDispose()
97  {
98  OSStatus result = noErr;
99  if (m_port != 0) {
100  result = MIDIPortDispose(m_port);
101  if (result != noErr) {
102  qDebug() << "MIDIPortDispose() error:" << result;
103  m_port = 0;
104  }
105  }
106  if (m_endpoint != 0) {
107  result = MIDIEndpointDispose(m_endpoint);
108  if (result != noErr) {
109  qDebug() << "MIDIEndpointDispose() err:" << result;
110  m_endpoint = 0;
111  }
112  }
113  if (m_client != 0) {
114  result = MIDIClientDispose(m_client);
115  if (result != noErr) {
116  qDebug() << "MIDIClientDispose() err:" << result;
117  m_client = 0;
118  }
119  }
120  }
121 
122  void reloadDeviceList(bool advanced)
123  {
124  int num = MIDIGetNumberOfSources();
125  m_clientFilter = !advanced;
126  m_inputDevices.clear();
127  for (int i = 0; i < num; ++i) {
128  bool excluded = false;
129  MIDIEndpointRef dest = MIDIGetSource( i );
130  if (dest != 0) {
131  QString name = getEndpointName(dest);
132  if ( m_clientFilter &&
133  name.contains(QLatin1String("IAC"), Qt::CaseSensitive) )
134  continue;
135  if ( name.contains(m_publicName))
136  continue;
137  foreach( const QString& n, m_excludedNames) {
138  if (name.contains(n)) {
139  excluded = true;
140  break;
141  }
142  }
143  if (!excluded)
144  m_inputDevices << name;
145  }
146  }
147  if (!m_currentInput.isEmpty() && m_source != 0 &&
148  !m_inputDevices.contains(m_currentInput)) {
149  m_currentInput.clear();
150  m_source = 0;
151  }
152  }
153 
154  void setPublicName(QString name)
155  {
156  if (m_publicName != name) {
157  internalDispose();
158  internalCreate(name.toCFString());
159  m_publicName = name;
160  }
161  }
162 
163  void open(QString name)
164  {
165  int index = -1;
166  OSStatus result = noErr;
167  QStringList allInputDevices;
168  int num = MIDIGetNumberOfSources();
169  for (int i = 0; i < num; ++i) {
170  MIDIEndpointRef dest = MIDIGetDestination( i );
171  if (dest != 0)
172  allInputDevices << getEndpointName( dest );
173  }
174  index = allInputDevices.indexOf(name);
175  if (index < 0)
176  return;
177  m_source = MIDIGetSource( index );
178  result = MIDIPortConnectSource( m_port, m_source, NULL );
179  if (result != noErr) {
180  qDebug() << "MIDIPortConnectSource() error:" << result;
181  return;
182  }
183  m_currentInput = name;
184  return;
185  }
186 
187  void close()
188  {
189  OSStatus result = noErr;
190  if (m_source != 0) {
191  result = MIDIPortDisconnectSource(m_port, m_source);
192  if (result != noErr)
193  qDebug() << "MIDIPortDisconnectSource() error:" << result;
194  m_source = 0;
195  m_currentInput.clear();
196  }
197  }
198 
199  void emitSignals(MIDIPacket* packet)
200  {
201  int value = 0;
202  int status = packet->data[0] & 0xf0;
203  int channel = packet->data[0] & 0x0f;
204  QByteArray data;
205  switch (status) {
206  case MIDI_STATUS_NOTEOFF:
207  if(m_out != 0 && m_thruEnabled)
208  m_out->sendNoteOff(channel, packet->data[1], packet->data[2]);
209  emit m_inp->midiNoteOff(channel, packet->data[1], packet->data[2]);
210  break;
211  case MIDI_STATUS_NOTEON:
212  if(m_out != 0 && m_thruEnabled)
213  m_out->sendNoteOn(channel, packet->data[1], packet->data[2]);
214  emit m_inp->midiNoteOn(channel, packet->data[1], packet->data[2]);
215  break;
216  case MIDI_STATUS_KEYPRESURE:
217  if(m_out != 0 && m_thruEnabled)
218  m_out->sendKeyPressure(channel, packet->data[1], packet->data[2]);
219  emit m_inp->midiKeyPressure(channel, packet->data[1], packet->data[2]);
220  break;
221  case MIDI_STATUS_CONTROLCHANGE:
222  if(m_out != 0 && m_thruEnabled)
223  m_out->sendController(channel, packet->data[1], packet->data[2]);
224  emit m_inp->midiController(channel, packet->data[1], packet->data[2]);
225  break;
226  case MIDI_STATUS_PROGRAMCHANGE:
227  if(m_out != 0 && m_thruEnabled)
228  m_out->sendProgram(channel, packet->data[1]);
229  emit m_inp->midiProgram(channel, packet->data[1]);
230  break;
231  case MIDI_STATUS_CHANNELPRESSURE:
232  if(m_out != 0 && m_thruEnabled)
233  m_out->sendChannelPressure(channel, packet->data[1]);
234  emit m_inp->midiChannelPressure(channel, packet->data[1]);
235  break;
236  case MIDI_STATUS_PITCHBEND:
237  value = (packet->data[1] + packet->data[2] * 0x80) - 8192;
238  if(m_out != 0 && m_thruEnabled)
239  m_out->sendPitchBend(channel, value);
240  emit m_inp->midiPitchBend(channel, value);
241  break;
242  case MIDI_STATUS_SYSEX:
243  data = QByteArray((const char *)packet->data, packet->length);
244  if(m_out != 0 && m_thruEnabled)
245  m_out->sendSysex(data);
246  emit m_inp->midiSysex(data);
247  break;
248  default:
249  qDebug() << "status?" << status;
250  }
251  }
252 
253  };
254 
255  void MacMIDIReadProc( const MIDIPacketList *pktlist,
256  void *refCon, void *connRefCon )
257  {
258  Q_UNUSED(connRefCon)
259  MacMIDIInputPrivate *obj = NULL;
260  if (refCon != NULL)
261  obj = static_cast<MacMIDIInputPrivate*>(refCon);
262 
263  MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
264  for (unsigned int i = 0; i < pktlist->numPackets; ++i) {
265  if (obj != NULL)
266  obj->emitSignals(packet);
267  packet = MIDIPacketNext(packet);
268  }
269  }
270 
271  MacMIDIInput::MacMIDIInput(QObject *parent) :
272  MIDIInput(parent), d(new MacMIDIInputPrivate(this))
273  {
274  }
275 
276  MacMIDIInput::~MacMIDIInput()
277  {
278  delete d;
279  }
280 
281  void MacMIDIInput::initialize(QSettings *settings)
282  {
283  Q_UNUSED(settings)
284  }
285 
286  QString MacMIDIInput::backendName()
287  {
288  return QLatin1Literal("CoreMIDI");
289  }
290 
291  QString MacMIDIInput::publicName()
292  {
293  return d->m_publicName;
294  }
295 
296  void MacMIDIInput::setPublicName(QString name)
297  {
298  d->setPublicName(name);
299  }
300 
301  QStringList MacMIDIInput::connections(bool advanced)
302  {
303  d->reloadDeviceList(advanced);
304  return d->m_inputDevices;
305  }
306 
307  void MacMIDIInput::setExcludedConnections(QStringList conns)
308  {
309  d->m_excludedNames = conns;
310  }
311 
312  void MacMIDIInput::open(QString name)
313  {
314  d->open(name);
315  }
316 
317  void MacMIDIInput::close()
318  {
319  d->close();
320  }
321 
322  QString MacMIDIInput::currentConnection()
323  {
324  return d->m_currentInput;
325  }
326 
327  void MacMIDIInput::setMIDIThruDevice(MIDIOutput *device)
328  {
329  d->m_out = device;
330  }
331 
332  void MacMIDIInput::enableMIDIThru(bool enable)
333  {
334  d->m_thruEnabled = enable;
335  }
336 
337  bool MacMIDIInput::isEnabledMIDIThru()
338  {
339  return d->m_thruEnabled && d->m_out != 0;
340  }
341 
342 }} // namespace drumstick::rt
343 
The QObject class is the base class of all Qt objects.