diff --git a/.gitignore b/.gitignore index aa4f17ab8..771eba607 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /build /target .*.sw? +.DS_Store *~ /CMakeLists.txt.user /plugins/zynaddsubfx/zynaddsubfx/ExternalPrograms/Controller/Makefile diff --git a/.travis/osx..install.sh b/.travis/osx..install.sh index 4f92db746..e9968e65b 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 fluid-synth portaudio node fltk qt5" +PACKAGES="cmake pkg-config libogg libvorbis lame libsndfile libsamplerate jack sdl libgig libsoundio stk fluid-synth portaudio node fltk qt5 carla" if "${TRAVIS}"; then PACKAGES="$PACKAGES ccache" diff --git a/CMakeLists.txt b/CMakeLists.txt index a9283cea6..b82076c0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ 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) +OPTION(WANT_SNDIO "Include sndio support" ON) OPTION(WANT_SOUNDIO "Include libsoundio support" ON) OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON) OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON) @@ -88,6 +89,7 @@ IF(LMMS_BUILD_WIN32) SET(WANT_JACK OFF) SET(WANT_PULSEAUDIO OFF) SET(WANT_PORTAUDIO OFF) + SET(WANT_SNDIO OFF) SET(WANT_SOUNDIO OFF) SET(WANT_WINMM ON) SET(LMMS_HAVE_WINMM TRUE) @@ -212,7 +214,11 @@ ENDIF(WANT_TAP) # check for CARLA IF(WANT_CARLA) - PKG_CHECK_MODULES(CARLA carla-standalone>=1.9.5) + PKG_CHECK_MODULES(CARLA carla-native-plugin) + # look for carla under old name + IF(NOT CARLA_FOUND) + PKG_CHECK_MODULES(CARLA carla-standalone>=1.9.5) + ENDIF() IF(CARLA_FOUND) SET(LMMS_HAVE_CARLA TRUE) SET(STATUS_CARLA "OK") @@ -430,15 +436,16 @@ IF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE OR LMMS_BUILD_OPENBSD) FIND_PACKAGE(Threads) ENDIF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE OR LMMS_BUILD_OPENBSD) -# check for sndio (openbsd only, roaraudio won't work yet) -FIND_PACKAGE(Sndio) -IF(LMMS_BUILD_OPENBSD AND SNDIO_FOUND) - SET(LMMS_HAVE_SNDIO TRUE) - SET(STATUS_SNDIO "OK") -ELSE() - SET(STATUS_SNDIO "") - SET(SNDIO_LIBRARY "") -ENDIF() +# check for sndio (roaraudio won't work yet) +IF(WANT_SNDIO) + FIND_PACKAGE(Sndio) + IF(SNDIO_FOUND) + SET(LMMS_HAVE_SNDIO TRUE) + SET(STATUS_SNDIO "OK") + ELSE() + SET(STATUS_SNDIO "") + ENDIF(SNDIO_FOUND) +ENDIF(WANT_SNDIO) # check for WINE IF(WANT_VST) diff --git a/cmake/apple/install_apple.sh.in b/cmake/apple/install_apple.sh.in index 855b5245d..63dc8145e 100644 --- a/cmake/apple/install_apple.sh.in +++ b/cmake/apple/install_apple.sh.in @@ -58,6 +58,17 @@ install_name_tool -change @rpath/libZynAddSubFxCore.dylib \ install_name_tool -change @rpath/libZynAddSubFxCore.dylib \ @loader_path/../../$zynfmk \ "$APP/Contents/$zynlib" + +# Replace @rpath with @loader_path for Carla +# See also plugins/carlabase/CMakeLists.txt +# This MUST be done BEFORE calling macdeployqt +install_name_tool -change @rpath/libcarlabase.dylib \ + @loader_path/libcarlabase.dylib \ + "$APP/Contents/lib/lmms/libcarlapatchbay.so" + +install_name_tool -change @rpath/libcarlabase.dylib \ + @loader_path/libcarlabase.dylib \ + "$APP/Contents/lib/lmms/libcarlarack.so" # Link lmms binary _executables="${_executables} -executable=$APP/Contents/$zynbin" @@ -79,6 +90,28 @@ done # shellcheck disable=SC2086 macdeployqt "$APP" $_executables +# Carla is a standalone plugin. Remove library, look for it side-by-side LMMS.app +# This MUST be done AFTER calling macdeployqt +# +# For example: +# /Applications/LMMS.app +# /Applications/Carla.app +carlalibs=$(echo "@CARLA_LIBRARIES@"|tr ";" "\n") + +# Loop over all libcarlas, fix linking +for file in "$APP/Contents/lib/lmms/"libcarla*; do + _thisfile="$APP/Contents/lib/lmms/${file##*/}" + for lib in $carlalibs; do + _oldpath="../../Frameworks/lib${lib}.dylib" + _newpath="Carla.app/Contents/MacOS/lib${lib}.dylib" + # shellcheck disable=SC2086 + install_name_tool -change @loader_path/$_oldpath \ + @executable_path/../../../$_newpath \ + "$_thisfile" + rm -f "$APP/Contents/Frameworks/lib${lib}.dylib" + done +done + # Cleanup rm -rf "$APP/Contents/bin" echo -e "\nFinished.\n\n" diff --git a/cmake/modules/BuildPlugin.cmake b/cmake/modules/BuildPlugin.cmake index bcc89222c..efa3e5b46 100644 --- a/cmake/modules/BuildPlugin.cmake +++ b/cmake/modules/BuildPlugin.cmake @@ -65,7 +65,11 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) INSTALL(TARGETS ${PLUGIN_NAME} DESTINATION "${PLUGIN_DIR}") IF(LMMS_BUILD_APPLE) - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-bundle_loader \"${CMAKE_BINARY_DIR}/lmms\"") + IF ("${PLUGIN_LINK}" STREQUAL "SHARED") + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + ELSE() + SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-bundle_loader \"${CMAKE_BINARY_DIR}/lmms\"") + ENDIF() ADD_DEPENDENCIES(${PLUGIN_NAME} lmms) ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32 AND STRIP) diff --git a/doc/lmms.1 b/doc/lmms.1 index 1d8fa8672..a7e8be232 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 "June 15, 2017" +.TH LMMS 1 "September 10, 2018" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -19,125 +19,83 @@ lmms \- software for easy music production .SH SYNOPSIS .B lmms -.RB "[ \--\fBallowroot\fP ]" +.RB "[\fBglobal options...\fP] [\fBaction\fP [\fBaction parameters\fP...]] .br -.B lmms -.RB "[ \--\fBbitrate\fP \fIbitrate\fP ]" -.br -.B lmms -.RB "[ \--\fBconfig\fP \fIconfigfile\fP ]" -.br -.B lmms -.RB "[ \--\fBdump\fP \fIin\fP ]" -.br -.B lmms -.RB "[ \--\fBfloat\fP ]" -.br -.B lmms -.RB "[ \--\fBformat\fP \fIformat\fP ]" -.br -.B lmms -.RB "[ \--\fBgeometry\fP \fIgeometry\fP ]" -.br -.B lmms -.RB "[ \--\fBhelp\fP ]" -.br -.B lmms -.RB "[ \--\interpolation\fP \fImethod\fP ]" -.br -.B lmms -.RB "[ \--\fBimport\fP \fIin\fP [ \-e ] ]" -.br -.B lmms -.RB "[ \--\fBloop\fP ]" -.br -.B lmms -.RB "[ \--\fBmode\fP \fIstereomode\fP ]" -.br -.B lmms -.RB "[ \--\fBoutput\fP \fIpath\fP ]" -.br -.B lmms -.RB "[ \--\fBoversampling\fP \fIvalue\fP ]" -.br -.B lmms -.RB "[ \--\fBprofile\fP \fIout\fP ]" -.br -.B lmms -.RB "[ \--\fBrender\fP \fIfile\fP ] [options]" -.br -.B lmms -.RB "[ \--\fBsamplerate\fP \fIsamplerate\fP ]" -.br -.B lmms -.RB "[ \--\fBupgrade\fP \fIin\fP \fIout\fP ]" -.br -.B lmms -.RB "[ \--\fBversion\fP ]" -.br -.B lmms -.RI "[ file ]" .SH DESCRIPTION .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. .B LMMS -LMMS is a free cross-platform alternative to commercial programs like FL Studio®, which allow you to produce music with your computer. This includes the creation of melodies and beats, the synthesis and mixing of sounds, and arranging of samples. You can have fun with your MIDI-keyboard and much more; all in a user-friendly and modern interface. +is a free cross-platform alternative to commercial programs like FL Studio®, which allow you to produce music with your computer. This includes the creation of melodies and beats, the synthesis and mixing of sounds, and arranging of samples. You can have fun with your MIDI-keyboard and much more; all in a user-friendly and modern interface. LMMS features components such as a Song Editor, a Beat+Bassline Editor, a Piano Roll, an FX Mixer as well as many powerful instruments and effects. -.SH OPTIONS -.IP "\fB\-a, --float\fP -32bit float bit depth -.IP "\fB\-b, --bitrate\fP \fIbitrate\fP -Specify output bitrate in KBit/s (for OGG encoding only), default is 160 +.SH ACTIONS + +.IP " [\fIoptions\fP...] [\fIproject\fP] +Start LMMS in normal GUI mode. +.IP "\fBdump\fP \fIin\fP +Dump XML of compressed (MMPZ) file \fIin\fP. +.IP "\fBrender\fP \fIproject\fP [\fIoptions\fP...] +Render given project file. +.IP "\fBrendertracks\fP \fIproject\fP [\fIoptions\fP...] +Render each track to a different file. +.IP "\fBupgrade\fP \fIin\fP [\fIout\fP] +Upgrade file \fIin\fP and save as \fIout\fP. Standard out is used if no output file is specifed. + +.SH GLOBAL OPTIONS + +.IP "\fB\ --allowroot +Bypass root user startup check (use with caution). .IP "\fB\-c, --config\fP \fIconfigfile\fP -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', 'flac', 'ogg' or 'mp3'. -.IP "\fB\ --geometry\fP \fIgeometry\fP -Specify the prefered size and position of the main window -.br -\fIgeometry\fP syntax is <\fIxsize\fPx\fIysize\fP+\fIxoffset\fP+\fIyoffset\fP>. -.br -Default: full screen +Get the configuration from \fIconfigfile\fP instead of ~/.lmmsrc.xml (default). .IP "\fB\-h, --help\fP Show usage information and exit. -.IP "\fB\-i, --interpolation\fP \fImethod\fP -Specify interpolation method - possible values are \fIlinear\fP, \fIsincfastest\fP (default), \fIsincmedium\fP, \fIsincbest\fP -.IP "\fB\ --import\fP \fIin\fP \fB\-e\fP -Import MIDI file \fIin\fP +.IP "\fB\-v, --version +Show version information and exit. + +.SH OPTIONS IF NO ACTION IS GIVEN + +.IP "\fB\ --geometry\fP \fIgeometry\fP +Specify the prefered size and position of the main window. .br +\fIgeometry\fP syntax is \fIxsize\fPx\fIysize\fP+\fIxoffset\fP+\fIyoffset\fP. +.br +Default: full screen. +.IP "\fB\ --import\fP \fIin\fP \fB\-e\fP +Import MIDI or Hydrogen file \fIin\fP. +.br + +.SH OPTIONS FOR RENDER AND RENDERTRACKS + +.IP "\fB\-a, --float\fP +Use 32bit float bit depth. +.IP "\fB\-b, --bitrate\fP \fIbitrate\fP +Specify output bitrate in KBit/s (for OGG encoding only), default is 160. +.IP "\fB\-f, --format\fP \fIformat\fP +Specify format of render-output where \fIformat\fP is either 'wav', 'flac', 'ogg' or 'mp3'. +.IP "\fB\-i, --interpolation\fP \fImethod\fP +Specify interpolation method - possible values are \fIlinear\fP, \fIsincfastest\fP (default), \fIsincmedium\fP, \fIsincbest\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 +Render into \fIpath\fP. .br For --render, this is interpreted as a file path. .br For --render-tracks, this is interpreted as a path to an existing directory. -IP "\fB\-p, --profile\fP \fIout\fP -Dump profiling information to file \fIout\fP -.IP "\fB\-r, --render\fP \fIproject-file\fP -Render given file to either a wav\- or ogg\-file. See \fB\-f\fP for details -.IP "\fB\-r, --rendertracks\fP \fIproject-file\fP -Render each track into a separate wav\- or ogg\-file. See \fB\-f\fP for details +.IP "\fB\-p, --profile\fP \fIout\fP +Dump profiling information to file \fIout\fP. .IP "\fB\-s, --samplerate\fP \fIsamplerate\fP -Specify output samplerate in Hz - range is 44100 (default) to 192000 -.IP "\fB\-u, --upgrade\fP \fIin\fP \fIout\fP -Upgrade file \fIin\fP and save as \fIout\fP -.IP "\fB\-v, --version -Show version information and exit. +Specify output samplerate in Hz - range is 44100 (default) to 192000. .IP "\fB\-x, --oversampling\fP \fIvalue\fP -Specify oversampling, possible values: 1, 2 (default), 4, 8 -.IP "\fB\ --allowroot -Bypass root user startup check (use with caution). +Specify oversampling, possible values: 1, 2 (default), 4, 8. + .SH SEE ALSO .BR https://lmms.io/ .BR https://lmms.io/documentation/ diff --git a/include/DataFile.h b/include/DataFile.h index 5c634a6fa..5890693da 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -84,25 +84,6 @@ public: return m_type; } - // small helper class for adjusting application's locale settings - // when loading or saving floating point values rendered to strings - class LocaleHelper - { - public: - enum Modes - { - ModeLoad, - ModeSave, - ModeCount - }; - typedef Modes Mode; - - LocaleHelper( Mode mode ); - ~LocaleHelper(); - - }; - - private: static Type type( const QString& typeName ); static QString typeName( Type type ); diff --git a/include/LocaleHelper.h b/include/LocaleHelper.h new file mode 100644 index 000000000..c5d9d4c46 --- /dev/null +++ b/include/LocaleHelper.h @@ -0,0 +1,67 @@ +/* + * LocaleHelper.h - compatibility functions for handling decimal separators + * Providing helper functions which handle both periods and commas + * for decimal separators to load old projects correctly + * + * Copyright (c) 2014 Tobias Doerffel + * Copyright (c) 2018 Hyunjin Song + * + * 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 LOCALEHELPER_H +#define LOCALEHELPER_H + +#include + +#include +#include + +namespace LocaleHelper +{ +inline double toDouble(QString str, bool* ok = nullptr) +{ + bool isOkay; + double value; + QLocale c(QLocale::C); + c.setNumberOptions(QLocale::RejectGroupSeparator); + value = c.toDouble(str, &isOkay); + if (!isOkay) + { + QLocale german(QLocale::German); + german.setNumberOptions(QLocale::RejectGroupSeparator); + value = german.toDouble(str, &isOkay); + } + if (ok != nullptr) {*ok = isOkay;} + return value; +} + +inline float toFloat(QString str, bool* ok = nullptr) +{ + double d = toDouble(str, ok); + if (!std::isinf(d) && std::fabs(d) > std::numeric_limits::max()) + { + if (ok != nullptr) {*ok = false;} + return 0.0f; + } + return static_cast(d); +} +} + +#endif // LOCALEHELPER_H diff --git a/include/Mixer.h b/include/Mixer.h index 743547b0f..88ff92e4e 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -314,6 +314,9 @@ public: void requestChangeInModel(); void doneChangeInModel(); + static bool isAudioDevNameValid(QString name); + static bool isMidiDevNameValid(QString name); + signals: void qualitySettingsChanged(); diff --git a/include/Song.h b/include/Song.h index 1f012f1be..7ab1f54f6 100644 --- a/include/Song.h +++ b/include/Song.h @@ -87,10 +87,19 @@ public: { return m_currentFrame; } + inline void setJumped( const bool jumped ) + { + m_jumped = jumped; + } + inline bool jumped() const + { + return m_jumped; + } TimeLineWidget * m_timeLine; private: float m_currentFrame; + bool m_jumped; } ; diff --git a/include/VstSyncController.h b/include/VstSyncController.h index c9190a9d8..1c920182e 100644 --- a/include/VstSyncController.h +++ b/include/VstSyncController.h @@ -61,6 +61,11 @@ public: m_syncData->isCycle = false; } + void setPlaybackJumped( bool jumped ) + { + m_syncData->m_playbackJumped = jumped; + } + void update(); diff --git a/include/VstSyncData.h b/include/VstSyncData.h index a4c5db80b..d8694f1b2 100644 --- a/include/VstSyncData.h +++ b/include/VstSyncData.h @@ -49,6 +49,7 @@ struct VstSyncData bool hasSHM; float cycleStart; float cycleEnd; + bool m_playbackJumped; int m_bufferSize; int m_sampleRate; int m_bpm; diff --git a/plugins/Eq/EqFader.h b/plugins/Eq/EqFader.h index 6f9b26615..c588c2b92 100644 --- a/plugins/Eq/EqFader.h +++ b/plugins/Eq/EqFader.h @@ -80,7 +80,7 @@ private slots: { const float opl = getPeak_L(); const float opr = getPeak_R(); - const float fall_off = 1.2; + const float fallOff = 1.07; if( *m_lPeak > opl ) { setPeak_L( *m_lPeak ); @@ -88,7 +88,7 @@ private slots: } else { - setPeak_L( opl/fall_off ); + setPeak_L( opl/fallOff ); } if( *m_rPeak > opr ) @@ -98,7 +98,7 @@ private slots: } else { - setPeak_R( opr/fall_off ); + setPeak_R( opr/fallOff ); } update(); } diff --git a/plugins/Eq/EqSpectrumView.cpp b/plugins/Eq/EqSpectrumView.cpp index 79bf5b04a..959578499 100644 --- a/plugins/Eq/EqSpectrumView.cpp +++ b/plugins/Eq/EqSpectrumView.cpp @@ -223,7 +223,7 @@ void EqSpectrumView::paintEvent(QPaintEvent *event) float peak; m_path.moveTo( 0, height() ); m_peakSum = 0; - float fallOff = 1.2; + const float fallOff = 1.07; for( int x = 0; x < MAX_BANDS; ++x, ++bands ) { peak = ( fh * 2.0 / 3.0 * ( 20 * ( log10( *bands / energy ) ) - LOWER_Y ) / ( - LOWER_Y ) ); diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index fde08646f..c5aa75149 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -36,6 +36,7 @@ #include "TrackContainer.h" #include "BBTrack.h" #include "InstrumentTrack.h" +#include "LocaleHelper.h" #include "plugin_export.h" @@ -134,7 +135,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, { base_pitch += masterPitch; } - base_volume = it.attribute("volume", "100").toDouble()/100.0; + base_volume = LocaleHelper::toDouble(it.attribute("volume", "100"))/100.0; } if (n.nodeName() == "pattern") @@ -205,7 +206,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, { base_pitch += masterPitch; } - base_volume = it.attribute("volume", "100").toDouble() / 100.0; + base_volume = LocaleHelper::toDouble(it.attribute("volume", "100")) / 100.0; } if (n.nodeName() == "pattern") @@ -274,7 +275,7 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, // TODO interpret pan="0" fxch="0" pitchrange="1" MidiNote mnote; mnote.pitch = qMax(0, qMin(127, note.attribute("key", "0").toInt() + base_pitch)); - mnote.volume = qMin(qRound(base_volume * note.attribute("vol", "100").toDouble()), 127); + mnote.volume = qMin(qRound(base_volume * LocaleHelper::toDouble(note.attribute("vol", "100"))), 127); mnote.time = base_time + note.attribute("pos", "0").toInt(); mnote.duration = note.attribute("len", "0").toInt(); pat.push_back(mnote); diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index 07c7063c6..5d22f93bd 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -27,6 +27,7 @@ #include "VstEffectControls.h" #include "VstEffect.h" +#include "LocaleHelper.h" #include "MainWindow.h" #include "GuiApplication.h" #include @@ -85,8 +86,8 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) if( !( knobFModel[ i ]->isAutomated() || knobFModel[ i ]->controllerConnection() ) ) { - knobFModel[ i ]->setValue( (s_dumpValues.at( 2 ) ).toFloat() ); - knobFModel[ i ]->setInitValue( (s_dumpValues.at( 2 ) ).toFloat() ); + knobFModel[ i ]->setValue(LocaleHelper::toFloat(s_dumpValues.at(2))); + knobFModel[ i ]->setInitValue(LocaleHelper::toFloat(s_dumpValues.at(2))); } connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); @@ -373,7 +374,7 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * if( !hasKnobModel ) { sprintf( paramStr, "%d", i); - m_vi->knobFModel[ i ] = new FloatModel( ( s_dumpValues.at( 2 ) ).toFloat(), + m_vi->knobFModel[ i ] = new FloatModel( LocaleHelper::toFloat(s_dumpValues.at(2)), 0.0f, 1.0f, 0.01f, _eff, tr( paramStr ) ); } connect( m_vi->knobFModel[ i ], SIGNAL( dataChanged() ), this, @@ -437,7 +438,7 @@ void manageVSTEffectView::syncPlugin( void ) { sprintf( paramStr, "param%d", i ); s_dumpValues = dump[ paramStr ].split( ":" ); - f_value = ( s_dumpValues.at( 2 ) ).toFloat(); + f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); m_vi2->knobFModel[ i ]->setInitValue( f_value ); } diff --git a/plugins/carlabase/CMakeLists.txt b/plugins/carlabase/CMakeLists.txt index 48a637c62..0311ca8ee 100644 --- a/plugins/carlabase/CMakeLists.txt +++ b/plugins/carlabase/CMakeLists.txt @@ -1,3 +1,11 @@ +# For MacOS, use "OLD" RPATH install_name behavior +# This can be changed to "NEW" safely if install_apple.sh.in +# is updated to relink libcarlabase.dylib. MacOS 10.8 uses +# cmake 3.9.6, so this can be done at any time. +IF(NOT CMAKE_VERSION VERSION_LESS 3.9) + CMAKE_POLICY(SET CMP0068 OLD) +ENDIF() + if(LMMS_HAVE_CARLA) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${CARLA_INCLUDE_DIRS}) diff --git a/plugins/carlabase/carla.cpp b/plugins/carlabase/carla.cpp index 2cec98220..578c3ac65 100644 --- a/plugins/carlabase/carla.cpp +++ b/plugins/carlabase/carla.cpp @@ -1,7 +1,7 @@ /* * carla.cpp - Carla for LMMS * - * Copyright (C) 2014 Filipe Coelho + * Copyright (C) 2014-2018 Filipe Coelho * * This file is part of LMMS - https://lmms.io * @@ -24,9 +24,6 @@ #include "carla.h" -#define REAL_BUILD // FIXME this shouldn't be needed -#include "CarlaHost.h" - #include "Engine.h" #include "Song.h" #include "gui_templates.h" @@ -132,14 +129,6 @@ static const char* host_ui_save_file(NativeHostHandle, bool isDir, const char* t // ----------------------------------------------------------------------- -CARLA_EXPORT -const NativePluginDescriptor* carla_get_native_patchbay_plugin(); - -CARLA_EXPORT -const NativePluginDescriptor* carla_get_native_rack_plugin(); - -// ----------------------------------------------------------------------- - CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const Descriptor* const descriptor, const bool isPatchbay) : Instrument(instrumentTrack, descriptor), kIsPatchbay(isPatchbay), @@ -161,8 +150,9 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D path.cdUp(); resourcesPath = path.absolutePath() + "/share/carla/resources"; #elif defined(CARLA_OS_MAC) - // assume standard install location - resourcesPath = "/Applications/Carla.app/Contents/MacOS/resources"; + // parse prefix from dll filename + QDir path = QFileInfo(dllName).dir(); + resourcesPath = path.absolutePath() + "/resources"; #elif defined(CARLA_OS_WIN32) || defined(CARLA_OS_WIN64) // not yet supported #endif @@ -254,7 +244,7 @@ void CarlaInstrument::handleUiClosed() emit uiClosed(); } -intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt) +intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t, const intptr_t, void* const, const float) { intptr_t ret = 0; @@ -267,13 +257,10 @@ intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opco qApp->processEvents(); break; default: - break; + break; } return ret; - - // unused for now - (void)index; (void)value; (void)ptr; (void)opt; } // ------------------------------------------------------------------- @@ -448,9 +435,12 @@ bool CarlaInstrument::handleMidiEvent(const MidiEvent& event, const MidiTime&, f PluginView* CarlaInstrument::instantiateView(QWidget* parent) { +// Disable plugin focus per https://bugreports.qt.io/browse/QTBUG-30181 +#ifndef CARLA_OS_MAC if (QWidget* const window = parent->window()) fHost.uiParentId = window->winId(); else +#endif fHost.uiParentId = 0; std::free((char*)fHost.uiName); diff --git a/plugins/carlabase/carla.h b/plugins/carlabase/carla.h index 175a570d6..a4efb67e0 100644 --- a/plugins/carlabase/carla.h +++ b/plugins/carlabase/carla.h @@ -1,7 +1,7 @@ /* * carla.h - Carla for LMMS * - * Copyright (C) 2014 Filipe Coelho + * Copyright (C) 2014-2018 Filipe Coelho * * This file is part of LMMS - https://lmms.io * @@ -29,6 +29,20 @@ #include "plugin_export.h" #include "CarlaNative.h" +#define REAL_BUILD // FIXME this shouldn't be needed +#if CARLA_VERSION_HEX >= 0x010911 + #include "CarlaNativePlugin.h" +#else + #include "CarlaBackend.h" + #include "CarlaNative.h" + #include "CarlaUtils.h" + CARLA_EXPORT + const NativePluginDescriptor* carla_get_native_patchbay_plugin(); + + CARLA_EXPORT + const NativePluginDescriptor* carla_get_native_rack_plugin(); +#endif + #include "Instrument.h" #include "InstrumentView.h" @@ -44,7 +58,7 @@ public: CarlaInstrument(InstrumentTrack* const instrumentTrack, const Descriptor* const descriptor, const bool isPatchbay); virtual ~CarlaInstrument(); - // CarlaNative functions + // Carla NativeHostDescriptor functions uint32_t handleGetBufferSize() const; double handleGetSampleRate() const; bool handleIsOffline() const; diff --git a/plugins/carlapatchbay/carlapatchbay.cpp b/plugins/carlapatchbay/carlapatchbay.cpp index bfc757070..69c71ce68 100644 --- a/plugins/carlapatchbay/carlapatchbay.cpp +++ b/plugins/carlapatchbay/carlapatchbay.cpp @@ -1,7 +1,7 @@ /* * carlapatchbay.cpp - Carla for LMMS (Patchbay) * - * Copyright (C) 2014 Filipe Coelho + * Copyright (C) 2014-2018 Filipe Coelho * * This file is part of LMMS - https://lmms.io * @@ -36,7 +36,7 @@ Plugin::Descriptor PLUGIN_EXPORT carlapatchbay_plugin_descriptor = QT_TRANSLATE_NOOP( "pluginBrowser", "Carla Patchbay Instrument" ), "falkTX ", - 0x0195, + CARLA_VERSION_HEX, Plugin::Instrument, new PluginPixmapLoader( "logo" ), NULL, diff --git a/plugins/carlarack/carlarack.cpp b/plugins/carlarack/carlarack.cpp index 420bc3029..8bc7d372d 100644 --- a/plugins/carlarack/carlarack.cpp +++ b/plugins/carlarack/carlarack.cpp @@ -1,7 +1,7 @@ /* * carlarack.cpp - Carla for LMMS (Rack) * - * Copyright (C) 2014 Filipe Coelho + * Copyright (C) 2014-2018 Filipe Coelho * * This file is part of LMMS - https://lmms.io * @@ -36,7 +36,7 @@ Plugin::Descriptor PLUGIN_EXPORT carlarack_plugin_descriptor = QT_TRANSLATE_NOOP( "pluginBrowser", "Carla Rack Instrument" ), "falkTX ", - 0x0195, + CARLA_VERSION_HEX, Plugin::Instrument, new PluginPixmapLoader( "logo" ), NULL, diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 0aa7ffe09..594e41a64 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -45,7 +45,7 @@ #include "gui_templates.h" #include "InstrumentPlayHandle.h" #include "InstrumentTrack.h" - +#include "LocaleHelper.h" #include "MainWindow.h" #include "Mixer.h" #include "GuiApplication.h" @@ -209,8 +209,8 @@ void vestigeInstrument::loadSettings( const QDomElement & _this ) if( !( knobFModel[ i ]->isAutomated() || knobFModel[ i ]->controllerConnection() ) ) { - knobFModel[ i ]->setValue( ( s_dumpValues.at( 2 )).toFloat() ); - knobFModel[ i ]->setInitValue( ( s_dumpValues.at( 2 )).toFloat() ); + knobFModel[ i ]->setValue(LocaleHelper::toFloat(s_dumpValues.at(2))); + knobFModel[ i ]->setInitValue(LocaleHelper::toFloat(s_dumpValues.at(2))); } connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); @@ -965,7 +965,7 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume if( !hasKnobModel ) { sprintf( paramStr, "%d", i); - m_vi->knobFModel[ i ] = new FloatModel( (s_dumpValues.at( 2 )).toFloat(), + m_vi->knobFModel[ i ] = new FloatModel( LocaleHelper::toFloat(s_dumpValues.at(2)), 0.0f, 1.0f, 0.01f, castModel(), tr( paramStr ) ); } connect( m_vi->knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); @@ -1026,7 +1026,7 @@ void manageVestigeInstrumentView::syncPlugin( void ) { sprintf( paramStr, "param%d", i ); s_dumpValues = dump[ paramStr ].split( ":" ); - f_value = ( s_dumpValues.at( 2 ) ).toFloat(); + f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); m_vi->knobFModel[ i ]->setInitValue( f_value ); } diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index dffc897cb..8589c8c5c 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -120,6 +120,7 @@ class RemoteVstPlugin; RemoteVstPlugin * __plugin = NULL; HWND __MessageHwnd = NULL; +DWORD __processingThreadId = 0; //Returns the last Win32 error, in string format. Returns an empty string if there is no error. @@ -168,6 +169,7 @@ public: // set given sample-rate for plugin virtual void updateSampleRate() { + SuspendPlugin suspend( this ); pluginDispatch( effSetSampleRate, 0, 0, NULL, (float) sampleRate() ); } @@ -175,9 +177,20 @@ public: // set given buffer-size for plugin virtual void updateBufferSize() { + SuspendPlugin suspend( this ); pluginDispatch( effSetBlockSize, 0, bufferSize() ); } + void setResumed( bool resumed ) + { + m_resumed = resumed; + pluginDispatch( effMainsChanged, 0, resumed ? 1 : 0 ); + } + + inline bool isResumed() const + { + return m_resumed; + } inline bool isInitialized() const { @@ -260,7 +273,7 @@ public: } // has to be called as soon as input- or output-count changes - void updateInOutCount(); + int updateInOutCount(); inline void lockShm() { @@ -330,6 +343,24 @@ private: ClosePlugin } ; + struct SuspendPlugin { + SuspendPlugin( RemoteVstPlugin * plugin ) : + m_plugin( plugin ), + m_resumed( plugin->isResumed() ) + { + if( m_resumed ) { m_plugin->setResumed( false ); } + } + + ~SuspendPlugin() + { + if( m_resumed ) { m_plugin->setResumed( true ); } + } + + private: + RemoteVstPlugin * m_plugin; + bool m_resumed; + }; + // callback used by plugin for being able to communicate with it's host static intptr_t VST_CALL_CONV hostCallback( AEffect * _effect, int32_t _opcode, int32_t _index, intptr_t _value, @@ -360,6 +391,7 @@ private: int m_windowHeight; bool m_initialized; + bool m_resumed; bool m_processing; @@ -385,6 +417,7 @@ private: { float lastppqPos; float m_Timestamp; + int32_t m_lastFlags; } ; in * m_in; @@ -411,6 +444,7 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_windowWidth( 0 ), m_windowHeight( 0 ), m_initialized( false ), + m_resumed( false ), m_processing( false ), m_messageList(), m_shouldGiveIdle( false ), @@ -463,12 +497,14 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_vstSyncData->ppqPos = 0; m_vstSyncData->isCycle = false; m_vstSyncData->hasSHM = false; + m_vstSyncData->m_playbackJumped = false; m_vstSyncData->m_sampleRate = sampleRate(); } m_in = ( in* ) new char[ sizeof( in ) ]; m_in->lastppqPos = 0; m_in->m_Timestamp = -1; + m_in->m_lastFlags = 0; // process until we have loaded the plugin while( 1 ) @@ -488,7 +524,7 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : RemoteVstPlugin::~RemoteVstPlugin() { destroyEditor(); - pluginDispatch( effMainsChanged, 0, 0 ); + setResumed( false ); pluginDispatch( effClose ); #ifndef USE_QT_SHMEM // detach shared memory segment @@ -664,7 +700,7 @@ void RemoteVstPlugin::init( const std::string & _plugin_file ) pluginDispatch( effSetProgram, 0, 0 ); */ // request rate and blocksize - pluginDispatch( effMainsChanged, 0, 1 ); + setResumed( true ); debugMessage( "creating editor\n" ); initEditor(); @@ -1419,8 +1455,21 @@ void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) -void RemoteVstPlugin::updateInOutCount() +int RemoteVstPlugin::updateInOutCount() { + if( inputCount() == RemotePluginClient::inputCount() && + outputCount() == RemotePluginClient::outputCount() ) + { + return 1; + } + + if( GetCurrentThreadId() == __processingThreadId ) + { + debugMessage( "Plugin requested I/O change from processing " + "thread. Request denied; stability may suffer.\n" ); + return 0; + } + lockShm(); setShmIsValid( false ); @@ -1448,6 +1497,8 @@ void RemoteVstPlugin::updateInOutCount() { m_outputs = new float * [outputCount()]; } + + return 1; } @@ -1553,7 +1604,6 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, __plugin->m_in->m_Timestamp ) { _timeInfo.ppqPos = __plugin->m_vstSyncData->ppqPos; - _timeInfo.flags |= kVstTransportChanged; __plugin->m_in->lastppqPos = __plugin->m_vstSyncData->ppqPos; __plugin->m_in->m_Timestamp = __plugin->m_vstSyncData->ppqPos; } @@ -1580,6 +1630,14 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, _timeInfo.flags |= kVstBarsValid; + if( ( _timeInfo.flags & ( kVstTransportPlaying | kVstTransportCycleActive ) ) != + ( __plugin->m_in->m_lastFlags & ( kVstTransportPlaying | kVstTransportCycleActive ) ) + || __plugin->m_vstSyncData->m_playbackJumped ) + { + _timeInfo.flags |= kVstTransportChanged; + } + __plugin->m_in->m_lastFlags = _timeInfo.flags; + return (intptr_t) &_timeInfo; case audioMasterProcessEvents: @@ -1588,10 +1646,9 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, return 0; case audioMasterIOChanged: - __plugin->updateInOutCount(); SHOW_CALLBACK( "amc: audioMasterIOChanged\n" ); - // numInputs and/or numOutputs has changed - return 0; + // numInputs, numOutputs, and/or latency has changed + return __plugin->updateInOutCount(); #ifdef OLD_VST_SDK case audioMasterWantMidi: @@ -1678,6 +1735,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, #endif case audioMasterSizeWindow: + { SHOW_CALLBACK( "amc: audioMasterSizeWindow\n" ); if( __plugin->m_window == 0 ) { @@ -1685,8 +1743,13 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, } __plugin->m_windowWidth = _index; __plugin->m_windowHeight = _value; - SetWindowPos( __plugin->m_window, 0, 0, 0, - _index + 8, _value + 26, + HWND window = __plugin->m_window; + DWORD dwStyle = GetWindowLongPtr( window, GWL_STYLE ); + RECT windowSize = { 0, 0, (int) _index, (int) _value }; + AdjustWindowRect( &windowSize, dwStyle, false ); + SetWindowPos( window, 0, 0, 0, + windowSize.right - windowSize.left, + windowSize.bottom - windowSize.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER ); __plugin->sendMessage( @@ -1694,6 +1757,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, addInt( __plugin->m_windowWidth ). addInt( __plugin->m_windowHeight ) ); return 1; + } case audioMasterGetSampleRate: SHOW_CALLBACK( "amc: audioMasterGetSampleRate\n" ); @@ -1874,6 +1938,8 @@ void RemoteVstPlugin::processUIThreadMessages() DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) { + __processingThreadId = GetCurrentThreadId(); + RemoteVstPlugin * _this = static_cast( _param ); RemotePluginClient::message m; @@ -2004,6 +2070,7 @@ int main( int _argc, char * * _argv ) return -1; } + OleInitialize(nullptr); #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_SCHED_H // try to set realtime-priority @@ -2111,6 +2178,7 @@ int main( int _argc, char * * _argv ) delete __plugin; + OleUninitialize(); return 0; } diff --git a/plugins/vst_base/RemoteVstPlugin/CMakeLists.txt b/plugins/vst_base/RemoteVstPlugin/CMakeLists.txt index 1baf94064..2a9f84076 100644 --- a/plugins/vst_base/RemoteVstPlugin/CMakeLists.txt +++ b/plugins/vst_base/RemoteVstPlugin/CMakeLists.txt @@ -26,10 +26,12 @@ FOREACH( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) ENDFOREACH() set(EXE_NAME RemoteVstPlugin${BITNESS}) -add_executable(${EXE_NAME} +add_executable(${EXE_NAME} WIN32 ../RemoteVstPlugin.cpp ) +target_link_libraries(${EXE_NAME} ole32) + target_include_directories(${EXE_NAME} PRIVATE "${LMMS_SOURCE_DIR}/plugins/vst_base/common" @@ -37,6 +39,15 @@ target_include_directories(${EXE_NAME} "${LMMS_BINARY_DIR}" ) +# Workaround for missing WinMain +if(MSVC) + set_property(TARGET ${EXE_NAME} + APPEND + PROPERTY LINK_FLAGS "/entry:mainCRTStartup" +) +endif() + + if(WIN32) find_package(Qt5Core REQUIRED) target_link_libraries(${EXE_NAME} Qt5::Core) diff --git a/plugins/vst_base/RemoteVstPlugin32.cmake b/plugins/vst_base/RemoteVstPlugin32.cmake index a74c5b1f8..fa8255afe 100644 --- a/plugins/vst_base/RemoteVstPlugin32.cmake +++ b/plugins/vst_base/RemoteVstPlugin32.cmake @@ -45,7 +45,7 @@ ELSEIF(LMMS_BUILD_LINUX) CMAKE_ARGS "${EXTERNALPROJECT_CMAKE_ARGS}" "-DCMAKE_CXX_COMPILER=${WINEGCC}" - "-DCMAKE_CXX_FLAGS=-m32 -mwindows" + "-DCMAKE_CXX_FLAGS=-m32" ) ELSEIF(CMAKE_TOOLCHAIN_FILE_32) ExternalProject_Add(RemoteVstPlugin32 diff --git a/plugins/vst_base/RemoteVstPlugin64.cmake b/plugins/vst_base/RemoteVstPlugin64.cmake index 3922ce171..f802bc4b9 100644 --- a/plugins/vst_base/RemoteVstPlugin64.cmake +++ b/plugins/vst_base/RemoteVstPlugin64.cmake @@ -7,7 +7,7 @@ ELSEIF(LMMS_BUILD_LINUX) CMAKE_ARGS "${EXTERNALPROJECT_CMAKE_ARGS}" "-DCMAKE_CXX_COMPILER=${WINEGCC}" - "-DCMAKE_CXX_FLAGS=-m64 -mwindows" + "-DCMAKE_CXX_FLAGS=-m64" ) INSTALL(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/../RemoteVstPlugin64" "${CMAKE_CURRENT_BINARY_DIR}/../RemoteVstPlugin64.exe.so" DESTINATION "${PLUGIN_DIR}") ENDIF() diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 72bad8d60..5138a58a6 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -46,12 +46,17 @@ #include #ifdef LMMS_BUILD_WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif + # include # include #endif #include "ConfigManager.h" #include "GuiApplication.h" +#include "LocaleHelper.h" #include "MainWindow.h" #include "Mixer.h" #include "Song.h" @@ -214,6 +219,11 @@ void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) void VstPlugin::loadSettings( const QDomElement & _this ) { + if( _this.hasAttribute( "program" ) ) + { + setProgram( _this.attribute( "program" ).toInt() ); + } + const int num_params = _this.attribute( "numparams" ).toInt(); // if it exists try to load settings chunk if( _this.hasAttribute( "chunk" ) ) @@ -233,11 +243,6 @@ void VstPlugin::loadSettings( const QDomElement & _this ) } setParameterDump( dump ); } - - if( _this.hasAttribute( "program" ) ) - { - setProgram( _this.attribute( "program" ).toInt() ); - } } @@ -356,7 +361,7 @@ void VstPlugin::setParameterDump( const QMap & _pdump ) { ( *it ).section( ':', 0, 0 ).toInt(), "", - ( *it ).section( ':', 2, -1 ).toFloat() + LocaleHelper::toFloat((*it).section(':', 2, -1)) } ; m.addInt( item.index ); m.addString( item.shortLabel ); diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index 4eb827bb8..b7d990935 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -239,7 +239,6 @@ void ZynAddSubFxInstrument::loadSettings( const QDomElement & _this ) doc.appendChild( doc.importNode( data, true ) ); QTemporaryFile tf; - tf.setAutoRemove( false ); if( tf.open() ) { QByteArray a = doc.toString( 0 ).toUtf8(); diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index e3ed6e445..da5a2d15b 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -28,6 +28,7 @@ #include "AutomationPattern.h" #include "ControllerConnection.h" +#include "LocaleHelper.h" #include "Mixer.h" #include "ProjectJournal.h" @@ -180,7 +181,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& if( node.isElement() ) { changeID( node.toElement().attribute( "id" ).toInt() ); - setValue( node.toElement().attribute( "value" ).toFloat() ); + setValue( LocaleHelper::toFloat( node.toElement().attribute( "value" ) ) ); if( node.toElement().hasAttribute( "scale_type" ) ) { if( node.toElement().attribute( "scale_type" ) == "linear" ) @@ -201,7 +202,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& if( element.hasAttribute( name ) ) // attribute => read the element's value from the attribute list { - setInitValue( element.attribute( name ).toFloat() ); + setInitValue( LocaleHelper::toFloat( element.attribute( name ) ) ); } else { diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index ab74c6eab..86f2c7af8 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -28,6 +28,7 @@ #include "AutomationPatternView.h" #include "AutomationTrack.h" +#include "LocaleHelper.h" #include "Note.h" #include "ProjectJournal.h" #include "BBTrackContainer.h" @@ -144,11 +145,11 @@ void AutomationPattern::setProgressionType( void AutomationPattern::setTension( QString _new_tension ) { bool ok; - float nt = _new_tension.toFloat( & ok ); + float nt = LocaleHelper::toFloat(_new_tension, & ok); if( ok && nt > -0.01 && nt < 1.01 ) { - m_tension = _new_tension.toFloat(); + m_tension = nt; } } @@ -585,7 +586,7 @@ void AutomationPattern::loadSettings( const QDomElement & _this ) if( element.tagName() == "time" ) { m_timeMap[element.attribute( "pos" ).toInt()] - = element.attribute( "value" ).toFloat(); + = LocaleHelper::toFloat(element.attribute("value")); } else if( element.tagName() == "object" ) { diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index bf2a43e6e..129c9738b 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -38,6 +38,7 @@ #include "Effect.h" #include "embed.h" #include "GuiApplication.h" +#include "LocaleHelper.h" #include "PluginFactory.h" #include "ProjectVersion.h" #include "SongEditor.h" @@ -65,37 +66,6 @@ DataFile::typeDescStruct -DataFile::LocaleHelper::LocaleHelper( Mode mode ) -{ - switch( mode ) - { - case ModeLoad: - // set a locale for which QString::fromFloat() returns valid values if - // floating point separator is a comma - otherwise we would fail to load - // older projects made by people from various countries due to their - // locale settings - QLocale::setDefault( QLocale::German ); - break; - - case ModeSave: - // set default locale to C so that floating point decimals are rendered to - // strings with periods as decimal point instead of commas in some countries - QLocale::setDefault( QLocale::C ); - - default: break; - } -} - - - -DataFile::LocaleHelper::~LocaleHelper() -{ - // revert to original locale - QLocale::setDefault( QLocale::system() ); -} - - - DataFile::DataFile( Type type ) : QDomDocument( "lmms-project" ), @@ -416,8 +386,8 @@ void DataFile::upgrade_0_2_1_20070501() QDomElement el = list.item( i ).toElement(); if( el.attribute( "vol" ) != "" ) { - el.setAttribute( "vol", el.attribute( - "vol" ).toFloat() * 100.0f ); + el.setAttribute( "vol", LocaleHelper::toFloat( + el.attribute( "vol" ) ) * 100.0f ); } else { @@ -543,7 +513,7 @@ void DataFile::upgrade_0_2_1_20070508() QDomElement el = list.item( i ).toElement(); if( el.hasAttribute( "vol" ) ) { - float value = el.attribute( "vol" ).toFloat(); + float value = LocaleHelper::toFloat( el.attribute( "vol" ) ); value = roundf( value * 0.585786438f ); el.setAttribute( "vol", value ); } diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index f34f9dbbc..d749c4343 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -831,14 +831,138 @@ void Mixer::runChangesInModel() } } +bool Mixer::isAudioDevNameValid(QString name) +{ +#ifdef LMMS_HAVE_SDL + if (name == AudioSdl::name()) + { + return true; + } +#endif +#ifdef LMMS_HAVE_ALSA + if (name == AudioAlsa::name()) + { + return true; + } +#endif + + +#ifdef LMMS_HAVE_PULSEAUDIO + if (name == AudioPulseAudio::name()) + { + return true; + } +#endif + + +#ifdef LMMS_HAVE_OSS + if (name == AudioOss::name()) + { + return true; + } +#endif + +#ifdef LMMS_HAVE_SNDIO + if (name == AudioSndio::name()) + { + return true; + } +#endif + +#ifdef LMMS_HAVE_JACK + if (name == AudioJack::name()) + { + return true; + } +#endif + + +#ifdef LMMS_HAVE_PORTAUDIO + if (name == AudioPortAudio::name()) + { + return true; + } +#endif + + +#ifdef LMMS_HAVE_SOUNDIO + if (name == AudioSoundIo::name()) + { + return true; + } +#endif + + if (name == AudioDummy::name()) + { + return true; + } + + return false; +} + +bool Mixer::isMidiDevNameValid(QString name) +{ +#ifdef LMMS_HAVE_ALSA + if (name == MidiAlsaSeq::name() || name == MidiAlsaRaw::name()) + { + return true; + } +#endif + +#ifdef LMMS_HAVE_JACK + if (name == MidiJack::name()) + { + return true; + } +#endif + +#ifdef LMMS_HAVE_OSS + if (name == MidiOss::name()) + { + return true; + } +#endif + +#ifdef LMMS_HAVE_SNDIO + if (name == MidiSndio::name()) + { + return true; + } +#endif + +#ifdef LMMS_BUILD_WIN32 + if (name == MidiWinMM::name()) + { + return true; + } +#endif + +#ifdef LMMS_BUILD_APPLE + if (name == MidiApple::name()) + { + return true; + } +#endif + + if (name == MidiDummy::name()) + { + return true; + } + + return false; +} AudioDevice * Mixer::tryAudioDevices() { bool success_ful = false; AudioDevice * dev = NULL; QString dev_name = ConfigManager::inst()->value( "mixer", "audiodev" ); + if( !isAudioDevNameValid( dev_name ) ) + { + dev_name = ""; + } m_audioDevStartFailed = false; @@ -982,6 +1106,10 @@ MidiClient * Mixer::tryMidiClients() { QString client_name = ConfigManager::inst()->value( "mixer", "mididev" ); + if( !isMidiDevNameValid( client_name ) ) + { + client_name = ""; + } #ifdef LMMS_HAVE_ALSA if( client_name == MidiAlsaSeq::name() || client_name == "" ) diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 9b4483611..e48eb725c 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -187,6 +187,8 @@ void Song::savePos() void Song::processNextBuffer() { + m_vstSyncController.setPlaybackJumped( false ); + // if not playing, nothing to do if( m_playing == false ) { @@ -255,10 +257,21 @@ void Song::processNextBuffer() setToTime(tl->loopBegin()); m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); + + m_vstSyncController.setAbsolutePosition( + tl->loopBegin().getTicks() ); + m_vstSyncController.setPlaybackJumped( true ); + emit updateSampleTracks(); } } + if( m_playPos[m_playMode].jumped() ) + { + m_vstSyncController.setPlaybackJumped( true ); + m_playPos[m_playMode].setJumped( false ); + } + f_cnt_t framesPlayed = 0; const float framesPerTick = Engine::framesPerTick(); @@ -312,6 +325,7 @@ void Song::processNextBuffer() setToTimeByTicks(ticks); m_vstSyncController.setAbsolutePosition( ticks ); + m_vstSyncController.setPlaybackJumped( true ); } } m_playPos[m_playMode].setTicks( ticks ); @@ -326,8 +340,12 @@ void Song::processNextBuffer() // beginning of the range if( m_playPos[m_playMode] >= tl->loopEnd() ) { - m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); + ticks = tl->loopBegin().getTicks(); + m_playPos[m_playMode].setTicks( ticks ); setToTime(tl->loopBegin()); + + m_vstSyncController.setAbsolutePosition( ticks ); + m_vstSyncController.setPlaybackJumped( true ); } else if( m_playPos[m_playMode] == tl->loopEnd() - 1 ) { @@ -604,6 +622,7 @@ void Song::setPlayPos( tick_t ticks, PlayModes playMode ) m_elapsedMilliSeconds[playMode] += MidiTime::ticksToMilliseconds( ticks - ticksFromPlayMode, getTempo() ); m_playPos[playMode].setTicks( ticks ); m_playPos[playMode].setCurrentFrame( 0.0f ); + m_playPos[playMode].setJumped( true ); // send a signal if playposition changes during playback if( isPlaying() ) @@ -995,8 +1014,6 @@ void Song::loadProject( const QString & fileName ) clearErrors(); - DataFile::LocaleHelper localeHelper( DataFile::LocaleHelper::ModeLoad ); - Engine::mixer()->requestChangeInModel(); // get the header information from the DOM @@ -1147,8 +1164,6 @@ void Song::loadProject( const QString & fileName ) // only save current song as _filename and do nothing else bool Song::saveProjectFile( const QString & filename ) { - DataFile::LocaleHelper localeHelper( DataFile::LocaleHelper::ModeSave ); - DataFile dataFile( DataFile::SongProject ); m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" ); diff --git a/src/core/main.cpp b/src/core/main.cpp index 71440654e..255fe7f2b 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -141,65 +141,57 @@ void printHelp() { printf( "LMMS %s\n" "Copyright (c) %s\n\n" - "Usage: lmms [ -a ]\n" - " [ -b ]\n" - " [ -c ]\n" - " [ -d ]\n" - " [ -f ]\n" - " [ --geometry ]\n" - " [ -h ]\n" - " [ -i ]\n" - " [ --import [-e]]\n" - " [ -l ]\n" - " [ -m ]\n" - " [ -o ]\n" - " [ -p ]\n" - " [ -r ] [ options ]\n" - " [ -s ]\n" - " [ -u ]\n" - " [ -v ]\n" - " [ -x ]\n" - " [ ]\n\n" - "-a, --float 32bit float bit depth\n" - "-b, --bitrate Specify output bitrate in KBit/s\n" - " Default: 160.\n" - "-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', 'flac', '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" - "-i, --interpolation Specify interpolation method\n" - " Possible values:\n" - " - linear\n" - " - sincfastest (default)\n" - " - sincmedium\n" - " - sincbest\n" - " --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" - "-p, --profile Dump profiling information to file \n" - "-r, --render Render given project file\n" - " --rendertracks Render each track to a different file\n" - "-s, --samplerate Specify output samplerate in Hz\n" - " Range: 44100 (default) to 192000\n" - "-u, --upgrade [out] Upgrade file and save as \n" - " Standard out is used if no output file is specifed\n" - "-v, --version Show version information and exit.\n" - " --allowroot Bypass root user startup check (use with caution).\n" - "-x, --oversampling Specify oversampling\n" - " Possible values: 1, 2, 4, 8\n" - " Default: 2\n\n", + "Usage: lmms [global options...] [ [action parameters...]]\n\n" + "Actions:\n" + " [options...] [] Start LMMS in normal GUI mode\n" + " dump Dump XML of compressed file \n" + " render [options...] Render given project file\n" + " rendertracks [options...] Render each track to a different file\n" + " upgrade [out] Upgrade file and save as \n" + " Standard out is used if no output file\n" + " is specifed\n" + "\nGlobal options:\n" + " --allowroot Bypass root user startup check (use with\n" + " caution).\n" + " -c, --config Get the configuration from \n" + " -h, --help Show this usage information and exit.\n" + " -v, --version Show version information and exit.\n" + "\nOptions if no action is given:\n" + " --geometry Specify the size and position of\n" + " the main window\n" + " geometry is .\n" + " --import [-e] Import MIDI or Hydrogen file .\n" + " If -e is specified lmms exits after importing the file.\n" + "\nOptions for \"render\" and \"rendertracks\":\n" + " -a, --float Use 32bit float bit depth\n" + " -b, --bitrate Specify output bitrate in KBit/s\n" + " Default: 160.\n" + " -f, --format Specify format of render-output where\n" + " Format is either 'wav', 'flac', 'ogg' or 'mp3'.\n" + " -i, --interpolation Specify interpolation method\n" + " Possible values:\n" + " - linear\n" + " - sincfastest (default)\n" + " - sincmedium\n" + " - sincbest\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" + " If not specified, render will overwrite the input file\n" + " For \"rendertracks\", this might be required\n" + " -p, --profile Dump profiling information to file \n" + " -s, --samplerate Specify output samplerate in Hz\n" + " Range: 44100 (default) to 192000\n" + " -x, --oversampling Specify oversampling\n" + " Possible values: 1, 2, 4, 8\n" + " Default: 2\n\n", LMMS_VERSION, LMMS_PROJECT_COPYRIGHT ); } @@ -274,11 +266,11 @@ int main( int argc, char * * argv ) if( arg == "--help" || arg == "-h" || arg == "--version" || arg == "-v" || - arg == "--render" || arg == "-r" ) + arg == "render" || arg == "--render" || arg == "-r" ) { coreOnly = true; } - else if( arg == "--rendertracks" ) + else if( arg == "rendertracks" || arg == "--rendertracks" ) { coreOnly = true; renderTracks = true; @@ -333,7 +325,7 @@ int main( int argc, char * * argv ) printHelp(); return EXIT_SUCCESS; } - else if( arg == "--upgrade" || arg == "-u" ) + else if( arg == "upgrade" || arg == "--upgrade" || arg == "-u") { ++i; @@ -369,7 +361,7 @@ int main( int argc, char * * argv ) #endif } - else if( arg == "--dump" || arg == "-d" ) + else if( arg == "dump" || arg == "--dump" || arg == "-d" ) { ++i; @@ -386,7 +378,8 @@ int main( int argc, char * * argv ) return EXIT_SUCCESS; } - else if( arg == "--render" || arg == "-r" || arg == "--rendertracks" ) + else if( arg == "render" || arg == "--render" || arg == "-r" || + arg == "rendertracks" || arg == "--rendertracks" ) { ++i; diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 043ec3637..63053663a 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -590,7 +590,7 @@ void FxMixerView::updateFaders() { const float opl = m_fxChannelViews[i]->m_fader->getPeak_L(); const float opr = m_fxChannelViews[i]->m_fader->getPeak_R(); - const float fall_off = 1.2; + const float fallOff = 1.07; if( m->effectChannel(i)->m_peakLeft > opl ) { m_fxChannelViews[i]->m_fader->setPeak_L( m->effectChannel(i)->m_peakLeft ); @@ -598,7 +598,7 @@ void FxMixerView::updateFaders() } else { - m_fxChannelViews[i]->m_fader->setPeak_L( opl/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_L( opl/fallOff ); } if( m->effectChannel(i)->m_peakRight > opr ) @@ -608,7 +608,7 @@ void FxMixerView::updateFaders() } else { - m_fxChannelViews[i]->m_fader->setPeak_R( opr/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_R( opr/fallOff ); } } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 93fad8ef0..07a6dcf9c 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -213,7 +213,7 @@ MainWindow::MainWindow() : vbox->addWidget( w ); setCentralWidget( main_widget ); - m_updateTimer.start( 1000 / 20, this ); // 20 fps + m_updateTimer.start( 1000 / 60, this ); // 60 fps if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() ) { @@ -555,7 +555,9 @@ void MainWindow::finalize() } // look whether mixer failed to start the audio device selected by the // user and is using AudioDummy as a fallback - else if( Engine::mixer()->audioDevStartFailed() ) + // or the audio device is set to invalid one + else if( Engine::mixer()->audioDevStartFailed() || !Mixer::isAudioDevNameValid( + ConfigManager::inst()->value( "mixer", "audiodev" ) ) ) { // if so, offer the audio settings section of the setup dialog SetupDialog sd( SetupDialog::AudioSettings ); @@ -767,7 +769,10 @@ void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de ) // first restore the window, as attempting to resize a maximized window causes graphics glitching _w->setWindowState( _w->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized) ); - _w->resize( r.size() ); + // Check isEmpty() to work around corrupt project files with empty size + if ( ! r.size().isEmpty() ) { + _w->resize( r.size() ); + } _w->move( r.topLeft() ); // set the window to its correct minimized/maximized/restored state diff --git a/src/gui/SetupDialog.cpp b/src/gui/SetupDialog.cpp index 11a6d8386..857261eb1 100644 --- a/src/gui/SetupDialog.cpp +++ b/src/gui/SetupDialog.cpp @@ -812,7 +812,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : // If no preferred audio device is saved, save the current one QString audioDevName = ConfigManager::inst()->value( "mixer", "audiodev" ); - if( audioDevName.length() == 0 ) + if( m_audioInterfaces->findText(audioDevName) < 0 ) { audioDevName = Engine::mixer()->audioDevName(); ConfigManager::inst()->setValue( @@ -908,7 +908,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : QString midiDevName = ConfigManager::inst()->value( "mixer", "mididev" ); - if( midiDevName.length() == 0 ) + if( m_midiInterfaces->findText(midiDevName) < 0 ) { midiDevName = Engine::mixer()->midiClientName(); ConfigManager::inst()->setValue( diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 71b384dc8..4e51a19f4 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -364,6 +364,7 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) Engine::getSong()->setToTime(t, Song::Mode_None); } m_pos.setCurrentFrame( 0 ); + m_pos.setJumped( true ); updatePosition(); positionMarkerMoved(); break; diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 3d1828fe5..c3e26ae11 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -41,6 +41,7 @@ #include "embed.h" #include "gui_templates.h" #include "GuiApplication.h" +#include "LocaleHelper.h" #include "MainWindow.h" #include "ProjectJournal.h" #include "Song.h" @@ -558,7 +559,7 @@ void Knob::dropEvent( QDropEvent * _de ) QString val = StringPairDrag::decodeValue( _de ); if( type == "float_value" ) { - model()->setValue( val.toFloat() ); + model()->setValue( LocaleHelper::toFloat(val) ); _de->accept(); } else if( type == "automatable_model" ) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index e1101580f..298430b03 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1611,8 +1611,6 @@ void InstrumentTrackWindow::saveSettingsBtnClicked() !sfd.selectedFiles().isEmpty() && !sfd.selectedFiles().first().isEmpty() ) { - DataFile::LocaleHelper localeHelper( DataFile::LocaleHelper::ModeSave ); - DataFile dataFile( DataFile::InstrumentTrackSettings ); m_track->setSimpleSerializing(); m_track->saveSettings( dataFile, dataFile.content() );