hydrogen 1.2.3
PortMidiDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
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, see https://www.gnu.org/licenses
20 *
21 */
22
23
26#include <core/Basics/Note.h>
29#include <core/Hydrogen.h>
30#include <core/Globals.h>
31
32
33#ifdef WIN32
34#include <windows.h>
35#endif
36
37#if defined(H2CORE_HAVE_PORTMIDI) || _DOXYGEN_
38
39#include <porttime.h>
40#define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
41
42#include <pthread.h>
43
44namespace H2Core
45{
46
48
49void* PortMidiDriver_thread( void* param )
50{
51 Base *__object = (Base *)param;
52 PortMidiDriver *instance = ( PortMidiDriver* )param;
53 __INFOLOG( "PortMidiDriver_thread starting" );
54
55 PmError status;
56 int length;
57 PmEvent buffer[1];
58
59 // SysEx messages in PortMidi spread across multiple PmEvents and
60 // it is our responsibility to put them together.
61 MidiMessage sysExMsg;
62 while ( instance->m_bRunning && instance->m_pMidiIn != nullptr ) {
63 length = Pm_Read( instance->m_pMidiIn, buffer, 1 );
64 if ( length > 0 ) {
65
66 int nEventType = Pm_MessageStatus( buffer[0].message );
67
68 if ( nEventType > 127 && nEventType != 247 && nEventType < 256 ) {
69 // New MIDI message received.
70 //
71 // In case of a SysEx message spanning multiple
72 // PmEvents only the first one will have SysEx status
73 // byte. In all remaining events it is omit and the
74 // first byte is an actual data byte [0,127]. The
75 // termination of such an SysEx message is indicated
76 // using 247 which by itself must not be interpreted
77 // as the beginning of a new message.
78 //
79 // 'System Realtime' messages are allowed to occur in
80 // between events corresponding to one and the same
81 // SysEx message but all other event types indicated
82 // that either the previous SysEx message was
83 // completed or that it was truncated (e.g. MIDI cable
84 // removed).
85 if ( nEventType < 248 ) {
86 // No System Realtime event
87 sysExMsg.clear();
88 }
89
90 if ( nEventType == 240 ) {
91 // New SysEx message
92 sysExMsg.m_type = MidiMessage::SYSEX;
93 if ( PortMidiDriver::appendSysExData( &sysExMsg,
94 buffer[0].message ) ) {
95 instance->handleMidiMessage( sysExMsg );
96 }
97 }
98 else {
99 // Other MIDI message consisting only of a single PmEvent.
100 MidiMessage msg;
101 msg.setType( nEventType );
102 msg.m_nData1 = Pm_MessageData1( buffer[0].message );
103 msg.m_nData2 = Pm_MessageData2( buffer[0].message );
104 instance->handleMidiMessage( msg );
105 }
106 }
107 else if ( nEventType >= 256 ) {
108 __ERRORLOG( QString( "Unsupported midi message type: [%1]" )
109 .arg( nEventType ) );
110 }
111 else {
112 // Continuation of a SysEx message.
113 if ( PortMidiDriver::appendSysExData( &sysExMsg,
114 buffer[0].message ) ) {
115 instance->handleMidiMessage( sysExMsg );
116 }
117 }
118 }
119 else if ( length == 0 ) {
120 // No data available
121#ifdef WIN32
122 Sleep( 1 );
123#else
124 usleep( 100 );
125#endif
126 }
127 else {
128 // An error occurred, e.g. a buffer overflow.
129 __ERRORLOG( QString( "Error in Pm_Read: [%1]" )
130 .arg( PortMidiDriver::translatePmError( static_cast<PmError>(length) ) ) );
131 }
132 }
133
134
135
136 __INFOLOG( "MIDI Thread DESTROY" );
137 pthread_exit( nullptr );
138 return nullptr;
139}
140
143 , m_bRunning( false )
144 , m_pMidiIn( nullptr )
145 , m_pMidiOut( nullptr )
146{
147 PmError err = Pm_Initialize();
148 if ( err != pmNoError ) {
149 ERRORLOG( QString( "Error in Pm_Initialize: [%1]" )
150 .arg( PortMidiDriver::translatePmError( err ) ) );
151 }
152}
153
154
156{
157 PmError err = Pm_Terminate();
158 if ( err != pmNoError ) {
159 ERRORLOG( QString( "Error in Pm_Terminate: [%1]" )
160 .arg( PortMidiDriver::translatePmError( err ) ) );
161 }
162}
163
164void PortMidiDriver::handleOutgoingControlChange( int param, int value, int channel )
165{
166 if ( m_pMidiOut == nullptr ) {
167 return;
168 }
169
170 if (channel < 0) {
171 return;
172 }
173
174 PmEvent event;
175 event.timestamp = 0;
176
177 //Control change
178 event.message = Pm_Message(0xB0 | channel, param, value);
179 Pm_Write(m_pMidiOut, &event, 1);
180}
181
182
183
185{
186 INFOLOG( "[open]" );
187
188 int nInputBufferSize = 100;
189
190 int nDeviceId = -1;
191 int nOutDeviceId = -1;
192 QString sMidiPortName = Preferences::get_instance()->m_sMidiPortName;
193 QString sMidiOutputPortName = Preferences::get_instance()->m_sMidiOutputPortName;
194 int nDevices = Pm_CountDevices();
195
196 // Find named devices
197 for ( int i = 0; i < nDevices; i++ ) {
198 const PmDeviceInfo *pInfo = Pm_GetDeviceInfo( i );
199
200 if ( pInfo == nullptr ) {
201 ERRORLOG( QString( "Could not open input device [%1]" ).arg( i ) );
202 }
203 else {
204 if ( pInfo->input == TRUE ) {
205 if ( strcmp( pInfo->name, sMidiPortName.toLocal8Bit().constData() ) == 0 &&
206 sMidiPortName != Preferences::getNullMidiPort() ) {
207 nDeviceId = i;
208 }
209 }
210
211 if ( pInfo->output == TRUE ) {
212 if ( strcmp( pInfo->name, sMidiOutputPortName.toLocal8Bit().constData() ) == 0 &&
213 sMidiOutputPortName != Preferences::getNullMidiPort() ) {
214 nOutDeviceId = i;
215 }
216 }
217 INFOLOG( QString( "%1%2%3device called [%4] using [%5] MIDI API" )
218 .arg( nDeviceId == i || nOutDeviceId == i ? "Using " :
219 "Found available " )
220 .arg( pInfo->input == TRUE ? "input " : "" )
221 .arg( pInfo->output == TRUE ? "output " : "" )
222 .arg( pInfo->name ).arg( pInfo->interf ) );
223 }
224 }
225
226 // Open input device if found
227 if ( nDeviceId != -1 ) {
228 const PmDeviceInfo *info = Pm_GetDeviceInfo( nDeviceId );
229 if ( info == nullptr ) {
230 ERRORLOG( "Error opening midi input device" );
231 }
232
233 // Timer started with 1ms accuracy without any callback
234 PtError startErr = Pt_Start( 1, 0, 0 );
235 if ( startErr != ptNoError ) {
236 QString sError;
237 switch( startErr ) {
238 case ptHostError:
239 sError = QString( "Host error" );
240 break;
241 case ptAlreadyStarted:
242 sError = QString( "Cannot start timer because it is already started" );
243 break;
244 case ptAlreadyStopped:
245 sError = QString( "Cannot stop timer because it is already stopped" );
246 break;
247 case ptInsufficientMemory:
248 sError = QString( "Memory could not be allocated" );
249 break;
250 }
251 ERRORLOG( QString( "Error in Pt_Start: [%1]" ).arg( sError ) );
252 }
253
254 PmError err = Pm_OpenInput(
255 &m_pMidiIn,
256 nDeviceId,
257 nullptr,
258 nInputBufferSize,
259 TIME_PROC,
260 nullptr
261 );
262
263 if ( err != pmNoError ) {
264 ERRORLOG( QString( "Error in Pm_OpenInput: [%1]" )
265 .arg( PortMidiDriver::translatePmError( err ) ) );
266 m_pMidiIn = nullptr;
267 }
268 }
269 else {
270 // If no input device was selected, there is no error in here.
271 if ( sMidiPortName != Preferences::getNullMidiPort() ) {
272 WARNINGLOG( QString( "MIDI input device [%1] not found." )
273 .arg( sMidiPortName ) );
274 } else {
275 INFOLOG( QString( "No MIDI input device selected" ) );
276 }
277 m_pMidiIn = nullptr;
278 }
279
280 // Open output device if found
281 if ( nOutDeviceId != -1 ) {
282 PmError err = Pm_OpenOutput(
283 &m_pMidiOut,
284 nOutDeviceId,
285 nullptr,
286 nInputBufferSize,
287 TIME_PROC,
288 nullptr,
289 0
290 );
291
292 if ( err != pmNoError ) {
293 ERRORLOG( QString( "Error in Pm_OpenOutput: [%1]" )
294 .arg( PortMidiDriver::translatePmError( err ) ) );
295 m_pMidiOut = nullptr;
296 }
297 }
298 else {
299 // If no output device was selected, there is no error in here.
300 if ( sMidiOutputPortName != Preferences::getNullMidiPort() ) {
301 WARNINGLOG( QString( "MIDI output device [%1] not found." )
302 .arg( sMidiOutputPortName ) );
303 } else {
304 INFOLOG( QString( "No MIDI output device selected" ) );
305 }
306 m_pMidiOut = nullptr;
307 }
308
309 if ( m_pMidiIn != nullptr ) {
310 m_bRunning = true;
311
312 pthread_attr_t attr;
313 pthread_attr_init( &attr );
314 pthread_create( &PortMidiDriverThread, &attr, PortMidiDriver_thread, ( void* )this );
315 }
316}
317
318
320{
321 INFOLOG( "[close]" );
322 if ( m_bRunning ) {
323 m_bRunning = false;
324 pthread_join( PortMidiDriverThread, nullptr );
325 PmError err = Pm_Close( m_pMidiIn );
326 if ( err != pmNoError ) {
327 ERRORLOG( QString( "Error in Pm_Close: [%1]" )
328 .arg( PortMidiDriver::translatePmError( err ) ) );
329 }
330 }
331}
332
334{
335 std::vector<QString> portList;
336
337 int nDevices = Pm_CountDevices();
338 for ( int i = 0; i < nDevices; i++ ) {
339 const PmDeviceInfo *pInfo = Pm_GetDeviceInfo( i );
340 if ( pInfo == nullptr ) {
341 ERRORLOG( QString( "Could not open output device [%1]" ).arg( i ) );
342 } else if ( pInfo->output == TRUE ) {
343 INFOLOG( pInfo->name );
344 portList.push_back( pInfo->name );
345 }
346 }
347
348 return portList;
349}
350
352{
353 std::vector<QString> portList;
354
355 int nDevices = Pm_CountDevices();
356 for ( int i = 0; i < nDevices; i++ ) {
357 const PmDeviceInfo *pInfo = Pm_GetDeviceInfo( i );
358 if ( pInfo == nullptr ) {
359 ERRORLOG( QString( "Could not open input device [%1]" ).arg( i ) );
360 } else if ( pInfo->input == TRUE ) {
361 INFOLOG( pInfo->name );
362 portList.push_back( pInfo->name );
363 }
364 }
365
366 return portList;
367}
368
370{
371 if ( m_pMidiOut == nullptr ) {
372 return;
373 }
374
375 int channel = pNote->get_instrument()->get_midi_out_channel();
376 if ( channel < 0 ) {
377 return;
378 }
379
380 int key = pNote->get_midi_key();
381 int velocity = pNote->get_midi_velocity();
382
383 PmEvent event;
384 event.timestamp = 0;
385
386 //Note off
387 event.message = Pm_Message(0x80 | channel, key, velocity);
388 PmError err = Pm_Write(m_pMidiOut, &event, 1);
389 if ( err != pmNoError ) {
390 ERRORLOG( QString( "Error in Pm_Write for Note off: [%1]" )
391 .arg( PortMidiDriver::translatePmError( err ) ) );
392 }
393
394 //Note on
395 event.message = Pm_Message(0x90 | channel, key, velocity);
396 err = Pm_Write(m_pMidiOut, &event, 1);
397 if ( err != pmNoError ) {
398 ERRORLOG( QString( "Error in Pm_Write for Note on: [%1]" )
399 .arg( PortMidiDriver::translatePmError( err ) ) );
400 }
401}
402
403void PortMidiDriver::handleQueueNoteOff( int channel, int key, int velocity )
404{
405 if ( m_pMidiOut == nullptr ) {
406 return;
407 }
408
409 if ( channel < 0 ) {
410 return;
411 }
412
413 PmEvent event;
414 event.timestamp = 0;
415
416 //Note off
417 event.message = Pm_Message(0x80 | channel, key, velocity);
418 PmError err = Pm_Write(m_pMidiOut, &event, 1);
419 if ( err != pmNoError ) {
420 ERRORLOG( QString( "Error in Pm_Write: [%1]" )
421 .arg( PortMidiDriver::translatePmError( err ) ) );
422 }
423}
424
426{
427 if ( m_pMidiOut == nullptr ) {
428 return;
429 }
430
431 auto instList = Hydrogen::get_instance()->getSong()->getInstrumentList();
432
433 unsigned int numInstruments = instList->size();
434 for (int index = 0; index < numInstruments; ++index) {
435 auto pCurInst = instList->get(index);
436
437 int channel = pCurInst->get_midi_out_channel();
438 if (channel < 0) {
439 continue;
440 }
441 int key = pCurInst->get_midi_out_note();
442
443 PmEvent event;
444 event.timestamp = 0;
445
446 //Note off
447 event.message = Pm_Message(0x80 | channel, key, 0);
448 PmError err = Pm_Write(m_pMidiOut, &event, 1);
449 if ( err != pmNoError ) {
450 ERRORLOG( QString( "Error for instrument [%1] in Pm_Write: [%2]" )
451 .arg( pCurInst->get_name() )
452 .arg( PortMidiDriver::translatePmError( err ) ) );
453 }
454 }
455}
456
457bool PortMidiDriver::appendSysExData( MidiMessage* pMidiMessage, PmMessage msg ) {
458 // End of exception byte indicating the end of a SysEx message.
459 unsigned char eox = 247;
460 unsigned char c = msg & 0x000000ffUL;
461 pMidiMessage->m_sysexData.push_back( c );
462 if ( c == eox ) {
463 return true;
464 }
465
466 c = (msg & 0x0000ff00UL) >> 8;
467 pMidiMessage->m_sysexData.push_back( c );
468 if ( c == eox ) {
469 return true;
470 }
471
472 c = (msg & 0x00ff0000UL) >> 16;
473 pMidiMessage->m_sysexData.push_back( c );
474 if ( c == eox ) {
475 return true;
476 }
477
478 c = (msg & 0xff000000UL) >> 24;
479 pMidiMessage->m_sysexData.push_back( c );
480 if ( c == eox ) {
481 return true;
482 }
483
484 return false;
485}
486
487QString PortMidiDriver::translatePmError( PmError err ) {
488 QString sRes( Pm_GetErrorText( err ) );
489 if ( err == pmHostError ) {
490 // Get OS-dependent part of the error messages, e.g. something
491 // went wrong in the underlying ALSA driver.
492 char *msg;
493 Pm_GetHostErrorText( msg, 100 );
494 sRes.append( QString( ": [%1]" ).arg( msg ) );
495 }
496
497 return std::move( sRes );
498}
499};
500
501#endif // H2CORE_HAVE_PORTMIDI
#define __ERRORLOG(x)
Definition Object.h:251
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define __INFOLOG(x)
Definition Object.h:249
#define ERRORLOG(x)
Definition Object.h:239
#define TIME_PROC
Base class.
Definition Object.h:62
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:122
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:83
MIDI input base class.
Definition MidiInput.h:39
void handleMidiMessage(const MidiMessage &msg)
Definition MidiInput.cpp:52
MidiMessageType m_type
Definition MidiCommon.h:87
void setType(int nStatusByte)
Derives and set m_type (and if applicable m_nChannel) using the statusByte of an incoming MIDI messag...
std::vector< unsigned char > m_sysexData
Definition MidiCommon.h:91
void clear()
Reset message.
MIDI input base class.
Definition MidiOutput.h:41
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:102
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:500
int get_midi_key() const
return scaled key for midi output, !!! DO NOT CHECK IF INSTRUMENT IS SET !!!
Definition Note.h:682
int get_midi_velocity() const
midi velocity accessor
Definition Note.h:690
static bool appendSysExData(MidiMessage *pMidiMessage, PmMessage msg)
Appends the content of msg to MidiMessage::m_sysexData of pMidiMessage till 247 (EOX - end of exclusi...
virtual void open() override
static QString translatePmError(PmError err)
virtual std::vector< QString > getInputPortList() override
virtual void close() override
virtual void handleQueueNoteOff(int channel, int key, int velocity) override
virtual std::vector< QString > getOutputPortList() override
virtual void handleQueueAllNoteOff() override
virtual void handleOutgoingControlChange(int param, int value, int channel) override
virtual void handleQueueNote(Note *pNote) override
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
QString m_sMidiOutputPortName
static QString getNullMidiPort()
Choice of m_sMidiPortName and m_sMidiOutputPortName in case no port/device was selected.
pthread_t PortMidiDriverThread
void * PortMidiDriver_thread(void *param)