From 32f07ac317b42dda42a1d00b0a3b1fa1feef0655 Mon Sep 17 00:00:00 2001 From: Jousboxx Date: Sat, 8 Apr 2017 15:32:30 -0400 Subject: [PATCH 01/65] Added Buzzer Beater to demos (#3482) Add 2017 kicker competition winner to demo projects. --- data/projects/demos/Jousboxx-BuzzerBeater.mmpz | Bin 0 -> 43351 bytes data/projects/demos/LICENSES.TXT | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 data/projects/demos/Jousboxx-BuzzerBeater.mmpz diff --git a/data/projects/demos/Jousboxx-BuzzerBeater.mmpz b/data/projects/demos/Jousboxx-BuzzerBeater.mmpz new file mode 100644 index 0000000000000000000000000000000000000000..60795608c70748062e7452efbbc2e9932133c615 GIT binary patch literal 43351 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 g&w0U Date: Sat, 15 Apr 2017 04:05:34 +0300 Subject: [PATCH 02/65] Simpler SF2 Player file dialog. Copied from GIG Player. Opens correct SF2 plugin directory when running LMMS from build directory without install. (#3502) --- plugins/sf2_player/sf2_player.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index 9f1796503..dbf919434 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -545,7 +545,7 @@ void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) { return; } - + const f_cnt_t tfp = _n->totalFramesPlayed(); if( tfp == 0 ) @@ -560,7 +560,7 @@ void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) return; } const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity(); - + SF2PluginData * pluginData = new SF2PluginData; pluginData->midiNote = midiNote; pluginData->lastPanning = 0; @@ -571,7 +571,7 @@ void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) pluginData->noteOffSent = false; _n->m_pluginData = pluginData; - + // insert the nph to the playing notes vector m_playingNotesMutex.lock(); m_playingNotes.append( _n ); @@ -593,7 +593,7 @@ void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) void sf2Instrument::noteOn( SF2PluginData * n ) { m_synthMutex.lock(); - + // get list of current voice IDs so we can easily spot the new // voice after the fluid_synth_noteon() call const int poly = fluid_synth_get_polyphony( m_synth ); @@ -644,7 +644,7 @@ void sf2Instrument::noteOff( SF2PluginData * n ) fluid_synth_noteoff( m_synth, m_channel, n->midiNote ); m_synthMutex.unlock(); } - + } @@ -696,7 +696,7 @@ void sf2Instrument::play( sampleFrame * _working_buffer ) currentNote = m_playingNotes[i]; } } - + // process the current note: // first see if we're synced in frame count SF2PluginData * currentData = static_cast( currentNote->m_pluginData ); @@ -736,7 +736,7 @@ void sf2Instrument::play( sampleFrame * _working_buffer ) instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL ); } - + void sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf ) { m_synthMutex.lock(); @@ -1083,15 +1083,7 @@ void sf2InstrumentView::showFileDialog() QString dir; if( k->m_filename != "" ) { - QString f = k->m_filename; - if( QFileInfo( f ).isRelative() ) - { - f = ConfigManager::inst()->sf2Dir() + f; - if( QFileInfo( f ).exists() == false ) - { - f = ConfigManager::inst()->factorySamplesDir() + k->m_filename; - } - } + QString f = SampleBuffer::tryToMakeAbsolute( k->m_filename ); ofd.setDirectory( QFileInfo( f ).absolutePath() ); ofd.selectFile( QFileInfo( f ).fileName() ); } From 2df56829a31151f448acb48cdacbcc96c0a0e35e Mon Sep 17 00:00:00 2001 From: Karmo Rosental Date: Sun, 16 Apr 2017 15:57:06 +0300 Subject: [PATCH 03/65] Fixed #3182. Always using master channel for preset previews. (#3503) * Fixed #3182. Always using master channel for preset previews. --- include/InstrumentTrack.h | 10 +++++++--- src/core/PresetPreviewPlayHandle.cpp | 1 + src/tracks/InstrumentTrack.cpp | 18 +++++++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index cb9abffe3..a6974a1a1 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -166,7 +166,7 @@ public: { return &m_baseNoteModel; } - + int baseNote() const; Piano *pianoModel() @@ -207,6 +207,8 @@ public: return &m_effectChannelModel; } + void setPreviewMode( const bool ); + signals: void instrumentChanged(); @@ -242,15 +244,17 @@ private: bool m_silentBuffersProcessed; + bool m_previewMode; + IntModel m_baseNoteModel; NotePlayHandleList m_processHandles; FloatModel m_volumeModel; FloatModel m_panningModel; - + AudioPort m_audioPort; - + FloatModel m_pitchModel; IntModel m_pitchRangeModel; IntModel m_effectChannelModel; diff --git a/src/core/PresetPreviewPlayHandle.cpp b/src/core/PresetPreviewPlayHandle.cpp index 94b25ae5f..dd57e9f9c 100644 --- a/src/core/PresetPreviewPlayHandle.cpp +++ b/src/core/PresetPreviewPlayHandle.cpp @@ -47,6 +47,7 @@ public: setJournalling( false ); m_previewInstrumentTrack = dynamic_cast( Track::create( Track::InstrumentTrack, this ) ); m_previewInstrumentTrack->setJournalling( false ); + m_previewInstrumentTrack->setPreviewMode( true ); } virtual ~PreviewTrackContainer() diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 1466eb645..80b5427b1 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -95,6 +95,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_notes(), m_sustainPedalPressed( false ), m_silentBuffersProcessed( false ), + m_previewMode( false ), m_baseNoteModel( 0, 0, KeysPerOctave * NumOctaves - 1, this, tr( "Base note" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), @@ -724,7 +725,10 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement m_pitchRangeModel.loadSettings( thisElement, "pitchrange" ); m_pitchModel.loadSettings( thisElement, "pitch" ); m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels()-1 ); - m_effectChannelModel.loadSettings( thisElement, "fxch" ); + if ( !m_previewMode ) + { + m_effectChannelModel.loadSettings( thisElement, "fxch" ); + } m_baseNoteModel.loadSettings( thisElement, "basenote" ); m_useMasterPitchModel.loadSettings( thisElement, "usemasterpitch"); @@ -791,6 +795,14 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement +void InstrumentTrack::setPreviewMode( const bool value ) +{ + m_previewMode = value; +} + + + + Instrument * InstrumentTrack::loadInstrument( const QString & _plugin_name ) { silenceAllNotes( true ); @@ -1774,7 +1786,7 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) idxOfNext = (idxOfNext + d + trackViews.size()) % trackViews.size(); newView = dynamic_cast(trackViews[idxOfNext]); // the window that should be brought to focus is the FIRST InstrumentTrackView that comes after us - if (bringToFront == nullptr && newView != nullptr) + if (bringToFront == nullptr && newView != nullptr) { bringToFront = newView; } @@ -1791,7 +1803,7 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) // save current window pos and then hide the window by unchecking its button in the track list QPoint curPos = parentWidget()->pos(); m_itv->m_tlb->setChecked(false); - + // enable the new window by checking its track list button & moving it to where our window just was newView->m_tlb->setChecked(true); newView->getInstrumentTrackWindow()->parentWidget()->move(curPos); From 2f51062d1e6b144a0ffe5229ba445f262d62ff3f Mon Sep 17 00:00:00 2001 From: Rebecca DeField Date: Thu, 27 Apr 2017 14:34:27 -0700 Subject: [PATCH 04/65] Dual Filter Plugin Redesign (#3484) * Dual Filter * Design Tweaks --- .../DualFilter/DualFilterControlDialog.cpp | 24 +++++++++--------- plugins/DualFilter/artwork.png | Bin 40095 -> 934 bytes 2 files changed, 12 insertions(+), 12 deletions(-) mode change 100644 => 100755 plugins/DualFilter/DualFilterControlDialog.cpp diff --git a/plugins/DualFilter/DualFilterControlDialog.cpp b/plugins/DualFilter/DualFilterControlDialog.cpp old mode 100644 new mode 100755 index 4c53ba36d..3144eddd0 --- a/plugins/DualFilter/DualFilterControlDialog.cpp +++ b/plugins/DualFilter/DualFilterControlDialog.cpp @@ -49,15 +49,15 @@ DualFilterControlDialog::DualFilterControlDialog( DualFilterControls* controls ) QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 150, 220 ); + setFixedSize( 373, 109 ); - makeknob( cut1Knob, 33, 30, m_cut1Model, tr( "FREQ" ), tr( "Cutoff frequency" ), "Hz" ) - makeknob( res1Knob, 75, 30, m_res1Model, tr( "RESO" ), tr( "Resonance" ), "" ) - makeknob( gain1Knob, 117, 30, m_gain1Model, tr( "GAIN" ), tr( "Gain" ), "%" ) - makeknob( mixKnob, 62, 100, m_mixModel, tr( "MIX" ), tr( "Mix" ), "" ) - makeknob( cut2Knob, 33, 145, m_cut2Model, tr( "FREQ" ), tr( "Cutoff frequency" ), "Hz" ) - makeknob( res2Knob, 75, 145, m_res2Model, tr( "RESO" ), tr( "Resonance" ), "" ) - makeknob( gain2Knob, 117, 145, m_gain2Model, tr( "GAIN" ), tr( "Gain" ), "%" ) + makeknob( cut1Knob, 24, 26, m_cut1Model, tr( "FREQ" ), tr( "Cutoff frequency" ), "Hz" ) + makeknob( res1Knob, 74, 26, m_res1Model, tr( "RESO" ), tr( "Resonance" ), "" ) + makeknob( gain1Knob, 124, 26, m_gain1Model, tr( "GAIN" ), tr( "Gain" ), "%" ) + makeknob( mixKnob, 173, 37, m_mixModel, tr( "MIX" ), tr( "Mix" ), "" ) + makeknob( cut2Knob, 222, 26, m_cut2Model, tr( "FREQ" ), tr( "Cutoff frequency" ), "Hz" ) + makeknob( res2Knob, 272, 26, m_res2Model, tr( "RESO" ), tr( "Resonance" ), "" ) + makeknob( gain2Knob, 322, 26, m_gain2Model, tr( "GAIN" ), tr( "Gain" ), "%" ) gain1Knob-> setVolumeKnob( true ); gain2Knob-> setVolumeKnob( true ); @@ -67,20 +67,20 @@ DualFilterControlDialog::DualFilterControlDialog( DualFilterControls* controls ) LedCheckBox * enabled2Toggle = new LedCheckBox( "", this, tr( "Filter 2 enabled" ), LedCheckBox::Green ); - enabled1Toggle -> move( 5, 30 ); + enabled1Toggle -> move( 12, 11 ); enabled1Toggle -> setModel( &controls -> m_enabled1Model ); ToolTip::add( enabled1Toggle, tr( "Click to enable/disable Filter 1" ) ); - enabled2Toggle -> move( 5, 145 ); + enabled2Toggle -> move( 210, 11 ); enabled2Toggle -> setModel( &controls -> m_enabled2Model ); ToolTip::add( enabled2Toggle, tr( "Click to enable/disable Filter 2" ) ); ComboBox * m_filter1ComboBox = new ComboBox( this ); - m_filter1ComboBox->setGeometry( 5, 70, 140, 22 ); + m_filter1ComboBox->setGeometry( 19, 70, 137, 22 ); m_filter1ComboBox->setFont( pointSize<8>( m_filter1ComboBox->font() ) ); m_filter1ComboBox->setModel( &controls->m_filter1Model ); ComboBox * m_filter2ComboBox = new ComboBox( this ); - m_filter2ComboBox->setGeometry( 5, 185, 140, 22 ); + m_filter2ComboBox->setGeometry( 217, 70, 137, 22 ); m_filter2ComboBox->setFont( pointSize<8>( m_filter2ComboBox->font() ) ); m_filter2ComboBox->setModel( &controls->m_filter2Model ); } diff --git a/plugins/DualFilter/artwork.png b/plugins/DualFilter/artwork.png index 88b6a1f11801c138ccd9701714548c2da5fd5f52..00458f1019a0c806d38a094e9d017f55ac624dff 100644 GIT binary patch literal 934 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 Date: Mon, 1 May 2017 12:11:43 +0100 Subject: [PATCH 05/65] EQ plugin now responds to wet / dry control --- plugins/Eq/EqEffect.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/Eq/EqEffect.cpp b/plugins/Eq/EqEffect.cpp index 25ab79e98..03c046a88 100644 --- a/plugins/Eq/EqEffect.cpp +++ b/plugins/Eq/EqEffect.cpp @@ -70,6 +70,10 @@ EqEffect::~EqEffect() bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) { + //wet/dry controls + const float dry = dryLevel(); + const float wet = wetLevel(); + sample_t dryS[2]; // setup sample exact controls float hpRes = m_eqControls.m_hpResModel.value(); float lowShelfRes = m_eqControls.m_lowShelfResModel.value(); @@ -205,6 +209,9 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) for( fpp_t f = 0; f < frames; f++) { + //wet dry buffer + dryS[0] = buf[f][0]; + dryS[1] = buf[f][1]; if( hpActive ) { m_hp12.setParameters( sampleRate, *hpFreqPtr, *hpResPtr, 1 ); @@ -296,6 +303,10 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) } } + //apply wet / dry levels + buf[f][1] = ( dry * dryS[1] ) + ( wet * buf[f][1] ); + buf[f][0] = ( dry * dryS[0] ) + ( wet * buf[f][0] ); + //increment pointers if needed hpResPtr += hpResInc; lowShelfResPtr += lowShelfResInc; From 50ccfba5d690b570729accdaaa5ab0cf020ef8ff Mon Sep 17 00:00:00 2001 From: curlymorphic Date: Mon, 1 May 2017 15:44:58 +0100 Subject: [PATCH 06/65] Correct the Delay syncronisation The Delay plugin had an issue with the delay knob having the incorrect max value, this was resulting in incorrectly scaled times This has been corrected. --- plugins/Delay/DelayControls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Delay/DelayControls.cpp b/plugins/Delay/DelayControls.cpp index 97067607d..3f81eb470 100644 --- a/plugins/Delay/DelayControls.cpp +++ b/plugins/Delay/DelayControls.cpp @@ -32,7 +32,7 @@ DelayControls::DelayControls( DelayEffect* effect ): EffectControls( effect ), m_effect ( effect ), - m_delayTimeModel( 0.5, 0.01, 5.0, 0.0001, 20000.0, this, tr( "Delay Samples" )) , + m_delayTimeModel( 0.5, 0.01, 5.0, 0.0001, 5000.0, this, tr( "Delay Samples" )) , m_feedbackModel(0.0f,0.0f,1.0f,0.01f,this,tr( "Feedback" ) ), m_lfoTimeModel(2.0, 0.01, 5.0, 0.0001, 20000.0, this, tr( "Lfo Frequency" ) ), m_lfoAmountModel(0.0, 0.0, 0.5, 0.0001, 2000.0, this, tr ( "Lfo Amount" ) ), From 082dbed8e9eb7194c96e0f51e79b6e7d84032a03 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Mon, 24 Apr 2017 02:21:32 +0200 Subject: [PATCH 07/65] Revert "'Off beat' beat notes to MelodyPattern" This reverts commit e4474af091e9e94fbe06740e680c3fe9289a2178. --- include/Pattern.h | 3 ++- src/gui/editors/PianoRoll.cpp | 5 ----- src/tracks/Pattern.cpp | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/include/Pattern.h b/include/Pattern.h index 94eef796c..41b76553c 100644 --- a/include/Pattern.h +++ b/include/Pattern.h @@ -88,7 +88,7 @@ public: { return m_patternType; } - void checkType(); + // next/previous track based on position in the containing track Pattern * previousPattern() const; @@ -132,6 +132,7 @@ private: MidiTime beatPatternLength() const; void setType( PatternTypes _new_pattern_type ); + void checkType(); void resizeToFirstTrack(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 4ef28cfab..ced4e58a6 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -2459,11 +2459,6 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) note->setPos( MidiTime( pos_ticks ) ); note->setKey( key_num ); - // If dragging beat notes check if pattern should be MelodyPattern - if( note->length() < 0 ) - { - m_pattern->checkType(); - } } } } diff --git a/src/tracks/Pattern.cpp b/src/tracks/Pattern.cpp index 6757df22b..0e24efc8b 100644 --- a/src/tracks/Pattern.cpp +++ b/src/tracks/Pattern.cpp @@ -361,9 +361,7 @@ void Pattern::checkType() NoteVector::Iterator it = m_notes.begin(); while( it != m_notes.end() ) { - if( ( *it )->length() > 0 || - ( *it )->pos() % ( MidiTime::ticksPerTact() / - MidiTime::stepsPerTact() ) ) + if( ( *it )->length() > 0 ) { setType( MelodyPattern ); return; From d2e50df3ce2f8a56fd1750cc077eff3e76a6867d Mon Sep 17 00:00:00 2001 From: tresf Date: Thu, 4 May 2017 11:10:10 -0400 Subject: [PATCH 08/65] Disable HiDPI on Windows --- cmake/nsis/CMakeLists.txt | 2 ++ cmake/nsis/lmms.exe.manifest | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 cmake/nsis/lmms.exe.manifest diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index 8ddd28eca..ba3147874 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -12,6 +12,7 @@ SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${VERSION}-win32" SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${registerExtension} \\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" \\\${registerExtension} \\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" + WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" " PARENT_SCOPE) SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" @@ -64,3 +65,4 @@ IF(LMMS_HAVE_STK) INSTALL(FILES ${RAWWAVES} DESTINATION "${DATA_DIR}/stk/rawwaves") ENDIF() +INSTALL(FILES "lmms.exe.manifest" DESTINATION .) diff --git a/cmake/nsis/lmms.exe.manifest b/cmake/nsis/lmms.exe.manifest new file mode 100644 index 000000000..1d1a5eb93 --- /dev/null +++ b/cmake/nsis/lmms.exe.manifest @@ -0,0 +1,8 @@ + + + + + false + + + From 7e3ee14cf1a2d3e2975256c1fc2b8fb37d4c1c63 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sat, 6 May 2017 04:27:18 -0400 Subject: [PATCH 09/65] Make factory samples relative (#3510) * Make factory samples relative Fixes #3491 Related #1719 --- src/core/ConfigManager.cpp | 7 +++- src/core/SampleBuffer.cpp | 32 ++++++++++++------- tests/CMakeLists.txt | 1 + tests/src/core/RelativePathsTest.cpp | 48 ++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 tests/src/core/RelativePathsTest.cpp diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index 5cdfe3d97..13c6d9a85 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -63,7 +63,12 @@ ConfigManager::ConfigManager() : // If we're in development (lmms is not installed) let's get the source and // binary directories by reading the CMake Cache - QFile cmakeCache(qApp->applicationDirPath() + "/CMakeCache.txt"); + QDir appPath = qApp->applicationDirPath(); + // If in tests, get parent directory + if (appPath.dirName() == "tests") { + appPath.cdUp(); + } + QFile cmakeCache(appPath.absoluteFilePath("CMakeCache.txt")); if (cmakeCache.exists()) { cmakeCache.open(QFile::ReadOnly); QTextStream stream(&cmakeCache); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index f5ec42cdb..cb930b087 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -1411,25 +1411,35 @@ void SampleBuffer::setReversed( bool _on ) -QString SampleBuffer::tryToMakeRelative( const QString & _file ) +QString SampleBuffer::tryToMakeRelative( const QString & file ) { - if( QFileInfo( _file ).isRelative() == false ) + if( QFileInfo( file ).isRelative() == false ) { - QString f = QString( _file ).replace( QDir::separator(), '/' ); - QString fsd = ConfigManager::inst()->factorySamplesDir(); - QString usd = ConfigManager::inst()->userSamplesDir(); - fsd.replace( QDir::separator(), '/' ); - usd.replace( QDir::separator(), '/' ); - if( f.startsWith( fsd ) ) + QString f = QString( file ).replace( QDir::separator(), '/' ); + + // First, look in factory samples + // Isolate "samples/" from "data:/samples/" + QString samplesSuffix = ConfigManager::inst()->factorySamplesDir().mid( ConfigManager::inst()->dataDir().length() ); + + // Iterate over all valid "data:/" searchPaths + for ( const QString & path : QDir::searchPaths( "data" ) ) { - return QString( f ).mid( fsd.length() ); + QString samplesPath = QString( path + samplesSuffix ).replace( QDir::separator(), '/' ); + if ( f.startsWith( samplesPath ) ) + { + return QString( f ).mid( samplesPath.length() ); + } } - else if( f.startsWith( usd ) ) + + // Next, look in user samples + QString usd = ConfigManager::inst()->userSamplesDir(); + usd.replace( QDir::separator(), '/' ); + if( f.startsWith( usd ) ) { return QString( f ).mid( usd.length() ); } } - return _file; + return file; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2367ea4bb..8cbe23858 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,6 +19,7 @@ ADD_EXECUTABLE(tests $ src/core/ProjectVersionTest.cpp + src/core/RelativePathsTest.cpp src/tracks/AutomationTrackTest.cpp ) diff --git a/tests/src/core/RelativePathsTest.cpp b/tests/src/core/RelativePathsTest.cpp new file mode 100644 index 000000000..555fa39b5 --- /dev/null +++ b/tests/src/core/RelativePathsTest.cpp @@ -0,0 +1,48 @@ +/* + * RelativePathsTest.cpp + * + * Copyright (c) 2017 Tres Finocchiaro + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "QTestSuite.h" + +#include "ConfigManager.h" +#include "SampleBuffer.h" + +#include + +class RelativePathsTest : QTestSuite +{ + Q_OBJECT +private slots: + void RelativePathComparisonTests() + { + QFileInfo fi(ConfigManager::inst()->factorySamplesDir() + "/drums/kick01.ogg"); + QVERIFY(fi.exists()); + + QString absPath = fi.absoluteFilePath(); + QString relPath = "drums/kick01.ogg"; + QCOMPARE(SampleBuffer::tryToMakeRelative(absPath), relPath); + QCOMPARE(SampleBuffer::tryToMakeAbsolute(relPath), absPath); + } +} RelativePathTests; + +#include "RelativePathsTest.moc" From 1848ed20d9802c3615138a3d67a883df8d7c34dc Mon Sep 17 00:00:00 2001 From: curlymorphic Date: Sun, 7 May 2017 08:18:08 +0100 Subject: [PATCH 10/65] LFO controller now has correct frequency with multiple connections. m_bufferLastUpdated is now correctly set to the current frame upon updating the buffer. --- src/core/LfoController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 6bf54a8bc..c1c81119f 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -113,6 +113,7 @@ void LfoController::updateValueBuffer() } m_currentPhase = absFraction( phase - m_phaseOffset ); + m_bufferLastUpdated = s_periods; } void LfoController::updatePhase() From 75077f6200a5aee3a5821aae48a3b8466ed8714a Mon Sep 17 00:00:00 2001 From: Lukas W Date: Thu, 6 Apr 2017 23:10:00 +0200 Subject: [PATCH 11/65] Fix automation processing in BB tracks (#3481) Fixes #3464 --- include/BBTrackContainer.h | 3 +- include/Song.h | 5 +- include/TrackContainer.h | 3 + src/core/BBTrackContainer.cpp | 16 +++- src/core/Song.cpp | 116 ++++++++--------------- src/core/TrackContainer.cpp | 87 ++++++++++++++++- tests/src/tracks/AutomationTrackTest.cpp | 64 +++++++++++-- 7 files changed, 201 insertions(+), 93 deletions(-) diff --git a/include/BBTrackContainer.h b/include/BBTrackContainer.h index 2832de10d..aaff75c38 100644 --- a/include/BBTrackContainer.h +++ b/include/BBTrackContainer.h @@ -48,7 +48,7 @@ public: return "bbtrackcontainer"; } - tact_t lengthOfBB( int _bb ); + tact_t lengthOfBB( int _bb ) const; inline tact_t lengthOfCurrentBB() { return lengthOfBB( currentBB() ); @@ -62,6 +62,7 @@ public: void fixIncorrectPositions(); void createTCOsForBB( int _bb ); + AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum) const override; public slots: void play(); diff --git a/include/Song.h b/include/Song.h index e38a3668a..ad644ed9f 100644 --- a/include/Song.h +++ b/include/Song.h @@ -204,7 +204,8 @@ public: return m_globalAutomationTrack; } - static AutomatedValueMap automatedValuesAt(const Track::tcoVector& tcos, MidiTime time); + //TODO: Add Q_DECL_OVERRIDE when Qt4 is dropped + AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum = -1) const; // file management void createNewProject(); @@ -326,7 +327,7 @@ private: void removeAllControllers(); - void processAutomations(const TrackList& tracks, MidiTime timeStart, fpp_t frames, int tcoNum); + void processAutomations(const TrackList& tracks, MidiTime timeStart, fpp_t frames); AutomationTrack * m_globalAutomationTrack; diff --git a/include/TrackContainer.h b/include/TrackContainer.h index 0b7cd067d..33569c9d9 100644 --- a/include/TrackContainer.h +++ b/include/TrackContainer.h @@ -93,11 +93,14 @@ public: return m_TrackContainerType; } + virtual AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum = -1) const; signals: void trackAdded( Track * _track ); protected: + static AutomatedValueMap automatedValuesFromTracks(const TrackList &tracks, MidiTime timeStart, int tcoNum = -1); + mutable QReadWriteLock m_tracksMutex; private: diff --git a/src/core/BBTrackContainer.cpp b/src/core/BBTrackContainer.cpp index f0164b071..e9af1f621 100644 --- a/src/core/BBTrackContainer.cpp +++ b/src/core/BBTrackContainer.cpp @@ -90,7 +90,7 @@ void BBTrackContainer::updateAfterTrackAdd() -tact_t BBTrackContainer::lengthOfBB( int _bb ) +tact_t BBTrackContainer::lengthOfBB( int _bb ) const { MidiTime max_length = MidiTime::ticksPerTact(); @@ -239,6 +239,20 @@ void BBTrackContainer::createTCOsForBB( int _bb ) } } +AutomatedValueMap BBTrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const +{ + Q_ASSERT(tcoNum >= 0); + Q_ASSERT(time.getTicks() >= 0); + + auto length_tacts = lengthOfBB(tcoNum); + auto length_ticks = length_tacts * MidiTime::ticksPerTact(); + if (time > length_ticks) { + time = length_ticks; + } + + return TrackContainer::automatedValuesAt(time + (MidiTime::ticksPerTact() * tcoNum), tcoNum); +} + diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 568794a54..1af2dc2f3 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -378,7 +378,7 @@ void Song::processNextBuffer() if( ( f_cnt_t ) currentFrame == 0 ) { - processAutomations(trackList, m_playPos[m_playMode], framesToPlay, tcoNum); + processAutomations(trackList, m_playPos[m_playMode], framesToPlay); // loop through all tracks and play them for( int i = 0; i < trackList.size(); ++i ) @@ -401,58 +401,45 @@ void Song::processNextBuffer() } } -void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t frames, int tcoNum) + +void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t) { - QVector tracks; - - if(m_playMode == Mode_PlaySong) - { - tracks << m_globalAutomationTrack; - } - for( Track* track : tracklist) - { - if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack) - { - tracks << dynamic_cast(track); - } - } - std::remove_if(tracks.begin(), tracks.end(), std::mem_fn(&Track::isMuted)); - - Track::tcoVector tcos; AutomatedValueMap values; - if (tcoNum < 0) - { - // Collect all relevant patterns, sorted by start position - MidiTime timeEnd = timeStart + static_cast(frames / Engine::framesPerTick()); - for (AutomationTrack* track: tracks) - { - track->getTCOsInRange(tcos, 0, timeEnd); - } - - values = automatedValuesAt(tcos, timeStart); - } - else - { - if (tracklist.size() != 1) - { - qWarning() << "processAutomations called with specified tcoNum but not exactly one track"; - } - - for (AutomationTrack* track: tracks) - { - TrackContentObject* tco = track->getTCO(tcoNum); - auto p = dynamic_cast(tco); - - for (AutomatableModel* object : p->objects()) - { - values[object] = p->valueAt(timeStart); - } - tcos << tco; - } - } - QSet recordedModels; + + TrackContainer* container = this; + int tcoNum = -1; + + switch (m_playMode) + { + case Mode_PlaySong: + break; + case Mode_PlayBB: + { + Q_ASSERT(tracklist.size() == 1); + Q_ASSERT(tracklist.at(0)->type() == Track::BBTrack); + auto bbTrack = dynamic_cast(tracklist.at(0)); + auto bbContainer = Engine::getBBTrackContainer(); + container = bbContainer; + tcoNum = bbTrack->index(); + } + break; + default: + return; + } + + values = container->automatedValuesAt(timeStart, tcoNum); + TrackList tracks = container->tracks(); + + Track::tcoVector tcos; + for (Track* track : tracks) + { + if (track->type() == Track::AutomationTrack) { + track->getTCOsInRange(tcos, 0, timeStart); + } + } + // Process recording for (TrackContentObject* tco : tcos) { @@ -849,35 +836,10 @@ AutomationPattern * Song::tempoAutomationPattern() return AutomationPattern::globalAutomationPattern( &m_tempoModel ); } -AutomatedValueMap Song::automatedValuesAt(const Track::tcoVector &tcos, MidiTime time) + +AutomatedValueMap Song::automatedValuesAt(MidiTime time, int tcoNum) const { - AutomatedValueMap valueMap; - - for(TrackContentObject* tco : tcos) - { - if (tco->isMuted() || tco->startPosition() > time) { - continue; - } - AutomationPattern* p = dynamic_cast(tco); - if (!p) { - qCritical() << "automatedValuesAt: tco passed is not an automation pattern"; - continue; - } - - if (! p->hasAutomation()) { - continue; - } - - MidiTime relTime = time - p->startPosition(); - float value = p->valueAt(relTime); - - for (AutomatableModel* model : p->objects()) - { - valueMap[model] = value; - } - } - - return valueMap; + return TrackContainer::automatedValuesFromTracks(TrackList(tracks()) << m_globalAutomationTrack, time, tcoNum); } diff --git a/src/core/TrackContainer.cpp b/src/core/TrackContainer.cpp index 87f92ec05..9d8254cd9 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -29,12 +29,16 @@ #include #include +#include "AutomationPattern.h" +#include "AutomationTrack.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" #include "TrackContainer.h" #include "InstrumentTrack.h" -#include "GuiApplication.h" -#include "MainWindow.h" #include "Song.h" +#include "GuiApplication.h" +#include "MainWindow.h" TrackContainer::TrackContainer() : Model( NULL ), @@ -234,6 +238,85 @@ bool TrackContainer::isEmpty() const +AutomatedValueMap TrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const +{ + return automatedValuesFromTracks(tracks(), time, tcoNum); +} + + +AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, MidiTime time, int tcoNum) +{ + Track::tcoVector tcos; + + for (Track* track: tracks) + { + if (track->isMuted()) { + continue; + } + + switch(track->type()) + { + case Track::AutomationTrack: + case Track::HiddenAutomationTrack: + case Track::BBTrack: + if (tcoNum < 0) { + track->getTCOsInRange(tcos, 0, time); + } else { + Q_ASSERT(track->numOfTCOs() > tcoNum); + tcos << track->getTCO(tcoNum); + } + default: + break; + } + } + + AutomatedValueMap valueMap; + + Q_ASSERT(std::is_sorted(tcos.begin(), tcos.end(), TrackContentObject::comparePosition)); + + for(TrackContentObject* tco : tcos) + { + if (tco->isMuted() || tco->startPosition() > time) { + continue; + } + + if (auto* p = dynamic_cast(tco)) + { + if (! p->hasAutomation()) { + continue; + } + MidiTime relTime = time - p->startPosition(); + float value = p->valueAt(relTime); + + for (AutomatableModel* model : p->objects()) + { + valueMap[model] = value; + } + } + else if (auto* bb = dynamic_cast(tco)) + { + auto bbIndex = dynamic_cast(bb->getTrack())->index(); + auto bbContainer = Engine::getBBTrackContainer(); + + MidiTime bbTime = time - tco->startPosition(); + bbTime = std::min(bbTime, tco->length()); + bbTime = bbTime % (bbContainer->lengthOfBB(bbIndex) * MidiTime::ticksPerTact()); + + auto bbValues = bbContainer->automatedValuesAt(bbTime, bbIndex); + for (auto it=bbValues.begin(); it != bbValues.end(); it++) + { + // override old values, bb track with the highest index takes precedence + valueMap[it.key()] = it.value(); + } + } + else + { + continue; + } + } + + return valueMap; +}; diff --git a/tests/src/tracks/AutomationTrackTest.cpp b/tests/src/tracks/AutomationTrackTest.cpp index 63e85d7e0..542815dcc 100644 --- a/tests/src/tracks/AutomationTrackTest.cpp +++ b/tests/src/tracks/AutomationTrackTest.cpp @@ -28,6 +28,8 @@ #include "AutomationPattern.h" #include "AutomationTrack.h" +#include "BBTrack.h" +#include "BBTrackContainer.h" #include "TrackContainer.h" #include "Engine.h" @@ -69,35 +71,77 @@ private slots: QCOMPARE(p.valueAt(150), 1.0f); } - void testTrack() + void testPatterns() { FloatModel model; - AutomationPattern p1(nullptr); + auto song = Engine::getSong(); + AutomationTrack track(song); + + AutomationPattern p1(&track); p1.setProgressionType(AutomationPattern::LinearProgression); p1.putValue(0, 0.0, false); p1.putValue(10, 1.0, false); p1.movePosition(0); p1.addObject(&model); - AutomationPattern p2(nullptr); + AutomationPattern p2(&track); p2.setProgressionType(AutomationPattern::LinearProgression); p2.putValue(0, 0.0, false); p2.putValue(100, 1.0, false); p2.movePosition(100); p2.addObject(&model); - AutomationPattern p3(nullptr); + AutomationPattern p3(&track); p3.addObject(&model); //XXX: Why is this even necessary? p3.clear(); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 0)[&model], 0.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 5)[&model], 0.5f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 10)[&model], 1.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 50)[&model], 1.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 100)[&model], 0.0f); - QCOMPARE(Song::automatedValuesAt({&p1, &p2, &p3}, 150)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt( 5)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt( 10)[&model], 1.0f); + QCOMPARE(song->automatedValuesAt( 50)[&model], 1.0f); + QCOMPARE(song->automatedValuesAt(100)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt(150)[&model], 0.5f); + } + + void testBBTrack() + { + auto song = Engine::getSong(); + auto bbContainer = Engine::getBBTrackContainer(); + BBTrack bbTrack(song); + AutomationTrack automationTrack(bbContainer); + bbTrack.createTCOsForBB(bbTrack.index()); + + QVERIFY(automationTrack.numOfTCOs()); + AutomationPattern* p1 = dynamic_cast(automationTrack.getTCO(0)); + QVERIFY(p1); + + FloatModel model; + + p1->setProgressionType(AutomationPattern::LinearProgression); + p1->putValue(0, 0.0, false); + p1->putValue(10, 1.0, false); + p1->addObject(&model); + + QCOMPARE(bbContainer->automatedValuesAt( 0, bbTrack.index())[&model], 0.0f); + QCOMPARE(bbContainer->automatedValuesAt( 5, bbTrack.index())[&model], 0.5f); + QCOMPARE(bbContainer->automatedValuesAt(10, bbTrack.index())[&model], 1.0f); + QCOMPARE(bbContainer->automatedValuesAt(50, bbTrack.index())[&model], 1.0f); + + BBTrack bbTrack2(song); + bbTrack.createTCOsForBB(bbTrack2.index()); + + QCOMPARE(bbContainer->automatedValuesAt(5, bbTrack.index())[&model], 0.5f); + QVERIFY(! bbContainer->automatedValuesAt(5, bbTrack2.index()).size()); + + BBTCO tco(&bbTrack); + tco.changeLength(MidiTime::ticksPerTact() * 2); + tco.movePosition(0); + + QCOMPARE(song->automatedValuesAt(0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt(5)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt(MidiTime::ticksPerTact() + 5)[&model], 0.5f); } } AutomationTrackTest; From 4196a3041535010e7fa70c554cbd690a32a5ca91 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Fri, 28 Apr 2017 22:16:57 +0200 Subject: [PATCH 12/65] Implement 24 bit support for WAV export (#3021) Add a new value of "24 Bit Float" to the "Depth" combo box in the project export dialog. Add a new enum value to ProjectRenderer::Depth and extend the evaluation of the different enum values in ProjectRenderer. Add the new case of a depth of 24 to AudioFileWave and remove some repetition with regards to SF_FORMAT_WAV in the code. It's only set once now and then the depth is "added" in a switch statement. --- include/ProjectRenderer.h | 1 + src/core/ProjectRenderer.cpp | 26 +++++++++++++++++++++----- src/core/audio/AudioFileWave.cpp | 17 +++++++++++++---- src/gui/dialogs/export_project.ui | 27 +++++++-------------------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index c00a9bb51..06f08a20b 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -44,6 +44,7 @@ public: enum Depths { Depth_16Bit, + Depth_24Bit, Depth_32Bit, NumDepths } ; diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 75a707592..b14634332 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -76,14 +76,30 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & _qs, return; } - bool success_ful = false; + int depth; + switch (_os.depth) + { + case Depth_16Bit: + depth = 16; + break; + case Depth_24Bit: + depth = 24; + break; + case Depth_32Bit: + depth = 32; + break; + default: + // If this line is reached the enum has been extended without taking care here + Q_ASSERT(false); + } + + bool successful = false; m_fileDev = fileEncodeDevices[_file_format].m_getDevInst( - _os.samplerate, DEFAULT_CHANNELS, success_ful, + _os.samplerate, DEFAULT_CHANNELS, successful, _out_file, _os.vbr, _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, - _os.depth == Depth_32Bit ? 32 : 16, - Engine::mixer() ); - if( success_ful == false ) + depth, Engine::mixer() ); + if( !successful ) { delete m_fileDev; m_fileDev = NULL; diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index 65b87db79..a52ea5a96 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -64,11 +64,20 @@ bool AudioFileWave::startEncoding() m_si.sections = 1; m_si.seekable = 0; + m_si.format = SF_FORMAT_WAV; + switch( depth() ) { - case 32: m_si.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; break; - case 16: - default: m_si.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; break; + case 32: + m_si.format |= SF_FORMAT_FLOAT; + break; + case 24: + m_si.format |= SF_FORMAT_PCM_24; + break; + case 16: + default: + m_si.format |= SF_FORMAT_PCM_16; + break; } m_sf = sf_open( #ifdef LMMS_BUILD_WIN32 @@ -88,7 +97,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, const fpp_t _frames, const float _master_gain ) { - if( depth() == 32 ) + if( depth() == 32 || depth() == 24 ) { float * buf = new float[_frames*channels()]; for( fpp_t frame = 0; frame < _frames; ++frame ) diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index f6d695c23..4384993af 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -86,16 +86,7 @@ 6 - - 0 - - - 0 - - - 0 - - + 0 @@ -148,16 +139,7 @@ - - 0 - - - 0 - - - 0 - - + 0 @@ -174,6 +156,11 @@ 16 Bit Integer + + + 24 Bit Float + + 32 Bit Float From d5fc38ebd9c45000a745aa0b2dd8d5d25bf1a2fb Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 29 Apr 2017 15:00:54 +0200 Subject: [PATCH 13/65] Simplification of code and structure in the area of the file export Pull the class OutputSettings out of the class ProjectRenderer so that it can be used in other contexts as well. Also move the enum ProjectRenderer::Depth into the new class OutputSettings and rename it to BitDepth. Adjust all places that referenced ProjectRenderer::OutputSettings accordingly. Adjust the two places where an instance of OutputSettings is created: the main function and ExportProjectDialog::startExport. Store an instance of OutputSettings in AudioFileDevice and remove several members and methods which are now replaced by this instance. Add a getter for the OutputSettings to AudioFileDevice. Storing an instance of OutputSettings in the base class AudioFileDevice enables the simplification of the following constructors and general code in the following classes: * AudioFileDevice * AudioFileOgg * AudioFileWave Because OutputSettings contains everything related to sample rate, bit rate settings and bit depth these parameters could be removed from the parameter list of the aforementioned constructors. Simplify the signature of the factory method AudioFileDeviceInstantiaton (defined in AudioFileDevice.h) and reorder the parameters by significance. Move the logic of how the minimum and maximum bitrate is calculated using the nominal bitrate into AudioFileOgg::minBitrate() and AudioFileOgg::maxBitrate(). Previously this was defined in the constructor of ProjectRenderer where it does not belong as it an implementation detail of the OGG export. Remove the code that converted the bit depth enum to an integer from ProjectRenderer as it is now solely represented as an enum. Remove class members for the minimum and maximum bit rate from AudioFileOgg and adjust the code in the implementation to use the values stored in OutputSettings. --- include/AudioFileDevice.h | 64 +++++------------------ include/AudioFileOgg.h | 54 ++++++++++--------- include/AudioFileWave.h | 39 +++++--------- include/OutputSettings.h | 84 ++++++++++++++++++++++++++++++ include/ProjectRenderer.h | 26 +-------- include/RenderManager.h | 6 ++- src/core/ProjectRenderer.cpp | 51 ++++++------------ src/core/RenderManager.cpp | 3 +- src/core/audio/AudioFileDevice.cpp | 15 ++---- src/core/audio/AudioFileOgg.cpp | 53 ++++++++----------- src/core/audio/AudioFileWave.cpp | 31 +++++------ src/core/main.cpp | 12 +++-- src/gui/ExportProjectDialog.cpp | 11 ++-- 13 files changed, 213 insertions(+), 236 deletions(-) create mode 100644 include/OutputSettings.h diff --git a/include/AudioFileDevice.h b/include/AudioFileDevice.h index 70ea48843..7b6b81eab 100644 --- a/include/AudioFileDevice.h +++ b/include/AudioFileDevice.h @@ -29,19 +29,15 @@ #include #include "AudioDevice.h" +#include "OutputSettings.h" class AudioFileDevice : public AudioDevice { public: - AudioFileDevice( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* mixer ); + AudioFileDevice(OutputSettings const & outputSettings, + const ch_cnt_t _channels, const QString & _file, + Mixer* mixer ); virtual ~AudioFileDevice(); QString outputFile() const @@ -49,35 +45,12 @@ public: return m_outputFile.fileName(); } + OutputSettings const & getOutputSettings() const { return m_outputSettings; } + protected: int writeData( const void* data, int len ); - inline bool useVBR() const - { - return m_useVbr; - } - - inline bitrate_t nominalBitrate() const - { - return m_nomBitrate; - } - - inline bitrate_t minBitrate() const - { - return m_minBitrate; - } - - inline bitrate_t maxBitrate() const - { - return m_maxBitrate; - } - - inline int depth() const - { - return m_depth; - } - inline bool outputFileOpened() const { return m_outputFile.isOpen(); @@ -86,29 +59,16 @@ protected: private: QFile m_outputFile; - - bool m_useVbr; - - bitrate_t m_nomBitrate; - bitrate_t m_minBitrate; - bitrate_t m_maxBitrate; - - int m_depth; - + OutputSettings m_outputSettings; } ; typedef AudioFileDevice * ( * AudioFileDeviceInstantiaton ) - ( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, - bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* mixer ); + ( const QString & outputFilename, + OutputSettings const & outputSettings, + const ch_cnt_t channels, + Mixer* mixer, + bool & successful ); #endif diff --git a/include/AudioFileOgg.h b/include/AudioFileOgg.h index 50728fd96..656a7174e 100644 --- a/include/AudioFileOgg.h +++ b/include/AudioFileOgg.h @@ -38,33 +38,21 @@ class AudioFileOgg : public AudioFileDevice { public: - AudioFileOgg( const sample_rate_t _sample_rate, + AudioFileOgg( OutputSettings const & outputSettings, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, Mixer* mixer ); virtual ~AudioFileOgg(); - static AudioFileDevice * getInst( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, - bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* mixer ) + static AudioFileDevice * getInst( const QString & outputFilename, + OutputSettings const & outputSettings, + const ch_cnt_t channels, + Mixer* mixer, + bool & successful ) { - return new AudioFileOgg( _sample_rate, _channels, _success_ful, - _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, - _depth, mixer ); + return new AudioFileOgg( outputSettings, channels, successful, + outputFilename, mixer ); } @@ -77,15 +65,33 @@ private: void finishEncoding(); inline int writePage(); + inline bitrate_t nominalBitrate() const + { + return getOutputSettings().getBitRateSettings().getBitRate(); + } + inline bitrate_t minBitrate() const + { + if (nominalBitrate() > 64) + { + return nominalBitrate() - 64; + } + else + { + return 64; + } + } + + inline bitrate_t maxBitrate() const + { + return nominalBitrate() + 64; + } + +private: bool m_ok; ch_cnt_t m_channels; sample_rate_t m_rate; - // Various bitrate/quality options - bitrate_t m_minBitrate; - bitrate_t m_maxBitrate; - uint32_t m_serialNo; vorbis_comment * m_comments; diff --git a/include/AudioFileWave.h b/include/AudioFileWave.h index 88dfa89b0..4d2778bad 100644 --- a/include/AudioFileWave.h +++ b/include/AudioFileWave.h @@ -35,34 +35,21 @@ class AudioFileWave : public AudioFileDevice { public: - AudioFileWave( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, - bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, + AudioFileWave( OutputSettings const & outputSettings, + const ch_cnt_t channels, + bool & successful, + const QString & file, Mixer* mixer ); virtual ~AudioFileWave(); - static AudioFileDevice * getInst( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, - bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* mixer ) + static AudioFileDevice * getInst( const QString & outputFilename, + OutputSettings const & outputSettings, + const ch_cnt_t channels, + Mixer* mixer, + bool & successful ) { - return new AudioFileWave( _sample_rate, _channels, - _success_ful, _file, _use_vbr, - _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - mixer ); + return new AudioFileWave( outputSettings, channels, successful, + outputFilename, mixer ); } @@ -74,11 +61,9 @@ private: bool startEncoding(); void finishEncoding(); - +private: SF_INFO m_si; SNDFILE * m_sf; - } ; - #endif diff --git a/include/OutputSettings.h b/include/OutputSettings.h new file mode 100644 index 000000000..1aecb7df0 --- /dev/null +++ b/include/OutputSettings.h @@ -0,0 +1,84 @@ +/* + * OutputSettings.h - Stores the settings for file rendering + * + * Copyright (c) 2008-2009 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef OUTPUT_SETTINGS_H +#define OUTPUT_SETTINGS_H + + +class OutputSettings +{ +public: + enum BitDepth + { + Depth_16Bit, + Depth_24Bit, + Depth_32Bit, + NumDepths + }; + + class BitRateSettings + { + public: + BitRateSettings(bitrate_t bitRate, bool isVariableBitRate) : + m_bitRate(bitRate), + m_isVariableBitRate(isVariableBitRate) + {} + + bool isVariableBitRate() const { return m_isVariableBitRate; } + void setVariableBitrate(bool variableBitRate = true) { m_isVariableBitRate = variableBitRate; } + + bitrate_t getBitRate() const { return m_bitRate; } + void setBitRate(bitrate_t bitRate) { m_bitRate = bitRate; } + + private: + bitrate_t m_bitRate; + bool m_isVariableBitRate; + }; + +public: + OutputSettings( sample_rate_t sampleRate, + BitRateSettings const & bitRateSettings, + BitDepth bitDepth ) : + m_sampleRate(sampleRate), + m_bitRateSettings(bitRateSettings), + m_bitDepth(bitDepth) + { + } + + sample_rate_t getSampleRate() const { return m_sampleRate; } + void setSampleRate(sample_rate_t sampleRate) { m_sampleRate = sampleRate; } + + BitRateSettings const & getBitRateSettings() const { return m_bitRateSettings; } + void setBitRateSettings(BitRateSettings const & bitRateSettings) { m_bitRateSettings = bitRateSettings; } + + BitDepth getBitDepth() const { return m_bitDepth; } + void setBitDepth(BitDepth bitDepth) { m_bitDepth = bitDepth; } + +private: + sample_rate_t m_sampleRate; + BitRateSettings m_bitRateSettings; + BitDepth m_bitDepth; +}; + +#endif diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 06f08a20b..20ccbc0de 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -28,6 +28,7 @@ #include "AudioFileDevice.h" #include "lmmsconfig.h" #include "Mixer.h" +#include "OutputSettings.h" class ProjectRenderer : public QThread @@ -41,31 +42,6 @@ public: NumFileFormats } ; - enum Depths - { - Depth_16Bit, - Depth_24Bit, - Depth_32Bit, - NumDepths - } ; - - struct OutputSettings - { - sample_rate_t samplerate; - bool vbr; - int bitrate; - Depths depth; - - OutputSettings( sample_rate_t _sr, bool _vbr, int _bitrate, - Depths _d ) : - samplerate( _sr ), - vbr( _vbr ), - bitrate( _bitrate ), - depth( _d ) - { - } - } ; - struct FileEncodeDevice { ExportFileFormats m_fileFormat; diff --git a/include/RenderManager.h b/include/RenderManager.h index a22a047f5..e7e119b87 100644 --- a/include/RenderManager.h +++ b/include/RenderManager.h @@ -29,6 +29,8 @@ #include #include "ProjectRenderer.h" +#include "OutputSettings.h" + class RenderManager : public QObject { @@ -36,7 +38,7 @@ class RenderManager : public QObject public: RenderManager( const Mixer::qualitySettings & qualitySettings, - const ProjectRenderer::OutputSettings & outputSettings, + const OutputSettings & outputSettings, ProjectRenderer::ExportFileFormats fmt, QString outputPath); @@ -63,7 +65,7 @@ private: void restoreMutedState(); const Mixer::qualitySettings m_qualitySettings; - const ProjectRenderer::OutputSettings m_outputSettings; + const OutputSettings m_outputSettings; ProjectRenderer::ExportFileFormats m_format; QString m_outputPath; diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index b14634332..5dee4ed16 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -60,51 +60,32 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] = -ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & _qs, - const OutputSettings & _os, - ExportFileFormats _file_format, - const QString & _out_file ) : +ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings, + const OutputSettings & outputSettings, + ExportFileFormats exportFileFormat, + const QString & outputFilename ) : QThread( Engine::mixer() ), m_fileDev( NULL ), - m_qualitySettings( _qs ), + m_qualitySettings( qualitySettings ), m_oldQualitySettings( Engine::mixer()->currentQualitySettings() ), m_progress( 0 ), m_abort( false ) { - if( fileEncodeDevices[_file_format].m_getDevInst == NULL ) - { - return; - } + AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[exportFileFormat].m_getDevInst; - int depth; - switch (_os.depth) + if (audioEncoderFactory) { - case Depth_16Bit: - depth = 16; - break; - case Depth_24Bit: - depth = 24; - break; - case Depth_32Bit: - depth = 32; - break; - default: - // If this line is reached the enum has been extended without taking care here - Q_ASSERT(false); - } + bool successful = false; - bool successful = false; - m_fileDev = fileEncodeDevices[_file_format].m_getDevInst( - _os.samplerate, DEFAULT_CHANNELS, successful, - _out_file, _os.vbr, - _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, - depth, Engine::mixer() ); - if( !successful ) - { - delete m_fileDev; - m_fileDev = NULL; + m_fileDev = audioEncoderFactory( + outputFilename, outputSettings, DEFAULT_CHANNELS, + Engine::mixer(), successful ); + if( !successful ) + { + delete m_fileDev; + m_fileDev = NULL; + } } - } diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index f404b6e3d..5fcc6a583 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -30,9 +30,10 @@ #include "BBTrackContainer.h" #include "BBTrack.h" + RenderManager::RenderManager( const Mixer::qualitySettings & qualitySettings, - const ProjectRenderer::OutputSettings & outputSettings, + const OutputSettings & outputSettings, ProjectRenderer::ExportFileFormats fmt, QString outputPath) : m_qualitySettings(qualitySettings), diff --git a/src/core/audio/AudioFileDevice.cpp b/src/core/audio/AudioFileDevice.cpp index ac3d94c33..2c340861c 100644 --- a/src/core/audio/AudioFileDevice.cpp +++ b/src/core/audio/AudioFileDevice.cpp @@ -30,24 +30,15 @@ #include "GuiApplication.h" -AudioFileDevice::AudioFileDevice( const sample_rate_t _sample_rate, +AudioFileDevice::AudioFileDevice( OutputSettings const & outputSettings, const ch_cnt_t _channels, const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, Mixer* _mixer ) : AudioDevice( _channels, _mixer ), m_outputFile( _file ), - m_useVbr( _use_vbr ), - m_nomBitrate( _nom_bitrate ), - m_minBitrate( _min_bitrate ), - m_maxBitrate( _max_bitrate ), - m_depth( _depth ) + m_outputSettings(outputSettings) { - setSampleRate( _sample_rate ); + setSampleRate( outputSettings.getSampleRate() ); if( m_outputFile.open( QFile::WriteOnly | QFile::Truncate ) == false ) { diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index 39771f3d3..526776a73 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -36,21 +36,14 @@ #include "Mixer.h" -AudioFileOgg::AudioFileOgg( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, - bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* _mixer ) : - AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, - _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) +AudioFileOgg::AudioFileOgg( OutputSettings const & outputSettings, + const ch_cnt_t channels, + bool & successful, + const QString & file, + Mixer* mixer ) : + AudioFileDevice( outputSettings, channels, file, mixer ) { - m_ok = _success_ful = outputFileOpened() && startEncoding(); + m_ok = successful = outputFileOpened() && startEncoding(); } @@ -89,18 +82,18 @@ bool AudioFileOgg::startEncoding() vc.vendor = NULL; m_channels = channels(); - // vbr enabled? - if( useVBR() == 0 ) + + bool useVariableBitRate = getOutputSettings().getBitRateSettings().isVariableBitRate(); + bitrate_t minimalBitrate = nominalBitrate(); + bitrate_t maximumBitrate = nominalBitrate(); + + if( useVariableBitRate ) { - m_minBitrate = nominalBitrate(); // min for vbr - m_maxBitrate = nominalBitrate(); // max for vbr - } - else - { - m_minBitrate = minBitrate(); // min for vbr - m_maxBitrate = maxBitrate(); // max for vbr + minimalBitrate = minBitrate(); // min for vbr + maximumBitrate = maxBitrate(); // max for vbr } + m_rate = sampleRate(); // default-samplerate if( m_rate > 48000 ) { @@ -114,9 +107,9 @@ bool AudioFileOgg::startEncoding() vorbis_info_init( &m_vi ); if( vorbis_encode_setup_managed( &m_vi, m_channels, m_rate, - ( m_maxBitrate > 0 )? m_maxBitrate * 1000 : -1, + ( maximumBitrate > 0 )? maximumBitrate * 1000 : -1, nominalBitrate() * 1000, - ( m_minBitrate > 0 )? m_minBitrate * 1000 : -1 ) ) + ( minimalBitrate > 0 )? minimalBitrate * 1000 : -1 ) ) { printf( "Mode initialization failed: invalid parameters for " "bitrate\n" ); @@ -125,15 +118,15 @@ bool AudioFileOgg::startEncoding() return false; } - if( useVBR() == false ) - { - vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, NULL ); - } - else if( useVBR() == true ) + if( useVariableBitRate ) { // Turn off management entirely (if it was turned on). vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, NULL ); } + else + { + vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, NULL ); + } vorbis_encode_setup_init( &m_vi ); diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index a52ea5a96..fb6e2901f 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -28,21 +28,14 @@ #include "Mixer.h" -AudioFileWave::AudioFileWave( const sample_rate_t _sample_rate, - const ch_cnt_t _channels, bool & _success_ful, - const QString & _file, - const bool _use_vbr, - const bitrate_t _nom_bitrate, - const bitrate_t _min_bitrate, - const bitrate_t _max_bitrate, - const int _depth, - Mixer* _mixer ) : - AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, - _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ), +AudioFileWave::AudioFileWave( OutputSettings const & outputSettings, + const ch_cnt_t channels, bool & successful, + const QString & file, + Mixer* mixer ) : + AudioFileDevice( outputSettings, channels, file, mixer ), m_sf( NULL ) { - _success_ful = outputFileOpened() && startEncoding(); + successful = outputFileOpened() && startEncoding(); } @@ -66,15 +59,15 @@ bool AudioFileWave::startEncoding() m_si.format = SF_FORMAT_WAV; - switch( depth() ) + switch( getOutputSettings().getBitDepth() ) { - case 32: + case OutputSettings::Depth_32Bit: m_si.format |= SF_FORMAT_FLOAT; break; - case 24: + case OutputSettings::Depth_24Bit: m_si.format |= SF_FORMAT_PCM_24; break; - case 16: + case OutputSettings::Depth_16Bit: default: m_si.format |= SF_FORMAT_PCM_16; break; @@ -97,7 +90,9 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, const fpp_t _frames, const float _master_gain ) { - if( depth() == 32 || depth() == 24 ) + OutputSettings::BitDepth bitDepth = getOutputSettings().getBitDepth(); + + if( bitDepth == OutputSettings::Depth_32Bit || bitDepth == OutputSettings::Depth_24Bit ) { float * buf = new float[_frames*channels()]; for( fpp_t frame = 0; frame < _frames; ++frame ) diff --git a/src/core/main.cpp b/src/core/main.cpp index b2f962534..d288a0f2e 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -65,6 +65,7 @@ #include "GuiApplication.h" #include "ImportFilter.h" #include "MainWindow.h" +#include "OutputSettings.h" #include "ProjectRenderer.h" #include "RenderManager.h" #include "Song.h" @@ -259,8 +260,7 @@ int main( int argc, char * * argv ) new MainApplication( argc, argv ); Mixer::qualitySettings qs( Mixer::qualitySettings::Mode_HighQuality ); - ProjectRenderer::OutputSettings os( 44100, false, 160, - ProjectRenderer::Depth_16Bit ); + OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::Depth_16Bit ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; // second of two command-line parsing stages @@ -414,7 +414,7 @@ int main( int argc, char * * argv ) sample_rate_t sr = QString( argv[i] ).toUInt(); if( sr >= 44100 && sr <= 192000 ) { - os.samplerate = sr; + os.setSampleRate(sr); } else { @@ -439,7 +439,9 @@ int main( int argc, char * * argv ) if( br >= 64 && br <= 384 ) { - os.bitrate = br; + OutputSettings::BitRateSettings bitRateSettings = os.getBitRateSettings(); + bitRateSettings.setBitRate(br); + os.setBitRateSettings(bitRateSettings); } else { @@ -450,7 +452,7 @@ int main( int argc, char * * argv ) } else if( arg =="--float" || arg == "-a" ) { - os.depth = ProjectRenderer::Depth_32Bit; + os.setBitDepth(OutputSettings::Depth_32Bit); } else if( arg == "--interpolation" || arg == "-i" ) { diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index d993841fc..3f8c36abb 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -31,6 +31,7 @@ #include "Song.h" #include "GuiApplication.h" #include "MainWindow.h" +#include "OutputSettings.h" ExportProjectDialog::ExportProjectDialog( const QString & _file_name, @@ -137,13 +138,13 @@ void ExportProjectDialog::startExport() static_cast(oversamplingCB->currentIndex()) ); const int samplerates[5] = { 44100, 48000, 88200, 96000, 192000 }; - const int bitrates[6] = { 64, 128, 160, 192, 256, 320 }; + const bitrate_t bitrates[6] = { 64, 128, 160, 192, 256, 320 }; - ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( + OutputSettings::BitRateSettings bitRateSettings(bitrates[ bitrateCB->currentIndex() ], false); + OutputSettings os = OutputSettings( samplerates[ samplerateCB->currentIndex() ], - false, - bitrates[ bitrateCB->currentIndex() ], - static_cast( depthCB->currentIndex() ) ); + bitRateSettings, + static_cast( depthCB->currentIndex() ) ); m_renderManager = new RenderManager( qs, os, m_ft, m_fileName ); From 4ac6aa909d5e804a8101e340fd5d1d88d2faa6ca Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 29 Apr 2017 15:50:31 +0200 Subject: [PATCH 14/65] Only show relevant widgets on the export dialog Only show widgets on the export dialog that are relevant to the selected file format (Wave/Ogg): * Sample rate is always shown. * Bit depth settings are only shown when Wave is selected. * Bit rate settings are only shown when Ogg is selected. Remove the label that informs the user that not all settings apply to all export formats as it is not needed anymore. The english text of that label was: "Please note that not all of the parameters above apply for all file formats." --- include/ExportProjectDialog.h | 2 + src/gui/ExportProjectDialog.cpp | 28 +++++++ src/gui/dialogs/export_project.ui | 131 +++++++++++++++--------------- 3 files changed, 95 insertions(+), 66 deletions(-) diff --git a/include/ExportProjectDialog.h b/include/ExportProjectDialog.h index 5ac70bb8f..d7159780f 100644 --- a/include/ExportProjectDialog.h +++ b/include/ExportProjectDialog.h @@ -53,6 +53,8 @@ private slots: void accept(); void startExport(); + void onFileFormatChanged(int); + private: QString m_fileName; QString m_dirName; diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index 3f8c36abb..8c55e382f 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -171,7 +171,35 @@ void ExportProjectDialog::startExport() } +ProjectRenderer::ExportFileFormats convertIndexToExportFileFormat(int index) +{ + switch (index) + { + case 0: + return ProjectRenderer::WaveFile; + case 1: + return ProjectRenderer::OggFile; + default: + Q_ASSERT(false); + break; + } + return ProjectRenderer::NumFileFormats; +} + + +void ExportProjectDialog::onFileFormatChanged(int index) +{ + ProjectRenderer::ExportFileFormats exportFormat = + convertIndexToExportFileFormat(index); + + bool bitRateControlsEnabled = exportFormat == ProjectRenderer::OggFile; + bool bitDepthControlEnabled = exportFormat == ProjectRenderer::WaveFile; + + bitrateWidget->setVisible(bitRateControlsEnabled); + + depthWidget->setVisible(bitDepthControlEnabled); +} void ExportProjectDialog::startBtnClicked() { diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index 4384993af..2225a6495 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -6,8 +6,8 @@ 0 0 - 519 - 412 + 715 + 447 @@ -80,6 +80,47 @@ + + + + + 0 + 20 + + + + + 0 + + + + + Depth: + + + + + + + + 16 Bit Integer + + + + + 24 Bit Float + + + + + 32 Bit Float + + + + + + + @@ -90,7 +131,7 @@ 0 - + Bitrate: @@ -136,67 +177,6 @@ - - - - - 0 - - - - - Depth: - - - - - - - - 16 Bit Integer - - - - - 24 Bit Float - - - - - 32 Bit Float - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 1 - 10 - - - - - - - - Please note that not all of the parameters above apply for all file formats. - - - true - - - @@ -368,8 +348,8 @@ reject() - 357 - 293 + 511 + 372 202 @@ -377,5 +357,24 @@ + + fileFormatCB + currentIndexChanged(int) + ExportProjectDialog + onFileFormatChanged(int) + + + 111 + 85 + + + 518 + 212 + + + + + onFileFormatChanged(int) + From 823788919a1d3d411162eba425c6a6df1d03055e Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 29 Apr 2017 16:14:33 +0200 Subject: [PATCH 15/65] Add the option to enable variable bit rates to the export dialog If the variables bit rate is not enabled the nominal bit rate will be used for the minimum and maximum bit rate in the encoder. If the variable bit rate is enabled the current implementation will compute the minimum bitrate by subtracting 64 kBit/s from the nominal bit rate. The maximum bit rate is computed by adding 64 kBit/s to it. Example: The nominal bit rate is set to 160 kBit/s and variable bit rate is enabled in the export dialog. The minimum bit rate is then set to 96 kBit/s and the maximum bit rate to 224 kBit/s. --- src/gui/ExportProjectDialog.cpp | 4 +++- src/gui/dialogs/export_project.ui | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index 8c55e382f..c4f7696b8 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -140,7 +140,9 @@ void ExportProjectDialog::startExport() const int samplerates[5] = { 44100, 48000, 88200, 96000, 192000 }; const bitrate_t bitrates[6] = { 64, 128, 160, 192, 256, 320 }; - OutputSettings::BitRateSettings bitRateSettings(bitrates[ bitrateCB->currentIndex() ], false); + bool useVariableBitRate = checkBoxVariableBitRate->isChecked(); + + OutputSettings::BitRateSettings bitRateSettings(bitrates[ bitrateCB->currentIndex() ], useVariableBitRate); OutputSettings os = OutputSettings( samplerates[ samplerateCB->currentIndex() ], bitRateSettings, diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index 2225a6495..c3f77df94 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -174,6 +174,13 @@ + + + + Use variable bitrate + + + From f06eac3f91bd77f51f5be1b560817ef8ab8fb84e Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sun, 30 Apr 2017 11:19:57 +0200 Subject: [PATCH 16/65] Add copyright info to the file OutputSettings.h --- include/OutputSettings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/OutputSettings.h b/include/OutputSettings.h index 1aecb7df0..e4dccfd3a 100644 --- a/include/OutputSettings.h +++ b/include/OutputSettings.h @@ -2,6 +2,7 @@ * OutputSettings.h - Stores the settings for file rendering * * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2017 Michael Gregorius * * This file is part of LMMS - https://lmms.io * From 4b3309a4e991d01542e980cd07237ae2106e01e3 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sun, 30 Apr 2017 11:48:35 +0200 Subject: [PATCH 17/65] Make the 24 bit export the default selection in the export dialog --- src/gui/dialogs/export_project.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index c3f77df94..18d7edbb0 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -101,6 +101,9 @@ + + 1 + 16 Bit Integer From dfeb6bea3a415ffb9757ff4b7018cd7714bf0ee6 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Tue, 2 May 2017 18:11:29 +0200 Subject: [PATCH 18/65] Rename entry for bit depth from "24 Bit Float" to "24 Bit Integer" --- src/gui/dialogs/export_project.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index 18d7edbb0..2cd466ee3 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -111,7 +111,7 @@ - 24 Bit Float + 24 Bit Integer From 290556e43d200952e85a9f54607af9501621d484 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Sat, 10 Sep 2016 16:01:55 +0200 Subject: [PATCH 19/65] Remove Limited Session --- include/MainWindow.h | 3 +-- src/core/main.cpp | 31 +++---------------------------- src/gui/MainWindow.cpp | 11 +++-------- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 1f839b371..603dd1d29 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -110,8 +110,7 @@ public: enum SessionState { Normal, - Recover, - Limited, + Recover }; void setSession( SessionState session ) diff --git a/src/core/main.cpp b/src/core/main.cpp index d288a0f2e..5dbea3099 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -739,10 +739,6 @@ int main( int argc, char * * argv ) " %4" " %5" " " - " " - " %6" - " %7" - " " "" "" ).arg( MainWindow::tr( "There is a recovery file present. " @@ -753,10 +749,6 @@ int main( int argc, char * * argv ) MainWindow::tr( "Recover" ), MainWindow::tr( "Recover the file. Please don't run " "multiple instances of LMMS when you do this." ), - MainWindow::tr( "Ignore" ), - MainWindow::tr( "Launch LMMS as usual but with " - "automatic backup disabled to prevent the " - "present recover file from being overwritten." ), MainWindow::tr( "Discard" ), MainWindow::tr( "Launch a default session and delete " "the restored files. This is not reversible." ) @@ -768,7 +760,6 @@ int main( int argc, char * * argv ) QPushButton * recover; QPushButton * discard; - QPushButton * ignore; QPushButton * exit; #if QT_VERSION >= 0x050000 @@ -776,8 +767,6 @@ int main( int argc, char * * argv ) // to have a custom layout discard = mb.addButton( MainWindow::tr( "Discard" ), QMessageBox::AcceptRole ); - ignore = mb.addButton( MainWindow::tr( "Ignore" ), - QMessageBox::AcceptRole ); recover = mb.addButton( MainWindow::tr( "Recover" ), QMessageBox::AcceptRole ); @@ -785,8 +774,6 @@ int main( int argc, char * * argv ) // in qt4 the button order is reversed recover = mb.addButton( MainWindow::tr( "Recover" ), QMessageBox::AcceptRole ); - ignore = mb.addButton( MainWindow::tr( "Ignore" ), - QMessageBox::AcceptRole ); discard = mb.addButton( MainWindow::tr( "Discard" ), QMessageBox::AcceptRole ); @@ -799,7 +786,6 @@ int main( int argc, char * * argv ) // set icons recover->setIcon( embed::getIconPixmap( "recover" ) ); discard->setIcon( embed::getIconPixmap( "discard" ) ); - ignore->setIcon( embed::getIconPixmap( "ignore" ) ); mb.setDefaultButton( recover ); mb.setEscapeButton( exit ); @@ -814,13 +800,6 @@ int main( int argc, char * * argv ) fileToLoad = recoveryFile; gui->mainWindow()->setSession( MainWindow::SessionState::Recover ); } - else if( mb.clickedButton() == ignore ) - { - if( autoSaveEnabled ) - { - gui->mainWindow()->setSession( MainWindow::SessionState::Limited ); - } - } else // Exit { return 0; @@ -863,14 +842,11 @@ int main( int argc, char * * argv ) } } // If enabled, open last project if there is one. Else, create - // a new one. Also skip recently opened file if limited session to - // lower the chance of opening an already opened file. + // a new one. else if( ConfigManager::inst()-> value( "app", "openlastproject" ).toInt() && !ConfigManager::inst()-> - recentlyOpenedProjects().isEmpty() && - gui->mainWindow()->getSession() != - MainWindow::SessionState::Limited ) + recentlyOpenedProjects().isEmpty() ) { QString f = ConfigManager::inst()-> recentlyOpenedProjects().first(); @@ -893,8 +869,7 @@ int main( int argc, char * * argv ) // Finally we start the auto save timer and also trigger the // autosave one time as recover.mmp is a signal to possible other // instances of LMMS. - if( autoSaveEnabled && - gui->mainWindow()->getSession() != MainWindow::SessionState::Limited ) + if( autoSaveEnabled ) { gui->mainWindow()->autoSaveTimerReset(); gui->mainWindow()->autoSave(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 732ce2db8..09524238e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -664,10 +664,6 @@ void MainWindow::resetWindowTitle() { title += " - " + tr( "Recover session. Please save your work!" ); } - if( getSession() == Limited ) - { - title += " - " + tr( "Automatic backup disabled. Remember to save your work!" ); - } setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) ); } @@ -1374,8 +1370,8 @@ void MainWindow::closeEvent( QCloseEvent * _ce ) if( mayChangeProject(true) ) { // delete recovery file - if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() - && getSession() != Limited ) + if( ConfigManager::inst()-> + value( "ui", "enableautosave" ).toInt() ) { sessionCleanup(); _ce->accept(); @@ -1562,8 +1558,7 @@ void MainWindow::autoSave() // from the timer where we need to do extra tests. void MainWindow::runAutoSave() { - if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() && - getSession() != Limited ) + if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() ) { autoSave(); autoSaveTimerReset(); // Reset timer From 17b37e02a986c05b5a1af16f2852061b088a68eb Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sat, 13 May 2017 23:15:10 -0400 Subject: [PATCH 20/65] Provide support for fallback config values (#3551) Provide support for fallback config values Makes autosave and some other values checked by default. Supersedes #3541 --- include/ConfigManager.h | 3 +++ src/core/ConfigManager.cpp | 10 ++++++++++ src/gui/SetupDialog.cpp | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/ConfigManager.h b/include/ConfigManager.h index c5ef45b34..4c0a73e05 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -217,6 +217,9 @@ public: const QString & value( const QString & cls, const QString & attribute ) const; + const QString & value( const QString & cls, + const QString & attribute, + const QString & defaultVal ) const; void setValue( const QString & cls, const QString & attribute, const QString & value ); void deleteValue( const QString & cls, const QString & attribute); diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index 13c6d9a85..c9cf77357 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -318,6 +318,16 @@ const QString & ConfigManager::value( const QString & cls, +const QString & ConfigManager::value( const QString & cls, + const QString & attribute, + const QString & defaultVal ) const +{ + const QString & val = value( cls, attribute ); + return val.isEmpty() ? defaultVal : val; +} + + + void ConfigManager::setValue( const QString & cls, const QString & attribute, diff --git a/src/gui/SetupDialog.cpp b/src/gui/SetupDialog.cpp index f2fb66cd3..9f135784e 100644 --- a/src/gui/SetupDialog.cpp +++ b/src/gui/SetupDialog.cpp @@ -119,8 +119,8 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : #endif m_backgroundArtwork( QDir::toNativeSeparators( ConfigManager::inst()->backgroundArtwork() ) ), m_smoothScroll( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() ), - m_enableAutoSave( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() ), - m_enableRunningAutoSave( ConfigManager::inst()->value( "ui", "enablerunningautosave" ).toInt() ), + m_enableAutoSave( ConfigManager::inst()->value( "ui", "enableautosave", "1" ).toInt() ), + m_enableRunningAutoSave( ConfigManager::inst()->value( "ui", "enablerunningautosave", "1" ).toInt() ), m_saveInterval( ConfigManager::inst()->value( "ui", "saveinterval" ).toInt() < 1 ? MainWindow::DEFAULT_SAVE_INTERVAL_MINUTES : ConfigManager::inst()->value( "ui", "saveinterval" ).toInt() ), @@ -131,7 +131,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : m_syncVSTPlugins( ConfigManager::inst()->value( "ui", "syncvstplugins" ).toInt() ), m_animateAFP(ConfigManager::inst()->value( "ui", - "animateafp").toInt() ), + "animateafp", "1" ).toInt() ), m_printNoteLabels(ConfigManager::inst()->value( "ui", "printnotelabels").toInt() ), m_displayWaveform(ConfigManager::inst()->value( "ui", From 69d09f01865771c9604be002a3568d72ca9a90d2 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Sun, 14 May 2017 12:22:40 +0200 Subject: [PATCH 21/65] don't change sampleTco length if tempo changed (#3543) --- src/tracks/SampleTrack.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 2152c0cc1..af1060703 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -148,7 +148,7 @@ void SampleTCO::setSampleBuffer( SampleBuffer* sb ) void SampleTCO::setSampleFile( const QString & _sf ) { m_sampleBuffer->setAudioFile( _sf ); - updateLength(); + changeLength( (int) ( m_sampleBuffer->frames() / Engine::framesPerTick() ) ); emit sampleChanged(); emit playbackPositionChanged(); @@ -199,7 +199,7 @@ void SampleTCO::setIsPlaying(bool isPlaying) void SampleTCO::updateLength() { - changeLength( sampleLength() ); + emit sampleChanged(); } @@ -505,12 +505,6 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) // disable antialiasing for borders, since its not needed p.setRenderHint( QPainter::Antialiasing, false ); - if( r.width() < width() - 1 ) - { - p.drawLine( r.x(), r.y() + r.height() / 2, - rect().right() - TCO_BORDER_WIDTH, r.y() + r.height() / 2 ); - } - // inner border p.setPen( c.lighter( 160 ) ); p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH, From f390fd1723452f56740c4feb5ebcc2a6debd6da3 Mon Sep 17 00:00:00 2001 From: Paul Batchelor Date: Mon, 15 May 2017 03:21:34 -0700 Subject: [PATCH 22/65] ReverbSC: Method to change samplerate (#3401) (#3403) * ReverbSC: Method to change samplerate (#3401) * ReverbSC: added mutex for protected malloc * ReverbSC: small CMake fix to remove warning message * ReverbSC: samplerate changed to uint32_t. more CMakeFile tweaks * Fix dc block on oversampling --- plugins/ReverbSC/CMakeLists.txt | 10 +++++----- plugins/ReverbSC/ReverbSC.cpp | 25 +++++++++++++++++++++++-- plugins/ReverbSC/ReverbSC.h | 2 ++ plugins/ReverbSC/ReverbSCControls.cpp | 5 +++++ plugins/ReverbSC/ReverbSCControls.h | 1 + plugins/ReverbSC/base.h | 2 +- plugins/ReverbSC/dcblock.c | 5 +++-- plugins/ReverbSC/dcblock.h | 2 +- 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/plugins/ReverbSC/CMakeLists.txt b/plugins/ReverbSC/CMakeLists.txt index b2481e523..d2abafbe5 100644 --- a/plugins/ReverbSC/CMakeLists.txt +++ b/plugins/ReverbSC/CMakeLists.txt @@ -12,13 +12,13 @@ BUILD_PLUGIN( ReverbSCControlDialog.cpp base.c revsc.c - dcblock.c - MOCFILES - ReverbSC.h - ReverbSCControls.h - ReverbSCControlDialog.h base.h revsc.h dcblock.h + dcblock.c + ReverbSC.h + MOCFILES + ReverbSCControls.h + ReverbSCControlDialog.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png" ) diff --git a/plugins/ReverbSC/ReverbSC.cpp b/plugins/ReverbSC/ReverbSC.cpp index 93e526e87..4d938eb95 100644 --- a/plugins/ReverbSC/ReverbSC.cpp +++ b/plugins/ReverbSC/ReverbSC.cpp @@ -58,8 +58,8 @@ ReverbSCEffect::ReverbSCEffect( Model* parent, const Descriptor::SubPluginFeatur sp_dcblock_create(&dcblk[0]); sp_dcblock_create(&dcblk[1]); - sp_dcblock_init(sp, dcblk[0]); - sp_dcblock_init(sp, dcblk[1]); + sp_dcblock_init(sp, dcblk[0], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() ); + sp_dcblock_init(sp, dcblk[1], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() ); } ReverbSCEffect::~ReverbSCEffect() @@ -125,6 +125,27 @@ bool ReverbSCEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) return isRunning(); } + +void ReverbSCEffect::changeSampleRate() +{ + // Change sr variable in Soundpipe. does not need to be destroyed + sp->sr = Engine::mixer()->processingSampleRate(); + + mutex.lock(); + sp_revsc_destroy(&revsc); + sp_dcblock_destroy(&dcblk[0]); + sp_dcblock_destroy(&dcblk[1]); + + sp_revsc_create(&revsc); + sp_revsc_init(sp, revsc); + + sp_dcblock_create(&dcblk[0]); + sp_dcblock_create(&dcblk[1]); + + sp_dcblock_init(sp, dcblk[0], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() ); + sp_dcblock_init(sp, dcblk[1], Engine::mixer()->currentQualitySettings().sampleRateMultiplier() ); + mutex.unlock(); +} extern "C" { diff --git a/plugins/ReverbSC/ReverbSC.h b/plugins/ReverbSC/ReverbSC.h index 926afb6c6..ba987414b 100644 --- a/plugins/ReverbSC/ReverbSC.h +++ b/plugins/ReverbSC/ReverbSC.h @@ -48,12 +48,14 @@ public: return &m_reverbSCControls; } + void changeSampleRate(); private: ReverbSCControls m_reverbSCControls; sp_data *sp; sp_revsc *revsc; sp_dcblock *dcblk[2]; + QMutex mutex; friend class ReverbSCControls; } ; diff --git a/plugins/ReverbSC/ReverbSCControls.cpp b/plugins/ReverbSC/ReverbSCControls.cpp index 59fe6ccdd..5e52d3faf 100644 --- a/plugins/ReverbSC/ReverbSCControls.cpp +++ b/plugins/ReverbSC/ReverbSCControls.cpp @@ -38,6 +38,7 @@ ReverbSCControls::ReverbSCControls( ReverbSCEffect* effect ) : m_colorModel( 10000.0f, 100.0f, 15000.0f, 0.1f, this, tr( "Color" ) ), m_outputGainModel( 0.0f, -60.0f, 15, 0.1f, this, tr( "Output Gain" ) ) { + connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( changeSampleRate() )); } void ReverbSCControls::changeControl() @@ -60,3 +61,7 @@ void ReverbSCControls::saveSettings( QDomDocument& doc, QDomElement& _this ) m_outputGainModel.saveSettings( doc, _this, "output_gain" ); } +void ReverbSCControls::changeSampleRate() +{ + m_effect->changeSampleRate(); +} diff --git a/plugins/ReverbSC/ReverbSCControls.h b/plugins/ReverbSC/ReverbSCControls.h index d75a79db4..638bdade9 100644 --- a/plugins/ReverbSC/ReverbSCControls.h +++ b/plugins/ReverbSC/ReverbSCControls.h @@ -61,6 +61,7 @@ public: private slots: void changeControl(); + void changeSampleRate(); private: ReverbSCEffect* m_effect; diff --git a/plugins/ReverbSC/base.h b/plugins/ReverbSC/base.h index 6edf5a394..74c535cdc 100644 --- a/plugins/ReverbSC/base.h +++ b/plugins/ReverbSC/base.h @@ -25,7 +25,7 @@ typedef struct sp_auxdata { typedef struct sp_data { SPFLOAT *out; - int sr; + uint32_t sr; int nchan; unsigned long len; unsigned long pos; diff --git a/plugins/ReverbSC/dcblock.c b/plugins/ReverbSC/dcblock.c index 493a9ae92..f38bb73dc 100644 --- a/plugins/ReverbSC/dcblock.c +++ b/plugins/ReverbSC/dcblock.c @@ -10,6 +10,7 @@ * */ +#include #include #include "base.h" #include "dcblock.h" @@ -26,11 +27,11 @@ int sp_dcblock_destroy(sp_dcblock **p) return SP_OK; } -int sp_dcblock_init(sp_data *sp, sp_dcblock *p) +int sp_dcblock_init(sp_data *sp, sp_dcblock *p, int oversampling ) { p->outputs = 0.0; p->inputs = 0.0; - p->gain = 0.99; + p->gain = pow( 0.99, 1.0f / oversampling ); if (p->gain == 0.0 || p->gain>=1.0 || p->gain<=-1.0) p->gain = 0.99; return SP_OK; diff --git a/plugins/ReverbSC/dcblock.h b/plugins/ReverbSC/dcblock.h index 6584c400a..d98d58566 100644 --- a/plugins/ReverbSC/dcblock.h +++ b/plugins/ReverbSC/dcblock.h @@ -7,5 +7,5 @@ typedef struct { int sp_dcblock_create(sp_dcblock **p); int sp_dcblock_destroy(sp_dcblock **p); -int sp_dcblock_init(sp_data *sp, sp_dcblock *p); +int sp_dcblock_init(sp_data *sp, sp_dcblock *p, int oversampling ); int sp_dcblock_compute(sp_data *sp, sp_dcblock *p, SPFLOAT *in, SPFLOAT *out); From b0ae31c2ffa96432e25ddbefe90b62270483cdae Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 15 May 2017 19:46:15 +0200 Subject: [PATCH 23/65] Update Discord invite to link to #welcome (#3556) This is where we want new users directed to. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de99e5ec1..dbd8cb689 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build status](https://img.shields.io/travis/LMMS/lmms.svg?maxAge=3600)](https://travis-ci.org/LMMS/lmms) [![Latest stable release](https://img.shields.io/github/release/LMMS/lmms.svg?maxAge=3600)](https://lmms.io/download) [![Overall downloads on Github](https://img.shields.io/github/downloads/LMMS/lmms/total.svg?maxAge=3600)](https://github.com/LMMS/lmms/releases) -[![Join the chat at Discord](https://img.shields.io/badge/chat-on%20discord-7289DA.svg)](https://discord.gg/5kSc32Z) +[![Join the chat at Discord](https://img.shields.io/badge/chat-on%20discord-7289DA.svg)](https://discord.gg/PHcQDx3) [![Localise on transifex](https://img.shields.io/badge/localise-on_transifex-green.svg)](https://www.transifex.com/lmms/lmms/) What is LMMS? From b432707b9beba0bee1e37281bfa1c5e98e977172 Mon Sep 17 00:00:00 2001 From: Dave French Date: Wed, 17 May 2017 11:18:47 +0100 Subject: [PATCH 24/65] Remove glitching from the Flanger and Delay plugins (#3524) The previous delay code was incorrectly not utalising the whole buffer, causing glitches when incressing the delay time, due to outputting incorrect data, This was apparent when using the lfo in the Delay and Flanger plugins. This has been rectified. The read index is now offset from the write index. and the complete buffer is used in a circular fashon. Flanger - resolved issue where the lfo could create negative delay lengths --- plugins/Delay/StereoDelay.cpp | 18 ++++++++++-------- plugins/Delay/StereoDelay.h | 2 +- plugins/Flanger/FlangerEffect.cpp | 4 ++-- plugins/Flanger/MonoDelay.cpp | 12 ++++++------ plugins/Flanger/MonoDelay.h | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/plugins/Delay/StereoDelay.cpp b/plugins/Delay/StereoDelay.cpp index 327dfda03..d25f6b52b 100644 --- a/plugins/Delay/StereoDelay.cpp +++ b/plugins/Delay/StereoDelay.cpp @@ -36,7 +36,7 @@ StereoDelay::StereoDelay( int maxTime, int sampleRate ) m_maxLength = maxTime * sampleRate; m_length = m_maxLength; - m_index = 0; + m_writeIndex = 0; m_feedback = 0.0f; setSampleRate( sampleRate ); } @@ -57,13 +57,13 @@ StereoDelay::~StereoDelay() void StereoDelay::tick( sampleFrame frame ) { - m_index = ( int )m_length > 0 - ? ( m_index + 1 ) % ( int ) m_length - : m_index; - float lOut = m_buffer[ m_index ][ 0 ]; - float rOut = m_buffer[ m_index ] [1 ]; - m_buffer[ m_index ][ 0 ] = frame[ 0 ] + ( lOut * m_feedback ); - m_buffer[ m_index ][ 1 ] = frame[ 1 ] + ( rOut * m_feedback ); + m_writeIndex = ( m_writeIndex + 1 ) % ( int )m_maxLength; + int readIndex = m_writeIndex - m_length; + if (readIndex < 0 ) { readIndex += m_maxLength; } + float lOut = m_buffer[ readIndex ][ 0 ]; + float rOut = m_buffer[ readIndex ] [1 ]; + m_buffer[ m_writeIndex ][ 0 ] = frame[ 0 ] + ( lOut * m_feedback ); + m_buffer[ m_writeIndex ][ 1 ] = frame[ 1 ] + ( rOut * m_feedback ); frame[ 0 ] = lOut; frame[ 1 ] = rOut; } @@ -71,6 +71,8 @@ void StereoDelay::tick( sampleFrame frame ) + + void StereoDelay::setSampleRate( int sampleRate ) { if( m_buffer ) diff --git a/plugins/Delay/StereoDelay.h b/plugins/Delay/StereoDelay.h index 4886149dc..a0dbdc220 100644 --- a/plugins/Delay/StereoDelay.h +++ b/plugins/Delay/StereoDelay.h @@ -52,7 +52,7 @@ private: sampleFrame* m_buffer; int m_maxLength; float m_length; - int m_index; + int m_writeIndex; float m_feedback; float m_maxTime; }; diff --git a/plugins/Flanger/FlangerEffect.cpp b/plugins/Flanger/FlangerEffect.cpp index f9d55bb60..33ad8081b 100644 --- a/plugins/Flanger/FlangerEffect.cpp +++ b/plugins/Flanger/FlangerEffect.cpp @@ -107,8 +107,8 @@ bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) dryS[0] = buf[f][0]; dryS[1] = buf[f][1]; m_lfo->tick(&leftLfo, &rightLfo); - m_lDelay->setLength( ( float )length + ( amplitude * leftLfo ) ); - m_rDelay->setLength( ( float )length+ ( amplitude * rightLfo ) ); + m_lDelay->setLength( ( float )length + amplitude * (leftLfo+1.0) ); + m_rDelay->setLength( ( float )length + amplitude * (rightLfo+1.0) ); if(invertFeedback) { m_lDelay->tick( &buf[f][1] ); diff --git a/plugins/Flanger/MonoDelay.cpp b/plugins/Flanger/MonoDelay.cpp index 534f0bcf8..fc63d57d1 100644 --- a/plugins/Flanger/MonoDelay.cpp +++ b/plugins/Flanger/MonoDelay.cpp @@ -34,7 +34,7 @@ MonoDelay::MonoDelay( int maxTime , int sampleRate ) m_maxLength = maxTime * sampleRate; m_length = m_maxLength; - m_index = 0; + m_writeIndex = 0; m_feedback = 0.0f; setSampleRate( sampleRate ); } @@ -54,11 +54,11 @@ MonoDelay::~MonoDelay() void MonoDelay::tick( sample_t* sample ) { - m_index = ( int )m_length > 0 - ? ( m_index + 1 ) % ( int )m_length - : m_index; - float out = m_buffer[ m_index ]; - m_buffer[ m_index ] = *sample + ( out * m_feedback ); + m_writeIndex = ( m_writeIndex + 1 ) % ( int )m_maxLength; + int readIndex = m_writeIndex - m_length; + if (readIndex < 0 ) { readIndex += m_maxLength; } + float out = m_buffer[ readIndex ]; + m_buffer[ m_writeIndex ] = *sample + ( out * m_feedback ); *sample = out; } diff --git a/plugins/Flanger/MonoDelay.h b/plugins/Flanger/MonoDelay.h index 8b06bff1b..52898bfe4 100644 --- a/plugins/Flanger/MonoDelay.h +++ b/plugins/Flanger/MonoDelay.h @@ -52,7 +52,7 @@ private: sample_t* m_buffer; int m_maxLength; float m_length; - int m_index; + int m_writeIndex; float m_feedback; float m_maxTime; }; From 300058df0430848697a9ef6f0175857822d4cdd9 Mon Sep 17 00:00:00 2001 From: Dave French Date: Wed, 17 May 2017 11:21:00 +0100 Subject: [PATCH 25/65] Flanger LFO rate syncronised (#3521) The LFO rate was not correctly syncronising to tempo This has been rectifited, to utalise the TempoSyncKnob as intended, returning a period, instead of a frequency. The knob now reports the correct values in the GUI. Flanger LFO maximum period incressed to 60 seconds --- plugins/Flanger/FlangerControls.cpp | 2 +- plugins/Flanger/FlangerControlsDialog.cpp | 2 +- plugins/Flanger/FlangerEffect.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Flanger/FlangerControls.cpp b/plugins/Flanger/FlangerControls.cpp index 7525b8024..4ba91dba3 100644 --- a/plugins/Flanger/FlangerControls.cpp +++ b/plugins/Flanger/FlangerControls.cpp @@ -35,7 +35,7 @@ FlangerControls::FlangerControls( FlangerEffect *effect ) : EffectControls ( effect ), m_effect ( effect ), m_delayTimeModel(0.001, 0.0001, 0.050, 0.0001, this, tr( "Delay Samples" ) ) , - m_lfoFrequencyModel( 0.25, 0.01, 5, 0.0001, 20000.0 ,this, tr( "Lfo Frequency" ) ), + m_lfoFrequencyModel( 0.25, 0.01, 60, 0.0001, 60000.0 ,this, tr( "Lfo Frequency" ) ), m_lfoAmountModel( 0.0, 0.0, 0.0025 , 0.0001 , this , tr( "Seconds" ) ), m_feedbackModel( 0.0 , 0.0 , 1.0 , 0.0001, this, tr( "Regen" ) ), m_whiteNoiseAmountModel( 0.0 , 0.0 , 0.05 , 0.0001, this, tr( "Noise" ) ), diff --git a/plugins/Flanger/FlangerControlsDialog.cpp b/plugins/Flanger/FlangerControlsDialog.cpp index 0b95ac781..972015b0b 100644 --- a/plugins/Flanger/FlangerControlsDialog.cpp +++ b/plugins/Flanger/FlangerControlsDialog.cpp @@ -52,7 +52,7 @@ FlangerControlsDialog::FlangerControlsDialog( FlangerControls *controls ) : lfoFreqKnob->setVolumeKnob( false ); lfoFreqKnob->setModel( &controls->m_lfoFrequencyModel ); lfoFreqKnob->setLabel( tr( "RATE" ) ); - lfoFreqKnob->setHintText( tr ( "Rate:" ) , "Hz" ); + lfoFreqKnob->setHintText( tr ( "Period:" ) , " Sec" ); Knob * lfoAmtKnob = new Knob( knobBright_26, this ); lfoAmtKnob->move( 85,10 ); diff --git a/plugins/Flanger/FlangerEffect.cpp b/plugins/Flanger/FlangerEffect.cpp index 33ad8081b..ab952e5a4 100644 --- a/plugins/Flanger/FlangerEffect.cpp +++ b/plugins/Flanger/FlangerEffect.cpp @@ -94,7 +94,7 @@ bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) const float noise = m_flangerControls.m_whiteNoiseAmountModel.value(); float amplitude = m_flangerControls.m_lfoAmountModel.value() * Engine::mixer()->processingSampleRate(); bool invertFeedback = m_flangerControls.m_invertFeedbackModel.value(); - m_lfo->setFrequency( m_flangerControls.m_lfoFrequencyModel.value() ); + m_lfo->setFrequency( 1.0/m_flangerControls.m_lfoFrequencyModel.value() ); m_lDelay->setFeedback( m_flangerControls.m_feedbackModel.value() ); m_rDelay->setFeedback( m_flangerControls.m_feedbackModel.value() ); sample_t dryS[2]; From c0b910ec578889065b8e46734bfdb36f14f74403 Mon Sep 17 00:00:00 2001 From: Mark-Agent003 Date: Wed, 17 May 2017 09:10:40 -0500 Subject: [PATCH 26/65] Replacement metronome samples (#3513) Replacement metronome sounds --- data/samples/misc/metronome01.ogg | Bin 4754 -> 6523 bytes data/samples/misc/metronome02.ogg | Bin 5291 -> 5834 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/samples/misc/metronome01.ogg b/data/samples/misc/metronome01.ogg index ea8481ff8358d61341407c1d2f261dfe817aa80a..0797c6e80f210750564be14c2d043e6a47c1dbec 100644 GIT binary patch literal 6523 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 literal 4754 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 diff --git a/data/samples/misc/metronome02.ogg b/data/samples/misc/metronome02.ogg index 70b53e117e58ccb8e6c396f7034401ee289d8ab5..780530b3d2e13bbce52df5f86a050f9e22cebb33 100644 GIT binary patch delta 1558 zcmV+x2I={$DatJxPiJRS00IC200000004G!000000002@B@2-nE+=+!00031005xb zKOYi1|NsC0|NsC0|NsC0|NsC0kpp&ba$;$7E0J#-7cww1Ff$-1Pi<~#aB^jHWo~pS z00000vEhgie=`dJ000000CsW!00IC20EeLOKM7M@QU7lLkpGC3Vrdr`03L&Xiq)d$ z#RWA6_m)KsS5O6g`ZMTtDRYfXgRWlZ`6-8AQjaQ;UMY4=yEE+L&mQqEvj71}6 z$#sMYv=ZocB#?|`i`5IEB>+@JSNb+Df4#an(cs?Uit#OP7S)hd&0z!Z zEnagHPhOY!eW*LxbVo{BR=F0uq`o9 ztDLplP@;OyH1u0p*;!~v&Elq}mls|q;KkGLkprt^h@5a|rJ=D5I0IQ*jTAW_a#{?? zf7N7KuL|*;eRf4q(*siLuAV!Gb|fpO7QlXwYEw(DifXM$?Jl!M$n6|~bg%JQ4v-P8+(NI-084Z;T0EEV=G;x$h zjrR5fuEl;Pmf9w9Z7o4dgV|93)aLHV0>S5(v(RquL5*@xvqzPvp*Hxky@VV0e|A}_ z>M<5HSySm&)}&n304{f9nPcoYl0204^P7A>Og5F%^fbk2dd5qU1-v==t?q5DC=QYW zLCrFNCd&ddPf}*QtEJ_A)q)I&i z005AHD*_GU9zeWH4Fy@GJL4r@e=GnXM`pyd_4ZEc)!Fl#c}4{Qjn*iR3JWjRnumT> z(@m#j3Y4-ZfALH~H~!I|GO4M&I4yZU~~GZLg9f6IGF_oyo82?PA>Hp0kv%6G8CC->dS-ATM`D)T-4sc-Z@ zXU`$w{JX3<4yO95N(y8&MGw#HuQo^DAALu2U${RGhMYsz9Z}~~l~96;VlgnV>4#mX zFm`X~+4<%oz{##oL$1$gb^aH7yqmgn=*c=C?u+VxsViz9AFhWUf2e)lW_m^pe}y-^ zkLwp_@-fv@k$*PrRGdwIKc_~99)R8_o#)p#drAxf^Y=45%IBY3%JH>6-lRhAaUE+=J}{uBWK001i5 zb7>NL|NsC0|NsC0|NsC0|NsC0kpp&ba$;$7Es<{U#Ffkw~Q)6ghb!KI5b!TT~ zbSVJ<0024w002X8Z)`nOXlZjGb8l{EAZTH8AYx@@ZXj)8WMv?Ab7^j8AWUg)b$B36 zb!>ELO=VMRJPTG)O+Y+ALqbbY zQ5-%!g|@wA-Bp5V;N8ThuE@4eQm9H04ZPQHwdj^ekYg}a%$iNHTGd6hzOZ6o)y6Rc zm;jU*odN0WFhxnf85NewOs{lHg|EyR2{)Ve`B(CGIw&9 zsXB1bm@K!ft!AJ~OJ#8nd=Xq0aOahak+JAkdP*o!g5R=Dw7OkiVI&bp1_O(XwAz*|?Wq4u(n$tf0(UdDcE&Bj0wB{@ z9?{8cHXt9PQWruiKq|!;Rd!9XdYg=;6$E{y#ce+qnC) z-gfJNn$mJ4%!jxu0uX`(M{umvL?MMR42dyDD;N>cgaW5Sgm`l)e-hel|B@RhE}+R@ zC;p8^rIz9ZEbhwFAfT2^fsDpoIT5%piAsS?J<~%tficFUSf|no#SE282l3Fc0*k!M zU`;JGnNZK#JaR`Lj(u>^cOnM>pds{4BmFqUO5?S$OD77ouWHM zW{(*L-I}6O22x9mFfd-Gs;E*f+GwLB(myI;nn=QGdr)k}wg4|8t!tLNoy)7GuL1nw zN-5eZ#o!8mxKf%(9RRpBHI)!-F!?78hDdRkdDGe_KJ+1-o9^s){0_1&tN3 z((Y92nsZWBj0K}tFj_(0@IrsuNddg!%1|KW?tEfy!<9pUP^=27s&C(K;b>G4fmU2z> z5U+i9^9!xkNI`={IP!Li*FC%VMONz|Pa+)t`t4+}tP%j&^#_Z^s)DM~tPd2qWN(94RVci~pl~r{C07pjbs#am?00o+~a{vGU From 6970c88e2892d93fc6176eff9172df0bca9740b0 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Wed, 17 May 2017 21:37:35 +0200 Subject: [PATCH 27/65] fix alternate shading for changing time signature (#3559) * fix alternate shading for changing time signature * fix alternade shading for automation editor on changing time signature * code cleanup --- src/gui/editors/AutomationEditor.cpp | 14 ++++++++------ src/gui/editors/PianoRoll.cpp | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index e2c58f286..7f2e549a3 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -1233,18 +1233,20 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } } + // alternating shades for better contrast - // count the bars which disappear on left by scrolling - + float timeSignature = static_cast( Engine::getSong()->getTimeSigModel().getNumerator() ) + / static_cast( Engine::getSong()->getTimeSigModel().getDenominator() ); float zoomFactor = m_zoomXLevels[m_zoomingXModel.value()]; - int barCount = m_currentPosition / MidiTime::ticksPerTact(); - int leftBars = m_currentPosition * zoomFactor / m_ppt; + //the bars which disappears at the left side by scrolling + int leftBars = m_currentPosition * zoomFactor / MidiTime::ticksPerTact(); - for( int x = VALUES_WIDTH; x < width() + m_currentPosition * zoomFactor; x += m_ppt, ++barCount ) + //iterates the visible bars and draw the shading on uneven bars + for( int x = VALUES_WIDTH, barCount = leftBars; x < width() + m_currentPosition * zoomFactor / timeSignature; x += m_ppt, ++barCount ) { if( ( barCount + leftBars ) % 2 != 0 ) { - p.fillRect( x - m_currentPosition * zoomFactor, TOP_MARGIN, m_ppt, + p.fillRect( x - m_currentPosition * zoomFactor / timeSignature, TOP_MARGIN, m_ppt, height() - ( SCROLLBAR_SIZE + TOP_MARGIN ), backgroundShade() ); } } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index ced4e58a6..003082594 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -2887,18 +2887,20 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) ++key; } + // Draw alternating shades on bars - // count the bars which disappear on left by scrolling - + float timeSignature = static_cast( Engine::getSong()->getTimeSigModel().getNumerator() ) + / static_cast( Engine::getSong()->getTimeSigModel().getDenominator() ); float zoomFactor = m_zoomLevels[m_zoomingModel.value()]; - int barCount = m_currentPosition / MidiTime::ticksPerTact(); - int leftBars = m_currentPosition * zoomFactor / m_ppt; + //the bars which disappears at the left side by scrolling + int leftBars = m_currentPosition * zoomFactor / MidiTime::ticksPerTact(); - for( int x = WHITE_KEY_WIDTH; x < width() + m_currentPosition * zoomFactor; x += m_ppt, ++barCount ) + //iterates the visible bars and draw the shading on uneven bars + for( int x = WHITE_KEY_WIDTH, barCount = leftBars; x < width() + m_currentPosition * zoomFactor / timeSignature; x += m_ppt, ++barCount ) { if( ( barCount + leftBars ) % 2 != 0 ) { - p.fillRect( x - m_currentPosition * zoomFactor, PR_TOP_MARGIN, m_ppt, + p.fillRect( x - m_currentPosition * zoomFactor / timeSignature, PR_TOP_MARGIN, m_ppt, height() - ( PR_BOTTOM_MARGIN + PR_TOP_MARGIN ), backgroundShade() ); } } From f8a508f9393364e5ebdecfba562d64861867bb27 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Mon, 22 May 2017 06:09:39 +0900 Subject: [PATCH 28/65] Fixes #3563 (#3567) Added m_pattern->updateLength(); in PianoRoll::shiftPos --- src/gui/editors/PianoRoll.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 003082594..905ba9bda 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -997,6 +997,7 @@ void PianoRoll::shiftPos( int amount ) //shift notes pos by amount } m_pattern->rearrangeAllNotes(); + m_pattern->updateLength(); m_pattern->dataChanged(); // we modified the song From 38bca9af00abdc5c4c06e08ea3185e0f6336b489 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Tue, 23 May 2017 23:38:30 +0200 Subject: [PATCH 29/65] fix time signature updates timelinewidget also on pos 0 (#3572) --- src/gui/TimeLineWidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 00bbc3127..06b32ff7f 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -94,6 +94,8 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppt, connect( updateTimer, SIGNAL( timeout() ), this, SLOT( updatePosition() ) ); updateTimer->start( 50 ); + connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), + this, SLOT( update() ) ); } From 3607c92fcd3e868a6d2bb3fe8aac73f6ed74bd0b Mon Sep 17 00:00:00 2001 From: Rebecca LaVie Date: Sat, 27 May 2017 07:39:57 -0700 Subject: [PATCH 30/65] Bitcrush Plugin Redesign (#3575) --- plugins/Bitcrush/BitcrushControlDialog.cpp | 36 ++++++++++----------- plugins/Bitcrush/artwork.png | Bin 52367 -> 1380 bytes 2 files changed, 18 insertions(+), 18 deletions(-) mode change 100644 => 100755 plugins/Bitcrush/BitcrushControlDialog.cpp mode change 100644 => 100755 plugins/Bitcrush/artwork.png diff --git a/plugins/Bitcrush/BitcrushControlDialog.cpp b/plugins/Bitcrush/BitcrushControlDialog.cpp old mode 100644 new mode 100755 index 1bbbd2c47..995378068 --- a/plugins/Bitcrush/BitcrushControlDialog.cpp +++ b/plugins/Bitcrush/BitcrushControlDialog.cpp @@ -41,73 +41,73 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 215, 120 ); + setFixedSize( 181, 128 ); // labels QLabel * inLabel = new QLabel( tr( "IN" ), this ); - inLabel->move( 12, 10); + inLabel->move( 24, 15 ); QLabel * outLabel = new QLabel( tr( "OUT" ), this ); - outLabel->move( 176, 10 ); + outLabel->move( 139, 15 ); // input knobs Knob * inGain = new Knob( knobBright_26, this ); - inGain->move( 12, 25 ); + inGain->move( 16, 32 ); inGain->setModel( & controls->m_inGain ); inGain->setLabel( tr( "GAIN" ) ); inGain->setHintText( tr( "Input Gain:" ) + " ", " dBFS" ); Knob * inNoise = new Knob( knobBright_26, this ); - inNoise->move( 12, 70 ); + inNoise->move( 14, 76 ); inNoise->setModel( & controls->m_inNoise ); - inNoise->setLabel( tr( "NOIS" ) ); + inNoise->setLabel( tr( "NOISE" ) ); inNoise->setHintText( tr( "Input Noise:" ) + " ", "%" ); // output knobs Knob * outGain = new Knob( knobBright_26, this ); - outGain->move( 176, 25 ); + outGain->move( 138, 32 ); outGain->setModel( & controls->m_outGain ); outGain->setLabel( tr( "GAIN" ) ); outGain->setHintText( tr( "Output Gain:" ) + " ", " dBFS" ); Knob * outClip = new Knob( knobBright_26, this ); - outClip->move( 176, 70 ); + outClip->move( 138, 76 ); outClip->setModel( & controls->m_outClip ); outClip->setLabel( tr( "CLIP" ) ); outClip->setHintText( tr( "Output Clip:" ) + " ", "%" ); // leds - LedCheckBox * rateEnabled = new LedCheckBox( tr( "Rate" ), this, tr( "Rate Enabled" ), LedCheckBox::Green ); - rateEnabled->move( 50, 30 ); + LedCheckBox * rateEnabled = new LedCheckBox( "", this, tr( "Rate Enabled" ), LedCheckBox::Green ); + rateEnabled->move( 64, 14 ); rateEnabled->setModel( & controls->m_rateEnabled ); ToolTip::add( rateEnabled, tr( "Enable samplerate-crushing" ) ); - LedCheckBox * depthEnabled = new LedCheckBox( tr( "Depth" ), this, tr( "Depth Enabled" ), LedCheckBox::Green ); - depthEnabled->move( 50, 80 ); + LedCheckBox * depthEnabled = new LedCheckBox( "", this, tr( "Depth Enabled" ), LedCheckBox::Green ); + depthEnabled->move( 101, 14 ); depthEnabled->setModel( & controls->m_depthEnabled ); ToolTip::add( depthEnabled, tr( "Enable bitdepth-crushing" ) ); // rate crushing knobs Knob * rate = new Knob( knobBright_26, this ); - rate->move( 100, 20 ); + rate->move( 59, 32 ); rate->setModel( & controls->m_rate ); - rate->setLabel( tr( "Rate" ) ); + rate->setLabel( tr( "FREQ" ) ); rate->setHintText( tr( "Sample rate:" ) + " ", " Hz" ); Knob * stereoDiff = new Knob( knobBright_26, this ); - stereoDiff->move( 140, 20 ); + stereoDiff->move( 72, 76 ); stereoDiff->setModel( & controls->m_stereoDiff ); - stereoDiff->setLabel( tr( "STD" ) ); + stereoDiff->setLabel( tr( "STEREO" ) ); stereoDiff->setHintText( tr( "Stereo difference:" ) + " ", "%" ); // depth crushing knob Knob * levels = new Knob( knobBright_26, this ); - levels->move( 140, 70 ); + levels->move( 92, 32 ); levels->setModel( & controls->m_levels ); - levels->setLabel( tr( "Levels" ) ); + levels->setLabel( tr( "QUANT" ) ); levels->setHintText( tr( "Levels:" ) + " ", "" ); } diff --git a/plugins/Bitcrush/artwork.png b/plugins/Bitcrush/artwork.png old mode 100644 new mode 100755 index c0c97bdd525f16d86aef5c1377154adae3c075a8..72d980c9164c9f83613c7a76e68a74f76d1e053c GIT binary patch literal 1380 zcmX9;3sllq7-x|}55dmV(iEvv9%~)YS-wC`5zU+rLXV~^Bt*prZ$+$ZikYuQ<+6eX zDk74mY|eBUYMGkD^rX01D=TqhajXs_qZDg@*gfaF-?{hxzVG{ek8`UN>K{Km>^9JUjqZ|PLhgH6a@D>*Xm#$oV zDwoK};_}FBDu~D9p=k6x4u#C5q8RLK@f)lg0CKm2l4#s~D(IWII3hBENu?u+j6ybr zij1K!=p0G9-BkeRV#S3Qa&s7L&=KkhDuqHuMpC0_)cj1px2sA$6)f=wCo%% z2#51QCKjH^r%{lRi~=%^4!|>M6f&2_pd)j*TxKvDO<_}ka}kxoC@26zS!tn}Hk(YF z3A%*JW}tmhKIl=M`Xi9GUrc0pQt|cI&*JD`hC5ci%w0MNMef+a0`2mD`gz~J3w@FI zN$&86_J>9rTfAr*(rHs@9{NWAZ*oadLchAybPJNPIIaYPqGWC4bIp|=HEu=TxpR-B zvQ6OQ|7ca+@`T}s{-&l+oSHTI)?-ZNblUy;*GKbjWh-EE>=mq{reyHXk$$u1!or9q z8WQlNw~Mr3QV5-vK`8!Ypcw z68E8NH%neCqW8hdHLpgd@vnVSJ8*1in!0&b*B)^i!7<8f($ul-V<~4>uvxlvRqJSQ zRZ7u1c#TTOX&k4zD_TR(S7Y^@Vcm|(v#WK->tAsPE zR7iuTsy;a3=6p;4O7-|ot=@OLXL^ZhSg;eOWsoe*RY8LNdXoMUqJzsk+crgcUx->( z4DGwZdO@$ocFQ4{4yCWm^5nRS)u;jS_vYJM&80&yAm{o4Gw#>qZgD_mtmKteRsRW3 zFt2WJdU`C^_`t8<^Xg2e;Rnl_c0u3_hYL1`B*Z=Lz^`FBjT?HHjT`bJ3Bxdm$VaED z(apo%+oai6o_h_bdHMgv?lwMB+J*t)XhXNM(f=FaHv1#20wk0!wT z?DsHP9pHVoLl7=8^-2yCfwEp!%i2OxUyPQY4C5++ereR6cVtkE<-d1~4B`@3toJ>DNus7tlds(r6*90_t7pBtsW9k9w%PNu;?9GYO%n}>rItw|&p7Cn zBJA3@%%2#IR@mmW&kW|+^IM6-u&{w4*pF_#Z2^#Z!hLYyBL8lgm+_?=_C=1hSpoa2 zh0cO)i`Nbuur{>bGIfP=&KPJgCO3r&hu^L-i+K)a&I3OUd~TfNxM$6-0oLg##G?D8 zX<|(vGhmWPxm+B*1!B-&i+8}d=iIBqNld^5M7Mv^ggYB%0^d}6HNp^1tj1DF&}!MB zt#zUN)$aMcxyNPZ zm#{+b&vW^=9-S0s>w)R;Pu3pX?XOpsh0RS1Jfwry!KE^wKzIj?lP!51VR#blReq5q U{CUq$z!wFI!N*5-Mr4%#2aoKCBme*a literal 52367 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�f zz8h()=mZJq9%$^?`l8js7LF!CMS1mx=<|-)RH_PNc4mv;Gm@ZO^jSEf<0*5cPQEhn z6m$e78Ly`7IWuh``Nc7xoHEH)S*P>(VZKKxa_4gZJk`cd!_Y7BmwcA+)Y&Swtw}K`@cD;pdA| z56~|xcDOpWB~)&6!Fvi17tYlQ5h{ybAH?U2sKUPPb8LBIyc+ofI6+bdSJW=rm!L_A zx$u23{$iBx6L?a1-E+h~@3M7w{HM@L+@4nyxe~}5cO!3==mU>RNt3C8O<;Xd(?Rr& z{#fj>2uTPHwUy!b6m7i7d1rLvalkny**1EIoECW*BTwcjm;neX!4jxXf8 zdt)V`*y0_X-R}q)Z6UeB*~v9vRy17{e{!aieQoeqw3W#YT?R*a;O_~WGbN+G5LJ@D z_<^!b=+B*K7c+!?2u&`MRk|uV7S#fL;R16d@(LFHb{6DHe-A<&kxcR6Y!|r}d=K>N zq^*p7zolXMFkV02lzcEF*f)58P13fbOA(wdfy9o*i(V=lffOf&@-;hV2NrLLLe|Bc zlNb<(-D82cm@TpEV&+2ALGFckDtX-Qig!yqOJc}^d~&P@uNah6Sg*y$pDJ-VYC%zb?vmA>M)HN-P0;(>{%qf$6L|@E?i#>wqRLa#N3%rXN!engkB^qs32J05&ub+%IM1D0oMw0DzqNt zys#0BylAf=WkFOhfr(O{!ugkp7Du$hd&iSd(&WuxUzwQ`(SoG~zb2MCGBzZg*w}fe zi#iLs2i+5SDMLG3K3MOLRA-V&+LSY}KeSl@^pv^hzIt0x{g z(In9F=2#mqpOl=L@5Q%2?~HR&kDW2TD6!KeLpUF%cs5!agav6;vUVgHLty-Pla7qf zg{O@%CwV1?yh&@rQlR0a{hC|S?|K^O$(1Fj!;du)|;_1lr*VCdarRYMNJ-fvpZr0+ZOYVeVG z|9De%W5i&u!TSnQDVQku?cm2N78f%cNdw&%c?vI|{_$4ye5G-Ac zVWgxG9qeGWFR~u!cEXv_1*s;c-;SDoE=-K!6GO-WYpblQVKvZmkW^_Y<7X#*gY|OM z4rq(W7vn3Dv*Qy+nfO;Be{WbkDdR-)M!PygU@MAT&}EUzZY}m= z6~%@S^F(zg*1>#E>X^6}#7@L2W5FCJD(j1!Hs(ltJmK=YxLL@W($r zxV|oqj}O}V{KtJQ6*$Df3ccO#lfsV1g```In!lacL-DqtRNiB;O{J%cv@)uJ;>dQe zz8b42MTOadcTs)~=byM3nV!RlYAZ0L5uSx^G4iMBdXGV8qEEo}@EiUzD~ ztak59rFQS?ohjc#=kKt-oRl6&I9@L1D`VM-p{zXcmytd?5>M(nk-XFAxbI8OXon_F zsqmind^fB?SM1nG5e(1lI`K`B?nu0`RPnXp52v~lQW1G!dN6UkUlcjetd%)4&mG=N ze>bgWih`_QrQrNpJqAIuBL_Y#_=i8Mvm4?c6@SW4d$TwJ@5mJ~oZZ)<93}-W= zb>W%o9clB)PUY-D&dTWUr(bv8Unh@m-+A|+WPj4#NoH&^39Iz0;~Hc(G%hq}hB))L zh8=i1C5q zlzlSXk(JQw7;rA5wn4T9@xjy=X>5!%C~TBxaE7wEG34YZ58Aa5TP!s!O{`^fe(_l{ zkFK;`k+b0{R8O>Zk&A*JcpvOJ2ovyimx3O+4Pq@uelYt9(>cNzk{DyMRcAaCe10-p zC4E%Vdl27^ub-2uiD{j~66XjgMioK2bHyU1imr}UWxBKNKv!PAP|ai?tYvH|(Pm(} z@EnLM(J!V8k8B8qUOM!Qr3GtHQ{o!Jqg0wM!cGqIy--*Za|wU^e(-*s?2jks_(@pM zYybct07*naRHDQ|i%Cv)Z&X{QpLa$r>_{}EWCn}%+d!Xkn_E-nbq+QcPPs^7sDa6Z z{R4kptP;pvggaM(Z#b{BQulUe`z+v%&=2N#-G+p%AtMt$$aSZWftJKgE1+Rh~fh=Y2lldv=BM>Uvnk?HHvl9aKFmxyv zi@L!%0v(1}BbJdK8wsb)+XXTz&=%$wW|fE?e>!;%)-;g2hnqH;=OpQa(35m#bl;q| z6l3DCQ|h`ua)Ik-kT)TVLN;2T0 z9T!0e`bgA(l?O5xqa3X9f*$M`jfAg8DbSCM{rqH&3g0s2T=+NP^>eVHte8BK^6HM1 z#2$LjM|_aehD;~z!idRZH>3++zOX2yF_@moS-7U743>d}!RpLVjw0ZNRxaYoXkJ7( zn-$IlN8Wy4r*C{n=4&icN~{(9`rP?CFOK6uUnki&nmaiqk~prJ@g7J~O6*8nkn_EA z@mH`KL`uBcV0YoPPC`i;jDYO}|9rD@+<%P_N=wQI&Z||9{p8(@YC^1wr4M|*5igW| zKu@eWDD|L?j%=CH2ARSs4O@z-Fa-$E1Zg@>Vu~`?KsRT-7p4c1l*JeNDsTkaVD1b5 zEl3YRBe5@M=ECyM#MzGor`-0r9j*rA3E?D`Vp~T1uH1I{D_FPst+oLN>)<}m1(q9T z4~E_rFHJjP4Y@Mc-q1!uWC9yn8nlGh!S&NgCCC__l&d*bVZH{w8&xl6`?z;^YNI)@ z3@M5Qv>~LmsA&-6!AM`&tha-}GvxtiB=#lD)TsGjv`MHiRWNf>&qVv=z03bk)VnRo zvFu2e3$UBJM{@5|UDE&mfz{J>HklFbb^$OCmNg@MASK@z88Daus!MtG#QT-W?;k~m z2rF)Ks(AV$=KN7*Cm@)Xseq+Hvb`g*{y~Otl6WK&F(`S9d-L}aZkLj5@NWqkfP zhX@O!X63sZavs!BY#EPDYPwK2%C)b{uPeiVZ#Rl3@_Lw5N)Dz$im@k76k?2RQO^S2 z80{gRN-h&taHPWR&R4FiWP}gCO5%Gtni8?#@7KY*-@LBZzE;IS$0#k4b?;o_apZs& z2S`Wi_HwO%Ci)VzZwvfq4$ctnyl5_zBBW5_dg1RkD+S5+;HM|}70$aCuJZ66nVN)2 z$I}a+10s{!Kt{rRaI)a}wBvljgeiql|&jv zon;_CSS^UCxIN5Byt057dcBFKQSu}foVD_q&XF3fhTG&-6Q3ERk`T-M^*VTZ=hxR? z^f@RTd{~sSVJSozyFtfHjx&2wkk-;MHi#9Vb~q)G^Csg0U2(GpmqB!K33LTuE3icv;XZ zIT-@fRCv$DSu2_*!{E$t+xCU%N_<_)Sc})|i}Bp3G#&=6WQ0Y|C+k^I7u7D7+^7}= z@b}m!yv0vpb&@9@K2(HK6qaD;f!v*yd?N>NycDij{F%a6a()k}8ch38%NJugv7nZY ztirIxb-uXgHYLun9n#YtU^72tE!?4HnB5==OO)`19!_-U;9^#gabtHSf9(nCOiuh3 z^d*RAN(j~zSp(C_S2|cL<7qFspBY3rSr)?-Ju*cz(<)U5Rulel$ek(;^ON`$$XMGQ z-UbI39~QFqpvnmJ4#xjduQS5!_Vt9_R^{ggkEgCsF@C zNZ-bD4(tXMAr!I*V@^0Lhb(NsoQqm3z82|7_?(r;hnzC15Z?p61Ro2R%4c>;R0a=K5?a#sSG-Q-{b7l14WR@pDThz~%;4`L{GP%Q zg2h8lU-TI;q18_GeVQNj;Ik%jg7r?Ouwpr9E~GAcrJ{Op`}y! znF_vw7;7UIj>YLOzI~$0i4YZLDvb{hT7EhI{kza!!YPU;=e~{C zwV4re}gh)`X>s^+8z|lKwAq{DmCfcyEwmtC_!qzLbV-MYL4bS}YL^ zP+7cYW%f7Yf;US7 zB$x}rU=|_Ix+il8r@|s*x(Lf891zabc+bUADiIs-S&J}iTjSM*SA(&Pc70)UA|$#k zS}nv{WNrKE(iVB1Ed7vwI#zrSrv|cb55zw*`_5)@$5c%-{ zu3cDuK#ZGspXe{}F_}40-k@h}37H0~?BC6(&dU$N1xrE;=bnLU&RlB9~5*imb|N0zR?1NXO#zi#H32fKW67p2_-GBi>NXg|3solky#%D`oWeKaJ}P zS_qbd(Sz$0ra_3J)^V>SlzcoqGvR6!$yh4H=%jU``TsEH-{|oN)=4B|S?2sJ(-%~X zVH1@GFvGBprpYcWo#fmIzJP`5!ad<;)bfRWKX4i{C)U1* z&!AKI<<3VFc?KMYi;}!Ey3)QH=^LIlh^IgZM34mfbkfzx%b6oEAGA{Tp5-d&7^GTZ z!HfxWLtU2Z#>qJ(CbI}-6)Xb7IW%pluq^OQiiIc~x$w^zv|1qgrbU$vsxiqFl&d<^ zoO~Qup9sohgL-CsZWa8TK{=c}8zbE$XX9kgjX3iZ# zE<)DIvoc2>Xok@PDFca2vc~$#paXj^(iyaM@{U2cZDDo<-Ut342l1_xVu+74!&f4Zf`Kj6c|+L@PGa)ppjgE`m@;XqEHToLWnO#|3N2-Rccaz1e-=v3 ziT+GXghJv}$3n>MM5mEdc+9Cs#fDQ$V~(IShmq*o$x;yv?~9aSOh={h@j-Uwk-^~u z%>{Lc_QIb(cuI&X5{8$8`rp&-DD_<6 z#}x#!=mz*8Cnu*9mkZ$rpDZaz zbeewAKW{jLG>l$f#50&u`K89=LNSLrafHN&Pp^Ev3Vb{37{n~-3?v(>${dUOm0$(F zg5`U#5NpBJ(9ub?A})JBvl5150!1rJCK`)ggysik3sd8j3rif0*j7nfAaF?Hdl>ap z)Hb6(Ct-|%7+(M!(c8n`Bs=m;}Z|n zg`S*a4x}_R0&f@5|KL(kQ&b#pg|Y_saQ?29KYfy132unW2uK!`Ycl&L7?+%xC>%lb zNzqD}V`-p%Qe*7NYBS7017^sE8Wl&DQ2k(?LX1xIgE$OXyReD{GsSio%lJs{UiN}0YghYnH-KKI_KL7{T8+P+C~58|*5_F2!C^;y{mz%z(n z2kRT^Yr$%qz>Ra%488~gr=ngR8;P11GYJwWA~zC=E4@hi%}vHr&=6KR(0=3oH;MnE z%YTycAAG(LeQ+z(uflvLMFoqwouM%qR;XWzc|W`&Gv>`YGVxUU=^P%+72pFQXxa#A z5DyU(*M#`PpZB3%u=AG99iNc5OL77W7;a#Us-*WTv3GGNFa~}nq6Ojmjg?H8vF$b@f#!sJ z;B8SxL)*ddNfo0HVU5AA;i8YRS4=D6 zvG9Aq*Fpad+PMp>;XD!Bu(}Kj2u&mw>MPUKSuK;T;qo9TXS$I5%^kY$tg%gnF>WOO zMUDT%)c=LnKg9S#^1y7}crVz$cK|c zcx+KFC8liO7tU2HHVu7MY-)G;|5(PAWp6q?~XI zye`VDl(W%irzPjPl~o?96`Z{n-1e!;!GJ8>_ohCFL6eHLop2J*BC3()WSS!>khv8) zeh;_~?%Qc!i&%{s+q-wPg8NQUiIhk_i3VCQ=8?#~V04=AjwR{Mph8lr2~jKFA1eQ% z#D8(?ztHkVo)fe)mvX%FepKhJLfRaG&Rm)N%Jlo@OB&DWaAa&&xP&VEN`_6c=w5)P z17c$K5Tzi#xa38D2CEun8Si|O*0x^%!ueiK&dOpx_#+L*lXxAO)dQ;oi-NB}nvg>n z%Si{w5_|;~!Br}2I($`fpHL6Pl}{MOf-a6sCFQaO$Px?}h@Zu+PL5JBUrdo5<6{d6 z;b@g-EvShz3|oaCmpqcpgs8`;Q#Bn&_j{geBXlOoyXY+?+*#B$6+pWw;%UmZ-Ba(@pY3%_rm!lLGSdZaFSy ze<<=d>i@-C{wB4b$7WvJ>V77c*)A-FWs0oD@PwYqx*vW?;j=p06dMKE(|Sgqgbk7k zDHoztu*tH4*1c%_(#HGlth#7sd~zfCz`gRN#y3Gq%8G@$BGQ>Zh1I*U%0Rs0!;$vm zB_WJh6o(!P6LQHcog7=g3p6l8@}#wj#oTYPwJ_h~ z+aotV)`^n$T4ENYY%sBcB1)-fW64z0Ay5nUN zQrDbNw(EGU1vOC1cmNWdB{NpQmC$?QwUGj5EDA8m=yFPFEE$|7c&39M$P!3KEeZEQ zkdbA(n*^No;2jU7$`yw9t$d$sHF!#k(krtCnUj)@WzMvq94B*pc*Vh!jiV_sH!L%o z?oF92G*33EzjFK7owTw$RymO{LJPJYLK`UzO%Srh&V4<6wJ)Cg->g+B$ta?{N2b_B zPvL$~ie&1kd~A`9Ogs;oUpUV4Nz#IPXSmSnVD&;I*@aaSRRY&Zp1;tO{d>6uE1Rcc zaUv|D3;9~a3hJ4$v9E&BGwt<@r@y&0Gj3xXlglc>WM(0yf_dYN8#0XhAyo(|B-J0g z%_Pr`;UqmU3$#K!LXySm13wS$5nN>vv!XVCxG4!USg{B#R3@v+z5$8tA=!vGSqo>b zke&_V&xvLWef`CF|E9`cl=wGqd6DWHoi9iab1mw55*{S>h;$7jS?oveS?Qm}D34IJ|LCrB)WP?FM)+E&fkIvPC_Z^@o zY3(G%+A?b{EGn@&j^Gg-<={I$kWa4vVJ)=9K&b?&I^`+w<%kt>Hf%W4g?zmj^Ub+3 zcQ%e9*bs(4q@{#r)EC0y?ZF|H&*&frk(~5=h`a|%kq(v*_&J<%hVTSxJnO@mPUiC) z^F~Qd68N5tY}=vSpFmY9dBbD!S%~LG)3vXzu?t#%nE$2jpaj`#BfKUmj;BGHU&Q4* zDB%IC6-{rP3-N(HN`B3s!m7lGy#wos>(`4u-Za&Xa61;KRbVj`QYuWZ96T%$#?whz z_s~dyl79}SJ%Gri;Rc$6c#NV(?}Io`p6Prw!>ph&SuULI=Lz8fxl&_njQWCLk;Gvh zXqL)B#*=NugjB9Ik*{C$@88t)9~ArVU0CHConIh>HJtN0aW_iYr=JZc2aJIFYK(jG zm4s(Hr3Q1A9khnh$HHP!HKUSPQ7FmjY!-x1Ego&9-dNA!lN+qpH$`F>3yrHKj zFKoP-QAy&Yq{4ACu+AD{pl3ppa!N%3IkV+Le@l?u8N*b>3{UDVBG< zk8JGLGNY|v={_xwRB)e|465!ia{bZmIf2(5xhawmUz|UF)Ec~>Op!`q?@~%ud~6$ij5kig3102uJHBx}c&&+>5H)Zw+pcpa^0%PBl$eSZ zn70urNac%VZw@OwX>ruT>W+y~mSUnLIt2W|xuozO12~ARr2dDHw#Um{On=a0zk^6Y zvWz9;?l+fw5%bNoM$(`pp0l^RKJNMFPn#5z6?vSMHy5M*^`d`1w50eF?lQSn zhA@UeX@y~x7M&2V(Gi>#O4S6VFz2A)*ferGS!*K7mIa$vF-j7murVbUacQYKD? z&mwCDF!je-~#@Rw@apx`08Hsyz zTq~ZH(*MC^uQ5UoIuEHeQs0!PV!IQr`EcqNKHm&4>Vc)3JYMj3B5s4V=;=iD zhV%mK=#!AYCq9FE9mJSyf2Yv?^+msL$`3B@se@}(BGx8k)yf!^BL;ypM@Q2J4>iqr zPS_f>1algxU6@VEk+JS%g+Jqs9={m(;@a~maD$XMvY^2C*pBY-L9_y6lclR0?kChm zSfhubT_+)R7uNA&J%7mcU+DTbPkE8jhZKKf@-QFYg+*{y$KqhEL6WViIR0upe+FL{ z^mow8!_ymbDMwPiKNECt<$_Y^nkX^o5|rfF5Dta+XF(246By6v~n zVlbm{md(eTwkbiM(aGvGDTo=P-1w}NTJab(gGUvbP(Dsd4P@fUNGtyo*4pu`@}X3C z4@IdcJJC?sX_1mEKEAP1F>|hV@Oyqk`QoyHg%Ej%7hpm?^7agU6^INnS@$9|QLZo6 z`cPJ)*U426Y%N@&dchoW48n|03aTZhO{O$RLRtSXeHRvT(7jV@LuN;^QY>?4$7ZML z>!)KSat_qtGZJE*{K`CI?I~SOEZKM7hcR z1-~+JJJ@2iiz2(Q!Y1-$;@8CbE-ao7M8_J&R)WFGLaGO&XQ~b& zj5#JLZ9r#{ij~BggCg+ENh%jH2IXw{y*R}A9v{;2MZXtk16%(SNS|rC`S^dvq_hlk zLP>j3pPlN?S{KxTdS$xrz&*5b_+pj|e|}T+KS}d9Grh=qlk{JheDJZjt^-GC$r$Tk zjZPwX7F>UA^6KkS?ssq=gQqw2R9Z@WJ`-VbZbaajN}`Y?<26t>h=rz#_czfRm~*w0KlTmz;#`whx-)p$;oR*|c<-1Q z43aL^<3vrQ^JLA37K!_GUb$h*wg%6Xxq>1a4D-oBUKx|gh>8}Wt`Ex!q6(x(6u@jyTa{Ek=0}^EWyDZ_@g|%yf~`O)md| z`G*+Bd0mLgHkEN=-pR2Q5!bJa`}g8?3H@CZCg*ydh+exh`zXN zfp5uE5QykbNPG|Eb%1aA&3v3V6fGCAIwgd6bACy|ma`%#kuX;Z3$w+iD9IA8lfE)q zg=P=a_WwoX*rC{^kk`Z|Q9bcVfvpcE3O+wL2~#!uR1->zbN z3-w=LtnV9@KxgHyopTlf!;GI@870f!VilciHRx9_Xh6Sv>w z{J+WbznSHWteuqp#_h9xHP?vQxT|h66s$T0XGAB( z!Ft|1gLnn49K7wD;N-M@omxo|f+R{V33y$}mhHTs(!zm#74jbrdRf?tv_HL#0MwVO{K!@cvMWF5`2b|S)@bh zeIZe4$yw&Ot}T>Rp^eI?bn+@J>72CR)`Tu&xFN!xVyEC^P7%X>AwTam$&h_1G|A~%&)5{lyj_Q9v-1{1H9wEZ&Ho>K0Yh2!p6Rtk_Rq^ih0k>mem>g{$Ud6qq^WoG^n zky+Kf_n9NEcn5d~UL1rF2oOjk5Q0IBgb)HGX8*gpG9&!M+_>;Mz>}P8| zz#S`-@ug@II6jg2#+Gm$aK=`<`m$|knTr&i>5KcR*jy;W?MBK%_0HV~n=Sm{l^WS9 zX?1)FH7BkW;BPY9h34#pcL{h8C=AlMAv{8&nD3Q+)?OYYD0kvjI%yhibI;halbY_A zViQ^t4uq9&tUpK}#=O?%YScnRBk2QQ1M9;2gw-z2C~F(?~c_6@@|x$5Bzth+T z8>Ibq!UVUV-4}UOC<0}nV=^K!yKm!3G(-}ni>XF$gX5^&*CNNDY9?Ya;uqx4?QvdC zC3x9>SW?fB9Wg^K5I0_oonA->IW8!fs|m#tx-u*8j@gu(B)jRO=Yh{XCmDPfmIyw# zGfGz=6-b7>9W5uUiBxC24M~a|pJ;oLLU`BU4BJWa6;v_WbTT`oaX$o`pbIV+a)x6q z-mMX`$nwEET^w?dqO;s6C2{#g{7tfh=pC2MjNeP4K@mqE!h25Odx$>+zK5~FB=^Es zanev`o;yfe)-I5okfD3cMq?LNdtj$9F5iOmVlX?Jo;&y1gZP9PZ%Tb`^UYJZU&7;G zos3^^NRXz{k4_r0ui^!aliMMeiClvv5IPA2A|Z$2b8?hIdo{EktkyaI{=xjKQML0c zZY2GHKClehCvCe9Ikg}@HehUxnyhb9IL`sX^AflsEC{Y;V zTS&eP$paY&MJD53xCNmw+D%#&DSH-VqtnBPF#u(7qq_H$!ZP#jeCb8iCq`!trDV8wr?Rr0i|#+p_H{b{{+x?t}+Tzj5Q{;7Op=HtrZ`g z=jXPu9C`4iH}C_-*nY9LXcCwORSL+Uk441ZK-U7JFP1gxRC?Pej1|swLvo|62XCEZ zT@(SaM3jR1`9LEu{bcc*62@^PL~QGA?2~)FFdz6-%JXDggZwD0sT_K9t!=X?B^VQ? zcSH+?LNjM_W3y~~MBjni-XYTNpm}d787+cm$bAc{nT_zoT5lxnSyiO6KTeO6**dls zCdMQf@(+Tadv|99mBCxKL#%jVrNM3s1Zf)Y=)0eh3aTARnfA3vRlhk3j?T%_PUXq$ zf8oc2n2S^nLcZ{Ia)uJ4@2l)g)Kd@>>;;i3n4?9AOSU_@K4H8N6)x%gNXIsP45e!3 zJ)NvU>jVF>XM8c7%au}<62ZH@kz`~(XsJ>4z~shQUVbLZ`P@B$BQ<`>jjtcbY^Vy>g_ayuNKpv}eL4$RG0>v4+gYjaKy`!AAY?FS zCeq;WPi`6HaH?ztl$;yBs=~tTLCA}`67|firIfOe^vQ@f=FV~>e;oKVu}3Bb9Be6E zN=8jP=Cpjbrwf^Glf_M%m8frYJkiSDPwXcgWm8_34M{uflP;5 z8FP}3LiA2rPD#cHVepF;Ke43WjeyBp_MTMood-;kzmw|1D>^BZWEn{f(L{UsX7-K3 zJSNryw?@{J(f=^NQ{`4pV*SC_lLx{Y4Jid&$j^zGVVB@#r^*)&ZQEhi@r3rZ$XyO)HO1rJ zgZFV`#|Iua@fOw(A=*W%2dO3Gv3TFgkH2U7?;DbFEak3_b~sT+79_UmGNsK5Q~{#n zVW5eW4CbU92mMcHojdQBkCXXY9QMU;8seYWn&0W1pxGpea!?WJc70la>W288a;oJ()3BQz%mTv*uo58IJrtp;L+Oj947i_W0u3*QP8F?lhlZ7 z2`DLnw2r0y@ULa8ly@^_+|+u||J=;SNu8ZsPv$!)=?}jGWPR*bIs$9bZJ_}r3QHP; zV9rH^@5|<#V1qLAcVQVWowzH@8fAU(r5AYwg&k;?i><%2gww7A#39+f~<;?VQrsM zB*E^*r4yUBPpGsoGX1?1;xaS+m{XRW;EaXTKAwH8EZy7I$!V1$3JK0UjxmF zqdC_%`RQSWX@JP47N;P3M@?CC(~74REGgO><@lhz-;C$MF>mVeV7`N#fAc2L=3t+Fe%d*EwBQ%7ZowW`a zH!k}lSB}Jd{~_4}N#A&ZKDZT9@B1{=n{1Wvi5`{S8u?ji?=1vBQesS@>%T+g12JPw zC28mPgkpkO#pVrG_qFIfcxfkfLkg$`N}|6fngv<=p-MN*I;j}5Pv)uUNT?bqz3F%1 zVa8}TR*XfW)9la>cc5Ezqt zDfL@WKHm3!xYL847q%*gUi4llI(RL}I#??aeQrjDu8l58gVbOp=*wBtkhHKnnCjm- zK__P7u8Y)z`xxjp^T!T#F6^XMfus5q$QVD$M7R=j;w>SCxN$+3yy|A<3{U+Ch z&WxVU`lS-#26qTgM?X+#lb+{9gRYgC^6 zmKW((YH<%@nF9BH%k^eCIwl$@0+=@QnNNNF-~suxxSEuGl9h^S2MoN|H% z(j3vktpf|EHKjfST!=oomvAP5q>R_sUO~wpOmoyTHNCl7rpVaAW}UbjYwQ$3XuuF$ z26jVap~b{ULUaolrtXuQIan(*&Y*O7rAe9xs)KZII#Z%>FXJeSus5rnd!95l2VbqS z+MD$FNgrSMQ1ndrxG1%fXF==W^)?=V58}^cS>jxSuN25wJc}_@fJyU&q=C+T3t8Uw zAU|hJLE7MW{-k}q;5;baIL;6H%Sradmk7EF8l6x@$D~(7MJb+GdT&~DcbZJr*t=jb zF;{#gLY?6k(JHgvRDbY`Ka{2%Y2pH6CsQtt`=C9X!bz$NCg@s1N{&!)ov^;|rK+qY z*k>kQlOs0Jx$wGnxHGQ@%@%3EER$0Vznpf0dD{9_yBK$+*hYS7Jy>p}vLILT1%4XI2GgB$ zC4xn-4=fr`n5l8{z}1lkXyw*Pip6S1{dBfcX7HXuHNlE8|NLQ!lI+3Mu`MzA#oZF8 z1vg5PiHE{iXo7$(cehFIu{UvL5Z^YNG&iJFDM(CKB>bFM7rtz8tO|6j@9t)@Ob;dH zN#q;u2T}lMlbG^%VV!?x_Sfc%90?ycN3Gb%aCELKbN+1+UyG@UBPK5dS&Pq7nYK~Z z+e|spD@dly)ewwsi6nsy=lJ-+{pW?B2YFl^=Lgr9lk5*K1sw-@w`j02xr?D{l&LIj zEHQjK%^dBWVZvISG?j2;$!1o1t9WS~^T8keqIRRmV8}vJ1-W?ile2f2S}WsrbTp2l%vt#29~60z6lPaa-Td++foek9 zi$+DOFyA|Iua`28xmQmn{JA%RE9sLv#lI5_Y$u?{O0x=l(rJ`&}p6BOb#xLi0)$)|Du`ol~{ z(v!%JQYd!O?#v^xx3F#LEUJ zm5@%FLNY})afj^}aGCI`-;}2v9h00k5$ycQYQKpPYk2}=qQontUe@`LJ?qIsHknC`E=saR)PcMc93bD0!7xMHMdVLpyjY@WDYS7@bHe5U zXXpKWV(|rwLd}DhZmRDOJGjgdCVeX940tV4HYOb%M$<%6LAz5PKbY??=25ABQO+Oq z%gK82wgvtm`@kiW`$ao4DoSm}viIhljqNG0cGGig?#DC~W2`~b4zG+Br>qCR_)YFg zW*}J^v9WK*d~l_kLn<7?>Wg}2?zeClsN2?q5{jrHA^SU}`);wb@UgxDFfcrDb51`Q z^`hf^G}r-xgX#A+n$IAgC-eFXDL+wdth8f6GlVo6S5K(kB-<#gbX0olF$)0 z7-3xcMb-xy*=yIiak)}u5_Xc;i?J$JK`y7qz?#zQpq7eE=QSIrf~^Oati2^$oKI2i z>5S7h9SH&HM9Wz0E2))2i|u({#i*~vsEM@Gj=QD17R@SE9bbv*7iW4vb)+dFnWdcs zw3av@9Zwnmbu+{^x8jlMfBm(6(E0&&!YmZK`D(J&u`4jy1R`+-3u47L8)Q+Y;Suz- zMHt3FduGx|aT4ohd1W1-4qw`MMrG2;_xdiZOczjHkUR2KSal23_f^?Q?PBB)vR_OD zEOXDB!wmHTwQ*mWTl}tlYR$D8dsXAe^ORwa$xO6D+M*B4#RaL59X^gB#?eHveL#Rt?l-5A2e}z z8B(@#_bdL8yOCl*QF<84;PjJLCS{d9I3JT*Gxr;64MtgLEZ@+56<9zv_=b8;0w0h? zS;Pjo*y0!uW`1!CJa4}9U4^-B5D=FwmA?J~=?6mJwA>j?g4`}HKTvC={9w#RK2Gkt zkuBrX(5sQNFCCM6iTgE=p!iLfz^u~N_D5?RSm|8%E-d|rFmmR|gGMkT_(7p5Xso^N@yx9EAeBsu8yRVP zw+&=Wa{XYw-;Cqr@E7&`Cq{$Bn=3gse`05BW^|Q*a(8M=|D{x#rY7~ajm@Hp6(zv2{*C`%=P^iT|^S%$Q*BI*(lbSE|gqZz2m81 zY2mJWcP(u=qBc-H7&@>SjIKOB2Q3P`CMy)vNq!XkdNV>$4wiM)6D4(CH=N0MkIlwi z1j`MHfrik>ggVilS+W>u;nPTIkXm8*g~`ckzwz>e6T1KaAOJ~3K~%Kc#5R<--kf~k z+Nl@hDvZeV48layg3F-(%hgkMUb26mSkdSLrU$oo9_;*|<~LU_9G?UJozRZLnFbwU zgz`7Zv?QCWKOjE~aStq|{bC%($eWZa4ykt?3R8=-kPC0S#nd1=IJ5mA9@mJT?wml|F#%r2fEykdh&(2DS<%O@4PcGklG{_sT5r^=**k8_SGcs%|U z?<+|9;vI_|AJ}}+g`uGL zVm{W^3w5QZNkN$tECsQQY+z&UHh4^=CZYu8?Odi5{f3>Eguf>KI7mf_X@p;tyfMPj zb_a0fwv*!%e*SRYp2^)A)5*5TKMMZ77-E;Q2|)!J2!n^OPr%-lQ)fpGuqGg%vl6M6|Rq+di@5 zfhbsiGwg{(+cPq$40j%^EzGbz3(Suj`3>W!oYEMgOyF-h(F6>qkDz>1)_b6)^w_km zS%riV;pF_uyx!br<}qK?^Y6T`MX@*UfGi)_{9?$1W4&oLkxNGIy&YlsAg+ezjEMHJ~Wgu-{d&S-iTCK-|2_C@?NHGLOLrdLNQ~wBDR=JbY?;YjopJ# zOkqqU$-Zt(N0*=~%mJfJ@^v8RicWA_IrO>nWB-5B0Eae(pC7@?Gu9`=1Jgi1EA#zr7zxEQl)l5h?*_F5 zSDR!_z+y%M9J#-Zn|V=Y!euhliP(jue$ZDV%R#g+W`1Ha88f&oI5}DAh5Rzg6WTOZ z*+RRdnRN@2oE46h6Jf!>6c!y<$WrNRQ5cX;L~rIe$R9WQTbaknA&seq2mH{&<&bek z56ZLPUmYt#$H`0SCD2(A7A1c&t~c!z&e=)lkKOdpikVN;OWLQ(E!gh7#jQhfl zjH{C`VI-qSV_Dq^?AwW>Kw7L}gimUzj2Osk5|W7G-y|aA=Vx%OjI@apm@V>0#jiIj zHezntBF%*6$?Jw(oOU%Nwi0M-^A>VXrYN2Ur7NL>$v$;s-N-zc>n6#8-Y@+8z+^C| z&~@_qF$Mh9sMJW`azcWPL{{yZ~5Te*WO^CkaNM`gH)ZDFV1DVr9BUpbdL0- z-;Hqw2a8-j8F%7w-26BH-Ty?E2lyXCKB)7e6(MV7^sPeB}~^{Sj~{^{IY2%4m4f!Y?l7d`YZq2}WMl{319s0|3~n@{h$8}+B>Z~zIN(9l?UNL ze_xa$*gE(fgDNlteiY&5!eDSprANn-F+IrBLHEMs8V+S&v(jx)4b{$+W4m{6SS4>l z^eyOeYB_PecVS5)s<768QTTjJ+A6GT?AyT#^jTQ<&6=4EEDLNULWS>~>h4R>i)s81gzgQyN5&S6Mg(aP_pp=u* z2e~@ef-}-CtbPzo4*TGK-^_DS11volmvBBV{`>#&f92Od-r!%L4{ThFBPi*~YR=GK z`^38&o(jHR_%yE7P_H~#^a<}Z$Wchr=wdi+&#L;wOCtIL3m)motqWbUUy4aM1o}#t zIX*iY2~T@IIlm)ow_KPXSX$_siyQ$eTkxGHb{SHgCeDv{fif7kZEirVa1S0@@zn8k zAnDpnZ{MP@>Wyn1NDNBd5KhZa?yK`SGJV1vlcKt9^#!bN#p)OdsYZ0CsG!yM8|eB; zIy(R6U;k_Vi~sz;;_JHbe3R-)xZtgk^FmkQx6i$3C&FhAUQ?Mdcxv6gU`-G~nQUgd z8f!Y7`|6cjB2L@$Iri*o^@Ws#p22*BxKq+ew+neKmL%fXEJeKXsEs=+@$N|4wn{q^ z^SZaJKpeL~m!UFg#P@)_v&aO-O5nry0x5#);2a6hi#8pVV9pM5GWt#8K>C~cILSEE zg{h6Y9%#DYQtA`lS3(~L;lI#0_tiW@Oeddp!?^F8S?YIgaVwW@so46$;$+6nT$ys- z=t~*LgTjlYLR;{+T)6KbNoUSoSZ&^P$vba3aW$>+S+Jy{J#jo6zrTc!TyVd(=4Uomb+Z0sw9c?UR6S@{XK7}wo3)gS z-7zf!-6!ukNZ+UKB^l!T1dEOrW6p)?;>_pnX!_P+X;SJ6dK{bL>jRC-QbE<}J#l8Z zw39|9m$kEyu}Go99Euc2Uk0bsaY{EN&u=!|;4X#If&*jPWJtkgCu&8fGuEW6&OMGj zW3dBYH@Q5y+fAwocOgcnTBfgsXCe6@Z0+!=Cgd9AR5rwa-L&?B9s}CNn1;&Y-~0Ff z&VTS9{znLgzb`04S_gl+b7t_BILoBD(RK1c;kHJK%KyXDyX;ufEa`R2%x@8UXVy8* zCI=jl1B%d$5+p(ZCwl*(00DXmlIO3g%)KN0VrCk6b-zFYnHe78?)H6aNr7^2P3v?; z0wSj^E1PA%5#5=QsX2ClIVtH#jC;4OpXI?_FZgYUXLzt3PnKzVbC(D4-I0RwOs;Qyj5MagOazZ9EGJ--@mO=4t?6k6Y^o&*Gnrk<=r(>FN-${8LH{&cEYRsW{ zGBg@F->f_HaVU3J^rPYHCn+AgxLSp%NR#3x%ifTcRDuPj&pkFIPdWGy4Zx7c#f{H39$Szx{9gAOHJ*#Y6E|hYH0{zQ&|gVMXPv zlY1#MIUf}IpM^z^KMQNmCz8vaO91qYeHeKW+i@98&uIKvSPQD(yJ=k}&tC9qTP&f1 z&qd^d`L<=IaWl@$A;uiSN(;40N;h+4o=0$XMLsIaev<5o%|MFcb1$sm1~+akyE5d3 zWQL^!<&D^}zgRA$qoY@2l)RxhYV?))I1=Lm%>%okJ{s43Q472mR8P!fP{spGlk5-7 zUr>~kgGnQe%<*ILkDtLaW%42XxBu(^=70Im|B<9l(htU%7~dTEWW8>p9JpWmj3u39 zN(sfs%_}-38jZy}3C<$VMoS9|Vfn^XoTV~Eb|_gKGBg(^2cm|oPQ<=3tPdf2;mpP@ zyHrY=TROP*JwgB)r;)z@EUZ|xxvM5?I*}H4Y1AQn0KGa!1zY%$Ddw!T(6ul-S-N)m z{7=e#tect>VFg>AnkrM+jPG?L8A1en187<*LPHOc~-)H zk>?Y4M~Yx;(#|L07iyDNq;0K~Oo@qUBXi^HAYx*ckaD4SW7R_9MszV&<#8nXE6}p= z9+XGrzB;)GZwro4tW3&yGV}goxxa|AaoSxf=}sJfTm1FYc;=lhY5HKr#ee=!|ABw^ zZ~p_!jgU845A1dA%M0F^74CGCOen5|-F$~py-;oPTZ52&173Cej4igOptZ_OnH(E$ z9wkv7COcjfHVA2GhE#)?l_NV>3DgQnoFg^9SLdh+cSu>#^c(zR#=@~Ah~K0dJMilV*rEwo z4GN39KXx%MZq~bzbTRVczy6p1b0^l{18bmu@HH0Y09K);^@p9lI4m*JLZ#rkt&{CY zbTg8|O8HMwQ7Jzh838Gw0!epcH9rddtCMTyVTo~c?90d`!q&~VElOmHE&j@jEZY@U z#4!=fog6`V9840$Z0DeJqO2`}40aVpWFIGL1!f`ZWHmu12+pCIUu*Kn8DBywy8B%I z!?=gh-1owA195t7OgkEtX&w2Og|+s=GM|68 zg7*d}5vV_4_Pwy0l5yTwL(>F0wS&*ou6=J>ADBCjQ^{299xN@mc5GE{z0k24A2u#tZWJ2A(5(W@J$>^I0<4q^0~k$dp~-Zhx91=1Xu6kCL?v7<$I49+Z=43brr_iajF2WH>2 z{J?!OQ$g*fG~Fq4`+(UwLa>^)*x_o34pN?sSEkj;!YL{QTTr*oJ<0#wC+!i)Xw+=H zBWPuxeR>)|B^RZv;An}r?JcX7AcYfF$xmnYMa~T;_7{r;&&2!tCYQoFE8{WX%Se(C z4d&V^9oLSxC%i695_3$VB|Hp?io|3SEqChPtHC>%GIy0-14;oBIxbc!M26JavI(3V ziC7bFmAN`;Hq<*kE6s!+@vmR3&<-Q>aSGo@Xv6KBX9pnqz59j;Wt~rg_soF3f6b(cV`ZC-*RTHx>O!xEVv(a-$CKS)C@#Ymq zMyYG^SK01uUq+R*&*=hHg!XY@6ZB7ACXyi2Nm5v{kQJnyq3%Gh4xv>3yqq8fe3r`p z7?aPGa2Mnd`nQpFaum>0<Qy496-|^oGugr1mSV~IlTE=DAX(W5_ zn#S>Pq#xAm+&C=@*_Eynv{G^=Pv>lf%Z#dZ!(G?lqi3bibTX&y zwEQY`9ps#GI>jq@EON?VCrf@&^^>s4$_b5&T$Hr}@sr}6;@gu|6y8^%rKs-HWTY};@^hQAUPhHoE9@Vkxux&(4x%lyRxGlwLiS}l?a&>sh|@X zJ9^buq84F;5)*OXyEK`cV}Pl##3;5C>DEqcOmgg7yP?8kDz`YEpt<5QVKsg{o!=vo z9|vpvqVn7ZkaJ_)+T+O@FOnoK34RXF<*dUZjfWo%W9#paCerUoO!Xj z?tG~&j>iY@?-$2s;x&SYWI{ULPyBSUD=|BIDobEeXhV5?8oz!mYD#olJfbnvq(}#O zFlE9TlmxR+3|L}BG;Zz0%gD=_!?@yfJ{^_qG1C&* z1xHB?4aA*N%6?JG?yByR_y#KK4HnJp=WqO)>>qkdt-Ie;BiP13mixht&Z0uV>zxM;^5K$>U$usCV zQIl|AN=}_zh4)vcodY*0x-UFZ5BwR3J-ED6TcD$Hvbe-~lmrGN8$>20rG-(C#{25z zl(Dp{+(x1$V_imRk9}QQl)?sN-tU`~E2S&z$HKlBLV@Ij7g8AvVc+__{v^7uMdU=3 z8yycsJ2nfZgCifTbZ^CJ3~VNj97M^e2{A5KQalN@D=XRF1|u*HF9qv^EDf%7rFnyj z68Hpbg(?eYV(XyQJ)q_f=kI7wUSD6FpM`f14#`9sSbs9lpqLUiNXH(^E^VAsIX;Y^ zzl~aiX_F5rjJjyikvJGSc3Cwm^B%Y;Qw;IO)Iof&KSvbhjz!5(`^EognP*I#LS&L=+4Ap_kUL}gi_AYE zap9VP8>+CDVBVTd)s9?7{7WdOB%T4xHb~ z`N)Me+2P9AGKgk_(%MD_XvV}zo`^_1@(v~Iabh^4N{dXY#_N5PN~4X0|Cq%0LO6-+ zge5fZkS9{nYa$KCy;vnd?&$r1JXkYh>EcWey58t5T+#9QM{H3GX$iIlD`i|VZ8%XD zbMEL*+{SDfyLTB;>|$!7b!F7;e`*Lb9K7=UFuuMM_2UWSCn=tLVLf17!TIA8zkeg1 z>BV{02No3_zaT%6Yhii*As;P5V~^r$-9^(_*g7d?;ax!&ETgn1->=^sAC;>^kpyXA z_Xp!}4pY_|lp=T$I+>>l=jY=0Z=b^L2? zG5ktg)+t3e?l=GQXncw>Oek76Ow|@gNyyPyb2CshHBvGB4dqdICnzT544x7qf-a}l zf@rX8+fP5v!av63qY9X!M`B(F zQjr?piG!bt&1K)B9q31vTqn?5^|ZO zagxgbhCq40Vy;BU;7kt&7jg@oeVu(&u$oCDGuKU|#F9j{1!|1poH_8}*fX=<3n>{{ zZ-ykw6nfe4nBx2Vpwc)WlkZod{q2GGe<1RK2jqt`-$DC4k^9#!j8^BN4?GH~|0I}`888Q{W)7ds?xa)@hm~zZLghwAVYuxa z;GJ0dMUlVZ>xJe@_zNlf+S6Czh=uh1FqWoVJxC=;+4%i+@HhtXr=aPolrGG+P(OLw zpteQ2KRA-{N{b(o!7}f9Z}Xuh&KT6vc=bWmjOR%NdZsq#ZIHdd7?3s%Yo;Kz1^t1h!S&}1a-@SL9eOaiuvvLzm`TYa zVRqrU(j#rXt4#WIjucoo>{Qm5k!vH5iy@gd{}?%vplkcggoC4Xu6L$>e&E+XfR7#E zZo;@G=Z`1SU#w_YG0yyfX+g&?v_7|wIok%btQ#u{MX;pgWxyU6wBm5@JgEevTp0J8 zau!ApvMO3`?E1hS!HIB>&a)+E6J})2u?;Z4{(_t)Bb0N&)e>!Wf`gt1vV;`DT9Zh^ z?MC!O92fioPut_x?IItQa{cCa{ooOUYX*ljG-qNNt;%M&kA+I2XhDlJzo9)EZ-VNM z)P++?DkzIqDk*1fb7H~gnfb?beiQ*uo2N22qp%&uZy>9XUyD&4^FqSV@O>~|>(9c< zdtu$f&|R}+F_|G`28@h(Yyjc_(=V$24IeMa1@jlu5=O>*riCFl^bDma{a%!;lr;H$ z9X#6r7c>c$740zFMEps&NjVp#f6`>~CB~1G2}``no0<+BBeF(XXVJSl29zOtYcbng9i(X$(V4|9qP3+F@HI=6~t~v&eUbx*(vFdvNab- zl~UeZSE2m)#QT3o0j}PcCrWL#xXZqN=`~H*Co__=blZ3M7L|TP_AxiN@ z;4Ckc=}igH(ir_FADJ~E>pp+k`-wfA7Q#IzA5B?hcfTGT>SOZtQz+$TkU5w0 zs!*&$6lNYUA$efF2o+v2k-7D~@e?fFlyz|VH&QG4`iob2a>(R1;nV|7N{m6RLF9wz zTmF~RZnn?w;5;iYwp2o@w+4x5{{+7W_p%!!m^In|C;&%iD z)Z3XYu-M+_`8G6aFk=yGvFi33^)bmQ5w@Wl7WyX!^9ST|fWINQDEfodzc#umUPx9v z3Vtgs9Jzy8pytFq*T($n;MaBV_cFo#U%?=Cu(8Va$?!pLlcWEjNavM|AGxrma+jN` z50)tpy~(xmz9Hwd1FJ(J3r&T-VuM|>qDLh!M~d<4M$L_M4dQQse*;pG=IEWNZSl2) z_Am3Q>dc{77F-r(Jt%o_-wDx0O%Em=UCzLnt8ixU1ZtVpUqr5$2#vYX1WRZow%+tm z*0*vT3G=~Jp-iJ^*|tKn{YK7-oG#`(sGpz2{Xf~^G^bQyPUG?UMEe)E{vIc1xqLok;oH^T9vH z;BPen6+Ihe1$TFjw)i$75ZZ7?0gsCMV7`rffX-#G9Lt&HZ^m#`Gd`RZYY!8p{{gT- zJr#dB=uJu=*mx7=B;p$>38%0wr6GGOScR08u_m=7JUU-@;!2}NPSQyiVmf3orr(*A|%CRt(2xU04B&uxqPSQr?ipmR{2j%gE`yU`5C`zrm zAF|Jbcz?6(#ESDM4@??*{X~zGaU~*8H1fYn`<$IPsj<&NtVAl1X2SHMP$4TeCt4Cr zh?ch$aU86}dARUq@=O`4d)DojXv zUii1;WzwQ?`-P-N9lvq%4d}IL^#F1a(Evfy_ib z5+w$e3Z*dCAT0QNp8R#){4JH;uR985K)0ag&dVIbIlE!m32lf^#>+_+>I&2`Oe?b9 zI|Z*9pW7|95L5;>6--H0=&yz6K}nz3d=t4LvUQv8g>}OrTg^F3?AVB{4RxXO8kHYq zvCJqc3|X9!m=)9>+|en`N%sR`@M7_!HP%qZagjJN7oKGxr7AEyjHbo{W?l ztcx(1O!`!w=(eU}Gzr!;)+4iCN=XZsJwvP086{D%-38MVS|;fi zF`m@N6UHy1e2~OR4f=G>XTjfZJPyctXNi5cKOr5XUX?8I4-lkg`G;3UTk+hNTpS><(i11luN1S;#VjSgk=-W z!dgLhB~qu3idLiFf+eGBMy%rwO9o>kK18_H$dMV=St(<>&xOMm%M(>&H?WV)`VyKh zO!q&kb!B9wu@f^%4)iQY{Kn^l@^ND0C!Rmhwe}2g1kYz?T`xoqNRvl7m|D^6A5c&H zZkX=HveuiZCn3SG;310@4t;}x(mELFMUjRV2wOzfZBD6~>;sRAWv7{Pr^SQ3YcW%K zE+Kt%-oN*=bW7!9I1=X_RUh8(7p{lUa;OL+6dZg#3hT&M$LR4Au0)SAr14g@}uL6FY50VcqjDLEZeBTbOBQ(N@`iPgb{rGv2wM}i(k zk<46^sk+Ai))~`Tt~7$V3^@vZC5|#!3Wjl9nO=n|hQ~tufu0%nzX*Gf+Y?`3kUvR2 zNwu(s^L!S@`%RJuF2=JQ3_XzVpGZ0Iky-V<)8y-gl?UJqbDnApUobOK7THdQyf%0! zZ3&-gBD>;Es4?($V0jOSd!9U~P>g$KJ{&P#gM)isph~rGzU0Z77gvWy-ElN?>W3hbnIp(qNU9W7<`o=A;Wdrg zGE&FpF&?|sQEYGb#??VGkMrQK>*B}p*gdZwm3jyFGBht7p=;t}1~W}o93-FE+xNm+ z!EbX!gVGhXrGzW8HZZYUrf_q;gJQDHdN-hND*w!|UmLba$HPG~{WhK@G5TWUo#Y4% zRDsUxe(+SstaGHoYu=pc23^-Z3p44!sBmi!~p3bJ7%69z3XcnRH7$JdirM-a**n zE(edfxKrA>(Uq7_C*GAJ#%&YJgZrAC@nFcpZ^7ivLE~O;Oq6C9-}&TG2lpK`3Yzwy zyMF9A-rbE_g`$~M5_351IJig9j(s+kUdRunU=%OJM`2tGa^i7Re%&{p?Zk$We>|vn zFagp;5r!(yv6(NOKV{OlAs^c{_j?(Nkp_DPyosV4%{8Wy8QeD%950G@K;DS_o84kf z4@^3t`xfpViyyUcy(cT%-oG*s0ny2KfAExrGiar9kHI6I+;za6afO_WG!F=4sPXrY z#_O$6CrK(k6OTg)9NwU<;JP?c!5p$0t_N9zlrq+KT}CrpbQ&jmU-0*ae@~_qNXl^w zQ6^uy%Z5G{DFrk3*-6UaT*^9xc`4KP*HYF3NxWqbh^sihd`kpwV-}FuhjzaOOHK*F zm&4icThO@h94rZrsoXUx%n!%kjlyEOVQDbmPT|~e>tws9rq~4Pm5XDIWZ{3k5Yr7><7Wh5kA*5ye9Fr`L&cyPCxuYxvxw!_^)ae&g zF-&1y4t%5O-(c$(Ne(PcKu{8|KKNV7T(3d=aSTgdBu${s>ppnIBBXO@<=tEKucbomh42f`*Z5 zp}&oF#5SbF1-CGgQ$h(}#F2?1B<`(a4dtAPciD6$lYw&LN5bPTGM`8~34P(5XzX^g zFF0zZUl+N35G#0=%1aVuTu=*X9Nh9np%H##`2<_c2p&bb?@2@$oYw(uBQH;@{QIN7DZ22_A@}IL%qPUzP4L;rNrtW`VIQetof5%4lLUy7NLCi&X2OcSe63OM9vHM4HSL% zN?i-lPScZDzd6f;H*a)q08Q8;#lfb>E&l#&e0>Y$G)RfnGv~Q+WpWGYkd4PK!kUR9 z6UqZ6PzgAn)VXjm=FVQD^MyVSqECirNWyUoRu->Rc+4Pdq8iL_#;lMh=gO=K;{ppO zKQi==rov?dYzJCxIQgiIx3M09SfRuPw=%L*NSg|EWUMRt2gY&-d<@}LgQgJC(Rkn` zqwFgeM#OPEZh9f|9{N%evB;L$3tQ0lypQb=}ir5Du;9yc>R5FgAGJhO1W z2aqXc5c9+tXcAVgyE%}A>TpS+mBAVZl8plNwDx6DGu$i1LGoaYE#DhuBLZ9sA&WAM z^(pxGNtS`FU=_pOfu_pBVF(u2?s2U*mK%B9+>$uzq<0}jLko=Mq~}7TF}qWnqQ>S; zn$!M;?pyu{B`i^ngxVi#<}+bm3q1-)DSTa%&!ezxl8y&$PDUi8ICSS#6Q3jSYAo3U zBVm5G}1HH})7m|zUVmB^c8s8@H+YK)gq#BkjU3;ibew_rc1Eo(!8vzhc)XP`ryL5SlT+d4H&~_W!p+f|=&yx+%7(@Gn;4myootC_$z|UthLr2dR#Tt z)X@KbmBu}LS7nkB{s5qc=ed7S#U$wN2D52NXa+qM9(A#LL9$ac?KV&nD#r9oiU1wV zCD=U~wUB%wURW}z6YN9q*GZOZi(P49Z$ndIxf4V7iB(Q~U3hwu=fy?k*^H|z#ST&p zrY-8zNxpZRr71gQln4(FJ-FQ{88jQ&Gg=Dx+8FOsnO_qI|b*C{-Q;YVPU-DN$Td66Z(4xRAS$9=f42Ett1*=)%f#AHB(;eUe6Jw`6YDa~;=ycbGA( zKvl}^SX*30Ic~#jAQHF)vsX9=&$(?o;{_f@IrfQ_r0^XBS(Tg@@!!spH`u4Ztf;?W zW?UWl*%3;~%#mn%BUQOP@j2mj7}XueN!mawD*wjiiP=fkH$zXV+fNNs&>o3-FN#($ z<*3TH1&BPr{ z>xtMGT`C`C@zsU08XViXQ6C`Dn8T?>kp)r+dQ--SaLUY?{BlpIigkygib z|2ROQ+;6-cFc-`BJ-xh*IE>j6$MxS2PQ!5e-SDGxc41_pznw@yDGA0+N`;s2#b?eF znLi-aTUZ~!C}@16cg8b7?9a3&R!=k;L{@Gq{93{55O(~`Nl`!(F8@W!Pr{y5y&3r= z*?=m9Iqi|T-kn56DI7(3yV87PuB*3wVy%Qo$I6MuV2E>8;r@0~0cB!-fZv_A)}1%w#Uw!=tm7c#DBC0Wn2CN2W`9t)aNB-NyC$hN zd^u}u1e>G->|&*g#uxSz#{4sNtH)fB5|Thqt^oeyU__-Xz`9`P9Ktl-E1c&bj-7@67oOro2i0M2|!tPCAt6 ziK-J%C%Fikf~gPE8aR#t5s7+t;yA!|9jay_za2lfov2)kn0`T?T*@i4;GMub%Q^^dnU;@dM9RbG!_LTa$?ZwCnXQ;N~pr2!u>-xlH@FDWB`9NqQEt<+Q1F<%t(`S zB>K@={iJeX{(~*J400k?>`YziNZQb&Ng>BK%O12m_@0WcLONmgMfwobwsIX!U@hu# zU~W`tyd-G4JG|3U(1NlyzWh++4e3!yI(glRXDJK@sXS;d^x;J9ly~C&OgwJjfZ>Wz z$@h&^!4k}AoR11KSZTM;c!MeXWQ%0Tf*}j_9K?hbZ&d!q?B8fSuma&iXyU71{M{P& zwGay|?E-+D!5jt&?nUIuvpo3E*Ka;P|E9;+9;}?PzL^N(fyu^I_dC(rW~1AkX-{M2 zH|hI8YwS3~nc-3rlgKAp6M{4Jq}Tu)4*?Rj58`adT6h#lAh%(s|Aa3W%YvNtiN!dX z598w&W=f3Vgzjq)Hb^P+ia=H(=_DBQ^LjpPC1?WxlWeGgz6Vi?=3Y|b zI~s0_Y=v0{XHSe{V*NqoMm&fxtQ%1jabb+0q=}Sc!(KNgXQ!O=+eX;wvypUg_rh5-LvLC+$rg-ha60Xs zcs+yX0#A;`-v8!nK$HDrIgL~kF&+Ilkaxh5m?FfSfRS7=Cb|Ok>9`G&{>8LEWZMu* zmJ`75@#3HQVBCvjv3cGq)VlkGor-irs_{sTKi_|N{`!Z@-{@+jACP6v2kOpbQJXS* zq8*NEW!{;zxnyOvz9My$v~K z$LjCNawuxMiglj!hx6B5%%ohyP+>zw{HCPL%NP2?sZ-lrMH{gxKX<0po6;M%E{ZtG z94&=8lw2o>aB1aX#w#nQZ(TIeNRn9khe~C|2RVK-^JBwNYi5O!it_4%BMSR6wzwlx zVh{qQd@|&l+#PG1lq5cR#pI(V?mN&lP>tPiKW;P?W~o$3;DZ_oFAaSJdMnNrS>9=@ z!YLce5=Hk4VNgi@#_EBsh0exole7Q4vBp7>8+-=_*IYzNs44w&Qd>l>Wa})+sJ>wx zl;XUn5>sf;;Jya>88pk-N<uL8twS$XHwy7y!hAgNO?1$YPPneZg!Yhtc6-FqzYgYj_wnv+|U*Dy*7LpY_RY-^knWLr@wi7FP^D$~I7o4oX&*SMpe-S{XIdiW0Fl zxToxTs5}%4Xc;b1QYf_sV<|Z)B0GmA>t?hAvyIHQqw~0h@o?76WIC||vxM6qNeekhYb$7GPGc{1He zc5t`gIXidgPk&Sp0b+}!%F8Fo9-Gl;u(D&9kseAPg_0+EDA$gJl0^_Lo3{EeVhN)h z9KIM?+3(u@Wh^LDzbT&S@uc_{cY308aVSg|O08UX=d?`Wz+O4ncJOk?)je&%N)eTEkMNF7|gK|QY6wWlo7?iaEsPCazDD5!bGf1C7 zv%szFUhQ>rJU;MrbJz!?DS5sL$>g7#i>UJH%2gG)gZQgbY|v-nIJR_^q><8wqRJ_h z7yLfqxi-BVEzDsY%do6F<96SqM`O)Ea-iRE{OcF4LcR?tyJY-|iHbA&76)&8qwoHk z7EkOjWGRt__dR)*C*SvrbL#dBs*;n_M z2jn#Tt~}1oSHJGW=R>*W59R%fdhBBHawOskvVfl_s*bR)dc_Ci!ZOD-cr4>8mAnGa zo1&!mKtBpjQ0D$aHXY36h*z#6e5{+{nQz-9AONvI#d$l#dh8QxII9J58|6{xJyU)P z#P3+7*q~OIgvU09GL=~y&GvP(xUurug=nhu^hJxzhzB*^-1;P~MNPtBXWy>Tc}69( zt!Aq#Y5vIj~cjmRy=9oX7~Z=%Xpp2bH&D3YhfNoE znn}x$pI}#}wt6VHA)7A_~c@F;d?D=Y$YoNBwf5L5q@&yjtpZS5SNi2 z2X|L;?&u}lERqO084(#5CD)Fu!mO1RvFl#Vm>MK6qzqHPXi``_D0(yWB!$rg1|Z_x zbMT}hcH(+rC6R{%n+LJ1qwm0~k>miJJ0@om)^bFQRG_aynvhb^Tjng8Ws?WWDtWWd zr((;fIT*e_!~9ch9+60S?E2hwnx#dL0?kwFv>Qnwghj_q|_Lr6M6C|nXlh3 zp3hH)FY**T3uy+Cw&f>8h;tylavXbCcE8~HA-v@`=lF|SjP6Ql3Gbjeh*AJ2t1!tF zzkp*8tHy?wma?;FSx5@>K%WQbK&G(*S`yYWG%7v8XWR_S{9gNtYZ4R>R3_KTh#WL7 z;)Z!bOe38qJ14kc2)!8Dg_1z5;z>wp5TjsiC$OE2tjS1D@=OvKe9=;6*|`Ucb9>z) zxPhFMf$<>)#)0RKm$V7E2+}Iy9d5^(lk_0mdB@Lw^?Q0po}GWZ$U&qIKg>`a-iMjLCHwEmwLU)EUgWC^^$axFVCKF#AMw za^}SA>*n$K_g=(J|2eTXnJ5SXA=*UmgVq!eXT0EeX1@8uc|FP18J5W{Z#aoMQFh2N zVT?%B7$6581DD`&JFk+-Yj-F~0Mp^IJ%KWJU1`d~>I?P4CCaBl(#aE#d9e0sl@H%;)pvs z52k$Kr64^qms4tD$>gwx+r)}++`cVHJrPT(GB(=mRE`MD8lG+>1}*)?EH{t)WSxn) z{~#O`*}c$`PfmAw6Vh8re`R6})@&4E4{{VrPWW1QbIMn6KN9EK%u35Y*W@XTnh~6M zb@X3PVj0+Ey#+lphNH_idnC51syd+?y#~wQr1clA8(-;*q9;0SaL?eVjj=j)I$pNI zS1`U@NuG9VNA5gI;`Qg|@%f7t16@EVXrH(QE@{WEYo}ZX^$;9q-aF>N^qDAX+Zm7?Sx_>>OlXyTN{+^O8r3R+;(tDrA1|y(6DmFSyLD~Y#gcv=X6Jh#x z$4FbO{vaEmJBpum_bnGIcu7c2$PY86Xxe7m>?r&UmecZLMZxcz(8|)FT)PeO;}*u5 zuJv4IX#!U6tzlbW{S&@phnPBEzr+sVMY? zNZ}UYOyBg?coDRi^avgfiQr`!&y`2K(ODQzBYKcap|3(olkkM8q06Ynam%D8cwXdr zG9E^=2axd9u|uE>5`mSAEXsUWavnSGvVum?%|J7kz4*lm(FncKoY1vFDCe1(qvHdj zEY{km%w*#frcWpdZ3DX=VNr;F zK`KmvGBUFco-6Y{5`G!wbYczEo|yi@@`=o#kICb4Fk;cxMvRRtv}orNg>i)s;ga_2QN8DY31aZ``P^}VQ{A4FQj6;LWM`-aol4yDa> z^SC~crx4-9%-eOHe^c)*Ud&O^(^z-xJL|FOJr(2flSiAVD{C1m!Sfwlhmb6OA}>x5 zSUbqM!)>hLNK&{bkhyRUz3Hp+7UA$k7v~9x7~h@=Egal16ZWzHPo>n&xhM-(-go=u zv}&wT(GnX>GaCI^G&}GJ!fsX-SZPy!b0?WFdLz}Fl?rsHVBF?Ns`PmAFMnV(!d_@f z$WA(NJTr3{J`hNzO$pw9X;Sh8aoHqn}~m!k(n z1d`g%U+OQ1uy+`ckj85l-A4aOqP4@~|r@&~ev*%!xI=rL*DP`T+D>dWwl zvL;O1ypWy+sZIuB9k>p%Ij#L-9+UR2__LDjzcBmYXwLA!^yFk=Igv)7p9gk-;r_9c z4cVxkL6jOBkd$xeXX0>0G%%@{U*xWMDtMXr_auEZ`V=%K(b=DqLb8don=z*gxk7jl z>IfZQPx6Ahd^5*M(+$scGjxnan~r2~6;7RekF;6jD~Pzs59RvWc^AW~y$ z0$EtvdAy&*LzuB(6$GJ|-_-t#))vbe>2TH*vN*It#4b;G+rguD#FS;us>0*jxlTpx zhujm6DbSvz^dcs^k-i8r79o4E&NP{p=qSe;+yal-3g7o?gl4MT=t|ggVeehv*)5TC zAYO7}11Owl^{@$FmWo>|zNunxB{oD<{$t#MfSO z|1o$7X7V#w&LP1JklxX}PpmXD4JT&NO_j#pY*TJSvbtfMmjMAyE19>)d{D0W|;5dWnj>&^(7+!U&pue8j z{f)QdaZx%%$yk~2Hso0uOQ;O&b|mkscZ$G)Lxd>> zErw6ooypDT%drB_ zFXQ?UG-6wm9A*kUKM}r&rTZ$FWuK&xxqJ^+_e7`C=Ab9x45zB`pV{z~sM^VvnU8^A zP;2F0iCP>Tf@MeCcs9eYgWMJz3h%@yP&kMb+j(CFF(EI*>ZD3A`h%J~PR7O_itdi4 zgYMt_Wd}1qi2g!bMQleF$|K>XtZN~4VT(h}`R)RDNp`Ftb@3fimS|>3V+5W=PH$5Gs75Lj} z&%#><$13F9nMG*tg*U~Q(#<*3F3eEf-Cwd!B8~m_%^$2hXjer)E7JcbzCLMBXD!1x zd4}UP&>_g@gZ2IoVoy{8tp=+R0VNi82zo2M!5Mq7f&=pxWq{|OME%={Gc!#oJ{kRo zXe|4>?B*N-<{3$YSP4rV*ee+6%1Up{8&BCck}yY;k?RBXw$-LH2Y>EN3t@zTFZ8jP z-$8nu+behNhjR2zst0{7G-W1lp7(?GAz%}+gqh*(O}ihIY^>RkYJ@4tj5v0t=a8U} z#?h`VZTDa%;qij&k-!W!Mco+5i2O;GKii=(1u2fbjiiYS=aHRhiRp=EH@&1iSlRi{ zYG_gpZn7o%If$NUwb3nc6oamKal(v8gK^ir9Bu`C<+;g( zT#=eMbt{xBVC6m7(q(%$1wrf}(x8Sxdf=^d+y(h;`1`+b z`?UvaI3`a{UszeBsrbjiy8lgzUqo8_JGC}{vG^jIAh$4T@Fd|9?56D(WhFdq;UsQ@ zv*5m=pmR;KR$K+qg|DENU`8VH!aXy!BX$$9`xCt0tjHW1+|%&oNE&F`TufaNoEiSp zH{EqELt8%#G@ z^-*?Nw+4MSYI}p%pLP}JdBghSPR4H+h$@JRc&KxCkyK)x8ZpG^^0(>@@_pZdr=Rv&Tu27``fe&M~-D;Re zV0BW$x&5T39fMg`X)(DS(1V*dzd{-L7k<4d&)wBK1ahmyjBT@OM)l~QhO2fx=z zxeD>oi1)uCK4>4t9D(PPlL-~_68v%C_iwWQB6J}IV(cHfY6~8UUCOA=LlQF#m5Rwt znM&xshI)563Lc8YV2nxDgO!9N3mZbM#+r#A`4!Vnwi}CI8$qbf^tAVsbKxGOI#GF2 zmeIAKO_kqcOVqXuVlm52d`T{RRpWG$*=$ZEc5{u#mSj-IRd{XQWv4mKP6vzJrxd^vA zM>eLxh(cMNu8D?`(&B3!D2c{R)sws2q*95AU1_uvHyy-4{qwRfUD@ zYdDK%GF(q0FWSh+v%>p7M0{{OjzWNHiwbHUP`P(u)`y#cigrCTlEm{b?BW#TY`Xi#anCBfPMrqw8kMuWhACP9U84 z7N>$eSpFdU;%0Z4>DLqv4}5KYp4U!XLL3G4xfP?D5F6z3WZc`|XL0fCof)69cU>qj z(dy{Tw6gn@_xe83+_>77ncI)=t+j86O3Xzs%C9T&E)&)wSweLWR<2Ld`o>&YQ;8bH z7v!3_BYp_B!Sq7)o1PL)7FCTuYs=(CUX*n5PG6)^$!V)UM}v7SylkAmvNVFj24twbZ-=01i`Y*Y0kwYrw4B?el4K+ z;$hgEni`h`3@EB33$9R77*UTkN#Ev8(Z{8#0^2bGg-zv)$}Z(|%8TgDAM zLU$A=D@%mxiJ``?;+URjb=pWoYsCESC_Cf4sfm`c1Nd z$uHzzluYN% zuRC*p{6P6#@aR}oVr;d|tU?yZL0!(PY?Y_wOrJt6FC;dVX_dt1C4AROc#vICUEE1X z^+EC%;ew5XHivH~x5OPr`LX_(HwqV*C{KYZli%wgNv824>&e9z*-ld0)i(8~cGXKl zV)8s;yc3cZ?K~M5tQH$~1GpKhD2YV52mOF*gW-)L1`_}4s&FyM`sAe-e+|LwCw9MS zXF&2I47i+HXD*wnb$>* z6Vz}5w?-&5-xR0W!L#wq2e%obN{CSnW;9Y>s5_pwvYle>VvuxT<`gl;h^=O?itFyb zhBs*~f=~Q5v@YJZOU*q3wMCpAnI}cz^35l)zALX)-3U*3xv)3*S!lAbYv;FW3?9G! z#p~;Z6lL<_@4M2Uz>*)9#(c5@bgzk$R8;oOKD7?8O^K>Pp&L4Y_BvbgJ=&`{qagf+| z!DM|2LI4guHl7ol? zyl(?>a@H%8KZWQMYK2%oi_gT2o0baQoMo~j{L6M!KGrVJ2&1llSo&|e9NUv{Iq9!T ze@}!bCx7-}eH)`3xZN0uV;XOPqJoKX$bNJEeFT$($W9v$j|TG#vk%fwA^JKR=a^V> zgbcON`$geEQfw$}R9v7jNRP5t7HT}oNuPnLZu9aoIwz^zyTym?3`t>(Wzgl5?4obl@Y%>MUQVX=3I#ekZII zP6B(&ww8>=|NZ~`zx-eS$Nz~I!DRC5YV-$S8O=(_AoBJG=IkgZ=-xZ&O1x`OEt7NM z8c^Dcv>y00yadNvc%Oz$C5NL$=nA#&NV;2KcP1asdTpM@T!BX5$yu|Ixp_z8F@sdz z{OKo&MCO|$2XA?!9N@mc02ORFQ9vAOhJI&?PRe=k{c^kns)cNc8S^Jorj*h7u0mTk zPD2c`cJ7}y)>|f3KKRl%pCjS*B>JHK|6QHUt}It_oxh0vlbKcLUXo4GFbua1!v-kB zPvMCMeq#>=zlJrECEIFs(`=IGR8{8wi3kn$u?89?P{XQvvL&2`T@NW>Zl~FWR4nO3CV$5C1PQ*vrgJesc(sx6UiHRC-@Z8 z%E?|T-3%jhq(rpAnV`M&G zQ>98^B#H3=n&hE&ktt5leQ(t@@?lB&Jci%luB8mjlj87z=S;mngdmN56SpzbH?x20}R!FFY?W1v&O zfs*KHkkT7E8)1SELAObNE!+E;i_Anad~OuIxX2t6@^50bnX%on# zyyA93ofL|c$!LY56WI!{576BS3dIsb#!`etX6u9Z0yk?+Jz#?7#$_9a&vkj{#)qDK zow~G==RzvSG|)mMBbAJIC1k)b@^-B7@Y&D=Qwm3MyxnmB6!FF)H66RKmQe<_6VY*m zk|yn${<*L;RfxqW$p#lE`&Maf;!-HFVxG>Dsl79AnKTB~zHspiL-)0uy&(1#PCv%K!Sq66N6iUI%fiwPl9O0>EAQn~+o<#>a17jbcA&>V)gdx<4BoRZ z`?ubSVQ6j)-`RZ9tyAk4PQCatl_(o@CmoK25Rr)7(Xy}N&`va?9NVJKj*cjdDD0cz z{f+4-2qY3#61ST|U)ywp>IcNI$q{_l)OE~U3QJvi!+cF_Ahw*l7T-_;2aOor$ zhVB$Sm%zaIOu=ki@ku{E19o9toxFp!!qAf{J9a$@9pt!icH_Pa?_=`AH{bG?KmN!M zfARflbt3ckfB!dJGbk=Aa@7Qqi$gJp}$Hb3{_Y2*2{Az?`Y+iiVzvb~v zVMrqY01LrML_t(Fu{-2@<;VZ}eq)Z46cA03-54{GZ1P|J^2cRV%aa%tl}QujHtaLnX9ETCM$VN> zPcjE$lh8sIXk(xPNJdjMFD=^y!IR#FlHseDF|49K#3$^r}V8cCt zlPNdq9Q4si+nu3}Y67|==A_~f3r#1oT0a>Ibmo|B$G#k|<04>5vNl877ka!v6G~vJ z-0v9~K_n&3%HUk$&>Xbi66Gz{GJ9JV3yVy4rNv3z3f_&ON(lio&JZ?hj3XmsubGwl2W6Pj*UGlBNu+S7g`7uZ?T6q{KWqEb~)&QGPx-e)vkJa3y6k{~VPb z3*me(Y4xGoSUWkW0x@nVi(za0z$ zW?TL@t<=(p>pM(exFiyeYvOiKNdMex^-R~-LQ+d`z9-7vhz@zr_*|OGLgI{*eP1Dg zR@Sg6d~mt4MaPci+O2%y)qf`H8)Kf_y3&QvTwW=`N(AY*UF_VN0E@|7-HsWwqxx_}&7(XO)Hs5e5iJKSkfm{y+*ABvTi8 zIOl{%tlERm!jiTBTCzVRujjukEOTG~6-&5&#$DN5dO^~X^QkaDpB^iRKva1=PX6xi z{wKfu`YSmS?to2Zfoum&KX4Cfyb+_5+oQw2G2(^f13xE{glHFgWM21#JSV<8KmX|` zE-Je?WCy#U{9iZ<`45@17)=l`JiB&eVO-`c+Z$mp=?UY z&hu2HuEdWUi8cqCgvgol>O2_K0>4;YC^uWFj5LGMknPUQKcNFe4ib)}30BbLE5vTA zXl%rM&@Fh|?zCTjCi=jch#sVLq2ntif5OP9IxdOZTjH1hX9$(#iN-}v1toYqVLu%I z6QtV)*Ts}Xk;*xr9QO@B$3KH&G$xk`yUt|}v`&pTuJON!^o1FJ;^qfGcPt)m5Iql9s^THM`7j_Iduhmvt|Bv7$i7mVrnq+AlgCRmc^vy%GNswOsOlV z&Q4S^NBwj|kk=$W60&_-KQ5!*HoC6^E2L0-U=&D>&dKbVd<$$Uok4O(&Wh}l(VeIh zIV;a{@>MowKUY3nIe4C(Titl|#=h01IMP9`iP6`!V)`V95)L{CV}}$wJwLJG?0&=M z#kB=0$Bxm6yu&$>ELdyouDp%}&$&da<~(BKkV&M0j61{!Yz4i9D>Y;)O2M=M58MmV zCygKv#dl*4BSxlZU#H`&3_B4Q^ks}~&}N2l;SQk*OE07^d&?+FJqNJ~?aa(@ltzlm zGv1N-4q3iziVa@F$SE%_d7euOw001DoI;dNcxDH#v4SNmCCc;SgUOyjX2m10aT4Xg zE`zax)<^COZ!J6O1DCNfq;)d9!M>rE@%};c;K&C}=4UH72-!&agnno0lPQ^q0VIwC zXogEA8Jzwi%W}*<9>I1@q6d0x#Q9>djIC$?V80do(J*A4;4wIdP_%PyijGE}H_q#4 zT<^5;V4oXTzKAgxSEZJBW`bywr>&OUqoSkXVrVP$w3H23IA5xAetw5XS6WOg52Eep z9;ic4h0+@)&Lok6HeM*omX{~2lOZ|TmSDgYJ>sa`2|tf+mhPJbput0_TE& z;DsDZnJw|o^c4_x0i{FK!R!X3P^xh1Tw8W)WCVR*r@)*A9euT5nwaGzjSaI&&q955 z{H9!|eEN}IsBT<5(D;^^9}pX5p1dqbnsGBEb^3>(6{ zaE{MWx7$j^lv1&4qW&b(ommFa45BVEGJ$%6JaC_+{XnM@cF_p#axgbr^kFkvY~+05 z=N-#WrsefEOogq+x|2#FPv?q@k``O!dP3PJG$oD-*NYYjG3D|X<(7%ZU?gx?+#0=S zvR=%R$=Bj1Uh@|$z0t;#yWSXO5M$7;ja<%!Z@wnY6v<9_A*^FK(i4{$n-5NcFG;!j zShAo2FPTsSyEbAsvMEgyxt~i3{-9e$YBZ7%ob& z1#WEvb_ywEqSPXh`vI)%;MaycJBDjTFaDQQ~lGBq}H{=@NmFs$Ea--P?Z|UHS6B~{G+Q_-#Izct1=#tH>JfOyFw^-UDyG6C_Qy%pH$x%w(`$vEWPTs4YIoOKmPi^)30^M zIyXfxM>j`Y8By4@;|XHi$mxVzfhSO%74@f-BN09z390GQ1R7|Gbvf)A+0ydScxIZd zsF!yk-i*sD+Zap)bi?a|+RJLk3ZDSX!swGkqUFY}8<%{L>|1<37BpXsiJ+Pg4XXlsi`EiuX(h*#&o7YKznf zSE5!*=n@@|!ItjKeSW_6CYNp8_YVCHPcMdVNDOk$lucHVMxlf;!YG;%WxcI;qx6Iy zncNyJgcM4nQ*H@;IhMgKErs`$(e1)hA$40I1^d9bGvvuP`o<}pu)(}Ga-L8I(S&Y7 zsuQ!iSp5pAIb9Zh?nUrcHd;SXl&Y&ZC<*I{*n@l8cn#QTm;~X4loN9_l)~-y$=)Y1 zlW;h&XF>vdZ`{87ma}&RM^j-BV@$zoPz0{$;vyQ$HJ*In=Rqnf-l*qEeK_yg`I^^y z@Zp`Rg)5w!RUYx;OSy5*Ao+#dEB;~Vo)?_Ql|;qb$=EVG2}{P9#y&SJjTPY(O84M6 cmJs}(0BsNKT+_&^y8r+H07*qoM6N<$f=NJ;egFUf From 745042dffa4826392c9d4f8599412c113210b2f7 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sun, 28 May 2017 15:55:58 -0400 Subject: [PATCH 31/65] Fix CMP0050 compilation warnings --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b809b3c55..e508c423c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ IF(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) IF (CMAKE_MAJOR_VERSION GREATER 2) CMAKE_POLICY(SET CMP0026 OLD) + CMAKE_POLICY(SET CMP0050 OLD) ENDIF() ENDIF(COMMAND CMAKE_POLICY) From 1eef218aeb91fa8f1f744a651fb26f350be1c9d2 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Mon, 29 May 2017 15:53:06 +0200 Subject: [PATCH 32/65] fix export project for sampletracks with vst effects on it (#3571) * fix export project for sampletracks with vst effects on it * removed playTriggered Signal --- src/tracks/SampleTrack.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index af1060703..8624497d4 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -71,6 +71,8 @@ SampleTCO::SampleTCO( Track * _track ) : { connect( timeLine, SIGNAL( positionMarkerMoved() ), this, SLOT( playbackPositionChanged() ) ); } + //playbutton clicked or space key / on Export Song set isPlaying to false + connect( Engine::getSong(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackPositionChanged() ) ); //care about loops connect( Engine::getSong(), SIGNAL( updateSampleTracks() ), this, SLOT( playbackPositionChanged() ) ); //care about mute TCOs @@ -79,11 +81,6 @@ SampleTCO::SampleTCO( Track * _track ) : connect( getTrack()->getMutedModel(), SIGNAL( dataChanged() ),this, SLOT( playbackPositionChanged() ) ); //care about TCO position connect( this, SIGNAL( positionChanged() ), this, SLOT( updateTrackTcos() ) ); - //playbutton clicked or space key - if( gui ) - { - connect( gui->songEditor(), SIGNAL( playTriggered() ), this, SLOT( playbackPositionChanged() ) ); - } switch( getTrack()->trackContainer()->type() ) { From 4ff091fd6fdf9f7b5a23e7d40550869fd7d39f61 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Tue, 30 May 2017 01:40:05 +0900 Subject: [PATCH 33/65] Fix loop condition in project rendering (#3105, #2030) (#3576) * Change loop condition in ProjectRenderer::run() * Remove Song::isExportDone() --- src/core/ProjectRenderer.cpp | 5 +++-- src/core/Song.cpp | 23 ----------------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 5dee4ed16..07dccc6ce 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -172,10 +172,11 @@ void ProjectRenderer::run() m_progress = 0; std::pair exportEndpoints = Engine::getSong()->getExportEndpoints(); tick_t startTick = exportEndpoints.first.getTicks(); - tick_t lengthTicks = exportEndpoints.second.getTicks() - startTick; + tick_t endTick = exportEndpoints.second.getTicks(); + tick_t lengthTicks = endTick - startTick; // Continually track and emit progress percentage to listeners - while( Engine::getSong()->isExportDone() == false && + while( exportPos.getTicks() < endTick && Engine::getSong()->isExporting() == true && !m_abort ) { diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 1af2dc2f3..babc391f3 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -464,29 +464,6 @@ void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fp } } -bool Song::isExportDone() const -{ - if ( m_renderBetweenMarkers ) - { - return m_exporting == true && - m_playPos[Mode_PlaySong].getTicks() >= - m_playPos[Mode_PlaySong].m_timeLine->loopEnd().getTicks(); - } - - if( m_exportLoop ) - { - return m_exporting == true && - m_playPos[Mode_PlaySong].getTicks() >= - length() * ticksPerTact(); - } - else - { - return m_exporting == true && - m_playPos[Mode_PlaySong].getTicks() >= - ( length() + 1 ) * ticksPerTact(); - } -} - std::pair Song::getExportEndpoints() const { if ( m_renderBetweenMarkers ) From 74790c0c45ee9693d8b0385713f35e0261db3db4 Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 29 May 2017 19:26:43 +0200 Subject: [PATCH 34/65] Update Discord link to point to #rules (#3588) We shuffled the channels around a bit and #welcome got deleted. #rules is what we want to link to now. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbd8cb689..661b13121 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build status](https://img.shields.io/travis/LMMS/lmms.svg?maxAge=3600)](https://travis-ci.org/LMMS/lmms) [![Latest stable release](https://img.shields.io/github/release/LMMS/lmms.svg?maxAge=3600)](https://lmms.io/download) [![Overall downloads on Github](https://img.shields.io/github/downloads/LMMS/lmms/total.svg?maxAge=3600)](https://github.com/LMMS/lmms/releases) -[![Join the chat at Discord](https://img.shields.io/badge/chat-on%20discord-7289DA.svg)](https://discord.gg/PHcQDx3) +[![Join the chat at Discord](https://img.shields.io/badge/chat-on%20discord-7289DA.svg)](https://discord.gg/3sc5su7) [![Localise on transifex](https://img.shields.io/badge/localise-on_transifex-green.svg)](https://www.transifex.com/lmms/lmms/) What is LMMS? From 9bdc0119049f41d0859c26e7145b5c8a36446eee Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Tue, 30 May 2017 17:04:00 +0200 Subject: [PATCH 35/65] Fix export - double dialog windows on writing over existing file (#3526) * Fix export - double dialog windows on writing over existing file * Case sensitivity --- src/core/Song.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/Song.cpp b/src/core/Song.cpp index babc391f3..b776b531f 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1337,13 +1337,14 @@ void Song::exportProject( bool multiExport ) efd.setWindowTitle( tr( "Select file for project-export..." ) ); } + QString suffix = "wav"; + efd.setDefaultSuffix( suffix ); efd.setAcceptMode( FileDialog::AcceptSave ); - if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() ) { - QString suffix = ""; + QString exportFileName = efd.selectedFiles()[0]; if ( !multiExport ) { @@ -1355,19 +1356,18 @@ void Song::exportProject( bool multiExport ) // Get first extension from selected dropdown. // i.e. ".wav" from "WAV-File (*.wav), Dummy-File (*.dum)" suffix = efd.selectedNameFilter().mid( stx + 2, etx - stx - 2 ).split( " " )[0].trimmed(); + exportFileName.remove( "." + suffix, Qt::CaseInsensitive ); if ( efd.selectedFiles()[0].endsWith( suffix ) ) { - suffix = ""; + if( VersionedSaveDialog::fileExistsQuery( exportFileName + suffix, + tr( "Save project" ) ) ) + { + exportFileName += suffix; + } } } } - if( VersionedSaveDialog::fileExistsQuery( exportFileName + suffix, - tr( "Save project" ) ) ) - { - exportFileName += suffix; - } - ExportProjectDialog epd( exportFileName, gui->mainWindow(), multiExport ); epd.exec(); } From 53772c052ba8d0de1bb82b22249895e4737cfdf6 Mon Sep 17 00:00:00 2001 From: Umcaruje Date: Wed, 31 May 2017 16:19:58 +0200 Subject: [PATCH 36/65] Allow WidgetTab to use artwork tabs (@StCyr) (#3569) * First version of artwork tabs for the InstrumentTrackWindow. This version can only display & manage artwork tabs, which breaks the InstrumentSoundShapingView as it still uses text tabs. I'm planing to improve this implementation to let these artwork tabs fall back to text mode when no artwork is given. This would solve the problem of the InstrumentSoundShapingView. * Second version of artwork tabs for the InstrumentTrackWindow. This version will draw an artwork tab when the TabWidget::addTab function is given a pixmapName. Otherwise, when pixmapName is NULL, it will fall back drawing a text tab. * Created artwork for the artwork tabs. * 1st PoC for autosizeable artwork tabs. * TabWidget is 20 pixels tall when it's going to display artwork tabs. * Added tooltip support for the TabWidget class. Atm, tooltips are simply tabs' name. * Imported artworks from RebeccaDeField * Reverted to 12px tall TabWidget * Fine tuning for the positioning of artwork tabs: Take into account the caption 'space. * New artwork for the ENV/LFO tab (has now an ADSR-based look) * 1) Tabs in TabWidget class have now a "tooltip" attribute. So that they can now show more meaningfull information then simply the tab's name. 2) Fixed the compilation problem with QT5 * Fine tuning the positioning of highlighted artwork tabs. * Fixed an issue in TabWidget's artwork tabs autosize function that makes gdb crash with SIGFPE. * TabWidget is 17 pixels tall when it's going to display artwork tabs. * Removed underscore prefix for function parameters as coding convention has changed. (Request at https://github.com/LMMS/lmms/pull/2599/files/dccf9f411996c8c866ff1086b65f15020ef08cd9#r61165005) Cyrille * Removed background gradient for TabWidget as LMMS is going to a more flat design. Cyrille * Increased the graphical TabWidget's height by 2 pixels for eye-candy. The InstrumentTrackWindow's height has been increased by the same amount. Cyrille * Removed gradient in GrouBox widgets as LMMS is going for a more flattened design. Cyrille * Made the background of TabWidget themeable Cyrille * The highlighting color for a TabWidget'selected tab is now themeable. * Made TabWidget's Title text and tab text themeable. * Added a darker background to the TabWidget's tab bar. * Further flatened the design of TabWidget * Flatened the design of the GroupBox widget * Fine tuning the placement of TabWidgets' highlighting background + some code cleaning in TabWidgets * Made the TabWidget's title background and borders themeable * TabWidget - Artwork tabs: Do not change the icon color when it is highlighted * TabWidget: Made the artworks' color themeable * Adapted format to follow LMMS coding conventions * Some more blank spaces to tabs translation to comply with LMMS coding standards. * Some more blank spaces to tabs translation to comply with LMMS coding standards. * Revert "TabWidget: Made the artworks' color themeable" This reverts commit 5b162c07e2ee26796b6e9408364eba19142acd71. Conflicts: src/gui/widgets/TabWidget.cpp Reason: Artwork's color themeability had the side-effect that it removed the artworks' alpha channel, thus making them ugly. * Made GroupBox's background color themeable * Update background color, only use one set of images * Use name as tooltip, more descriptive names * Update icons and colors * more things * formatting fixes * Remove update() from constructor --- data/themes/default/env_lfo_tab.png | Bin 0 -> 255 bytes data/themes/default/func_tab.png | Bin 0 -> 179 bytes data/themes/default/fx_tab.png | Bin 0 -> 292 bytes data/themes/default/midi_tab.png | Bin 0 -> 170 bytes data/themes/default/misc_tab.png | Bin 0 -> 188 bytes data/themes/default/plugin_tab.png | Bin 0 -> 210 bytes data/themes/default/style.css | 13 + include/GroupBox.h | 2 +- include/TabWidget.h | 50 ++- src/gui/widgets/EffectRackView.cpp | 2 +- src/gui/widgets/GroupBox.cpp | 54 +--- .../widgets/InstrumentSoundShapingView.cpp | 3 +- src/gui/widgets/TabWidget.cpp | 296 +++++++++++++----- src/tracks/InstrumentTrack.cpp | 16 +- 14 files changed, 287 insertions(+), 149 deletions(-) create mode 100644 data/themes/default/env_lfo_tab.png create mode 100644 data/themes/default/func_tab.png create mode 100644 data/themes/default/fx_tab.png create mode 100644 data/themes/default/midi_tab.png create mode 100644 data/themes/default/misc_tab.png create mode 100644 data/themes/default/plugin_tab.png diff --git a/data/themes/default/env_lfo_tab.png b/data/themes/default/env_lfo_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..8916ea44a1af88f64c10fa6052ac9ae3dfa43e6e GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(m4E{-7@ z6PNa#=5=roalP+jvVc=iDxg7Xi^9czttkr_c{?%=T#8C6%yBdNICDm!d4BQv-R@8P z8`MPZG#at6L^P_fu*_(*U}1UDctSkpOm6K#rW+OhWk$E^7EFmdKI;Vst0D8DqVE_OC literal 0 HcmV?d00001 diff --git a/data/themes/default/func_tab.png b/data/themes/default/func_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..8776571cd21de849fb239deaf33ed4cfacc65505 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt%?gE{-7@ z6O$7bm?s=j{rLa?|Mfpii~{fLZeh}>Q+cBA=H`|sQLXWFqDZ}Bv*HtXhPl(Y|0I97 RxE`pN!PC{xWt~$(697a{HYET6 literal 0 HcmV?d00001 diff --git a/data/themes/default/fx_tab.png b/data/themes/default/fx_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..58fbcb9bd5968c506db94da0957948a02a5f001c GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&5ZT^vI+ zCLW!1miLeWPwW163o$W=r#%m5#0m(dTM64MZ}8ggpu@o2;w)RRIP4*de^<)>Bj>88 zJ=E`DHrThdL^kC$HP(j&)CT3XaS zz4)$avGXH!*8asWL{A;O*xSwje!FLu))}3Unl|MGPxCqMp0DI)Fo#=~}U&Kt<}FE{-7@ z6O$7bu>5If3OX|5#MYb;<&9E_j=eIxH;$}GfBfK@hZzHd1)uu+N!yrL0<|!By85}S Ib4q9e03mZQXaE2J literal 0 HcmV?d00001 diff --git a/data/themes/default/misc_tab.png b/data/themes/default/misc_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..efb0d8f411da0b4502e4692382ca80270cab3a1f GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-0GE{-7@ z6W^Xv~}U&Kt=wZE{-7@ z6VFcYs~LXFM5P^t%AjNr)4Ir`<#NN6{^a1?=^YsHAy9^qMFNdTd_s9 xjnJ!YTn#4NdDc-HyXJ466l)uGQ`K%ELyG?nyE_LKmIIAu@O1TaS?83{1ONdYJs|)9 literal 0 HcmV?d00001 diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 035ed7a5f..a4fd6abd9 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -136,6 +136,19 @@ PianoRoll { qproperty-textShadow: #fff; } +TabWidget { + background-color: #262b30; + qproperty-tabText: rgba(255, 255, 255, 180); + qproperty-tabTitleText: #fff; + qproperty-tabSelected: #323940; + qproperty-tabBackground: #181b1f; + qproperty-tabBorder: #181b1f; +} + +GroupBox { + background-color: #262b30; +} + /* main toolbar oscilloscope - can have transparent bg now */ VisualizationWidget { diff --git a/include/GroupBox.h b/include/GroupBox.h index 6b4a1e98b..39d9a522a 100644 --- a/include/GroupBox.h +++ b/include/GroupBox.h @@ -57,7 +57,7 @@ public: protected: virtual void mousePressEvent( QMouseEvent * _me ); - virtual void resizeEvent( QResizeEvent * _re ); + virtual void paintEvent( QPaintEvent * _pe ); private: diff --git a/include/TabWidget.h b/include/TabWidget.h index f4888f0cc..402d7c9cc 100644 --- a/include/TabWidget.h +++ b/include/TabWidget.h @@ -29,25 +29,47 @@ #include #include +const int TEXT_TAB_HEIGHT = 14; +const int GRAPHIC_TAB_HEIGHT = 17; class TabWidget : public QWidget { Q_OBJECT public: - TabWidget( const QString & _caption, QWidget * _parent ); + TabWidget( const QString & _caption, QWidget * _parent, bool usePixmap = false ); virtual ~TabWidget(); - void addTab( QWidget * _w, const QString & _name, int _idx = -1 ); + void addTab( QWidget * w, const QString & name, const char *pixmap = NULL, int idx = -1 ); - void setActiveTab( int _idx ); + void setActiveTab( int idx ); + + int findTabAtPos( const QPoint *pos ); inline int activeTab() const { return( m_activeTab ); } + // Themeability + Q_PROPERTY( QColor tabText READ tabText WRITE setTabText) + Q_PROPERTY( QColor tabTitleText READ tabTitleText WRITE setTabTitleText) + Q_PROPERTY( QColor tabSelected READ tabSelected WRITE setTabSelected) + Q_PROPERTY( QColor tabBackground READ tabBackground WRITE setTabBackground) + Q_PROPERTY( QColor tabBorder READ tabBorder WRITE setTabBorder) + + QColor tabText() const; + void setTabText( const QColor & c ); + QColor tabTitleText() const; + void setTabTitleText( const QColor & c ); + QColor tabSelected() const; + void setTabSelected( const QColor & c ); + QColor tabBackground() const; + void setTabBackground( const QColor & c ); + QColor tabBorder() const; + void setTabBorder( const QColor & c ); protected: + virtual bool event( QEvent * event ); virtual void mousePressEvent( QMouseEvent * _me ); virtual void paintEvent( QPaintEvent * _pe ); virtual void resizeEvent( QResizeEvent * _re ); @@ -57,16 +79,26 @@ protected: private: struct widgetDesc { - QWidget * w; // ptr to widget - QString name; // name for widget - int nwidth; // width of name when painting + QWidget * w; // ptr to widget + const char * pixmap; // artwork for the widget + QString name; // name for widget + int nwidth; // width of name when painting (only valid for text tab) } ; typedef QMap widgetStack; widgetStack m_widgets; - int m_activeTab; - QString m_caption; - quint8 m_tabheight; + + int m_activeTab; + QString m_caption; // Tab caption, used as the tooltip text on icon tabs + quint8 m_tabbarHeight; // The height of the tab bar + quint8 m_tabheight; // The height of the tabs + bool m_usePixmap; // true if the tabs are to be displayed with icons. False for text tabs. + + QColor m_tabText; // The color of the tabs' text. + QColor m_tabTitleText; // The color of the TabWidget's title text. + QColor m_tabSelected; // The highlighting color for the selected tab. + QColor m_tabBackground; // The TabWidget's background color. + QColor m_tabBorder; // The TabWidget's borders color. } ; #endif diff --git a/src/gui/widgets/EffectRackView.cpp b/src/gui/widgets/EffectRackView.cpp index 2fa25d575..b3f468d0e 100644 --- a/src/gui/widgets/EffectRackView.cpp +++ b/src/gui/widgets/EffectRackView.cpp @@ -39,7 +39,7 @@ EffectRackView::EffectRackView( EffectChain* model, QWidget* parent ) : ModelView( NULL, this ) { QVBoxLayout* mainLayout = new QVBoxLayout( this ); - mainLayout->setMargin( 0 ); + mainLayout->setMargin( 5 ); m_effectsGroupBox = new GroupBox( tr( "EFFECTS CHAIN" ) ); mainLayout->addWidget( m_effectsGroupBox ); diff --git a/src/gui/widgets/GroupBox.cpp b/src/gui/widgets/GroupBox.cpp index fbcf88ed7..158390bb5 100644 --- a/src/gui/widgets/GroupBox.cpp +++ b/src/gui/widgets/GroupBox.cpp @@ -41,8 +41,6 @@ GroupBox::GroupBox( const QString & _caption, QWidget * _parent ) : m_caption( _caption ), m_titleBarHeight( 11 ) { - updatePixmap(); - m_led = new PixmapButton( this, _caption ); m_led->setCheckable( true ); m_led->move( 3, 0 ); @@ -84,60 +82,22 @@ void GroupBox::mousePressEvent( QMouseEvent * _me ) -void GroupBox::resizeEvent( QResizeEvent * _ev ) +void GroupBox::paintEvent( QPaintEvent * pe ) { - updatePixmap(); - QWidget::resizeEvent( _ev ); -} + QPainter p( this ); - - -void GroupBox::updatePixmap() -{ - QColor bg_color = QApplication::palette().color( QPalette::Active, - QPalette::Background ); - QPixmap pm( size() ); - pm.fill( bg_color/*.dark( 132 )*/ ); - - QPainter p( &pm ); + // Draw background + p.fillRect( 0, 0, width() - 1, height() - 1, p.background() ); // outer rect - p.setPen( bg_color.dark( 150 ) ); + p.setPen( p.background().color().dark( 150 ) ); p.drawRect( 0, 0, width() - 1, height() - 1 ); - // brighter line at bottom/right - p.setPen( bg_color.light( 150 ) ); - p.drawLine( width() - 1, 0, width() - 1, height() - 1 ); - p.drawLine( 0, height() - 1, width() - 1, height() - 1 ); - - // draw groupbox-titlebar - QLinearGradient g( 0, 0, 0, m_titleBarHeight ); - g.setColorAt( 0, bg_color.darker( 250 ) ); - g.setColorAt( 0.1, bg_color.lighter( 120 ) ); - g.setColorAt( 1, bg_color.darker( 250 ) ); - p.fillRect( 2, 2, width() - 4, m_titleBarHeight, g ); - // draw line below titlebar - p.setPen( bg_color.dark( 400 ) ); - p.drawLine( 1, m_titleBarHeight + 1, width() - 3, m_titleBarHeight + 1 ); + p.fillRect( 1, 1, width() - 2, m_titleBarHeight + 1, p.background().color().darker( 150 ) ); - // black inner rect - p.drawRect( 1, 1, width() - 3, height() - 3 ); - - - //p.setPen( QColor( 255, 255, 255 ) ); + // draw text p.setPen( palette().color( QPalette::Active, QPalette::Text ) ); p.setFont( pointSize<8>( font() ) ); p.drawText( 22, m_titleBarHeight, m_caption ); - - QPalette pal = palette(); - pal.setBrush( backgroundRole(), QBrush( pm ) ); - setPalette( pal ); } - - - - - - - diff --git a/src/gui/widgets/InstrumentSoundShapingView.cpp b/src/gui/widgets/InstrumentSoundShapingView.cpp index 20eaaba5e..aa64b5596 100644 --- a/src/gui/widgets/InstrumentSoundShapingView.cpp +++ b/src/gui/widgets/InstrumentSoundShapingView.cpp @@ -76,7 +76,8 @@ InstrumentSoundShapingView::InstrumentSoundShapingView( QWidget * _parent ) : { m_envLfoViews[i] = new EnvelopeAndLfoView( m_targetsTabWidget ); m_targetsTabWidget->addTab( m_envLfoViews[i], - tr( InstrumentSoundShaping::targetNames[i][0].toUtf8().constData() ) ); + tr( InstrumentSoundShaping::targetNames[i][0].toUtf8().constData() ), + NULL ); } diff --git a/src/gui/widgets/TabWidget.cpp b/src/gui/widgets/TabWidget.cpp index 8048ecc0e..6fde217c1 100644 --- a/src/gui/widgets/TabWidget.cpp +++ b/src/gui/widgets/TabWidget.cpp @@ -2,7 +2,7 @@ * TabWidget.cpp - tabwidget for LMMS * * Copyright (c) 2005-2014 Tobias Doerffel - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -27,53 +27,71 @@ #include #include +#include +#include +#include #include "gui_templates.h" +#include "embed.h" - - -TabWidget::TabWidget( const QString & _caption, QWidget * _parent ) : - QWidget( _parent ), +TabWidget::TabWidget( const QString & caption, QWidget * parent, bool usePixmap ) : + QWidget( parent ), m_activeTab( 0 ), - m_caption( _caption ), - m_tabheight( _caption.isEmpty() ? 11: 10 ) + m_caption( caption ), + m_usePixmap( usePixmap ), + m_tabText( 0, 0, 0 ), + m_tabTitleText( 0, 0, 0 ), + m_tabSelected( 0, 0, 0 ), + m_tabBackground( 0, 0, 0 ), + m_tabBorder( 0, 0, 0 ) { + + // Create taller tabbar when it's to display artwork tabs + m_tabbarHeight = usePixmap ? GRAPHIC_TAB_HEIGHT : TEXT_TAB_HEIGHT; + + m_tabheight = caption.isEmpty() ? m_tabbarHeight - 3 : m_tabbarHeight - 4; + setFont( pointSize<8>( font() ) ); setAutoFillBackground( true ); - QColor bg_color = QApplication::palette().color( QPalette::Active, - QPalette::Background ). - darker( 132 ); + QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ). darker( 132 ); QPalette pal = palette(); pal.setColor( QPalette::Background, bg_color ); setPalette( pal ); + } - - TabWidget::~TabWidget() { } - - -void TabWidget::addTab( QWidget * _w, const QString & _name, int _idx ) +void TabWidget::addTab( QWidget * w, const QString & name, const char *pixmap, int idx ) { setFont( pointSize<8>( font() ) ); - widgetDesc d = { _w, _name, fontMetrics().width( _name ) + 10 } ; - if( _idx < 0/* || m_widgets.contains( _idx ) == true*/ ) + + // Append tab when position is not given + if( idx < 0/* || m_widgets.contains( idx ) == true*/ ) { - while( m_widgets.contains( ++_idx ) == true ) + while( m_widgets.contains( ++idx ) == true ) { } } - m_widgets[_idx] = d; - _w->setFixedSize( width() - 4, height() - 14 ); - _w->move( 2, 13 ); - _w->hide(); + // Tab's width when it is a text tab. This isn't correct for artwork tabs, but it's fixed later during the PaintEvent + int tab_width = fontMetrics().width( name ) + 10; + + // Register new tab + widgetDesc d = { w, pixmap, name, tab_width }; + m_widgets[idx] = d; + + // Position tab's window + w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + w->move( 2, m_tabbarHeight - 1 ); + w->hide(); + + // Show tab's window if it's active if( m_widgets.contains( m_activeTab ) ) { // make sure new tab doesn't overlap current widget @@ -85,15 +103,15 @@ void TabWidget::addTab( QWidget * _w, const QString & _name, int _idx ) -void TabWidget::setActiveTab( int _idx ) +void TabWidget::setActiveTab( int idx ) { - if( m_widgets.contains( _idx ) ) + if( m_widgets.contains( idx ) ) { int old_active = m_activeTab; - m_activeTab = _idx; + m_activeTab = idx; m_widgets[m_activeTab].w->raise(); m_widgets[m_activeTab].w->show(); - if( old_active != _idx && m_widgets.contains( old_active ) ) + if( old_active != idx && m_widgets.contains( old_active ) ) { m_widgets[old_active].w->hide(); } @@ -102,27 +120,74 @@ void TabWidget::setActiveTab( int _idx ) } - - -void TabWidget::mousePressEvent( QMouseEvent * _me ) +// Return the index of the tab at position "pos" +int TabWidget::findTabAtPos( const QPoint *pos ) { - if( _me->y() > 1 && _me->y() < 13 ) + + if( pos->y() > 1 && pos->y() < m_tabbarHeight - 1 ) { - int cx = ( ( m_caption == "" ) ? 4 : 14 ) + - fontMetrics().width( m_caption ); - for( widgetStack::iterator it = m_widgets.begin(); - it != m_widgets.end(); ++it ) + int cx = ( ( m_caption == "" ) ? 4 : 14 ) + fontMetrics().width( m_caption ); + + for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { - if( _me->x() >= cx && - _me->x() <= cx + ( *it ).nwidth ) + if( pos->x() >= cx && pos->x() <= cx + ( *it ).nwidth ) { - setActiveTab( it.key() ); - update(); - return; + return( it.key() ); } cx += ( *it ).nwidth; } } + + // Haven't found any tab at position "pos" + return( -1 ); +} + + +// Overload the QWidget::event handler to display tooltips (from https://doc.qt.io/qt-4.8/qt-widgets-tooltips-example.html) +bool TabWidget::event(QEvent *event) +{ + + if ( event->type() == QEvent::ToolTip ) + { + QHelpEvent *helpEvent = static_cast(event); + + int idx = findTabAtPos( & helpEvent->pos() ); + + if ( idx != -1 ) + { + // Display tab's tooltip + QToolTip::showText( helpEvent->globalPos(), m_widgets[idx].name ); + } + else + { + // The tooltip event doesn't relate to any tab, let's ignore it + QToolTip::hideText(); + event->ignore(); + } + + return true; + } + + // not a Tooltip event, let's propagate it to the other event handlers + return QWidget::event(event); +} + + +// Activate tab when clicked +void TabWidget::mousePressEvent( QMouseEvent * me ) +{ + + // Find index of tab that has been clicked + QPoint pos = me->pos(); + int idx = findTabAtPos( &pos ); + + // When found, activate tab that has been clicked + if ( idx != -1 ) + { + setActiveTab( idx ); + update(); + return; + } } @@ -133,7 +198,7 @@ void TabWidget::resizeEvent( QResizeEvent * ) for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { - ( *it ).w->setFixedSize( width() - 4, height() - 14 ); + ( *it ).w->setFixedSize( width() - 4, height() - m_tabbarHeight ); } } @@ -141,66 +206,77 @@ void TabWidget::resizeEvent( QResizeEvent * ) -void TabWidget::paintEvent( QPaintEvent * _pe ) +void TabWidget::paintEvent( QPaintEvent * pe ) { - setFont( pointSize<8>( font() ) ); QPainter p( this ); + p.setFont( pointSize<7>( font() ) ); - QColor bg_color = QApplication::palette().color( QPalette::Active, - QPalette::Background ); - QLinearGradient g( 0, 0, 0, m_tabheight ); - g.setColorAt( 0, bg_color.darker( 250 ) ); - g.setColorAt( 0.1, bg_color.lighter( 120 ) ); - g.setColorAt( 1, bg_color.darker( 250 ) ); + // Draw background + QBrush bg_color = p.background(); p.fillRect( 0, 0, width() - 1, height() - 1, bg_color ); - bool big_tab_captions = ( m_caption == "" ); - - p.setPen( bg_color.darker( 150 ) ); + // Draw external borders + p.setPen( tabBorder() ); p.drawRect( 0, 0, width() - 1, height() - 1 ); - p.setPen( bg_color.light( 150 ) ); - p.drawLine( width() - 1, 0, width() - 1, height() - 1 ); - p.drawLine( 0, height() - 1, width() - 1, height() - 1 ); - - p.setPen( QColor( 0, 0, 0 ) ); - p.drawRect( 1, 1, width() - 3, height() - 3 ); - - p.fillRect( 2, 2, width() - 4, m_tabheight, g ); - p.drawLine( 2, m_tabheight + 2, width() - 3, m_tabheight + 2); + // Draw tabs' bar background + p.fillRect( 1, 1, width() - 2, m_tabheight + 2, tabBackground() ); + // Draw title, if any if( ! m_caption.isEmpty() ) { - p.setPen( QColor( 255, 255, 255 ) ); + p.setFont( pointSize<8>( p.font() ) ); + p.setPen( tabTitleText() ); p.drawText( 5, 11, m_caption ); } // Calculate the tabs' x (tabs are painted next to the caption) int tab_x_offset = m_caption.isEmpty() ? 4 : 14 + fontMetrics().width( m_caption ); - QColor cap_col( 160, 160, 160 ); - if( big_tab_captions ) + // Compute tabs' width depending on the number of tabs (only applicable for artwork tabs) + widgetStack::iterator first = m_widgets.begin(); + widgetStack::iterator last = m_widgets.end(); + int tab_width = width(); + if ( first != last ) { - p.setFont( pointSize<8>( p.font() ) ); - cap_col = QColor( 224, 224, 224 ); + tab_width = ( width() - tab_x_offset ) / std::distance( first, last ); } - else - { - p.setFont( pointSize<7>( p.font() ) ); - } - p.setPen( cap_col ); - - for( widgetStack::iterator it = m_widgets.begin(); - it != m_widgets.end(); ++it ) + // Draw all tabs + p.setPen( tabText() ); + for( widgetStack::iterator it = first ; it != last ; ++it ) { - if( it.key() == m_activeTab ) + // Draw a text tab or a artwork tab. + if( m_usePixmap ) { - p.setPen( QColor( 32, 48, 64 ) ); - p.fillRect( tab_x_offset, 2, ( *it ).nwidth - 6, 10, cap_col ); + // Fixes tab's width, because original size is only correct for text tabs + ( *it ).nwidth = tab_width; + + // Get artwork + QPixmap artwork( embed::getIconPixmap( ( *it ).pixmap ) ); + + // Highlight active tab + if( it.key() == m_activeTab ) + { + p.fillRect( tab_x_offset, 0, ( *it ).nwidth, m_tabbarHeight - 1, tabSelected() ); + } + + // Draw artwork + p.drawPixmap(tab_x_offset + ( ( *it ).nwidth - artwork.width() ) / 2, 1, artwork ); } - p.drawText( tab_x_offset + 3, m_tabheight, ( *it ).name ); - p.setPen( cap_col ); + else + { + // Highlight tab when active + if( it.key() == m_activeTab ) + { + p.fillRect( tab_x_offset, 2, ( *it ).nwidth - 6, m_tabbarHeight - 4, tabSelected() ); + } + + // Draw text + p.drawText( tab_x_offset + 3, m_tabheight + 1, ( *it ).name ); + } + + // Next tab's horizontal position tab_x_offset += ( *it ).nwidth; } } @@ -208,13 +284,16 @@ void TabWidget::paintEvent( QPaintEvent * _pe ) -void TabWidget::wheelEvent( QWheelEvent * _we ) +// Switch between tabs with mouse wheel +void TabWidget::wheelEvent( QWheelEvent * we ) { - if (_we->y() > m_tabheight) + if( we->y() > m_tabheight ) + { return; + } - _we->accept(); - int dir = ( _we->delta() < 0 ) ? 1 : -1; + we->accept(); + int dir = ( we->delta() < 0 ) ? 1 : -1; int tab = m_activeTab; while( tab > -1 && static_cast( tab ) < m_widgets.count() ) { @@ -227,9 +306,62 @@ void TabWidget::wheelEvent( QWheelEvent * _we ) setActiveTab( tab ); } +// Return the color to be used to draw a TabWidget's title text (if any) +QColor TabWidget::tabTitleText() const +{ + return m_tabTitleText; +} +// Set the color to be used to draw a TabWidget's title text (if any) +void TabWidget::setTabTitleText( const QColor & c ) +{ + m_tabTitleText = c; +} +// Return the color to be used to draw a TabWidget's text (if any) +QColor TabWidget::tabText() const +{ + return m_tabText; +} +// Set the color to be used to draw a TabWidget's text (if any) +void TabWidget::setTabText( const QColor & c ) +{ + m_tabText = c; +} +// Return the color to be used to highlight a TabWidget'selected tab (if any) +QColor TabWidget::tabSelected() const +{ + return m_tabSelected; +} +// Set the color to be used to highlight a TabWidget'selected tab (if any) +void TabWidget::setTabSelected( const QColor & c ) +{ + m_tabSelected = c; +} +// Return the color to be used for the TabWidget's background +QColor TabWidget::tabBackground() const +{ + return m_tabBackground; +} + +// Set the color to be used for the TabWidget's background +void TabWidget::setTabBackground( const QColor & c ) +{ + m_tabBackground = c; +} + +// Return the color to be used for the TabWidget's borders +QColor TabWidget::tabBorder() const +{ + return m_tabBorder; +} + +// Set the color to be used for the TabWidget's borders +void TabWidget::setTabBorder( const QColor & c ) +{ + m_tabBorder = c; +} diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 80b5427b1..b847c85e6 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1411,8 +1411,8 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : generalSettingsLayout->addLayout( basicControlsLayout ); - m_tabWidget = new TabWidget( "", this ); - m_tabWidget->setFixedHeight( INSTRUMENT_HEIGHT + 10 ); + m_tabWidget = new TabWidget( "", this, true ); + m_tabWidget->setFixedHeight( INSTRUMENT_HEIGHT + GRAPHIC_TAB_HEIGHT - 4 ); // create tab-widgets @@ -1439,11 +1439,11 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : m_miscView = new InstrumentMiscView( m_track, m_tabWidget ); - m_tabWidget->addTab( m_ssView, tr( "ENV/LFO" ), 1 ); - m_tabWidget->addTab( instrumentFunctions, tr( "FUNC" ), 2 ); - m_tabWidget->addTab( m_effectView, tr( "FX" ), 3 ); - m_tabWidget->addTab( m_midiView, tr( "MIDI" ), 4 ); - m_tabWidget->addTab( m_miscView, tr( "MISC" ), 5 ); + m_tabWidget->addTab( m_ssView, tr( "Envelope, filter & LFO" ), "env_lfo_tab", 1 ); + m_tabWidget->addTab( instrumentFunctions, tr( "Chord stacking & arpeggio" ), "func_tab", 2 ); + m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 ); + m_tabWidget->addTab( m_midiView, tr( "MIDI settings" ), "midi_tab", 4 ); + m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 ); // setup piano-widget m_pianoView = new PianoView( this ); @@ -1617,7 +1617,7 @@ void InstrumentTrackWindow::updateInstrumentView() if( m_track->m_instrument != NULL ) { m_instrumentView = m_track->m_instrument->createView( m_tabWidget ); - m_tabWidget->addTab( m_instrumentView, tr( "PLUGIN" ), 0 ); + m_tabWidget->addTab( m_instrumentView, tr( "Plugin" ), "plugin_tab", 0 ); m_tabWidget->setActiveTab( 0 ); m_ssView->setFunctionsHidden( m_track->m_instrument->flags().testFlag( Instrument::IsSingleStreamed ) ); From afd22c8b74e23f6a89f2fac9ae596dd16979f4f7 Mon Sep 17 00:00:00 2001 From: Umcaruje Date: Wed, 31 May 2017 16:42:45 +0200 Subject: [PATCH 37/65] Bump version number for RC3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e508c423c..4577b42e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ SET(PROJECT_COPYRIGHT "2008-${PROJECT_YEAR} ${PROJECT_AUTHOR}") SET(VERSION_MAJOR "1") SET(VERSION_MINOR "2") SET(VERSION_RELEASE "0") -SET(VERSION_STAGE "rc2") +SET(VERSION_STAGE "rc3") SET(VERSION_BUILD "0") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_RELEASE}") IF(VERSION_STAGE) From 635b50bfb5d18bf8e30fc4361688c2721dc16349 Mon Sep 17 00:00:00 2001 From: Karmo Rosental Date: Wed, 31 May 2017 21:55:22 +0300 Subject: [PATCH 38/65] VeSTige opens correct folder (#3550) Open correct VST folder in previously saved projects with existing VeSTige instruments. --- plugins/GigPlayer/GigPlayer.cpp | 1 - plugins/sf2_player/sf2_player.cpp | 1 - plugins/vestige/vestige.cpp | 39 +++++++++---------------------- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index 266e93bad..14da95514 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -1066,7 +1066,6 @@ void GigInstrumentView::showFileDialog() types << tr( "GIG Files (*.gig)" ); ofd.setNameFilters( types ); - QString dir; if( k->m_filename != "" ) { QString f = SampleBuffer::tryToMakeAbsolute( k->m_filename ); diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index dbf919434..1ff7a5ae6 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -1080,7 +1080,6 @@ void sf2InstrumentView::showFileDialog() types << tr( "SoundFont2 Files (*.sf2)" ); ofd.setNameFilters( types ); - QString dir; if( k->m_filename != "" ) { QString f = SampleBuffer::tryToMakeAbsolute( k->m_filename ); diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index a2e5d2d1f..403d24bbd 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -41,6 +41,7 @@ #include "Mixer.h" #include "GuiApplication.h" #include "PixmapButton.h" +#include "SampleBuffer.h" #include "StringPairDrag.h" #include "TextFloat.h" #include "ToolTip.h" @@ -173,17 +174,6 @@ void vestigeInstrument::setParameter( void ) void vestigeInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - if( QFileInfo( m_pluginDLL ).isAbsolute() ) - { - QString f = QString( m_pluginDLL ).replace( QDir::separator(), '/' ); - QString vd = QString( ConfigManager::inst()->vstDir() ).replace( QDir::separator(), '/' ); - QString relativePath; - if( !( relativePath = f.section( vd, 1, 1 ) ).isEmpty() ) - { - m_pluginDLL = relativePath; - } - } - _this.setAttribute( "plugin", m_pluginDLL ); m_pluginMutex.lock(); if( m_plugin != NULL ) @@ -253,8 +243,7 @@ void vestigeInstrument::loadFile( const QString & _file ) { closePlugin(); } - - m_pluginDLL = _file; + m_pluginDLL = SampleBuffer::tryToMakeRelative( _file ); TextFloat * tf = TextFloat::displayMessage( tr( "Loading plugin" ), tr( "Please wait while loading VST-plugin..." ), @@ -268,6 +257,7 @@ void vestigeInstrument::loadFile( const QString & _file ) closePlugin(); delete tf; collectErrorForUI( VstPlugin::tr( "The VST plugin %1 could not be loaded." ).arg( m_pluginDLL ) ); + m_pluginDLL = ""; return; } @@ -613,29 +603,22 @@ void VestigeInstrumentView::openPlugin() { FileDialog ofd( NULL, tr( "Open VST-plugin" ) ); - QString dir; - if( m_vi->m_pluginDLL != "" ) - { - dir = QFileInfo( m_vi->m_pluginDLL ).absolutePath(); - } - else - { - dir = ConfigManager::inst()->vstDir(); - } - // change dir to position of previously opened file - ofd.setDirectory( dir ); - ofd.setFileMode( FileDialog::ExistingFiles ); - // set filters QStringList types; types << tr( "DLL-files (*.dll)" ) << tr( "EXE-files (*.exe)" ) ; ofd.setNameFilters( types ); + if( m_vi->m_pluginDLL != "" ) { - // select previously opened file - ofd.selectFile( QFileInfo( m_vi->m_pluginDLL ).fileName() ); + QString f = SampleBuffer::tryToMakeAbsolute( m_vi->m_pluginDLL ); + ofd.setDirectory( QFileInfo( f ).absolutePath() ); + ofd.selectFile( QFileInfo( f ).fileName() ); + } + else + { + ofd.setDirectory( ConfigManager::inst()->vstDir() ); } if ( ofd.exec () == QDialog::Accepted ) From 7be63741d3334f14980c58e673be58b1b2a84263 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 1 Jun 2017 02:13:59 -0400 Subject: [PATCH 39/65] Add more brew packages (#3595) * Add more brew packages --- .travis/osx..install.sh | 2 +- plugins/GigPlayer/CMakeLists.txt | 5 +++++ plugins/GigPlayer/PatchesDialog.h | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis/osx..install.sh b/.travis/osx..install.sh index 0fb8560eb..e1bdcac28 100644 --- a/.travis/osx..install.sh +++ b/.travis/osx..install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PACKAGES="cmake pkgconfig fftw libogg libvorbis libsndfile libsamplerate jack sdl stk portaudio node fltk" +PACKAGES="cmake pkgconfig fftw libogg libvorbis libsndfile libsamplerate jack sdl libgig libsoundio stk portaudio node fltk" if [ $QT5 ]; then PACKAGES="$PACKAGES homebrew/versions/qt55" diff --git a/plugins/GigPlayer/CMakeLists.txt b/plugins/GigPlayer/CMakeLists.txt index 24db813bd..88e15f87b 100644 --- a/plugins/GigPlayer/CMakeLists.txt +++ b/plugins/GigPlayer/CMakeLists.txt @@ -2,6 +2,11 @@ if(LMMS_HAVE_GIG) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${GIG_INCLUDE_DIRS}) + # Disable C++11 on Clang until gig.h is patched + IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + REMOVE_DEFINITIONS(-std=c++0x) + ENDIF() + # Required for not crashing loading files with libgig SET(GCC_COVERAGE_COMPILE_FLAGS "-fexceptions") add_definitions(${GCC_COVERAGE_COMPILE_FLAGS}) diff --git a/plugins/GigPlayer/PatchesDialog.h b/plugins/GigPlayer/PatchesDialog.h index c8bcce157..0836631ac 100644 --- a/plugins/GigPlayer/PatchesDialog.h +++ b/plugins/GigPlayer/PatchesDialog.h @@ -30,7 +30,6 @@ #include "LcdSpinBox.h" #include "GigPlayer.h" -#include #include #include From 445bfe9fa77be473431d91605b1d617d9ca46ad7 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 1 Jun 2017 16:03:12 -0400 Subject: [PATCH 40/65] VST plugin compilation fix in ubuntu 64bit. (#3593) When compiling on ubuntu 16.10 64bit, the VST compilation succeeds only when adding `-L/usr/lib/i386-linux-gnu/wine/` to the linker flags. --- plugins/vst_base/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index 9f7b821a9..a3ee56ab0 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -37,7 +37,7 @@ IF(LMMS_HOST_X86_64) # workaround for broken wineg++ in WINE 1.4 (shipped e.g. with Ubuntu Precise) EXEC_PROGRAM( ${WINE_CXX} ARGS "-v -m32 /dev/zero" OUTPUT_VARIABLE WINEBUILD_OUTPUT) IF("${WINEBUILD_OUTPUT}" MATCHES ".*x86_64-linux-gnu/wine/libwinecrt0.a.*") - SET(EXTRA_FLAGS ${EXTRA_FLAGS} -nodefaultlibs /usr/lib/i386-linux-gnu/wine/libwinecrt0.a -luser32 -lkernel32 -lgdi32) + SET(EXTRA_FLAGS ${EXTRA_FLAGS} -nodefaultlibs /usr/lib/i386-linux-gnu/wine/libwinecrt0.a -L/usr/lib/i386-linux-gnu/wine/ -luser32 -lkernel32 -lgdi32) ENDIF() #The following check works on Fedora systems IF("${WINEBUILD_OUTPUT}" MATCHES ".*lib64/wine/libwinecrt0.a.*") From 1f66f62ed76b85d8ccfdb1eb7ca9cca9595f5553 Mon Sep 17 00:00:00 2001 From: miketurn Date: Thu, 1 Jun 2017 21:28:09 -0400 Subject: [PATCH 41/65] Proper Case Renames (#3573) --- src/gui/widgets/ProjectNotes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/widgets/ProjectNotes.cpp b/src/gui/widgets/ProjectNotes.cpp index 209b89887..4e23349ad 100644 --- a/src/gui/widgets/ProjectNotes.cpp +++ b/src/gui/widgets/ProjectNotes.cpp @@ -67,7 +67,7 @@ ProjectNotes::ProjectNotes() : setupActions(); setCentralWidget( m_edit ); - setWindowTitle( tr( "Project notes" ) ); + setWindowTitle( tr( "Project Notes" ) ); setWindowIcon( embed::getIconPixmap( "project_notes" ) ); gui->mainWindow()->addWindowedWidget( this ); @@ -89,7 +89,7 @@ ProjectNotes::~ProjectNotes() void ProjectNotes::clear() { - m_edit->setHtml( tr( "Put down your project notes here." ) ); + m_edit->setHtml( tr( "Enter project notes here" ) ); m_edit->selectAll(); m_edit->setTextColor( QColor( 224, 224, 224 ) ); QTextCursor cursor = m_edit->textCursor(); From cee68c773ed62a195468178482bb0829261f7b31 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Fri, 2 Jun 2017 03:36:36 +0200 Subject: [PATCH 42/65] fixes position marker height on startup (#3587) Fixes position marker height on startup --- include/SongEditor.h | 5 +++++ src/gui/editors/SongEditor.cpp | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/SongEditor.h b/include/SongEditor.h index e8961b8bc..db4d7d781 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -81,6 +81,7 @@ public slots: void setEditModeSelect(); void updatePosition( const MidiTime & t ); + void updatePositionLine(); protected: virtual void closeEvent( QCloseEvent * ce ); @@ -152,6 +153,9 @@ public: SongEditor* m_editor; +protected: + virtual void resizeEvent( QResizeEvent * event ); + protected slots: void play(); void record(); @@ -162,6 +166,7 @@ protected slots: signals: void playTriggered(); + void resized(); private: QAction* m_addBBTrackAction; diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 699fa5eac..d110201c8 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -586,6 +586,14 @@ void SongEditor::updatePosition( const MidiTime & t ) +void SongEditor::updatePositionLine() +{ + m_positionLine->setFixedHeight( height() ); +} + + + + void SongEditor::zoomingChanged() { setPixelsPerTact( m_zoomLevels[m_zoomingModel->value()] * DEFAULT_PIXELS_PER_TACT ); @@ -697,6 +705,7 @@ SongEditorWindow::SongEditorWindow(Song* song) : zoomToolBar->addWidget( m_zoomingComboBox ); connect(song, SIGNAL(projectLoaded()), this, SLOT(adjustUiAfterProjectLoad())); + connect(this, SIGNAL(resized()), m_editor, SLOT(updatePositionLine())); } QSize SongEditorWindow::sizeHint() const @@ -705,6 +714,14 @@ QSize SongEditorWindow::sizeHint() const } + + +void SongEditorWindow::resizeEvent(QResizeEvent *event) +{ + emit resized(); +} + + void SongEditorWindow::play() { emit playTriggered(); From dd802686693c892364d75201881c0ee6a6fbc655 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Fri, 2 Jun 2017 17:15:44 -0400 Subject: [PATCH 43/65] Fix overzealous quoting (#3604) Fixes builds from directories with spaces --- data/locale/CMakeLists.txt | 2 +- plugins/vst_base/CMakeLists.txt | 3 ++- src/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/locale/CMakeLists.txt b/data/locale/CMakeLists.txt index 25de01e24..4de6d5ff7 100644 --- a/data/locale/CMakeLists.txt +++ b/data/locale/CMakeLists.txt @@ -31,7 +31,7 @@ FOREACH(_ts_file ${lmms_LOCALES}) COMMAND "${QT_LUPDATE_EXECUTABLE}" -locations none -no-obsolete -I ${CMAKE_SOURCE_DIR}/include/ ${LMMS_SRCS} ${LMMS_INCLUDES} ${LMMS_UIS} `find "\"${CMAKE_SOURCE_DIR}/plugins/\"" -type f -name '*.cpp' -or -name '*.h'` -ts "\"${_ts_file}\"" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) ADD_CUSTOM_TARGET(${_qm_target} - COMMAND "${QT_LRELEASE_EXECUTABLE}" "\"${_ts_file}\"" -qm "\"${_qm_file}\"" + COMMAND "${QT_LRELEASE_EXECUTABLE}" "${_ts_file}" -qm "${_qm_file}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) LIST(APPEND ts_targets "${_ts_target}") LIST(APPEND qm_targets "${_qm_target}") diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index a3ee56ab0..a9a808841 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -51,7 +51,8 @@ STRING(REPLACE " " ";" WINE_BUILD_FLAGS ${CMAKE_CXX_FLAGS} " " ${CMAKE_EXE_LINKE ADD_CUSTOM_COMMAND( SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp" COMMAND ${WINE_CXX} - ARGS "-I\"${CMAKE_BINARY_DIR}\"" "-I\"${CMAKE_SOURCE_DIR}/include\"" "-I\"${CMAKE_INSTALL_PREFIX}/include/wine/windows\"" "-I\"${CMAKE_INSTALL_PREFIX}/include\"" -I/usr/include/wine/windows "\"${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp\"" -ansi -mwindows -lpthread ${EXTRA_FLAGS} -fno-omit-frame-pointer ${WINE_BUILD_FLAGS} -o ../RemoteVstPlugin + ARGS -I${CMAKE_BINARY_DIR} -I${CMAKE_SOURCE_DIR}/include -I${CMAKE_INSTALL_PREFIX}/include/wine/windows -I${CMAKE_INSTALL_PREFIX}/include -I/usr/include/wine/windows ${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp -ansi -mwindows -lpthread ${EXTRA_FLAGS} -fno-omit-frame-pointer ${WINE_BUILD_FLAGS} -o ../RemoteVstPlugin + # Ensure correct file extension COMMAND sh -c "mv ../RemoteVstPlugin.exe ../RemoteVstPlugin || true" TARGET vstbase OUTPUTS ../RemoteVstPlugin diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32bdc1788..1b8f98ebd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,7 +46,7 @@ SET(lmms_EMBEDDED_RESOURCES "${CMAKE_SOURCE_DIR}/LICENSE.txt" "${CMAKE_SOURCE_DIR}/doc/CONTRIBUTORS") SET(LMMS_ER_H "${CMAKE_CURRENT_BINARY_DIR}/embedded_resources.h") -ADD_CUSTOM_COMMAND(OUTPUT "${LMMS_ER_H}" COMMAND "${BIN2RES}" ARGS ${lmms_EMBEDDED_RESOURCES} > "\"${LMMS_ER_H}\"" DEPENDS bin2res) +ADD_CUSTOM_COMMAND(OUTPUT "${LMMS_ER_H}" COMMAND "${BIN2RES}" ARGS ${lmms_EMBEDDED_RESOURCES} > "${LMMS_ER_H}" DEPENDS bin2res) # Paths relative to lmms executable FILE(RELATIVE_PATH LIB_DIR_RELATIVE "/${BIN_DIR}" "/${LIB_DIR}") From 6bb19f4fbd3de5e7c3b77c408e40378cbe74e7d6 Mon Sep 17 00:00:00 2001 From: liushuyu Date: Tue, 9 May 2017 21:49:16 +0800 Subject: [PATCH 44/65] update i18n strings --- data/locale/en.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/data/locale/en.ts b/data/locale/en.ts index 969de0ef0..d72aa2ce8 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -1974,10 +1974,6 @@ Right clicking will bring up a context menu where you can change the order in wh 32 Bit Float - - Please note that not all of the parameters above apply for all file formats. - - Quality settings @@ -2063,6 +2059,14 @@ Right clicking will bring up a context menu where you can change the order in wh Please make sure you have write permission to the file and the directory containing the file and try again! + + 24 Bit Integer + + + + Use variable bitrate + + Fader @@ -2238,6 +2242,18 @@ You can remove and move FX channels in the context menu, which is accessed by ri FX %1 + + Volume + + + + Mute + + + + Solo + + FxMixerView From 075d980d0d38ac361c9d9a847a0670e9e6aab7b2 Mon Sep 17 00:00:00 2001 From: liushuyu Date: Sat, 3 Jun 2017 22:44:18 +0800 Subject: [PATCH 45/65] Update i18n source strings (from stable-1.2) --- data/locale/en.ts | 112 +++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/data/locale/en.ts b/data/locale/en.ts index d72aa2ce8..49b55bd88 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -721,10 +721,6 @@ If you're interested in translating LMMS in another language or want to imp Input Gain: - - NOIS - - Input Noise: @@ -741,10 +737,6 @@ If you're interested in translating LMMS in another language or want to imp Output Clip: - - Rate - - Rate Enabled @@ -753,10 +745,6 @@ If you're interested in translating LMMS in another language or want to imp Enable samplerate-crushing - - Depth - - Depth Enabled @@ -769,20 +757,28 @@ If you're interested in translating LMMS in another language or want to imp Sample rate: - - STD - - Stereo difference: - Levels + Levels: - Levels: + NOISE + + + + FREQ + + + + STEREO + + + + QUANT @@ -2170,10 +2166,6 @@ Please make sure you have write permission to the file and the directory contain RATE - - Rate: - - AMNT @@ -2194,6 +2186,10 @@ Please make sure you have write permission to the file and the directory contain Invert + + Period: + + FxLine @@ -3370,22 +3366,10 @@ You can remove and move FX channels in the context menu, which is accessed by ri FX channel - - ENV/LFO - - - - FUNC - - FX - - MIDI - - Save preset @@ -3394,10 +3378,6 @@ You can remove and move FX channels in the context menu, which is accessed by ri XML preset file (*.xpf) - - PLUGIN - - Pitch range (semitones) @@ -3414,10 +3394,6 @@ You can remove and move FX channels in the context menu, which is accessed by ri Click here, if you want to save current instrument track settings in a preset file. Later you can load this preset by double-clicking it in the preset-browser. - - MISC - - Use these controls to view and edit the next/previous track in the song editor. @@ -3426,6 +3402,30 @@ You can remove and move FX channels in the context menu, which is accessed by ri SAVE + + Envelope, filter & LFO + + + + Chord stacking & arpeggio + + + + Effects + + + + MIDI settings + + + + Miscellaneous + + + + Plugin + + Knob @@ -3913,14 +3913,6 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. Recover the file. Please don't run multiple instances of LMMS when you do this. - - Ignore - - - - Launch LMMS as usual but with automatic backup disabled to prevent the present recover file from being overwritten. - - Discard @@ -3993,10 +3985,6 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. Recover session. Please save your work! - - Automatic backup disabled. Remember to save your work! - - Recovered project not saved @@ -5706,14 +5694,6 @@ Reason: "%2" ProjectNotes - - Project notes - - - - Put down your project notes here. - - Edit Actions @@ -5822,6 +5802,14 @@ Reason: "%2" &Color... + + Project Notes + + + + Enter project notes here + + ProjectRenderer From e36b05e425a707c13c45a5984814987e056fb156 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Mon, 5 Jun 2017 23:50:39 +0200 Subject: [PATCH 46/65] Add myself (Michael Gregorius) to the list of authors --- doc/AUTHORS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/AUTHORS b/doc/AUTHORS index e14e92732..8dc5a3039 100644 --- a/doc/AUTHORS +++ b/doc/AUTHORS @@ -85,3 +85,7 @@ Chrissy McManus Oskar Wallgren Development + +Michael Gregorius + + Development From d3359f5a704d889ad97217a68207fb4334e4ef10 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Tue, 6 Jun 2017 23:06:15 +0200 Subject: [PATCH 47/65] Fix #3616 by preventing fold overs for clipped data Use libsndfile functionality to prevent fold overs when exporting clipped data. The fold overs occurred when exporting with a bit depth of 24 bit. --- src/core/audio/AudioFileWave.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index fb6e2901f..45e46b838 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -79,7 +79,12 @@ bool AudioFileWave::startEncoding() outputFile().toUtf8().constData(), #endif SFM_WRITE, &m_si ); + + // Prevent fold overs when encountering clipped data + sf_command(m_sf, SFC_SET_CLIPPING, NULL, SF_TRUE); + sf_set_string ( m_sf, SF_STR_SOFTWARE, "LMMS" ); + return true; } From 567898143b48e6cb5131ec379d471c0ff2552bcb Mon Sep 17 00:00:00 2001 From: Karmo Rosental Date: Thu, 8 Jun 2017 00:07:48 +0300 Subject: [PATCH 48/65] Fixed LMMS crash when pressing Q in not existing piano roll. (#3609) --- src/gui/editors/PianoRoll.cpp | 39 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 905ba9bda..f09907fcb 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -805,7 +805,7 @@ void PianoRoll::setBackgroundShade( const QColor & c ) -void PianoRoll::drawNoteRect( QPainter & p, int x, int y, +void PianoRoll::drawNoteRect( QPainter & p, int x, int y, int width, const Note * n, const QColor & noteCol, const QColor & selCol, const int noteOpc, const bool borders ) { @@ -1918,7 +1918,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) { // select the notes within the selection rectangle and // then destroy the selection rectangle - computeSelectedNotes( + computeSelectedNotes( me->modifiers() & Qt::ShiftModifier ); } else if( m_action == ActionMoveNote ) @@ -2463,7 +2463,7 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) } } } - } + } else if (m_action == ActionResizeNote) { // When resizing notes: @@ -2471,7 +2471,7 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) // If shift is pressed we resize and rearrange only the selected notes // If shift + ctrl then we also rearrange all posterior notes (sticky) // If shift is pressed but only one note is selected, apply sticky - + if (shift) { // Algorithm: @@ -2491,8 +2491,8 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) const Note *posteriorNote = nullptr; for (const Note *note : notes) { - if (note->selected() && (posteriorNote == nullptr || - note->oldPos().getTicks() + note->oldLength().getTicks() > + if (note->selected() && (posteriorNote == nullptr || + note->oldPos().getTicks() + note->oldLength().getTicks() > posteriorNote->oldPos().getTicks() + posteriorNote->oldLength().getTicks())) { posteriorNote = note; @@ -2512,9 +2512,9 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) if(note->selected()) { // scale relative start and end positions by scaleFactor - int newStart = stretchStartTick + scaleFactor * + int newStart = stretchStartTick + scaleFactor * (note->oldPos().getTicks() - stretchStartTick); - int newEnd = stretchStartTick + scaleFactor * + int newEnd = stretchStartTick + scaleFactor * (note->oldPos().getTicks()+note->oldLength().getTicks() - stretchStartTick); // if not holding alt, quantize the offsets if(!alt) @@ -2533,7 +2533,7 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) int newLength = qMax(1, newEnd-newStart); if (note == posteriorNote) { - posteriorDeltaThisFrame = (newStart+newLength) - + posteriorDeltaThisFrame = (newStart+newLength) - (note->pos().getTicks() + note->length().getTicks()); } note->setLength( MidiTime(newLength) ); @@ -3918,27 +3918,32 @@ int PianoRoll::quantization() const void PianoRoll::quantizeNotes() { + if( ! hasValidPattern() ) + { + return; + } + NoteVector notes = getSelectedNotes(); - if (notes.empty()) + if( notes.empty() ) { - for (Note* n : m_pattern->notes()) + for( Note* n : m_pattern->notes() ) { - notes.push_back(n); + notes.push_back( n ); } } - for (Note* n : notes) + for( Note* n : notes ) { - if (n->length() == MidiTime(0)) + if( n->length() == MidiTime( 0 ) ) { continue; } Note copy(*n); - m_pattern->removeNote(n); - copy.quantizePos(quantization()); - m_pattern->addNote(copy); + m_pattern->removeNote( n ); + copy.quantizePos( quantization() ); + m_pattern->addNote( copy ); } update(); From 143571761f22ca743985273991920fbc76da6338 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Thu, 8 Jun 2017 12:31:53 +0200 Subject: [PATCH 49/65] fixes sample muting bug for sampletrack (#3591) * fixes sample muting bug for sampletrack * simplify SampleTrack::updateTcos --- include/SampleTrack.h | 1 + src/tracks/SampleTrack.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 25841486c..6d39ed355 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -153,6 +153,7 @@ public: public slots: void updateTcos(); + void setPlayingTcos( bool isPlaying ); private: FloatModel m_volumeModel; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 8624497d4..45f587607 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -166,7 +166,8 @@ void SampleTCO::toggleRecord() void SampleTCO::playbackPositionChanged() { Engine::mixer()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle ); - m_isPlaying = false; + SampleTrack * st = dynamic_cast( getTrack() ); + st->setPlayingTcos( false ); } @@ -707,12 +708,21 @@ void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this ) void SampleTrack::updateTcos() +{ + Engine::mixer()->removePlayHandlesOfTypes( this, PlayHandle::TypeSamplePlayHandle ); + setPlayingTcos( false ); +} + + + + +void SampleTrack::setPlayingTcos( bool isPlaying ) { for( int i = 0; i < numOfTCOs(); ++i ) { TrackContentObject * tco = getTCO( i ); SampleTCO * sTco = dynamic_cast( tco ); - sTco->playbackPositionChanged(); + sTco->setIsPlaying( isPlaying ); } } From 5a2d8f12ad6f927ff71ca7e54433dda0dc54a50c Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Fri, 9 Jun 2017 10:28:59 +0200 Subject: [PATCH 50/65] fixes rounding issue in automatablemodel (#3597) * fixes rounding issue in automatablemodel * fix CRS knob sticking in instrument plugins --- include/AutomatableModel.h | 2 +- src/core/AutomatableModel.cpp | 6 +++--- src/gui/widgets/Knob.cpp | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index a4096f9ba..dd9bd7680 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -407,7 +407,7 @@ public: { } float getRoundedValue() const; - float getDigitCount(); + int getDigitCount() const; defaultTypedMethods(float); } ; diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 468f701e5..0391061b6 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -716,19 +716,19 @@ float AutomatableModel::globalAutomationValueAt( const MidiTime& time ) float FloatModel::getRoundedValue() const { - return static_cast( static_cast( value() / step() + 0.5 ) ) * step(); + return qRound( value() / step() ) * step(); } -float FloatModel::getDigitCount() +int FloatModel::getDigitCount() const { float steptemp = step(); int digits = 0; while ( steptemp < 1 ) { - steptemp = steptemp / 0.1f; + steptemp = steptemp * 10.0f; digits++; } return digits; diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 2c1739540..73ef42758 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -631,7 +631,6 @@ void Knob::mouseMoveEvent( QMouseEvent * _me ) emit sliderMoved( model()->value() ); QCursor::setPos( mapToGlobal( m_origMousePos ) ); } - s_textFloat->setText( displayValue() ); } @@ -735,7 +734,7 @@ void Knob::setPosition( const QPoint & _p ) float newValue = value * ratio; if( qAbs( newValue ) >= step ) { - float roundedValue = static_cast( static_cast( ( oldValue - newValue ) / step + 0.5 ) ) * step; + float roundedValue = qRound( ( oldValue - value ) / step ) * step; model()->setValue( roundedValue ); m_leftOver = 0.0f; } @@ -749,7 +748,7 @@ void Knob::setPosition( const QPoint & _p ) { if( qAbs( value ) >= step ) { - float roundedValue = static_cast( static_cast( ( oldValue - value ) / step + 0.5 ) ) * step; + float roundedValue = qRound( ( oldValue - value ) / step ) * step; model()->setValue( roundedValue ); m_leftOver = 0.0f; } From a371ff0ea1d516ca3d3947751d0d0ac728fdedee Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Sat, 10 Jun 2017 20:38:17 +0200 Subject: [PATCH 51/65] Some fixes to recent files (#3621) * Add a factory default data/projects/templates/default.mpt. Fixes #528 * On launch, if the last project was a template we create a new project (default.mpt) instead. * If there is a recovery file present and you discard it we create a new project as the project launched could be defective or, if .lmmsrc.xml wasn't written, an earlier project. --- data/projects/templates/default.mpt | 89 +++++++++++++++++++++++++++++ src/core/ConfigManager.cpp | 3 +- src/core/main.cpp | 6 +- src/gui/MainWindow.cpp | 9 ++- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 data/projects/templates/default.mpt diff --git a/data/projects/templates/default.mpt b/data/projects/templates/default.mpt new file mode 100644 index 000000000..18fcdd925 --- /dev/null +++ b/data/projects/templates/default.mpt @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Enter project notes here

]]>
+ + + + diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index c9cf77357..5ccb4d345 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -282,7 +282,8 @@ void ConfigManager::addRecentlyOpenedProject( const QString & file ) { QFileInfo recentFile( file ); if( recentFile.suffix().toLower() == "mmp" || - recentFile.suffix().toLower() == "mmpz" ) + recentFile.suffix().toLower() == "mmpz" || + recentFile.suffix().toLower() == "mpt" ) { m_recentlyOpenedProjects.removeAll( file ); if( m_recentlyOpenedProjects.size() > 50 ) diff --git a/src/core/main.cpp b/src/core/main.cpp index 5dbea3099..617e3804d 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -846,13 +846,15 @@ int main( int argc, char * * argv ) else if( ConfigManager::inst()-> value( "app", "openlastproject" ).toInt() && !ConfigManager::inst()-> - recentlyOpenedProjects().isEmpty() ) + recentlyOpenedProjects().isEmpty() && + !recoveryFilePresent ) { QString f = ConfigManager::inst()-> recentlyOpenedProjects().first(); QFileInfo recentFile( f ); - if ( recentFile.exists() ) + if ( recentFile.exists() && + recentFile.suffix().toLower() != "mpt" ) { Engine::getSong()->loadProject( f ); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 09524238e..ddd2faacf 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -876,8 +876,8 @@ void MainWindow::updateRecentlyOpenedProjectsMenu() m_recentlyOpenedProjectsMenu->clear(); QStringList rup = ConfigManager::inst()->recentlyOpenedProjects(); -// The file history goes 30 deep but we only show the 15 -// most recent ones that we can open. +// The file history goes 50 deep but we only show the 15 +// most recent ones that we can open and omit .mpt files. int shownInMenu = 0; for( QStringList::iterator it = rup.begin(); it != rup.end(); ++it ) { @@ -885,6 +885,11 @@ void MainWindow::updateRecentlyOpenedProjectsMenu() if ( recentFile.exists() && *it != ConfigManager::inst()->recoveryFile() ) { + if( recentFile.suffix().toLower() == "mpt" ) + { + continue; + } + m_recentlyOpenedProjectsMenu->addAction( embed::getIconPixmap( "project_file" ), *it ); #ifdef LMMS_BUILD_APPLE From ad2c843e20dc66104a00322483a316d2c200a0e5 Mon Sep 17 00:00:00 2001 From: Karmo Rosental Date: Sat, 10 Jun 2017 22:55:00 +0300 Subject: [PATCH 52/65] Fixed inverted zooming. (#3570) * Fixed inverted zooming. * Prevent useless tests. --- src/gui/editors/AutomationEditor.cpp | 28 ++++++++++++++-------------- src/gui/editors/PianoRoll.cpp | 14 +++++++------- src/gui/editors/SongEditor.cpp | 10 +++++----- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index 7f2e549a3..29958d886 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -69,7 +69,7 @@ QPixmap * AutomationEditor::s_toolYFlip = NULL; QPixmap * AutomationEditor::s_toolXFlip = NULL; const QVector AutomationEditor::m_zoomXLevels = - { 8.0f, 4.0f, 2.0f, 1.0f, 0.5f, 0.25f, 0.125f }; + { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f }; @@ -1303,7 +1303,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) //Don't bother doing/rendering anything if there is no automation points if( time_map.size() > 0 ) { - timeMap::iterator it = time_map.begin(); + timeMap::iterator it = time_map.begin(); while( it+1 != time_map.end() ) { // skip this section if it occurs completely before the @@ -1320,7 +1320,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) { break; } - + //NEEDS Change in CSS /*bool is_selected = false; // if we're in move-mode, we may only draw @@ -1360,8 +1360,8 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) for( int i = 0; i < ( it + 1 ).key() - it.key(); i++ ) { path.lineTo( QPointF( xCoordOfTick( it.key() + i ), yCoordOfLevel( values[i] ) ) ); //NEEDS Change in CSS - //drawLevelTick( p, it.key() + i, values[i], is_selected ); - + //drawLevelTick( p, it.key() + i, values[i], is_selected ); + } path.lineTo( QPointF( xCoordOfTick( ( it + 1 ).key() ), yCoordOfLevel( nextValue ) ) ); path.lineTo( QPointF( xCoordOfTick( ( it + 1 ).key() ), yCoordOfLevel( 0 ) ) ); @@ -1537,12 +1537,12 @@ void AutomationEditor::drawLevelTick(QPainter & p, int tick, float value) p.fillRect( x, y_start, rect_width, rect_height, currentColor ); } - + else { printf("not in range\n"); } - + } @@ -1600,12 +1600,12 @@ void AutomationEditor::wheelEvent(QWheelEvent * we ) { y++; } - if( we->delta() < 0 ) + else if( we->delta() < 0 ) { y--; } y = qBound( 0, y, m_zoomingYModel.size() - 1 ); - m_zoomingYModel.setValue( y ); + m_zoomingYModel.setValue( y ); } else if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::AltModifier ) { @@ -1614,7 +1614,7 @@ void AutomationEditor::wheelEvent(QWheelEvent * we ) { q--; } - if( we->delta() < 0 ) + else if( we->delta() < 0 ) { q++; } @@ -1626,13 +1626,13 @@ void AutomationEditor::wheelEvent(QWheelEvent * we ) { int x = m_zoomingXModel.value(); if( we->delta() > 0 ) - { - x--; - } - if( we->delta() < 0 ) { x++; } + else if( we->delta() < 0 ) + { + x--; + } x = qBound( 0, x, m_zoomingXModel.size() - 1 ); m_zoomingXModel.setValue( x ); } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index f09907fcb..080369d8c 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -143,7 +143,7 @@ PianoRoll::PianoRollKeyTypes PianoRoll::prKeyOrder[] = const int DEFAULT_PR_PPT = KEY_LINE_HEIGHT * DefaultStepsPerTact; const QVector PianoRoll::m_zoomLevels = - { 8.0f, 4.0f, 2.0f, 1.0f, 0.5f, 0.25f, 0.125f }; + { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f }; PianoRoll::PianoRoll() : @@ -3256,7 +3256,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) { q--; } - if( we->delta() < 0 ) + else if( we->delta() < 0 ) { q++; } @@ -3270,7 +3270,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) { l--; } - if( we->delta() < 0 ) + else if( we->delta() < 0 ) { l++; } @@ -3281,13 +3281,13 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) { int z = m_zoomingModel.value(); if( we->delta() > 0 ) - { - z--; - } - if( we->delta() < 0 ) { z++; } + else if( we->delta() < 0 ) + { + z--; + } z = qBound( 0, z, m_zoomingModel.size() - 1 ); // update combobox with zooming-factor m_zoomingModel.setValue( z ); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index d110201c8..ed0491e20 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -71,7 +71,7 @@ void positionLine::paintEvent( QPaintEvent * pe ) } const QVector SongEditor::m_zoomLevels = - { 16.0f, 8.0f, 4.0f, 2.0f, 1.0f, 0.5f, 0.25f, 0.125f }; + { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f }; SongEditor::SongEditor( Song * song ) : @@ -360,13 +360,13 @@ void SongEditor::wheelEvent( QWheelEvent * we ) int z = m_zoomingModel->value(); if( we->delta() > 0 ) - { - z--; - } - if( we->delta() < 0 ) { z++; } + else if( we->delta() < 0 ) + { + z--; + } z = qBound( 0, z, m_zoomingModel->size() - 1 ); // update combobox with zooming-factor m_zoomingModel->setValue( z ); From c53dd3106405a3d615ceeaf9072a980028957b66 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Mon, 12 Jun 2017 07:53:32 +0200 Subject: [PATCH 53/65] Silence warning message and fix obsolete vorbis tag message (#3634) We give the stream a unique serial number and make sure it isn't '0' or 'UINT_32_MAX'. Change obsolete 'Linux MultiMedia Studio' to 'LMMS' in ogg file comment. --- src/core/audio/AudioFileOgg.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index 526776a73..170f411d2 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -70,8 +70,7 @@ inline int AudioFileOgg::writePage() bool AudioFileOgg::startEncoding() { vorbis_comment vc; - const char * comments = "Cool=This song has been made using Linux " - "MultiMedia Studio"; + const char * comments = "Cool=This song has been made using LMMS"; int comment_length = strlen( comments ); char * user_comments = new char[comment_length + 1]; strcpy( user_comments, comments ); @@ -100,7 +99,7 @@ bool AudioFileOgg::startEncoding() m_rate = 48000; setSampleRate( 48000 ); } - m_serialNo = 0; // track-num? + m_comments = &vc; // comments for ogg-file // Have vorbisenc choose a mode for us @@ -135,6 +134,10 @@ bool AudioFileOgg::startEncoding() vorbis_analysis_init( &m_vd, &m_vi ); vorbis_block_init( &m_vd, &m_vb ); + // We give our ogg file a random serial number and avoid + // 0 and UINT32_MAX which can get you into trouble. + qsrand( time( 0 ) ); + m_serialNo = 0xD0000000 + qrand() % 0x0FFFFFFF; ogg_stream_init( &m_os, m_serialNo ); // Now, build the three header packets and send through to the stream From c2f26a76d4ac5d729397bbda478a0e3f6fac23ea Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Mon, 12 Jun 2017 19:57:08 +0200 Subject: [PATCH 54/65] Implement MP3 encoding support Implement MP3 encoding support --- .travis/linux..install.sh | 2 +- .travis/linux.win32.install.sh | 2 +- .travis/linux.win64.install.sh | 2 +- .travis/osx..install.sh | 2 +- CMakeLists.txt | 17 ++++ cmake/modules/FindLame.cmake | 16 ++++ include/AudioFileMP3.h | 74 +++++++++++++++++ include/OutputSettings.h | 24 +++++- include/ProjectRenderer.h | 3 + src/CMakeLists.txt | 6 ++ src/core/CMakeLists.txt | 1 + src/core/ProjectRenderer.cpp | 10 +++ src/core/Song.cpp | 3 +- src/core/audio/AudioFileMP3.cpp | 133 ++++++++++++++++++++++++++++++ src/gui/ExportProjectDialog.cpp | 37 ++++++++- src/gui/dialogs/export_project.ui | 113 +++++++++++++++++-------- src/lmmsconfig.h.in | 1 + 17 files changed, 402 insertions(+), 44 deletions(-) create mode 100644 cmake/modules/FindLame.cmake create mode 100644 include/AudioFileMP3.h create mode 100644 src/core/audio/AudioFileMP3.cpp diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index 7b591feb9..f5988a83d 100644 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev +PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev libasound2-dev libjack-dev libsdl-dev libsamplerate0-dev libstk0-dev libfluidsynth-dev portaudio19-dev wine-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev" diff --git a/.travis/linux.win32.install.sh b/.travis/linux.win32.install.sh index 75c54f0e9..5f775db13 100644 --- a/.travis/linux.win32.install.sh +++ b/.travis/linux.win32.install.sh @@ -11,7 +11,7 @@ MINGW_PACKAGES="mingw32-x-sdl mingw32-x-libvorbis mingw32-x-fluidsynth mingw32-x mingw32-x-glib2 mingw32-x-portaudio mingw32-x-libsndfile mingw32-x-fftw mingw32-x-flac mingw32-x-fltk mingw32-x-libsamplerate mingw32-x-pkgconfig mingw32-x-binutils mingw32-x-gcc mingw32-x-runtime - mingw32-x-libgig mingw32-x-libsoundio $MINGW_PACKAGES" + mingw32-x-libgig mingw32-x-libsoundio mingw32-x-lame $MINGW_PACKAGES" export MINGW_PACKAGES diff --git a/.travis/linux.win64.install.sh b/.travis/linux.win64.install.sh index 538fda18c..eb0c64510 100644 --- a/.travis/linux.win64.install.sh +++ b/.travis/linux.win64.install.sh @@ -16,7 +16,7 @@ MINGW_PACKAGES="mingw64-x-sdl mingw64-x-libvorbis mingw64-x-fluidsynth mingw64-x mingw64-x-glib2 mingw64-x-portaudio mingw64-x-libsndfile mingw64-x-fftw mingw64-x-flac mingw64-x-fltk mingw64-x-libsamplerate mingw64-x-pkgconfig mingw64-x-binutils mingw64-x-gcc mingw64-x-runtime - mingw64-x-libgig mingw64-x-libsoundio $MINGW_PACKAGES" + mingw64-x-libgig mingw64-x-libsoundio mingw64-x-lame $MINGW_PACKAGES" export MINGW_PACKAGES diff --git a/.travis/osx..install.sh b/.travis/osx..install.sh index e1bdcac28..7af8bd374 100644 --- a/.travis/osx..install.sh +++ b/.travis/osx..install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PACKAGES="cmake pkgconfig fftw libogg libvorbis libsndfile libsamplerate jack sdl libgig libsoundio stk portaudio node fltk" +PACKAGES="cmake pkgconfig fftw libogg libvorbis lame libsndfile libsamplerate jack sdl libgig libsoundio stk portaudio node fltk" if [ $QT5 ]; then PACKAGES="$PACKAGES homebrew/versions/qt55" diff --git a/CMakeLists.txt b/CMakeLists.txt index 4577b42e2..a0306c5c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ OPTION(WANT_CAPS "Include C* Audio Plugin Suite (LADSPA plugins)" ON) OPTION(WANT_CARLA "Include Carla plugin" ON) OPTION(WANT_CMT "Include Computer Music Toolkit LADSPA plugins" ON) OPTION(WANT_JACK "Include JACK (Jack Audio Connection Kit) support" ON) +OPTION(WANT_MP3LAME "Include MP3/Lame support" ON) OPTION(WANT_OGGVORBIS "Include OGG/Vorbis support" ON) OPTION(WANT_PULSEAUDIO "Include PulseAudio support" ON) OPTION(WANT_PORTAUDIO "Include PortAudio support" ON) @@ -306,6 +307,21 @@ IF(NOT LMMS_HAVE_PULSEAUDIO) ENDIF(NOT LMMS_HAVE_PULSEAUDIO) +# check for MP3/Lame-libraries +IF(WANT_MP3LAME) + FIND_PACKAGE(Lame) + IF(LAME_FOUND) + SET(LMMS_HAVE_MP3LAME TRUE) + SET(STATUS_MP3LAME "OK") + ELSE(LAME_FOUND) + SET(STATUS_MP3LAME "not found, please install libmp3lame-dev (or similar)") + SET(LAME_LIBRARIES "") + SET(LAME_INCLUDE_DIRS "") + ENDIF(LAME_FOUND) +ELSE(WANT_MP3LAME) + SET(STATUS_MP3LAME "Disabled for build") +ENDIF(WANT_MP3LAME) + # check for OGG/Vorbis-libraries IF(WANT_OGGVORBIS) FIND_PACKAGE(OggVorbis) @@ -593,6 +609,7 @@ MESSAGE( "-----------------------------------------\n" "* WAVE : OK\n" "* OGG/VORBIS : ${STATUS_OGGVORBIS}\n" +"* MP3/Lame : ${STATUS_MP3LAME}\n" ) MESSAGE( diff --git a/cmake/modules/FindLame.cmake b/cmake/modules/FindLame.cmake new file mode 100644 index 000000000..e5dc93bd5 --- /dev/null +++ b/cmake/modules/FindLame.cmake @@ -0,0 +1,16 @@ +# - Try to find LAME +# Once done this will define +# +# LAME_FOUND - system has liblame +# LAME_INCLUDE_DIRS - the liblame include directory +# LAME_LIBRARIES - The liblame libraries + +find_path(LAME_INCLUDE_DIRS lame/lame.h) +find_library(LAME_LIBRARIES mp3lame) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Lame DEFAULT_MSG LAME_INCLUDE_DIRS LAME_LIBRARIES) + +list(APPEND LAME_DEFINITIONS -DHAVE_LIBMP3LAME=1) + +mark_as_advanced(LAME_INCLUDE_DIRS LAME_LIBRARIES LAME_DEFINITIONS) diff --git a/include/AudioFileMP3.h b/include/AudioFileMP3.h new file mode 100644 index 000000000..497208e20 --- /dev/null +++ b/include/AudioFileMP3.h @@ -0,0 +1,74 @@ +/* + * AudioFileMP3.h - Audio-device which encodes a wave stream into + * an MP3 file. This is used for song export. + * + * Copyright (c) 2017 to present Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef AUDIO_FILE_MP3_H +#define AUDIO_FILE_MP3_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_MP3LAME + +#include "AudioFileDevice.h" + +#include "lame/lame.h" + + +class AudioFileMP3 : public AudioFileDevice +{ +public: + AudioFileMP3( OutputSettings const & outputSettings, + const ch_cnt_t _channels, + bool & successful, + const QString & _file, + Mixer* mixer ); + virtual ~AudioFileMP3(); + + static AudioFileDevice * getInst( const QString & outputFilename, + OutputSettings const & outputSettings, + const ch_cnt_t channels, + Mixer* mixer, + bool & successful ) + { + return new AudioFileMP3( outputSettings, channels, successful, + outputFilename, mixer ); + } + +protected: + virtual void writeBuffer( const surroundSampleFrame * /* _buf*/, + const fpp_t /*_frames*/, + const float /*_master_gain*/ ); + +private: + void flushRemainingBuffers(); + bool initEncoder(); + void tearDownEncoder(); + +private: + lame_t m_lame; +}; + +#endif + +#endif diff --git a/include/OutputSettings.h b/include/OutputSettings.h index e4dccfd3a..2b294e13c 100644 --- a/include/OutputSettings.h +++ b/include/OutputSettings.h @@ -38,6 +38,13 @@ public: NumDepths }; + enum StereoMode + { + StereoMode_Stereo, + StereoMode_JointStereo, + StereoMode_Mono + }; + class BitRateSettings { public: @@ -60,10 +67,19 @@ public: public: OutputSettings( sample_rate_t sampleRate, BitRateSettings const & bitRateSettings, - BitDepth bitDepth ) : + BitDepth bitDepth, + StereoMode stereoMode ) : m_sampleRate(sampleRate), m_bitRateSettings(bitRateSettings), - m_bitDepth(bitDepth) + m_bitDepth(bitDepth), + m_stereoMode(stereoMode) + { + } + + OutputSettings( sample_rate_t sampleRate, + BitRateSettings const & bitRateSettings, + BitDepth bitDepth ) : + OutputSettings(sampleRate, bitRateSettings, bitDepth, StereoMode_Stereo ) { } @@ -76,10 +92,14 @@ public: BitDepth getBitDepth() const { return m_bitDepth; } void setBitDepth(BitDepth bitDepth) { m_bitDepth = bitDepth; } + StereoMode getStereoMode() const { return m_stereoMode; } + void setStereoMode(StereoMode stereoMode) { m_stereoMode = stereoMode; } + private: sample_rate_t m_sampleRate; BitRateSettings m_bitRateSettings; BitDepth m_bitDepth; + StereoMode m_stereoMode; }; #endif diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 20ccbc0de..15d043e51 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -39,11 +39,14 @@ public: { WaveFile, OggFile, + MP3File, NumFileFormats } ; struct FileEncodeDevice { + bool isAvailable() const { return m_getDevInst != nullptr; } + ExportFileFormats m_fileFormat; const char * m_description; const char * m_extension; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b8f98ebd..154ff4f85 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,6 +75,10 @@ IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}") ENDIF() +IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL "")) + INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}") +ENDIF() + # Use libraries in non-standard directories (e.g., another version of Qt) IF(LMMS_BUILD_LINUX) LINK_LIBRARIES(-Wl,--enable-new-dtags) @@ -139,6 +143,7 @@ SET(LMMS_REQUIRED_LIBS ${PULSEAUDIO_LIBRARIES} ${JACK_LIBRARIES} ${OGGVORBIS_LIBRARIES} + ${LAME_LIBRARIES} ${SAMPLERATE_LIBRARIES} ${SNDFILE_LIBRARIES} ${EXTRA_LIBRARIES} @@ -194,6 +199,7 @@ IF(LMMS_BUILD_WIN32) "${MINGW_PREFIX}/bin/libvorbisfile-3.dll" "${MINGW_PREFIX}/bin/libjpeg-9.dll" "${MINGW_PREFIX}/bin/libogg-0.dll" + "${MINGW_PREFIX}/bin/libmp3lame-0.dll" "${MINGW_PREFIX}/bin/libfftw3f-3.dll" "${MINGW_PREFIX}/bin/libFLAC-8.dll" "${MINGW_PREFIX}/bin/libpng16-16.dll" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c0f0e76e1..5dcfa3c42 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -69,6 +69,7 @@ set(LMMS_SRCS core/audio/AudioAlsa.cpp core/audio/AudioDevice.cpp core/audio/AudioFileDevice.cpp + core/audio/AudioFileMP3.cpp core/audio/AudioFileOgg.cpp core/audio/AudioFileWave.cpp core/audio/AudioJack.cpp diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 07dccc6ce..10fb9eb0e 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -30,6 +30,7 @@ #include "AudioFileWave.h" #include "AudioFileOgg.h" +#include "AudioFileMP3.h" #ifdef LMMS_HAVE_SCHED_H #include "sched.h" @@ -48,6 +49,15 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] = &AudioFileOgg::getInst #else NULL +#endif + }, + { ProjectRenderer::MP3File, + QT_TRANSLATE_NOOP( "ProjectRenderer", "Compressed MP3-File (*.mp3)" ), + ".mp3", +#ifdef LMMS_HAVE_MP3LAME + &AudioFileMP3::getInst +#else + NULL #endif }, // ... insert your own file-encoder-infos here... may be one day the diff --git a/src/core/Song.cpp b/src/core/Song.cpp index b776b531f..0732fd1d3 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1316,7 +1316,8 @@ void Song::exportProject( bool multiExport ) efd.setFileMode( FileDialog::AnyFile ); int idx = 0; QStringList types; - while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats ) + while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats && + ProjectRenderer::fileEncodeDevices[idx].isAvailable()) { types << tr( ProjectRenderer::fileEncodeDevices[idx].m_description ); ++idx; diff --git a/src/core/audio/AudioFileMP3.cpp b/src/core/audio/AudioFileMP3.cpp new file mode 100644 index 000000000..cce7ec8e4 --- /dev/null +++ b/src/core/audio/AudioFileMP3.cpp @@ -0,0 +1,133 @@ +/* + * AudioFileMP3.cpp - Audio-device which encodes a wave stream into + * an MP3 file. This is used for song export. + * + * Copyright (c) 2017 to present Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "AudioFileMP3.h" + +#ifdef LMMS_HAVE_MP3LAME + +#include "Mixer.h" + +#include + + +AudioFileMP3::AudioFileMP3( OutputSettings const & outputSettings, + const ch_cnt_t channels, + bool & successful, + const QString & file, + Mixer* mixer ) : + AudioFileDevice( outputSettings, channels, file, mixer ) +{ + successful = true; + // For now only accept stereo sources + successful &= channels == 2; + successful &= initEncoder(); + successful &= outputFileOpened(); +} + +AudioFileMP3::~AudioFileMP3() +{ + flushRemainingBuffers(); + tearDownEncoder(); +} + +void AudioFileMP3::writeBuffer( const surroundSampleFrame * _buf, + const fpp_t _frames, + const float _master_gain ) +{ + if (_frames < 1) + { + return; + } + + // TODO Why isn't the gain applied by the driver but inside the device? + std::vector interleavedDataBuffer(_frames * 2); + for (fpp_t i = 0; i < _frames; ++i) + { + interleavedDataBuffer[2*i] = _buf[i][0] * _master_gain; + interleavedDataBuffer[2*i + 1] = _buf[i][1] * _master_gain; + } + + size_t minimumBufferSize = 1.25 * _frames + 7200; + std::vector encodingBuffer(minimumBufferSize); + + int bytesWritten = lame_encode_buffer_interleaved_ieee_float(m_lame, &interleavedDataBuffer[0], _frames, &encodingBuffer[0], static_cast(encodingBuffer.size())); + assert (bytesWritten >= 0); + + writeData(&encodingBuffer[0], bytesWritten); +} + +void AudioFileMP3::flushRemainingBuffers() +{ + // The documentation states that flush should have at least 7200 bytes. So let's be generous. + std::vector encodingBuffer(7200 * 4); + + int bytesWritten = lame_encode_flush(m_lame, &encodingBuffer[0], static_cast(encodingBuffer.size())); + assert (bytesWritten >= 0); + + writeData(&encodingBuffer[0], bytesWritten); +} + +MPEG_mode mapToMPEG_mode(OutputSettings::StereoMode stereoMode) +{ + switch (stereoMode) + { + case OutputSettings::StereoMode_Stereo: + return STEREO; + case OutputSettings::StereoMode_JointStereo: + return JOINT_STEREO; + case OutputSettings::StereoMode_Mono: + return MONO; + default: + return NOT_SET; + } +} + +bool AudioFileMP3::initEncoder() +{ + m_lame = lame_init(); + + // Handle stereo/joint/mono settings + OutputSettings::StereoMode stereoMode = getOutputSettings().getStereoMode(); + lame_set_mode(m_lame, mapToMPEG_mode(stereoMode)); + + // Handle bit rate settings + OutputSettings::BitRateSettings bitRateSettings = getOutputSettings().getBitRateSettings(); + int bitRate = static_cast(bitRateSettings.getBitRate()); + + lame_set_VBR(m_lame, vbr_off); + lame_set_brate(m_lame, bitRate); + + // Add a comment + id3tag_set_comment(m_lame, "Created with LMMS"); + + return lame_init_params(m_lame) != -1; +} + +void AudioFileMP3::tearDownEncoder() +{ + lame_close(m_lame); +} + +#endif diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index c4f7696b8..83c7883cc 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -58,7 +58,7 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, int cbIndex = 0; for( int i = 0; i < ProjectRenderer::NumFileFormats; ++i ) { - if( ProjectRenderer::fileEncodeDevices[i].m_getDevInst != NULL ) + if( ProjectRenderer::fileEncodeDevices[i].isAvailable() ) { // get the extension of this format QString renderExt = ProjectRenderer::fileEncodeDevices[i].m_extension; @@ -128,7 +128,20 @@ void ExportProjectDialog::closeEvent( QCloseEvent * _ce ) } - +OutputSettings::StereoMode mapToStereoMode(int index) +{ + switch (index) + { + case 0: + return OutputSettings::StereoMode_Stereo; + case 1: + return OutputSettings::StereoMode_JointStereo; + case 2: + return OutputSettings::StereoMode_Mono; + default: + return OutputSettings::StereoMode_Stereo; + } +} void ExportProjectDialog::startExport() { @@ -146,7 +159,8 @@ void ExportProjectDialog::startExport() OutputSettings os = OutputSettings( samplerates[ samplerateCB->currentIndex() ], bitRateSettings, - static_cast( depthCB->currentIndex() ) ); + static_cast( depthCB->currentIndex() ), + mapToStereoMode(stereoModeComboBox->currentIndex()) ); m_renderManager = new RenderManager( qs, os, m_ft, m_fileName ); @@ -181,6 +195,8 @@ ProjectRenderer::ExportFileFormats convertIndexToExportFileFormat(int index) return ProjectRenderer::WaveFile; case 1: return ProjectRenderer::OggFile; + case 2: + return ProjectRenderer::MP3File; default: Q_ASSERT(false); break; @@ -195,10 +211,23 @@ void ExportProjectDialog::onFileFormatChanged(int index) ProjectRenderer::ExportFileFormats exportFormat = convertIndexToExportFileFormat(index); - bool bitRateControlsEnabled = exportFormat == ProjectRenderer::OggFile; + bool stereoModeVisible = exportFormat == ProjectRenderer::MP3File; + + bool sampleRateControlsVisible = exportFormat != ProjectRenderer::MP3File; + + bool bitRateControlsEnabled = + (exportFormat == ProjectRenderer::OggFile || + exportFormat == ProjectRenderer::MP3File); + bool bitDepthControlEnabled = exportFormat == ProjectRenderer::WaveFile; + bool variableBitrateVisible = exportFormat != ProjectRenderer::MP3File; + + stereoModeWidget->setVisible(stereoModeVisible); + sampleRateWidget->setVisible(sampleRateControlsVisible); + bitrateWidget->setVisible(bitRateControlsEnabled); + checkBoxVariableBitRate->setVisible(variableBitrateVisible); depthWidget->setVisible(bitDepthControlEnabled); } diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index 2cd466ee3..838fbee1f 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -7,7 +7,7 @@ 0 0 715 - 447 + 491 @@ -45,39 +45,48 @@ - - - Samplerate: - - - - - - - - 44100 Hz + + + + 0 - - - - 48000 Hz - - - - - 88200 Hz - - - - - 96000 Hz - - - - - 192000 Hz - - + + + + Samplerate: + + + + + + + + 44100 Hz + + + + + 48000 Hz + + + + + 88200 Hz + + + + + 96000 Hz + + + + + 192000 Hz + + + + + @@ -124,6 +133,44 @@ + + + + + 0 + + + + + Stereo mode: + + + + + + + 1 + + + + Stereo + + + + + Joint Stereo + + + + + Mono + + + + + + + diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index 0948d8529..421d0cc99 100644 --- a/src/lmmsconfig.h.in +++ b/src/lmmsconfig.h.in @@ -11,6 +11,7 @@ #cmakedefine LMMS_HAVE_ALSA #cmakedefine LMMS_HAVE_FLUIDSYNTH #cmakedefine LMMS_HAVE_JACK +#cmakedefine LMMS_HAVE_MP3LAME #cmakedefine LMMS_HAVE_OGGVORBIS #cmakedefine LMMS_HAVE_OSS #cmakedefine LMMS_HAVE_SNDIO From b2919d60d586f8b498c8ed8f72794c504b2b4f9b Mon Sep 17 00:00:00 2001 From: tresf Date: Mon, 12 Jun 2017 18:07:08 -0400 Subject: [PATCH 55/65] Disable HiDPI fix for Windows 7 and older Closes #3619 --- cmake/nsis/CMakeLists.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index ba3147874..b21c920e5 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -7,12 +7,17 @@ SET(CPACK_NSIS_URL_INFO_ABOUT "${PROJECT_URL}" PARENT_SCOPE) SET(CPACK_NSIS_CONTACT "${PROJECT_EMAIL}" PARENT_SCOPE) SET(CPACK_PACKAGE_EXECUTABLES "${CMAKE_PROJECT_NAME}.exe;${PROJECT_NAME_UCASE}" PARENT_SCOPE) SET(CPACK_NSIS_MENU_LINKS "${CMAKE_PROJECT_NAME}.exe;${PROJECT_NAME_UCASE}" PARENT_SCOPE) -SET(CPACK_NSIS_DEFINES "!include ${CMAKE_SOURCE_DIR}/cmake/nsis/FileAssociation.nsh") +SET(CPACK_NSIS_DEFINES " + !include ${CMAKE_SOURCE_DIR}/cmake/nsis/FileAssociation.nsh + !include LogicLib.nsh + !include WinVer.nsh") SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${VERSION}-win32") -SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " +SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${registerExtension} \\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" \\\${registerExtension} \\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" - WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" + \\\${IfNot} \\\${AtMostWin7} + WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" + \\\${EndIf} " PARENT_SCOPE) SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" From fc6d844a92d50242b9efaca08bb8bf5412710704 Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Tue, 13 Jun 2017 12:31:21 +0200 Subject: [PATCH 56/65] saves the correct subwindow size when it is hidden (#3589) * saves the correct subwindow size when it is hidden * remove invisible size from saveWidgetState() --- include/MainWindow.h | 2 +- src/gui/MainWindow.cpp | 8 ++++---- src/gui/editors/AutomationEditor.cpp | 2 +- src/gui/editors/BBEditor.cpp | 2 +- src/gui/editors/PianoRoll.cpp | 2 +- src/gui/editors/SongEditor.cpp | 2 +- src/gui/widgets/ControllerRackView.cpp | 2 +- src/gui/widgets/ProjectNotes.cpp | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index 603dd1d29..41fc06d5d 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -142,7 +142,7 @@ public: return m_keyMods.m_alt; } - static void saveWidgetState( QWidget * _w, QDomElement & _de, QSize const & sizeIfInvisible = QSize(0, 0) ); + static void saveWidgetState( QWidget * _w, QDomElement & _de ); static void restoreWidgetState( QWidget * _w, const QDomElement & _de ); public slots: diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ddd2faacf..8c2898da3 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -734,7 +734,7 @@ void MainWindow::clearKeyModifiers() -void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de, QSize const & sizeIfInvisible ) +void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de ) { // If our widget is the main content of a window (e.g. piano roll, FxMixer, etc), // we really care about the position of the *window* - not the position of the widget within its window @@ -757,7 +757,7 @@ void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de, QSize const & _de.setAttribute( "x", normalGeom.x() ); _de.setAttribute( "y", normalGeom.y() ); - QSize sizeToStore = visible ? normalGeom.size() : sizeIfInvisible; + QSize sizeToStore = normalGeom.size(); _de.setAttribute( "width", sizeToStore.width() ); _de.setAttribute( "height", sizeToStore.height() ); } @@ -769,8 +769,8 @@ void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de ) { QRect r( qMax( 1, _de.attribute( "x" ).toInt() ), qMax( 1, _de.attribute( "y" ).toInt() ), - qMax( 100, _de.attribute( "width" ).toInt() ), - qMax( 100, _de.attribute( "height" ).toInt() ) ); + qMax( _w->sizeHint().width(), _de.attribute( "width" ).toInt() ), + qMax( _w->minimumHeight(), _de.attribute( "height" ).toInt() ) ); if( _de.hasAttribute( "visible" ) && !r.isNull() ) { // If our widget is the main content of a window (e.g. piano roll, FxMixer, etc), diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index 29958d886..1e94c1b50 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -239,7 +239,7 @@ void AutomationEditor::setCurrentPattern(AutomationPattern * new_pattern ) void AutomationEditor::saveSettings(QDomDocument & doc, QDomElement & dom_parent) { - MainWindow::saveWidgetState(parentWidget(), dom_parent, QSize( 640, 400 )); + MainWindow::saveWidgetState( parentWidget(), dom_parent ); } diff --git a/src/gui/editors/BBEditor.cpp b/src/gui/editors/BBEditor.cpp index 50ff0c1e3..699ff2524 100644 --- a/src/gui/editors/BBEditor.cpp +++ b/src/gui/editors/BBEditor.cpp @@ -237,7 +237,7 @@ void BBTrackContainerView::removeBBView(int bb) void BBTrackContainerView::saveSettings(QDomDocument& doc, QDomElement& element) { - MainWindow::saveWidgetState(parentWidget(), element, QSize( 640, 400 ) ); + MainWindow::saveWidgetState( parentWidget(), element ); } void BBTrackContainerView::loadSettings(const QDomElement& element) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 080369d8c..9b65c6847 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -4392,7 +4392,7 @@ void PianoRollWindow::reset() void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de ) { - MainWindow::saveWidgetState( this, de, QSize( 640, 480 ) ); + MainWindow::saveWidgetState( this, de ); } diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index ed0491e20..cf561f179 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -262,7 +262,7 @@ SongEditor::~SongEditor() void SongEditor::saveSettings( QDomDocument& doc, QDomElement& element ) { - MainWindow::saveWidgetState(parentWidget(), element, QSize( 640, 400 )); + MainWindow::saveWidgetState( parentWidget(), element ); } void SongEditor::loadSettings( const QDomElement& element ) diff --git a/src/gui/widgets/ControllerRackView.cpp b/src/gui/widgets/ControllerRackView.cpp index cf1cf3c83..79e1bafa0 100644 --- a/src/gui/widgets/ControllerRackView.cpp +++ b/src/gui/widgets/ControllerRackView.cpp @@ -102,7 +102,7 @@ ControllerRackView::~ControllerRackView() void ControllerRackView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - MainWindow::saveWidgetState( this, _this, QSize( 400, 300) ); + MainWindow::saveWidgetState( this, _this ); } diff --git a/src/gui/widgets/ProjectNotes.cpp b/src/gui/widgets/ProjectNotes.cpp index 4e23349ad..d8d6636c4 100644 --- a/src/gui/widgets/ProjectNotes.cpp +++ b/src/gui/widgets/ProjectNotes.cpp @@ -438,7 +438,7 @@ void ProjectNotes::alignmentChanged( int _a ) void ProjectNotes::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - MainWindow::saveWidgetState( this, _this, QSize( 640, 400 ) ); + MainWindow::saveWidgetState( this, _this ); QDomCDATASection ds = _doc.createCDATASection( m_edit->toHtml() ); _this.appendChild( ds ); From 842c7194b049ec84d022190d7814465382c04fec Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sun, 18 Jun 2017 13:59:48 +0200 Subject: [PATCH 57/65] Implement command line export for MP3 (#3641) Add the option 'mp3' to the format switch (-f / --format) if LMMS is compiled with MP3 support. Add the new switch 'mode' which can be used to set the stereo mode used for the MP3 export. Adjust the man page for the new options. --- doc/lmms.1 | 9 +++++++-- src/core/main.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/doc/lmms.1 b/doc/lmms.1 index b9c297caf..41f906ecf 100644 --- a/doc/lmms.1 +++ b/doc/lmms.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH LMMS 1 "February 17, 2016" +.TH LMMS 1 "June 15, 2017" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -52,6 +52,9 @@ lmms \- software for easy music production .RB "[ \--\fBloop\fP ]" .br .B lmms +.RB "[ \--\fBmode\fP \fIstereomode\fP ]" +.br +.B lmms .RB "[ \--\fBoutput\fP \fIpath\fP ]" .br .B lmms @@ -94,7 +97,7 @@ Get the configuration from \fIconfigfile\fP instead of ~/.lmmsrc.xml (default) .IP "\fB\-d, --dump\fP \fIin\fP Dump XML of compressed file \fIin\fP (i.e. MMPZ-file) .IP "\fB\-f, --format\fP \fIformat\fP -Specify format of render-output where \fIformat\fP is either 'wav' or 'ogg' +Specify format of render-output where \fIformat\fP is either 'wav', 'ogg' or 'mp3'. .IP "\fB\ --geometry\fP \fIgeometry\fP Specify the prefered size and position of the main window .br @@ -111,6 +114,8 @@ Import MIDI file \fIin\fP If -e is specified lmms exits after importing the file. .IP "\fB\-l, --loop Render the given file as a loop, i.e. stop rendering at exactly the end of the song. Additional silence or reverb tails at the end of the song are not rendered. +.IP "\fB\-m, --mode\fP \fIstereomode\fP +Set the stereo mode used for the MP3 export. \fIstereomode\fP can be either 's' (stereo mode), 'j' (joint stereo) or 'm' (mono). If no mode is given 'j' is used as the default. .IP "\fB\-o, --output\fP \fIpath\fP Render into \fIpath\fP .br diff --git a/src/core/main.cpp b/src/core/main.cpp index 617e3804d..8d30a1912 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -124,6 +124,7 @@ void printHelp() " [ -i ]\n" " [ --import [-e]]\n" " [ -l ]\n" + " [ -m ]\n" " [ -o ]\n" " [ -p ]\n" " [ -r ] [ options ]\n" @@ -138,7 +139,7 @@ void printHelp() "-c, --config Get the configuration from \n" "-d, --dump Dump XML of compressed file \n" "-f, --format Specify format of render-output where\n" - " Format is either 'wav' or 'ogg'.\n" + " Format is either 'wav', 'ogg' or 'mp3'.\n" " --geometry Specify the size and position of the main window\n" " geometry is .\n" "-h, --help Show this usage information and exit.\n" @@ -151,6 +152,12 @@ void printHelp() " --import [-e] Import MIDI file .\n" " If -e is specified lmms exits after importing the file.\n" "-l, --loop Render as a loop\n" + "-m, --mode Stereo mode used for MP3 export\n" + " Possible values: s, j, m\n" + " s: Stereo\n" + " j: Joint Stereo\n" + " m: Mono\n" + " Default: j\n" "-o, --output Render into \n" " For --render, provide a file path\n" " For --rendertracks, provide a directory path\n" @@ -260,7 +267,7 @@ int main( int argc, char * * argv ) new MainApplication( argc, argv ); Mixer::qualitySettings qs( Mixer::qualitySettings::Mode_HighQuality ); - OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::Depth_16Bit ); + OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::Depth_16Bit, OutputSettings::StereoMode_JointStereo ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; // second of two command-line parsing stages @@ -391,6 +398,12 @@ int main( int argc, char * * argv ) { eff = ProjectRenderer::OggFile; } +#endif +#ifdef LMMS_HAVE_MP3LAME + else if( ext == "mp3" ) + { + eff = ProjectRenderer::MP3File; + } #endif else { @@ -446,6 +459,38 @@ int main( int argc, char * * argv ) else { printf( "\nInvalid bitrate %s.\n\n" + "Try \"%s --help\" for more information.\n\n", argv[i], argv[0] ); + return EXIT_FAILURE; + } + } + else if( arg == "--mode" || arg == "-m" ) + { + ++i; + + if( i == argc ) + { + printf( "\nNo stereo mode specified.\n\n" + "Try \"%s --help\" for more information.\n\n", argv[0] ); + return EXIT_FAILURE; + } + + QString const mode( argv[i] ); + + if( mode == "s" ) + { + os.setStereoMode(OutputSettings::StereoMode_Stereo); + } + else if( mode == "j" ) + { + os.setStereoMode(OutputSettings::StereoMode_JointStereo); + } + else if( mode == "m" ) + { + os.setStereoMode(OutputSettings::StereoMode_Mono); + } + else + { + printf( "\nInvalid stereo mode %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i], argv[0] ); return EXIT_FAILURE; } From 21caf2be693484cc8032b707d9378c7dc4b3e42d Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Mon, 19 Jun 2017 22:35:24 +0900 Subject: [PATCH 58/65] Correct spellings in fft_helpers.cpp (#3645) * Correct spellings in fft_helpers.cpp change bandwith to bandwidth * Correct spellings in fft_helpers.cpp Changed bandwith to bandwidth --- src/core/fft_helpers.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/fft_helpers.cpp b/src/core/fft_helpers.cpp index 867ab0066..a17ebf719 100644 --- a/src/core/fft_helpers.cpp +++ b/src/core/fft_helpers.cpp @@ -160,7 +160,7 @@ int calc13octaveband31(float *absspec_buffer, float *subbands, int num_spec, flo { static const int onethirdoctavecenterfr[] = {20, 25, 31, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000}; int i, j; - float f_min, f_max, frequency, bandwith; + float f_min, f_max, frequency, bandwidth; int j_min, j_max=0; float fpower; @@ -189,13 +189,13 @@ static const int onethirdoctavecenterfr[] = {20, 25, 31, 40, 50, 63, 80, 100, 12 { subbands[i]=0; - // calculate bandwith for subband + // calculate bandwidth for subband frequency=onethirdoctavecenterfr[i]; - bandwith=(pow(2, 1.0/3.0)-1)*frequency; + bandwidth=(pow(2, 1.0/3.0)-1)*frequency; - f_min=frequency-bandwith/2.0; - f_max=frequency+bandwith/2.0; + f_min=frequency-bandwidth/2.0; + f_max=frequency+bandwidth/2.0; j_min=(int)(f_min/max_frequency*(float)num_spec); From afdd5ac8cbd0c08e91570198f97e447b1e4e12c8 Mon Sep 17 00:00:00 2001 From: Karmo Rosental Date: Sun, 25 Jun 2017 00:47:32 +0300 Subject: [PATCH 59/65] Fix for misaligned controls icon in VeSTige plugin. --- data/themes/default/track_op_menu.png | Bin 496 -> 0 bytes data/themes/default/track_op_menu_active.png | Bin 403 -> 0 bytes plugins/VstEffect/VstEffectControlDialog.cpp | 14 +++++++------- .../VstEffect/controls.png | Bin .../VstEffect/controls_active.png | Bin plugins/vestige/controls.png | Bin 0 -> 841 bytes plugins/vestige/controls_active.png | Bin 0 -> 824 bytes plugins/vestige/vestige.cpp | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) delete mode 100755 data/themes/default/track_op_menu.png delete mode 100755 data/themes/default/track_op_menu_active.png rename data/themes/classic/track_op_menu.png => plugins/VstEffect/controls.png (100%) rename data/themes/classic/track_op_menu_active.png => plugins/VstEffect/controls_active.png (100%) create mode 100644 plugins/vestige/controls.png create mode 100644 plugins/vestige/controls_active.png diff --git a/data/themes/default/track_op_menu.png b/data/themes/default/track_op_menu.png deleted file mode 100755 index 50c2b478cfe6ed780d8f6a92673ef2a208c821fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 496 zcmVcoPFj^#0fje^%C9T^DLOp^{*dw$s zd<28GE)x^n2!b%SI4$PRbQ>9)paTQn{X5^CIrl&XNE%8SR>?i6lJpnk4JDbRTS-S% zizOX-jb*-Ch~EI$z@?-yAfN+Wga!j(4om?9`oM{Y-*9LCwvey}>Ok3T6JQH8ffX<| zv*bs^&~)I5Rj>v|S+nBE_?Y!zZ-5D~aQh7C0mp9Fy(UNTFJJLJ<_35+vv)K5^k)W) zJcj3X_BA{Y=ZTqZ3vt}{T2e!84`CN2et?f~Zp9w|PVAbQJ!OXT?9|OOU{+EW$6T7( zOW6(Q;oOr{FT^uy-33=1f2ZM;l(dji<55yu(xE?X-~niOPU1B=iaa#Z4alg|0Pc&o z4A@dwjwlY<>NQC>AfX(I=QM$S6qI^eP*AeOHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nj}PZ!4! zi{7OwCt|etGV|*ereP`N#Fj9FGFbN ztv!LSg)Yb+h*kXlplI!PrWEdNT#9?r8>TLCKJ`00v3-sqx8J0=x#h78_jdf&OPX_} z{9<1f^Q##wF^tvIqf@7E75>7ycQf;fH&s0e%o{Wq|Hs}id{eYv?)lk7#x;%C8lw_Y u@AaQmyuHsYDlPowx6mNI8JCV)e-meQcHsQv%W(@BfDE3lelF{r5}E)HRi&K( diff --git a/plugins/VstEffect/VstEffectControlDialog.cpp b/plugins/VstEffect/VstEffectControlDialog.cpp index 49fa13315..3c6c84c89 100644 --- a/plugins/VstEffect/VstEffectControlDialog.cpp +++ b/plugins/VstEffect/VstEffectControlDialog.cpp @@ -88,9 +88,9 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_managePluginButton->setCheckable( false ); m_managePluginButton->setCursor( Qt::PointingHandCursor ); m_managePluginButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "track_op_menu" ) ); + "controls_active" ) ); m_managePluginButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "track_op_menu" ) ); + "controls" ) ); connect( m_managePluginButton, SIGNAL( clicked() ), _ctl, SLOT( managePlugin() ) ); ToolTip::add( m_managePluginButton, tr( "Control VST-plugin from LMMS host" ) ); @@ -98,8 +98,8 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_managePluginButton->setWhatsThis( tr( "Click here, if you want to control VST-plugin from host." ) ); - m_managePluginButton->setMinimumWidth( 21 ); - m_managePluginButton->setMaximumWidth( 21 ); + m_managePluginButton->setMinimumWidth( 26 ); + m_managePluginButton->setMaximumWidth( 26 ); m_managePluginButton->setMinimumHeight( 21 ); m_managePluginButton->setMaximumHeight( 21 ); @@ -216,7 +216,7 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : space1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QFont f( "Arial", 10 ); - l->addItem( new QSpacerItem( newSize - 20, 30, QSizePolicy::Fixed, + l->addItem( new QSpacerItem( newSize - 20, 30, QSizePolicy::Fixed, QSizePolicy::Fixed ), 1, 0 ); l->addWidget( resize, 2, 0, 1, 1, Qt::AlignCenter ); l->addWidget( m_pluginWidget, 3, 0, 1, 1, Qt::AlignCenter ); @@ -250,8 +250,8 @@ void VstEffectControlDialog::paintEvent( QPaintEvent * ) { if( m_plugin != NULL && tbLabel != NULL ) { - tbLabel->setText( tr( "Effect by: " ) + m_plugin->vendorString() + - tr( "       
" ) + + tbLabel->setText( tr( "Effect by: " ) + m_plugin->vendorString() + + tr( "       
" ) + m_plugin->currentProgramName() ); } } diff --git a/data/themes/classic/track_op_menu.png b/plugins/VstEffect/controls.png similarity index 100% rename from data/themes/classic/track_op_menu.png rename to plugins/VstEffect/controls.png diff --git a/data/themes/classic/track_op_menu_active.png b/plugins/VstEffect/controls_active.png similarity index 100% rename from data/themes/classic/track_op_menu_active.png rename to plugins/VstEffect/controls_active.png diff --git a/plugins/vestige/controls.png b/plugins/vestige/controls.png new file mode 100644 index 0000000000000000000000000000000000000000..4e03eb35112b855031479a20e45d7a0e6fdc2d55 GIT binary patch literal 841 zcmV-P1GfB$P)(*1K~zYI?UqewBt;a*f3LfGrdtD%)p z$-#pWJfsI@V3qai*(@GpvowR`5<)IHxx#`dS#c5ruHq$T$kh)B0|5oeOiy>!^N{p7 zZpI-j>>if=Lmj#wuYUEus1jA>VaR(Zl*gdQpkbNek0eQ60qP?167Z*pe7?NAeB&Y; zHJi=R>+7#sYrl5R zVXehE2cW>sIF1{DdS|Cu5s?o>M1mk73`5H0GEo%adESQ?2|3roA*zBD~O{gyEXW6b3C_O|-IkMH|<-f907X|mQbIXOv|Wq6)PyWMU9D?=cK zv#wLsR8@P$O4IZk-}f=b5Cj3n7)qrQVHgrc5k;CxrP9D|jKTMPvMjqZyqBt`1JJ#y zR4Tt+pe`ddF)=aH*Npl(J3D*XTFYrY&*E3bT01gzd7hK!Io4WcW@erT&UNWXP!I&~ z^!t6BbDW%<{Jpriczs+Ho12?g!!WGmc}}@pejE6BB&gYJuBhs@Vt$MKRnD^T?AqFz=6Q~)qN+FQ^?Jhth~xOHgM)*Xb564?qu1+^rYYTS zm%Y8c+xHar+}hpUz18h@2Yy+W;hfXM!^7_zjmB;Gca8*jy;iHeS*z8qRIAlz`u%=y ze}Dg{jg5_uRP~o}5sAp9g@uJr=H}*J2*dEnPN(zh&d$!=R;%?T@P(>68NSLzC9 zFb(v8pH=m{agZV37lF&b6Tt7lkE(h&u#b8(h=}NYJ|_OpcO>XR=+V3Q9zD=Mt+RG? T>c!$x00000NkvXXu0mjf?#z$! literal 0 HcmV?d00001 diff --git a/plugins/vestige/controls_active.png b/plugins/vestige/controls_active.png new file mode 100644 index 0000000000000000000000000000000000000000..5759cd7bbb449c4a99c32495fbf41e7ff3b3a955 GIT binary patch literal 824 zcmV-81IPS{P){ipR;gl8$i|RE@Zfc$m-ONxl-*`#o`+_`+Qw7_ zdnkP{huOF9{pP(lZ)O+~;V6V33FQQI0*Z?npH!>Wt6J;(jIk?>v7bcr+I8KPzj5Sw z-dz9>0Gt8vNo)OTdV2apq-Rkdd7k$IKmh<_jKMGraL&Ow&ySCfuMdJW8jWjO>$gfN zD5cQ#NdQ(|*IfWWybD5kkPSti9C&4?WL& z*-Kq6m)ii)jtsZ9w(h0V>9eM3!Z3^rhk=sGt=B z#`C<9Y&Lt#G)>sHeR_L)n+(H%VHnt3C_qGrQk6=jAf*Ij43$cy3}871BA~nLh$tkY zAR0jsyyKiBa&B1`5{U$a5U_0Pt$f&iPFo27llJuph8(t2bQ zh9Nqg4z$)(tJOZw%*>R4?#a+?uIuyj^EdMO{8T2B$%J7Tl*{GX($dlsBKkTgBF5NA zp-^}>H#g@xj^p^g?-z^3>e|}c27n?FX%@fA8Dr-GOaVv(2msWH=;I(r%=R*XNdTt+ zd7R2#qm0000setCursor( Qt::PointingHandCursor ); m_managePluginButton->move( 216, 101 ); m_managePluginButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "track_op_menu_active" ) ); + "controls_active" ) ); m_managePluginButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "track_op_menu" ) ); + "controls" ) ); connect( m_managePluginButton, SIGNAL( clicked() ), this, SLOT( managePlugin() ) ); ToolTip::add( m_managePluginButton, tr( "Control VST-plugin from LMMS host" ) ); From 13e65b7f56c2fbc8e58277f76ba92ccc0e9e0c6c Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Thu, 22 Jun 2017 18:13:38 +0200 Subject: [PATCH 60/65] Ensure midi port exists before removing --- src/core/midi/MidiClient.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/midi/MidiClient.cpp b/src/core/midi/MidiClient.cpp index 9add4e80b..8ba70bcb7 100644 --- a/src/core/midi/MidiClient.cpp +++ b/src/core/midi/MidiClient.cpp @@ -67,6 +67,11 @@ void MidiClient::addPort( MidiPort* port ) void MidiClient::removePort( MidiPort* port ) { + if( ! port ) + { + return; + } + QVector::Iterator it = qFind( m_midiPorts.begin(), m_midiPorts.end(), port ); if( it != m_midiPorts.end() ) From 494714bf44705f01f56e1ed121fd80611f93bbe3 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Mon, 3 Jul 2017 20:37:46 +0900 Subject: [PATCH 61/65] Update classic/style.css with TabWidget, TrackView, PianoView and Fader colors (#3665) * Add TabWidget color settings to classic/style.css * update the classic theme * Revert unnecessary formattings * Fix style.css --- data/themes/classic/style.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 03e24ebcf..21a3b1551 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -139,6 +139,19 @@ PianoRoll { qproperty-textShadow: rgb( 240, 240, 240 ); } +TabWidget { + background-color: #5b6571; + qproperty-tabText: rgba(255, 255, 255, 180); + qproperty-tabTitleText: #fff; + qproperty-tabSelected: #61666b; + qproperty-tabBackground: #3c434b; + qproperty-tabBorder: #3c434b; +} + +GroupBox { + background-color: #5b6571; +} + /* main toolbar oscilloscope - can have transparent bg now */ VisualizationWidget { @@ -281,6 +294,11 @@ TrackContainerView QFrame{ background-color: #49515b; } +/* background for track controls */ +TrackView > QWidget { + background-color: #5b6571; +} + /* autoscroll, loop, stop behaviour toggle buttons */ /* track background colors */ @@ -523,6 +541,12 @@ PluginDescWidget:hover { background-color: #e0e0e0; } +/* piano widget */ + +PianoView { + background-color: #14171a; +} + /* font sizes for text buttons */ FxMixerView QPushButton, EffectRackView QPushButton, ControllerRackView QPushButton { @@ -543,6 +567,7 @@ FxLine { /* persistent peak markers for fx peak meters */ Fader { qproperty-peakGreen: rgb( 74, 253, 133); + qproperty-peakYellow: rgb(224, 222, 18); qproperty-peakRed: rgb( 255, 100, 100); } From c6c67b3c751b80a2915a644c49f7cf5d9a158233 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Fri, 7 Jul 2017 03:47:01 +0900 Subject: [PATCH 62/65] Fixes for project loading progress display (#3672) Fix project loading progress jumping back. Show the name of the track currently being loaded. --- include/Song.h | 5 +++++ src/core/Song.cpp | 21 +++++++++++++++++++++ src/core/TrackContainer.cpp | 18 +++++++++--------- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/include/Song.h b/include/Song.h index ad644ed9f..be7f9e508 100644 --- a/include/Song.h +++ b/include/Song.h @@ -97,6 +97,10 @@ public: void processNextBuffer(); + inline int getLoadingTrackCount() const + { + return m_nLoadingTrack; + } inline int getMilliseconds() const { return m_elapsedMilliSeconds; @@ -339,6 +343,7 @@ private: ControllerVector m_controllers; + int m_nLoadingTrack; QString m_fileName; QString m_oldFileName; diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 0732fd1d3..af359a881 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1054,6 +1054,27 @@ void Song::loadProject( const QString & fileName ) } node = dataFile.content().firstChild(); + + QDomNodeList tclist=dataFile.content().elementsByTagName("trackcontainer"); + m_nLoadingTrack=0; + for( int i=0,n=tclist.count(); igetLoadingTrackCount(), gui->mainWindow() ); pd->setWindowModality( Qt::ApplicationModal ); pd->setWindowTitle( tr( "Please wait..." ) ); pd->show(); } - else - { - start_val = pd->value(); - pd->setMaximum( pd->maximum() + - _this.childNodes().count() ); - } } QDomNode node = _this.firstChild(); @@ -124,6 +117,14 @@ void TrackContainer::loadSettings( const QDomElement & _this ) if( node.isElement() && !node.toElement().attribute( "metadata" ).toInt() ) { + QString trackName = node.toElement().hasAttribute( "name" ) ? + node.toElement().attribute( "name" ) : + node.firstChild().toElement().attribute( "name" ); + if( pd != NULL ) + { + pd->setLabelText( tr("Loading Track %1 (%2/Total %3)").arg( trackName ). + arg( pd->value() + 1 ).arg( Engine::getSong()->getLoadingTrackCount() ) ); + } Track::create( node.toElement(), this ); } node = node.nextSibling(); @@ -131,7 +132,6 @@ void TrackContainer::loadSettings( const QDomElement & _this ) if( pd != NULL ) { - pd->setValue( start_val + _this.childNodes().count() ); if( was_null ) { delete pd; From d65e1a361a60498f2371accf3b50b32ff834ab2f Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Fri, 7 Jul 2017 00:15:59 +0200 Subject: [PATCH 63/65] MIDI - Don't apply base velocity to all controller values. (#3678) --- src/core/midi/MidiPort.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index a9a28dc5e..43019876e 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -129,11 +129,11 @@ void MidiPort::processInEvent( const MidiEvent& event, const MidiTime& time ) { return; } - } - if( fixedInputVelocity() >= 0 && inEvent.velocity() > 0 ) - { - inEvent.setVelocity( fixedInputVelocity() ); + if( fixedInputVelocity() >= 0 && inEvent.velocity() > 0 ) + { + inEvent.setVelocity( fixedInputVelocity() ); + } } m_midiEventProcessor->processInEvent( inEvent, time ); From 020f1652bf08208458d53aa9d9fe038d1689b5d2 Mon Sep 17 00:00:00 2001 From: Oskar Wallgren Date: Mon, 3 Jul 2017 19:31:41 +0200 Subject: [PATCH 64/65] Clean up after render in ProjectRenderer destructor We need to wait with calling Mixer::restoreAudioDevice() and Mixer::changeQuality() after render until all threads have stopped. Moving these calls to ProjectRenderer::~ProjectRenderer() ensures all render theads are done. --- src/core/ProjectRenderer.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 10fb9eb0e..3c16bce96 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -103,6 +103,8 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings ProjectRenderer::~ProjectRenderer() { + Engine::mixer()->restoreAudioDevice(); // also deletes audio-dev + Engine::mixer()->changeQuality( m_oldQualitySettings ); } @@ -201,12 +203,8 @@ void ProjectRenderer::run() Engine::getSong()->stopExport(); - const QString f = m_fileDev->outputFile(); - - Engine::mixer()->restoreAudioDevice(); // also deletes audio-dev - Engine::mixer()->changeQuality( m_oldQualitySettings ); - // if the user aborted export-process, the file has to be deleted + const QString f = m_fileDev->outputFile(); if( m_abort ) { QFile( f ).remove(); From aa6d528c9894058641ab2d8ca51dcdcf82843743 Mon Sep 17 00:00:00 2001 From: Alexandre Almeida Date: Sat, 1 Jul 2017 11:01:19 -0300 Subject: [PATCH 65/65] Cancel track rename with Escape key --- include/TrackLabelButton.h | 5 +- include/TrackRenameLineEdit.h | 46 +++++++++++++++++++ src/gui/CMakeLists.txt | 1 + src/gui/widgets/TrackLabelButton.cpp | 7 +-- src/gui/widgets/TrackRenameLineEdit.cpp | 61 +++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 include/TrackRenameLineEdit.h create mode 100644 src/gui/widgets/TrackRenameLineEdit.cpp diff --git a/include/TrackLabelButton.h b/include/TrackLabelButton.h index 5b0e6c4a8..f1059bdbf 100644 --- a/include/TrackLabelButton.h +++ b/include/TrackLabelButton.h @@ -29,9 +29,10 @@ #include #include - class TrackView; +class TrackRenameLineEdit; + class TrackLabelButton : public QToolButton { @@ -60,7 +61,7 @@ protected: private: TrackView * m_trackView; QString m_iconName; - QLineEdit * m_renameLineEdit; + TrackRenameLineEdit * m_renameLineEdit; QRect m_buttonRect; QString elideName( const QString &name ); diff --git a/include/TrackRenameLineEdit.h b/include/TrackRenameLineEdit.h new file mode 100644 index 000000000..6883b9b05 --- /dev/null +++ b/include/TrackRenameLineEdit.h @@ -0,0 +1,46 @@ +/* + * TrackRenameLineEdit.h - class TrackRenameLineEdit + * + * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2017 Alexandre Almeida + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#ifndef TRACK_RENAME_LINE_EDIT_H +#define TRACK_RENAME_LINE_EDIT_H + +#include + +class TrackRenameLineEdit : public QLineEdit +{ + Q_OBJECT +public: + TrackRenameLineEdit( QWidget * parent ); + void show(); + +protected: + virtual void keyPressEvent( QKeyEvent * ke ); + +private: + QString m_oldName; +} ; + +#endif diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4d40dcc08..5b4050bca 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -85,6 +85,7 @@ SET(LMMS_SRCS gui/widgets/ToolButton.cpp gui/widgets/ToolTip.cpp gui/widgets/TrackLabelButton.cpp + gui/widgets/TrackRenameLineEdit.cpp gui/widgets/VisualizationWidget.cpp PARENT_SCOPE diff --git a/src/gui/widgets/TrackLabelButton.cpp b/src/gui/widgets/TrackLabelButton.cpp index 231509e74..db310a05e 100644 --- a/src/gui/widgets/TrackLabelButton.cpp +++ b/src/gui/widgets/TrackLabelButton.cpp @@ -36,6 +36,7 @@ #include "InstrumentTrack.h" #include "RenameDialog.h" #include "Song.h" +#include "TrackRenameLineEdit.h" @@ -48,7 +49,7 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : setAcceptDrops( true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) ); setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); - m_renameLineEdit = new QLineEdit( this ); + m_renameLineEdit = new TrackRenameLineEdit( this ); m_renameLineEdit->hide(); if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) @@ -83,8 +84,8 @@ void TrackLabelButton::rename() if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { QString txt = m_trackView->getTrack()->name(); - RenameDialog rename_dlg( txt ); - rename_dlg.exec(); + RenameDialog renameDlg( txt ); + renameDlg.exec(); if( txt != text() ) { m_trackView->getTrack()->setName( txt ); diff --git a/src/gui/widgets/TrackRenameLineEdit.cpp b/src/gui/widgets/TrackRenameLineEdit.cpp new file mode 100644 index 000000000..b68af3141 --- /dev/null +++ b/src/gui/widgets/TrackRenameLineEdit.cpp @@ -0,0 +1,61 @@ +/* + * TrackRenameLineEdit.cpp - implementation of class TrackRenameLineEdit, which + * represents the text field that appears when one + * double-clicks a track's label to rename it + * + * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2017 Alexandre Almeida + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#include "TrackRenameLineEdit.h" + +#include + + + +TrackRenameLineEdit::TrackRenameLineEdit( QWidget * parent ) : + QLineEdit( parent ) +{ +} + + + + +void TrackRenameLineEdit::show() +{ + m_oldName = text(); + QLineEdit::show(); +} + + + + +void TrackRenameLineEdit::keyPressEvent( QKeyEvent * ke ) +{ + if( ke->key() == Qt::Key_Escape ) + { + setText( m_oldName ); + hide(); + } + + QLineEdit::keyPressEvent( ke ); +}