drumstick  1.0.2
winmidioutput.cpp
1 /*
2  Drumstick RT Windows 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 <QDebug>
21 #include <QStringList>
22 #include <QByteArray>
23 #include <QVarLengthArray>
24 #include <qmath.h>
25 #include <windows.h>
26 #include <mmsystem.h>
27 #include "winmidioutput.h"
28 
29 namespace drumstick {
30 namespace rt {
31 
32  union WinMIDIPacket {
33  WinMIDIPacket() : dwPacket(0) {}
34  DWORD dwPacket;
35  quint8 data[sizeof(DWORD)];
36  };
37 
38  static QLatin1Literal DEFAULT_PUBLIC_NAME = QLatin1Literal("MIDI Out");
39 
40  void CALLBACK midiCallback( HMIDIOUT hmo,
41  UINT wMsg,
42  DWORD_PTR dwInstance,
43  DWORD_PTR dwParam1,
44  DWORD_PTR dwParam2);
45 
46  class WinMIDIOutput::WinMIDIOutputPrivate {
47  public:
48  HMIDIOUT m_handle;
49  bool m_clientFilter;
50  QString m_publicName;
51  QString m_currentOutput;
52  QMap<int,QString> m_outputDevices;
53  MIDIHDR m_midiSysexHdr;
54  QByteArray m_sysexBuffer;
55  QStringList m_excludedNames;
56 
57  WinMIDIOutputPrivate():
58  m_handle(0),
59  m_clientFilter(true),
60  m_publicName(DEFAULT_PUBLIC_NAME)
61  {
62  reloadDeviceList(true);
63  }
64 
65  void reloadDeviceList(bool advanced)
66  {
67  MMRESULT res;
68  MIDIOUTCAPS deviceCaps;
69  QString devName;
70  unsigned int dev, max = midiOutGetNumDevs();
71  m_outputDevices.clear();
72  m_clientFilter = !advanced;
73 
74  for ( dev = 0; dev < max; ++dev) {
75  res = midiOutGetDevCaps( dev, &deviceCaps, sizeof(MIDIOUTCAPS));
76  if (res != MMSYSERR_NOERROR)
77  break;
78  if (m_clientFilter && (deviceCaps.wTechnology == MOD_MAPPER))
79  continue;
80  #if defined(UNICODE)
81  devName = QString::fromWCharArray(deviceCaps.szPname);
82  #else
83  devName = QString::fromLocal8Bit(deviceCaps.szPname);
84  #endif
85  m_outputDevices[dev] = devName;
86  }
87  if (!m_clientFilter) {
88  dev = MIDI_MAPPER;
89  res = midiOutGetDevCaps( dev, &deviceCaps, sizeof(MIDIOUTCAPS));
90  if (res == MMSYSERR_NOERROR) {
91  #if defined(UNICODE)
92  devName = QString::fromWCharArray(deviceCaps.szPname);
93  #else
94  devName = QString::fromLocal8Bit(deviceCaps.szPname);
95  #endif
96  m_outputDevices[dev] = devName;
97  }
98  }
99  }
100 
101  void setPublicName(QString name)
102  {
103  if (m_publicName != name) {
104  m_publicName = name;
105  }
106  }
107 
108  int deviceIndex( const QString& newOutputDevice )
109  {
110  int index = -1;
111  QMap<int,QString>::ConstIterator it;
112  for( it = m_outputDevices.constBegin();
113  it != m_outputDevices.constEnd(); ++it ) {
114  if (it.value() == newOutputDevice) {
115  index = it.key();
116  break;
117  }
118  }
119  return index;
120  }
121 
122  void open(QString name)
123  {
124  MMRESULT res;
125  int dev = -1;
126 
127  if (m_handle != 0)
128  close();
129  reloadDeviceList(!m_clientFilter);
130  dev = deviceIndex(name);
131  if (dev > -1) {
132  res = midiOutOpen( &m_handle, dev, (DWORD_PTR) midiCallback, (DWORD_PTR) this, CALLBACK_FUNCTION);
133  if (res == MMSYSERR_NOERROR)
134  m_currentOutput = name;
135  else
136  qDebug() << "midiStreamOpen() err:" << mmErrorString(res);
137  }
138  }
139 
140  void close()
141  {
142  MMRESULT res;
143  if (m_handle != 0) {
144  res = midiOutReset( m_handle );
145  if (res != MMSYSERR_NOERROR)
146  qDebug() << "midiOutReset() err:" << mmErrorString(res);
147  res = midiOutClose( m_handle );
148  if (res == MMSYSERR_NOERROR)
149  m_currentOutput.clear();
150  else
151  qDebug() << "midiStreamClose() err:" << mmErrorString(res);
152  m_handle = 0;
153  }
154  }
155 
156  void doneHeader( LPMIDIHDR lpMidiHdr )
157  {
158  MMRESULT res;
159  res = midiOutUnprepareHeader( m_handle, lpMidiHdr, sizeof(MIDIHDR) );
160  if (res != MMSYSERR_NOERROR)
161  qDebug() << "midiOutUnprepareHeader() err:" << mmErrorString(res);
162  if ((lpMidiHdr->dwFlags & MHDR_ISSTRM) == 0)
163  return; // sysex header?
164  }
165 
166  void sendShortMessage(WinMIDIPacket &packet)
167  {
168  MMRESULT res;
169  res = midiOutShortMsg( m_handle, packet.dwPacket );
170  if ( res != MMSYSERR_NOERROR )
171  qDebug() << "midiOutShortMsg() err:" << mmErrorString(res);
172  }
173 
174  void sendSysexEvent(const QByteArray& data)
175  {
176  MMRESULT res;
177  m_sysexBuffer = data;
178  m_midiSysexHdr.lpData = (LPSTR) m_sysexBuffer.data();
179  m_midiSysexHdr.dwBufferLength = m_sysexBuffer.size();
180  m_midiSysexHdr.dwBytesRecorded = m_sysexBuffer.size();
181  m_midiSysexHdr.dwFlags = 0;
182  m_midiSysexHdr.dwUser = 0;
183  res = midiOutPrepareHeader( m_handle, &m_midiSysexHdr, sizeof(MIDIHDR) );
184  if (res != MMSYSERR_NOERROR)
185  qDebug() << "midiOutPrepareHeader() err:" << mmErrorString(res);
186  else {
187  res = midiOutLongMsg( m_handle, &m_midiSysexHdr, sizeof(MIDIHDR) );
188  if (res != MMSYSERR_NOERROR)
189  qDebug() << "midiOutLongMsg() err:" << mmErrorString(res);
190  }
191  }
192 
193  QString mmErrorString(MMRESULT err)
194  {
195  QString errstr;
196  #ifdef UNICODE
197  WCHAR buffer[1024];
198  midiOutGetErrorText(err, &buffer[0], sizeof(buffer));
199  errstr = QString::fromUtf16((const ushort*)buffer);
200  #else
201  char buffer[1024];
202  midiOutGetErrorText(err, &buffer[0], sizeof(buffer));
203  errstr = QString::fromLocal8Bit(buffer);
204  #endif
205  return errstr;
206  }
207  };
208 
209 
210  void CALLBACK midiCallback( HMIDIOUT hmo,
211  UINT wMsg,
212  DWORD_PTR dwInstance,
213  DWORD_PTR dwParam1,
214  DWORD_PTR dwParam2)
215  {
216  //Q_UNUSED(hmo)
217  Q_UNUSED(dwParam2)
218 
219  WinMIDIOutput::WinMIDIOutputPrivate* obj = (WinMIDIOutput::WinMIDIOutputPrivate*) dwInstance;
220  switch( wMsg ) {
221  case MOM_DONE:
222  obj->doneHeader( (LPMIDIHDR) dwParam1 );
223  break;
224  case MOM_OPEN:
225  qDebug() << "Open output" << hmo;
226  break;
227  case MOM_CLOSE:
228  qDebug() << "Close output" << hmo;
229  break;
230  default:
231  qDebug() << "unknown output message:" << hex << wMsg;
232  break;
233  }
234  }
235 
236  WinMIDIOutput::WinMIDIOutput(QObject *parent) :
237  MIDIOutput(parent), d(new WinMIDIOutputPrivate)
238  { }
239 
240  WinMIDIOutput::~WinMIDIOutput()
241  {
242  delete d;
243  }
244 
245  void WinMIDIOutput::initialize(QSettings *settings)
246  {
247  Q_UNUSED(settings)
248  }
249 
250  QString WinMIDIOutput::backendName()
251  {
252  return "Windows MM";
253  }
254 
255  QString WinMIDIOutput::publicName()
256  {
257  return d->m_publicName;
258  }
259 
260  void WinMIDIOutput::setPublicName(QString name)
261  {
262  d->setPublicName(name);
263  }
264 
265  QStringList WinMIDIOutput::connections(bool advanced)
266  {
267  d->reloadDeviceList(advanced);
268  return d->m_outputDevices.values();
269  }
270 
271  void WinMIDIOutput::setExcludedConnections(QStringList conns)
272  {
273  d->m_excludedNames = conns;
274  }
275 
276  void WinMIDIOutput::open(QString name)
277  {
278  d->open(name);
279  }
280 
281  void WinMIDIOutput::close()
282  {
283  d->close();
284  }
285 
286  QString WinMIDIOutput::currentConnection()
287  {
288  return d->m_currentOutput;
289  }
290 
291  void WinMIDIOutput::sendNoteOn(int chan, int note, int vel)
292  {
293  WinMIDIPacket packet;
294  packet.data[0] = MIDI_STATUS_NOTEON | (chan & MIDI_CHANNEL_MASK);
295  packet.data[1] = note;
296  packet.data[2] = vel;
297  d->sendShortMessage(packet);
298  }
299 
300  void WinMIDIOutput::sendNoteOff(int chan, int note, int vel)
301  {
302  WinMIDIPacket packet;
303  packet.data[0] = MIDI_STATUS_NOTEOFF | (chan & MIDI_CHANNEL_MASK);
304  packet.data[1] = note;
305  packet.data[2] = vel;
306  d->sendShortMessage(packet);
307  }
308 
309  void WinMIDIOutput::sendController(int chan, int control, int value)
310  {
311  WinMIDIPacket packet;
312  packet.data[0] = MIDI_STATUS_CONTROLCHANGE | (chan & MIDI_CHANNEL_MASK);
313  packet.data[1] = control;
314  packet.data[2] = value;
315  d->sendShortMessage(packet);
316  }
317 
318  void WinMIDIOutput::sendKeyPressure(int chan, int note, int value)
319  {
320  WinMIDIPacket packet;
321  packet.data[0] = MIDI_STATUS_KEYPRESURE | (chan & MIDI_CHANNEL_MASK);
322  packet.data[1] = note;
323  packet.data[2] = value;
324  d->sendShortMessage(packet);
325  }
326 
327  void WinMIDIOutput::sendProgram(int chan, int program)
328  {
329  WinMIDIPacket packet;
330  packet.data[0] = MIDI_STATUS_PROGRAMCHANGE | (chan & MIDI_CHANNEL_MASK);
331  packet.data[1] = program;
332  d->sendShortMessage(packet);
333  }
334 
335  void WinMIDIOutput::sendChannelPressure(int chan, int value)
336  {
337  WinMIDIPacket packet;
338  packet.data[0] = MIDI_STATUS_CHANNELPRESSURE | (chan & MIDI_CHANNEL_MASK);
339  packet.data[1] = value;
340  d->sendShortMessage(packet);
341  }
342 
343  void WinMIDIOutput::sendPitchBend(int chan, int value)
344  {
345  WinMIDIPacket packet;
346  packet.data[0] = MIDI_STATUS_PITCHBEND | (chan & MIDI_CHANNEL_MASK);
347  packet.data[1] = MIDI_LSB(value);
348  packet.data[2] = MIDI_MSB(value);
349  d->sendShortMessage(packet);
350  }
351 
352  void WinMIDIOutput::sendSystemMsg(const int status)
353  {
354  WinMIDIPacket packet;
355  packet.data[0] = status;
356  d->sendShortMessage(packet);
357  }
358 
359  void WinMIDIOutput::sendSysex(const QByteArray &data)
360  {
361  d->sendSysexEvent(data);
362  }
363 
364 }}
The QObject class is the base class of all Qt objects.