hydrogen 1.2.6
ExportSongDialog.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 <QProgressBar>
24#include <QLabel>
25
26#include "CommonStrings.h"
27#include "ExportSongDialog.h"
28#include "HydrogenApp.h"
29#include "Mixer/Mixer.h"
30#include "Widgets/FileDialog.h"
31
32#include <core/Basics/Note.h>
33#include <core/Basics/Pattern.h>
40#include <core/Basics/Song.h>
41#include <core/Hydrogen.h>
43#include <core/Timeline.h>
44#include <core/IO/AudioOutput.h>
49#include <core/EventQueue.h>
50
51#include <memory>
52
53#ifdef WIN32
54#include <time.h>
55#endif
56
57using namespace H2Core;
58
60
62{
63 int Index = 0;
64
65 switch ( interpolateMode ) {
66 case Interpolation::InterpolateMode::Linear:
67 Index = 0;
68 break;
69 case Interpolation::InterpolateMode::Cosine:
70 Index = 1;
71 break;
72 case Interpolation::InterpolateMode::Third:
73 Index = 2;
74 break;
75 case Interpolation::InterpolateMode::Cubic:
76 Index = 3;
77 break;
78 case Interpolation::InterpolateMode::Hermite:
79 Index = 4;
80 break;
81 }
82
83 return Index;
84}
85
86// Here we are going to store export filename
88
90 : QDialog(parent)
91 , m_bExporting( false )
92 , m_pHydrogen( Hydrogen::get_instance() )
93 , m_pPreferences( Preferences::get_instance() )
94{
95 setupUi( this );
96 setModal( true );
97 setWindowTitle( tr( "Export song" ) );
98
99 exportTypeCombo->addItem(tr("Export to a single track"));
100 exportTypeCombo->addItem(tr("Export to separate tracks"));
101 exportTypeCombo->addItem(tr("Both"));
102
104
105 browseBtn->setFixedFontSize( 13 );
106 browseBtn->setSize( QSize( 80, 26 ) );
107 browseBtn->setBorderRadius( 3 );
108 browseBtn->setType( Button::Type::Push );
109 okBtn->setFixedFontSize( 13 );
110 okBtn->setSize( QSize( 80, 26 ) );
111 okBtn->setBorderRadius( 3 );
112 okBtn->setType( Button::Type::Push );
113 closeBtn->setFixedFontSize( 13 );
114 closeBtn->setBorderRadius( 3 );
115 closeBtn->setSize( QSize( 80, 26 ) );
116 closeBtn->setType( Button::Type::Push );
117
118 m_pProgressBar->setValue( 0 );
119
120 m_bQfileDialog = false;
121 m_bExportTrackouts = false;
122 m_nInstrument = 0;
124 m_bOverwriteFiles = false;
125 m_bOldRubberbandBatchMode = m_pPreferences->getRubberBandBatchMode();
126
127 // Format combo box
128 formatCombo->addItem( "FLAC (Free Lossless Audio Codec)" );
130 formatCombo->addItem( "OPUS (Opus Compressed Audio)" );
132 formatCombo->addItem( "OGG (Vorbis Compressed Audio)" );
134 formatCombo->addItem( "MP3 (MP3 Compressed Audio)" );
136 formatCombo->addItem( "WAV (Waveform Audio)" );
138 formatCombo->addItem( "AIFF (Audio Interchange File Format)" );
140 formatCombo->addItem( "AU (Sun/NeXT Audio)" );
142 formatCombo->addItem( "CAF (Core Audio Format)" );
144 formatCombo->addItem( "VOC (Creative Voice)" );
146 formatCombo->addItem( "W64 (Sonic Foundry’s 64 bit RIFF/WAV)" );
148
149 // Per-default FLAC format is chosen, for which we only provide compression
150 // level but no sample rate and depth settings (IMHO providing them and
151 // setting sample rates smaller than 48k does only make sense when encoding
152 // speech and not for music yet alone drumkits.)
153 formatCombo->setCurrentIndex( 0 );
154 connect( formatCombo, SIGNAL(currentIndexChanged(int)),
155 this, SLOT(formatComboIndexChanged(int) ) );
156
157 sampleRateCombo->hide();
158 sampleRateLabel->hide();
159 sampleDepthCombo->hide();
160 sampleDepthLabel->hide();
161 // Best-possible quality (MP3, Vorbis, Opus) / fastest encoding (FLAC) as
162 // default.
163 compressionLevelSpinBox->setValue( 0.0 );
164
165 // use of rubberband batch
166 if( checkUseOfRubberband() ) {
167 toggleRubberbandCheckBox->setChecked( m_bOldRubberbandBatchMode );
168 connect(toggleRubberbandCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleRubberbandBatchMode( bool )));
169 } else {
170 toggleRubberbandCheckBox->setEnabled( false );
171 toggleRubberbandCheckBox->setToolTip( tr( "No sample in the current song uses Rubberband" ) );
172 }
173
174 // use of timeline
175 auto pSong = H2Core::Hydrogen::get_instance()->getSong();
176 toggleTimeLineBPMCheckBox->setChecked( pSong->getIsTimelineActivated());
177 m_bOldTimeLineBPMMode = pSong->getIsTimelineActivated();
178 connect(toggleTimeLineBPMCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleTimeLineBPMMode( bool )));
179
180 // use of interpolation mode
181 m_OldInterpolationMode = m_pHydrogen->getAudioEngine()->getSampler()->getInterpolateMode();
182 resampleComboBox->setCurrentIndex( interpolateModeToComboBoxIndex( m_OldInterpolationMode ) );
183 connect(resampleComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resampleComboBoIndexChanged(int)));
184
185 //Load the other settings..
187
188 // Have the dialog find the best size
189 adjustSize();
190}
191
193{
194 if ( auto pH2App = HydrogenApp::get_instance() ) {
195 pH2App->removeEventListener( this );
196 }
197}
198
200{
201 QString sDefaultFilename = m_pHydrogen->getSong()->getFilename();
202
203 // If song is not saved then use song name otherwise use the song filename
204 if( sDefaultFilename.isEmpty() ){
205 sDefaultFilename = m_pHydrogen->getSong()->getName();
206 } else {
207 // extracting filename from full path
208 QFileInfo qDefaultFile( sDefaultFilename );
209 sDefaultFilename = qDefaultFile.fileName();
210 }
211
212 sDefaultFilename.replace( '*', "_" );
213 sDefaultFilename.replace( Filesystem::songs_ext, "" );
214 return QString( "%1.%2" ).arg( sDefaultFilename ).arg( m_sExtension );
215}
216
218{
219 // extracting dirname from export box
220 QString sFilename = exportNameTxt->text();
221 QFileInfo info( sFilename );
222 QDir dir = info.absoluteDir();
223 if ( !dir.exists() ) {
224 // very strange if it happens but better to check for it anyway
225 return;
226 }
227
228 // saving filename for this session
229 sLastFilename = info.fileName();
231
232 // saving other options
233 m_pPreferences->setExportModeIdx( exportTypeCombo->currentIndex() );
234 m_pPreferences->setExportFormat( m_formatMap[ formatCombo->currentIndex() ] );
235 m_pPreferences->setExportCompressionLevel(compressionLevelSpinBox->value() );
236 m_pPreferences->setExportSampleRateIdx( sampleRateCombo->currentIndex() );
237 m_pPreferences->setExportSampleDepthIdx( sampleDepthCombo->currentIndex() );
238}
239
241{
242 // In case the filename used in the last session is invalid, we discard it.
243 if ( ! sLastFilename.isEmpty() ) {
244 QFileInfo info( sLastFilename );
245 if ( info.suffix().isEmpty() ||
246 Filesystem::AudioFormatFromSuffix( info.suffix() ) ==
248 sLastFilename = "";
249 }
250 }
251
252 if ( sLastFilename.isEmpty() ) {
254 }
255
256 // loading previous directory
257 QString sDirPath = m_pPreferences->getLastExportSongDirectory();
258 QDir qd = QDir( sDirPath );
259 // joining filepath with dirname
260 QString sFullPath = qd.absoluteFilePath( sLastFilename );
261
262 exportNameTxt->setText( sFullPath );
263
264 // loading rest of the options
265 const auto previousFormat = m_pPreferences->getExportFormat();
266 if ( previousFormat != Filesystem::AudioFormat::Unknown ) {
267 for ( const auto [ nnIndex, fformat ] : m_formatMap ) {
268 if ( fformat == previousFormat ) {
269 formatCombo->setCurrentIndex( nnIndex );
270 break;
271 }
272 }
273 }
274 compressionLevelSpinBox->setValue(
275 m_pPreferences->getExportCompressionLevel() );
276 exportTypeCombo->setCurrentIndex( m_pPreferences->getExportModeIdx() );
277
278 int nExportSampleRateIdx = m_pPreferences->getExportSampleRateIdx();
279 if( nExportSampleRateIdx > 0 ) {
280 sampleRateCombo->setCurrentIndex( nExportSampleRateIdx );
281 } else {
282 sampleRateCombo->setCurrentIndex( 0 );
283 }
284
285
286 int nExportBithDepthIdx = m_pPreferences->getExportSampleDepthIdx();
287 if( nExportBithDepthIdx > 0 ) {
288 sampleDepthCombo->setCurrentIndex( nExportBithDepthIdx );
289 } else {
290 sampleDepthCombo->setCurrentIndex( 0 );
291 }
292}
293
295{
297 if ( ! Filesystem::dir_writable( sPath, false ) ){
298 sPath = QDir::homePath();
299 }
300
301 FileDialog fd(this);
302 fd.setFileMode(QFileDialog::AnyFile);
303
304 const auto format = m_formatMap[ formatCombo->currentIndex() ];
305
306 switch ( format ) {
308 fd.setNameFilter( "Microsoft WAV (*.wav *.WAV)" );
309 break;
311 fd.setNameFilter( "Apple AIFF (*.aiff *.AIFF)" );
312 break;
314 fd.setNameFilter( "Lossless Flac (*.flac *.FLAC)" );
315 break;
317 fd.setNameFilter( "Compressed Ogg/Opus (*.opus *.OPUS)" );
318 break;
320 fd.setNameFilter( "Compressed Ogg/Vorbis (*.ogg *.OGG)" );
321 break;
323 fd.setNameFilter( "Compressed MPEG Layer 3 (*.mp3 *.MP3)" );
324 break;
326 fd.setNameFilter( "Sun/NeXT AU (*.au *.AU)" );
327 break;
329 fd.setNameFilter( "Core Audio Format (*.caf *.CAF)" );
330 break;
332 fd.setNameFilter( "Creative Voice File (*.voc *.VOC)" );
333 break;
335 fd.setNameFilter( "Sonic Foundrys' 64 bit RIFF/WAV (*.w64 *.W64)" );
336 break;
338 default:
339 ERRORLOG( QString( "Unhandle combo index [%1]" )
340 .arg( formatCombo->currentIndex() ) );
341 return;
342 }
343
344 fd.setDirectory( sPath );
345 fd.setAcceptMode( QFileDialog::AcceptSave );
346 fd.setWindowTitle( tr( "Export song" ) );
347
348 QString defaultFilename = exportNameTxt->text();
349
350 fd.selectFile(defaultFilename);
351
352 QString sFilename = "";
353 if ( fd.exec() ) {
354 sFilename = fd.selectedFiles().first();
355 m_bQfileDialog = true;
356 }
357
358 if ( !sFilename.isEmpty() ) {
359 // this second extension check is mostly important if you leave a dot
360 // without a regular extionsion in a sFilename
361 if( ! sFilename.endsWith( m_sExtension ) ){
362 sFilename.append( QString( ".%1" ).arg( m_sExtension ) );
363 }
364
365 exportNameTxt->setText( sFilename );
366 }
367}
368
370{
371 // check if directory exists otherwise error
372 QString filename = exportNameTxt->text();
373 QFileInfo file( filename );
374 QDir dir = file.dir();
375 if( !dir.exists() ) {
376 QMessageBox::warning(
377 this, "Hydrogen",
378 tr( "Directory %1 does not exist").arg( dir.absolutePath() ),
379 QMessageBox::Ok
380 );
381 return false;
382 }
383
384 return true;
385}
386
388{
389 if ( m_bExporting ) {
390 return;
391 }
392
393 if( !validateUserInput() ) {
394 return;
395 }
396
398
399 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
400
401 QFileInfo fileInfo( exportNameTxt->text() );
402 if ( ! Filesystem::dir_writable( fileInfo.absoluteDir().absolutePath(), false ) ) {
403 QMessageBox::warning( this, "Hydrogen",
404 pCommonStrings->getFileDialogMissingWritePermissions(),
405 QMessageBox::Ok );
406 return;
407 }
408
409 int nSampleRate = sampleRateCombo->currentText().toInt();
410 int nSampleDepth = sampleDepthCombo->currentText().toInt();
411 const float fCompressionLevel = compressionLevelSpinBox->value();
412
413 // Some formats only support a certain set of parameters and need special
414 // treatment.
415 const auto format = m_formatMap[ formatCombo->currentIndex() ];
416 if ( format == Filesystem::AudioFormat::Ogg ||
418 nSampleDepth = 32;
419 nSampleRate = 48000;
420 }
421 else if ( format == Filesystem::AudioFormat::Voc ) {
422 nSampleDepth = std::min( 16, nSampleDepth );
423 }
424 else if ( format == Filesystem::AudioFormat::Flac ||
425 format == Filesystem::AudioFormat::Mp3 ) {
426 nSampleDepth = 16;
427 nSampleRate = 48000;
428 }
429
430 auto pPref = Preferences::get_instance();
431 std::shared_ptr<Song> pSong = m_pHydrogen->getSong();
432 auto pInstrumentList = pSong->getInstrumentList();
433
434 // License related export warnings
435 if ( pPref->m_bShowExportSongLicenseWarning ) {
436
437 QMessageBox licenseWarning( this );
438
439 auto drumkitContent =
440 pSong->getInstrumentList()->summarizeContent( pSong->getComponents() );
441
442 bool bHasAttribution = false;
443 bool bIsCopyleft = false;
444 QStringList licenses;
445 QString sLicense;
446 for ( const auto& ccontent : drumkitContent ) {
447 if ( ccontent->m_license.hasAttribution() ) {
448 sLicense = QString( "%1 (by %2)" )
449 .arg( ccontent->m_license.getLicenseString() )
450 .arg( ccontent->m_license.getCopyrightHolder() );
451 bHasAttribution = true;
452 }
453 else {
454 sLicense = ccontent->m_license.getLicenseString();
455 }
456
457 if ( ! licenses.contains( sLicense ) ) {
458 licenses << sLicense;
459
460 if ( ccontent->m_license.isCopyleft() ) {
461 bIsCopyleft = true;
462 }
463 }
464 }
465 QString sMsg = QString( tr( "Your song uses samples of the following license:" ) )
466 .append( "<ul>" );
467 for ( const auto& llicense : licenses ) {
468 sMsg.append( QString( "<li>%1</li>" ).arg( llicense ) );
469 }
470 sMsg.append( "</ul>" );
471
472 if ( bIsCopyleft ) {
473 sMsg.append( QString( "<p>%1</p>" )
474 .arg( pCommonStrings->getLicenseCopyleftWarning() ) );
475 }
476
477 if ( bHasAttribution ) {
478 sMsg.append( QString( "<p>%1</p>" )
479 .arg( pCommonStrings->getLicenseAttributionWarning() ) );
480 }
481
482 sMsg.append( "\n" ).append( tr( "Be sure you satisfy all license conditions and give the required attribution." ) );
483
484 licenseWarning.setWindowTitle( pCommonStrings->getLicenseWarningWindowTitle() );
485 licenseWarning.setText( sMsg );
486 licenseWarning.setTextFormat( Qt::RichText );
487
488 licenseWarning.addButton( pCommonStrings->getButtonOk(),
489 QMessageBox::AcceptRole );
490 auto pMuteButton =
491 licenseWarning.addButton( pCommonStrings->getMutableDialog(),
492 QMessageBox::YesRole );
493 auto pRejectButton =
494 licenseWarning.addButton( pCommonStrings->getButtonCancel(),
495 QMessageBox::RejectRole );
496 licenseWarning.exec();
497
498 if ( licenseWarning.clickedButton() == pMuteButton ) {
499 pPref->m_bShowExportSongLicenseWarning = false;
500 }
501 else if ( licenseWarning.clickedButton() == pRejectButton ) {
502 return;
503 }
504 }
505
506 m_bOverwriteFiles = false;
507
508 if( exportTypeCombo->currentIndex() == EXPORT_TO_SINGLE_TRACK || exportTypeCombo->currentIndex() == EXPORT_TO_BOTH ){
509 m_bExportTrackouts = false;
510
511 QString filename = exportNameTxt->text();
512 if ( fileInfo.exists() == true && m_bQfileDialog == false ) {
513
514 int res;
515 if( exportTypeCombo->currentIndex() == EXPORT_TO_SINGLE_TRACK ){
516 res = QMessageBox::information( this, "Hydrogen", tr( "The file %1 exists. \nOverwrite the existing file?").arg(filename), QMessageBox::Yes | QMessageBox::No );
517 } else {
518 res = QMessageBox::information( this, "Hydrogen", tr( "The file %1 exists. \nOverwrite the existing file?").arg(filename), QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll);
519 }
520
521 if (res == QMessageBox::YesToAll ){
522 m_bOverwriteFiles = true;
523 }
524
525 if (res == QMessageBox::No ) {
526 return;
527 }
528 }
529
530 if( exportTypeCombo->currentIndex() == EXPORT_TO_BOTH ){
531 m_bExportTrackouts = true;
532 }
533
534 /* arm all tracks for export */
535 for (auto i = 0; i < pInstrumentList->size(); i++) {
536 pInstrumentList->get(i)->set_currently_exported( true );
537 }
538
539 if ( ! m_pHydrogen->startExportSession(
540 nSampleRate, nSampleDepth, fCompressionLevel ) ) {
541 QMessageBox::critical( this, "Hydrogen", tr( "Unable to export song" ) );
542 return;
543 }
544 m_pHydrogen->startExportSong( filename );
545 return;
546 }
547
548 if ( exportTypeCombo->currentIndex() == EXPORT_TO_SEPARATE_TRACKS ){
549 m_bExportTrackouts = true;
550 if ( ! m_pHydrogen->startExportSession(
551 nSampleRate, nSampleDepth, fCompressionLevel ) ) {
552 QMessageBox::critical( this, "Hydrogen", tr( "Unable to export tracks" ) );
553 return;
554 }
555 exportTracks();
556 return;
557 }
558
559}
560
562{
563 std::shared_ptr<Song> pSong = m_pHydrogen->getSong();
564 unsigned nPatterns = pSong->getPatternList()->size();
565
566 bool bInstrumentHasNotes = false;
567
568 for ( unsigned i = 0; i < nPatterns; i++ ) {
569 Pattern *pPattern = pSong->getPatternList()->get( i );
570 const Pattern::notes_t* notes = pPattern->get_notes();
571 FOREACH_NOTE_CST_IT_BEGIN_LENGTH(notes,it,pPattern) {
572 Note *pNote = it->second;
573 assert( pNote );
574
575 if( pNote->get_instrument()->get_id() == pSong->getInstrumentList()->get(m_nInstrument)->get_id() ){
576 bInstrumentHasNotes = true;
577 break;
578 }
579 }
580 }
581
582 return bInstrumentHasNotes;
583}
584
585QString ExportSongDialog::findUniqueExportFilenameForInstrument( std::shared_ptr<Instrument> pInstrument )
586{
587 std::shared_ptr<Song> pSong = m_pHydrogen->getSong();
588 QString uniqueInstrumentName;
589
590 int instrumentOccurence = 0;
591 for(int i=0; i < pSong->getInstrumentList()->size(); i++ ){
592 if( pSong->getInstrumentList()->get(m_nInstrument)->get_name() == pInstrument->get_name()){
593 instrumentOccurence++;
594 }
595 }
596
597 if(instrumentOccurence >= 2){
598 uniqueInstrumentName = pInstrument->get_name() + QString("_") + QString::number( pInstrument->get_id() );
599 } else {
600 uniqueInstrumentName = pInstrument->get_name();
601 }
602
603 return uniqueInstrumentName;
604}
605
607{
608 std::shared_ptr<Song> pSong = m_pHydrogen->getSong();
609 auto pInstrumentList = pSong->getInstrumentList();
610
612
613 //if a instrument contains no notes we jump to the next instrument
614 bool bInstrumentHasNotes = currentInstrumentHasNotes();
615
616 if( !bInstrumentHasNotes ){
617 if( m_nInstrument == pInstrumentList->size() -1 ){
618 m_bExportTrackouts = false;
619 m_nInstrument = 0;
620 return;
621 } else {
623 exportTracks();
624 return;
625 }
626 }
627
628 // Ensure we use the right extension.
629 const QString sSuffix = QString( ".%1" ).arg( m_sExtension );
630 const QString sTemplateName = exportNameTxt->text();
631 QString sBaseName = sTemplateName;
632 if ( sTemplateName.endsWith( sSuffix, Qt::CaseInsensitive ) ) {
633 sBaseName.chop( sSuffix.size() );
634 }
635
636 const QString sInstrumentName = findUniqueExportFilenameForInstrument(
637 pInstrumentList->get( m_nInstrument ) );
638 QString sExportName;
639 if ( sBaseName.isEmpty() || sBaseName.endsWith( "/" ) ||
640 sBaseName.endsWith( "\\" ) ) {
641 // Allow to use just the instrument names when leaving the song name
642 // blank.
643 sExportName = QString( "%1%2" ).arg( sBaseName )
644 .arg( sInstrumentName );
645 }
646 else {
647 sExportName = QString( "%1-%2" ).arg( sBaseName )
648 .arg( sInstrumentName );
649 }
650
651 const QString sFileName = QString( "%1%2" ).arg( sExportName )
652 .arg( sSuffix );
653
654 if ( QFile( sFileName ).exists() == true && m_bQfileDialog == false &&
656 const int nRes = QMessageBox::information(
657 this, "Hydrogen", tr( "The file %1 exists. \nOverwrite the existing file?")
658 .arg( sFileName ),
659 QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll );
660 if ( nRes == QMessageBox::No ) {
661 return;
662 }
663 if ( nRes == QMessageBox::YesToAll ) {
664 m_bOverwriteFiles = true;
665 }
666 }
667
668 if( m_nInstrument > 0 ){
669 m_pHydrogen->stopExportSong();
670 m_bExporting = false;
671 }
672
673 for (auto i = 0; i < pInstrumentList->size(); i++) {
674 pInstrumentList->get(i)->set_currently_exported( false );
675 }
676
677 pSong->getInstrumentList()->get(m_nInstrument)->set_currently_exported( true );
678
679 m_pHydrogen->startExportSong( sFileName );
680
681 if(! (m_nInstrument == pInstrumentList->size()) ){
683 }
684 }
685
686}
687
688void ExportSongDialog::closeEvent( QCloseEvent *event ) {
689 UNUSED( event );
690 closeExport();
691}
697
698 m_pHydrogen->stopExportSong();
699 m_pHydrogen->stopExportSession();
700
701 m_bExporting = false;
702
703 if( m_pPreferences->getRubberBandBatchMode() ){
704 m_pHydrogen->getAudioEngine()->lock( RIGHT_HERE );
705 m_pHydrogen->recalculateRubberband( m_pHydrogen->getAudioEngine()->getTransportPosition()->getBpm() );
706 m_pHydrogen->getAudioEngine()->unlock();
707 }
708 m_pPreferences->setRubberBandBatchMode( m_bOldRubberbandBatchMode );
709 m_pHydrogen->setIsTimelineActivated( m_bOldTimeLineBPMMode );
710
711 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( m_OldInterpolationMode );
712 accept();
713}
714
715
717{
718 const auto format = m_formatMap[ nIndex ];
719 if ( format == Filesystem::AudioFormat::Unknown ) {
720 ERRORLOG( QString( "Invalid index [%1]" ).arg( nIndex ) );
721 okBtn->setIsActive( false );
722 return;
723 }
724
725 okBtn->setIsActive( true );
726
727 switch( format ) {
736 sampleRateCombo->show();
737 sampleRateLabel->show();
738 sampleDepthCombo->show();
739 sampleDepthLabel->show();
740 compressionLevelSpinBox->hide();
741 compressionLevelLabel->hide();
742 break;
747 default:
748 sampleRateCombo->hide();
749 sampleRateLabel->hide();
750 sampleDepthCombo->hide();
751 sampleDepthLabel->hide();
752 compressionLevelSpinBox->show();
753 compressionLevelLabel->show();
754 break;
755 }
756
757 if ( format == Filesystem::AudioFormat::Voc ) {
758 // Voc files do only support sample rates up to 16 bits
759 if ( sampleDepthCombo->count() == 4 ) {
760 sampleDepthCombo->removeItem( 3 );
761 sampleDepthCombo->removeItem( 2 );
762 }
763 }
764 else {
765 if ( sampleDepthCombo->count() == 2 ) {
766 sampleDepthCombo->addItems( QStringList() << "24" << "32" );
767 }
768 }
769
771
772 if ( ! exportNameTxt->text().isEmpty() ) {
773 const QString sPreviousFilename = exportNameTxt->text();
774 auto splitty = sPreviousFilename.split(".");
775 splitty.removeLast();
776 exportNameTxt->setText( QString( "%1.%2" )
777 .arg( splitty.join( "." ) ).arg( m_sExtension ) );
778 }
779}
780
782{
783 const QString sFilenameLower = exportNameTxt->text().toLower();
784 okBtn->setEnabled( ! sFilenameLower.isEmpty() );
785
786 const auto splittedFilename = exportNameTxt->text().split(".");
787
788 const auto format = Filesystem::AudioFormatFromSuffix(
789 splittedFilename.last(), true );
790
791 if ( format == Filesystem::AudioFormat::Unknown ) {
792 WARNINGLOG( QString( "Unknown file format in filename [%1]" )
793 .arg( exportNameTxt->text() ) );
794 okBtn->setIsActive( false );
795 return;
796 }
797 okBtn->setIsActive( true );
798 const auto previousFormat = m_formatMap[ formatCombo->currentIndex() ];
799
800 if ( previousFormat != format ) {
801 for ( const auto [ nnIndex, fformat ] : m_formatMap ) {
802 if ( fformat == format ) {
803 formatCombo->setCurrentIndex( nnIndex );
804 break;
805 }
806 }
807 }
808}
809
811{
812 m_pProgressBar->setValue( nValue );
813 if ( nValue == 100 ) {
814
815 m_bExporting = false;
816
817 // Check whether an error occured during export.
818 const auto pDriver = static_cast<DiskWriterDriver*>(
820 if ( pDriver != nullptr && pDriver->m_bWritingFailed ) {
821 m_nInstrument = 0;
822 m_bExportTrackouts = false;
823 QMessageBox::critical( this, "Hydrogen",
824 tr( "Unable to export song" ),
825 QMessageBox::Ok );
826 m_pProgressBar->setValue( 0 );
827 }
828 else {
829 if ( m_nInstrument ==
830 Hydrogen::get_instance()->getSong()->getInstrumentList()->size() ) {
831 m_nInstrument = 0;
832 m_bExportTrackouts = false;
833 }
834
835 if ( m_bExportTrackouts ) {
836 exportTracks();
837 }
838 }
839 }
840
841 if ( nValue < 100 ) {
842 closeBtn->setEnabled( false );
843 resampleComboBox->setEnabled( false );
844 }
845 else {
846 closeBtn->setEnabled( true );
847 resampleComboBox->setEnabled( true );
848 }
849}
850
852{
853 m_pPreferences->setRubberBandBatchMode(toggled);
854}
855
857{
858 m_pHydrogen->setIsTimelineActivated( toggled );
859}
860
865
867{
868 switch ( index ){
869 case 0:
870 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( Interpolation::InterpolateMode::Linear );
871 break;
872 case 1:
873 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( Interpolation::InterpolateMode::Cosine );
874 break;
875 case 2:
876 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( Interpolation::InterpolateMode::Third );
877 break;
878 case 3:
879 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( Interpolation::InterpolateMode::Cubic );
880 break;
881 case 4:
882 m_pHydrogen->getAudioEngine()->getSampler()->setInterpolateMode( Interpolation::InterpolateMode::Hermite );
883 break;
884 }
885}
886
888{
889 std::shared_ptr<Song> pSong = m_pHydrogen->getSong();
890 assert(pSong);
891
892 if(pSong){
893 auto pSongInstrList = pSong->getInstrumentList();
894 assert(pSongInstrList);
895 for ( unsigned nInstr = 0; nInstr < pSongInstrList->size(); ++nInstr ) {
896 auto pInstr = pSongInstrList->get( nInstr );
897 assert( pInstr );
898 if ( pInstr ){
899 for ( const auto& pCompo : *pInstr->get_components() ) {
900 if ( pCompo != nullptr ) {
901 for ( int nLayer = 0; nLayer < InstrumentComponent::getMaxLayers(); nLayer++ ) {
902 auto pLayer = pCompo->get_layer( nLayer );
903 if ( pLayer ) {
904 auto pSample = pLayer->get_sample();
905 if ( pSample != nullptr ) {
906 if( pSample->get_rubberband().use ) {
907 return true;
908 }
909 }
910 }
911 }
912 }
913 }
914 }
915 }
916 }
917 return false;
918}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
@ EXPORT_TO_BOTH
@ EXPORT_TO_SEPARATE_TRACKS
@ EXPORT_TO_SINGLE_TRACK
static int interpolateModeToComboBoxIndex(Interpolation::InterpolateMode interpolateMode)
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
#define FOREACH_NOTE_CST_IT_BEGIN_LENGTH(_notes, _it, _pattern)
Iterate over all accessible notes between position 0 and length of _pattern in an immutable way.
Definition Pattern.h:285
@ Push
Button is not set checkable.
Definition Button.h:68
static QString sLastFilename
virtual void progressEvent(int nValue) override
void toggleRubberbandBatchMode(bool toggled)
void setResamplerMode(int index)
H2Core::Preferences * m_pPreferences
QString createDefaultFilename()
QString findUniqueExportFilenameForInstrument(std::shared_ptr< H2Core::Instrument > pInstrument)
std::map< int, H2Core::Filesystem::AudioFormat > m_formatMap
void closeEvent(QCloseEvent *event) override
void on_exportNameTxt_textChanged(const QString &text)
void formatComboIndexChanged(int index)
void restoreSettingsFromPreferences()
ExportSongDialog(QWidget *parent)
InterpolateMode m_OldInterpolationMode
void resampleComboBoIndexChanged(int index)
void toggleTimeLineBPMMode(bool toggled)
H2Core::Hydrogen * m_pHydrogen
Custom file dialog checking whether the user has write access to the selected folder before allowing ...
Definition FileDialog.h:34
AudioOutput * getAudioDriver() const
Driver for export audio to disk.
static AudioFormat AudioFormatFromSuffix(const QString &sFile, bool bSilent=false)
Determines the audio format of the provided filename or path based on its suffix.
static bool dir_writable(const QString &path, bool silent=false)
returns true if the given path is a writable regular directory
static const QString songs_ext
Definition Filesystem.h:117
static QString AudioFormatToSuffix(const AudioFormat &format, bool bSilent=false)
Converts format to the default lower case suffix of the format.
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
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
Pattern class is a Note container.
Definition Pattern.h:46
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
Manager for User Preferences File (singleton)
Definition Preferences.h:79
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
void setLastExportSongDirectory(QString sPath)
QString getLastExportSongDirectory() const
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
#define UNUSED(v)
Definition Globals.h:42