hydrogen 1.2.6
MainForm.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include <core/EventQueue.h>
24#include <core/Version.h>
25#include <core/Hydrogen.h>
28#include <core/SMF/SMF.h>
29#include <core/Timeline.h>
30#include <core/Helpers/Files.h>
31#include <core/Basics/Pattern.h>
38#include <core/IO/MidiCommon.h>
39#include <core/NsmClient.h>
41
42#include "AboutDialog.h"
43#include "AudioEngineInfoForm.h"
44#include "CommonStrings.h"
45#include "ExportSongDialog.h"
46#include "ExportMidiDialog.h"
47#include "HydrogenApp.h"
48#include "Skin.h"
49#include "InstrumentRack.h"
50#include "MainForm.h"
51#include "PlayerControl.h"
52#include "LadspaFXProperties.h"
54#include "UndoActions.h"
55#include "Widgets/InfoBar.h"
56#include "Widgets/FileDialog.h"
57
58#include "Director.h"
59#include "Mixer/Mixer.h"
70
71#include <QtGui>
72#include <QtWidgets>
73
74#ifndef WIN32
75#include <sys/time.h>
76#include <sys/socket.h>
77#endif
78
79#ifdef H2CORE_HAVE_LASH
80#include <lash-1.0/lash/lash.h>
82#endif
83
84#include <memory>
85#include <cassert>
86
87using namespace H2Core;
88
90
91MainForm::MainForm( QApplication * pQApplication, QString sSongFilename )
92 : QMainWindow( nullptr )
94{
96 auto pHydrogen = H2Core::Hydrogen::get_instance();
97
98 setObjectName( "MainForm" );
99
100#ifndef WIN32
101 if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigusr1Fd)) {
102 qFatal("Couldn't create HUP socketpair");
103 }
104 snUsr1 = new QSocketNotifier(sigusr1Fd[1], QSocketNotifier::Read, this);
105 connect(snUsr1, SIGNAL(activated(int)), this, SLOT( handleSigUsr1() ));
106#endif
107
108 m_pQApp = pQApplication;
109
110 m_pQApp->processEvents();
111
112 // When using the Non Session Management system, the new Song
113 // will be loaded by the NSM client singleton itself and not
114 // by the MainForm. The latter will just access the already
115 // loaded Song.
116 if ( ! pHydrogen->isUnderSessionManagement() ){
117 std::shared_ptr<H2Core::Song>pSong = nullptr;
118
119 if ( sSongFilename.isEmpty() ) {
120 if ( pPref->isRestoreLastSongEnabled() ) {
121 sSongFilename = pPref->getLastSongFilename();
122 }
123 }
124
125 bool bRet = false;
126 if ( !sSongFilename.isEmpty() ) {
127 if ( sSongFilename == H2Core::Filesystem::empty_song_path() ) {
129 } else {
130 bRet = HydrogenApp::openSong( sSongFilename );
131 }
132 }
133
134 if ( sSongFilename.isEmpty() || ! bRet ) {
136 HydrogenApp::openSong( pSong );
137 }
138 }
139
140 QFont font( pPref->getApplicationFontFamily(), getPointSize( pPref->getFontSize() ) );
141 setFont( font );
142 m_pQApp->setFont( font );
143
144 h2app = new HydrogenApp( this );
146 h2app->addEventListener( this );
151
152 h2app->showStatusBarMessage( tr("Hydrogen Ready.") );
153
155
156 // we need to do all this to support the keyboard playing
157 // for all the window modes
158 h2app->getMixer()->installEventFilter (this);
159 h2app->getPatternEditorPanel()->installEventFilter (this);
160 h2app->getSongEditorPanel()->installEventFilter (this);
161 h2app->getPlayerControl()->installEventFilter(this);
162 InstrumentEditorPanel::get_instance()->installEventFilter(this);
163 h2app->getAudioEngineInfoForm()->installEventFilter(this);
164 h2app->getDirector()->installEventFilter(this);
165 // h2app->getPlayListDialog()->installEventFilter(this);
166 installEventFilter( this );
167
168 connect( &m_AutosaveTimer, SIGNAL(timeout()), this, SLOT(onAutoSaveTimer()));
170
171#ifdef H2CORE_HAVE_LASH
172
173 if ( pPref->useLash() ){
174 LashClient* lashClient = LashClient::get_instance();
175 if (lashClient->isConnected())
176 {
177 // send alsa client id now since it can only be sent
178 // after the audio engine has been started.
179 if ( pPref->m_sMidiDriver == "ALSA" ) {
180 // infoLog("[LASH] Sending alsa seq id to LASH server");
181 lashClient->sendAlsaClientId();
182 }
183 // start timer for polling lash events
184 lashPollTimer = new QTimer(this);
185 connect( lashPollTimer, SIGNAL( timeout() ), this, SLOT( onLashPollTimer() ) );
186 lashPollTimer->start(500);
187 }
188 }
189#endif
190
191 //playlist display timer
192 QTimer *playlistDisplayTimer = new QTimer(this);
193 connect( playlistDisplayTimer, SIGNAL( timeout() ), this, SLOT( onPlaylistDisplayTimer() ) );
194 playlistDisplayTimer->start(30000); // update player control at
195 // ~ playlist display timer
196
197 //beatcouter
198 pHydrogen->setBcOffsetAdjust();
199
200 m_pUndoView = new QUndoView(h2app->m_pUndoStack);
201 m_pUndoView->setWindowTitle(tr("Undo history"));
202
203 //restore last playlist
204 if ( pPref->isRestoreLastPlaylistEnabled() &&
205 ! pPref->getLastPlaylistFilename().isEmpty() ) {
206 bool bLoadSuccessful = h2app->getPlayListDialog()->loadListByFileName(
207 pPref->getLastPlaylistFilename() );
208 if ( bLoadSuccessful ) {
209 if ( pPref->getPlaylistDialogProperties().visible ){
210 // If there was a playlist used during the last
211 // session and it was still visible/shown when closing
212 // Hydrogen, bring it up again.
214 }
215 }
216 else {
217 _ERRORLOG( QString( "Unable to load last playlist [%1]" )
218 .arg( pPref->getLastPlaylistFilename() ) );
219 }
220 }
221
222 // Must be done _after_ the creation of the HydrogenApp instance.
223 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
224
225 // Check whether the audio driver could be loaded based on the
226 // content of the config file
227 if ( pHydrogen->getAudioOutput() == nullptr ||
228 dynamic_cast<NullDriver*>(pHydrogen->getAudioOutput()) != nullptr ) {
229 QMessageBox::warning(
230 this, "Hydrogen", QString( "%1 [%2]\n%3" )
231 .arg( pCommonStrings->getAudioDriverStartError() )
232 .arg( Preferences::audioDriverToQString( pPref->m_audioDriver ) )
233 .arg( pCommonStrings->getAudioDriverErrorHint() ) );
234 }
235}
236
237
239{
240 auto pHydrogen = Hydrogen::get_instance();
241
242 // Remove the autosave file in case all modifications already have
243 // been written to disk.
244 if ( ! pHydrogen->getSong()->getIsModified() ) {
245 QFile file( getAutoSaveFilename() );
246 file.remove();
247 }
248
249 if ( (Hydrogen::get_instance()->getAudioEngine()->getState() == H2Core::AudioEngine::State::Playing) ) {
251 }
252
253 hide();
254
255 delete m_pUndoView;
256
257 if (h2app != nullptr) {
258 delete Playlist::get_instance();
259 delete h2app;
260 h2app = nullptr;
261 }
262
263}
264
269{
270 // menubar
271 QMenuBar *pMenubar = new QMenuBar( this );
272 pMenubar->setObjectName( "MainMenu" );
273 setMenuBar( pMenubar );
274
275 // FILE menu
276 m_pFileMenu = pMenubar->addMenu( tr( "Pro&ject" ) );
277
278 // Then under session management a couple of options will be named
279 // differently and some must be even omitted.
280 const bool bUnderSessionManagement = H2Core::Hydrogen::get_instance()->isUnderSessionManagement();
281
282 QString sLabelNew, sLabelOpen, sLabelOpenRecent, sLabelSaveAs, sLabelOpenDemo;
283
284 if ( bUnderSessionManagement ) {
285 /*: When Hydrogen is under session management the path the
286 song is stored to can not be changed by the user. This option
287 allows to replace the current song with an empty one.*/
288 sLabelNew = tr( "Replace With &New Song" );
289 /*: When Hydrogen is under session management the path the
290 song is stored to can not be changed by the user. This option
291 allows to replace the current song with one chosen by the
292 user via a file browser widget.*/
293 sLabelOpen = tr( "Imp&ort Into Session" );
294 /*: When Hydrogen is under session management the path the
295 song is stored to can not be changed by the user. This option
296 allows to replace the current song with one chosen recently
297 used by the user.*/
298 sLabelOpenRecent = tr( "Import &Recent Into Session" );
299 /*: When Hydrogen is under session management the path the
300 song is stored to can not be changed by the user. This option
301 allows the user store the current song in a .h2song anywhere
302 on her system. The filepath of the current song won't be
303 altered.*/
304 sLabelSaveAs = tr( "Export From Session &As..." );
305 } else {
306 sLabelNew = tr( "&New" );
307 sLabelOpen = tr( "&Open" );
308 sLabelOpenRecent = tr( "Open &Recent" );
309 sLabelSaveAs = tr( "Save &As..." );
310 sLabelOpenDemo = tr( "Open &Demo" );
311 }
312
313 auto pActionFileNew = m_pFileMenu->addAction(
314 sLabelNew, this, SLOT( action_file_new() ) );
315 pActionFileNew->setShortcut( QKeySequence( "Ctrl+N" ) );
316
317 m_pFileMenu->addSeparator(); // -----
318
319 m_pFileMenu->addAction( tr( "Song Properties" ), this, SLOT( action_file_songProperties() ) );
320
321 m_pFileMenu->addSeparator(); // -----
322
323 auto pActionFileOpen = m_pFileMenu->addAction(
324 sLabelOpen, this, SLOT( action_file_open() ) );
325 pActionFileOpen->setShortcut( QKeySequence( "Ctrl+O" ) );
326 if ( ! bUnderSessionManagement ) {
327 auto pActionFileDemo = m_pFileMenu->addAction(
328 sLabelOpenDemo, this, SLOT( action_file_openDemo() ) );
329 pActionFileDemo->setShortcut( QKeySequence( "Ctrl+D" ) );
330 }
331 m_pRecentFilesMenu = m_pFileMenu->addMenu( sLabelOpenRecent );
332
333 m_pFileMenu->addSeparator(); // -----
334
335 auto pActionFileSave = m_pFileMenu->addAction(
336 tr( "&Save" ), this, SLOT( action_file_save() ) );
337 pActionFileSave->setShortcut( QKeySequence( "Ctrl+S" ) );
338 auto pActionFileSaveAs = m_pFileMenu->addAction(
339 sLabelSaveAs, this, SLOT( action_file_save_as() ) );
340 pActionFileSaveAs->setShortcut( QKeySequence( "Ctrl+Shift+S" ) );
341
342 m_pFileMenu->addSeparator(); // -----
343
344 auto pActionOpenPattern = m_pFileMenu->addAction (
345 tr( "Open &Pattern" ), this, SLOT( action_file_openPattern() ) );
346 pActionOpenPattern->setShortcut( QKeySequence( "Ctrl+Shift+P" ) );
347 auto pActionExportPattern = m_pFileMenu->addAction(
348 tr( "E&xport Pattern As..." ), this, SLOT( action_file_export_pattern_as() ) );
349 pActionExportPattern->setShortcut( QKeySequence( "Ctrl+P" ) );
350
351 m_pFileMenu->addSeparator(); // -----
352
353 auto pActionExportMidi = m_pFileMenu->addAction(
354 tr( "Export &MIDI File" ), this, SLOT( action_file_export_midi() ) );
355 pActionExportMidi->setShortcut( QKeySequence( "Ctrl+M" ) );
356 auto pActionExportSong = m_pFileMenu->addAction(
357 tr( "&Export Song" ), this, SLOT( action_file_export() ) );
358 pActionExportSong->setShortcut( QKeySequence( "Ctrl+E" ) );
359 auto pActionExportLilyPond = m_pFileMenu->addAction(
360 tr( "Export &LilyPond File" ), this, SLOT( action_file_export_lilypond() ) );
361 pActionExportLilyPond->setShortcut( QKeySequence( "Ctrl+L" ) );
362
363
364#ifndef Q_OS_MACX
365 m_pFileMenu->addSeparator(); // -----
366
367 auto pActionQuit = m_pFileMenu->addAction(
368 tr("&Quit"), this, SLOT( action_file_exit() ) );
369 pActionQuit->setShortcut( QKeySequence( "Ctrl+Q" ) );
370#endif
371
373 connect( m_pRecentFilesMenu, SIGNAL( triggered(QAction*) ), this, SLOT( action_file_open_recent(QAction*) ) );
374 // ~ FILE menu
375
376 // Undo menu
377 m_pUndoMenu = pMenubar->addMenu( tr( "&Undo" ) );
378 auto pActionUndo = m_pUndoMenu->addAction(
379 tr( "&Undo" ), this, SLOT( action_undo() ) );
380 pActionUndo->setShortcut( QKeySequence( "Ctrl+Z" ) );
381 auto pActionRedo = m_pUndoMenu->addAction(
382 tr( "&Redo" ), this, SLOT( action_redo() ) );
383 pActionRedo->setShortcut( QKeySequence( "Shift+Ctrl+Z" ) );
384 m_pUndoMenu->addAction( tr( "Undo &History" ), this, SLOT( openUndoStack() ) );
385
386 // DRUMKITS MENU
387 m_pDrumkitsMenu = pMenubar->addMenu( tr( "Drum&kits" ) );
388 m_pDrumkitsMenu->addAction( tr( "&New" ), this, SLOT( action_instruments_clearAll() ) );
389 m_pDrumkitsMenu->addAction( tr( "&Open" ), this, SLOT( action_banks_open() ) );
390 m_pDrumkitsMenu->addAction( tr( "&Properties" ), this, SLOT( action_banks_properties() ) );
391
392 m_pDrumkitsMenu->addSeparator(); // -----
393
394 m_pDrumkitsMenu->addAction( tr( "&Save" ), this, SLOT( action_instruments_saveLibrary() ) );
395 m_pDrumkitsMenu->addAction( tr( "Save &As" ), this, SLOT( action_instruments_saveAsLibrary() ) );
396
397 m_pDrumkitsMenu->addSeparator(); // -----
398
399 m_pDrumkitsMenu->addAction( tr( "&Export" ), this, SLOT( action_instruments_exportLibrary() ) );
400 m_pDrumkitsMenu->addAction( tr( "&Import" ), this, SLOT( action_instruments_importLibrary() ) );
401 m_pDrumkitsMenu->addAction( tr( "On&line Import" ), this, SLOT( action_instruments_onlineImportLibrary() ) );
402
403 // INSTRUMENTS MENU
404 m_pInstrumentsMenu = pMenubar->addMenu( tr( "In&struments" ) );
405 m_pInstrumentsMenu->addAction( tr( "Add &Instrument" ), this, SLOT( action_instruments_addInstrument() ) );
406 m_pInstrumentsMenu->addAction( tr( "Clea&r All" ), this, SLOT( action_instruments_clearAll() ) );
407
408 m_pInstrumentsMenu->addSeparator(); // -----
409
410 m_pInstrumentsMenu->addAction( tr( "Add &Component" ), this, SLOT( action_instruments_addComponent() ) );
411
412 // VIEW MENU
413 m_pViewMenu = pMenubar->addMenu( tr( "&View" ) );
414
415 m_pViewPlaylistEditorAction = m_pViewMenu->addAction( tr("Play&list Editor"), this, SLOT( action_window_showPlaylistDialog() ) );
416 m_pViewPlaylistEditorAction->setCheckable( true );
418 tr("&Director"), this, SLOT( action_window_show_DirectorWidget() ) );
419 m_pViewDirectorAction->setShortcut( QKeySequence( "Alt+D" ) );
420 m_pViewDirectorAction->setCheckable( true );
421
422 m_pFileMenu->addSeparator();
423 m_pViewMixerAction = m_pViewMenu->addAction(
424 tr("&Mixer"), this, SLOT( action_window_showMixer() ) );
425 m_pViewMixerAction->setShortcut( QKeySequence( "Alt+M" ) );
426 m_pViewMixerAction->setCheckable( true );
427 update_mixer_checkbox(); // if checkbox need to be checked.
428
430 tr("&Instrument Rack"), this, SLOT( action_window_showInstrumentRack() ) );
431 m_pViewMixerInstrumentRackAction->setShortcut( QKeySequence( "Alt+I" ) );
432 m_pViewMixerInstrumentRackAction->setCheckable( true );
433 update_instrument_checkbox( Preferences::get_instance()->getInstrumentRackProperties().visible );
434
436 tr("&Automation Path"), this, SLOT( action_window_showAutomationArea() ) );
437 m_pViewAutomationPathAction->setShortcut( QKeySequence( "Alt+A" ) );
438 m_pViewAutomationPathAction->setCheckable( true );
440
441 m_pViewMenu->addSeparator(); // -----
442
443 m_pViewTimelineAction = m_pViewMenu->addAction( tr("&Timeline"), this, SLOT( action_window_showTimeline() ) );
444 m_pViewTimelineAction->setCheckable( true );
445
446 m_pViewPlaybackTrackAction = m_pViewMenu->addAction( tr("&Playback Track"), this, SLOT( action_window_showPlaybackTrack() ) );
447 m_pViewPlaybackTrackAction->setCheckable( true );
448
449 m_pViewPlaybackTrackActionGroup = new QActionGroup( this );
453
454 m_pViewMenu->addSeparator(); // -----
455
456 auto pActionFullScreen = m_pViewMenu->addAction(
457 tr("&Full screen"), this, SLOT( action_window_toggleFullscreen() ) );
458 pActionFullScreen->setShortcut( QKeySequence( "Alt+F" ) );
459
460
461 // Options menu
462 m_pOptionsMenu = pMenubar->addMenu( tr( "&Options" ));
463
464 m_pInputModeMenu = m_pOptionsMenu->addMenu( tr( "Input &Mode" ) );
466 tr( "&Instrument" ), this, SLOT( action_inputMode_instrument() ) );
467 m_pInstrumentAction->setShortcut( QKeySequence( "Ctrl+Alt+I" ) );
468 m_pInstrumentAction->setCheckable( true );
469
471 tr( "&Drumkit" ), this, SLOT( action_inputMode_drumkit() ) );
472 m_pDrumkitAction->setShortcut( QKeySequence( "Ctrl+Alt+D" ) );
473 m_pDrumkitAction->setCheckable( true );
474
475 if( Preferences::get_instance()->__playselectedinstrument )
476 {
477 m_pInstrumentAction->setChecked( true );
478 m_pDrumkitAction->setChecked (false );
479 } else {
480 m_pInstrumentAction->setChecked( false );
481 m_pDrumkitAction->setChecked (true );
482 }
483
484 auto pActionPreferences = m_pOptionsMenu->addAction(
485 tr("&Preferences"), this, SLOT( showPreferencesDialog() ) );
486 pActionPreferences->setShortcut( QKeySequence( "Alt+P" ) );
487
488 // ~ Tools menu
489
490
491 Logger *pLogger = Logger::get_instance();
492 if ( pLogger->bit_mask() >= 1 ) {
493 // DEBUG menu
494 m_pDebugMenu = pMenubar->addMenu( tr("De&bug") );
495 m_pDebugMenu->addAction( tr( "Show &Audio Engine Info" ), this, SLOT( action_debug_showAudioEngineInfo() ) );
496 m_pDebugMenu->addAction( tr( "Show &Filesystem Info" ), this, SLOT( action_debug_showFilesystemInfo() ) );
497
498 m_pLogLevelMenu = m_pDebugMenu->addMenu( tr( "&Log Level" ) );
499 m_pLogLevelMenu->addAction( tr( "&None" ), this, SLOT( action_debug_logLevel_none() ) );
500 m_pLogLevelMenu->addAction( tr( "&Error" ), this, SLOT( action_debug_logLevel_info() ) );
501 m_pLogLevelMenu->addAction( tr( "&Warning" ), this, SLOT( action_debug_logLevel_warn() ) );
502 m_pLogLevelMenu->addAction( tr( "&Info" ), this, SLOT( action_debug_logLevel_info() ) );
503 m_pLogLevelMenu->addAction( tr( "&Debug" ), this, SLOT( action_debug_logLevel_debug() ) );
504
505 m_pDebugMenu->addAction( tr( "&Open Log File" ), this, SLOT( action_debug_openLogfile()) );
506
507 if(pLogger->bit_mask() == 8) { // hydrogen -V8 list object map in console
508 m_pDebugMenu->addAction( tr( "&Print Objects" ), this, SLOT( action_debug_printObjects() ) );
509 }
510 // ~ DEBUG menu
511 }
512
513 // INFO menu
514 m_pInfoMenu = pMenubar->addMenu( tr( "I&nfo" ) );
515 auto pActionUserManual = m_pInfoMenu->addAction(
516 tr("User &Manual"), this, SLOT( showUserManual() ) );
517 pActionUserManual->setShortcut( QKeySequence( "Ctrl+?" ) );
518 m_pInfoMenu->addSeparator();
519 auto pActionAbout = m_pInfoMenu->addAction( tr("&About"), this, SLOT( action_help_about() ) );
520 pActionAbout->setShortcut( QKeySequence( tr("", "Info|About") ) );
521 m_pInfoMenu->addAction( tr("&Report Bug"), this, SLOT( action_report_bug() ));
522 m_pInfoMenu->addAction( tr("&Donate"), this, SLOT( action_donate() ));
523 // ~ INFO menu
524}
525
527 int nAutosavesPerHour = Preferences::get_instance()->m_nAutosavesPerHour;
528
529 if ( nAutosavesPerHour > 0 ) {
530 if ( nAutosavesPerHour > 360 ) {
531 ERRORLOG( QString( "Too many autosaves per hour set [%1]. Using 360 - once a second - instead." )
532 .arg( nAutosavesPerHour ) );
533 nAutosavesPerHour = 360;
534 }
535 m_AutosaveTimer.start( std::round( 60 * 60 * 1000 /
536 static_cast<float>(nAutosavesPerHour) ) );
537 } else {
538 DEBUGLOG( "Autosave disabled" );
539 }
540}
541
543{
544#ifdef H2CORE_HAVE_LASH
545 if ( Preferences::get_instance()->useLash() ){
547
548 if (!client->isConnected())
549 {
550 WARNINGLOG("[LASH] Not connected to server!");
551 return;
552 }
553
554 bool keep_running = true;
555
556 lash_event_t* event;
557
558 std::string songFilename;
559 QString filenameSong;
560 std::shared_ptr<Song> song = Hydrogen::get_instance()->getSong();
561 // Extra parentheses for -Wparentheses
562 while ( (event = client->getNextEvent()) ) {
563
564 switch (lash_event_get_type(event)) {
565
566 case LASH_Save_File:
567
568 INFOLOG("[LASH] Save file");
569
570 songFilename.append(lash_event_get_string(event));
571 songFilename.append("/hydrogen.h2song");
572
573 filenameSong = QString::fromLocal8Bit( songFilename.c_str() );
574 song->setFilename( filenameSong );
576
577 client->sendEvent(LASH_Save_File);
578
579 break;
580
581 case LASH_Restore_File:
582
583 songFilename.append(lash_event_get_string(event));
584 songFilename.append("/hydrogen.h2song");
585
586 INFOLOG( QString("[LASH] Restore file: %1")
587 .arg( songFilename.c_str() ) );
588
589 filenameSong = QString::fromLocal8Bit( songFilename.c_str() );
590
591 HydrogenApp::get_instance()->openSong( filenameSong );
592
593 client->sendEvent(LASH_Restore_File);
594
595 break;
596
597 case LASH_Quit:
598
599 // infoLog("[LASH] Quit!");
600 keep_running = false;
601
602 break;
603
604 default:
605 ;
606 // infoLog("[LASH] Got unknown event!");
607
608 }
609
610 lash_event_destroy(event);
611
612 }
613
614 if (!keep_running)
615 {
616 lashPollTimer->stop();
618 }
619 }
620#endif
621}
622
624{
625 QMessageBox donationDialog;
626 donationDialog.setText( tr( "Hydrogen is an open source project which is developed by multiple people in their spare time. By making a donation you can say 'thank you' to the involved persons." ) );
627 donationDialog.setStandardButtons( QMessageBox::Cancel );
628 donationDialog.addButton( tr( "&Donate!" ), QMessageBox::AcceptRole );
629
630 int nRet = donationDialog.exec();
631
632 if ( nRet == QMessageBox::AcceptRole ) {
633 QDesktopServices::openUrl(QUrl::fromEncoded("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=sebastian%2emoors%40gmail%2ecom&lc=DE&item_name=Hydrogen%20donation&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHostedGuest"));
634 }
635}
636
639{
640 bool proceed = handleUnsavedChanges();
641 if(!proceed) {
642 return false;
643 }
644 closeAll();
645 return true;
646}
647
648
649
651{
652 const bool bUnderSessionManagement = H2Core::Hydrogen::get_instance()->isUnderSessionManagement();
653
654 Hydrogen * pHydrogen = Hydrogen::get_instance();
656 pHydrogen->sequencer_stop();
657 }
658
659 bool proceed = handleUnsavedChanges();
660 if(!proceed) {
661 return;
662 }
663
664 std::shared_ptr<Song> pSong = Song::getEmptySong();
665
666 if ( bUnderSessionManagement ) {
667 // Just a single click will allow the user to discard the
668 // current song and replace it with an empty one with no way
669 // of undoing the action. Therefore, a warning popup will
670 // check whether the action was intentional.
671 QMessageBox confirmationBox;
672 confirmationBox.setText( tr( "Replace current song with empty one?" ) );
673 confirmationBox.setInformativeText( tr( "You won't be able to undo this action after saving the new song! Please export the current song from the session first in order to keep it." ) );
674 confirmationBox.setStandardButtons( QMessageBox::Yes | QMessageBox::No );
675 confirmationBox.setDefaultButton( QMessageBox::No );
676
677 int confirmationChoice = confirmationBox.exec();
678
679 if ( confirmationChoice == QMessageBox::No ) {
680 return;
681 }
682 }
683
684 // Since the user explicitly chooses to open an empty song, we do
685 // not attempt to recover the autosave file generated while last
686 // working on an empty song but, instead, remove the corresponding
687 // autosave file in order to start fresh.
688 QFileInfo fileInfo( Filesystem::empty_song_path() );
689 QString sBaseName( fileInfo.completeBaseName() );
690 if ( sBaseName.startsWith( "." ) ) {
691 sBaseName.remove( 0, 1 );
692 }
693 QFileInfo autoSaveFile( QString( "%1/.%2.autosave.h2song" )
694 .arg( fileInfo.absoluteDir().absolutePath() )
695 .arg( sBaseName ) );
696 if ( autoSaveFile.exists() ) {
697 Filesystem::rm( autoSaveFile.absoluteFilePath() );
698 }
699
700 h2app->openSong( pSong );
701
702 // The drumkit of the new song will linked into the session
703 // folder during the next song save.
704 pHydrogen->setSessionDrumkitNeedsRelinking( true );
705}
706
707
708
710{
711 auto pHydrogen = Hydrogen::get_instance();
712 auto pSong = pHydrogen->getSong();
713
714 if ( pSong == nullptr ) {
715 return false;
716 }
717
718 const bool bUnderSessionManagement = pHydrogen->isUnderSessionManagement();
719 if ( bUnderSessionManagement &&
720 pHydrogen->getSessionDrumkitNeedsRelinking() ) {
721 // When used under session management "save as" will be used
722 // for exporting and Hydrogen is allowed to
723 // be in a transient state which is not ready for export. This
724 // way the user is able to undo e.g. loading a drumkit by
725 // closing the session without storing and the overall state
726 // is not getting bricked during unexpected shut downs.
727 //
728 // We will prompt for saving the changes applied to the
729 // drumkit usage and require the user to exit this transient
730 // state first.
731 if ( QMessageBox::information(
732 this, "Hydrogen",
733 tr( "\nThere have been recent changes to the drumkit settings.\n"
734 "The session needs to be saved before exporting will can be continued.\n" ),
735 QMessageBox::Save | QMessageBox::Cancel,
736 QMessageBox::Save )
737 == QMessageBox::Cancel ) {
738 INFOLOG( "Exporting cancelled at relinking" );
739 return false;
740 }
741
742 if ( ! action_file_save() ) {
743 ERRORLOG( "Unable to save file" );
744 return false;
745 }
746 }
747
749 if ( ! Filesystem::dir_writable( sPath, false ) ){
750 sPath = Filesystem::songs_dir();
751 }
752
753 FileDialog fd(this);
754 fd.setFileMode( QFileDialog::AnyFile );
755 fd.setNameFilter( Filesystem::songs_filter_name );
756 fd.setAcceptMode( QFileDialog::AcceptSave );
757 fd.setDirectory( sPath );
758
759 if ( bUnderSessionManagement ) {
760 fd.setWindowTitle( tr( "Export song from Session" ) );
761 } else {
762 fd.setWindowTitle( tr( "Save song" ) );
763 }
764
765 fd.setSidebarUrls( fd.sidebarUrls() << QUrl::fromLocalFile( Filesystem::songs_dir() ) );
766
767 QString sDefaultFilename;
768
769 // Cachce a couple of things we have to restore when under session
770 // management.
771 QString sLastFilename = pSong->getFilename();
772 QString sLastLoadedDrumkitPath = pSong->getLastLoadedDrumkitPath();
773
774 if ( sLastFilename == Filesystem::empty_song_path() ) {
775 sDefaultFilename = Filesystem::default_song_name();
776 } else if ( sLastFilename.isEmpty() ) {
777 sDefaultFilename = pHydrogen->getSong()->getName();
778 } else {
779 QFileInfo fileInfo( sLastFilename );
780 sDefaultFilename = fileInfo.completeBaseName();
781 }
782 sDefaultFilename += Filesystem::songs_ext;
783
784 fd.selectFile( sDefaultFilename );
785
786 if (fd.exec() == QDialog::Accepted) {
787 QString sNewFilename = fd.selectedFiles().first();
788
789 if ( ! sNewFilename.isEmpty() ) {
790 Preferences::get_instance()->setLastSaveSongAsDirectory( fd.directory().absolutePath( ) );
791
792 if ( ! sNewFilename.endsWith( Filesystem::songs_ext ) ) {
793 sNewFilename += Filesystem::songs_ext;
794 }
795
796#ifdef H2CORE_HAVE_OSC
797 // In a session all main samples (last loaded drumkit) are
798 // taken from the session folder itself (either via a
799 // symlink or a copy of the whole drumkit). When exporting
800 // a song, these "local" references have to be replaced by
801 // global ones (drumkits in the system's or user's data
802 // folder).
803 if ( bUnderSessionManagement ) {
804 pHydrogen->setSessionIsExported( true );
805 int nRet = NsmClient::dereferenceDrumkit( pSong );
806 if ( nRet == -2 ) {
807 QMessageBox::warning( this, "Hydrogen",
808 tr( "Drumkit [%1] used in session could not found on your system. Please install it in to make the exported song work properly." )
809 .arg( pSong->getLastLoadedDrumkitName() ) );
810 }
811 }
812#endif
813
814 // We do not use the CoreActionController::saveSongAs
815 // function directly since action_file_save as does some
816 // additional checks and prompts the user a warning dialog
817 // if required.
818 if ( ! action_file_save( sNewFilename ) ) {
819 ERRORLOG( "Unable to save song" );
820 return false;
821 }
822 }
823
824#ifdef H2CORE_HAVE_OSC
825 // When Hydrogen is under session management, we only copy a
826 // backup of the song to a different place but keep working on
827 // the original.
828 if ( bUnderSessionManagement ) {
829 pSong->setFilename( sLastFilename );
830 NsmClient::replaceDrumkitPath( pSong, sLastLoadedDrumkitPath );
831 h2app->showStatusBarMessage( tr("Song exported as: ") + sDefaultFilename );
832 pHydrogen->setSessionIsExported( false );
833 }
834 else {
835 h2app->showStatusBarMessage( tr("Song saved as: ") + sDefaultFilename );
836 }
837#else
838 h2app->showStatusBarMessage( tr("Song saved as: ") + sDefaultFilename );
839#endif
840
841 h2app->updateWindowTitle();
842 }
843
844 return true;
845}
846
847
848
850{
851 return action_file_save( "" );
852}
853bool MainForm::action_file_save( const QString& sNewFilename )
854{
855 auto pHydrogen = H2Core::Hydrogen::get_instance();
856 auto pSong = pHydrogen->getSong();
857
858 if ( pSong == nullptr ) {
859 return false;
860 }
861
862 auto pCoreActionController = pHydrogen->getCoreActionController();
863 QString sFilename = pSong->getFilename();
864
865 if ( sNewFilename.isEmpty() &&
866 ( sFilename.isEmpty() ||
867 sFilename == Filesystem::empty_song_path() ) ) {
868 // The empty song is treated differently in order to allow
869 // recovering changes and unsaved sessions. Therefore the
870 // users are ask to store a new song using a different file
871 // name.
872 return action_file_save_as();
873 }
874
875 if ( pSong->hasMissingSamples() ) {
876 if ( QMessageBox::information( this, "Hydrogen",
877 tr( "Some samples used by this song failed to load. If you save the song now "
878 "these missing samples will be removed from the song entirely.\n"
879 "Are you sure you want to save?" ),
880 QMessageBox::Save | QMessageBox::Cancel,
881 QMessageBox::Save )
882 == QMessageBox::Cancel ) {
883 return false;
884 }
885 pSong->clearMissingSamples();
886 }
887
888 // Clear the pattern editor selection to resolve any duplicates
890
891 bool bSaved;
892 if ( sNewFilename.isEmpty() ) {
893 bSaved = pCoreActionController->saveSong();
894 } else {
895 bSaved = pCoreActionController->saveSongAs( sNewFilename );
896 }
897
898 if( ! bSaved ) {
899 QMessageBox::warning( this, "Hydrogen", tr("Could not save song.") );
900 return false;
901 }
902
903 h2app->showStatusBarMessage( tr("Song saved into") + QString(": ") +
904 sFilename );
905 return true;
906}
907
908
910{
911 if( !Preferences::get_instance()->__playselectedinstrument )
912 {
914 m_pDrumkitAction->setChecked (false );
915 }
916 m_pInstrumentAction->setChecked( true );
917}
918
920{
921 if( Preferences::get_instance()->__playselectedinstrument )
922 {
924 m_pInstrumentAction->setChecked( false );
925 }
926 m_pDrumkitAction->setChecked (true );
927}
928
930 AboutDialog *dialog = new AboutDialog( nullptr );
931 dialog->exec();
932}
933
935{
936 QDesktopServices::openUrl(QString("https://github.com/hydrogen-music/hydrogen/issues"));
937}
938
939// Find and open (a translation of) the manual appropriate for the user's preferences and locale
941{
942 QString sDocPath = H2Core::Filesystem::doc_dir();
943 QString sPreferredLanguage = Preferences::get_instance()->getPreferredLanguage();
944 QStringList languages;
945
946 if ( !sPreferredLanguage.isNull() ) {
947 languages << sPreferredLanguage;
948 }
949 languages << QLocale::system().uiLanguages()
950 << "en"; // English as fallback
951
952 // Find manual in filesystem
953 for ( QString sLang : languages ) {
954 QStringList sCandidates ( sLang );
955 QStringList s = sLang.split('-');
956 if ( s.size() != 1 ) {
957 sCandidates << s[0];
958 }
959 for ( QString sCandidate : sCandidates ) {
960 QString sManualPath = QString( "%1/manual_%2.html" ) .arg( sDocPath ).arg( sCandidate );
961 if ( Filesystem::file_exists( sManualPath ) ) {
962 QDesktopServices::openUrl( QUrl::fromLocalFile( sManualPath ) );
963 return;
964 }
965 }
966 }
967
968 // No manual found, not even the default English one. This must be a broken installation, so let's open
969 // the online manual as a sensible fallback option.
970
971 QDesktopServices::openUrl( QString( "http://hydrogen-music.org/documentation/manual/manual_en.html" ) );
972
973}
974
976{
977 Hydrogen *pHydrogen = Hydrogen::get_instance();
978
979 if ( ( Hydrogen::get_instance()->getAudioEngine()->getState() == H2Core::AudioEngine::State::Playing ) ) {
981 }
982
983 if ( nPatternRow == -1 ) {
984 nPatternRow = pHydrogen->getSelectedPatternNumber();
985 }
986
987 if ( nPatternRow == -1 ) {
988 QMessageBox::warning( this, "Hydrogen", tr("No pattern selected.") );
989 return;
990 }
991
992 std::shared_ptr<Song> pSong = pHydrogen->getSong();
993
994 Pattern *pPattern = pSong->getPatternList()->get( nPatternRow );
995
997 if ( ! Filesystem::dir_writable( sPath, false ) ){
998 sPath = Filesystem::patterns_dir();
999 }
1000
1001 QString title = tr( "Save Pattern as ..." );
1002 FileDialog fd(this);
1003 fd.setWindowTitle( title );
1004 fd.setDirectory( sPath );
1005 fd.selectFile( pPattern->get_name() );
1006 fd.setFileMode( QFileDialog::AnyFile );
1007 fd.setNameFilter( Filesystem::patterns_filter_name );
1008 fd.setAcceptMode( QFileDialog::AcceptSave );
1009 fd.setSidebarUrls( fd.sidebarUrls() << QUrl::fromLocalFile( Filesystem::patterns_dir() ) );
1010 fd.setDefaultSuffix( Filesystem::patterns_ext );
1011
1012 if ( fd.exec() != QDialog::Accepted ) {
1013 return;
1014 }
1015
1016 QFileInfo fileInfo( fd.selectedFiles().first() );
1018 QString filePath = fileInfo.absoluteFilePath();
1019
1020 QString originalName = pPattern->get_name();
1021 pPattern->set_name( fileInfo.baseName() );
1022 QString path = Files::savePatternPath( filePath, pPattern, pSong, pHydrogen->getLastLoadedDrumkitName() );
1023 pPattern->set_name( originalName );
1024
1025 if ( path.isEmpty() ) {
1026 QMessageBox::warning( this, "Hydrogen", tr("Could not export pattern.") );
1027 return;
1028 }
1029
1030 h2app->showStatusBarMessage( tr( "Pattern saved." ) );
1031
1032 if ( filePath.indexOf( Filesystem::patterns_dir() ) == 0 ) {
1034
1035 }
1036}
1037
1040 if ( ! Filesystem::dir_readable( sPath, false ) ){
1041 sPath = Filesystem::songs_dir();
1042 }
1043
1044 QString sWindowTitle;
1045 if ( H2Core::Hydrogen::get_instance()->isUnderSessionManagement() ) {
1046 sWindowTitle = tr( "Import song into Session" );
1047 } else {
1048 sWindowTitle = tr( "Open song" );
1049 }
1050
1051 openSongWithDialog( sWindowTitle, sPath, false );
1052}
1053
1054
1056{
1057 Hydrogen *pHydrogen = Hydrogen::get_instance();
1058 std::shared_ptr<Song> pSong = pHydrogen->getSong();
1059
1061 if ( ! Filesystem::dir_readable( sPath, false ) ){
1062 sPath = Filesystem::patterns_dir();
1063 }
1064
1065 FileDialog fd(this);
1066 fd.setAcceptMode( QFileDialog::AcceptOpen );
1067 fd.setFileMode ( QFileDialog::ExistingFiles );
1068 fd.setDirectory ( sPath );
1069 fd.setNameFilter( Filesystem::patterns_filter_name );
1070
1071 fd.setWindowTitle ( tr ( "Open Pattern" ) );
1072
1073 if ( fd.exec() == QDialog::Accepted ) {
1074 Preferences::get_instance()->setLastOpenPatternDirectory( fd.directory().absolutePath() );
1075
1076 for ( auto& ssFilename : fd.selectedFiles() ) {
1077
1078 auto pNewPattern = Pattern::load_file( ssFilename, pSong->getInstrumentList() );
1079 if ( pNewPattern == nullptr ) {
1080 QMessageBox::critical( this, "Hydrogen", HydrogenApp::get_instance()->getCommonStrings()->getPatternLoadError() );
1081 } else {
1082 int nRow;
1083 if ( pHydrogen->getSelectedPatternNumber() == -1 ) {
1084 nRow = pSong->getPatternList()->size();
1085 } else {
1086 nRow = pHydrogen->getSelectedPatternNumber() + 1;
1087 }
1088
1089 SE_insertPatternAction* pAction =
1090 new SE_insertPatternAction( nRow, pNewPattern );
1091 HydrogenApp::get_instance()->m_pUndoStack->push( pAction );
1092 }
1093 }
1094 }
1095}
1096
1098 QString sWindowTitle;
1099 if ( ! H2Core::Hydrogen::get_instance()->isUnderSessionManagement() ) {
1100 sWindowTitle = tr( "Open Demo Song" );
1101 } else {
1102 sWindowTitle = tr( "Import Demo Song into Session" );
1103 }
1104
1105 openSongWithDialog( sWindowTitle, Filesystem::demos_dir(), true );
1106}
1107
1109
1110 auto pHydrogen = Hydrogen::get_instance();
1111 if ( pHydrogen->getAudioEngine()->getState() ==
1113 pHydrogen->sequencer_stop();
1114 }
1115
1116 return handleUnsavedChanges();
1117}
1118
1119void MainForm::openSongWithDialog( const QString& sWindowTitle, const QString& sPath, bool bIsDemo ) {
1120 // Check for unsaved changes.
1121 if ( ! prepareSongOpening() ) {
1122 return;
1123 }
1124
1125 auto pHydrogen = Hydrogen::get_instance();
1126
1127 FileDialog fd(this);
1128 fd.setAcceptMode( QFileDialog::AcceptOpen );
1129 fd.setFileMode( QFileDialog::ExistingFile );
1130 fd.setDirectory( sPath );
1131 fd.setNameFilter( Filesystem::songs_filter_name );
1132 fd.setWindowTitle( sWindowTitle );
1133
1134 QString sFilename;
1135 if ( fd.exec() == QDialog::Accepted ) {
1136 if ( ! bIsDemo ) {
1137 Preferences::get_instance()->setLastOpenSongDirectory( fd.directory().absolutePath() );
1138 }
1139 sFilename = fd.selectedFiles().first();
1140 }
1141
1142 if ( !sFilename.isEmpty() ) {
1143 HydrogenApp::get_instance()->openSong( sFilename );
1144 if ( bIsDemo &&
1145 ! pHydrogen->isUnderSessionManagement() ) {
1146 pHydrogen->getSong()->setFilename( "" );
1147 }
1148 }
1149}
1150
1152{
1153 h2app->showPreferencesDialog();
1154}
1155
1157{
1158 h2app->showPlaylistDialog();
1159}
1160
1161// function to update director status in menu bar
1163{
1164 bool isVisible = HydrogenApp::get_instance()->getPlayListDialog()->isVisible();
1165 m_pViewPlaylistEditorAction->setChecked( isVisible );
1166}
1167
1169{
1170 h2app->showDirector();
1171}
1172
1173// function to update director status in menu bar
1175{
1176 bool isVisible = HydrogenApp::get_instance()->getDirector()->isVisible();
1177 m_pViewDirectorAction->setChecked( isVisible );
1178}
1179
1181{
1182 if( this->isFullScreen() ){
1183 this->showNormal();
1184 } else {
1185 this->showFullScreen();
1186 }
1187}
1188
1190{
1191 bool isVisible = HydrogenApp::get_instance()->getMixer()->isVisible();
1192 h2app->showMixer( !isVisible );
1193}
1194
1195// function to update mixer status in menu bar
1197{
1198 bool isVisible = HydrogenApp::get_instance()->getMixer()->isVisible();
1199 m_pViewMixerAction->setChecked( isVisible );
1200}
1201
1203{
1204 h2app->showAudioEngineInfoForm();
1205}
1206
1208{
1209 h2app->showFilesystemInfoForm();
1210}
1211
1213{
1214 Logger* pLogger = Logger::get_instance();
1215 pLogger->set_bit_mask( Logger::None );
1216}
1217
1223
1229
1235
1241
1243{
1244 QDesktopServices::openUrl( Filesystem::log_file_path() );
1245}
1246
1247
1252{
1253 bool isVisible = h2app->getSongEditorPanel()->isVisible();
1254 h2app->getSongEditorPanel()->setHidden( isVisible );
1255}
1256
1258{
1259 h2app->getSongEditorPanel()->showTimeline();
1260}
1261
1262
1264{
1265 h2app->getSongEditorPanel()->showPlaybackTrack();
1266}
1267
1269{
1270 h2app->getSongEditorPanel()->toggleAutomationAreaVisibility();
1271}
1272
1273
1274
1280
1281
1283{
1284 bool bIsOkPressed;
1285 QString sNewName = QInputDialog::getText( this, "Hydrogen", tr( "Component name" ), QLineEdit::Normal, "New Component", &bIsOkPressed );
1286 if ( bIsOkPressed ) {
1287 Hydrogen *pHydrogen = Hydrogen::get_instance();
1288
1289 auto pDrumkitComponent = std::make_shared<DrumkitComponent>( InstrumentEditor::findFreeDrumkitComponentId(), sNewName );
1290 pHydrogen->getSong()->getComponents()->push_back( pDrumkitComponent );
1291
1293
1294 // this will force an update...
1296
1297#ifdef H2CORE_HAVE_JACK
1298 pHydrogen->renameJackPorts(pHydrogen->getSong());
1299#endif
1300 }
1301 else {
1302 // user entered nothing or pressed Cancel
1303 }
1304}
1305
1306
1308{
1309 SoundLibraryOpenDialog dialog( this );
1310 dialog.exec();
1311}
1312
1313
1315{
1316 switch(
1317 QMessageBox::information( this, //NOLINT
1318 "Hydrogen",
1319 tr("Clear all instruments?"),
1320 QMessageBox::Cancel | QMessageBox::Ok,
1321 QMessageBox::Cancel)) {
1322 case QMessageBox::Ok:
1323 // ok btn pressed
1324 break;
1325 case QMessageBox::Cancel:
1326 // cancel btn pressed
1327 return;
1328 default:
1329 // Not reached
1330 return;
1331 }
1332
1333 // Remove all instruments
1334 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
1335 auto pList = pSong->getInstrumentList();
1336 for (uint i = pList->size(); i > 0; i--) {
1338 }
1339
1341}
1342
1344{
1345 Hydrogen* pHydrogen = Hydrogen::get_instance();
1346 std::shared_ptr<Song> pSong = pHydrogen->getSong();
1347 auto pSelectedInstrument = pSong->getInstrumentList()->get( nInstrument );
1348 if ( pSelectedInstrument == nullptr ) {
1349 ERRORLOG( "No instrument selected" );
1350 return;
1351 }
1352
1353 std::list< Note* > noteList;
1354 PatternList *pPatternList = pSong->getPatternList();
1355
1356 QString sInstrumentName = pSelectedInstrument->get_name();
1357 QString sDrumkitPath = pSelectedInstrument->get_drumkit_path();
1358
1359 for ( int i = 0; i < pPatternList->size(); i++ ) {
1360 const H2Core::Pattern *pPattern = pPatternList->get(i);
1361 const Pattern::notes_t* notes = pPattern->get_notes();
1363 Note *pNote = it->second;
1364 assert( pNote );
1365 if ( pNote->get_instrument() == pSelectedInstrument ) {
1366 pNote->set_pattern_idx( i );
1367 noteList.push_back( pNote );
1368 }
1369 }
1370 }
1371
1372 SE_deleteInstrumentAction *pAction =
1373 new SE_deleteInstrumentAction( noteList, sDrumkitPath,
1374 sInstrumentName, nInstrument );
1375 HydrogenApp::get_instance()->m_pUndoStack->push( pAction );
1376}
1377
1378
1380
1381 auto pHydrogen = H2Core::Hydrogen::get_instance();
1382 auto pSong = pHydrogen->getSong();
1383
1384 auto pDrumkit = pHydrogen->getSoundLibraryDatabase()
1385 ->getDrumkit( pHydrogen->getLastLoadedDrumkitPath() );
1386
1387 if ( pDrumkit != nullptr ){
1388
1389 auto pNewDrumkit = std::make_shared<Drumkit>( pDrumkit );
1390 pNewDrumkit->set_instruments( pSong->getInstrumentList() );
1391 pNewDrumkit->set_components( pSong->getComponents() );
1392 SoundLibraryExportDialog exportDialog( this, pNewDrumkit );
1393 exportDialog.exec();
1394 }
1395 else {
1396 QMessageBox::warning( this, "Hydrogen", QString( "%1 [%2]")
1397 .arg( HydrogenApp::get_instance()->getCommonStrings()->getSoundLibraryFailedPreDrumkitLoad() )
1398 .arg( pHydrogen->getLastLoadedDrumkitPath() ) );
1399 }
1400}
1401
1402
1403
1404
1406{
1407 SoundLibraryImportDialog dialog( this, false );
1408 dialog.exec();
1409}
1410
1411
1413{
1414 SoundLibraryImportDialog dialog( this, true );
1415 dialog.exec();
1416}
1417
1418
1420{
1421 auto pHydrogen = Hydrogen::get_instance();
1422 auto pSong = pHydrogen->getSong();
1423
1424 auto pDrumkit = pHydrogen->getSoundLibraryDatabase()->
1425 getDrumkit( pHydrogen->getLastLoadedDrumkitPath() );
1426 auto drumkitType = Filesystem::determineDrumkitType(
1427 pHydrogen->getLastLoadedDrumkitPath() );
1428
1429 // In case the user does not have write access to the folder of
1430 // pDrumkit, the save as dialog will be opened.
1431 if ( pDrumkit != nullptr &&
1432 ( drumkitType == Filesystem::DrumkitType::User ||
1434 auto pNewDrumkit = std::make_shared<Drumkit>(pDrumkit);
1435 pNewDrumkit->set_instruments( pSong->getInstrumentList() );
1436 pNewDrumkit->set_components( pSong->getComponents() );
1437
1438 if ( ! HydrogenApp::checkDrumkitLicense( pNewDrumkit ) ) {
1439 ERRORLOG( "User cancelled dialog due to licensing issues." );
1440 return;
1441 }
1442
1443 if ( ! pNewDrumkit->save() ) {
1444 QMessageBox::information( this, "Hydrogen", tr( "Saving of this library failed."));
1445 return;
1446 }
1447
1448 pHydrogen->getSoundLibraryDatabase()->updateDrumkits();
1449 }
1450 else {
1452 }
1453}
1454
1455
1460
1461
1462
1463
1464
1465
1466
1470void MainForm::closeEvent( QCloseEvent* ev )
1471{
1472 if ( action_file_exit() == false ) {
1473 // don't close!!!
1474 ev->ignore();
1475 return;
1476 }
1477
1478 ev->accept();
1479}
1480
1481
1482
1484{
1485 if ( Hydrogen::get_instance()->getAudioEngine()->getState() == H2Core::AudioEngine::State::Playing ) {
1487 }
1488
1489 ExportSongDialog *dialog = new ExportSongDialog(this);
1490 dialog->exec();
1491 delete dialog;
1492}
1493
1494
1495
1497{
1499 pPanel->setHidden( pPanel->isVisible() );
1500 update_instrument_checkbox( pPanel->isVisible() );
1501}
1502
1504{
1505 m_pViewMixerInstrumentRackAction->setChecked( show );
1506}
1507
1509{
1511
1512 if(pref->getShowAutomationArea()){
1513 m_pViewAutomationPathAction->setChecked(true);
1514 } else {
1515 m_pViewAutomationPathAction->setChecked(false);
1516 }
1517}
1518
1520{
1522
1523 // Note that the ActionGroup unchecks the other menu item automatically
1524 if ( pPref->getShowPlaybackTrack() ) {
1525 m_pViewPlaybackTrackAction->setChecked( true );
1526 } else {
1527 m_pViewTimelineAction->setChecked( true );
1528 }
1529}
1530
1532 // save window properties in the preferences files
1533 Preferences *pPreferences = Preferences::get_instance();
1534
1535 // mainform
1536 pPreferences->setMainFormProperties( h2app->getWindowProperties( this ) );
1537 // Save mixer properties
1538 pPreferences->setMixerProperties( h2app->getWindowProperties( h2app->getMixer() ) );
1539 // save pattern editor properties
1540 pPreferences->setPatternEditorProperties( h2app->getWindowProperties( h2app->getPatternEditorPanel() ) );
1541 // save song editor properties
1542 pPreferences->setSongEditorProperties( h2app->getWindowProperties( h2app->getSongEditorPanel() ) );
1543 pPreferences->setInstrumentRackProperties( h2app->getWindowProperties( h2app->getInstrumentRack() ) );
1544 // save audio engine info properties
1545 pPreferences->setAudioEngineInfoProperties( h2app->getWindowProperties( h2app->getAudioEngineInfoForm() ) );
1546
1547 pPreferences->setPlaylistDialogProperties(
1548 h2app->getWindowProperties( h2app->getPlayListDialog() ) );
1549 pPreferences->setDirectorProperties(
1550 h2app->getWindowProperties( h2app->getDirector() ) );
1551
1552#ifdef H2CORE_HAVE_LADSPA
1553 // save LADSPA FX window properties
1554 for (uint nFX = 0; nFX < MAX_FX; nFX++) {
1555 pPreferences->setLadspaProperties( nFX, h2app->getWindowProperties( h2app->getLadspaFXProperties( nFX ) ) );
1556 }
1557#endif
1558}
1559
1561 // Store the last playlist in the Preferences in order to allow to
1562 // reopen it at startup.
1564 Playlist::get_instance()->getFilename() );
1565
1567 m_pQApp->quit();
1568}
1569
1570
1572 auto pPref = H2Core::Preferences::get_instance();
1573
1574 if ( changes & H2Core::Preferences::Changes::Font ) {
1575
1576 QFont font( pPref->getApplicationFontFamily(), getPointSize( pPref->getFontSize() ) );
1577 m_pQApp->setFont( font );
1578 menuBar()->setFont( font );
1579
1580 m_pFileMenu->setFont( font );
1581 m_pUndoMenu->setFont( font );
1582 m_pDrumkitsMenu->setFont( font );
1583 m_pInstrumentsMenu->setFont( font );
1584 m_pViewMenu->setFont( font );
1585 m_pOptionsMenu->setFont( font );
1586 if ( m_pDebugMenu != nullptr ) {
1587 m_pDebugMenu->setFont( font );
1588 }
1589 m_pInfoMenu->setFont( font );
1590
1592 }
1593
1594 if ( changes & H2Core::Preferences::Changes::Colors ) {
1596 }
1597
1600 }
1601}
1602
1603
1604// keybindings..
1605
1607{
1608 switch ( Hydrogen::get_instance()->getAudioEngine()->getState() ) {
1611 break;
1612
1615 break;
1616
1617 default:
1618 ERRORLOG( "[MainForm::onPlayStopAccelEvent()] Unhandled case." );
1619 }
1620}
1621
1622
1623
1625{
1626 Hydrogen* pHydrogen = Hydrogen::get_instance();
1627 pHydrogen->getCoreActionController()->locateToColumn( 0 );
1628}
1629
1630
1631
1633 auto pHydrogen = Hydrogen::get_instance();
1634 auto pAudioEngine = pHydrogen->getAudioEngine();
1635
1636 pHydrogen->getSong()->setBpm( pAudioEngine->getTransportPosition()->getBpm() + 0.1 );
1637
1638 pAudioEngine->lock( RIGHT_HERE );
1639 pAudioEngine->setNextBpm( pAudioEngine->getTransportPosition()->getBpm() + 0.1 );
1640 pAudioEngine->unlock();
1641
1643}
1644
1645
1646
1648 auto pHydrogen = Hydrogen::get_instance();
1649 auto pAudioEngine = pHydrogen->getAudioEngine();
1650
1651 pHydrogen->getSong()->setBpm( pAudioEngine->getTransportPosition()->getBpm() - 0.1 );
1652
1653 pAudioEngine->lock( RIGHT_HERE );
1654 pAudioEngine->setNextBpm( pAudioEngine->getTransportPosition()->getBpm() - 0.1 );
1655 pAudioEngine->unlock();
1656
1658}
1659
1661{
1662 m_pRecentFilesMenu->clear();
1663
1665 std::vector<QString> recentUsedSongs = pPref->getRecentFiles();
1666
1667 QString sFilename;
1668
1669 for ( uint i = 0; i < recentUsedSongs.size(); ++i ) {
1670 sFilename = recentUsedSongs[ i ];
1671
1672 if ( !sFilename.isEmpty() ) {
1673 QAction *pAction = new QAction( this );
1674 pAction->setText( sFilename );
1675 m_pRecentFilesMenu->addAction( pAction );
1676 }
1677 }
1678}
1679
1680
1681
1683{
1684 // Check for unsaved changes.
1685 if ( ! prepareSongOpening() ) {
1686 return;
1687 }
1688
1689 HydrogenApp::get_instance()->openSong( pAction->text() );
1690}
1691
1693{
1694 if ( Hydrogen::get_instance()->getSong()->hasMissingSamples() ) {
1695 m_pMissingSamplesInfoBar = h2app->addInfoBar();
1696 m_pMissingSamplesInfoBar->setTitle( tr( "Song drumkit samples" ) );
1697 m_pMissingSamplesInfoBar->setText( tr( "Some samples used in this song could not be loaded. This may be because it uses an older default drumkit. This might be fixed by opening a new drumkit." ) );
1698
1699 QPushButton *fix = m_pMissingSamplesInfoBar->addButton( tr( "Open drumkit" ) );
1700 QObject::connect( fix, SIGNAL( clicked() ),
1701 this, SLOT( onFixMissingSamples() ) );
1703 }
1704}
1705
1706
1708{
1709 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
1710 if ( pSong->getInstrumentList()->has_all_midi_notes_same() ) {
1711 WARNINGLOG( "Incorrect MIDI setup" );
1712
1713 m_pMidiSetupInfoBar = h2app->addInfoBar();
1714 m_pMidiSetupInfoBar->reset();
1715 m_pMidiSetupInfoBar->setTitle( tr("MIDI setup advice") );
1716 m_pMidiSetupInfoBar->setText( tr("MIDI out notes are not configured for this drumkit, so exporting this song to MIDI file may fail. Would you like Hydrogen to automatically fix this by assigning default values?") );
1717 QPushButton *fix = m_pMidiSetupInfoBar->addButton( tr("Set default values") );
1718 QObject::connect( fix, SIGNAL(clicked()), this, SLOT(onFixMidiSetup()) );
1719 m_pMidiSetupInfoBar->show();
1720 } else {
1721 m_pMidiSetupInfoBar = nullptr;
1722 }
1723}
1724
1726{
1727 //Make sure that all directories which are needed by Hydrogen are existing and usable.
1728 QString sTempDir = Filesystem::tmp_dir();
1729
1730 if( !Filesystem::dir_writable(sTempDir))
1731 {
1732 QMessageBox::warning( this, "Hydrogen", tr("Could not write to temporary directory %1.").arg(sTempDir) );
1733 }
1734}
1735
1737{
1738 INFOLOG( "Fixing MIDI setup" );
1739 auto pHydrogen = Hydrogen::get_instance();
1740 auto pSong = pHydrogen->getSong();
1741 if ( pSong != nullptr ) {
1742 pSong->getInstrumentList()->set_default_midi_out_notes();
1743 pHydrogen->setIsModified( true );
1744
1745 m_pMidiSetupInfoBar->hide();
1746 }
1747}
1748
1749
1751{
1752 INFOLOG( "Fixing MIDI setup" );
1753 SoundLibraryOpenDialog dialog( this );
1754 dialog.exec();
1755
1757}
1758
1759
1761{
1762
1763 QString loc = QLocale::system().name();
1764
1765 // Mimics a MIDI NOTE_ON event which has pitch values between
1766 // MidiMessage::instrumentOffset and 127.
1768
1770 //locale for keyboardlayout QWERTZ
1771 // de_DE, de_AT, de_LU, de_CH, de
1772
1773 //locale for keyboardlayout AZERTY
1774 // fr_BE, fr_CA, fr_FR, fr_LU, fr_CH
1775
1776 //locale for keyboardlayout QWERTY
1777 // en_GB, en_US, en_ZA, usw.
1778
1779 if ( loc.contains( "de" ) || loc.contains( "DE" )){
1780 keycodeInstrumentMap[Qt::Key_Y] = nNote++;
1781 keycodeInstrumentMap[Qt::Key_S] = nNote++;
1782 keycodeInstrumentMap[Qt::Key_X] = nNote++;
1783 keycodeInstrumentMap[Qt::Key_D] = nNote++;
1784 keycodeInstrumentMap[Qt::Key_C] = nNote++;
1785 keycodeInstrumentMap[Qt::Key_V] = nNote++;
1786 keycodeInstrumentMap[Qt::Key_G] = nNote++;
1787 keycodeInstrumentMap[Qt::Key_B] = nNote++;
1788 keycodeInstrumentMap[Qt::Key_H] = nNote++;
1789 keycodeInstrumentMap[Qt::Key_N] = nNote++;
1790 keycodeInstrumentMap[Qt::Key_J] = nNote++;
1791 keycodeInstrumentMap[Qt::Key_M] = nNote++;
1792
1793 keycodeInstrumentMap[Qt::Key_Q] = nNote++;
1794 keycodeInstrumentMap[Qt::Key_2] = nNote++;
1795 keycodeInstrumentMap[Qt::Key_W] = nNote++;
1796 keycodeInstrumentMap[Qt::Key_3] = nNote++;
1797 keycodeInstrumentMap[Qt::Key_E] = nNote++;
1798 keycodeInstrumentMap[Qt::Key_R] = nNote++;
1799 keycodeInstrumentMap[Qt::Key_5] = nNote++;
1800 keycodeInstrumentMap[Qt::Key_T] = nNote++;
1801 keycodeInstrumentMap[Qt::Key_6] = nNote++;
1802 keycodeInstrumentMap[Qt::Key_Z] = nNote++;
1803 keycodeInstrumentMap[Qt::Key_7] = nNote++;
1804 keycodeInstrumentMap[Qt::Key_U] = nNote++;
1805 }
1806 else if ( loc.contains( "fr" ) || loc.contains( "FR" )){
1807 keycodeInstrumentMap[Qt::Key_W] = nNote++;
1808 keycodeInstrumentMap[Qt::Key_S] = nNote++;
1809 keycodeInstrumentMap[Qt::Key_X] = nNote++;
1810 keycodeInstrumentMap[Qt::Key_D] = nNote++;
1811 keycodeInstrumentMap[Qt::Key_C] = nNote++;
1812 keycodeInstrumentMap[Qt::Key_V] = nNote++;
1813 keycodeInstrumentMap[Qt::Key_G] = nNote++;
1814 keycodeInstrumentMap[Qt::Key_B] = nNote++;
1815 keycodeInstrumentMap[Qt::Key_H] = nNote++;
1816 keycodeInstrumentMap[Qt::Key_N] = nNote++;
1817 keycodeInstrumentMap[Qt::Key_J] = nNote++;
1818 keycodeInstrumentMap[Qt::Key_Question] = nNote++;
1819
1820 keycodeInstrumentMap[Qt::Key_A] = nNote++;
1821 keycodeInstrumentMap[Qt::Key_2] = nNote++;
1822 keycodeInstrumentMap[Qt::Key_Z] = nNote++;
1823 keycodeInstrumentMap[Qt::Key_3] = nNote++;
1824 keycodeInstrumentMap[Qt::Key_E] = nNote++;
1825 keycodeInstrumentMap[Qt::Key_R] = nNote++;
1826 keycodeInstrumentMap[Qt::Key_5] = nNote++;
1827 keycodeInstrumentMap[Qt::Key_T] = nNote++;
1828 keycodeInstrumentMap[Qt::Key_6] = nNote++;
1829 keycodeInstrumentMap[Qt::Key_Y] = nNote++;
1830 keycodeInstrumentMap[Qt::Key_7] = nNote++;
1831 keycodeInstrumentMap[Qt::Key_U] = nNote++;
1832 }else
1833 {
1834 keycodeInstrumentMap[Qt::Key_Z] = nNote++;
1835 keycodeInstrumentMap[Qt::Key_S] = nNote++;
1836 keycodeInstrumentMap[Qt::Key_X] = nNote++;
1837 keycodeInstrumentMap[Qt::Key_D] = nNote++;
1838 keycodeInstrumentMap[Qt::Key_C] = nNote++;
1839 keycodeInstrumentMap[Qt::Key_V] = nNote++;
1840 keycodeInstrumentMap[Qt::Key_G] = nNote++;
1841 keycodeInstrumentMap[Qt::Key_B] = nNote++;
1842 keycodeInstrumentMap[Qt::Key_H] = nNote++;
1843 keycodeInstrumentMap[Qt::Key_N] = nNote++;
1844 keycodeInstrumentMap[Qt::Key_J] = nNote++;
1845 keycodeInstrumentMap[Qt::Key_M] = nNote++;
1846
1847 keycodeInstrumentMap[Qt::Key_Q] = nNote++;
1848 keycodeInstrumentMap[Qt::Key_2] = nNote++;
1849 keycodeInstrumentMap[Qt::Key_W] = nNote++;
1850 keycodeInstrumentMap[Qt::Key_3] = nNote++;
1851 keycodeInstrumentMap[Qt::Key_E] = nNote++;
1852 keycodeInstrumentMap[Qt::Key_R] = nNote++;
1853 keycodeInstrumentMap[Qt::Key_5] = nNote++;
1854 keycodeInstrumentMap[Qt::Key_T] = nNote++;
1855 keycodeInstrumentMap[Qt::Key_6] = nNote++;
1856 keycodeInstrumentMap[Qt::Key_Y] = nNote++;
1857 keycodeInstrumentMap[Qt::Key_7] = nNote++;
1858 keycodeInstrumentMap[Qt::Key_U] = nNote++;
1859 }
1860}
1861
1862
1863bool MainForm::eventFilter( QObject *o, QEvent *e )
1864{
1865 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
1866 auto pHydrogen = Hydrogen::get_instance();
1867 auto pHydrogenApp = HydrogenApp::get_instance();
1868
1869 if ( e->type() == QEvent::FileOpen ) {
1870 // Mac OS always opens files (including via double click in Finder) via a FileOpenEvent.
1871 QFileOpenEvent *fe = dynamic_cast<QFileOpenEvent*>(e);
1872 assert( fe != nullptr );
1873 QString sFileName = fe->file();
1874
1875 if ( sFileName.endsWith( H2Core::Filesystem::songs_ext ) ) {
1876 if ( handleUnsavedChanges() ) {
1877 pHydrogenApp->openSong( sFileName );
1878 }
1879
1880 } else if ( sFileName.endsWith( H2Core::Filesystem::drumkit_ext ) ) {
1881 H2Core::Drumkit::install( sFileName );
1882
1883 } else if ( sFileName.endsWith( H2Core::Filesystem::playlist_ext ) ) {
1884 bool loadlist = pHydrogenApp->getPlayListDialog()->loadListByFileName( sFileName );
1885 if ( loadlist ) {
1887 }
1888 }
1889 return true;
1890
1891 } else if ( e->type() == QEvent::KeyPress ) {
1892 // special processing for key press
1893 QKeyEvent *k = (QKeyEvent *)e;
1894
1895 if ( k->matches( QKeySequence::StandardKey::Undo ) ) {
1896 k->accept();
1897 action_undo();
1898 return true;
1899 } else if ( k->matches( QKeySequence::StandardKey::Redo ) ) {
1900 k->accept();
1901 action_redo();
1902 return true;
1903 }
1904
1905
1906 // qDebug( "Got key press for instrument '%c'", k->ascii() );
1907 switch (k->key()) {
1908 case Qt::Key_Space:
1909
1910 // Hint that something is wrong in case there is no proper audio
1911 // driver set.
1912 if ( pHydrogen->getAudioOutput() == nullptr ||
1913 dynamic_cast<NullDriver*>(pHydrogen->getAudioOutput()) != nullptr ) {
1914 QMessageBox::warning( this, "Hydrogen",
1915 QString( "%1\n%2" )
1916 .arg( pCommonStrings->getAudioDriverNotPresent() )
1917 .arg( pCommonStrings->getAudioDriverErrorHint() ) );
1918 return true;
1919 }
1920
1921 switch ( k->modifiers() ) {
1922 case Qt::NoModifier:
1924 break;
1925
1926#ifndef Q_OS_MACX
1927 case Qt::ControlModifier:
1929 break;
1930 }
1931#else
1932 case Qt::AltModifier:
1934 break;
1935 }
1936#endif
1937
1938 return true; // eat event
1939 break;
1940
1941 case Qt::Key_Comma:
1942 pHydrogen->handleBeatCounter();
1943 return true; // eat even
1944 break;
1945
1946 case Qt::Key_Backspace:
1948 return true; // eat event
1949 break;
1950
1951 case Qt::Key_Plus:
1953 return true; // eat event
1954 break;
1955
1956 case Qt::Key_Minus:
1958 return true; // eat event
1959 break;
1960
1961 case Qt::Key_Backslash:
1962 pHydrogen->onTapTempoAccelEvent();
1963 return true; // eat event
1964 break;
1965
1966 case Qt::Key_S:
1967 if ( k->modifiers() ==
1968 ( Qt::ControlModifier | Qt::ShiftModifier ) ) {
1970 return true;
1971 } else if ( k->modifiers() == Qt::ControlModifier ) {
1973 return true;
1974 }
1975 break;
1976
1977 case Qt::Key_F5 :
1978 if( Playlist::get_instance()->size() == 0) {
1979 break;
1980 }
1982 break;
1983
1984 case Qt::Key_F6 :
1985 if( Playlist::get_instance()->size() == 0) {
1986 break;
1987 }
1989 break;
1990
1991 case Qt::Key_F12 : //panic button stop all playing notes
1992 pHydrogen->__panic();
1993 //QMessageBox::information( this, "Hydrogen", tr( "Panic" ) );
1994 return true;
1995 break;
1996
1997 case Qt::Key_F9 : // Qt::Key_Left do not work. Some ideas ?
1998 pHydrogen->getCoreActionController()->locateToColumn( pHydrogen->getAudioEngine()->getTransportPosition()->getColumn() - 1 );
1999 return true;
2000 break;
2001
2002 case Qt::Key_F10 : // Qt::Key_Right do not work. Some ideas ?
2003 pHydrogen->getCoreActionController()->locateToColumn( pHydrogen->getAudioEngine()->getTransportPosition()->getColumn() + 1 );
2004 return true;
2005 break;
2006 }
2007
2008 // virtual keyboard handling
2009 if ( k->modifiers() == Qt::NoModifier ) {
2010 std::map<int,int>::iterator found = keycodeInstrumentMap.find ( k->key() );
2011 if ( found != keycodeInstrumentMap.end() ) {
2012 const int nNote = (*found).second;
2013
2014 INFOLOG( QString( "[Virtual Keyboard] triggering note [%1]" )
2015 .arg( nNote ) );
2016
2017 pHydrogen->getCoreActionController()->handleNote(
2018 nNote, 0.8, false );
2019
2020 return true; // eat event
2021 }
2022 }
2023 return false; // let it go
2024 }
2025 else {
2026 return false; // standard event processing
2027 }
2028}
2029
2030
2031
2032
2033
2036{
2037 INFOLOG( "[action_debug_printObjects]" );
2039}
2040
2041
2042
2043
2044
2045
2047{
2048 if ( Hydrogen::get_instance()->getAudioEngine()->getState() == H2Core::AudioEngine::State::Playing ) {
2050 }
2051
2052 ExportMidiDialog *dialog = new ExportMidiDialog(this);
2053 dialog->exec();
2054 delete dialog;
2055}
2056
2057
2058
2059
2061{
2062 if ( Hydrogen::get_instance()->getAudioEngine()->getState() == H2Core::AudioEngine::State::Playing ) {
2064 }
2065
2066 QMessageBox::information(
2067 this,
2068 "Hydrogen",
2069 tr( "\nThe LilyPond export is an experimental feature.\n"
2070 "It should work like a charm provided that you use the "
2071 "GMRockKit, and that you do not use triplet.\n" ),
2072 QMessageBox::Ok );
2073
2075 if ( ! Filesystem::dir_writable( sPath, false ) ){
2076 sPath = Filesystem::usr_data_path();
2077 }
2078
2079 FileDialog fd( this );
2080 fd.setFileMode( QFileDialog::AnyFile );
2081 fd.setNameFilter( tr( "LilyPond file (*.ly)" ) );
2082 fd.setDirectory( sPath );
2083 fd.setWindowTitle( tr( "Export LilyPond file" ) );
2084 fd.setAcceptMode( QFileDialog::AcceptSave );
2085
2086 QString sFilename;
2087 if ( fd.exec() == QDialog::Accepted ) {
2088 Preferences::get_instance()->setLastExportLilypondDirectory( fd.directory().absolutePath() );
2089 sFilename = fd.selectedFiles().first();
2090 }
2091
2092 if ( !sFilename.isEmpty() ) {
2093 if ( sFilename.endsWith( ".ly" ) == false ) {
2094 sFilename += ".ly";
2095 }
2096
2097 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
2098
2099 LilyPond ly;
2100 ly.extractData( *pSong );
2101 ly.write( sFilename );
2102 }
2103}
2104
2105void MainForm::errorEvent( int nErrorCode )
2106{
2107 //ERRORLOG( "[errorEvent]" );
2108
2109 QString msg;
2110 switch (nErrorCode) {
2112 msg = tr( "Unknown audio driver" );
2113 break;
2114
2116 msg = tr( "Error starting audio driver" );
2117 break;
2118
2120 msg = tr( "Jack driver: server shutdown" );
2121 break;
2122
2124 msg = tr( "Jack driver: cannot activate client" );
2125 break;
2126
2128 msg = tr( "Jack driver: cannot connect output port" );
2129 break;
2130
2132 msg = tr( "Jack driver: cannot disconnect client" );
2133 break;
2134
2136 msg = tr( "Jack driver: error in port register" );
2137 break;
2138
2140 msg = QString( tr( "OSC Server: Cannot connect to given port, using port %1 instead" ) ).arg( Preferences::get_instance()->m_nOscTemporaryPort );
2141 break;
2142
2144 msg = tr( "Playback track couldn't be read" );
2145 break;
2146
2147 default:
2148 msg = QString( tr( "Unknown error %1" ) ).arg( nErrorCode );
2149 }
2150 QMessageBox::information( this, "Hydrogen", msg );
2151}
2152
2154{
2155 Playlist* pPlaylist = Playlist::get_instance();
2156
2157 QString songFilename;
2158 if( !pPlaylist->getSongFilenameByNumber( nIndex, songFilename ) ) {
2159 return;
2160 }
2161
2162 HydrogenApp::get_instance()->openSong( songFilename );
2163
2164 pPlaylist->activateSong( nIndex );
2165
2166 HydrogenApp::get_instance()->showStatusBarMessage( tr( "Playlist: Set song No. %1" )
2167 .arg( nIndex +1 ) );
2168}
2169
2171{
2172 switch (nEvent){
2173 case 0:
2175 break;
2176 case 1:
2178 break;
2179 }
2180
2181}
2182
2184{
2185 if ( H2Core::Hydrogen::get_instance()->getSong() == nullptr ) {
2186 return;
2187 }
2188
2189 SongPropertiesDialog *pDialog = new SongPropertiesDialog( this );
2190 if ( pDialog->exec() ) {
2191 // Ensure the update name is taken into account in the window
2192 // title.
2194 }
2195 delete pDialog;
2196}
2197
2198
2200{
2201 bool isVisible = HydrogenApp::get_instance()->getPatternEditorPanel()->isVisible();
2202 HydrogenApp::get_instance()->getPatternEditorPanel()->setHidden( isVisible );
2203}
2204
2205
2207{
2208 Preferences *pPreferences = Preferences::get_instance();
2209 bool isDevelWarningEnabled = pPreferences->getShowDevelWarning();
2210
2211 //set this to 'false' for the case that you want to make a release..
2212 if ( H2CORE_IS_DEVEL_BUILD ) {
2213 if(isDevelWarningEnabled) {
2214 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
2215
2216 QString msg = tr( "You're using a development version of Hydrogen, please help us reporting bugs or suggestions in the hydrogen-devel mailing list.<br><br>Thank you!" );
2217 QMessageBox develMessageBox( this );
2218 develMessageBox.setText( msg );
2219 develMessageBox.addButton( pCommonStrings->getButtonOk(),
2220 QMessageBox::YesRole );
2221 develMessageBox.addButton( pCommonStrings->getMutableDialog(),
2222 QMessageBox::AcceptRole );
2223
2224 if( develMessageBox.exec() == 1 ){
2225 //don't show warning again
2226 pPreferences->setShowDevelWarning( false );
2227 }
2228 }
2229 } else {
2230 // Release builds
2231 if ( !isDevelWarningEnabled ) {
2232 // Running a release build, we should re-enable the development-build warning if it's been
2233 // disabled, since the user might have tried a release build at some time in the past, then
2234 // continued working happily with a release build. They will still benefit from knowing that a
2235 // *new* release build they're trying is in fact a release build.
2236 pPreferences->setShowDevelWarning( true );
2237 }
2238 }
2239}
2240
2241
2242
2244{
2245 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
2246 assert( pSong );
2247 QString sOldFilename = pSong->getFilename();
2248 QString sNewName;
2249
2250 if ( !sOldFilename.isEmpty() ) {
2251
2252 QFileInfo fileInfo( sOldFilename );
2253
2254 // In case the user did open a hidden file, the baseName()
2255 // will be an empty string.
2256 QString sBaseName( fileInfo.completeBaseName() );
2257 if ( sBaseName.startsWith( "." ) ) {
2258 sBaseName.remove( 0, 1 );
2259 }
2260
2261 QString sAbsoluteDir( fileInfo.absoluteDir().absolutePath() );
2262 if ( ! Filesystem::file_writable( sOldFilename, true ) ) {
2263
2264 sNewName = QString( "%1%2.autosave.h2song" )
2265 .arg( Filesystem::songs_dir() ).arg( sBaseName );
2266
2267 WARNINGLOG( QString( "Path of current song [%1] is not writable. Autosave will store the song as [%2] instead." )
2268 .arg( sOldFilename ).arg( sNewName ) );
2269 } else {
2270 sNewName = QString( "%1/.%2.autosave.h2song" )
2271 .arg( sAbsoluteDir ).arg( sBaseName );
2272 }
2273 } else {
2274 // Store the default autosave file in the user's song data
2275 // folder to not clutter their working directory.
2276 sNewName = QString( "%1autosave.h2song" )
2277 .arg( Filesystem::songs_dir() );
2278 }
2279
2280 return sNewName;
2281}
2282
2283
2284
2286{
2287 auto pHydrogen = Hydrogen::get_instance();
2288 std::shared_ptr<Song> pSong = pHydrogen->getSong();
2289
2290 assert( pSong );
2291 if ( pSong->getIsModified() ) {
2292 QString sOldFilename = pSong->getFilename();
2293
2294 QString sAutoSaveFilename = getAutoSaveFilename();
2295 if ( sAutoSaveFilename != m_sPreviousAutoSaveFilename ) {
2296 if ( ! m_sPreviousAutoSaveFilename.isEmpty() ) {
2297 QFile file( m_sPreviousAutoSaveFilename );
2298 file.remove();
2299 }
2300 m_sPreviousAutoSaveFilename = sAutoSaveFilename;
2301 }
2302
2303 pSong->save( sAutoSaveFilename );
2304
2305 pSong->setFilename( sOldFilename );
2306 pHydrogen->setIsModified( true );
2307 }
2308}
2309
2310
2312{
2313 if( Playlist::get_instance()->size() == 0) {
2314 return;
2315 }
2316
2317 int songnumber = Playlist::get_instance()->getActiveSongNumber();
2318 QString songname;
2319 if ( songnumber == -1 ) {
2320 return;
2321 }
2322
2323 if ( Hydrogen::get_instance()->getSong()->getName() == "Untitled Song" ){
2324 songname = Hydrogen::get_instance()->getSong()->getFilename();
2325 } else {
2326 songname = Hydrogen::get_instance()->getSong()->getName();
2327 }
2328 QString message = (tr("Playlist: Song No. %1").arg( songnumber + 1)) + QString(" --- Songname: ") + songname + QString(" --- Author: ") + Hydrogen::get_instance()->getSong()->getAuthor();
2330}
2331
2332// Returns true if unsaved changes are successfully handled (saved, discarded, etc.)
2333// Returns false if not (i.e. Cancel)
2335{
2336 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
2337 bool done = false;
2338 bool rv = true;
2339 while ( !done && Hydrogen::get_instance()->getSong()->getIsModified() ) {
2340 switch (
2341 QMessageBox::information(
2342 this, "Hydrogen", tr("\nThe document contains unsaved changes.\n"
2343 "Do you want to save the changes?\n"),
2344 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
2345 QMessageBox::Save ) ) {
2346 case QMessageBox::Save: // Save clicked or Alt+S pressed or Enter pressed.
2347 // If the save fails, the __is_modified flag will still be true
2348 if ( ! Hydrogen::get_instance()->getSong()->getFilename().isEmpty() ) {
2349 if ( ! action_file_save() ) {
2350 return false;
2351 }
2352 }
2353 else {
2354 // never been saved
2355 if ( ! action_file_save_as() ) {
2356 return false;
2357 }
2358 }
2359 // save
2360 break;
2361 case QMessageBox::Discard:
2362 // don't save but exit
2363 done = true;
2364 break;
2365 case QMessageBox::Cancel:
2366 // don't exit
2367 return false;
2368 }
2369 }
2370
2371 while ( !done && Playlist::get_instance()->getIsModified() ) {
2372 switch(
2373 QMessageBox::information(
2374 this, "Hydrogen",
2375 tr("\nThe current playlist contains unsaved changes.\n"
2376 "Do you want to discard the changes?\n"),
2377 QMessageBox::Discard | QMessageBox::Cancel,
2378 QMessageBox::Discard ) ) {
2379 case QMessageBox::Discard:
2380 // don't save but exit
2381 done = true;
2382 break;
2383 case QMessageBox::Cancel:
2384 // don't exit
2385 return false;
2386 }
2387 }
2388
2389 return true;
2390}
2391
2392
2394{
2395 char a = 1;
2396 ::write(sigusr1Fd[0], &a, sizeof(a));
2397}
2398
2400{
2401 snUsr1->setEnabled(false);
2402 char tmp;
2403 ::read(sigusr1Fd[1], &tmp, sizeof(tmp));
2404
2406 snUsr1->setEnabled(true);
2407}
2408
2410{
2411 m_pUndoView->show();
2412 m_pUndoView->setAttribute(Qt::WA_QuitOnClose, false);
2413}
2414
2416 h2app->m_pUndoStack->undo();
2417}
2418
2420 h2app->m_pUndoStack->redo();
2421}
2422
2424
2425 if ( nValue == 0 ) {
2426 // Write the state of the GUI to the Preferences.
2429
2430 } else if ( nValue == 1 ) {
2431
2432 // Reflect the changes in the preferences in the objects
2433 // stored in MainForm.
2434 if( Preferences::get_instance()->__playselectedinstrument ) {
2435 m_pInstrumentAction->setChecked( true );
2436 m_pDrumkitAction->setChecked (false );
2437 } else {
2438 m_pInstrumentAction->setChecked( false );
2439 m_pDrumkitAction->setChecked (true );
2440 }
2441
2442 } else {
2443 ERRORLOG( QString( "Unknown event parameter [%1] MainForm::updatePreferencesEvent" )
2444 .arg( nValue ) );
2445 }
2446
2447}
2448
2450 if( nEvent == 0 ) {
2451 h2app->m_pUndoStack->undo();
2452 } else if(nEvent == 1) {
2453 h2app->m_pUndoStack->redo();
2454 }
2455}
2456
2458{
2459 int nPlaylistSize = Playlist::get_instance()->size();
2460 int nSongnumber = Playlist::get_instance()->getActiveSongNumber();
2461
2462 if( nSongnumber+step >= 0 && nSongnumber+step <= nPlaylistSize-1 ){
2463 Playlist::get_instance()->setNextSongByNumber( nSongnumber + step );
2464 } else {
2465 return false;
2466 }
2467
2468 return true;
2469}
2470
2474
2475void MainForm::editDrumkitProperties( bool bDrumkitNameLocked )
2476{
2477 auto pHydrogen = Hydrogen::get_instance();
2478 auto pSong = pHydrogen->getSong();
2479
2480 auto pDrumkit = pHydrogen->getSoundLibraryDatabase()
2481 ->getDrumkit( pHydrogen->getLastLoadedDrumkitPath() );
2482
2483 if ( pDrumkit == nullptr ) {
2484 ERRORLOG( QString( "Unable to find drumkit at path [%1]. Trying drumkit name [%2] instead." )
2485 .arg( pHydrogen->getLastLoadedDrumkitPath() )
2486 .arg( pHydrogen->getLastLoadedDrumkitName() ) );
2487 // No luck when searching for the kit using the absolute path found in
2488 // the .h2song. Let's try the last loaded drumkit name.
2489 const QString sDrumkitPath =
2490 Filesystem::drumkit_path_search( pHydrogen->getLastLoadedDrumkitName(),
2492 pDrumkit = pHydrogen->getSoundLibraryDatabase()
2493 ->getDrumkit( sDrumkitPath );
2494 }
2495
2496 if ( pDrumkit == nullptr && ! bDrumkitNameLocked ) {
2497 ERRORLOG( QString( "Unable to find drumkit of name [%1] either. Falling back to empty one." )
2498 .arg( pHydrogen->getLastLoadedDrumkitName() ) );
2499 // If that didn't worked either and the user wants to "Save As", we fall
2500 // back to the default kit.
2501 pDrumkit = std::make_shared<Drumkit>();
2502 }
2503
2504 if ( pDrumkit != nullptr ){
2505
2506 auto pNewDrumkit = std::make_shared<Drumkit>( pDrumkit );
2507 pNewDrumkit->set_instruments( pSong->getInstrumentList() );
2508 pNewDrumkit->set_components( pSong->getComponents() );
2509
2510 SoundLibraryPropertiesDialog dialog( this, pNewDrumkit, bDrumkitNameLocked );
2511 if ( dialog.exec() == QDialog::Accepted ) {
2512 // Saving was successful.
2513
2514 if ( pNewDrumkit->get_path() != pDrumkit->get_path() ) {
2515 // A new drumkit was created based on the original
2516 // one. We call the drumkit setter to ensure
2517 // everything in the Song and GUI is still in sync.
2518 pHydrogen->getCoreActionController()->setDrumkit( pNewDrumkit, false );
2519 }
2520 }
2521 }
2522 else {
2523 QMessageBox::warning( this, "Hydrogen", QString( "%1 [%2]")
2524 .arg( HydrogenApp::get_instance()->getCommonStrings()->getSoundLibraryFailedPreDrumkitLoad() )
2525 .arg( pHydrogen->getLastLoadedDrumkitPath() ) );
2526 }
2527}
2528
2529void MainForm::updateSongEvent( int nValue ) {
2530 if ( nValue == 0 || nValue == 1 ) {
2531 // A new song was set.
2533 }
2534}
2535
2537 closeAll();
2538}
2539
2540void MainForm::startPlaybackAtCursor( QObject* pObject ) {
2541
2542 Hydrogen* pHydrogen = Hydrogen::get_instance();
2543 auto pSong = pHydrogen->getSong();
2545 auto pCoreActionController = pHydrogen->getCoreActionController();
2546 auto pAudioEngine = pHydrogen->getAudioEngine();
2547
2548 if ( pSong == nullptr ) {
2549 return;
2550 }
2551
2552 if ( pObject->inherits( "SongEditorPanel" ) ) {
2553
2554 if ( pHydrogen->getMode() != Song::Mode::Song ) {
2555 pCoreActionController->activateSongMode( true );
2556 }
2557
2558 const int nCursorColumn =
2560
2561 // Within the core locating to a position beyond the length of
2562 // the song with loop mode enabled is a valid
2563 // operation. The resulting location will the wrapped as if
2564 // transport was looped. This is important when allowing
2565 // external applications to relocate but it is not what we
2566 // want in here.
2567 if ( nCursorColumn >= pSong->getPatternGroupVector()->size() ) {
2568 ERRORLOG( QString( "Cursor column [%1] is outside of the current song [0,%2]" )
2569 .arg( nCursorColumn )
2570 .arg( pSong->getPatternGroupVector()->size() - 1 ) );
2571 return;
2572 }
2573
2574 if ( ! pCoreActionController->locateToColumn( nCursorColumn ) ) {
2575 // Cursor is at a position it is not allowed to locate to.
2576 return;
2577 }
2578
2579 } else if ( pObject->inherits( "PatternEditorPanel" ) ) {
2580 // Covers both the PatternEditor and the
2581 // NotePropertiesRuler.
2582
2583 if ( pHydrogen->getMode() != Song::Mode::Pattern ) {
2584 pCoreActionController->activateSongMode( false );
2585 }
2586
2587 // To provide a similar behaviour as when pressing
2588 // [backspace], transport is relocated to the beginning of
2589 // the song.
2590 const int nCursorColumn = pApp->getPatternEditorPanel()->getCursorPosition();
2591
2592 if ( ! pCoreActionController->locateToTick( nCursorColumn ) ) {
2593 // Cursor is at a position it is not allowed to locate to.
2594 return;
2595 }
2596 } else {
2597 ERRORLOG( QString( "Unknown object class" ) );
2598 }
2599
2600 if ( pAudioEngine->getState() == H2Core::AudioEngine::State::Ready ) {
2601 pHydrogen->sequencer_play();
2602 pApp->showStatusBarMessage( tr("Playing.") );
2603 }
2604}
#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
#define _ERRORLOG(x)
Definition Object.h:248
#define DEBUGLOG(x)
Definition Object.h:239
#define FOREACH_NOTE_CST_IT_BEGIN_END(_notes, _it)
Iterate over all provided notes in an immutable way.
Definition Pattern.h:268
virtual void selectedInstrumentChangedEvent()
Dialog for exporting song to midi.
Dialog for exporting song.
Custom file dialog checking whether the user has write access to the selected folder before allowing ...
Definition FileDialog.h:34
@ Playing
Transport is rolling.
@ Ready
Ready to process audio.
State getState() const
static void write_objects_map_to_cerr()
output objects map to stderr
Definition Object.h:95
bool locateToColumn(int nPatternGroup)
Relocates transport to the beginning of a particular column/Pattern group.
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 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 QString savePatternPath(const QString &filePath, Pattern *pattern, std::shared_ptr< Song > song, const QString &drumkitName)
save the given pattern to filePath will overwrite an existing file
Definition Files.h:88
static const QString patterns_filter_name
Definition Filesystem.h:126
static QString demos_dir()
returns system demos path
static bool dir_readable(const QString &path, bool silent=false)
returns true if the given path is a readable regular directory
static const QString patterns_ext
Definition Filesystem.h:119
static QString songs_dir()
returns user songs path
static DrumkitType determineDrumkitType(const QString &sPath)
static bool file_writable(const QString &path, bool silent=false)
returns true if the given path is a possibly writable file (may exist or not)
static bool rm(const QString &path, bool recursive=false, bool bSilent=false)
remove a path
static QString doc_dir()
returns documentation path
static bool dir_writable(const QString &path, bool silent=false)
returns true if the given path is a writable regular directory
static bool file_exists(const QString &path, bool silent=false)
returns true if the given path is an existing regular file
static QString empty_song_path()
Provides the full path to the current empty song.
@ stacked
First, looks in the system drumkits and, afterwards, in the user drumkits.
Definition Filesystem.h:63
static QString drumkit_path_search(const QString &dk_name, Lookup lookup=Lookup::stacked, bool bSilent=false)
Returns the path to a H2Core::Drumkit folder.
static const QString playlist_ext
Definition Filesystem.h:120
static const QString songs_filter_name
Definition Filesystem.h:123
static QString tmp_dir()
returns temp path
@ SessionReadWrite
Kit was loaded via a NSM session, OSC command, or CLI option, only persist for the current Hydrogen s...
Definition Filesystem.h:86
@ User
Kit was installed by the user, is automatically loaded, and most probably writable.
Definition Filesystem.h:78
static QString usr_data_path()
returns user data path
static QString log_file_path()
returns the full path (including filename) of the logfile
static const QString drumkit_ext
Definition Filesystem.h:121
static const QString songs_ext
Definition Filesystem.h:117
static QString default_song_name()
Default option to offer the user when saving an empty song to disk.
static QString patterns_dir()
returns user patterns path
Hydrogen Audio Engine.
Definition Hydrogen.h:54
void sequencer_stop()
Stop the internal sequencer.
Definition Hydrogen.cpp:231
void renameJackPorts(std::shared_ptr< Song > pSong)
Calls audioEngine_renameJackPorts() if Preferences::m_bJackTrackOuts is set to true.
Definition Hydrogen.cpp:957
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
bool isUnderSessionManagement() const
Song::Mode getMode() const
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
@ UNKNOWN_DRIVER
The provided input in createDriver() does not match any of the choices for H2Core::Preferences::Audio...
Definition Hydrogen.h:247
@ ERROR_STARTING_DRIVER
Unable to connect the audio driver stored in H2Core::AudioEngine::m_pAudioDriver in audioEngine_start...
Definition Hydrogen.h:254
@ JACK_CANNOT_ACTIVATE_CLIENT
Definition Hydrogen.h:256
@ JACK_ERROR_IN_PORT_REGISTER
Unable to register output ports for the JACK client using jack_port_register() (jack/jack....
Definition Hydrogen.h:280
@ OSC_CANNOT_CONNECT_TO_PORT
Unable to start the OSC server with the given port number.
Definition Hydrogen.h:285
@ JACK_CANNOT_CLOSE_CLIENT
The client of Hydrogen can not be disconnected from the JACK server using jack_client_close() (jack/j...
Definition Hydrogen.h:273
@ JACK_CANNOT_CONNECT_OUTPUT_PORT
Unable to connect either the JackAudioDriver::output_port_1 and the JackAudioDriver::output_port_name...
Definition Hydrogen.h:267
void setSessionDrumkitNeedsRelinking(bool bNeedsRelinking)
Definition Hydrogen.h:683
SoundLibraryDatabase * getSoundLibraryDatabase() const
Definition Hydrogen.h:95
QString getLastLoadedDrumkitName() const
CoreActionController * getCoreActionController() const
Definition Hydrogen.h:653
void sequencer_play()
Start the internal sequencer.
Definition Hydrogen.cpp:221
A class to convert a Hydrogen song to LilyPond format.
Definition Lilypond.h:42
void extractData(const Song &song)
Definition Lilypond.cpp:75
void write(const QString &sFilename) const
Definition Lilypond.cpp:96
Class for writing logs to the console.
Definition Logger.h:41
static unsigned bit_mask()
return the current log level bit mask
Definition Logger.h:103
static Logger * get_instance()
Returns a pointer to the current H2Core::Logger singleton stored in __instance.
Definition Logger.h:81
static void set_bit_mask(unsigned msk)
set the bitmask
Definition Logger.h:101
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
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
void set_pattern_idx(int value)
__pattern_idx setter
Definition Note.h:589
PatternList is a collection of patterns.
Definition PatternList.h:43
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
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
const notes_t * get_notes() const
get the virtual pattern set
Definition Pattern.h:355
std::multimap< int, Note * > notes_t
< multimap note type
Definition Pattern.h:50
Drumkit info.
Definition Playlist.h:37
void setNextSongByNumber(int SongNumber)
Definition Playlist.cpp:207
int getActiveSongNumber()
Definition Playlist.h:148
void activateSong(int SongNumber)
Definition Playlist.cpp:183
static Playlist * get_instance()
Returns a pointer to the current Playlist singleton stored in __instance.
Definition Playlist.h:60
int size() const
Definition Playlist.h:122
bool getSongFilenameByNumber(int songNumber, QString &fileName)
Definition Playlist.cpp:191
Manager for User Preferences File (singleton)
Definition Preferences.h:79
QString getLastSaveSongAsDirectory() const
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
QString getLastExportPatternAsDirectory() const
void setLadspaProperties(unsigned nFX, const WindowProperties &prop)
void setMixerProperties(const WindowProperties &prop)
void setPatternEditorProperties(const WindowProperties &prop)
void setLastExportLilypondDirectory(QString sPath)
void setSongEditorProperties(const WindowProperties &prop)
QString getLastExportLilypondDirectory() const
void setShowDevelWarning(bool value)
QString getLastOpenSongDirectory() const
bool savePreferences()
Save the preferences file.
void setInstrumentRackProperties(const WindowProperties &prop)
QString getLastOpenPatternDirectory() const
void setDirectorProperties(const WindowProperties &prop)
void setPlaylistDialogProperties(const WindowProperties &prop)
static QString audioDriverToQString(const AudioDriver &driver)
void setLastOpenSongDirectory(QString sPath)
void setLastOpenPatternDirectory(QString sPath)
void setMainFormProperties(const WindowProperties &prop)
void setAudioEngineInfoProperties(const WindowProperties &prop)
std::vector< QString > getRecentFiles() const
const QString & getPreferredLanguage()
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ GeneralTab
Any option in the General tab appeared.
@ Font
Either the font size or font family have changed.
@ Colors
At least one of the colors has changed.
bool getShowPlaybackTrack() const
void setLastSaveSongAsDirectory(QString sPath)
void setLastPlaylistFilename(const QString &filename)
void setLastExportPatternAsDirectory(QString sPath)
static std::shared_ptr< Song > getEmptySong()
Definition Song.cpp:964
void updatePatterns(bool bTriggerEvent=true)
void updateWindowTitle()
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
InstrumentRack * getInstrumentRack()
static bool openSong(QString sFilename)
PlaylistDialog * getPlayListDialog()
static bool checkDrumkitLicense(std::shared_ptr< H2Core::Drumkit > pDrumkit)
void showStatusBarMessage(const QString &sMessage, const QString &sCaller="")
static bool recoverEmptySong()
Specialized version of openSong( QString sFilename ) trying to open the autosave file corresponding t...
SongEditorPanel * getSongEditorPanel()
Director * getDirector()
Mixer * getMixer()
static InstrumentEditorPanel * get_instance()
static int findFreeDrumkitComponentId(int startingPoint=0)
void sendAlsaClientId()
lash_event_t * getNextEvent()
void sendEvent(LASH_Event_Type eventType, const char *value)
bool isConnected()
static LashClient * get_instance()
Definition LashClient.h:39
void startPlaybackAtCursor(QObject *pObject)
Relocates to current position of the cursor and starts playback if the transport isn't rolling yet.
QMenu * m_pDrumkitsMenu
Definition MainForm.h:313
void onBPMMinusAccelEvent()
void update_instrument_checkbox(bool show)
virtual void errorEvent(int nErrorCode) override
bool handleSelectNextPrevSongOnPlaylist(int step)
void handleSigUsr1()
void action_window_showTimeline()
void action_debug_logLevel_warn()
void action_file_export_lilypond()
void showUserManual()
Definition MainForm.cpp:940
void action_debug_logLevel_debug()
void showDevelWarning()
void action_window_showPatternEditor()
QAction * m_pViewTimelineAction
Definition MainForm.h:259
QMenu * m_pInputModeMenu
Definition MainForm.h:253
void action_file_export_pattern_as(int nPatternRow=-1)
Definition MainForm.cpp:975
void closeAll()
Wrapper around savePreferences() and quit() method of m_pQApp.
void action_debug_logLevel_error()
bool eventFilter(QObject *o, QEvent *e) override
void savePreferences()
Stores the current state of the GUI (position, width, height, and visibility of the widgets) in the H...
void onFixMissingSamples()
QMenu * m_pRecentFilesMenu
Definition MainForm.h:265
void action_window_showAutomationArea()
void action_debug_showFilesystemInfo()
void updateRecentUsedSongList()
void action_file_open()
Project > Open / Import into Session handling function.
void action_window_showSongEditor()
Shows the song editor.
QAction * m_pViewPlaybackTrackAction
Definition MainForm.h:260
std::map< int, int > keycodeInstrumentMap
Definition MainForm.h:284
QMenu * m_pUndoMenu
Definition MainForm.h:312
bool action_file_save_as()
Project > Save As / Export from Session handling function.
Definition MainForm.cpp:709
void action_instruments_importLibrary()
void action_file_open_recent(QAction *pAction)
void action_banks_properties()
void onRestartAccelEvent()
void action_report_bug()
Definition MainForm.cpp:934
void checkMissingSamples()
void action_debug_printObjects()
print the object map
void action_window_showPlaylistDialog()
HydrogenApp * h2app
Definition MainForm.h:247
QString getAutoSaveFilename()
void action_inputMode_instrument()
Definition MainForm.cpp:909
void update_automation_checkbox()
QAction * m_pViewPlaylistEditorAction
Definition MainForm.h:254
void action_file_export()
void action_instruments_addInstrument()
void createMenuBar()
Create the menubar.
Definition MainForm.cpp:268
QMenu * m_pInfoMenu
Definition MainForm.h:318
void onFixMidiSetup()
virtual void updatePreferencesEvent(int nValue) override
Handles the loading and saving of the H2Core::Preferences from the core part of H2Core::Hydrogen.
MainForm(QApplication *pQApplication, QString sSongFilename)
Definition MainForm.cpp:91
void update_mixer_checkbox()
virtual void quitEvent(int) override
void onPlayStopAccelEvent()
bool action_file_exit()
return true if the app needs to be closed.
Definition MainForm.cpp:638
void action_file_new()
Project > New handling function.
Definition MainForm.cpp:650
void onPreferencesChanged(H2Core::Preferences::Changes changes)
void closeEvent(QCloseEvent *ev) override
Window close event.
void action_help_about()
Definition MainForm.cpp:929
virtual void updateSongEvent(int nValue) override
void action_debug_logLevel_none()
void onLashPollTimer()
Definition MainForm.cpp:542
QAction * m_pDrumkitAction
Definition MainForm.h:263
virtual void undoRedoActionEvent(int nEvent) override
void action_donate()
Definition MainForm.cpp:623
void action_instruments_exportLibrary()
bool prepareSongOpening()
QSocketNotifier * snUsr1
Definition MainForm.h:250
QApplication * m_pQApp
Definition MainForm.h:52
static int sigusr1Fd[2]
Definition MainForm.h:249
virtual void jacksessionEvent(int nValue) override
QAction * m_pInstrumentAction
Definition MainForm.h:262
void action_instruments_onlineImportLibrary()
void startAutosaveTimer()
debug only
Definition MainForm.cpp:526
void update_playback_track_group()
void openSongWithDialog(const QString &sWindowTitle, const QString &sPath, bool bIsDemo)
void action_undo()
QMenu * m_pInstrumentsMenu
Definition MainForm.h:314
void action_instruments_saveAsLibrary()
void update_director_checkbox()
void checkMidiSetup()
QAction * m_pViewMixerAction
Definition MainForm.h:256
void functionDeleteInstrument(int nInstrument)
QMenu * m_pDebugMenu
Definition MainForm.h:317
void action_instruments_clearAll()
void openUndoStack()
void action_window_showMixer()
QMenu * m_pViewMenu
Definition MainForm.h:315
QMenu * m_pOptionsMenu
Definition MainForm.h:316
QUndoView * m_pUndoView
Definition MainForm.h:272
void action_instruments_saveLibrary()
void onAutoSaveTimer()
InfoBar * m_pMidiSetupInfoBar
Definition MainForm.h:292
QString m_sPreviousAutoSaveFilename
Since the filename of the current song does change whenever the users uses "Save As" multiple autosav...
Definition MainForm.h:326
void update_playlist_checkbox()
void onPlaylistDisplayTimer()
QMenu * m_pLogLevelMenu
Definition MainForm.h:252
void action_banks_open()
void action_window_showInstrumentRack()
void action_instruments_addComponent()
void action_inputMode_drumkit()
Definition MainForm.cpp:919
bool action_file_save()
Definition MainForm.cpp:849
QAction * m_pViewDirectorAction
Definition MainForm.h:255
void checkNecessaryDirectories()
void action_file_openDemo()
QAction * m_pViewAutomationPathAction
Definition MainForm.h:258
void showPreferencesDialog()
void editDrumkitProperties(bool bDrumkitNameLocked)
InfoBar * m_pMissingSamplesInfoBar
Definition MainForm.h:293
bool handleUnsavedChanges()
void action_window_toggleFullscreen()
void action_debug_openLogfile()
void action_file_export_midi()
void action_file_openPattern()
void action_redo()
QAction * m_pViewMixerInstrumentRackAction
Definition MainForm.h:257
void action_debug_showAudioEngineInfo()
void action_window_showPlaybackTrack()
void action_window_show_DirectorWidget()
virtual void playlistLoadSongEvent(int nIndex) override
void onBPMPlusAccelEvent()
static void usr1SignalHandler(int unused)
void action_file_songProperties()
QActionGroup * m_pViewPlaybackTrackActionGroup
Definition MainForm.h:261
void initKeyInstMap()
QMenu * m_pFileMenu
Definition MainForm.h:311
QTimer m_AutosaveTimer
Definition MainForm.h:275
void action_debug_logLevel_info()
static int dereferenceDrumkit(std::shared_ptr< H2Core::Song > pSong)
Replaces a path in Song::m_sLastLoadedDrumkitPath pointing to the session folder with one pointing to...
static void replaceDrumkitPath(std::shared_ptr< H2Core::Song > pSong, const QString &sDrumkitPath)
Replaces @H2Core::Song::m_sLastLoadedDrumkitPath as well as all @H2Core::Instrument::__drumkit_path b...
DrumPatternEditor * getDrumPatternEditor()
void clearSelection()
Clear the pattern editor selection.
static void setPalette(QApplication *pQApp)
Function used to update the global palette of the QApplication.
Definition Skin.cpp:106
SongEditor * getSongEditor() const
int getCursorColumn() const
Definition SongEditor.h:256
Song Properties Dialog.
This dialog is used to import a SoundLibrary file from a local file or via HTTP.
constexpr int getPointSize(H2Core::FontTheme::FontSize fontSize) const
#define MAX_FX
Maximum number of effects.
Definition config.dox:83
@ EVENT_TEMPO_CHANGED
Definition EventQueue.h:103
@ EVENT_SELECTED_INSTRUMENT_CHANGED
Definition EventQueue.h:77