hydrogen 1.2.3
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-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
26#include <core/EventQueue.h>
27#include <core/Hydrogen.h>
32#include <core/Basics/Pattern.h>
33#include "core/OscServer.h"
34#include <core/MidiAction.h>
35#include "core/MidiMap.h"
36#include <core/Helpers/Xml.h>
38
40#include <core/IO/MidiOutput.h>
42
43#ifdef H2CORE_HAVE_OSC
44#include <core/NsmClient.h>
45#endif
46
47namespace H2Core
48{
49
50
51CoreActionController::CoreActionController() : m_nDefaultMidiFeedbackChannel(0)
52{
53 //nothing
54}
55
59
60bool CoreActionController::setMasterVolume( float fMasterVolumeValue )
61{
62 auto pSong = Hydrogen::get_instance()->getSong();
63
64 if ( pSong == nullptr ) {
65 ERRORLOG( "no song set" );
66 return false;
67 }
68
69 pSong->setVolume( fMasterVolumeValue );
70
72}
73
74bool CoreActionController::setStripVolume( int nStrip, float fVolumeValue, bool bSelectStrip )
75{
76 auto pHydrogen = Hydrogen::get_instance();
77
78 auto pInstr = getStrip( nStrip );
79 if ( pInstr != nullptr ) {
80
81 pInstr->set_volume( fVolumeValue );
82
83 if ( bSelectStrip ) {
84 pHydrogen->setSelectedInstrumentNumber( nStrip );
85 }
86
87 pHydrogen->setIsModified( true );
88
89 return sendStripVolumeFeedback( nStrip );
90 }
91
92 return false;
93}
94
101
103{
104 auto pHydrogen = Hydrogen::get_instance();
105 auto pSong = pHydrogen->getSong();
106
107 if ( pSong == nullptr ) {
108 ERRORLOG( "no song set" );
109 return false;
110 }
111
112 pSong->setIsMuted( bIsMuted );
113
114 pHydrogen->setIsModified( true );
115
117}
118
120{
121 auto pInstr = getStrip( nStrip );
122 if ( pInstr == nullptr ) {
123 return false;
124 }
125
126 return setStripIsMuted( nStrip, !pInstr->is_muted() );
127}
128
129bool CoreActionController::setStripIsMuted( int nStrip, bool bIsMuted )
130{
131 auto pHydrogen = Hydrogen::get_instance();
132 auto pInstr = getStrip( nStrip );
133 if ( pInstr != nullptr ) {
134 pInstr->set_muted( bIsMuted );
135
137
138 pHydrogen->setIsModified( true );
139
140 return sendStripIsMutedFeedback( nStrip );
141 }
142
143 return false;
144}
145
147{
148 auto pInstr = getStrip( nStrip );
149 if ( pInstr == nullptr ) {
150 return false;
151 }
152
153 return setStripIsSoloed( nStrip, !pInstr->is_soloed() );
154}
155
156bool CoreActionController::setStripIsSoloed( int nStrip, bool isSoloed )
157{
158 auto pHydrogen = Hydrogen::get_instance();
159 auto pInstr = getStrip( nStrip );
160 if ( pInstr != nullptr ) {
161
162 pInstr->set_soloed( isSoloed );
163
165
166 pHydrogen->setIsModified( true );
167
168 return sendStripIsSoloedFeedback( nStrip );
169 }
170
171 return false;
172}
173
174bool CoreActionController::setStripPan( int nStrip, float fValue, bool bSelectStrip )
175{
176 auto pHydrogen = Hydrogen::get_instance();
177 auto pInstr = getStrip( nStrip );
178 if ( pInstr != nullptr ) {
179
180 pInstr->setPanWithRangeFrom0To1( fValue );
181
183
184 pHydrogen->setIsModified( true );
185
186 if ( bSelectStrip ) {
187 pHydrogen->setSelectedInstrumentNumber( nStrip );
188 }
189
190 return sendStripPanFeedback( nStrip );
191 }
192
193 return false;
194}
195
196
197bool CoreActionController::setStripPanSym( int nStrip, float fValue, bool bSelectStrip )
198{
199 auto pHydrogen = Hydrogen::get_instance();
200 auto pInstr = getStrip( nStrip );
201 if ( pInstr != nullptr ) {
202
203 pInstr->setPan( fValue );
204
206
207 pHydrogen->setIsModified( true );
208
209 if ( bSelectStrip ) {
210 pHydrogen->setSelectedInstrumentNumber( nStrip );
211 }
212
213 return sendStripPanFeedback( nStrip );
214 }
215
216 return false;
217}
218
220 auto pSong = Hydrogen::get_instance()->getSong();
221 if ( pSong == nullptr ) {
222 ERRORLOG( "no song set" );
223 return false;
224 }
225
226 float fMasterVolume = pSong->getVolume();
227
228#ifdef H2CORE_HAVE_OSC
229 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
230
231 std::shared_ptr<Action> pFeedbackAction =
232 std::make_shared<Action>( "MASTER_VOLUME_ABSOLUTE" );
233
234 pFeedbackAction->setValue( QString("%1")
235 .arg( fMasterVolume ) );
236 OscServer::get_instance()->handleAction( pFeedbackAction );
237 }
238#endif
239
240 MidiMap* pMidiMap = MidiMap::get_instance();
241
242 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("MASTER_VOLUME_ABSOLUTE"));
243
244 return handleOutgoingControlChanges( ccParamValues, (fMasterVolume / 1.5) * 127 );
245}
246
248
249 auto pInstr = getStrip( nStrip );
250 if ( pInstr != nullptr ) {
251
252 float fStripVolume = pInstr->get_volume();
253
254#ifdef H2CORE_HAVE_OSC
255 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
256
257 std::shared_ptr<Action> pFeedbackAction =
258 std::make_shared<Action>( "STRIP_VOLUME_ABSOLUTE" );
259
260 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
261 pFeedbackAction->setValue( QString("%1").arg( fStripVolume ) );
262 OscServer::get_instance()->handleAction( pFeedbackAction );
263 }
264#endif
265
266 MidiMap* pMidiMap = MidiMap::get_instance();
267
268 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_VOLUME_ABSOLUTE"),
269 QString("%1").arg( nStrip ) );
270
271 return handleOutgoingControlChanges( ccParamValues, (fStripVolume / 1.5) * 127 );
272 }
273
274 return false;
275}
276
278 auto pPref = Preferences::get_instance();
279
280#ifdef H2CORE_HAVE_OSC
281 if ( pPref->getOscFeedbackEnabled() ) {
282 std::shared_ptr<Action> pFeedbackAction =
283 std::make_shared<Action>( "TOGGLE_METRONOME" );
284
285 pFeedbackAction->setParameter1( QString("%1")
286 .arg( static_cast<int>(pPref->m_bUseMetronome) ) );
287 OscServer::get_instance()->handleAction( pFeedbackAction );
288 }
289#endif
290
291 MidiMap* pMidiMap = MidiMap::get_instance();
292
293 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("TOGGLE_METRONOME"));
294
295 return handleOutgoingControlChanges( ccParamValues,
296 static_cast<int>(pPref->m_bUseMetronome) * 127 );
297}
298
300 auto pSong = Hydrogen::get_instance()->getSong();
301 if ( pSong == nullptr ) {
302 ERRORLOG( "no song set" );
303 return false;
304 }
305
306#ifdef H2CORE_HAVE_OSC
307 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
308 std::shared_ptr<Action> pFeedbackAction =
309 std::make_shared<Action>( "MUTE_TOGGLE" );
310
311 pFeedbackAction->setParameter1( QString("%1")
312 .arg( static_cast<int>(pSong->getIsMuted()) ) );
313 OscServer::get_instance()->handleAction( pFeedbackAction );
314 }
315#endif
316
317 MidiMap* pMidiMap = MidiMap::get_instance();
318
319 auto ccParamValues = pMidiMap->findCCValuesByActionType( QString("MUTE_TOGGLE") );
320
321 return handleOutgoingControlChanges( ccParamValues,
322 static_cast<int>(pSong->getIsMuted()) * 127 );
323}
324
326 auto pInstr = getStrip( nStrip );
327 if ( pInstr != nullptr ) {
328
329#ifdef H2CORE_HAVE_OSC
330 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
331 std::shared_ptr<Action> pFeedbackAction =
332 std::make_shared<Action>( "STRIP_MUTE_TOGGLE" );
333
334 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
335 pFeedbackAction->setValue( QString("%1")
336 .arg( static_cast<int>(pInstr->is_muted()) ) );
337 OscServer::get_instance()->handleAction( pFeedbackAction );
338 }
339#endif
340
341 MidiMap* pMidiMap = MidiMap::get_instance();
342
343 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_MUTE_TOGGLE"),
344 QString("%1").arg( nStrip ) );
345
346 return handleOutgoingControlChanges( ccParamValues,
347 static_cast<int>(pInstr->is_muted()) * 127 );
348 }
349
350 return false;
351}
352
354 auto pInstr = getStrip( nStrip );
355 if ( pInstr != nullptr ) {
356
357#ifdef H2CORE_HAVE_OSC
358 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
359 std::shared_ptr<Action> pFeedbackAction =
360 std::make_shared<Action>( "STRIP_SOLO_TOGGLE" );
361
362 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
363 pFeedbackAction->setValue( QString("%1")
364 .arg( static_cast<int>(pInstr->is_soloed()) ) );
365 OscServer::get_instance()->handleAction( pFeedbackAction );
366 }
367#endif
368
369 MidiMap* pMidiMap = MidiMap::get_instance();
370 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("STRIP_SOLO_TOGGLE"),
371 QString("%1").arg( nStrip ) );
372
373 return handleOutgoingControlChanges( ccParamValues,
374 static_cast<int>(pInstr->is_soloed()) * 127 );
375 }
376
377 return false;
378}
379
381 auto pInstr = getStrip( nStrip );
382 if ( pInstr != nullptr ) {
383
384#ifdef H2CORE_HAVE_OSC
385 if ( Preferences::get_instance()->getOscFeedbackEnabled() ) {
386 std::shared_ptr<Action> pFeedbackAction =
387 std::make_shared<Action>( "PAN_ABSOLUTE" );
388
389 pFeedbackAction->setParameter1( QString("%1").arg( nStrip + 1 ) );
390 pFeedbackAction->setValue( QString("%1")
391 .arg( pInstr->getPanWithRangeFrom0To1() ) );
392 OscServer::get_instance()->handleAction( pFeedbackAction );
393 }
394#endif
395
396 MidiMap* pMidiMap = MidiMap::get_instance();
397 auto ccParamValues = pMidiMap->findCCValuesByActionParam1( QString("PAN_ABSOLUTE"),
398 QString("%1").arg( nStrip ) );
399
400 return handleOutgoingControlChanges( ccParamValues,
401 pInstr->getPanWithRangeFrom0To1() * 127 );
402 }
403
404 return false;
405}
406
407bool CoreActionController::handleOutgoingControlChanges( std::vector<int> params, int nValue)
408{
410 Hydrogen *pHydrogen = Hydrogen::get_instance();
411 MidiOutput *pMidiDriver = pHydrogen->getMidiOutput();
412
413 if ( pHydrogen->getSong() == nullptr ) {
414 ERRORLOG( "no song set" );
415 return false;
416 }
417
418 for ( auto param : params ) {
419 if ( pMidiDriver != nullptr &&
420 pPref->m_bEnableMidiFeedback && param >= 0 ){
421 pMidiDriver->handleOutgoingControlChange( param, nValue, m_nDefaultMidiFeedbackChannel );
422 }
423 }
424
425 return true;
426}
427
428std::shared_ptr<Instrument> CoreActionController::getStrip( int nStrip ) const {
429 auto pSong = Hydrogen::get_instance()->getSong();
430 if ( pSong == nullptr ) {
431 ERRORLOG( "no song set" );
432 return nullptr;
433 }
434
435 auto pInstr = pSong->getInstrumentList()->get( nStrip );
436 if ( pInstr == nullptr ) {
437 ERRORLOG( QString( "Couldn't find instrument [%1]" ).arg( nStrip ) );
438 }
439
440 return pInstr;
441}
442
444{
445 /*
446 * Push the current state of Hydrogen to the attached control interfaces (e.g. OSC clients)
447 */
448
449 //MASTER_VOLUME_ABSOLUTE
450 auto pHydrogen = Hydrogen::get_instance();
451 auto pSong = pHydrogen->getSong();
452
453 if ( pSong == nullptr ) {
454 ERRORLOG( "no song set" );
455 return false;
456 }
457
459
460 //PER-INSTRUMENT/STRIP STATES
461 auto pInstrList = pSong->getInstrumentList();
462 for ( int ii = 0; ii < pInstrList->size(); ii++){
463 auto pInstr = pInstrList->get( ii );
464 if ( pInstr != nullptr ) {
465
466 //STRIP_VOLUME_ABSOLUTE
468
469 //PAN_ABSOLUTE
471
472 //STRIP_MUTE_TOGGLE
474
475 //SOLO
477 }
478 }
479
480 //TOGGLE_METRONOME
482
483 //MUTE_TOGGLE
485
486 return true;
487}
488
491
492bool CoreActionController::newSong( const QString& sSongPath ) {
493
494 auto pHydrogen = Hydrogen::get_instance();
495
496 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
497 // Stops recording, all queued MIDI notes, and the playback of
498 // the audio driver.
499 pHydrogen->sequencer_stop();
500 }
501
502 // Create an empty Song.
503 auto pSong = Song::getEmptySong();
504
505 // Check whether the provided path is valid.
506 if ( !Filesystem::isSongPathValid( sSongPath ) ) {
507 // Filesystem::isSongPathValid takes care of the error log message.
508
509 return false;
510 }
511
512 if ( pHydrogen->isUnderSessionManagement() ) {
513 pHydrogen->restartDrivers();
514 // The drumkit of the new song will linked into the session
515 // folder during the next song save.
516 pHydrogen->setSessionDrumkitNeedsRelinking( true );
517 }
518
519 pSong->setFilename( sSongPath );
520
521 pHydrogen->setSong( pSong );
522
523 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
525 }
526
527 return true;
528}
529
530bool CoreActionController::openSong( const QString& sSongPath, const QString& sRecoverSongPath ) {
531 auto pHydrogen = Hydrogen::get_instance();
532
533 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
534 // Stops recording, all queued MIDI notes, and the playback of
535 // the audio driver.
536 pHydrogen->sequencer_stop();
537 }
538
539 // Check whether the provided path is valid.
540 if ( !Filesystem::isSongPathValid( sSongPath, true ) ) {
541 // Filesystem::isSongPathValid takes care of the error log message.
542 return false;
543 }
544
545 std::shared_ptr<Song> pSong;
546 if ( ! sRecoverSongPath.isEmpty() ) {
547 // Use an autosave file to load the song
548 pSong = Song::load( sRecoverSongPath );
549 if ( pSong != nullptr ) {
550 pSong->setFilename( sSongPath );
551 }
552 } else {
553 pSong = Song::load( sSongPath );
554 }
555
556 if ( pSong == nullptr ) {
557 ERRORLOG( QString( "Unable to open song [%1]." )
558 .arg( sSongPath ) );
559 return false;
560 }
561
562 return setSong( pSong );
563}
564
565bool CoreActionController::openSong( std::shared_ptr<Song> pSong, bool bRelinking ) {
566
567 auto pHydrogen = Hydrogen::get_instance();
568
569 if ( pHydrogen->getAudioEngine()->getState() == AudioEngine::State::Playing ) {
570 // Stops recording, all queued MIDI notes, and the playback of
571 // the audio driver.
572 pHydrogen->sequencer_stop();
573 }
574
575 if ( pSong == nullptr ) {
576 ERRORLOG( QString( "Unable to open song." ) );
577 return false;
578 }
579
580 return setSong( pSong, bRelinking );
581}
582
583bool CoreActionController::setSong( std::shared_ptr<Song> pSong, bool bRelinking ) {
584
585 auto pHydrogen = Hydrogen::get_instance();
586
587 // Update the Song.
588 pHydrogen->setSong( pSong, bRelinking );
589
590 if ( pHydrogen->isUnderSessionManagement() ) {
591 pHydrogen->restartDrivers();
592 } else if ( pSong->getFilename() != Filesystem::empty_song_path() ) {
593 // Add the new loaded song in the "last used song" vector.
594 // This behavior is prohibited under session management. Only
595 // songs open during normal runs will be listed. In addition,
596 // empty songs - created and set when hitting "New Song" in
597 // the main menu - aren't listed either.
598 insertRecentFile( pSong->getFilename() );
599 Preferences::get_instance()->setLastSongFilename( pSong->getFilename() );
600 }
601
602 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
604 }
605
606 // As we just set a fresh song, we can mark it not modified
607 pHydrogen->setIsModified( false );
608
609 return true;
610}
611
613
614 auto pHydrogen = Hydrogen::get_instance();
615 auto pSong = pHydrogen->getSong();
616
617 if ( pSong == nullptr ) {
618 ERRORLOG( "no song set" );
619 return false;
620 }
621
622 // Extract the path to the associate .h2song file.
623 QString sSongPath = pSong->getFilename();
624
625 if ( sSongPath.isEmpty() ) {
626 ERRORLOG( "Unable to save song. Empty filename!" );
627 return false;
628 }
629
630#ifdef H2CORE_HAVE_OSC
631 if ( pHydrogen->isUnderSessionManagement() &&
632 pHydrogen->getSessionDrumkitNeedsRelinking() &&
633 ! pHydrogen->getSessionIsExported() ) {
634
635 NsmClient::linkDrumkit( pSong );
636
637 // Properly set in NsmClient::linkDrumkit()
638 QString sSessionDrumkitPath = pSong->getLastLoadedDrumkitPath();
639
640 auto drumkitDatabase = pHydrogen->getSoundLibraryDatabase()->getDrumkitDatabase();
641 if ( drumkitDatabase.find( sSessionDrumkitPath ) != drumkitDatabase.end() ) {
642 // In case the session folder is already present in the
643 // SoundLibraryDatabase, we have to update it (takes a
644 // while) to ensure it's clean and all kits are valid. If
645 // it's not present, we can skip it because loading is
646 // done lazily.
647 pHydrogen->getSoundLibraryDatabase()->updateDrumkit( sSessionDrumkitPath );
648 }
649 }
650#endif
651
652 // Actual saving
653 bool bSaved = pSong->save( sSongPath );
654 if ( ! bSaved ) {
655 ERRORLOG( QString( "Current song [%1] could not be saved!" )
656 .arg( sSongPath ) );
657 return false;
658 }
659
660 // Update the status bar.
661 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
663 }
664
665 return true;
666}
667
668bool CoreActionController::saveSongAs( const QString& sNewFilename ) {
669
670 auto pHydrogen = Hydrogen::get_instance();
671 auto pSong = pHydrogen->getSong();
672
673 if ( pSong == nullptr ) {
674 ERRORLOG( "no song set" );
675 return false;
676 }
677
678 // Check whether the provided path is valid.
679 if ( !Filesystem::isSongPathValid( sNewFilename ) ) {
680 // Filesystem::isSongPathValid takes care of the error log message.
681 return false;
682 }
683
684 QString sPreviousFilename( pSong->getFilename() );
685 pSong->setFilename( sNewFilename );
686
687 // Actual saving
688 if ( ! saveSong() ) {
689 return false;
690 }
691
692 // Update the recentFiles list by replacing the former file name
693 // with the new one.
694 insertRecentFile( sNewFilename );
695 if ( ! pHydrogen->isUnderSessionManagement() ) {
696 Preferences::get_instance()->setLastSongFilename( pSong->getFilename() );
697 }
698
699 return true;
700}
701
703
705 // Update the status bar and let the GUI save the preferences
706 // (after writing its current settings to disk).
708 return true;
709 }
710
712}
714
717 } else {
718 // TODO: Close Hydrogen with no GUI present.
719
720 ERRORLOG( QString( "Error: Closing the application via the core part is not supported yet!" ) );
721 return false;
722
723 }
724
725 return true;
726}
727
729 auto pHydrogen = Hydrogen::get_instance();
730
731 if ( pHydrogen->getSong() == nullptr ) {
732 ERRORLOG( "no song set" );
733 return false;
734 }
735
736 pHydrogen->setIsTimelineActivated( bActivate );
737
738 if ( pHydrogen->getJackTimebaseState() == JackAudioDriver::Timebase::Slave ) {
739 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 master." )
740 .arg( bActivate ? "enabled" : "disabled" ) );
741 } else if ( pHydrogen->getMode() == Song::Mode::Pattern ) {
742 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." )
743 .arg( bActivate ? "enabled" : "disabled" ) );
744 }
745
746 return true;
747}
748
749bool CoreActionController::addTempoMarker( int nPosition, float fBpm ) {
750 auto pHydrogen = Hydrogen::get_instance();
751 auto pAudioEngine = pHydrogen->getAudioEngine();
752 auto pTimeline = pHydrogen->getTimeline();
753
754 if ( pHydrogen->getSong() == nullptr ) {
755 ERRORLOG( "no song set" );
756 return false;
757 }
758
759 pAudioEngine->lock( RIGHT_HERE );
760
761 pTimeline->deleteTempoMarker( nPosition );
762 pTimeline->addTempoMarker( nPosition, fBpm );
763 pHydrogen->getAudioEngine()->handleTimelineChange();
764
765 pAudioEngine->unlock();
766
767 pHydrogen->setIsModified( true );
768
770
771 return true;
772}
773
775 auto pHydrogen = Hydrogen::get_instance();
776 auto pAudioEngine = pHydrogen->getAudioEngine();
777
778 if ( pHydrogen->getSong() == nullptr ) {
779 ERRORLOG( "no song set" );
780 return false;
781 }
782
783 pAudioEngine->lock( RIGHT_HERE );
784
785 pHydrogen->getTimeline()->deleteTempoMarker( nPosition );
786 pHydrogen->getAudioEngine()->handleTimelineChange();
787
788 pAudioEngine->unlock();
789
790 pHydrogen->setIsModified( true );
792
793 return true;
794}
795
796bool CoreActionController::addTag( int nPosition, const QString& sText ) {
797 auto pHydrogen = Hydrogen::get_instance();
798 auto pTimeline = pHydrogen->getTimeline();
799
800 if ( pHydrogen->getSong() == nullptr ) {
801 ERRORLOG( "no song set" );
802 return false;
803 }
804
805 pTimeline->deleteTag( nPosition );
806 pTimeline->addTag( nPosition, sText );
807
808 pHydrogen->setIsModified( true );
809
811
812 return true;
813}
814
815bool CoreActionController::deleteTag( int nPosition ) {
816 auto pHydrogen = Hydrogen::get_instance();
817 auto pAudioEngine = pHydrogen->getAudioEngine();
818
819 if ( pHydrogen->getSong() == nullptr ) {
820 ERRORLOG( "no song set" );
821 return false;
822 }
823
824 pHydrogen->getTimeline()->deleteTag( nPosition );
825
826 pHydrogen->setIsModified( true );
828
829 return true;
830}
831
833
834#ifdef H2CORE_HAVE_JACK
835 if ( !Hydrogen::get_instance()->hasJackAudioDriver() ) {
836 ERRORLOG( "Unable to (de)activate Jack transport. Please select the Jack driver first." );
837 return false;
838 }
839
841 if ( bActivate ) {
843 } else {
845 }
847
848 EventQueue::get_instance()->push_event( EVENT_JACK_TRANSPORT_ACTIVATION, static_cast<int>( bActivate ) );
849
850 return true;
851#else
852 ERRORLOG( "Unable to (de)activate Jack transport. Your Hydrogen version was not compiled with jack support." );
853 return false;
854#endif
855}
856
858 auto pHydrogen = Hydrogen::get_instance();
859
860#ifdef H2CORE_HAVE_JACK
861 if ( !pHydrogen->hasJackAudioDriver() ) {
862 ERRORLOG( "Unable to (de)activate Jack timebase master. Please select the Jack driver first." );
863 return false;
864 }
865
866 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
867 if ( bActivate ) {
869 pHydrogen->onJackMaster();
870 } else {
872 pHydrogen->offJackMaster();
873 }
874 pHydrogen->getAudioEngine()->unlock();
875
877 static_cast<int>(pHydrogen->getJackTimebaseState()) );
878
879 return true;
880#else
881 ERRORLOG( "Unable to (de)activate Jack timebase master. Your Hydrogen version was not compiled with jack support." );
882 return false;
883#endif
884}
885
887
888 auto pHydrogen = Hydrogen::get_instance();
889 auto pAudioEngine = pHydrogen->getAudioEngine();
890 auto pSong = pHydrogen->getSong();
891
892 if ( pSong == nullptr ) {
893 ERRORLOG( "no song set" );
894 return false;
895 }
896
897 if ( !( bActivate && pHydrogen->getMode() != Song::Mode::Song ) &&
898 ! ( ! bActivate && pHydrogen->getMode() != Song::Mode::Pattern ) ) {
899 // No changes.
900 return true;
901 }
902
903 pHydrogen->sequencer_stop();
904
905 pAudioEngine->lock( RIGHT_HERE );
906
907 if ( bActivate && pHydrogen->getMode() != Song::Mode::Song ) {
908 pHydrogen->setMode( Song::Mode::Song );
909 }
910 else if ( ! bActivate && pHydrogen->getMode() != Song::Mode::Pattern ) {
911 pHydrogen->setMode( Song::Mode::Pattern );
912 }
913
914 pAudioEngine->handleSongModeChanged();
915
916 pAudioEngine->unlock();
917
918 return true;
919}
920
922
923 auto pHydrogen = Hydrogen::get_instance();
924 auto pSong = pHydrogen->getSong();
925 auto pAudioEngine = pHydrogen->getAudioEngine();
926
927 if ( pHydrogen->getSong() == nullptr ) {
928 ERRORLOG( "no song set" );
929 return false;
930 }
931
932
933 bool bChange = false;
934
935 if ( bActivate &&
936 pSong->getLoopMode() != Song::LoopMode::Enabled ) {
937 pSong->setLoopMode( Song::LoopMode::Enabled );
938 bChange = true;
939
940 } else if ( ! bActivate &&
941 pSong->getLoopMode() == Song::LoopMode::Enabled ) {
942 // If the transport was already looped at least once, disabling
943 // loop mode will result in immediate stop. Instead, we want to
944 // stop transport at the end of the song.
945 if ( pSong->lengthInTicks() <
946 pAudioEngine->getTransportPosition()->getTick() ) {
947 pSong->setLoopMode( Song::LoopMode::Finishing );
948 } else {
949 pSong->setLoopMode( Song::LoopMode::Disabled );
950 }
951 bChange = true;
952 }
953
954 pAudioEngine->lock( RIGHT_HERE );
955 pAudioEngine->handleLoopModeChanged();
956 pAudioEngine->unlock();
957
958 if ( bChange ) {
960 static_cast<int>( bActivate ) );
961 }
962
963 return true;
964}
965
966bool CoreActionController::setDrumkit( const QString& sDrumkit, bool bConditional ) {
967
969 ->getDrumkit( sDrumkit );
970 if ( pDrumkit == nullptr ) {
971 ERRORLOG( QString( "Drumkit [%1] could not be loaded." )
972 .arg( sDrumkit ) );
973 return false;
974 }
975
976 return setDrumkit( pDrumkit, bConditional );
977}
978
979bool CoreActionController::setDrumkit( std::shared_ptr<Drumkit> pDrumkit, bool bConditional ) {
980 if ( pDrumkit != nullptr ) {
981
982 auto pHydrogen = Hydrogen::get_instance();
983 auto pSong = pHydrogen->getSong();
984 if ( pSong != nullptr ) {
985
986 INFOLOG( QString( "Setting drumkit [%1] located at [%2]" )
987 .arg( pDrumkit->get_name() )
988 .arg( pDrumkit->get_path() ) );
989
990 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
991
992 pSong->setDrumkit( pDrumkit, bConditional );
993
994 if ( pHydrogen->getSelectedInstrumentNumber() >=
995 pSong->getInstrumentList()->size() ) {
996 pHydrogen->setSelectedInstrumentNumber(
997 std::max( 0, pSong->getInstrumentList()->size() -1 ),
998 false );
999 }
1000
1001 pHydrogen->renameJackPorts( pSong );
1002
1003 pHydrogen->getAudioEngine()->unlock();
1004
1006
1007 pHydrogen->setIsModified( true );
1008
1009 // Create a symbolic link in the session folder when under session
1010 // management.
1011 if ( pHydrogen->isUnderSessionManagement() ) {
1012 pHydrogen->setSessionDrumkitNeedsRelinking( true );
1013 }
1014
1016 }
1017 else {
1018 ERRORLOG( "No song set yet" );
1019 return false;
1020 }
1021 }
1022 else {
1023 ERRORLOG( "Provided Drumkit is not valid" );
1024 return false;
1025 }
1026
1027 return true;
1028}
1029
1030bool CoreActionController::upgradeDrumkit( const QString& sDrumkitPath, const QString& sNewPath ) {
1031
1032 if ( sNewPath.isEmpty() ) {
1033 INFOLOG( QString( "Upgrading kit at [%1] inplace." )
1034 .arg( sDrumkitPath ) );
1035 } else {
1036 INFOLOG( QString( "Upgrading kit at [%1] into [%2]." )
1037 .arg( sDrumkitPath ).arg( sNewPath ) );
1038 }
1039
1040 QFileInfo sourceFileInfo( sDrumkitPath );
1041 if ( ! sNewPath.isEmpty() ) {
1042 // Check whether there is already a file or directory
1043 // present. The latter has to be writable. If none is present,
1044 // create a folder.
1045 if ( ! Filesystem::path_usable( sNewPath, true, false ) ) {
1046 return false;
1047 }
1048 } else {
1049 // We have to assure that the source folder is not just
1050 // readable since an inplace upgrade was requested
1051 if ( ! Filesystem::dir_writable( sourceFileInfo.dir().absolutePath(),
1052 true ) ) {
1053 ERRORLOG( QString( "Unable to upgrade drumkit [%1] in place: Folder is in read-only mode" )
1054 .arg( sDrumkitPath ) );
1055 return false;
1056 }
1057 }
1058
1059 QString sTemporaryFolder, sDrumkitDir;
1060 // Whether the drumkit was provided as compressed .h2drumkit file.
1061 bool bIsCompressed;
1062 auto pDrumkit = retrieveDrumkit( sDrumkitPath, &bIsCompressed,
1063 &sDrumkitDir, &sTemporaryFolder );
1064
1065 if ( pDrumkit == nullptr ) {
1066 ERRORLOG( QString( "Unable to load drumkit from source path [%1]" )
1067 .arg( sDrumkitPath ) );
1068 return false;
1069 }
1070
1071 // If the drumkit is not updated inplace, we also need to copy
1072 // all samples and metadata, like images.
1073 QString sPath;
1074 if ( ! sNewPath.isEmpty() ) {
1075
1076 // When dealing with a compressed drumkit, we can just leave
1077 // it in the temporary folder and copy the compressed content
1078 // to the destination right away.
1079 if ( ! bIsCompressed ) {
1080 // Copy content
1081 QDir drumkitDir( sDrumkitDir );
1082 for ( const auto& ssFile : drumkitDir.entryList( QDir::Files ) ) {
1083
1084 // We handle the drumkit file later
1085 if ( ssFile.contains( ".xml" ) ) {
1086 continue;
1087 }
1088 Filesystem::file_copy( drumkitDir.absolutePath() + "/" + ssFile,
1089 sNewPath + "/" + ssFile, true, true );
1090 }
1091 sPath = sNewPath;
1092 } else {
1093 sPath = sDrumkitDir;
1094 }
1095
1096 } else {
1097 // Upgrade inplace.
1098
1099 if ( ! bIsCompressed ) {
1100 // Make a backup of the original file in order to make the
1101 // upgrade reversible.
1102 QString sBackupPath =
1104 if ( ! Filesystem::file_copy( Filesystem::drumkit_file( sDrumkitDir ),
1105 sBackupPath, true, true ) ) {
1106 ERRORLOG( QString( "Unable to backup source drumkit XML file from [%1] to [%2]. We abort instead of overwriting things." )
1107 .arg( Filesystem::drumkit_file( sDrumkitDir ) )
1108 .arg( sBackupPath ) );
1109 return false;
1110 }
1111 } else {
1112 QString sBackupPath = Filesystem::drumkit_backup_path( sDrumkitPath );
1113 if ( ! Filesystem::file_copy( sDrumkitPath, sBackupPath, true, true ) ) {
1114 ERRORLOG( QString( "Unable to backup source .h2drumkit file from [%1] to [%2]. We abort instead of overwriting things." )
1115 .arg( sDrumkitPath ).arg( sBackupPath ) );
1116 return false;
1117 }
1118 }
1119
1120 sPath = sDrumkitDir;
1121 }
1122
1123 if ( ! pDrumkit->save( sPath, -1, true, true ) ) {
1124 ERRORLOG( QString( "Error while saving upgraded kit to [%1]" )
1125 .arg( sPath ) );
1126 return false;
1127 }
1128
1129 // Compress the updated drumkit again in order to provide the same
1130 // format handed over as input.
1131 if ( bIsCompressed ) {
1132 QString sExportPath;
1133 if ( ! sNewPath.isEmpty() ) {
1134 sExportPath = sNewPath;
1135 } else {
1136 sExportPath = sourceFileInfo.dir().absolutePath();
1137 }
1138
1139 if ( ! pDrumkit->exportTo( sExportPath, "", true, false ) ) {
1140 ERRORLOG( QString( "Unable to export upgrade drumkit to [%1]" )
1141 .arg( sExportPath ) );
1142 return false;
1143 }
1144
1145 INFOLOG( QString( "Upgraded drumkit exported as [%1]" )
1146 .arg( sExportPath + "/" + pDrumkit->get_name() +
1148 }
1149
1150 // Upgrade was successful. Cleanup
1151 if ( ! sTemporaryFolder.isEmpty() ) {
1152 // Filesystem::rm( sTemporaryFolder, true, true );
1153 }
1154
1155 INFOLOG( QString( "Drumkit [%1] successfully upgraded!" )
1156 .arg( sDrumkitPath ) );
1157
1158 return true;
1159}
1160
1161bool CoreActionController::validateDrumkit( const QString& sDrumkitPath, bool bCheckLegacyVersions ) {
1162
1163 INFOLOG( QString( "Validating kit [%1]" ).arg( sDrumkitPath ) );
1164
1165 QString sTemporaryFolder, sDrumkitDir;
1166 // Whether the drumkit was provided as compressed .h2drumkit file.
1167 bool bIsCompressed;
1168 auto pDrumkit = retrieveDrumkit( sDrumkitPath, &bIsCompressed,
1169 &sDrumkitDir, &sTemporaryFolder );
1170
1171 if ( pDrumkit == nullptr ) {
1172 ERRORLOG( QString( "Unable to load drumkit from source path [%1]" )
1173 .arg( sDrumkitPath ) );
1174 return false;
1175 }
1176
1177 if ( ! Filesystem::drumkit_valid( sDrumkitDir ) ) {
1178 ERRORLOG( QString( "Something went wrong in the drumkit retrieval of [%1]. Unable to load from [%2]" )
1179 .arg( sDrumkitPath ).arg( sDrumkitDir ) );
1180 return false;
1181 }
1182
1183 auto validateXSD = [&]( const QString& sXSDPath, const QString& sContext ) {
1184
1185 XMLDoc doc;
1186 if ( !doc.read( Filesystem::drumkit_file( sDrumkitDir ),
1187 sXSDPath, true ) ) {
1188 ERRORLOG( QString( "Drumkit file [%1] does not comply with [%2] XSD definition" )
1189 .arg( Filesystem::drumkit_file( sDrumkitDir ) )
1190 .arg( sContext ) );
1191 return false;
1192 }
1193
1194 XMLNode root = doc.firstChildElement( "drumkit_info" );
1195 if ( root.isNull() ) {
1196 ERRORLOG( QString( "Drumkit file [%1] seems bricked: 'drumkit_info' node not found" )
1197 .arg( Filesystem::drumkit_file( sDrumkitDir ) ) );
1198 return false;
1199 }
1200
1201 INFOLOG( QString( "Drumkit file [%1] validates [%2] XSD definition" )
1202 .arg( Filesystem::drumkit_file( sDrumkitDir ) )
1203 .arg( sContext ) );
1204
1205 return true;
1206 };
1207
1208 bool bValid = validateXSD( Filesystem::drumkit_xsd_path(), "current" );
1209 if ( ! bValid && ! bCheckLegacyVersions ) {
1210 return false;
1211 }
1212
1213 if ( ! bValid && bCheckLegacyVersions ) {
1214 const auto legacyXSDFiles = Filesystem::drumkit_xsd_legacy_paths();
1215
1216 for ( const auto& sPath : legacyXSDFiles ) {
1217 QString sContext( sPath );
1218 sContext.remove( Filesystem::xsd_dir() );
1219 sContext.remove( Filesystem::drumkit_xsd() );
1220
1221 if ( validateXSD( sPath, sContext ) ) {
1222 bValid = true;
1223 break;
1224 }
1225 }
1226
1227 if ( ! bValid ) {
1228 return false;
1229 }
1230 }
1231
1232 INFOLOG( QString( "Drumkit [%1] is valid!" )
1233 .arg( sDrumkitPath ) );
1234
1235 return true;
1236}
1237
1238std::shared_ptr<Drumkit> CoreActionController::retrieveDrumkit( const QString& sDrumkitPath, bool* bIsCompressed, QString *sDrumkitDir, QString* sTemporaryFolder ) {
1239
1240 std::shared_ptr<Drumkit> pDrumkit = nullptr;
1241
1242 // We do not attempt to retrieve the drumkit from disk since this
1243 // function is intended to be used for validating or upgrading
1244 // drumkits via CLI or OSC command. It should always refer to the
1245 // latest copy found on disk.
1246
1247 *bIsCompressed = false;
1248 *sTemporaryFolder = "";
1249 *sDrumkitDir = "";
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( sDrumkitPath, false, true );
1257 *sDrumkitDir = sDrumkitPath;
1258
1259 } else if ( sourceFileInfo.fileName() == Filesystem::drumkit_xml() &&
1260 Filesystem::file_readable( sDrumkitPath, true ) ) {
1261
1262 // Providing the path of a drumkit.xml file within a drumkit
1263 // folder.
1264 QString sDrumkitDirPath = QFileInfo( sDrumkitPath ).absoluteDir().absolutePath();
1265 pDrumkit = Drumkit::load( sDrumkitDirPath, false, true );
1266 *sDrumkitDir = sourceFileInfo.dir().absolutePath();
1267
1268 } else if ( ( "." + sourceFileInfo.suffix() ) == Filesystem::drumkit_ext &&
1269 Filesystem::file_readable( sDrumkitPath, true ) ) {
1270
1271 *bIsCompressed = true;
1272
1273 // Temporary folder used to extract a compressed drumkit (
1274 // .h2drumkit ).
1275 QString sTemplateName( Filesystem::tmp_dir() + "/" +
1276 sourceFileInfo.baseName() + "_XXXXXX" );
1277 QTemporaryDir tmpDir( sTemplateName );
1278 tmpDir.setAutoRemove( false );
1279 if ( ! tmpDir.isValid() ) {
1280 ERRORLOG( QString( "Unable to create temporary folder using template name [%1]" )
1281 .arg( sTemplateName ) );
1282 return nullptr;
1283 }
1284
1285 *sTemporaryFolder = tmpDir.path();
1286
1287 // Providing the path to a compressed .h2drumkit file. It will
1288 // be extracted to a temporary folder and loaded from there.
1289 if ( ! Drumkit::install( sDrumkitPath, tmpDir.path(), true ) ) {
1290 ERRORLOG( QString( "Unabled to extract provided drumkit [%1] into [%2]" )
1291 .arg( sDrumkitPath ).arg( tmpDir.path() ) );
1292 return nullptr;
1293 }
1294
1295 // INFOLOG( QString( "Extracting drumkit [%1] into [%2]" )
1296 // .arg( sDrumkitPath ).arg( tmpDir.path() ) );
1297
1298 // The extracted folder is expected to contain a single
1299 // directory named as the drumkit itself. But some kits
1300 // deviate from the latter condition. So, we just use the
1301 // former one.
1302 QDir extractedDir( tmpDir.path() );
1303 QStringList extractedContent =
1304 extractedDir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot );
1305 QStringList extractedFolders =
1306 extractedDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
1307 if ( ( extractedContent.size() != extractedFolders.size() ) ||
1308 ( extractedFolders.size() != 1 ) ) {
1309 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" )
1310 .arg( sDrumkitPath ) );
1311 for ( const auto& sFile : extractedContent ) {
1312 ERRORLOG( sFile );
1313 }
1314 return nullptr;
1315 }
1316
1317 *sDrumkitDir = tmpDir.path() + "/" + extractedFolders[0];
1318
1319 pDrumkit = Drumkit::load( *sDrumkitDir, false, true );
1320
1321 } else {
1322 ERRORLOG( QString( "Provided source path [%1] does not point to a Hydrogen drumkit" )
1323 .arg( sDrumkitPath ) );
1324 return nullptr;
1325 }
1326
1327 return pDrumkit;
1328}
1329
1330bool CoreActionController::extractDrumkit( const QString& sDrumkitPath, const QString& sTargetDir ) {
1331
1332 QString sTarget;
1333 bool bInstall = false;
1334 if ( sTargetDir.isEmpty() ) {
1335 bInstall = true;
1336 INFOLOG( QString( "Installing drumkit [%1]" ).arg( sDrumkitPath ) );
1337 sTarget = Filesystem::usr_drumkits_dir();
1338 } else {
1339 INFOLOG( QString( "Extracting drumkit [%1] to [%2]" )
1340 .arg( sDrumkitPath ).arg( sTargetDir ) );
1341 sTarget = sTargetDir;
1342 }
1343
1344 if ( ! Filesystem::path_usable( sTarget, true, false ) ) {
1345 ERRORLOG( QString( "Target dir [%1] is neither a writable folder nor can it be created." )
1346 .arg( sTarget ) );
1347 return false;
1348 }
1349
1350 QFileInfo sKitInfo( sDrumkitPath );
1351 if ( ! Filesystem::file_readable( sDrumkitPath, true ) ||
1352 "." + sKitInfo.suffix() != Filesystem::drumkit_ext ) {
1353 ERRORLOG( QString( "Invalid drumkit path [%1]. Please provide an absolute path to a .h2drumkit file." )
1354 .arg( sDrumkitPath ) );
1355 return false;
1356 }
1357
1358 if ( ! Drumkit::install( sDrumkitPath, sTarget, true ) ) {
1359 ERRORLOG( QString( "Unabled to extract provided drumkit [%1] into [%2]" )
1360 .arg( sDrumkitPath ).arg( sTarget ) );
1361 return false;
1362 }
1363
1364 if ( bInstall ) {
1366 }
1367
1368 return true;
1369}
1370
1371bool CoreActionController::locateToColumn( int nPatternGroup ) {
1372
1373 if ( nPatternGroup < -1 ) {
1374 ERRORLOG( QString( "Provided column [%1] too low. Assigning 0 instead." )
1375 .arg( nPatternGroup ) );
1376 nPatternGroup = 0;
1377 }
1378
1379 auto pHydrogen = Hydrogen::get_instance();
1380 if ( pHydrogen->getSong() == nullptr ) {
1381 ERRORLOG( "no song set" );
1382 return false;
1383 }
1384
1385 long nTotalTick = pHydrogen->getTickForColumn( nPatternGroup );
1386 if ( nTotalTick < 0 ) {
1387 if ( pHydrogen->getMode() == Song::Mode::Song ) {
1388 ERRORLOG( QString( "Provided column [%1] violates the allowed range [0;%2). No relocation done." )
1389 .arg( nPatternGroup )
1390 .arg( pHydrogen->getSong()->getPatternGroupVector()->size() ) );
1391 return false;
1392 } else {
1393 // In case of Pattern mode this is not a problem and we
1394 // will treat this case as the beginning of the song.
1395 nTotalTick = 0;
1396 }
1397 }
1398
1399 return locateToTick( nTotalTick );
1400}
1401
1402bool CoreActionController::locateToTick( long nTick, bool bWithJackBroadcast ) {
1403
1404 const auto pHydrogen = Hydrogen::get_instance();
1405 auto pAudioEngine = pHydrogen->getAudioEngine();
1406
1407 if ( pHydrogen->getSong() == nullptr ) {
1408 ERRORLOG( "no song set" );
1409 return false;
1410 }
1411
1412 pAudioEngine->lock( RIGHT_HERE );
1413
1414 pAudioEngine->locate( nTick, bWithJackBroadcast );
1415
1416 pAudioEngine->unlock();
1417
1419 return true;
1420}
1421
1422bool CoreActionController::newPattern( const QString& sPatternName ) {
1423 auto pPatternList = Hydrogen::get_instance()->getSong()->getPatternList();
1424 Pattern* pPattern = new Pattern( sPatternName );
1425
1426 return setPattern( pPattern, pPatternList->size() );
1427}
1428bool CoreActionController::openPattern( const QString& sPath, int nPatternPosition ) {
1429 auto pHydrogen = Hydrogen::get_instance();
1430 auto pSong = pHydrogen->getSong();
1431
1432 if ( pHydrogen->getSong() == nullptr ) {
1433 ERRORLOG( "no song set" );
1434 return false;
1435 }
1436
1437 auto pPatternList = pSong->getPatternList();
1438 Pattern* pNewPattern = Pattern::load_file( sPath, pSong->getInstrumentList() );
1439
1440 if ( pNewPattern == nullptr ) {
1441 ERRORLOG( QString( "Unable to loading the pattern [%1]" ).arg( sPath ) );
1442 return false;
1443 }
1444
1445 if ( nPatternPosition == -1 ) {
1446 nPatternPosition = pPatternList->size();
1447 }
1448
1449 return setPattern( pNewPattern, nPatternPosition );
1450}
1451
1452bool CoreActionController::setPattern( Pattern* pPattern, int nPatternPosition ) {
1453 auto pHydrogen = Hydrogen::get_instance();
1454
1455 if ( pHydrogen->getSong() == nullptr ) {
1456 ERRORLOG( "no song set" );
1457 return false;
1458 }
1459
1460 auto pPatternList = pHydrogen->getSong()->getPatternList();
1461
1462 // Check whether the name of the new pattern is unique.
1463 if ( !pPatternList->check_name( pPattern->get_name() ) ){
1464 pPattern->set_name( pPatternList->find_unused_pattern_name( pPattern->get_name() ) );
1465 }
1466
1467 pPatternList->insert( nPatternPosition, pPattern );
1468 if ( pHydrogen->isPatternEditorLocked() ) {
1469 pHydrogen->updateSelectedPattern( true );
1470 } else {
1471 pHydrogen->setSelectedPatternNumber( nPatternPosition );
1472 }
1473 pHydrogen->setIsModified( true );
1474
1475 // Update the SongEditor.
1476 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
1478 }
1479 return true;
1480}
1481
1482bool CoreActionController::removePattern( int nPatternNumber ) {
1483 auto pHydrogen = Hydrogen::get_instance();
1484 auto pAudioEngine = pHydrogen->getAudioEngine();
1485 auto pSong = pHydrogen->getSong();
1486
1487
1488 if ( pSong == nullptr ) {
1489 ERRORLOG( "no song set" );
1490 return false;
1491 }
1492
1493 INFOLOG( QString( "Deleting pattern [%1]" ).arg( nPatternNumber ) );
1494
1495 auto pPatternList = pSong->getPatternList();
1496 auto pPatternGroupVector = pSong->getPatternGroupVector();
1497 auto pPlayingPatterns = pAudioEngine->getPlayingPatterns();
1498 auto pNextPatterns = pAudioEngine->getNextPatterns();
1499
1500 int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber();
1501 auto pPattern = pPatternList->get( nPatternNumber );
1502
1503 if ( pPattern == nullptr ) {
1504 ERRORLOG( QString( "Pattern [%1] not found" ).arg( nPatternNumber ) );
1505 return false;
1506 }
1507
1508 pAudioEngine->lock( RIGHT_HERE );
1509
1510 // Ensure there is always at least one pattern present in the
1511 // list.
1512 if ( pPatternList->size() == 0 ) {
1513 Pattern* pEmptyPattern = new Pattern( "Pattern 1" );
1514 pPatternList->add( pEmptyPattern );
1515 }
1516
1517 // Delete all instances of the pattern in the pattern group vector
1518 // (columns of the SongEditor)
1519 for ( const auto& ppatternList : *pPatternGroupVector ) {
1520 for ( int ii = 0; ii < ppatternList->size(); ++ii ) {
1521 if ( ppatternList->get( ii ) == pPattern ) {
1522 ppatternList->del( ii );
1523 // there is at most one instance of a pattern per
1524 // column.
1525 continue;
1526 }
1527 }
1528 }
1529
1530 PatternList* pColumn;
1531 // Ensure there are no empty columns in the pattern group vector.
1532 for ( int ii = pPatternGroupVector->size() - 1; ii >= 0; --ii ) {
1533 pColumn = pPatternGroupVector->at( ii );
1534 if ( pColumn->size() == 0 ) {
1535 pPatternGroupVector->erase( pPatternGroupVector->begin() + ii );
1536 delete pColumn;
1537 }
1538 else {
1539 break;
1540 }
1541 }
1542
1543 if ( pHydrogen->isPatternEditorLocked() ) {
1544 pHydrogen->updateSelectedPattern( false );
1545 } else if ( nPatternNumber == nSelectedPatternNumber ) {
1546 pHydrogen->setSelectedPatternNumber( std::max( 0, nPatternNumber - 1 ),
1547 false );
1548 }
1549
1550 // Remove the pattern from the list of of patterns that are played
1551 // next in pattern mode.
1552 // IMPORTANT: it has to be removed from the next patterns list
1553 // _before_ updating the playing patterns.
1554 for ( int ii = 0; ii < pNextPatterns->size(); ++ii ) {
1555 if ( pNextPatterns->get( ii ) == pPattern ) {
1556 pAudioEngine->toggleNextPattern( nPatternNumber );
1557 }
1558 }
1559
1560 // Ensure the pattern is not among the list of currently played
1561 // patterns cached in the audio engine if transport is in pattern
1562 // mode.
1563 pAudioEngine->removePlayingPattern( pPattern );
1564
1565 // Delete the pattern from the list of available patterns.
1566 pPatternList->del( pPattern );
1567
1568 pHydrogen->updateSongSize();
1569
1570 pAudioEngine->unlock();
1571
1572 // Update virtual pattern presentation.
1573 for ( const auto& ppattern : *pPatternList ) {
1574
1576 ppattern->get_virtual_patterns()->find( pPattern );
1577 if ( it != ppattern->get_virtual_patterns()->end() ) {
1578 ppattern->virtual_patterns_del( *it );
1579 }
1580 }
1581
1582 pHydrogen->updateVirtualPatterns();
1583 pHydrogen->setIsModified( true );
1584
1585 delete pPattern;
1586
1587 return true;
1588}
1589
1590bool CoreActionController::toggleGridCell( int nColumn, int nRow ){
1591 auto pHydrogen = Hydrogen::get_instance();
1592
1593 if ( pHydrogen->getSong() == nullptr ) {
1594 ERRORLOG( "no song set" );
1595 return false;
1596 }
1597
1598 auto pSong = pHydrogen->getSong();
1599 auto pPatternList = pSong->getPatternList();
1600 std::vector<PatternList*>* pColumns = pSong->getPatternGroupVector();
1601
1602 if ( nRow < 0 || nRow > pPatternList->size() ) {
1603 ERRORLOG( QString( "Provided row [%1] is out of bound [0,%2]" )
1604 .arg( nRow ).arg( pPatternList->size() ) );
1605 return false;
1606 }
1607
1608 auto pNewPattern = pPatternList->get( nRow );
1609 if ( pNewPattern == nullptr ) {
1610 ERRORLOG( QString( "Unable to obtain Pattern in row [%1]." )
1611 .arg( nRow ) );
1612
1613 return false;
1614 }
1615
1616 pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
1617 if ( nColumn >= 0 && nColumn < pColumns->size() ) {
1618 PatternList *pColumn = ( *pColumns )[ nColumn ];
1619 auto pPattern = pColumn->del( pNewPattern );
1620 if ( pPattern == nullptr ) {
1621 // No pattern in this row. Let's add it.
1622 pColumn->add( pNewPattern );
1623 } else {
1624 // There was already a pattern present and we removed it.
1625 // Ensure that there are no empty columns at the end of
1626 // the song.
1627 for ( int ii = pColumns->size() - 1; ii >= 0; ii-- ) {
1628 PatternList *pColumn = ( *pColumns )[ ii ];
1629 if ( pColumn->size() == 0 ) {
1630 pColumns->erase( pColumns->begin() + ii );
1631 delete pColumn;
1632 } else {
1633 break;
1634 }
1635 }
1636 }
1637 } else if ( nColumn >= pColumns->size() ) {
1638 // We need to add some new columns..
1639 PatternList *pColumn;
1640
1641 for ( int ii = 0; nColumn - pColumns->size() + 1; ii++ ) {
1642 pColumn = new PatternList();
1643 pColumns->push_back( pColumn );
1644 }
1645 pColumn->add( pNewPattern );
1646 } else {
1647 // nColumn < 0
1648 ERRORLOG( QString( "Provided column [%1] is out of bound [0,%2]" )
1649 .arg( nColumn ).arg( pColumns->size() ) );
1650 return false;
1651 }
1652
1653 pHydrogen->updateSongSize();
1654 pHydrogen->updateSelectedPattern( false );
1655
1656 pHydrogen->getAudioEngine()->unlock();
1657
1658 pHydrogen->setIsModified( true );
1659
1660 // Update the SongEditor.
1661 if ( pHydrogen->getGUIState() != Hydrogen::GUIState::unavailable ) {
1663 }
1664
1665 return true;
1666}
1667
1669 auto pPref = Preferences::get_instance();
1670 auto pHydrogen = Hydrogen::get_instance();
1671 auto pAudioEngine = pHydrogen->getAudioEngine();
1672
1673 pAudioEngine->getMetronomeInstrument()->set_volume(
1674 pPref->m_fMetronomeVolume );
1675
1676 // If the GUI is active, we have to update it to reflect the
1677 // changes in the preferences.
1678 if ( pHydrogen->getGUIState() == H2Core::Hydrogen::GUIState::ready ) {
1680 }
1681}
1682
1683void CoreActionController::insertRecentFile( const QString sFilename ){
1684
1685 auto pPref = Preferences::get_instance();
1686
1687 // The most recent file will always be added on top and possible
1688 // duplicates are removed later on.
1689 bool bAlreadyContained = false;
1690
1691 std::vector<QString> recentFiles = pPref->getRecentFiles();
1692
1693 recentFiles.insert( recentFiles.begin(), sFilename );
1694
1695 if ( std::find( recentFiles.begin(), recentFiles.end(),
1696 sFilename ) != recentFiles.end() ) {
1697 // Eliminate all duplicates in the list while keeping the one
1698 // inserted at the beginning. Also, in case the file got renamed,
1699 // remove it's previous name from the list.
1700 std::vector<QString> sTmpVec;
1701 for ( const auto& ssFilename : recentFiles ) {
1702 if ( std::find( sTmpVec.begin(), sTmpVec.end(), ssFilename ) ==
1703 sTmpVec.end() ) {
1704 // Particular file is not contained yet.
1705 sTmpVec.push_back( ssFilename );
1706 }
1707 }
1708
1709 recentFiles = sTmpVec;
1710 }
1711
1712 pPref->setRecentFiles( recentFiles );
1713}
1714}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:59
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
@ Playing
Transport is rolling.
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 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 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.
std::shared_ptr< Drumkit > retrieveDrumkit(const QString &sDrumkitPath, bool *bIsCompressed, QString *sDrumkitDir, QString *sTemporaryFolder)
Loads the drumkit specified in sDrumkitPath.
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 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.
bool upgradeDrumkit(const QString &sDrumkitPath, const QString &sNewPath="")
Upgrades the drumkit found at absolute path sDrumkitPath.
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 activateJackTimebaseMaster(bool bActivate)
(De)activates the usage of Jack timebase master.
bool setStripIsSoloed(int nStrip, bool isSoloed)
bool saveSongAs(const QString &sNewFilename)
Saves the current H2Core::Song to the path provided in sNewFilename.
bool extractDrumkit(const QString &sDrumkitPath, const QString &sTargetDir="")
Extracts the compressed .h2drumkit file in sDrumkitPath into sTargetDir.
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 std::shared_ptr< Drumkit > load(const QString &dk_dir, bool bUpgrade=true, bool bSilent=false)
Load drumkit information from a directory.
Definition Drumkit.cpp:90
static bool install(const QString &sSourcePath, const QString &sTargetPath="", bool bSilent=false)
Extract a .h2drumkit file.
Definition Drumkit.cpp:565
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 QStringList drumkit_xsd_legacy_paths()
static QString drumkit_backup_path(const QString &dk_path)
Create a backup path from a drumkit path.
static QString drumkit_xsd_path()
returns the path to the drumkit XSD (xml schema definition) file
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:91
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 QString drumkit_xsd()
returns the drumkit XSD (xml schema definition) name
static QString xsd_dir()
returns system xsd path
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:122
@ unavailable
No GUI available.
@ ready
There is a working GUI.
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:83
MidiOutput * getMidiOutput() const
Definition Hydrogen.cpp:730
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:649
SoundLibraryDatabase * getSoundLibraryDatabase() const
Definition Hydrogen.h:94
@ Slave
An external program is timebase master and Hydrogen will disregard all tempo markers on the Timeline ...
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
const virtual_patterns_t * get_virtual_patterns() const
get the flattened virtual pattern set
Definition Pattern.h:360
static Pattern * load_file(const QString &pattern_path, std::shared_ptr< InstrumentList > instruments)
load a pattern from a file
Definition Pattern.cpp:106
virtual_patterns_t::const_iterator virtual_patterns_cst_it_t
Definition Pattern.h:60
Manager for User Preferences File (singleton)
Definition Preferences.h:78
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
int m_bJackMasterMode
Specifies if Hydrogen should run as JACK time master.
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...
@ NO_JACK_TRANSPORT
Specifies whether or not to use JACK transport capabilities.
@ USE_JACK_TIME_MASTER
Specifies that Hydrogen is using in the time master mode and will thus control specific aspects of th...
Definition Preferences.h:96
@ NO_JACK_TIME_MASTER
Specifies that Hydrogen is note using in the time master mode.
@ USE_JACK_TRANSPORT
Specifies whether or not to use JACK transport capabilities.
Definition Preferences.h:89
int m_bJackTransportMode
Specifies whether or not Hydrogen will use the JACK transport system.
static std::shared_ptr< Song > load(const QString &sFilename, bool bSilent=false)
Load a song from file.
Definition Song.cpp:187
@ Finishing
Transport is still in loop mode (frames and ticks larger than song size are allowed) but playback end...
static std::shared_ptr< Song > getEmptySong()
Definition Song.cpp:953
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, const QString &schemapath=nullptr, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:296
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
@ EVENT_JACK_TIMEBASE_STATE_CHANGED
Toggles the button indicating the usage Jack timebase master and informs the GUI about a state change...
Definition EventQueue.h:144