hydrogen 1.2.6
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-2025 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 const auto sMidiPortNameLocal8Bit = sMidiPortName.toLocal8Bit();
206 if ( strcmp( pInfo->name, sMidiPortNameLocal8Bit.constData() ) == 0 &&
207 sMidiPortName != Preferences::getNullMidiPort() ) {
208 nDeviceId = i;
209 }
210 }
211
212 if ( pInfo->output == TRUE ) {
213 const auto sMidiOutputPortNameLocal8Bit = sMidiOutputPortName.toLocal8Bit();
214 if ( strcmp( pInfo->name, sMidiOutputPortNameLocal8Bit.constData() ) == 0 &&
215 sMidiOutputPortName != Preferences::getNullMidiPort() ) {
216 nOutDeviceId = i;
217 }
218 }
219 INFOLOG( QString( "%1%2%3device called [%4] using [%5] MIDI API" )
220 .arg( nDeviceId == i || nOutDeviceId == i ? "Using " :
221 "Found available " )
222 .arg( pInfo->input == TRUE ? "input " : "" )
223 .arg( pInfo->output == TRUE ? "output " : "" )
224 .arg( pInfo->name ).arg( pInfo->interf ) );
225 }
226 }
227
228 // Open input device if found
229 if ( nDeviceId != -1 ) {
230 const PmDeviceInfo *info = Pm_GetDeviceInfo( nDeviceId );
231 if ( info == nullptr ) {
232 ERRORLOG( "Error opening midi input device" );
233 }
234
235 // Timer started with 1ms accuracy without any callback
236 PtError startErr = Pt_Start( 1, 0, 0 );
237 if ( startErr != ptNoError ) {
238 QString sError;
239 switch( startErr ) {
240 case ptHostError:
241 sError = QString( "Host error" );
242 break;
243 case ptAlreadyStarted:
244 sError = QString( "Cannot start timer because it is already started" );
245 break;
246 case ptAlreadyStopped:
247 sError = QString( "Cannot stop timer because it is already stopped" );
248 break;
249 case ptInsufficientMemory:
250 sError = QString( "Memory could not be allocated" );
251 break;
252 }
253 ERRORLOG( QString( "Error in Pt_Start: [%1]" ).arg( sError ) );
254 }
255
256 PmError err = Pm_OpenInput(
257 &m_pMidiIn,
258 nDeviceId,
259 nullptr,
260 nInputBufferSize,
261 TIME_PROC,
262 nullptr
263 );
264
265 if ( err != pmNoError ) {
266 ERRORLOG( QString( "Error in Pm_OpenInput: [%1]" )
267 .arg( PortMidiDriver::translatePmError( err ) ) );
268 m_pMidiIn = nullptr;
269 }
270 }
271 else {
272 // If no input device was selected, there is no error in here.
273 if ( sMidiPortName != Preferences::getNullMidiPort() ) {
274 WARNINGLOG( QString( "MIDI input device [%1] not found." )
275 .arg( sMidiPortName ) );
276 } else {
277 INFOLOG( QString( "No MIDI input device selected" ) );
278 }
279 m_pMidiIn = nullptr;
280 }
281
282 // Open output device if found
283 if ( nOutDeviceId != -1 ) {
284 PmError err = Pm_OpenOutput(
285 &m_pMidiOut,
286 nOutDeviceId,
287 nullptr,
288 nInputBufferSize,
289 TIME_PROC,
290 nullptr,
291 0
292 );
293
294 if ( err != pmNoError ) {
295 ERRORLOG( QString( "Error in Pm_OpenOutput: [%1]" )
296 .arg( PortMidiDriver::translatePmError( err ) ) );
297 m_pMidiOut = nullptr;
298 }
299 }
300 else {
301 // If no output device was selected, there is no error in here.
302 if ( sMidiOutputPortName != Preferences::getNullMidiPort() ) {
303 WARNINGLOG( QString( "MIDI output device [%1] not found." )
304 .arg( sMidiOutputPortName ) );
305 } else {
306 INFOLOG( QString( "No MIDI output device selected" ) );
307 }
308 m_pMidiOut = nullptr;
309 }
310
311 if ( m_pMidiIn != nullptr ) {
312 m_bRunning = true;
313
314 pthread_attr_t attr;
315 pthread_attr_init( &attr );
316 pthread_create( &PortMidiDriverThread, &attr, PortMidiDriver_thread, ( void* )this );
317 }
318}
319
320
322{
323 INFOLOG( "[close]" );
324 if ( m_bRunning ) {
325 m_bRunning = false;
326 pthread_join( PortMidiDriverThread, nullptr );
327 PmError err = Pm_Close( m_pMidiIn );
328 if ( err != pmNoError ) {
329 ERRORLOG( QString( "Error in Pm_Close: [%1]" )
330 .arg( PortMidiDriver::translatePmError( err ) ) );
331 }
332 }
333}
334
336{
337 std::vector<QString> portList;
338
339 int nDevices = Pm_CountDevices();
340 for ( int i = 0; i < nDevices; i++ ) {
341 const PmDeviceInfo *pInfo = Pm_GetDeviceInfo( i );
342 if ( pInfo == nullptr ) {
343 ERRORLOG( QString( "Could not open output device [%1]" ).arg( i ) );
344 } else if ( pInfo->output == TRUE ) {
345 INFOLOG( pInfo->name );
346 portList.push_back( pInfo->name );
347 }
348 }
349
350 return portList;
351}
352
354{
355 std::vector<QString> portList;
356
357 int nDevices = Pm_CountDevices();
358 for ( int i = 0; i < nDevices; i++ ) {
359 const PmDeviceInfo *pInfo = Pm_GetDeviceInfo( i );
360 if ( pInfo == nullptr ) {
361 ERRORLOG( QString( "Could not open input device [%1]" ).arg( i ) );
362 } else if ( pInfo->input == TRUE ) {
363 INFOLOG( pInfo->name );
364 portList.push_back( pInfo->name );
365 }
366 }
367
368 return portList;
369}
370
372{
373 if ( m_pMidiOut == nullptr ) {
374 return;
375 }
376
377 int channel = pNote->get_instrument()->get_midi_out_channel();
378 if ( channel < 0 ) {
379 return;
380 }
381
382 int key = pNote->get_midi_key();
383 int velocity = pNote->get_midi_velocity();
384
385 PmEvent event;
386 event.timestamp = 0;
387
388 //Note off
389 event.message = Pm_Message(0x80 | channel, key, velocity);
390 PmError err = Pm_Write(m_pMidiOut, &event, 1);
391 if ( err != pmNoError ) {
392 ERRORLOG( QString( "Error in Pm_Write for Note off: [%1]" )
393 .arg( PortMidiDriver::translatePmError( err ) ) );
394 }
395
396 //Note on
397 event.message = Pm_Message(0x90 | channel, key, velocity);
398 err = Pm_Write(m_pMidiOut, &event, 1);
399 if ( err != pmNoError ) {
400 ERRORLOG( QString( "Error in Pm_Write for Note on: [%1]" )
401 .arg( PortMidiDriver::translatePmError( err ) ) );
402 }
403}
404
405void PortMidiDriver::handleQueueNoteOff( int channel, int key, int velocity )
406{
407 if ( m_pMidiOut == nullptr ) {
408 return;
409 }
410
411 if ( channel < 0 ) {
412 return;
413 }
414
415 PmEvent event;
416 event.timestamp = 0;
417
418 //Note off
419 event.message = Pm_Message(0x80 | channel, key, velocity);
420 PmError err = Pm_Write(m_pMidiOut, &event, 1);
421 if ( err != pmNoError ) {
422 ERRORLOG( QString( "Error in Pm_Write: [%1]" )
423 .arg( PortMidiDriver::translatePmError( err ) ) );
424 }
425}
426
428{
429 if ( m_pMidiOut == nullptr ) {
430 return;
431 }
432
433 auto instList = Hydrogen::get_instance()->getSong()->getInstrumentList();
434
435 unsigned int numInstruments = instList->size();
436 for (int index = 0; index < numInstruments; ++index) {
437 auto pCurInst = instList->get(index);
438
439 int channel = pCurInst->get_midi_out_channel();
440 if (channel < 0) {
441 continue;
442 }
443 int key = pCurInst->get_midi_out_note();
444
445 PmEvent event;
446 event.timestamp = 0;
447
448 //Note off
449 event.message = Pm_Message(0x80 | channel, key, 0);
450 PmError err = Pm_Write(m_pMidiOut, &event, 1);
451 if ( err != pmNoError ) {
452 ERRORLOG( QString( "Error for instrument [%1] in Pm_Write: [%2]" )
453 .arg( pCurInst->get_name() )
454 .arg( PortMidiDriver::translatePmError( err ) ) );
455 }
456 }
457}
458
459bool PortMidiDriver::appendSysExData( MidiMessage* pMidiMessage, PmMessage msg ) {
460 // End of exception byte indicating the end of a SysEx message.
461 unsigned char eox = 247;
462 unsigned char c = msg & 0x000000ffUL;
463 pMidiMessage->m_sysexData.push_back( c );
464 if ( c == eox ) {
465 return true;
466 }
467
468 c = (msg & 0x0000ff00UL) >> 8;
469 pMidiMessage->m_sysexData.push_back( c );
470 if ( c == eox ) {
471 return true;
472 }
473
474 c = (msg & 0x00ff0000UL) >> 16;
475 pMidiMessage->m_sysexData.push_back( c );
476 if ( c == eox ) {
477 return true;
478 }
479
480 c = (msg & 0xff000000UL) >> 24;
481 pMidiMessage->m_sysexData.push_back( c );
482 if ( c == eox ) {
483 return true;
484 }
485
486 return false;
487}
488
489QString PortMidiDriver::translatePmError( PmError err ) {
490 QString sRes( Pm_GetErrorText( err ) );
491 if ( err == pmHostError ) {
492 // Get OS-dependent part of the error messages, e.g. something
493 // went wrong in the underlying ALSA driver.
494 char *msg;
495 Pm_GetHostErrorText( msg, 100 );
496 sRes.append( QString( ": [%1]" ).arg( msg ) );
497 }
498
499 return std::move( sRes );
500}
501};
502
503#endif // H2CORE_HAVE_PORTMIDI
#define __ERRORLOG(x)
Definition Object.h:254
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define __INFOLOG(x)
Definition Object.h:252
#define ERRORLOG(x)
Definition Object.h:242
#define TIME_PROC
Base class.
Definition Object.h:62
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
void handleMidiMessage(const MidiMessage &msg)
Definition MidiInput.cpp:51
MidiMessageType m_type
Definition MidiCommon.h:93
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:97
void clear()
Reset message.
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
int get_midi_key() const
return scaled key for midi output, !
Definition Note.h:681
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)