diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 000000000..476165408 --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,2 @@ +# Label requiring a response +responseRequiredLabel: "response required" diff --git a/.travis/osx..install.sh b/.travis/osx..install.sh index 4542c7059..4f92db746 100755 --- a/.travis/osx..install.sh +++ b/.travis/osx..install.sh @@ -2,7 +2,7 @@ set -e -PACKAGES="cmake pkg-config libogg libvorbis lame libsndfile libsamplerate jack sdl libgig libsoundio stk portaudio node fltk qt5" +PACKAGES="cmake pkg-config libogg libvorbis lame libsndfile libsamplerate jack sdl libgig libsoundio stk fluid-synth portaudio node fltk qt5" if "${TRAVIS}"; then PACKAGES="$PACKAGES ccache" @@ -19,9 +19,4 @@ brew install $PACKAGES # fftw tries to install gcc which conflicts with travis brew install fftw --ignore-dependencies -# Recompile fluid-synth without CoreAudio per issues #649 -# Ruby formula must be a URL - -brew install --build-from-source "https://gist.githubusercontent.com/tresf/c9260c43270abd4ce66ff40359588435/raw/fluid-synth.rb" - sudo npm install -g appdmg diff --git a/CMakeLists.txt b/CMakeLists.txt index dde248972..6c67e9316 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,7 @@ INCLUDE(FindPkgConfig) STRING(TOUPPER "${CMAKE_PROJECT_NAME}" PROJECT_NAME_UCASE) -# Updated by maintenance tasks -SET(PROJECT_YEAR 2017) +SET(PROJECT_YEAR 2018) SET(PROJECT_AUTHOR "LMMS Developers") SET(PROJECT_URL "https://lmms.io") @@ -521,6 +520,22 @@ FILE(REMOVE include/lmmsconfig.h) FILE(GLOB LMMS_INCLUDES "${CMAKE_SOURCE_DIR}/include/*.h") LIST(SORT LMMS_INCLUDES) +# Get list of all committers from git history, ordered by number of commits. +# The CONTRIBUTORS file is used by AboutDialog. This information can be provided +# with -DCONTRIBUTORS=/path/to/CONTRIBUTORS instead. For instance, to generate +# this file for version 1.1.3, the command is: +# git shortlog -sne v1.1.3 | cut -c8- +FIND_PACKAGE(Git) +IF(GIT_FOUND AND NOT CONTRIBUTORS) + SET(CONTRIBUTORS "${CMAKE_BINARY_DIR}/CONTRIBUTORS") + EXECUTE_PROCESS( + COMMAND "${GIT_EXECUTABLE}" shortlog -sne + COMMAND cut -c8- + OUTPUT_FILE "${CONTRIBUTORS}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + TIMEOUT 1) +ENDIF() + # we somehow have to make LMMS-binary depend on MOC-files ADD_FILE_DEPENDENCIES("${CMAKE_BINARY_DIR}/lmmsconfig.h") diff --git a/cmake/modules/FindWine.cmake b/cmake/modules/FindWine.cmake index 939961c12..9967dd9ba 100644 --- a/cmake/modules/FindWine.cmake +++ b/cmake/modules/FindWine.cmake @@ -9,7 +9,7 @@ LIST(APPEND CMAKE_PREFIX_PATH /opt/wine-stable /opt/wine-devel /opt/wine-staging /usr/lib/wine/) -FIND_PATH(WINE_INCLUDE_DIR windows/windows.h PATH_SUFFIXES wine) +FIND_PATH(WINE_INCLUDE_DIR windows/windows.h PATH_SUFFIXES wine wine/wine) FIND_LIBRARY(WINE_LIBRARY NAMES wine PATH_SUFFIXES wine i386-linux-gnu/wine) FIND_PROGRAM(WINE_CXX NAMES wineg++ winegcc winegcc64 winegcc32 winegcc-stable diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index cf5c22400..1cf9375bc 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -127,7 +127,7 @@ PianoRoll { qproperty-noteBorders: true; /* boolean property, set false to have borderless notes */ qproperty-selectedNoteColor: rgb( 0, 125, 255 ); qproperty-barColor: #4afd85; - qproperty-markedSemitoneColor: rgba( 40, 40, 40, 200 ); + qproperty-markedSemitoneColor: rgba( 0, 255, 200, 60 ); /* Grid colors */ qproperty-lineColor: rgba( 128, 128, 128, 80 ); qproperty-beatLineColor: rgba( 128, 128, 128, 160 ); diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 701583433..a4a31ab6b 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -144,9 +144,9 @@ PianoRoll { qproperty-noteColor: #0bd556; qproperty-noteOpacity: 165; qproperty-noteBorders: false; /* boolean property, set false to have borderless notes */ - qproperty-selectedNoteColor: #006b65; + qproperty-selectedNoteColor: #064d79; qproperty-barColor: #078f3a; - qproperty-markedSemitoneColor: #06170E; + qproperty-markedSemitoneColor: rgba(255, 255, 255, 30); /* Grid colors */ qproperty-lineColor: #292929; qproperty-beatLineColor: #2d6b45; diff --git a/doc/CONTRIBUTORS b/doc/CONTRIBUTORS deleted file mode 100644 index e69de29bb..000000000 diff --git a/include/Track.h b/include/Track.h index 8b1bf5e90..6ac6e390f 100644 --- a/include/Track.h +++ b/include/Track.h @@ -300,8 +300,6 @@ private: TextFloat * m_hint; - MidiTime m_oldTime;// used for undo/redo while mouse-button is pressed - // qproperty fields QColor m_mutedColor; QColor m_mutedBackgroundColor; diff --git a/plugins/peak_controller_effect/peak_controller_effect.cpp b/plugins/peak_controller_effect/peak_controller_effect.cpp index e08abcb24..f7dbb14f9 100644 --- a/plugins/peak_controller_effect/peak_controller_effect.cpp +++ b/plugins/peak_controller_effect/peak_controller_effect.cpp @@ -26,6 +26,7 @@ #include "Controller.h" #include "Song.h" +#include "PresetPreviewPlayHandle.h" #include "PeakController.h" #include "peak_controller_effect.h" #include "lmms_math.h" @@ -67,7 +68,7 @@ PeakControllerEffect::PeakControllerEffect( m_autoController( NULL ) { m_autoController = new PeakController( Engine::getSong(), this ); - if( !Engine::getSong()->isLoadingProject() ) + if( !Engine::getSong()->isLoadingProject() && !PresetPreviewPlayHandle::isPreviewing() ) { Engine::getSong()->addController( m_autoController ); } diff --git a/plugins/peak_controller_effect/peak_controller_effect_controls.cpp b/plugins/peak_controller_effect/peak_controller_effect_controls.cpp index 816c0d961..96282d29c 100644 --- a/plugins/peak_controller_effect/peak_controller_effect_controls.cpp +++ b/plugins/peak_controller_effect/peak_controller_effect_controls.cpp @@ -29,7 +29,6 @@ #include "PeakController.h" #include "peak_controller_effect_controls.h" #include "peak_controller_effect.h" -#include "PresetPreviewPlayHandle.h" #include "Song.h" @@ -81,12 +80,6 @@ void PeakControllerEffectControls::loadSettings( const QDomElement & _this ) // TODO: Fix possible collision m_effect->m_effectId = rand(); } - - if( m_effect->m_autoController && PresetPreviewPlayHandle::isPreviewing() == true ) - { - delete m_effect->m_autoController; - m_effect->m_autoController = 0; - } } diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index fb2412517..63253d4c0 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -114,6 +114,12 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : m_notesRunning[i] = 0; } + +#if QT_VERSION_CHECK(FLUIDSYNTH_VERSION_MAJOR, FLUIDSYNTH_VERSION_MINOR, FLUIDSYNTH_VERSION_MICRO) >= QT_VERSION_CHECK(1,1,9) + // Deactivate all audio drivers in fluidsynth + const char *none[] = { NULL }; + fluid_audio_driver_register( none ); +#endif m_settings = new_fluid_settings(); //fluid_settings_setint( m_settings, (char *) "audio.period-size", engine::mixer()->framesPerPeriod() ); diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index b3dcc0e7e..b0a478232 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -49,6 +49,7 @@ SET(WINE_CXX_ARGS -I${CMAKE_BINARY_DIR} -I${CMAKE_SOURCE_DIR}/include -I${WINE_INCLUDE_BASE_DIR} + -I${WINE_INCLUDE_DIR}/windows -L${WINE_LIBRARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp -std=c++0x diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 04ff97731..71ebf74c0 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1450,7 +1450,6 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, case audioMasterAutomate: SHOW_CALLBACK( "amc: audioMasterAutomate\n" ); // index, value, returns 0 - _effect->setParameter( _effect, _index, _opt ); return 0; case audioMasterVersion: diff --git a/src/3rdparty/rpmalloc/rpmalloc b/src/3rdparty/rpmalloc/rpmalloc index 2e0479192..36b1942fb 160000 --- a/src/3rdparty/rpmalloc/rpmalloc +++ b/src/3rdparty/rpmalloc/rpmalloc @@ -1 +1 @@ -Subproject commit 2e0479192b8dfb15e0084969fdf06208cffbfd09 +Subproject commit 36b1942fbc309b139e56a03166ba19a87f28f26c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1453584ef..ff0ca8a8e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,7 +45,7 @@ INCLUDE(GenQrc) ADD_GEN_QRC(LMMS_RCC_OUT lmms.qrc "${CMAKE_SOURCE_DIR}/doc/AUTHORS" "${CMAKE_SOURCE_DIR}/LICENSE.txt" - "${CMAKE_SOURCE_DIR}/doc/CONTRIBUTORS" + "${CMAKE_BINARY_DIR}/CONTRIBUTORS" ) # Paths relative to lmms executable diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index a99223a9d..177a90437 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -26,6 +26,11 @@ #include #include #include +#if QT_VERSION >= 0x050000 +#include +#else +#include +#endif #include #include "ConfigManager.h" @@ -50,7 +55,11 @@ ConfigManager * ConfigManager::s_instanceOfMe = NULL; ConfigManager::ConfigManager() : m_lmmsRcFile( QDir::home().absolutePath() +"/.lmmsrc.xml" ), - m_workingDir( QDir::home().absolutePath() + "/lmms/"), + #if QT_VERSION >= 0x050000 + m_workingDir( QStandardPaths::writableLocation( QStandardPaths::DocumentsLocation ) + "/lmms/"), + #else + m_workingDir( QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation ) + "/lmms/"), + #endif m_dataDir( "data:/" ), m_artworkDir( defaultArtworkDir() ), m_vstDir( m_workingDir + "vst/" ), @@ -58,6 +67,10 @@ ConfigManager::ConfigManager() : m_sf2Dir( m_workingDir + SF2_PATH ), m_version( defaultVersion() ) { + // Detect < 1.2.0 working directory as a courtesy + if ( QFileInfo( QDir::home().absolutePath() + "/lmms/projects/" ).exists() ) + m_workingDir = QDir::home().absolutePath() + "/lmms/"; + if (! qgetenv("LMMS_DATA_DIR").isEmpty()) QDir::addSearchPath("data", QString::fromLocal8Bit(qgetenv("LMMS_DATA_DIR"))); @@ -227,7 +240,7 @@ bool ConfigManager::hasWorkingDir() const void ConfigManager::setWorkingDir( const QString & wd ) { - m_workingDir = ensureTrailingSlash( QFileInfo( wd ).canonicalFilePath() ); + m_workingDir = ensureTrailingSlash( QDir::cleanPath( wd ) ); } diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index 4d0c5e8ae..886899c04 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -188,11 +188,8 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b { return false; } - const bool exporting = Engine::getSong()->isExporting(); - if( exporting ) // strip infs/nans if exporting - { - MixHelpers::sanitize( _buf, _frames ); - } + + MixHelpers::sanitize( _buf, _frames ); bool moreEffects = false; for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); ++it ) @@ -200,10 +197,7 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b if( hasInputNoise || ( *it )->isRunning() ) { moreEffects |= ( *it )->processAudioBuffer( _buf, _frames ); - if( exporting ) // strip infs/nans if exporting - { - MixHelpers::sanitize( _buf, _frames ); - } + MixHelpers::sanitize( _buf, _frames ); } } diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index ac50cab99..51e66939a 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -117,7 +117,6 @@ void FxChannel::unmuteForSolo() void FxChannel::doProcessing() { const fpp_t fpp = Engine::mixer()->framesPerPeriod(); - const bool exporting = Engine::getSong()->isExporting(); if( m_muted == false ) { @@ -140,25 +139,21 @@ void FxChannel::doProcessing() if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data... { const float v = sender->m_volumeModel.value() * sendModel->value(); - if( exporting ) { MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp ); } - else { MixHelpers::addMultiplied( m_buffer, ch_buf, v, fpp ); } + MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp ); } else if( volBuf && sendBuf ) // both volume and send have sample-exact data { - if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } - else { MixHelpers::addMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } + MixHelpers::addSanitizedMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } else if( volBuf ) // volume has sample-exact data but send does not { const float v = sendModel->value(); - if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } - else { MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } + MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } else // vice versa { const float v = sender->m_volumeModel.value(); - if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } - else { MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } + MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } m_hasInput = true; } diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index fac61649c..e1a2e092f 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -82,6 +82,10 @@ bool sanitize( sampleFrame * src, int frames ) src[f][c] = 0.0f; found = true; } + else + { + src[f][c] = qBound( -4.0f, src[f][c], 4.0f ); + } } } return found; diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 976e3c96e..9e5e654a6 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -33,7 +33,6 @@ #include "Mixer.h" #include "EffectChain.h" #include "plugins/peak_controller_effect/peak_controller_effect.h" -#include "PresetPreviewPlayHandle.h" PeakControllerEffectVector PeakController::s_effects; int PeakController::m_getCount; @@ -64,11 +63,7 @@ PeakController::PeakController( Model * _parent, PeakController::~PeakController() { - //EffectChain::loadSettings() appends effect to EffectChain::m_effects - //When it's previewing, EffectChain::loadSettings() is not called - //Therefore, we shouldn't call removeEffect() as it is not even appended. - //NB: Most XML setting are loaded on preview, except controller fx. - if( m_peakEffect != NULL && m_peakEffect->effectChain() != NULL && PresetPreviewPlayHandle::isPreviewing() == false ) + if( m_peakEffect != NULL && m_peakEffect->effectChain() != NULL ) { m_peakEffect->effectChain()->removeEffect( m_peakEffect ); } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 2a470f4fe..9cde88cf5 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -1404,7 +1404,7 @@ QString SampleBuffer::tryToMakeRelative( const QString & file ) if( QFileInfo( file ).isRelative() == false ) { // Normalize the path - QString f = QFileInfo( file ).canonicalFilePath().replace( QDir::separator(), '/' ); + QString f( QDir::cleanPath( file ) ); // First, look in factory samples // Isolate "samples/" from "data:/samples/" @@ -1413,7 +1413,7 @@ QString SampleBuffer::tryToMakeRelative( const QString & file ) // Iterate over all valid "data:/" searchPaths for ( const QString & path : QDir::searchPaths( "data" ) ) { - QString samplesPath = QString( path + samplesSuffix ).replace( QDir::separator(), '/' ); + QString samplesPath = QDir::cleanPath( path + samplesSuffix ) + "/"; if ( f.startsWith( samplesPath ) ) { return QString( f ).mid( samplesPath.length() ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index e4bd41fc9..c8f68d459 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -741,30 +741,47 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) && !m_tco->getAutoResize() ) { m_action = ResizeLeft; - m_oldTime = m_tco->startPosition(); QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); - s_textFloat->setTitle( tr( "Current length" ) ); } else if( me->x() < width() - RESIZE_GRIP_WIDTH ) { m_action = Move; - m_oldTime = m_tco->startPosition(); QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); - s_textFloat->setTitle( tr( "Current position" ) ); } else if( !m_tco->getAutoResize() ) { m_action = Resize; - m_oldTime = m_tco->length(); QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); + } + + if( m_action == Move ) + { + s_textFloat->setTitle( tr( "Current position" ) ); + s_textFloat->setText( QString( "%1:%2" ). + arg( m_tco->startPosition().getTact() + 1 ). + arg( m_tco->startPosition().getTicks() % + MidiTime::ticksPerTact() ) ); + } + else if( m_action == Resize || m_action == ResizeLeft ) + { s_textFloat->setTitle( tr( "Current length" ) ); + s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). + arg( m_tco->length().getTact() ). + arg( m_tco->length().getTicks() % + MidiTime::ticksPerTact() ). + arg( m_tco->startPosition().getTact() + 1 ). + arg( m_tco->startPosition().getTicks() % + MidiTime::ticksPerTact() ). + arg( m_tco->endPosition().getTact() + 1 ). + arg( m_tco->endPosition().getTicks() % + MidiTime::ticksPerTact() ) ); } // s_textFloat->reparent( this ); // setup text-float as if TCO was already moved/resized - mouseMoveEvent( me ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); s_textFloat->show(); } @@ -891,8 +908,7 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) arg( m_tco->startPosition().getTact() + 1 ). arg( m_tco->startPosition().getTicks() % MidiTime::ticksPerTact() ) ); - s_textFloat->moveGlobal( this, QPoint( width() + 2, - height() + 2 ) ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) ); } else if( m_action == MoveSelection ) { @@ -980,8 +996,7 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) arg( m_tco->endPosition().getTact() + 1 ). arg( m_tco->endPosition().getTicks() % MidiTime::ticksPerTact() ) ); - s_textFloat->moveGlobal( this, QPoint( width() + 2, - height() + 2) ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } else { diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 788d38cba..5518eb81d 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -60,7 +60,7 @@ GuiApplication::GuiApplication() QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); #endif - // prompt the user to create the LMMS working directory (e.g. ~/lmms) if it doesn't exist + // prompt the user to create the LMMS working directory (e.g. ~/Documents/lmms) if it doesn't exist if ( !ConfigManager::inst()->hasWorkingDir() && QMessageBox::question( NULL, tr( "Working directory" ), diff --git a/src/gui/LmmsStyle.cpp b/src/gui/LmmsStyle.cpp index b883a2b2f..e57e29e47 100644 --- a/src/gui/LmmsStyle.cpp +++ b/src/gui/LmmsStyle.cpp @@ -177,6 +177,13 @@ void LmmsStyle::drawComplexControl( ComplexControl control, return; } } + else if (control == CC_MdiControls) + { + QStyleOptionComplex so(*option); + so.palette.setColor(QPalette::Button, QColor(223, 228, 236)); + QProxyStyle::drawComplexControl(control, &so, painter, widget); + return; + } /* else if( control == CC_ScrollBar ) { painter->fillRect( option->rect, QApplication::palette().color( QPalette::Active, @@ -365,4 +372,3 @@ void LmmsStyle::hoverColors( bool sunken, bool hover, bool active, QColor& color blend = QColor( 33, 33, 33 ); } } - diff --git a/src/gui/PianoView.cpp b/src/gui/PianoView.cpp index 351806936..2a64e72dd 100644 --- a/src/gui/PianoView.cpp +++ b/src/gui/PianoView.cpp @@ -327,7 +327,9 @@ void PianoView::modelChanged() */ int PianoView::getKeyFromMouse( const QPoint & _p ) const { - int key_num = (int)( (float) _p.x() / (float) PW_WHITE_KEY_WIDTH ); + int offset = _p.x() % PW_WHITE_KEY_WIDTH; + if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH; + int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH; for( int i = 0; i <= key_num; ++i ) { @@ -336,6 +338,13 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const ++key_num; } } + for( int i = 0; i >= key_num; --i ) + { + if ( Piano::isBlackKey( m_startKey+i ) ) + { + --key_num; + } + } key_num += m_startKey; @@ -345,16 +354,14 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const // then do extra checking whether the mouse-cursor is over // a black key if( key_num > 0 && Piano::isBlackKey( key_num-1 ) && - _p.x() % PW_WHITE_KEY_WIDTH <= - ( PW_WHITE_KEY_WIDTH / 2 ) - - ( PW_BLACK_KEY_WIDTH / 2 ) ) + offset <= ( PW_WHITE_KEY_WIDTH / 2 ) - + ( PW_BLACK_KEY_WIDTH / 2 ) ) { --key_num; } if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) && - _p.x() % PW_WHITE_KEY_WIDTH >= - ( PW_WHITE_KEY_WIDTH - - PW_BLACK_KEY_WIDTH / 2 ) ) + offset >= ( PW_WHITE_KEY_WIDTH - + PW_BLACK_KEY_WIDTH / 2 ) ) { ++key_num; } diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index c47a58873..bb19ff0ee 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -1077,8 +1077,8 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) inline void AutomationEditor::drawCross( QPainter & p ) { QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); - float level = getLevel( mouse_pos.y() ); int grid_bottom = height() - SCROLLBAR_SIZE - 1; + float level = getLevel( mouse_pos.y() ); float cross_y = m_y_auto ? grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( level - m_minLevel ) @@ -1087,13 +1087,23 @@ inline void AutomationEditor::drawCross( QPainter & p ) p.setPen( crossColor() ); p.drawLine( VALUES_WIDTH, (int) cross_y, width(), (int) cross_y ); - p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), - height() - SCROLLBAR_SIZE ); + p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), height() - SCROLLBAR_SIZE ); + + QPoint tt_pos = QCursor::pos(); - tt_pos.ry() -= 64; - tt_pos.rx() += 32; + tt_pos.ry() -= 51; + tt_pos.rx() += 26; + float scaledLevel = m_pattern->firstObject()->scaledValue( level ); - QToolTip::showText( tt_pos, QString::number( scaledLevel ), this ); + + // Limit the scaled-level tooltip to the grid + if( mouse_pos.x() >= 0 && + mouse_pos.x() <= width() - SCROLLBAR_SIZE && + mouse_pos.y() >= 0 && + mouse_pos.y() <= height() - SCROLLBAR_SIZE ) + { + QToolTip::showText( tt_pos, QString::number( scaledLevel ), this ); + } } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 4a044891e..17fc7bd2e 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -891,10 +892,6 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x, for( timeMap::ConstIterator it = map.begin(); it != map.end(); ++it ) { int pos_ticks = it.key(); - if( pos_ticks > _n->length() ) - { - break; - } int pos_x = _x + pos_ticks * m_ppt / MidiTime::ticksPerTact(); const float level = it.value(); @@ -1340,8 +1337,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) if( m_editMode == ModeEditDetuning && noteUnderMouse() ) { - static AutomationPattern* detuningPattern = nullptr; - if (detuningPattern != nullptr) + static QPointer detuningPattern = nullptr; + if (detuningPattern.data() != nullptr) { detuningPattern->disconnect(this); } @@ -1351,7 +1348,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) n->createDetuning(); } detuningPattern = n->detuning()->automationPattern(); - connect(detuningPattern, SIGNAL(dataChanged()), this, SLOT(update())); + connect(detuningPattern.data(), SIGNAL(dataChanged()), this, SLOT(update())); gui->automationEditor()->open(detuningPattern); return; } @@ -2639,23 +2636,6 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) int key = m_startKey; - // display note marks before drawing other lines - for( int i = 0; i < m_markedSemiTones.size(); i++ ) - { - const int key_num = m_markedSemiTones.at( i ); - const int y = keyAreaBottom() + 5 - - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ); - - if( y > keyAreaBottom() ) - { - break; - } - - p.fillRect( WHITE_KEY_WIDTH + 1, y - KEY_LINE_HEIGHT / 2, width() - 10, KEY_LINE_HEIGHT, - markedSemitoneColor() ); - } - - // draw all white keys... for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN; key_line_y -= KEY_LINE_HEIGHT, ++keys_processed ) @@ -2919,7 +2899,6 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } } - // Draw the vertical beat lines int ticksPerBeat = DefaultTicksPerTact / Engine::getSong()->getTimeSigModel().getDenominator(); @@ -2940,8 +2919,23 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.setPen( barLineColor() ); p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN ); } - } + // draw marked semitones after the grid + for( int i = 0; i < m_markedSemiTones.size(); i++ ) + { + const int key_num = m_markedSemiTones.at( i ); + const int y = keyAreaBottom() + 5 + - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ); + + if( y > keyAreaBottom() ) + { + break; + } + + p.fillRect( WHITE_KEY_WIDTH + 1, y - KEY_LINE_HEIGHT / 2, width() - 10, KEY_LINE_HEIGHT + 1, + markedSemitoneColor() ); + } + } // following code draws all notes in visible area // and the note editing stuff (volume, panning, etc) @@ -4082,14 +4076,14 @@ PianoRollWindow::PianoRollWindow() : QAction* drawAction = editModeGroup->addAction( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ) ); QAction* eraseAction = editModeGroup->addAction( embed::getIconPixmap( "edit_erase" ), tr("Erase mode (Shift+E)" ) ); QAction* selectAction = editModeGroup->addAction( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ) ); - QAction* detuneAction = editModeGroup->addAction( embed::getIconPixmap( "automation" ), tr("Detune mode (Shift+T)" ) ); + QAction* pitchBendAction = editModeGroup->addAction( embed::getIconPixmap( "automation" ), tr("Pitch Bend mode (Shift+T)" ) ); drawAction->setChecked( true ); drawAction->setShortcut( Qt::SHIFT | Qt::Key_D ); eraseAction->setShortcut( Qt::SHIFT | Qt::Key_E ); selectAction->setShortcut( Qt::SHIFT | Qt::Key_S ); - detuneAction->setShortcut( Qt::SHIFT | Qt::Key_T ); + pitchBendAction->setShortcut( Qt::SHIFT | Qt::Key_T ); drawAction->setWhatsThis( tr( "Click here and draw mode will be activated. In this " @@ -4117,8 +4111,8 @@ PianoRollWindow::PianoRollWindow() : #else "Ctrl" ) ); #endif - detuneAction->setWhatsThis( - tr( "Click here and detune mode will be activated. " + pitchBendAction->setWhatsThis( + tr( "Click here and Pitch Bend mode will be activated. " "In this mode you can click a note to open its " "automation detuning. You can utilize this to slide " "notes from one to another. You can also press " @@ -4132,7 +4126,7 @@ PianoRollWindow::PianoRollWindow() : notesActionsToolBar->addAction( drawAction ); notesActionsToolBar->addAction( eraseAction ); notesActionsToolBar->addAction( selectAction ); - notesActionsToolBar->addAction( detuneAction ); + notesActionsToolBar->addAction( pitchBendAction ); notesActionsToolBar->addSeparator(); notesActionsToolBar->addAction( quantizeAction ); diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index 4d61e814c..4131a956e 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -393,14 +393,20 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) m_params->m_userWave.setAudioFile( StringPairDrag::decodeValue( _de ) ); m_userLfoBtn->model()->setValue( true ); + m_params->m_lfoWaveModel.setValue(EnvelopeAndLfoParameters::UserDefinedWave); _de->accept(); + update(); } else if( type == QString( "tco_%1" ).arg( Track::SampleTrack ) ) { DataFile dataFile( value.toUtf8() ); - m_params->m_userWave.setAudioFile( dataFile.content().firstChild().toElement(). attribute( "src" ) ); + m_params->m_userWave.setAudioFile( dataFile.content(). + firstChildElement().firstChildElement(). + firstChildElement().attribute( "src" ) ); m_userLfoBtn->model()->setValue( true ); + m_params->m_lfoWaveModel.setValue(EnvelopeAndLfoParameters::UserDefinedWave); _de->accept(); + update(); } } diff --git a/src/tracks/Pattern.cpp b/src/tracks/Pattern.cpp index 372c0bb1b..560cd73f7 100644 --- a/src/tracks/Pattern.cpp +++ b/src/tracks/Pattern.cpp @@ -900,8 +900,9 @@ void PatternView::paintEvent( QPaintEvent * ) } // Compute pixels per tact - const int baseWidth = fixedTCOs() ? parentWidget()->width() : width(); - const float pixelsPerTact = ( baseWidth - 2 * TCO_BORDER_WIDTH ) / (float) m_pat->length().getTact(); + const int baseWidth = fixedTCOs() ? parentWidget()->width() - 2 * TCO_BORDER_WIDTH + : width() - TCO_BORDER_WIDTH; + const float pixelsPerTact = baseWidth / (float) m_pat->length().getTact(); // Length of one tact/beat in the [0,1] x [0,1] coordinate system const float tactLength = 1. / m_pat->length().getTact();