hydrogen 1.2.6
MidiTable.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 "../Skin.h"
24#include "LCDCombo.h"
25#include "LCDSpinBox.h"
26#include "MidiSenseWidget.h"
27#include "MidiTable.h"
28
29#include <core/MidiMap.h>
30#include <core/IO/MidiCommon.h>
32#include <core/Globals.h>
33#include <core/Hydrogen.h>
35
36#include <QHeaderView>
37
38MidiTable::MidiTable( QWidget *pParent )
39 : QTableWidget( pParent )
40 , m_nRowHeight( 29 )
41 , m_nColumn0Width( 25 )
42 , m_nColumn1Width( 146 )
43 , m_nColumn2Width( 85 )
44 , m_nColumn3Width( 175 )
45 , m_nColumn4Width( 56 )
46 , m_nColumn5Width( 56 )
47 , m_nColumn6Width( 56 )
48 {
49 m_nRowCount = 0;
51
52 m_pUpdateTimer = new QTimer( this );
54}
55
56
58{
59 for( int myRow = 0; myRow <= m_nRowCount ; myRow++ ) {
60 delete cellWidget( myRow, 0 );
61 delete cellWidget( myRow, 1 );
62 delete cellWidget( myRow, 2 );
63 delete cellWidget( myRow, 3 );
64 delete cellWidget( myRow, 4 );
65 delete cellWidget( myRow, 5 );
66 delete cellWidget( myRow, 6 );
67 }
68}
69
71
73 MidiSenseWidget midiSenseWidget( this );
74 midiSenseWidget.exec();
75 if ( midiSenseWidget.getLastMidiEvent() == H2Core::MidiMessage::Event::Null ) {
76 // Rejected
77 return;
78 }
79
80 LCDCombo* pEventCombo = dynamic_cast<LCDCombo*>( cellWidget( nRow, 1 ) );
81 LCDSpinBox* pEventSpinner = dynamic_cast<LCDSpinBox*>( cellWidget( nRow, 2 ) );
82 if ( pEventCombo == nullptr ) {
83 ERRORLOG( QString( "No event combobox in row [%1]" ).arg( nRow ) );
84 return;
85 }
86 if ( pEventSpinner == nullptr ) {
87 ERRORLOG( QString( "No event spinner in row [%1]" ).arg( nRow ) );
88 return;
89 }
90
91 pEventCombo->setCurrentIndex( pEventCombo->findText(
93 midiSenseWidget.getLastMidiEvent() ) ) );
94 pEventSpinner->setValue( midiSenseWidget.getLastMidiEventParameter() );
95
96 m_pUpdateTimer->start( 100 );
97
98 emit changed();
99}
100
101// Reimplementing this one is quite expensive. But the visibility of
102// the spinBoxes is reset after the end of updateTable(). In addition,
103// the function is only called frequently when interacting the the
104// table via mouse. This won't happen too often.
105void MidiTable::paintEvent( QPaintEvent* ev ) {
106 QTableWidget::paintEvent( ev );
107 updateTable();
108}
109
111 if( m_nRowCount > 0 ) {
112 // Ensure that the last row is empty
113 LCDCombo* pEventCombo = dynamic_cast<LCDCombo*>( cellWidget( m_nRowCount - 1, 1 ) );
114 LCDCombo* pActionCombo = dynamic_cast<LCDCombo*>( cellWidget( m_nRowCount - 1, 3 ) );
115
116 if ( pEventCombo == nullptr || pActionCombo == nullptr ) {
117 return;
118 }
119
120 if( ! pActionCombo->currentText().isEmpty() && ! pEventCombo->currentText().isEmpty() ) {
121 std::shared_ptr<Action> pAction = std::make_shared<Action>();
122 insertNewRow( pAction, "", 0 );
123 }
124
125 // Ensure that all other empty rows are removed and that the
126 // parameter spinboxes are only shown when required for that
127 // particular parameter.
128 for ( int ii = 0; ii < m_nRowCount; ii++ ) {
129 updateRow( ii );
130 }
131 }
132}
133
135 emit changed();
136}
137
138void MidiTable::insertNewRow(std::shared_ptr<Action> pAction, QString eventString, int eventParameter)
139{
141
142 insertRow( m_nRowCount );
143
144 int oldRowCount = m_nRowCount;
145
146 ++m_nRowCount;
147
148 QPushButton *midiSenseButton = new QPushButton(this);
149 midiSenseButton->setObjectName( "MidiSenseButton" );
150 midiSenseButton->setIcon(QIcon(Skin::getSvgImagePath() + "/icons/record.svg"));
151 midiSenseButton->setIconSize( QSize( 13, 13 ) );
152 midiSenseButton->setToolTip( tr("press button to record midi event") );
153
154 connect( midiSenseButton, &QPushButton::clicked, [=](){
155 for ( int ii = 0; ii < rowCount(); ii++ ) {
156 if ( cellWidget( ii, 0 ) == midiSenseButton ) {
157 midiSensePressed( ii );
158 return;
159 }
160 }
161 ERRORLOG( QString( "Unable to midiSenseButton of initial row [%1] in MidiTable!" )
162 .arg( oldRowCount ) );
163 });
164 setCellWidget( oldRowCount, 0, midiSenseButton );
165
166 LCDCombo *eventBox = new LCDCombo(this);
167 eventBox->setSize( QSize( m_nColumn1Width, m_nRowHeight ) );
168 eventBox->insertItems( oldRowCount , H2Core::MidiMessage::getEventList() );
169 eventBox->setCurrentIndex( eventBox->findText(eventString) );
170 connect( eventBox , SIGNAL( currentIndexChanged( int ) ) , this , SLOT( updateTable() ) );
171 connect( eventBox , SIGNAL( currentIndexChanged( int ) ),
172 this, SLOT( sendChanged() ) );
173 setCellWidget( oldRowCount, 1, eventBox );
174
175
176 LCDSpinBox *eventParameterSpinner = new LCDSpinBox(this);
177 eventParameterSpinner->setSize( QSize( m_nColumn2Width, m_nRowHeight ) );
178 setCellWidget( oldRowCount , 2, eventParameterSpinner );
179 eventParameterSpinner->setMaximum( 999 );
180 eventParameterSpinner->setValue( eventParameter );
181 connect( eventParameterSpinner, SIGNAL( valueChanged( double ) ),
182 this, SLOT( sendChanged() ) );
183
184
185 LCDCombo *actionBox = new LCDCombo(this);
186 actionBox->setSize( QSize( m_nColumn3Width, m_nRowHeight ) );
187 actionBox->insertItems( oldRowCount, pActionHandler->getActionList());
188 actionBox->setCurrentIndex ( actionBox->findText( pAction->getType() ) );
189 connect( actionBox , SIGNAL( currentIndexChanged( int ) ) , this , SLOT( updateTable() ) );
190 connect( actionBox , SIGNAL( currentIndexChanged( int ) ),
191 this, SLOT( sendChanged() ) );
192 setCellWidget( oldRowCount , 3, actionBox );
193
194 bool ok;
195 LCDSpinBox *actionParameterSpinner1 = new LCDSpinBox(this);
196 actionParameterSpinner1->setSize( QSize( m_nColumn4Width, m_nRowHeight ) );
197 setCellWidget( oldRowCount , 4, actionParameterSpinner1 );
198 actionParameterSpinner1->setMaximum( 999 );
199 actionParameterSpinner1->setValue( pAction->getParameter1().toInt(&ok,10) );
200 actionParameterSpinner1->hide();
201 connect( actionParameterSpinner1, SIGNAL( valueChanged( double ) ),
202 this, SLOT( sendChanged() ) );
203
204 LCDSpinBox *actionParameterSpinner2 = new LCDSpinBox(this);
205 actionParameterSpinner2->setSize( QSize( m_nColumn5Width, m_nRowHeight ) );
206 setCellWidget( oldRowCount , 5, actionParameterSpinner2 );
207 actionParameterSpinner2->setMaximum( std::max(MAX_FX, MAX_COMPONENTS) );
208 actionParameterSpinner2->setValue( pAction->getParameter2().toInt(&ok,10) );
209 actionParameterSpinner2->hide();
210 connect( actionParameterSpinner2, SIGNAL( valueChanged( double ) ),
211 this, SLOT( sendChanged() ) );
212
213 LCDSpinBox *actionParameterSpinner3 = new LCDSpinBox(this);
214 actionParameterSpinner3->setSize( QSize( m_nColumn6Width, m_nRowHeight ) );
215 setCellWidget( oldRowCount , 6, actionParameterSpinner3 );
216 actionParameterSpinner3->setMaximum( H2Core::InstrumentComponent::getMaxLayers() );
217 actionParameterSpinner3->setValue( pAction->getParameter3().toInt(&ok,10) );
218 actionParameterSpinner3->hide();
219 connect( actionParameterSpinner3, SIGNAL( valueChanged( double ) ),
220 this, SLOT( sendChanged() ) );
221}
222
224{
225 MidiMap *pMidiMap = MidiMap::get_instance();
226
227 QStringList items;
228 items << "" << tr("Incoming Event") << tr("Event Para.")
229 << tr("Action") << tr("Para. 1") << tr("Para. 2") << tr("Para. 3");
230
231 setRowCount( 0 );
232 setColumnCount( 7 );
233
234 verticalHeader()->hide();
235
236 setHorizontalHeaderLabels( items );
237 horizontalHeader()->setStretchLastSection(true);
238
239 setColumnWidth( 0 , m_nColumn0Width );
240 setColumnWidth( 1 , m_nColumn1Width );
241 setColumnWidth( 2, m_nColumn2Width );
242 setColumnWidth( 3, m_nColumn3Width );
243 setColumnWidth( 4 , m_nColumn4Width );
244 setColumnWidth( 5 , m_nColumn5Width );
245 setColumnWidth( 6 , m_nColumn6Width );
246
247 for ( const auto& [ssMmcType, ppAction] : pMidiMap->getMMCActionMap() ) {
248 if ( ppAction != nullptr && ! ppAction->isNull() ) {
249 insertNewRow( ppAction, ssMmcType, 0 );
250 }
251 }
252
253 for ( const auto& [nnPitch, ppAction] : pMidiMap->getNoteActionMap() ) {
254 if ( ppAction != nullptr && ! ppAction->isNull() ) {
255 insertNewRow( ppAction,
258 nnPitch );
259 }
260 }
261
262 for ( const auto& [nnParam, ppAction] : pMidiMap->getCCActionMap() ) {
263 if ( ppAction != nullptr && ! ppAction->isNull() ) {
264 insertNewRow( ppAction,
267 nnParam );
268 }
269 }
270
271 for ( const auto& ppAction : pMidiMap->getPCActions() ) {
272 if ( ppAction != nullptr && ! ppAction->isNull() ) {
273 insertNewRow( ppAction,
276 }
277 }
278
279 std::shared_ptr<Action> pAction = std::make_shared<Action>();
280 insertNewRow( pAction, "", 0 );
281}
282
283
285{
287
288 for ( int row = 0; row < m_nRowCount; row++ ) {
289
290 LCDCombo * eventCombo = dynamic_cast <LCDCombo *> ( cellWidget( row, 1 ) );
291 LCDSpinBox * eventSpinner = dynamic_cast <LCDSpinBox *> ( cellWidget( row, 2 ) );
292 LCDCombo * actionCombo = dynamic_cast <LCDCombo *> ( cellWidget( row, 3 ) );
293 LCDSpinBox * actionSpinner1 = dynamic_cast <LCDSpinBox *> ( cellWidget( row, 4 ) );
294 LCDSpinBox * actionSpinner2 = dynamic_cast <LCDSpinBox *> ( cellWidget( row, 5 ) );
295 LCDSpinBox * actionSpinner3 = dynamic_cast <LCDSpinBox *> ( cellWidget( row, 6 ) );
296
297 if( !eventCombo->currentText().isEmpty() && !actionCombo->currentText().isEmpty() ){
298 const QString sEventString = eventCombo->currentText();
299 const auto event = H2Core::MidiMessage::QStringToEvent( sEventString );
300
301 const QString actionString = actionCombo->currentText();
302
303 std::shared_ptr<Action> pAction = std::make_shared<Action>( actionString );
304
305 if( actionSpinner1->cleanText() != ""){
306 pAction->setParameter1( actionSpinner1->cleanText() );
307 }
308 if( actionSpinner2->cleanText() != ""){
309 pAction->setParameter2( actionSpinner2->cleanText() );
310 }
311 if( actionSpinner3->cleanText() != ""){
312 pAction->setParameter3( actionSpinner3->cleanText() );
313 }
314
315 switch ( event ) {
317 mM->registerCCEvent( eventSpinner->cleanText().toInt() , pAction );
318 break;
319
321 mM->registerNoteEvent( eventSpinner->cleanText().toInt() , pAction );
322 break;
323
325 mM->registerPCEvent( pAction );
326 break;
327
329 // Event not recognized
330 continue;
331
332 default:
333 // All remaining events should be different trades of
334 // MMC events. If not, registerMMCEvent will handle it.
335 mM->registerMMCEvent( sEventString , pAction );
336 }
337 }
338 }
339}
340
341void MidiTable::updateRow( int nRow ) {
342 LCDCombo* pEventCombo = dynamic_cast<LCDCombo*>( cellWidget( nRow, 1 ) );
343 LCDCombo* pActionCombo = dynamic_cast<LCDCombo*>( cellWidget( nRow, 3 ) );
344
345 if ( pEventCombo == nullptr || pActionCombo == nullptr ) {
346 return;
347 }
348
349 if( pActionCombo->currentText().isEmpty() &&
350 pEventCombo->currentText().isEmpty() && nRow != m_nRowCount - 1 ) {
351
352 removeRow( nRow );
353 m_nRowCount--;
354 return;
355 }
356
357 // Adjust the event parameter spin box to fit the need of the
358 // particular event.
359 LCDSpinBox* pEventParameterSpinner = dynamic_cast<LCDSpinBox*>( cellWidget( nRow, 2 ) );
360 const QString sEventString = pEventCombo->currentText();
361 const auto event = H2Core::MidiMessage::QStringToEvent( sEventString );
362
363 switch ( event ) {
365 pEventParameterSpinner->show();
366 pEventParameterSpinner->setMinimum( 0 );
367 pEventParameterSpinner->setMaximum( 127 );
368 break;
369
371 pEventParameterSpinner->show();
372 pEventParameterSpinner->setMinimum( MIDI_OUT_NOTE_MIN );
373 pEventParameterSpinner->setMaximum( MIDI_OUT_NOTE_MAX );
374 break;
375
378 default:
379 // Includes all MMC events
380 pEventParameterSpinner->hide();
381 }
382
383 QString sActionType = pActionCombo->currentText();
384 LCDSpinBox* pActionSpinner1 = dynamic_cast<LCDSpinBox*>( cellWidget( nRow, 4 ) );
385 LCDSpinBox* pActionSpinner2 = dynamic_cast<LCDSpinBox*>( cellWidget( nRow, 5 ) );
386 LCDSpinBox* pActionSpinner3 = dynamic_cast<LCDSpinBox*>( cellWidget( nRow, 6 ) );
387 if ( sActionType == Action::getNullActionType() || sActionType.isEmpty() ) {
388 pActionSpinner1->hide();
389 pActionSpinner2->hide();
390 pActionSpinner3->hide();
391
392 } else {
393 int nParameterNumber = MidiActionManager::get_instance()->getParameterNumber( sActionType );
394 if ( nParameterNumber != -1 ) {
395 if ( nParameterNumber < 3 ) {
396 pActionSpinner3->hide();
397 } else {
398 pActionSpinner3->show();
399 }
400 if ( nParameterNumber < 2 ) {
401 pActionSpinner2->hide();
402 } else {
403 pActionSpinner2->show();
404 }
405 if ( nParameterNumber < 1 ) {
406 pActionSpinner1->hide();
407 } else {
408 pActionSpinner1->show();
409 }
410 } else {
411 ERRORLOG( QString( "Unable to find MIDI action [%1]" ).arg( sActionType ) );
412 }
413
414 // Relative changes should allow for both increasing and
415 // decreasing the pattern number.
416 if ( sActionType == "SELECT_NEXT_PATTERN_RELATIVE" ) {
417 pActionSpinner1->setMinimum( -1 * pActionSpinner1->maximum() );
418 }
419 }
420}
#define ERRORLOG(x)
Definition Object.h:242
static QString getNullActionType()
Definition MidiAction.h:34
static QStringList getEventList()
Retrieve the string representation for all available Event.
static QString EventToQString(Event event)
static Event QStringToEvent(const QString &sEvent)
void setSize(QSize size)
Definition LCDCombo.cpp:178
Custom spin box.
Definition LCDSpinBox.h:50
void setValue(double fValue)
void setSize(QSize size)
The MidiActionManager cares for the execution of MidiActions.
Definition MidiAction.h:142
static MidiActionManager * get_instance()
Returns a pointer to the current MidiActionManager singleton stored in __instance.
Definition MidiAction.h:255
QStringList getActionList()
Definition MidiAction.h:257
int getParameterNumber(const QString &sActionType) const
The MidiMap maps MidiActions to MidiEvents.
Definition MidiMap.h:38
void registerMMCEvent(QString, std::shared_ptr< Action >)
Sets up the relation between a mmc event and an action.
Definition MidiMap.cpp:96
void registerNoteEvent(int, std::shared_ptr< Action >)
Sets up the relation between a note event and an action.
Definition MidiMap.cpp:130
std::multimap< int, std::shared_ptr< Action > > getCCActionMap() const
Definition MidiMap.h:128
void registerPCEvent(std::shared_ptr< Action >)
Sets up the relation between a program change and an action.
Definition MidiMap.cpp:190
std::vector< std::shared_ptr< Action > > getPCActions() const
Returns the pc action which was linked to the given event.
Definition MidiMap.h:131
std::multimap< QString, std::shared_ptr< Action > > getMMCActionMap() const
Definition MidiMap.h:122
std::multimap< int, std::shared_ptr< Action > > getNoteActionMap() const
Definition MidiMap.h:125
static MidiMap * get_instance()
Returns a pointer to the current MidiMap singleton stored in __instance.
Definition MidiMap.h:65
void registerCCEvent(int, std::shared_ptr< Action >)
Sets up the relation between a cc event and an action.
Definition MidiMap.cpp:161
int getLastMidiEventParameter() const
H2Core::MidiMessage::Event getLastMidiEvent() const
void saveMidiTable()
int m_nColumn3Width
Definition MidiTable.h:67
int m_nColumn5Width
Definition MidiTable.h:69
int m_nColumn1Width
Definition MidiTable.h:65
QTimer * m_pUpdateTimer
Definition MidiTable.h:62
int m_nRowHeight
Definition MidiTable.h:63
void setupMidiTable()
void changed()
Identicates a user action changing the content of the table.
int m_nColumn6Width
Definition MidiTable.h:70
void insertNewRow(std::shared_ptr< Action > pAction, QString eventString, int eventParameter)
int m_nColumn2Width
Definition MidiTable.h:66
MidiTable(QWidget *pParent)
Definition MidiTable.cpp:38
void updateRow(int nRow)
int m_nColumn4Width
Definition MidiTable.h:68
void midiSensePressed(int)
Definition MidiTable.cpp:70
int m_nRowCount
Definition MidiTable.h:60
int m_nCurrentMidiAutosenseRow
Definition MidiTable.h:61
void sendChanged()
void updateTable()
virtual void paintEvent(QPaintEvent *ev) override
int m_nColumn0Width
Definition MidiTable.h:64
static QString getSvgImagePath()
Definition Skin.h:40
#define MAX_COMPONENTS
Maximum number of components each Instrument is allowed to have.
Definition config.dox:75
#define MAX_FX
Maximum number of effects.
Definition config.dox:83
#define MIDI_OUT_NOTE_MIN
Definition Globals.h:30
#define MIDI_OUT_NOTE_MAX
Definition Globals.h:31