hydrogen 1.2.6
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-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#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{
41 //
42
43}
44
45
47{
48 //INFOLOG( "DESTROY" );
49}
50
52{
54
55 INFOLOG( QString( "Incoming message: [%1]" ).arg( msg.toQString() ) );
56
57 // midi channel filter for all messages
58 bool bIsChannelValid = true;
60 if ( pPref->m_nMidiChannelFilter != -1
61 && pPref->m_nMidiChannelFilter != msg.m_nChannel
62 ) {
63 bIsChannelValid = false;
64 }
65
66 // exclude all midi channel filter independent messages
67 int type = msg.m_type;
68 if ( MidiMessage::SYSEX == type
69 || MidiMessage::START == type
70 || MidiMessage::CONTINUE == type
71 || MidiMessage::STOP == type
72 || MidiMessage::SONG_POS == type
74 ) {
75 bIsChannelValid = true;
76 }
77
78 if ( !bIsChannelValid) {
79 return;
80 }
81
82 Hydrogen* pHydrogen = Hydrogen::get_instance();
83 auto pAudioEngine = pHydrogen->getAudioEngine();
84 if ( ! pHydrogen->getSong() ) {
85 ERRORLOG( "No song loaded, skipping note" );
86 return;
87 }
88
89 switch ( type ) {
91 handleSysexMessage( msg );
92 break;
93
96 break;
97
99 handleNoteOffMessage( msg, false );
100 break;
101
104 break;
105
108 break;
109
112 break;
113
114 case MidiMessage::START: /* Start from position 0 */
115 if ( pAudioEngine->getState() != AudioEngine::State::Playing ) {
116 pHydrogen->getCoreActionController()->locateToColumn( 0 );
117 auto pAction = std::make_shared<Action>("PLAY");
119 }
120 break;
121
122 case MidiMessage::CONTINUE: /* Just start */ {
123 auto pAction = std::make_shared<Action>("PLAY");
125 break;
126 }
127
128 case MidiMessage::STOP: /* Stop in current position i.e. Pause */ {
129 auto pAction = std::make_shared<Action>("PAUSE");
131 break;
132 }
133
143 ERRORLOG( QString( "MIDI message of type [%1] is not supported by Hydrogen" )
144 .arg( MidiMessage::TypeToQString( msg.m_type ) ) );
145 break;
146
148 ERRORLOG( "Unknown midi message" );
149 break;
150
151 default:
152 ERRORLOG( QString( "unhandled midi message type: %1 (%2)" )
153 .arg( static_cast<int>( msg.m_type ) )
154 .arg( MidiMessage::TypeToQString( msg.m_type ) ) );
155 }
156
157 // Two spaces after "msg." in a row to align message parameters
158 INFOLOG( QString( "DONE handling msg: [%1]" ).arg( msg.toQString() ) );
159}
160
162{
163 //INFOLOG( QString( "[handleMidiMessage] CONTROL_CHANGE Parameter: %1, Value: %2" ).arg( msg.m_nData1 ).arg( msg.m_nData2 ) );
164 Hydrogen *pHydrogen = Hydrogen::get_instance();
166 MidiMap *pMidiMap = MidiMap::get_instance();
167
168 for ( const auto& ppAction : pMidiMap->getCCActions( msg.m_nData1 ) ) {
169 if ( ppAction != nullptr && ! ppAction->isNull() ) {
170 auto pNewAction = std::make_shared<Action>( ppAction );
171 pNewAction->setValue( QString::number( msg.m_nData2 ) );
172 pMidiActionManager->handleAction( pNewAction );
173 }
174 }
175
176 if ( msg.m_nData1 == 04 ) {
177 pHydrogen->setHihatOpenness( msg.m_nData2 );
178 }
179
181 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
182}
183
185{
186 Hydrogen *pHydrogen = Hydrogen::get_instance();
188 MidiMap *pMidiMap = MidiMap::get_instance();
189
190 for ( const auto& ppAction : pMidiMap->getPCActions() ) {
191 if ( ppAction != nullptr && ! ppAction->isNull() ) {
192 auto pNewAction = std::make_shared<Action>( ppAction );
193 pNewAction->setValue( QString::number( msg.m_nData1 ) );
194 pMidiActionManager->handleAction( pNewAction );
195 }
196 }
197
199 pHydrogen->setLastMidiEventParameter( 0 );
200}
201
203{
204// INFOLOG( "handleNoteOnMessage" );
205
206 const int nNote = msg.m_nData1;
207 float fVelocity = msg.m_nData2 / 127.0;
208
209 if ( fVelocity == 0 ) {
210 handleNoteOffMessage( msg, false );
211 return;
212 }
213
214 MidiActionManager * pMidiActionManager = MidiActionManager::get_instance();
215 MidiMap * pMidiMap = MidiMap::get_instance();
216 Hydrogen *pHydrogen = Hydrogen::get_instance();
217 auto pPref = Preferences::get_instance();
218
220 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
221
222 bool bActionSuccess = false;
223 for ( const auto& ppAction : pMidiMap->getNoteActions( msg.m_nData1 ) ) {
224 if ( ppAction != nullptr && ! ppAction->isNull() ) {
225 auto pNewAction = std::make_shared<Action>( ppAction );
226 pNewAction->setValue( QString::number( msg.m_nData2 ) );
227 if ( pMidiActionManager->handleAction( pNewAction ) ) {
228 bActionSuccess = true;
229 }
230 }
231 }
232
233 if ( bActionSuccess && pPref->m_bMidiDiscardNoteAfterAction ) {
234 return;
235 }
236
237 pHydrogen->getCoreActionController()->handleNote( nNote, fVelocity, false );
238}
239
240/*
241 EDrums (at least Roland TD-6V) uses PolyphonicKeyPressure
242 for cymbal choke.
243 If the message is 127 (choked) we send a NoteOff
244*/
246{
247 if( msg.m_nData2 == 127 ) {
248 handleNoteOffMessage( msg, true );
249 }
250}
251
252void MidiInput::handleNoteOffMessage( const MidiMessage& msg, bool CymbalChoke )
253{
254// INFOLOG( "handleNoteOffMessage" );
255 if ( !CymbalChoke && Preferences::get_instance()->m_bMidiNoteOffIgnore ) {
256 return;
257 }
258
260 handleNote( msg.m_nData1, 0.0, true );
261}
262
264{
265
266 /*
267 General MMC message
268 0 1 2 3 4 5
269 F0 7F id 6 cmd 247
270
271 cmd:
272 1 stop
273 2 play
274 3 Deferred play
275 4 Fast Forward
276 5 Rewind
277 6 Record strobe (punch in)
278 7 Record exit (punch out)
279 8 Record ready
280 9 Pause
281
282
283 Goto MMC message
284 0 1 2 3 4 5 6 7 8 9 10 11 12
285 240 127 id 6 68 6 1 hr mn sc fr ff 247
286 */
287
288
289 MidiActionManager * pMidiActionManager = MidiActionManager::get_instance();
290 MidiMap * pMidiMap = MidiMap::get_instance();
291 Hydrogen *pHydrogen = Hydrogen::get_instance();
292
293
294 if ( msg.m_sysexData.size() == 6 &&
295 msg.m_sysexData[ 1 ] == 127 && msg.m_sysexData[ 3 ] == 6 ) {
296 // MIDI Machine Control (MMC) message
297
299 QString sMMCtype;
300 switch ( msg.m_sysexData[4] ) {
301 case 1: // STOP
303 break;
304
305 case 2: // PLAY
307 break;
308
309 case 3: //DEFERRED PLAY
311 break;
312
313 case 4: // FAST FWD
315 break;
316
317 case 5: // REWIND
319 break;
320
321 case 6: // RECORD STROBE (PUNCH IN)
323 break;
324
325 case 7: // RECORD EXIT (PUNCH OUT)
327 break;
328
329 case 8: // RECORD READY
331 break;
332
333 case 9: //PAUSE
335 break;
336 }
337
338 if ( event != MidiMessage::Event::Null ) {
339 const QString sMMCtype = MidiMessage::EventToQString( event );
340 INFOLOG( QString( "MIDI Machine Control command: [%1]" )
341 .arg( sMMCtype ) );
342
343 pHydrogen->setLastMidiEvent( event );
344 pHydrogen->setLastMidiEventParameter( msg.m_nData1 );
345
346 pMidiActionManager->handleActions( pMidiMap->getMMCActions( sMMCtype ) );
347 }
348 else {
349 WARNINGLOG( "Unknown MIDI Machine Control (MMC) Command" );
350 }
351 }
352 else if ( msg.m_sysexData.size() == 13 &&
353 msg.m_sysexData[ 1 ] == 127 && msg.m_sysexData[ 3 ] == 68 ) {
354 WARNINGLOG( "MMC GOTO Message not implemented yet" );
355 // int hr = msg.m_sysexData[7];
356 // int mn = msg.m_sysexData[8];
357 // int sc = msg.m_sysexData[9];
358 // int fr = msg.m_sysexData[10];
359 // int ff = msg.m_sysexData[11];
360 // char tmp[200];
361 // sprintf( tmp, "[handleSysexMessage] GOTO %d:%d:%d:%d:%d", hr, mn, sc, fr, ff );
362 // INFOLOG( tmp );
363 }
364 else {
365 WARNINGLOG( QString( "Unsupported SysEx message: [%1]" )
366 .arg( msg.toQString() ) );
367 }
368}
369
370};
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
@ Playing
Transport is rolling.
bool locateToColumn(int nPatternGroup)
Relocates transport to the beginning of a particular column/Pattern group.
bool handleNote(int nNote, float fVelocity, bool bNoteOff=false)
Handle an incoming note event, e.g.
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:704
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
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
void setLastMidiEvent(MidiMessage::Event event)
Definition Hydrogen.h:698
CoreActionController * getCoreActionController() const
Definition Hydrogen.h:653
void setHihatOpenness(int nValue)
Definition Hydrogen.h:710
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:46
void handleMidiMessage(const MidiMessage &msg)
Definition MidiInput.cpp:51
void handlePolyphonicKeyPressureMessage(const MidiMessage &msg)
MidiMessageType m_type
Definition MidiCommon.h:93
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:97
static QString EventToQString(Event event)
Manager for User Preferences File (singleton)
Definition Preferences.h:79
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:255
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