hydrogen 1.2.3
MidiInput.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#include <core/IO/MidiInput.h>
24#include <core/EventQueue.h>
27#include <core/Hydrogen.h>
30#include <core/Basics/Note.h>
31#include <core/MidiAction.h>
33#include <core/MidiMap.h>
34
35namespace H2Core
36{
37
39 : m_bActive( false )
40 , __hihat_cc_openess ( 127 )
41{
42 //
43
44}
45
46
48{
49 //INFOLOG( "DESTROY" );
50}
51
53{
55
56 INFOLOG( QString( "Incoming message: [%1]" ).arg( msg.toQString() ) );
57
58 // midi channel filter for all messages
59 bool bIsChannelValid = true;
61 if ( pPref->m_nMidiChannelFilter != -1
62 && pPref->m_nMidiChannelFilter != msg.m_nChannel
63 ) {
64 bIsChannelValid = false;
65 }
66
67 // exclude all midi channel filter independent messages
68 int type = msg.m_type;
69 if ( MidiMessage::SYSEX == type
70 || MidiMessage::START == type
71 || MidiMessage::CONTINUE == type
72 || MidiMessage::STOP == type
73 || MidiMessage::SONG_POS == type
75 ) {
76 bIsChannelValid = true;
77 }
78
79 if ( !bIsChannelValid) {
80 return;
81 }
82
83 Hydrogen* pHydrogen = Hydrogen::get_instance();
84 auto pAudioEngine = pHydrogen->getAudioEngine();
85 if ( ! pHydrogen->getSong() ) {
86 ERRORLOG( "No song loaded, skipping note" );
87 return;
88 }
89
90 switch ( type ) {
92 handleSysexMessage( msg );
93 break;
94
97 break;
98
100 handleNoteOffMessage( msg, false );
101 break;
102
105 break;
106
109 break;
110
113 break;
114
115 case MidiMessage::START: /* Start from position 0 */
116 if ( pAudioEngine->getState() != AudioEngine::State::Playing ) {
117 pHydrogen->getCoreActionController()->locateToColumn( 0 );
118 auto pAction = std::make_shared<Action>("PLAY");
120 }
121 break;
122
123 case MidiMessage::CONTINUE: /* Just start */ {
124 auto pAction = std::make_shared<Action>("PLAY");
126 break;
127 }
128
129 case MidiMessage::STOP: /* Stop in current position i.e. Pause */ {
130 auto pAction = std::make_shared<Action>("PAUSE");
132 break;
133 }
134
144 ERRORLOG( QString( "MIDI message of type [%1] is not supported by Hydrogen" )
145 .arg( MidiMessage::TypeToQString( msg.m_type ) ) );
146 break;
147
149 ERRORLOG( "Unknown midi message" );
150 break;
151
152 default:
153 ERRORLOG( QString( "unhandled midi message type: %1 (%2)" )
154 .arg( static_cast<int>( msg.m_type ) )
155 .arg( MidiMessage::TypeToQString( msg.m_type ) ) );
156 }
157
158 // Two spaces after "msg." in a row to align message parameters
159 INFOLOG( QString( "DONE handling msg: [%1]" ).arg( msg.toQString() ) );
160}
161
163{
164 //INFOLOG( QString( "[handleMidiMessage] CONTROL_CHANGE Parameter: %1, Value: %2" ).arg( msg.m_nData1 ).arg( msg.m_nData2 ) );
165 Hydrogen *pHydrogen = Hydrogen::get_instance();
167 MidiMap *pMidiMap = MidiMap::get_instance();
168
169 for ( const auto& ppAction : pMidiMap->getCCActions( msg.m_nData1 ) ) {
170 if ( ppAction != nullptr && ! ppAction->isNull() ) {
171 auto pNewAction = std::make_shared<Action>( ppAction );
172 pNewAction->setValue( QString::number( msg.m_nData2 ) );
173 pMidiActionManager->handleAction( pNewAction );
174 }
175 }
176
177 if(msg.m_nData1 == 04){
179 }
180
182 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
183}
184
186{
187 Hydrogen *pHydrogen = Hydrogen::get_instance();
189 MidiMap *pMidiMap = MidiMap::get_instance();
190
191 for ( const auto& ppAction : pMidiMap->getPCActions() ) {
192 if ( ppAction != nullptr && ! ppAction->isNull() ) {
193 auto pNewAction = std::make_shared<Action>( ppAction );
194 pNewAction->setValue( QString::number( msg.m_nData1 ) );
195 pMidiActionManager->handleAction( pNewAction );
196 }
197 }
198
200 pHydrogen->setLastMidiEventParameter( 0 );
201}
202
204{
205// INFOLOG( "handleNoteOnMessage" );
206
207 const int nNote = msg.m_nData1;
208 float fVelocity = msg.m_nData2 / 127.0;
209
210 if ( fVelocity == 0 ) {
211 handleNoteOffMessage( msg, false );
212 return;
213 }
214
215 MidiActionManager * pMidiActionManager = MidiActionManager::get_instance();
216 MidiMap * pMidiMap = MidiMap::get_instance();
217 Hydrogen *pHydrogen = Hydrogen::get_instance();
218 auto pPref = Preferences::get_instance();
219
221 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
222
223 bool bActionSuccess = false;
224 for ( const auto& ppAction : pMidiMap->getNoteActions( msg.m_nData1 ) ) {
225 if ( ppAction != nullptr && ! ppAction->isNull() ) {
226 auto pNewAction = std::make_shared<Action>( ppAction );
227 pNewAction->setValue( QString::number( msg.m_nData2 ) );
228 if ( pMidiActionManager->handleAction( pNewAction ) ) {
229 bActionSuccess = true;
230 }
231 }
232 }
233
234 if ( bActionSuccess && pPref->m_bMidiDiscardNoteAfterAction ) {
235 return;
236 }
237
238 static const float fPan = 0.f;
239
240 int nInstrument = nNote - 36;
241 auto pInstrList = pHydrogen->getSong()->getInstrumentList();
242 std::shared_ptr<Instrument> pInstr = nullptr;
243
244 if ( pPref->__playselectedinstrument ){
245 nInstrument = pHydrogen->getSelectedInstrumentNumber();
246 pInstr= pInstrList->get( pHydrogen->getSelectedInstrumentNumber());
247 }
248 else if ( pPref->m_bMidiFixedMapping ){
249 pInstr = pInstrList->findMidiNote( nNote );
250 nInstrument = pInstrList->index( pInstr );
251 }
252 else {
253 if( nInstrument < 0 || nInstrument >= pInstrList->size()) {
254 WARNINGLOG( QString( "Instrument number [%1] - derived from note [%2] - out of bound note [%3,%4]" )
255 .arg( nInstrument ).arg( nNote )
256 .arg( 0 ).arg( pInstrList->size() ) );
257 return;
258 }
259 pInstr = pInstrList->get( static_cast<uint>(nInstrument) );
260 }
261
262 if( pInstr == nullptr ) {
263 WARNINGLOG( QString( "Can't find corresponding Instrument for note %1" ).arg( nNote ));
264 return;
265 }
266
267 /*
268 Only look to change instrument if the
269 current note is actually of hihat and
270 hihat openness is outside the instrument selected
271 */
272 if ( pInstr != nullptr &&
273 pInstr->get_hihat_grp() >= 0 &&
274 ( __hihat_cc_openess < pInstr->get_lower_cc() ||
275 __hihat_cc_openess > pInstr->get_higher_cc() ) ) {
276
277 for ( int i = 0; i <= pInstrList->size(); i++ ) {
278 auto instr_contestant = pInstrList->get( i );
279 if ( instr_contestant != nullptr &&
280 pInstr->get_hihat_grp() == instr_contestant->get_hihat_grp() &&
281 __hihat_cc_openess >= instr_contestant->get_lower_cc() &&
282 __hihat_cc_openess <= instr_contestant->get_higher_cc() ) {
283
284 nInstrument = i;
285 break;
286 }
287 }
288 }
289
290 pHydrogen->addRealtimeNote( nInstrument, fVelocity, fPan, false, nNote );
291}
292
293/*
294 EDrums (at least Roland TD-6V) uses PolyphonicKeyPressure
295 for cymbal choke.
296 If the message is 127 (choked) we send a NoteOff
297*/
299{
300 if( msg.m_nData2 == 127 ) {
301 handleNoteOffMessage( msg, true );
302 }
303}
304
305void MidiInput::handleNoteOffMessage( const MidiMessage& msg, bool CymbalChoke )
306{
307// INFOLOG( "handleNoteOffMessage" );
308 if ( !CymbalChoke && Preferences::get_instance()->m_bMidiNoteOffIgnore ) {
309 return;
310 }
311
312 Hydrogen *pHydrogen = Hydrogen::get_instance();
313 auto pInstrList = pHydrogen->getSong()->getInstrumentList();
314
315 int nNote = msg.m_nData1;
316 int nInstrument = nNote - 36;
317 std::shared_ptr<Instrument> pInstr = nullptr;
318
319 if ( Preferences::get_instance()->__playselectedinstrument ){
320 nInstrument = pHydrogen->getSelectedInstrumentNumber();
321 pInstr = pInstrList->get( pHydrogen->getSelectedInstrumentNumber());
322 }
323 else if( Preferences::get_instance()->m_bMidiFixedMapping ) {
324 pInstr = pInstrList->findMidiNote( nNote );
325 nInstrument = pInstrList->index( pInstr );
326 }
327 else {
328 if( nInstrument < 0 || nInstrument >= pInstrList->size()) {
329 WARNINGLOG( QString( "Instrument number [%1] - derived from note [%2] - out of bound note [%3,%4]" )
330 .arg( nInstrument ).arg( nNote )
331 .arg( 0 ).arg( pInstrList->size() ) );
332 return;
333 }
334 pInstr = pInstrList->get( nInstrument );
335 }
336
337 if( pInstr == nullptr ) {
338 WARNINGLOG( QString( "Can't find corresponding Instrument for note %1" ).arg( nNote ));
339 return;
340 }
341
342 Hydrogen::get_instance()->addRealtimeNote( nInstrument, 0.0, 0.0, true, nNote );
343}
344
346{
347
348 /*
349 General MMC message
350 0 1 2 3 4 5
351 F0 7F id 6 cmd 247
352
353 cmd:
354 1 stop
355 2 play
356 3 Deferred play
357 4 Fast Forward
358 5 Rewind
359 6 Record strobe (punch in)
360 7 Record exit (punch out)
361 8 Record ready
362 9 Pause
363
364
365 Goto MMC message
366 0 1 2 3 4 5 6 7 8 9 10 11 12
367 240 127 id 6 68 6 1 hr mn sc fr ff 247
368 */
369
370
371 MidiActionManager * pMidiActionManager = MidiActionManager::get_instance();
372 MidiMap * pMidiMap = MidiMap::get_instance();
373 Hydrogen *pHydrogen = Hydrogen::get_instance();
374
375
376 if ( msg.m_sysexData.size() == 6 &&
377 msg.m_sysexData[ 1 ] == 127 && msg.m_sysexData[ 3 ] == 6 ) {
378 // MIDI Machine Control (MMC) message
379
381 QString sMMCtype;
382 switch ( msg.m_sysexData[4] ) {
383 case 1: // STOP
385 break;
386
387 case 2: // PLAY
389 break;
390
391 case 3: //DEFERRED PLAY
393 break;
394
395 case 4: // FAST FWD
397 break;
398
399 case 5: // REWIND
401 break;
402
403 case 6: // RECORD STROBE (PUNCH IN)
405 break;
406
407 case 7: // RECORD EXIT (PUNCH OUT)
409 break;
410
411 case 8: // RECORD READY
413 break;
414
415 case 9: //PAUSE
417 break;
418 }
419
420 if ( event != MidiMessage::Event::Null ) {
421 const QString sMMCtype = MidiMessage::EventToQString( event );
422 INFOLOG( QString( "MIDI Machine Control command: [%1]" )
423 .arg( sMMCtype ) );
424
425 pHydrogen->setLastMidiEvent( event );
426 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
427
428 pMidiActionManager->handleActions( pMidiMap->getMMCActions( sMMCtype ) );
429 }
430 else {
431 WARNINGLOG( "Unknown MIDI Machine Control (MMC) Command" );
432 }
433 }
434 else if ( msg.m_sysexData.size() == 13 &&
435 msg.m_sysexData[ 1 ] == 127 && msg.m_sysexData[ 3 ] == 68 ) {
436 WARNINGLOG( "MMC GOTO Message not implemented yet" );
437 // int hr = msg.m_sysexData[7];
438 // int mn = msg.m_sysexData[8];
439 // int sc = msg.m_sysexData[9];
440 // int fr = msg.m_sysexData[10];
441 // int ff = msg.m_sysexData[11];
442 // char tmp[200];
443 // sprintf( tmp, "[handleSysexMessage] GOTO %d:%d:%d:%d:%d", hr, mn, sc, fr, ff );
444 // INFOLOG( tmp );
445 }
446 else {
447 WARNINGLOG( QString( "Unsupported SysEx message: [%1]" )
448 .arg( msg.toQString() ) );
449 }
450}
451
452};
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
@ Playing
Transport is rolling.
bool locateToColumn(int nPatternGroup)
Relocates transport to the beginning of a particular column/Pattern group.
static EventQueue * get_instance()
Returns a pointer to the current EventQueue singleton stored in __instance.
Definition EventQueue.h:224
void push_event(const EventType type, const int nValue)
Queues the next event into the EventQueue.
Hydrogen Audio Engine.
Definition Hydrogen.h:54
void setLastMidiEventParameter(int nParam)
Definition Hydrogen.h:690
void addRealtimeNote(int instrument, float velocity, float fPan=0.0f, bool noteoff=false, int msg1=0)
Definition Hydrogen.cpp:357
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:122
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:664
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:83
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:649
void setLastMidiEvent(MidiMessage::Event event)
Definition Hydrogen.h:684
CoreActionController * getCoreActionController() const
Definition Hydrogen.h:639
void handleNoteOnMessage(const MidiMessage &msg)
void handleSysexMessage(const MidiMessage &msg)
void handleControlChangeMessage(const MidiMessage &msg)
void handleNoteOffMessage(const MidiMessage &msg, bool CymbalChoke)
void handleProgramChangeMessage(const MidiMessage &msg)
virtual ~MidiInput()
Definition MidiInput.cpp:47
void handleMidiMessage(const MidiMessage &msg)
Definition MidiInput.cpp:52
void handlePolyphonicKeyPressureMessage(const MidiMessage &msg)
MidiMessageType m_type
Definition MidiCommon.h:87
QString toQString(const QString &sPrefix="", bool bShort=true) const
Formatted string version for debugging purposes.
Event
Subset of incoming MIDI events that will be handled by Hydrogen.
Definition MidiCommon.h:66
static QString TypeToQString(MidiMessageType type)
std::vector< unsigned char > m_sysexData
Definition MidiCommon.h:91
static QString EventToQString(Event event)
Manager for User Preferences File (singleton)
Definition Preferences.h:78
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
The MidiActionManager cares for the execution of MidiActions.
Definition MidiAction.h:142
bool handleAction(std::shared_ptr< Action > action)
The handleAction method is the heart of the MidiActionManager class.
static MidiActionManager * get_instance()
Returns a pointer to the current MidiActionManager singleton stored in __instance.
Definition MidiAction.h:252
bool handleActions(std::vector< std::shared_ptr< Action > > actions)
Handles multiple actions at once and calls handleAction() on them.
The MidiMap maps MidiActions to MidiEvents.
Definition MidiMap.h:38
std::vector< std::shared_ptr< Action > > getCCActions(int nParameter)
Returns the cc action which was linked to the given event.
Definition MidiMap.cpp:246
std::vector< std::shared_ptr< Action > > getPCActions() const
Returns the pc action which was linked to the given event.
Definition MidiMap.h:131
static MidiMap * get_instance()
Returns a pointer to the current MidiMap singleton stored in __instance.
Definition MidiMap.h:65
std::vector< std::shared_ptr< Action > > getNoteActions(int nNote)
Returns all note actions which are linked to the given event.
Definition MidiMap.cpp:229
std::vector< std::shared_ptr< Action > > getMMCActions(QString sEventString)
Returns all MMC actions which are linked to the given event.
Definition MidiMap.cpp:212
@ EVENT_MIDI_ACTIVITY
Definition EventQueue.h:84