hydrogen 1.2.6
CoreActionController.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 <QDir>
24
28#include <core/EventQueue.h>
29#include <core/Hydrogen.h>
34#include <core/Basics/Pattern.h>
35#include "core/OscServer.h"
36#include <core/MidiAction.h>
37#include "core/MidiMap.h"
38#include <core/Helpers/Xml.h>
40
42#include <core/IO/MidiOutput.h>
44
45#ifdef H2CORE_HAVE_OSC
46#include <core/NsmClient.h>
47#endif
48
49namespace H2Core
50{
51
52
57
61
62bool CoreActionController::setMasterVolume( float fMasterVolumeValue )
63{
64 auto pSong = Hydrogen::get_instance()->getSong();
65
66 if ( pSong == nullptr ) {
67 ERRORLOG( "no song set" );
68 return false;
69 }
70
71 pSong->setVolume( fMasterVolumeValue );
72
74}
75
76bool CoreActionController::setStripVolume( int nStrip, float fVolumeValue, bool bSelectStrip )
77{
78 auto pHydrogen = Hydrogen::get_instance();
79
80 auto pInstr = getStrip( nStrip );
81 if ( pInstr != nullptr ) {
82
83 pInstr->set_volume( fVolumeValue );
84
85 if ( bSelectStrip ) {
86 pHydrogen->setSelectedInstrumentNumber( nStrip );
87 }
88
89 pHydrogen->setIsModified( true );
90
91 return sendStripVolumeFeedback( nStrip );
92 }
93
94 return false;
95}
96
97bool CoreActionController::setInstrumentPitch( int nInstrument, float fValue ){
99 if ( pSong == nullptr ) {
100 ERRORLOG( "no song set" );
101 return false;
102 }
103 auto pInstrumentList = pSong->getInstrumentList();
104 auto pInstrument = pInstrumentList->get( nInstrument );
105 if( pInstrument == nullptr ) {
106 ERRORLOG( QString( "Unable to retrieve instrument (Par. 1) [%1]" )
107 .arg( nInstrument ) );
108 return false;
109 }
110
111 pInstrument->set_pitch_offset( fValue );
114 nInstrument );
115
116 return true;
117}
118
125
127{
128 auto pHydrogen = Hydrogen::get_instance();
129 auto pSong = pHydrogen->getSong();
130
131 if ( pSong == nullptr ) {
132 ERRORLOG( "no song set" );
133 return false;
134 }
135
136 pSong->setIsMuted( bIsMuted );
137
138 pHydrogen->setIsModified( true );
139
141}
142
144{
145 auto pInstr = getStrip( nStrip );
146 if ( pInstr == nullptr ) {
147 return false;
148 }
149
150 return setStripIsMuted( nStrip, !pInstr->is_muted() );
151}
152
153bool CoreActionController::setStripIsMuted( int nStrip, bool bIsMuted )
154{
155 auto pHydrogen = Hydrogen::get_instance();
156 auto pInstr = getStrip( nStrip );
157 if ( pInstr != nullptr ) {
158 pInstr->set_muted( bIsMuted );
159
161
162 pHydrogen->setIsModified( true );
163
164 return sendStripIsMutedFeedback( nStrip );
165 }
166
167 return false;
168}
169
171{
172 auto pInstr = getStrip( nStrip );
173 if ( pInstr == nullptr ) {
174 return false;
175 }
176
177 return setStripIsSoloed( nStrip, !pInstr->is_soloed() );
178}
179
180bool CoreActionController::setStripIsSoloed( int nStrip, bool isSoloed )
181{
182 auto pHydrogen = Hydrogen::get_instance();
183 auto pInstr = getStrip( nStrip );
184 if ( pInstr != nullptr ) {
185
186 pInstr->set_soloed( isSoloed );
187
189
190 pHydrogen->setIsModified( true );
191
192 return sendStripIsSoloedFeedback( nStrip );
193 }
194
195 return false;
196}
197
198bool CoreActionController::setStripPan( int nStrip, float fValue, bool bSelectStrip )
199{
200 auto pHydrogen = Hydrogen::get_instance();
201 auto pInstr = getStrip( nStrip );
202 if ( pInstr != nullptr ) {
203
204 pInstr->setPanWithRangeFrom0To1( fValue );
205
207
208 pHydrogen->setIsModified( true );
209
210 if ( bSelectStrip ) {
211 pHydrogen->setSelectedInstrumentNumber( nStrip );
212 }
213
214 return sendStripPanFeedback( nStrip );
215 }
216
217 return false;
218}
219
220
221bool CoreActionController::setStripPanSym( int nStrip, float fValue, bool bSelectStrip )
222{
223 auto pHydrogen = Hydrogen::get_instance();
224 auto pInstr = getStrip( nStrip );
225 if ( pInstr != nullptr ) {
226
227 pInstr->setPan( fValue );
228
230
231 pHydrogen->setIsModified( true );
232
233 if ( bSelectStrip ) {
234 pHydrogen->setSelectedInstrumentNumber( nStrip );
235 }
236
237 return sendStripPanFeedback( nStrip );
238 }
239
240 return false;
241}
242
244 auto pSong = Hydrogen::get_instance()->getSong();
245 if ( pSong == nullptr ) {
246 ERRORLOG( "no song set" );
247 return false;
248 }
249
250 float fMasterVolume = pSong->getVolume();
251
252#ifdef H2CORE_HAVE_OSC
253 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
254
255 std::shared_ptr<Action> pFeedbackAction =
256 std::make_shared<Action>( "MASTER_VOLUME_ABSOLUTE" );
257
258 pFeedbackAction->setValue( QString("%1")
259 .arg( fMasterVolume ) );
260 OscServer::get_instance()->handleAction( pFeedbackAction );
261 }
262#endif
263
264 MidiMap* pMidiMap = MidiMap::get_instance();
265
266 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("MASTER_VOLUME_ABSOLUTE"));
267
268 return handleOutgoingControlChanges( ccParamValues, (fMasterVolume / 1.5) * 127 );
269}
270
272
273 auto pInstr = getStrip( nStrip );
274 if ( pInstr != nullptr ) {
275
276 float fStripVolume = pInstr->get_volume();
277
278#ifdef H2CORE_HAVE_OSC
279 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
280
281 std::shared_ptr<Action> pFeedbackAction =
282 std::make_shared<Action>( "STRIP_VOLUME_ABSOLUTE" );
283
284 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
285 pFeedbackAction->setValue( QString("%1").arg( fStripVolume ) );
286 OscServer::get_instance()->handleAction( pFeedbackAction );
287 }
288#endif
289
290 MidiMap* pMidiMap = MidiMap::get_instance();
291
292 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_VOLUME_ABSOLUTE"),
293 QString("%1").arg( nStrip ) );
294
295 return handleOutgoingControlChanges( ccParamValues, (fStripVolume / 1.5) * 127 );
296 }
297
298 return false;
299}
300
302 auto pPref = Preferences::get_instance();
303
304#ifdef H2CORE_HAVE_OSC
305 if ( pPref->getOscFeedbackEnabled() ) {
306 std::shared_ptr<Action> pFeedbackAction =
307 std::make_shared<Action>( "TOGGLE_METRONOME" );
308
309 pFeedbackAction->setParameter1( QString("%1")
310 .arg( static_cast<int>(pPref->m_bUseMetronome) ) );
311 OscServer::get_instance()->handleAction( pFeedbackAction );
312 }
313#endif
314
315 MidiMap* pMidiMap = MidiMap::get_instance();
316
317 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("TOGGLE_METRONOME"));
318
319 return handleOutgoingControlChanges( ccParamValues,
320 static_cast<int>(pPref->m_bUseMetronome) * 127 );
321}
322
324 auto pSong = Hydrogen::get_instance()->getSong();
325 if ( pSong == nullptr ) {
326 ERRORLOG( "no song set" );
327 return false;
328 }
329
330#ifdef H2CORE_HAVE_OSC
331 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
332 std::shared_ptr<Action> pFeedbackAction =
333 std::make_shared<Action>( "MUTE_TOGGLE" );
334
335 pFeedbackAction->setParameter1( QString("%1")
336 .arg( static_cast<int>(pSong->getIsMuted()) ) );
337 OscServer::get_instance()->handleAction( pFeedbackAction );
338 }
339#endif
340
341 MidiMap* pMidiMap = MidiMap::get_instance();
342
343 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("MUTE_TOGGLE") );
344
345 return handleOutgoingControlChanges( ccParamValues,
346 static_cast<int>(pSong->getIsMuted()) * 127 );
347}
348
350 auto pInstr = getStrip( nStrip );
351 if ( pInstr != nullptr ) {
352
353#ifdef H2CORE_HAVE_OSC
354 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
355 std::shared_ptr<Action> pFeedbackAction =
356 std::make_shared<Action>( "STRIP_MUTE_TOGGLE" );
357
358 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
359 pFeedbackAction->setValue( QString("%1")
360 .arg( static_cast<int>(pInstr->is_muted()) ) );
361 OscServer::get_instance()->handleAction( pFeedbackAction );
362 }
363#endif
364
365 MidiMap* pMidiMap = MidiMap::get_instance();
366
367 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_MUTE_TOGGLE"),
368 QString("%1").arg( nStrip ) );
369
370 return handleOutgoingControlChanges( ccParamValues,
371 static_cast<int>(pInstr->is_muted()) * 127 );
372 }
373
374 return false;
375}
376
378 auto pInstr = getStrip( nStrip );
379 if ( pInstr != nullptr ) {
380
381#ifdef H2CORE_HAVE_OSC
382 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
383 std::shared_ptr<Action> pFeedbackAction =
384 std::make_shared<Action>( "STRIP_SOLO_TOGGLE" );
385
386 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
387 pFeedbackAction->setValue( QString("%1")
388 .arg( static_cast<int>(pInstr->is_soloed()) ) );
389 OscServer::get_instance()->handleAction( pFeedbackAction );
390 }
391#endif
392
393 MidiMap* pMidiMap = MidiMap::get_instance();
394 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_SOLO_TOGGLE"),
395 QString("%1").arg( nStrip ) );
396
397 return handleOutgoingControlChanges( ccParamValues,
398 static_cast<int>(pInstr->is_soloed()) * 127 );
399 }
400
401 return false;
402}
403
405 auto pInstr = getStrip( nStrip );
406 if ( pInstr != nullptr ) {
407
408#ifdef H2CORE_HAVE_OSC
409 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
410 std::shared_ptr<Action> pFeedbackAction =
411 std::make_shared<Action>( "PAN_ABSOLUTE" );
412
413 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
414 pFeedbackAction->setValue( QString("%1")
415 .arg( pInstr->getPanWithRangeFrom0To1() ) );
416 OscServer::get_instance()->handleAction( pFeedbackAction );
417 }
418#endif
419
420 MidiMap* pMidiMap = MidiMap::get_instance();
421 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("PAN_ABSOLUTE"),
422 QString("%1").arg( nStrip ) );
423
424 return handleOutgoingControlChanges( ccParamValues,
425 pInstr->getPanWithRangeFrom0To1() * 127 );
426 }
427
428 return false;
429}
430
431bool CoreActionController::handleOutgoingControlChanges( std::vector<int> params, int nValue)
432{
434 Hydrogen *pHydrogen = Hydrogen::get_instance();
435 MidiOutput *pMidiDriver = pHydrogen->getMidiOutput();
436
437 if ( pHydrogen->getSong() == nullptr ) {
438 ERRORLOG( "no song set" );
439 return false;
440 }
441
442 for ( auto param : params ) {
443 if ( pMidiDriver != nullptr &&
444 pPref->m_bEnableMidiFeedback && param >= 0 ){
445 pMidiDriver->handleOutgoingControlChange( param, nValue, m_nDefaultMidiFeedbackChannel );
446 }
447 }
448
449 return true;
450}
451
452std::shared_ptr<Instrument> CoreActionController::getStrip( int nStrip ) const {
453 auto pSong = Hydrogen::get_instance()->getSong();
454 if ( pSong == nullptr ) {
455 ERRORLOG( "no song set" );
456 return nullptr;
457 }
458
459 auto pInstr = pSong->getInstrumentList()->get( nStrip );
460 if ( pInstr == nullptr ) {
461 ERRORLOG( QString( "Couldn't find instrument [%1]" ).arg( nStrip ) );
462 }
463
464 return pInstr;
465}
466
468{
469 /*
470 * Push the current state of Hydrogen to the attached control interfaces (e.g. OSC clients)
471 */
472
473 //MASTER_VOLUME_ABSOLUTE
474 auto pHydrogen = Hydrogen::get_instance();
475 auto pSong = pHydrogen->getSong();
476
477 if ( pSong == nullptr ) {
478 ERRORLOG( "no song set" );
479 return false;
480 }
481
483
484 //PER-INSTRUMENT/STRIP STATES
485 auto pInstrList = pSong->getInstrumentList();
486 for ( int ii = 0; ii < pInstrList->size(); ii++){
487 auto pInstr = pInstrList->get( ii );
488 if ( pInstr != nullptr ) {
489
490 //STRIP_VOLUME_ABSOLUTE
492
493 //PAN_ABSOLUTE
495
496 //STRIP_MUTE_TOGGLE
498
499 //SOLO
501 }
502 }
503
504 //TOGGLE_METRONOME
506
507 //MUTE_TOGGLE
509
510 return true;
511}
512
515
516bool CoreActionController::newSong( const QString& sSongPath ) {
517
518 auto pHydrogen = Hydrogen::get_instance();
519
520 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
521 // Stops recording, all queued MIDI notes, and the playback of
522 // the audio driver.
523 pHydrogen->sequencer_stop();
524 }
525
526 // Create an empty Song.
527 auto pSong = Song::getEmptySong();
528
529 // Check whether the provided path is valid.
530 if ( !Filesystem::isSongPathValid( sSongPath ) ) {
531 // Filesystem::isSongPathValid takes care of the error log message.
532
533 return false;
534 }
535
536 if ( pHydrogen->isUnderSessionManagement() ) {
537 pHydrogen->restartDrivers();
538 // The drumkit of the new song will linked into the session
539 // folder during the next song save.
540 pHydrogen->setSessionDrumkitNeedsRelinking( true );
541 }
542
543 pSong->setFilename( sSongPath );
544
545 pHydrogen->setSong( pSong );
546
547 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
549 }
550
551 return true;
552}
553
554bool CoreActionController::openSong( const QString& sSongPath, const QString& sRecoverSongPath ) {
555 auto pHydrogen = Hydrogen::get_instance();
556
557 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
558 // Stops recording, all queued MIDI notes, and the playback of
559 // the audio driver.
560 pHydrogen->sequencer_stop();
561 }
562
563 // Check whether the provided path is valid.
564 if ( !Filesystem::isSongPathValid( sSongPath, true ) ) {
565 // Filesystem::isSongPathValid takes care of the error log message.
566 return false;
567 }
568
569 std::shared_ptr<Song> pSong;
570 if ( ! sRecoverSongPath.isEmpty() ) {
571 // Use an autosave file to load the song
572 pSong = Song::load( sRecoverSongPath );
573 if ( pSong != nullptr ) {
574 pSong->setFilename( sSongPath );
575 }
576 } else {
577 pSong = Song::load( sSongPath );
578 }
579
580 if ( pSong == nullptr ) {
581 ERRORLOG( QString( "Unable to open song [%1]." )
582 .arg( sSongPath ) );
583 return false;
584 }
585
586 return setSong( pSong );
587}
588
589bool CoreActionController::openSong( std::shared_ptr<Song> pSong, bool bRelinking ) {
590
591 auto pHydrogen = Hydrogen::get_instance();
592
593 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
594 // Stops recording, all queued MIDI notes, and the playback of
595 // the audio driver.
596 pHydrogen->sequencer_stop();
597 }
598
599 if ( pSong == nullptr ) {
600 ERRORLOG( QString( "Unable to open song." ) );
601 return false;
602 }
603
604 return setSong( pSong, bRelinking );
605}
606
607bool CoreActionController::setSong( std::shared_ptr<Song> pSong, bool bRelinking ) {
608
609 auto pHydrogen = Hydrogen::get_instance();
610
611 // Update the Song.
612 pHydrogen->setSong( pSong, bRelinking );
613
614 if ( pHydrogen->isUnderSessionManagement() ) {
615 pHydrogen->restartDrivers();
616 } else if ( pSong->getFilename() != Filesystem::empty_song_path() ) {
617 // Add the new loaded song in the "last used song" vector.
618 // This behavior is prohibited under session management. Only
619 // songs open during normal runs will be listed. In addition,
620 // empty songs - created and set when hitting "New Song" in
621 // the main menu - aren't listed either.
622 insertRecentFile( pSong->getFilename() );
623 Preferences::get_instance()->setLastSongFilename( pSong->getFilename() );
624 }
625
626 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
628 }
629
630 // As we just set a fresh song, we can mark it not modified
631 pHydrogen->setIsModified( false );
632
633 return true;
634}
635
637
638 auto pHydrogen = Hydrogen::get_instance();
639 auto pSong = pHydrogen->getSong();
640
641 if ( pSong == nullptr ) {
642 ERRORLOG( "no song set" );
643 return false;
644 }
645
646 // Extract the path to the associate .h2song file.
647 QString sSongPath = pSong->getFilename();
648
649 if ( sSongPath.isEmpty() ) {
650 ERRORLOG( "Unable to save song. Empty filename!" );
651 return false;
652 }
653
654#ifdef H2CORE_HAVE_OSC
655 if ( pHydrogen->isUnderSessionManagement() &&
656 pHydrogen->getSessionDrumkitNeedsRelinking() &&
657 ! pHydrogen->getSessionIsExported() ) {
658
659 NsmClient::linkDrumkit( pSong );
660
661 // Properly set in NsmClient::linkDrumkit()
662 QString sSessionDrumkitPath = pSong->getLastLoadedDrumkitPath();
663
664 auto drumkitDatabase = pHydrogen->getSoundLibraryDatabase()->getDrumkitDatabase();
665 if ( drumkitDatabase.find( sSessionDrumkitPath ) != drumkitDatabase.end() ) {
666 // In case the session folder is already present in the
667 // SoundLibraryDatabase, we have to update it (takes a
668 // while) to ensure it's clean and all kits are valid. If
669 // it's not present, we can skip it because loading is
670 // done lazily.
671 pHydrogen->getSoundLibraryDatabase()->updateDrumkit( sSessionDrumkitPath );
672 }
673 }
674#endif
675
676 // Actual saving
677 bool bSaved = pSong->save( sSongPath );
678 if ( ! bSaved ) {
679 ERRORLOG( QString( "Current song [%1] could not be saved!" )
680 .arg( sSongPath ) );
681 return false;
682 }
683
684 // Update the status bar.
685 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
687 }
688
689 return true;
690}
691
692bool CoreActionController::saveSongAs( const QString& sNewFilename ) {
693
694 auto pHydrogen = Hydrogen::get_instance();
695 auto pSong = pHydrogen->getSong();
696
697 if ( pSong == nullptr ) {
698 ERRORLOG( "no song set" );
699 return false;
700 }
701
702 // Check whether the provided path is valid.
703 if ( !Filesystem::isSongPathValid( sNewFilename ) ) {
704 // Filesystem::isSongPathValid takes care of the error log message.
705 return false;
706 }
707
708 QString sPreviousFilename( pSong->getFilename() );
709 pSong->setFilename( sNewFilename );
710
711 // Actual saving
712 if ( ! saveSong() ) {
713 return false;
714 }
715
716 // Update the recentFiles list by replacing the former file name
717 // with the new one.
718 insertRecentFile( sNewFilename );
719 if ( ! pHydrogen->isUnderSessionManagement() ) {
720 Preferences::get_instance()->setLastSongFilename( pSong->getFilename() );
721 }
722
723 return true;
724}
725
727
729 // Update the status bar and let the GUI save the preferences
730 // (after writing its current settings to disk).
732 return true;
733 }
734
736}
739
740 return true;
741}
742
744 auto pHydrogen = Hydrogen::get_instance();
745
746 if ( pHydrogen->getSong() == nullptr ) {
747 ERRORLOG( "no song set" );
748 return false;
749 }
750
751 pHydrogen->setIsTimelineActivated( bActivate );
752
753 if ( pHydrogen->getJackTimebaseState() ==
755 WARNINGLOG( QString( "Timeline usage was [%1] in the Preferences. But these changes won't have an effect as long as there is still an external JACK Timebase controller." )
756 .arg( bActivate ? "enabled" : "disabled" ) );
757 } else if ( pHydrogen->getMode() == Song::Mode::Pattern ) {
758 WARNINGLOG( QString( "Timeline usage was [%1] in the Preferences. But these changes won't have an effect as long as Pattern Mode is still activated." )
759 .arg( bActivate ? "enabled" : "disabled" ) );
760 }
761
762 return true;
763}
764
765bool CoreActionController::addTempoMarker( int nPosition, float fBpm ) {
766 auto pHydrogen = Hydrogen::get_instance();
767 auto pAudioEngine = pHydrogen->getAudioEngine();
768 auto pTimeline = pHydrogen->getTimeline();
769
770 if ( pHydrogen->getSong() == nullptr ) {
771 ERRORLOG( "no song set" );
772 return false;
773 }
774
775 pAudioEngine->lock( RIGHT_HERE );
776
777 pTimeline->deleteTempoMarker( nPosition );
778 pTimeline->addTempoMarker( nPosition, fBpm );
779 pHydrogen->getAudioEngine()->handleTimelineChange();
780
781 pAudioEngine->unlock();
782
783 pHydrogen->setIsModified( true );
784
786
787 return true;
788}
789
791 auto pHydrogen = Hydrogen::get_instance();
792 auto pAudioEngine = pHydrogen->getAudioEngine();
793
794 if ( pHydrogen->getSong() == nullptr ) {
795 ERRORLOG( "no song set" );
796 return false;
797 }
798
799 pAudioEngine->lock( RIGHT_HERE );
800
801 pHydrogen->getTimeline()->deleteTempoMarker( nPosition );
802 pHydrogen->getAudioEngine()->handleTimelineChange();
803
804 pAudioEngine->unlock();
805
806 pHydrogen->setIsModified( true );
808
809 return true;
810}
811
812bool CoreActionController::addTag( int nPosition, const QString& sText ) {
813 auto pHydrogen = Hydrogen::get_instance();
814 auto pTimeline = pHydrogen->getTimeline();
815
816 if ( pHydrogen->getSong() == nullptr ) {
817 ERRORLOG( "no song set" );
818 return false;
819 }
820
821 pTimeline->deleteTag( nPosition );
822 pTimeline->addTag( nPosition, sText );
823
824 pHydrogen->setIsModified( true );
825
827
828 return true;
829}
830
831bool CoreActionController::deleteTag( int nPosition ) {
832 auto pHydrogen = Hydrogen::get_instance();
833 auto pAudioEngine = pHydrogen->getAudioEngine();
834
835 if ( pHydrogen->getSong() == nullptr ) {
836 ERRORLOG( "no song set" );
837 return false;
838 }
839
840 pHydrogen->getTimeline()->deleteTag( nPosition );
841
842 pHydrogen->setIsModified( true );
844
845 return true;
846}
847
849
850#ifdef H2CORE_HAVE_JACK
851 if ( !Hydrogen::get_instance()->hasJackAudioDriver() ) {
852 ERRORLOG( "Unable to (de)activate Jack transport. Please select the Jack driver first." );
853 return false;
854 }
855
857 if ( bActivate ) {
859 } else {
861 }
863
864 EventQueue::get_instance()->push_event( EVENT_JACK_TRANSPORT_ACTIVATION, static_cast<int>( bActivate ) );
865
866 return true;
867#else
868 ERRORLOG( "Unable to (de)activate Jack transport. Your Hydrogen version was not compiled with jack support." );
869 return false;
870#endif
871}
872
874 auto pHydrogen = Hydrogen::get_instance();
875
876#ifdef H2CORE_HAVE_JACK
877 if ( !pHydrogen->hasJackAudioDriver() ) {
878 ERRORLOG( "Unable to (de)activate JACK Timebase support. Please select the JACK driver first." );
879 return false;
880 }
881
882 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
883 if ( bActivate ) {
886 pHydrogen->initJackTimebaseControl();
887 } else {
890 pHydrogen->releaseJackTimebaseControl();
891 }
892 pHydrogen->getAudioEngine()->unlock();
893
894 return true;
895#else
896 ERRORLOG( "Unable to (de)activate JACK Timebase support. Your Hydrogen version was not compiled with JACK support." );
897 return false;
898#endif
899}
900
902
903 auto pHydrogen = Hydrogen::get_instance();
904 auto pAudioEngine = pHydrogen->getAudioEngine();
905 auto pSong = pHydrogen->getSong();
906
907 if ( pSong == nullptr ) {
908 ERRORLOG( "no song set" );
909 return false;
910 }
911
912 if ( !( bActivate && pHydrogen->getMode() != Song::Mode::Song ) &&
913 ! ( ! bActivate && pHydrogen->getMode() != Song::Mode::Pattern ) ) {
914 // No changes.
915 return true;
916 }
917
918 pHydrogen->sequencer_stop();
919
920 pAudioEngine->lock( RIGHT_HERE );
921
922 if ( bActivate && pHydrogen->getMode() != Song::Mode::Song ) {
923 pHydrogen->setMode( Song::Mode::Song );
924 }
925 else if ( ! bActivate && pHydrogen->getMode() != Song::Mode::Pattern ) {
926 pHydrogen->setMode( Song::Mode::Pattern );
927 }
928
929 pAudioEngine->handleSongModeChanged();
930
931 pAudioEngine->unlock();
932
933 return true;
934}
935
937
938 auto pHydrogen = Hydrogen::get_instance();
939 auto pSong = pHydrogen->getSong();
940 auto pAudioEngine = pHydrogen->getAudioEngine();
941
942 if ( pHydrogen->getSong() == nullptr ) {
943 ERRORLOG( "no song set" );
944 return false;
945 }
946
947
948 bool bChange = false;
949
950 if ( bActivate &&
951 pSong->getLoopMode() != Song::LoopMode::Enabled ) {
952 pSong->setLoopMode( Song::LoopMode::Enabled );
953 bChange = true;
954
955 } else if ( ! bActivate &&
956 pSong->getLoopMode() == Song::LoopMode::Enabled ) {
957 // If the transport was already looped at least once, disabling
958 // loop mode will result in immediate stop. Instead, we want to
959 // stop transport at the end of the song.
960 if ( pSong->lengthInTicks() <
961 pAudioEngine->getTransportPosition()->getTick() ) {
962 pSong->setLoopMode( Song::LoopMode::Finishing );
963 } else {
964 pSong->setLoopMode( Song::LoopMode::Disabled );
965 }
966 bChange = true;
967 }
968
969 pAudioEngine->lock( RIGHT_HERE );
970 pAudioEngine->handleLoopModeChanged();
971 pAudioEngine->unlock();
972
973 if ( bChange ) {
975 static_cast<int>( bActivate ) );
976 }
977
978 return true;
979}
980
981bool CoreActionController::setDrumkit( const QString& sDrumkit, bool bConditional ) {
982
984 ->getDrumkit( sDrumkit );
985 if ( pDrumkit == nullptr ) {
986 ERRORLOG( QString( "Drumkit [%1] could not be loaded." )
987 .arg( sDrumkit ) );
988 return false;
989 }
990
991 return setDrumkit( pDrumkit, bConditional );
992}
993
994bool CoreActionController::setDrumkit( std::shared_ptr<Drumkit> pDrumkit, bool bConditional ) {
995 if ( pDrumkit != nullptr ) {
996
997 auto pHydrogen = Hydrogen::get_instance();
998 auto pSong = pHydrogen->getSong();
999 if ( pSong != nullptr ) {
1000
1001 INFOLOG( QString( "Setting drumkit [%1] located at [%2]" )
1002 .arg( pDrumkit->get_name() )
1003 .arg( pDrumkit->get_path() ) );
1004
1005 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
1006
1007 pSong->setDrumkit( pDrumkit, bConditional );
1008
1009 if ( pHydrogen->getSelectedInstrumentNumber() >=
1010 pSong->getInstrumentList()->size() ) {
1011 pHydrogen->setSelectedInstrumentNumber(
1012 std::max( 0, pSong->getInstrumentList()->size() -1 ),
1013 false );
1014 }
1015
1016 pHydrogen->renameJackPorts( pSong );
1017
1018 pHydrogen->getAudioEngine()->unlock();
1019
1021
1022 pHydrogen->setIsModified( true );
1023
1024 // Create a symbolic link in the session folder when under session
1025 // management.
1026 if ( pHydrogen->isUnderSessionManagement() ) {
1027 pHydrogen->setSessionDrumkitNeedsRelinking( true );
1028 }
1029
1031 }
1032 else {
1033 ERRORLOG( "No song set yet" );
1034 return false;
1035 }
1036 }
1037 else {
1038 ERRORLOG( "Provided Drumkit is not valid" );
1039 return false;
1040 }
1041
1042 return true;
1043}
1044
1045bool CoreActionController::upgradeDrumkit( const QString& sDrumkitPath, const QString& sNewPath ) {
1046
1047 if ( sNewPath.isEmpty() ) {
1048 INFOLOG( QString( "Upgrading kit at [%1] inplace." )
1049 .arg( sDrumkitPath ) );
1050 } else {
1051 INFOLOG( QString( "Upgrading kit at [%1] into [%2]." )
1052 .arg( sDrumkitPath ).arg( sNewPath ) );
1053 }
1054
1055 QFileInfo sourceFileInfo( sDrumkitPath );
1056 if ( ! sNewPath.isEmpty() ) {
1057 // Check whether there is already a file or directory
1058 // present. The latter has to be writable. If none is present,
1059 // create a folder.
1060 if ( ! Filesystem::path_usable( sNewPath, true, false ) ) {
1061 return false;
1062 }
1063 } else {
1064 // We have to assure that the source folder is not just
1065 // readable since an inplace upgrade was requested
1066 if ( ! Filesystem::dir_writable( sourceFileInfo.dir().absolutePath(),
1067 true ) ) {
1068 ERRORLOG( QString( "Unable to upgrade drumkit [%1] in place: Folder is in read-only mode" )
1069 .arg( sDrumkitPath ) );
1070 return false;
1071 }
1072 }
1073
1074 QString sTemporaryFolder, sDrumkitDir;
1075 // Whether the drumkit was provided as compressed .h2drumkit file.
1076 bool bIsCompressed, bLegacyFormatEncountered;
1077 auto pDrumkit = retrieveDrumkit(
1078 sDrumkitPath, &bIsCompressed, &sDrumkitDir, &sTemporaryFolder,
1079 &bLegacyFormatEncountered );
1080
1081 if ( pDrumkit == nullptr ) {
1082 ERRORLOG( QString( "Unable to load drumkit from source path [%1]" )
1083 .arg( sDrumkitPath ) );
1084 return false;
1085 }
1086
1087 // If the drumkit is not updated inplace, we also need to copy
1088 // all samples and metadata, like images.
1089 QString sPath;
1090 if ( ! sNewPath.isEmpty() ) {
1091
1092 // When dealing with a compressed drumkit, we can just leave
1093 // it in the temporary folder and copy the compressed content
1094 // to the destination right away.
1095 if ( ! bIsCompressed ) {
1096 // Copy content
1097 QDir drumkitDir( sDrumkitDir );
1098 for ( const auto& ssFile : drumkitDir.entryList( QDir::Files ) ) {
1099
1100 // We handle the drumkit file later
1101 if ( ssFile.contains( ".xml" ) ) {
1102 continue;
1103 }
1104 Filesystem::file_copy( drumkitDir.absolutePath() + "/" + ssFile,
1105 sNewPath + "/" + ssFile, true, true );
1106 }
1107 sPath = sNewPath;
1108 } else {
1109 sPath = sDrumkitDir;
1110 }
1111
1112 } else {
1113 // Upgrade inplace.
1114
1115 if ( ! bIsCompressed ) {
1116 // Make a backup of the original file in order to make the
1117 // upgrade reversible.
1118 QString sBackupPath =
1120 if ( ! Filesystem::file_copy( Filesystem::drumkit_file( sDrumkitDir ),
1121 sBackupPath, true, true ) ) {
1122 ERRORLOG( QString( "Unable to backup source drumkit XML file from [%1] to [%2]. We abort instead of overwriting things." )
1123 .arg( Filesystem::drumkit_file( sDrumkitDir ) )
1124 .arg( sBackupPath ) );
1125 return false;
1126 }
1127 } else {
1128 QString sBackupPath = Filesystem::drumkit_backup_path( sDrumkitPath );
1129 if ( ! Filesystem::file_copy( sDrumkitPath, sBackupPath, true, true ) ) {
1130 ERRORLOG( QString( "Unable to backup source .h2drumkit file from [%1] to [%2]. We abort instead of overwriting things." )
1131 .arg( sDrumkitPath ).arg( sBackupPath ) );
1132 return false;
1133 }
1134 }
1135
1136 sPath = sDrumkitDir;
1137 }
1138
1139 if ( ! pDrumkit->save( sPath, -1, true, true ) ) {
1140 ERRORLOG( QString( "Error while saving upgraded kit to [%1]" )
1141 .arg( sPath ) );
1142 return false;
1143 }
1144
1145 // Compress the updated drumkit again in order to provide the same
1146 // format handed over as input.
1147 if ( bIsCompressed ) {
1148 QString sExportPath;
1149 if ( ! sNewPath.isEmpty() ) {
1150 sExportPath = sNewPath;
1151 } else {
1152 sExportPath = sourceFileInfo.dir().absolutePath();
1153 }
1154
1155 if ( ! pDrumkit->exportTo( sExportPath, "", true, nullptr, false ) ) {
1156 ERRORLOG( QString( "Unable to export upgrade drumkit to [%1]" )
1157 .arg( sExportPath ) );
1158 return false;
1159 }
1160
1161 INFOLOG( QString( "Upgraded drumkit exported as [%1]" )
1162 .arg( sExportPath + "/" + pDrumkit->get_name() +
1164 }
1165
1166 // Upgrade was successful. Cleanup
1167 if ( ! sTemporaryFolder.isEmpty() ) {
1168 // Filesystem::rm( sTemporaryFolder, true, true );
1169 }
1170
1171 INFOLOG( QString( "Drumkit [%1] successfully upgraded!" )
1172 .arg( sDrumkitPath ) );
1173
1174 return true;
1175}
1176
1177bool CoreActionController::validateDrumkit( const QString& sDrumkitPath, bool bCheckLegacyVersions ) {
1178
1179 INFOLOG( QString( "Validating kit [%1]" ).arg( sDrumkitPath ) );
1180
1181 QString sTemporaryFolder, sDrumkitDir;
1182 // Whether the drumkit was provided as compressed .h2drumkit file.
1183 bool bIsCompressed, bLegacyFormatEncountered;
1184 const auto pDrumkit = retrieveDrumkit(
1185 sDrumkitPath, &bIsCompressed, &sDrumkitDir, &sTemporaryFolder,
1186 &bLegacyFormatEncountered );
1187
1188 if ( pDrumkit == nullptr ) {
1189 ERRORLOG( QString( "Unable to load drumkit from source path [%1]" )
1190 .arg( sDrumkitPath ) );
1191 return false;
1192 }
1193
1194 if ( ! Filesystem::drumkit_valid( sDrumkitDir ) ) {
1195 ERRORLOG( QString( "Something went wrong in the drumkit retrieval of [%1]. Unable to load from [%2]" )
1196 .arg( sDrumkitPath ).arg( sDrumkitDir ) );
1197 return false;
1198 }
1199
1200 XMLDoc doc;
1201 if ( !doc.read( Filesystem::drumkit_file( sDrumkitDir ), true ) ) {
1202 ERRORLOG( QString( "Drumkit XML file [%1] can not be parsed." )
1203 .arg( Filesystem::drumkit_file( sDrumkitDir ) ) );
1204 return false;
1205 }
1206
1207 XMLNode root = doc.firstChildElement( "drumkit_info" );
1208 if ( root.isNull() ) {
1209 ERRORLOG( QString( "Drumkit file [%1] seems bricked: 'drumkit_info' node not found" )
1210 .arg( Filesystem::drumkit_file( sDrumkitDir ) ) );
1211 return false;
1212 }
1213
1214 if ( bLegacyFormatEncountered && ! bCheckLegacyVersions ) {
1215 ERRORLOG( QString( "Drumkit [%1] uses a legacy format" )
1216 .arg( sDrumkitPath ) );
1217 return false;
1218 }
1219
1220 INFOLOG( QString( "Drumkit [%1] is valid!" )
1221 .arg( sDrumkitPath ) );
1222
1223 return true;
1224}
1225
1227 const QString& sDrumkitPath,
1228 bool* bIsCompressed,
1229 QString *sDrumkitDir,
1230 QString* sTemporaryFolder,
1231 bool* pLegacyFormatEncountered )
1232{
1233
1234 std::shared_ptr<Drumkit> pDrumkit = nullptr;
1235
1236 // We do not attempt to retrieve the drumkit from disk since this
1237 // function is intended to be used for validating or upgrading
1238 // drumkits via CLI or OSC command. It should always refer to the
1239 // latest copy found on disk.
1240 if ( bIsCompressed == nullptr || sTemporaryFolder == nullptr ||
1241 sDrumkitDir == nullptr || pLegacyFormatEncountered == nullptr ) {
1242 ERRORLOG( "Invalid input" );
1243 return nullptr;
1244 }
1245
1246 *bIsCompressed = false;
1247 *sTemporaryFolder = "";
1248 *sDrumkitDir = "";
1249 *pLegacyFormatEncountered = false;
1250
1251 QFileInfo sourceFileInfo( sDrumkitPath );
1252
1253 if ( Filesystem::dir_readable( sDrumkitPath, true ) ) {
1254
1255 // Providing the folder containing the drumkit
1256 pDrumkit = Drumkit::load(
1257 sDrumkitPath, false, pLegacyFormatEncountered, true );
1258 *sDrumkitDir = sDrumkitPath;
1259
1260 }
1261 else if ( sourceFileInfo.fileName() == Filesystem::drumkit_xml() ) {
1262 if ( ! Filesystem::file_readable( sDrumkitPath, true ) ) {
1263 ERRORLOG( QString( "Drumkit file [%1] not readable" )
1264 .arg( sDrumkitPath ) );
1265 return nullptr;
1266 }
1267
1268 // Providing the path of a drumkit.xml file within a drumkit
1269 // folder.
1270 QString sDrumkitDirPath = QFileInfo( sDrumkitPath ).absoluteDir().absolutePath();
1271 pDrumkit = Drumkit::load(
1272 sDrumkitDirPath, false, pLegacyFormatEncountered, true );
1273 *sDrumkitDir = sourceFileInfo.dir().absolutePath();
1274
1275 }
1276 else if ( ( "." + sourceFileInfo.suffix() ) == Filesystem::drumkit_ext ) {
1277 if ( ! Filesystem::file_readable( sDrumkitPath, true ) ) {
1278 ERRORLOG( QString( "Drumkit archive [%1] not readable" )
1279 .arg( sDrumkitPath ) );
1280 return nullptr;
1281 }
1282
1283 *bIsCompressed = true;
1284
1285 // Temporary folder used to extract a compressed drumkit (
1286 // .h2drumkit ).
1287 QString sTemplateName( Filesystem::tmp_dir() + "/XXXXXX" );
1288 QTemporaryDir tmpDir( sTemplateName );
1289 tmpDir.setAutoRemove( false );
1290 if ( ! tmpDir.isValid() ) {
1291 ERRORLOG( QString( "Unable to create temporary folder using template name [%1]" )
1292 .arg( sTemplateName ) );
1293 return nullptr;
1294 }
1295
1296 *sTemporaryFolder = tmpDir.path();
1297
1298 // Providing the path to a compressed .h2drumkit file. It will
1299 // be extracted to a temporary folder and loaded from there.
1300 if ( ! Drumkit::install( sDrumkitPath, tmpDir.path(), sDrumkitDir,
1301 nullptr, true ) ) {
1302 ERRORLOG( QString( "Unabled to extract provided drumkit [%1] into [%2]" )
1303 .arg( sDrumkitPath ).arg( tmpDir.path() ) );
1304 return nullptr;
1305 }
1306
1307 // INFOLOG( QString( "Extracting drumkit [%1] into [%2]" )
1308 // .arg( sDrumkitPath ).arg( tmpDir.path() ) );
1309
1310 // The extracted folder is expected to contain a single
1311 // directory named as the drumkit itself. But some kits
1312 // deviate from the latter condition. So, we just use the
1313 // former one.
1314 QDir extractedDir( tmpDir.path() );
1315 QStringList extractedContent =
1316 extractedDir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot );
1317 QStringList extractedFolders =
1318 extractedDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
1319 if ( ( extractedContent.size() != extractedFolders.size() ) ||
1320 ( extractedFolders.size() != 1 ) ) {
1321 ERRORLOG( QString( "Unsupported content of [%1]. Expected a single folder within the archive containing all samples, metadata, as well as the drumkit.xml file. Instead:\n" )
1322 .arg( sDrumkitPath ) );
1323 for ( const auto& sFile : extractedContent ) {
1324 ERRORLOG( sFile );
1325 }
1326 return nullptr;
1327 }
1328
1329 pDrumkit = Drumkit::load(
1330 *sDrumkitDir, false, pLegacyFormatEncountered, true );
1331
1332 } else {
1333 ERRORLOG( QString( "Provided source path [%1] does not point to a Hydrogen drumkit" )
1334 .arg( sDrumkitPath ) );
1335 return nullptr;
1336 }
1337
1338 return pDrumkit;
1339}
1340
1341bool CoreActionController::extractDrumkit( const QString& sDrumkitPath,
1342 const QString& sTargetDir,
1343 QString* pInstalledPath,
1344 bool* pEncodingIssuesDetected ) {
1345 // Ensure variables are always set/initialized.
1346 if ( pInstalledPath != nullptr ) {
1347 *pInstalledPath = "";
1348 }
1349 if ( pEncodingIssuesDetected != nullptr ) {
1350 *pEncodingIssuesDetected = false;
1351 }
1352
1353 QString sTarget;
1354 bool bInstall = false;
1355 if ( sTargetDir.isEmpty() ) {
1356 bInstall = true;
1357 INFOLOG( QString( "Installing drumkit [%1]" ).arg( sDrumkitPath ) );
1358 sTarget = Filesystem::usr_drumkits_dir();
1359 } else {
1360 INFOLOG( QString( "Extracting drumkit [%1] to [%2]" )
1361 .arg( sDrumkitPath ).arg( sTargetDir ) );
1362 sTarget = sTargetDir;
1363 }
1364
1365 if ( ! Filesystem::path_usable( sTarget, true, false ) ) {
1366 ERRORLOG( QString( "Target dir [%1] is neither a writable folder nor can it be created." )
1367 .arg( sTarget ) );
1368 return false;
1369 }
1370
1371 QFileInfo sKitInfo( sDrumkitPath );
1372 if ( ! Filesystem::file_readable( sDrumkitPath, true ) ||
1373 "." + sKitInfo.suffix() != Filesystem::drumkit_ext ) {
1374 ERRORLOG( QString( "Invalid drumkit path [%1]. Please provide an absolute path to a .h2drumkit file." )
1375 .arg( sDrumkitPath ) );
1376 return false;
1377 }
1378
1379 if ( ! Drumkit::install( sDrumkitPath, sTarget, pInstalledPath,
1380 pEncodingIssuesDetected, true ) ) {
1381 ERRORLOG( QString( "Unabled to extract provided drumkit [%1] into [%2]" )
1382 .arg( sDrumkitPath ).arg( sTarget ) );
1383 return false;
1384 }
1385
1386 if ( bInstall ) {
1388 }
1389
1390 return true;
1391}
1392
1393bool CoreActionController::locateToColumn( int nPatternGroup ) {
1394
1395 if ( nPatternGroup < -1 ) {
1396 ERRORLOG( QString( "Provided column [%1] too low. Assigning 0 instead." )
1397 .arg( nPatternGroup ) );
1398 nPatternGroup = 0;
1399 }
1400
1401 auto pHydrogen = Hydrogen::get_instance();
1402 if ( pHydrogen->getSong() == nullptr ) {
1403 ERRORLOG( "no song set" );
1404 return false;
1405 }
1406
1407 long nTotalTick = pHydrogen->getTickForColumn( nPatternGroup );
1408 if ( nTotalTick < 0 ) {
1409 if ( pHydrogen->getMode() == Song::Mode::Song ) {
1410 ERRORLOG( QString( "Provided column [%1] violates the allowed range [0;%2). No relocation done." )
1411 .arg( nPatternGroup )
1412 .arg( pHydrogen->getSong()->getPatternGroupVector()->size() ) );
1413 return false;
1414 } else {
1415 // In case of Pattern mode this is not a problem and we
1416 // will treat this case as the beginning of the song.
1417 nTotalTick = 0;
1418 }
1419 }
1420
1421 return locateToTick( nTotalTick );
1422}
1423
1424bool CoreActionController::locateToTick( long nTick, bool bWithJackBroadcast ) {
1425
1426 const auto pHydrogen = Hydrogen::get_instance();
1427 auto pAudioEngine = pHydrogen->getAudioEngine();
1428
1429 if ( pHydrogen->getSong() == nullptr ) {
1430 ERRORLOG( "no song set" );
1431 return false;
1432 }
1433
1434 pAudioEngine->lock( RIGHT_HERE );
1435
1436 pAudioEngine->locate( nTick, bWithJackBroadcast );
1437
1438 pAudioEngine->unlock();
1439
1441 return true;
1442}
1443
1444bool CoreActionController::newPattern( const QString& sPatternName ) {
1445 auto pPatternList = Hydrogen::get_instance()->getSong()->getPatternList();
1446 Pattern* pPattern = new Pattern( sPatternName );
1447
1448 return setPattern( pPattern, pPatternList->size() );
1449}
1450bool CoreActionController::openPattern( const QString& sPath, int nPatternPosition ) {
1451 auto pHydrogen = Hydrogen::get_instance();
1452 auto pSong = pHydrogen->getSong();
1453
1454 if ( pHydrogen->getSong() == nullptr ) {
1455 ERRORLOG( "no song set" );
1456 return false;
1457 }
1458
1459 auto pPatternList = pSong->getPatternList();
1460 Pattern* pNewPattern = Pattern::load_file( sPath, pSong->getInstrumentList() );
1461
1462 if ( pNewPattern == nullptr ) {
1463 ERRORLOG( QString( "Unable to loading the pattern [%1]" ).arg( sPath ) );
1464 return false;
1465 }
1466
1467 if ( nPatternPosition == -1 ) {
1468 nPatternPosition = pPatternList->size();
1469 }
1470
1471 return setPattern( pNewPattern, nPatternPosition );
1472}
1473
1474bool CoreActionController::setPattern( Pattern* pPattern, int nPatternPosition ) {
1475 auto pHydrogen = Hydrogen::get_instance();
1476
1477 if ( pHydrogen->getSong() == nullptr ) {
1478 ERRORLOG( "no song set" );
1479 return false;
1480 }
1481
1482 auto pPatternList = pHydrogen->getSong()->getPatternList();
1483
1484 // Check whether the name of the new pattern is unique.
1485 if ( !pPatternList->check_name( pPattern->get_name() ) ){
1486 pPattern->set_name( pPatternList->find_unused_pattern_name( pPattern->get_name() ) );
1487 }
1488
1489 pPatternList->insert( nPatternPosition, pPattern );
1490 if ( pHydrogen->isPatternEditorLocked() ) {
1491 pHydrogen->updateSelectedPattern( true );
1492 } else {
1493 pHydrogen->setSelectedPatternNumber( nPatternPosition );
1494 }
1495 pHydrogen->setIsModified( true );
1496
1497 // Update the SongEditor.
1498 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
1500 }
1501 return true;
1502}
1503
1504bool CoreActionController::removePattern( int nPatternNumber ) {
1505 auto pHydrogen = Hydrogen::get_instance();
1506 auto pAudioEngine = pHydrogen->getAudioEngine();
1507 auto pSong = pHydrogen->getSong();
1508
1509
1510 if ( pSong == nullptr ) {
1511 ERRORLOG( "no song set" );
1512 return false;
1513 }
1514
1515 INFOLOG( QString( "Deleting pattern [%1]" ).arg( nPatternNumber ) );
1516
1517 auto pPatternList = pSong->getPatternList();
1518 auto pPatternGroupVector = pSong->getPatternGroupVector();
1519 auto pPlayingPatterns = pAudioEngine->getPlayingPatterns();
1520 auto pNextPatterns = pAudioEngine->getNextPatterns();
1521
1522 int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber();
1523 auto pPattern = pPatternList->get( nPatternNumber );
1524
1525 if ( pPattern == nullptr ) {
1526 ERRORLOG( QString( "Pattern [%1] not found" ).arg( nPatternNumber ) );
1527 return false;
1528 }
1529
1530 pAudioEngine->lock( RIGHT_HERE );
1531
1532 // Ensure there is always at least one pattern present in the
1533 // list.
1534 if ( pPatternList->size() == 0 ) {
1535 Pattern* pEmptyPattern = new Pattern( "Pattern 1" );
1536 pPatternList->add( pEmptyPattern );
1537 }
1538
1539 // Delete all instances of the pattern in the pattern group vector
1540 // (columns of the SongEditor)
1541 for ( const auto& ppatternList : *pPatternGroupVector ) {
1542 for ( int ii = 0; ii < ppatternList->size(); ++ii ) {
1543 if ( ppatternList->get( ii ) == pPattern ) {
1544 ppatternList->del( ii );
1545 // there is at most one instance of a pattern per
1546 // column.
1547 continue;
1548 }
1549 }
1550 }
1551
1552 PatternList* pColumn;
1553 // Ensure there are no empty columns in the pattern group vector.
1554 for ( int ii = pPatternGroupVector->size() - 1; ii >= 0; --ii ) {
1555 pColumn = pPatternGroupVector->at( ii );
1556 if ( pColumn->size() == 0 ) {
1557 pPatternGroupVector->erase( pPatternGroupVector->begin() + ii );
1558 delete pColumn;
1559 }
1560 else {
1561 break;
1562 }
1563 }
1564
1565 if ( pHydrogen->isPatternEditorLocked() ) {
1566 pHydrogen->updateSelectedPattern( false );
1567 } else if ( nPatternNumber == nSelectedPatternNumber ) {
1568 pHydrogen->setSelectedPatternNumber( std::max( 0, nPatternNumber - 1 ),
1569 false );
1570 }
1571
1572 // Remove the pattern from the list of of patterns that are played
1573 // next in pattern mode.
1574 // IMPORTANT: it has to be removed from the next patterns list
1575 // _before_ updating the playing patterns.
1576 for ( int ii = 0; ii < pNextPatterns->size(); ++ii ) {
1577 if ( pNextPatterns->get( ii ) == pPattern ) {
1578 pAudioEngine->toggleNextPattern( nPatternNumber );
1579 }
1580 }
1581
1582 // Ensure the pattern is not among the list of currently played
1583 // patterns cached in the audio engine if transport is in pattern
1584 // mode.
1585 pAudioEngine->removePlayingPattern( pPattern );
1586
1587 // Delete the pattern from the list of available patterns.
1588 pPatternList->del( pPattern );
1589
1590 pHydrogen->updateSongSize();
1591
1592 pAudioEngine->unlock();
1593
1594 // Update virtual pattern presentation.
1595 for ( const auto& ppattern : *pPatternList ) {
1596
1598 ppattern->get_virtual_patterns()->find( pPattern );
1599 if ( it != ppattern->get_virtual_patterns()->end() ) {
1600 ppattern->virtual_patterns_del( *it );
1601 }
1602 }
1603
1604 pHydrogen->updateVirtualPatterns();
1605 pHydrogen->setIsModified( true );
1606
1607 delete pPattern;
1608
1609 return true;
1610}
1611
1613 int nPatternNumber ) {
1614
1615 Hydrogen* pHydrogen = Hydrogen::get_instance();
1616 auto pSong = pHydrogen->getSong();
1617 if ( pSong == nullptr ) {
1618 ERRORLOG( "no song set" );
1619 return false;
1620 }
1621
1622 int nPattern;
1623 if ( nPatternNumber != -1 ) {
1624 nPattern = nPatternNumber;
1625 } else {
1626 nPattern = pHydrogen->getSelectedPatternNumber();
1627 }
1628
1629 auto pPattern = pSong->getPatternList()->get( nPattern );
1630 if ( pPattern == nullptr ) {
1631 ERRORLOG( QString( "Couldn't find pattern [%1]" ).arg( nPattern ) );
1632 return false;
1633 }
1634
1635 auto pInstrument = pSong->getInstrumentList()->get( nInstrument );
1636 if ( pInstrument == nullptr ) {
1637 ERRORLOG( QString( "Couldn't find instrument [%1]" ).arg( nInstrument ) );
1638 return false;
1639 }
1640
1641 pPattern->purge_instrument( pInstrument, true );
1642
1643 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
1645 }
1646
1647 return true;
1648}
1649
1650bool CoreActionController::toggleGridCell( int nColumn, int nRow ){
1651 auto pHydrogen = Hydrogen::get_instance();
1652
1653 if ( pHydrogen->getSong() == nullptr ) {
1654 ERRORLOG( "no song set" );
1655 return false;
1656 }
1657
1658 auto pSong = pHydrogen->getSong();
1659 auto pAudioEngine = pHydrogen->getAudioEngine();
1660 auto pPatternList = pSong->getPatternList();
1661 std::vector<PatternList*>* pColumns = pSong->getPatternGroupVector();
1662
1663 if ( nRow < 0 || nRow > pPatternList->size() ) {
1664 ERRORLOG( QString( "Provided row [%1] is out of bound [0,%2]" )
1665 .arg( nRow ).arg( pPatternList->size() ) );
1666 return false;
1667 }
1668
1669 auto pNewPattern = pPatternList->get( nRow );
1670 if ( pNewPattern == nullptr ) {
1671 ERRORLOG( QString( "Unable to obtain Pattern in row [%1]." )
1672 .arg( nRow ) );
1673
1674 return false;
1675 }
1676
1677 pAudioEngine->lock( RIGHT_HERE );
1678 if ( nColumn >= 0 && nColumn < pColumns->size() ) {
1679 PatternList *pColumn = ( *pColumns )[ nColumn ];
1680 auto pPattern = pColumn->del( pNewPattern );
1681 if ( pPattern == nullptr ) {
1682 // No pattern in this row. Let's add it.
1683 pColumn->add( pNewPattern );
1684 } else {
1685 // There was already a pattern present and we removed it.
1686 // Ensure that there are no empty columns at the end of
1687 // the song.
1688 for ( int ii = pColumns->size() - 1; ii >= 0; ii-- ) {
1689 PatternList *pColumn = ( *pColumns )[ ii ];
1690 if ( pColumn->size() == 0 ) {
1691 pColumns->erase( pColumns->begin() + ii );
1692 delete pColumn;
1693 } else {
1694 break;
1695 }
1696 }
1697 }
1698 }
1699 else if ( nColumn >= pColumns->size() ) {
1700 // We need to add some new columns..
1701 PatternList *pColumn;
1702
1703 for ( int ii = 0; nColumn - pColumns->size() + 1; ii++ ) {
1704 pColumn = new PatternList();
1705 pColumns->push_back( pColumn );
1706 }
1707 pColumn->add( pNewPattern );
1708 }
1709 else {
1710 // nColumn < 0
1711 ERRORLOG( QString( "Provided column [%1] is out of bound [0,%2]" )
1712 .arg( nColumn ).arg( pColumns->size() ) );
1713 pAudioEngine->unlock();
1714 return false;
1715 }
1716
1717 pHydrogen->updateSongSize();
1718 pHydrogen->updateSelectedPattern( false );
1719
1720 pAudioEngine->unlock();
1721
1722 pHydrogen->setIsModified( true );
1723
1724 // Update the SongEditor.
1725 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
1727 }
1728
1729 return true;
1730}
1731
1732bool CoreActionController::handleNote( int nNote, float fVelocity, bool bNoteOff ) {
1733 auto pPref = Preferences::get_instance();
1734 auto pHydrogen = Hydrogen::get_instance();
1735 auto pSong = pHydrogen->getSong();
1736 if ( pSong == nullptr ) {
1737 ERRORLOG( "no song set" );
1738 return false;
1739 }
1740
1741 std::shared_ptr<Instrument> pInstrument = nullptr;
1742 int nInstrument = 0;
1743 QString sMode;
1744
1745 auto pInstrumentList = pSong->getInstrumentList();
1746
1747 if ( pPref->__playselectedinstrument ){
1748 nInstrument = pHydrogen->getSelectedInstrumentNumber();
1749 pInstrument = pInstrumentList->get( pHydrogen->getSelectedInstrumentNumber());
1750 if ( pInstrument == nullptr ) {
1751 WARNINGLOG( "No instrument selected!" );
1752 return false;
1753 }
1754 sMode = "Play Selected Instrument";
1755 }
1756 else if ( pPref->m_bMidiFixedMapping ){
1757 pInstrument = pInstrumentList->findMidiNote( nNote );
1758 if ( pInstrument == nullptr ) {
1759 WARNINGLOG( QString( "Unable to map note [%1] to instrument" )
1760 .arg( nNote ) );
1761 return false;
1762 }
1763 nInstrument = pInstrumentList->index( pInstrument );
1764 sMode = "Map to Output MIDI note";
1765 }
1766 else {
1767 nInstrument = nNote - MidiMessage::instrumentOffset;
1768 if( nInstrument < 0 || nInstrument >= pInstrumentList->size()) {
1769 WARNINGLOG( QString( "Instrument number [%1] - derived from note [%2] - out of bound note [%3,%4]" )
1770 .arg( nInstrument ).arg( nNote )
1771 .arg( 0 ).arg( pInstrumentList->size() ) );
1772 return false;
1773 }
1774
1775 pInstrument = pInstrumentList->get( nInstrument );
1776 if ( pInstrument == nullptr ) {
1777 WARNINGLOG( QString( "Unable to retrieve instrument [%1]" )
1778 .arg( nInstrument ) );
1779 return false;
1780 }
1781 sMode = "Map to instrument list position";
1782 }
1783
1784
1785 // Only look to change instrument if the current note is actually of hihat
1786 // and hihat openness is outside the instrument selected
1787 const int nHihatOpenness = pHydrogen->getHihatOpenness();
1788 if ( pInstrument != nullptr &&
1789 pInstrument->get_hihat_grp() >= 0 &&
1790 ( nHihatOpenness < pInstrument->get_lower_cc() ||
1791 nHihatOpenness > pInstrument->get_higher_cc() ) ) {
1792
1793 for ( int i = 0; i <= pInstrumentList->size(); i++ ) {
1794 auto ppInstr = pInstrumentList->get( i );
1795 if ( ppInstr != nullptr &&
1796 pInstrument->get_hihat_grp() == ppInstr->get_hihat_grp() &&
1797 nHihatOpenness >= ppInstr->get_lower_cc() &&
1798 nHihatOpenness <= ppInstr->get_higher_cc() ) {
1799
1800 nInstrument = i;
1801 sMode = "Hihat Pressure Group";
1802 break;
1803 }
1804 }
1805 }
1806
1807 INFOLOG( QString( "[%1] mapped note [%2] to instrument [%3]" )
1808 .arg( sMode ).arg( nNote ).arg( nInstrument ) );
1809
1810 return pHydrogen->addRealtimeNote( nInstrument, fVelocity, bNoteOff, nNote );
1811}
1812
1814 auto pPref = Preferences::get_instance();
1815 auto pHydrogen = Hydrogen::get_instance();
1816 auto pAudioEngine = pHydrogen->getAudioEngine();
1817
1818 pAudioEngine->getMetronomeInstrument()->set_volume(
1819 pPref->m_fMetronomeVolume );
1820
1821 // If the GUI is active, we have to update it to reflect the
1822 // changes in the preferences.
1823 if ( pHydrogen->getGUIState() == H2Core::Hydrogen::GUIState::ready ) {
1825 }
1826}
1827
1828void CoreActionController::insertRecentFile( const QString sFilename ){
1829
1830 auto pPref = Preferences::get_instance();
1831
1832 // The most recent file will always be added on top and possible
1833 // duplicates are removed later on.
1834 bool bAlreadyContained = false;
1835
1836 std::vector<QString> recentFiles = pPref->getRecentFiles();
1837
1838 // We have to normalize directory separators. Else opening a
1839 // song via double click from file browser and from within
1840 // Hydrogen will give to distinct entries on Windows.
1841 const QString sFilenameCleaned = QDir::cleanPath( sFilename );
1842
1843 recentFiles.insert( recentFiles.begin(), sFilenameCleaned );
1844
1845 if ( std::find( recentFiles.begin(), recentFiles.end(),
1846 sFilenameCleaned ) != recentFiles.end() ) {
1847 // Eliminate all duplicates in the list while keeping the one
1848 // inserted at the beginning. Also, in case the file got renamed,
1849 // remove it's previous name from the list.
1850 std::vector<QString> sTmpVec;
1851 for ( const auto& ssFilename : recentFiles ) {
1852 if ( std::find( sTmpVec.begin(), sTmpVec.end(), ssFilename ) ==
1853 sTmpVec.end() ) {
1854 // Particular file is not contained yet.
1855 sTmpVec.push_back( ssFilename );
1856 }
1857 }
1858
1859 recentFiles = sTmpVec;
1860 }
1861
1862 pPref->setRecentFiles( recentFiles );
1863}
1864}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
#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.
void unlock()
Mutex unlocking of the AudioEngine.
void lock(const char *file, unsigned int line, const char *function)
Mutex locking of the AudioEngine.
bool locateToColumn(int nPatternGroup)
Relocates transport to the beginning of a particular column/Pattern group.
bool setPattern(Pattern *pPattern, int nPatternNumber)
Opens a pattern to the current pattern list.
bool clearInstrumentInPattern(int nInstrumentNumber, int nPatternNumber=-1)
Deletes all notes for instrument pInstrument in a specified pattern.
bool addTag(int nPosition, const QString &sText)
Adds a tag to the Timeline.
bool setSong(std::shared_ptr< Song > pSong, bool bRelinking=true)
Sets a H2Core::Song to be used by Hydrogen.
void updatePreferences()
In case a different preferences file was loaded with Hydrogen already fully set up this function refr...
bool openSong(const QString &songPath, const QString &sRecoverSongPath="")
Opens the H2Core::Song specified in songPath.
bool locateToTick(long nTick, bool bWithJackBroadcast=true)
Relocates transport to a particular tick.
bool handleNote(int nNote, float fVelocity, bool bNoteOff=false)
Handle an incoming note event, e.g.
bool deleteTempoMarker(int nPosition)
Delete a tempo marker from the Timeline.
std::shared_ptr< Instrument > getStrip(int nStrip) const
bool newPattern(const QString &sPatternName)
Creates an empty pattern and adds it to the pattern list.
bool newSong(const QString &songPath)
Create an empty H2Core::Song, which will be stored in songPath.
bool savePreferences()
Saves the current state of the H2Core::Preferences.
bool extractDrumkit(const QString &sDrumkitPath, const QString &sTargetDir="", QString *pInstalledPath=nullptr, bool *pEncodingIssuesDetected=nullptr)
Extracts the compressed .h2drumkit file in sDrumkitPath into sTargetDir.
bool setStripVolume(int nStrip, float fVolumeValue, bool bSelectStrip)
bool activateSongMode(bool bActivate)
Switches between Song and Pattern mode of playback.
bool validateDrumkit(const QString &sDrumkitPath, bool bCheckLegacyVersions=false)
Checks whether the provided drumkit in sDrumkitPath can be found, can be loaded, and does comply with...
bool setDrumkit(const QString &sDrumkit, bool bConditional=true)
Wrapper around setDrumkit() that allows loading drumkits by name or path.
bool setInstrumentPitch(int nInstrument, float fValue)
bool setStripPanSym(int nStrip, float fValue, bool bSelectStrip)
bool toggleGridCell(int nColumn, int nRow)
Fills or clears a specific grid cell in the SongEditor.
bool removePattern(int nPatternNumber)
Removes a pattern from the pattern list.
bool activateLoopMode(bool bActivate)
Toggle loop mode of playback.
std::shared_ptr< Drumkit > retrieveDrumkit(const QString &sDrumkitPath, bool *bIsCompressed, QString *sDrumkitDir, QString *sTemporaryFolder, bool *pLegacyFormatEncountered)
Loads the drumkit specified in sDrumkitPath.
bool upgradeDrumkit(const QString &sDrumkitPath, const QString &sNewPath="")
Upgrades the drumkit found at absolute path sDrumkitPath.
bool activateJackTimebaseControl(bool bActivate)
(Un)registers Hydrogen as JACK Timebaes constroller.
void insertRecentFile(const QString sFilename)
Add sFilename to the list of recent songs in Preferences::m_recentFiles.
bool activateJackTransport(bool bActivate)
(De)activates the usage of Jack transport.
bool setStripIsMuted(int nStrip, bool isMuted)
bool setMasterVolume(float masterVolumeValue)
bool activateTimeline(bool bActivate)
(De)activates the usage of the Timeline.
bool setStripIsSoloed(int nStrip, bool isSoloed)
bool saveSongAs(const QString &sNewFilename)
Saves the current H2Core::Song to the path provided in sNewFilename.
bool saveSong()
Saves the current H2Core::Song.
bool deleteTag(int nPosition)
Delete a tag from the Timeline.
bool setStripPan(int nStrip, float fValue, bool bSelectStrip)
bool quit()
Triggers the shutdown of Hydrogen.
bool addTempoMarker(int nPosition, float fBpm)
Adds a tempo marker to the Timeline.
bool openPattern(const QString &sPath, int nPatternNumber=-1)
Opens a pattern from disk and adds it to the pattern list.
bool handleOutgoingControlChanges(std::vector< int > params, int nValue)
static bool install(const QString &sSourcePath, const QString &sTargetPath="", QString *pInstalledPath=nullptr, bool *pEncodingIssuesDetected=nullptr, bool bSilent=false)
Extract a .h2drumkit file.
Definition Drumkit.cpp:721
static std::shared_ptr< Drumkit > load(const QString &dk_dir, bool bUpgrade=true, bool *pLegacyFormatEncountered=nullptr, bool bSilent=false)
Load drumkit information from a directory.
Definition Drumkit.cpp:94
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.
static bool file_copy(const QString &src, const QString &dst, bool overwrite=false, bool bSilent=false)
copy a source file to a destination
static bool dir_readable(const QString &path, bool silent=false)
returns true if the given path is a readable regular directory
static bool isSongPathValid(const QString &sSongPath, bool bCheckExistance=false)
Checks the path pointing to a .h2song.
static bool drumkit_valid(const QString &dk_path)
returns true if the path contains a usable drumkit
static QString usr_drumkits_dir()
returns user drumkits path
static QString drumkit_backup_path(const QString &dk_path)
Create a backup path from a drumkit path.
static bool dir_writable(const QString &path, bool silent=false)
returns true if the given path is a writable regular directory
static QString empty_song_path()
Provides the full path to the current empty song.
static QString drumkit_file(const QString &dk_path)
returns the path to the xml file within a supposed drumkit path
static QString tmp_dir()
returns temp path
static const QString drumkit_ext
Definition Filesystem.h:121
static bool path_usable(const QString &path, bool create=true, bool silent=false)
returns true if the path is a readable and writable regular directory, create if it not exists
static QString drumkit_xml()
Returns filename and extension of the expected drumkit file.
static bool file_readable(const QString &path, bool silent=false)
returns true if the given path is an existing readable regular file
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
@ unavailable
No GUI available.
Definition Hydrogen.h:403
@ ready
There is a working GUI.
Definition Hydrogen.h:405
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
void setSelectedInstrumentNumber(int nInstrument, bool bTriggerEvent=true)
Definition Hydrogen.cpp:944
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
MidiOutput * getMidiOutput() const
Definition Hydrogen.cpp:752
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
SoundLibraryDatabase * getSoundLibraryDatabase() const
Definition Hydrogen.h:95
GUIState getGUIState() const
Definition Hydrogen.h:667
@ Listener
An external program is Timebase controller and Hydrogen will disregard all tempo markers on the Timel...
static constexpr int instrumentOffset
When recording notes using MIDI NOTE_ON events this offset will be applied to the provided pitch in o...
Definition MidiCommon.h:91
MIDI input base class.
Definition MidiOutput.h:41
virtual void handleOutgoingControlChange(int param, int value, int channel)=0
PatternList is a collection of patterns.
Definition PatternList.h:43
void add(Pattern *pattern, bool bAddVirtuals=false)
add a pattern to the list
Pattern * del(int idx)
remove the pattern at a given index, does not delete it
int size() const
returns the numbers of patterns
Pattern class is a Note container.
Definition Pattern.h:46
void set_name(const QString &name)
get the name of the pattern
Definition Pattern.h:305
const QString & get_name() const
set the category of the pattern
Definition Pattern.h:310
static Pattern * load_file(const QString &sPpatternPath, std::shared_ptr< InstrumentList > pInstruments, bool bSilent=false)
load a pattern from a file
Definition Pattern.cpp:66
virtual_patterns_t::const_iterator virtual_patterns_cst_it_t
Definition Pattern.h:60
Manager for User Preferences File (singleton)
Definition Preferences.h:79
int m_bJackTimebaseMode
Specifies if Hydrogen support the of JACK Timebase protocol.
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
bool savePreferences()
Save the preferences file.
void setLastSongFilename(const QString &filename)
bool m_bUseMetronome
If set to true, samples of the metronome will be added to H2Core::AudioEngine::m_songNoteQueue and th...
int m_bJackTransportMode
Specifies whether or not Hydrogen will use the JACK transport system.
@ NO_JACK_TRANSPORT
Specifies whether or not to use JACK transport capabilities.
@ USE_JACK_TRANSPORT
Specifies whether or not to use JACK transport capabilities.
Definition Preferences.h:90
@ NO_JACK_TIMEBASE_CONTROL
Specifies that Hydrogen should not be in control of JACK Timebase information.
@ USE_JACK_TIMEBASE_CONTROL
Specifies that Hydrogen should attempt to acquire JACK Timebase control.
static std::shared_ptr< Song > load(const QString &sFilename, bool bSilent=false)
Load a song from file.
Definition Song.cpp:191
@ Finishing
Transport is still in loop mode (frames and ticks larger than song size are allowed) but playback end...
Definition Song.h:94
static std::shared_ptr< Song > getEmptySong()
Definition Song.cpp:964
void updateDrumkits(bool bTriggerEvent=true)
std::shared_ptr< Drumkit > getDrumkit(const QString &sDrumkitPath, bool bLoad=true)
Retrieve a drumkit from the database.
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
bool read(const QString &filepath, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:277
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
The MidiMap maps MidiActions to MidiEvents.
Definition MidiMap.h:38
std::vector< int > findCCValuesByActionParam1(QString sActionType, QString sParam1)
Definition MidiMap.cpp:262
static MidiMap * get_instance()
Returns a pointer to the current MidiMap singleton stored in __instance.
Definition MidiMap.h:65
std::vector< int > findCCValuesByActionType(QString sActionType)
Definition MidiMap.cpp:276
static void linkDrumkit(std::shared_ptr< H2Core::Song > pSong)
Responsible for linking a drumkit on user or system level into the session folder and updating all co...
static OscServer * get_instance()
Returns a pointer to the current OscServer singleton stored in __instance.
Definition OscServer.h:123
void handleAction(std::shared_ptr< Action > pAction)
Function called by H2Core::CoreActionController::initExternalControlInterfaces() to inform all client...
@ EVENT_RELOCATION
Triggered in case there is a relocation of the transport position while trasnsport is not rolling.
Definition EventQueue.h:166
@ EVENT_INSTRUMENT_PARAMETERS_CHANGED
Some parameters of an instrument have been changed.
Definition EventQueue.h:83
@ EVENT_DRUMKIT_LOADED
A the current drumkit was replaced by a new one.
Definition EventQueue.h:158
@ EVENT_JACK_TRANSPORT_ACTIVATION
Toggles the button indicating the usage JACK transport.
Definition EventQueue.h:141
@ EVENT_PATTERN_MODIFIED
A pattern was added, deleted, or modified.
Definition EventQueue.h:68
@ EVENT_TIMELINE_UPDATE
Tells the GUI some parts of the Timeline (tempo markers or tags) were modified.
Definition EventQueue.h:139
@ EVENT_UPDATE_SONG
Event triggering HydrogenApp::updateSongEvent() whenever the Song was changed outside of the GUI,...
Definition EventQueue.h:128
@ EVENT_LOOP_MODE_ACTIVATION
Toggles the button indicating the usage loop mode.
Definition EventQueue.h:150
@ EVENT_GRID_CELL_TOGGLED
Definition EventQueue.h:153
@ EVENT_QUIT
Triggering HydrogenApp::quitEvent() and enables a shutdown of the entire application via the command ...
Definition EventQueue.h:133
@ EVENT_UPDATE_PREFERENCES
Event triggering the loading or saving of the H2Core::Preferences whenever they were changed outside ...
Definition EventQueue.h:116