Merge branch 'master' into dynamic-effect-dialog
This commit is contained in:
24
src/3rdparty/CMakeLists.txt
vendored
24
src/3rdparty/CMakeLists.txt
vendored
@@ -1,8 +1,3 @@
|
||||
set(CMAKE_C_FLAGS "")
|
||||
set(CMAKE_CXX_FLAGS "")
|
||||
set(CMAKE_C_FLAGS_DEBUG "")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "")
|
||||
|
||||
IF(LMMS_BUILD_LINUX AND WANT_VST)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(qt5-x11embed)
|
||||
@@ -10,3 +5,22 @@ ENDIF()
|
||||
|
||||
ADD_SUBDIRECTORY(rpmalloc)
|
||||
ADD_SUBDIRECTORY(weakjack)
|
||||
|
||||
# The lockless ring buffer library is compiled as part of the core
|
||||
SET(RINGBUFFER_DIR "${CMAKE_SOURCE_DIR}/src/3rdparty/ringbuffer/")
|
||||
SET(RINGBUFFER_DIR ${RINGBUFFER_DIR} PARENT_SCOPE)
|
||||
# Create a dummy ringbuffer_export.h, since ringbuffer is not compiled as a library
|
||||
FILE(WRITE ${CMAKE_BINARY_DIR}/src/ringbuffer_export.h
|
||||
"#include \"${CMAKE_BINARY_DIR}/src/lmms_export.h\"\n
|
||||
#define RINGBUFFER_EXPORT LMMS_EXPORT")
|
||||
# Enable MLOCK support for ringbuffer if available
|
||||
INCLUDE(CheckIncludeFiles)
|
||||
CHECK_INCLUDE_FILES(sys/mman.h HAVE_SYS_MMAN)
|
||||
IF(HAVE_SYS_MMAN AND NOT CMAKE_SYSTEM_NAME MATCHES "Haiku")
|
||||
SET(USE_MLOCK ON)
|
||||
ELSE()
|
||||
SET(USE_MLOCK OFF)
|
||||
ENDIF()
|
||||
# Generate ringbuffer configuration headers
|
||||
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-config.h.in ${CMAKE_BINARY_DIR}/src/ringbuffer-config.h)
|
||||
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-version.h.in ${CMAKE_BINARY_DIR}/src/ringbuffer-version.h)
|
||||
|
||||
1
src/3rdparty/jack2
vendored
Submodule
1
src/3rdparty/jack2
vendored
Submodule
Submodule src/3rdparty/jack2 added at db76dd6bb8
1
src/3rdparty/ringbuffer
vendored
Submodule
1
src/3rdparty/ringbuffer
vendored
Submodule
Submodule src/3rdparty/ringbuffer added at 1c46ef34a2
16
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
16
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
@@ -1,5 +1,3 @@
|
||||
set(CMAKE_C_FLAGS "-std=c11")
|
||||
|
||||
add_library(rpmalloc STATIC
|
||||
rpmalloc/rpmalloc/rpmalloc.c
|
||||
rpmalloc/rpmalloc/rpmalloc.h
|
||||
@@ -9,12 +7,26 @@ target_include_directories(rpmalloc PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc
|
||||
)
|
||||
|
||||
set_property(TARGET rpmalloc PROPERTY C_STANDARD 11)
|
||||
|
||||
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(rpmalloc
|
||||
PRIVATE -Wno-unused-variable
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT LMMS_BUILD_WIN32)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -D_GNU_SOURCE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -D_WIN32_WINNT=0x600
|
||||
)
|
||||
endif()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
# rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS,
|
||||
# which is only available starting with GCC 5
|
||||
|
||||
2
src/3rdparty/rpmalloc/rpmalloc
vendored
2
src/3rdparty/rpmalloc/rpmalloc
vendored
Submodule src/3rdparty/rpmalloc/rpmalloc updated: b5bdc18051...80daac0d53
2
src/3rdparty/weakjack/weakjack
vendored
2
src/3rdparty/weakjack/weakjack
vendored
Submodule src/3rdparty/weakjack/weakjack updated: cbb05c5256...fd11655be3
@@ -9,17 +9,20 @@ SET(LMMS_UIS "")
|
||||
SET(CMAKE_AUTOMOC ON)
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Enable C++11
|
||||
SET(CMAKE_CXX_STANDARD 11)
|
||||
# Enable C++17
|
||||
SET(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
IF(LMMS_BUILD_APPLE)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
ENDIF()
|
||||
|
||||
ADD_SUBDIRECTORY(common)
|
||||
ADD_SUBDIRECTORY(core)
|
||||
ADD_SUBDIRECTORY(gui)
|
||||
ADD_SUBDIRECTORY(tracks)
|
||||
|
||||
LIST(APPEND LMMS_SRCS ${LMMS_COMMON_SRCS})
|
||||
|
||||
QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS})
|
||||
INCLUDE_DIRECTORIES(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
@@ -27,6 +30,7 @@ INCLUDE_DIRECTORIES(
|
||||
"${CMAKE_BINARY_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${RINGBUFFER_DIR}/include"
|
||||
)
|
||||
|
||||
IF(WIN32 AND MSVC)
|
||||
@@ -51,7 +55,7 @@ ADD_GEN_QRC(LMMS_RCC_OUT lmms.qrc
|
||||
# Paths relative to lmms executable
|
||||
FILE(RELATIVE_PATH LIB_DIR_RELATIVE "/${BIN_DIR}" "/${LIB_DIR}")
|
||||
FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}")
|
||||
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS} ${PORTAUDIO_DEFINITIONS})
|
||||
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS})
|
||||
INCLUDE_DIRECTORIES(
|
||||
${JACK_INCLUDE_DIRS}
|
||||
${SAMPLERATE_INCLUDE_DIRS}
|
||||
@@ -60,9 +64,7 @@ INCLUDE_DIRECTORIES(
|
||||
${FFTW3F_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
IF(NOT ("${SDL2_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${SDL2_INCLUDE_DIR}")
|
||||
ELSEIF(NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
|
||||
IF(NOT LMMS_HAVE_SDL2 AND NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${SDL_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
@@ -73,10 +75,6 @@ IF(LMMS_HAVE_WEAKJACK)
|
||||
ADD_DEFINITIONS(-DUSE_WEAK_JACK=1 -DNO_JACK_METADATA=1)
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${PORTAUDIO_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${PORTAUDIO_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
@@ -85,10 +83,19 @@ IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}")
|
||||
IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LILV_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${SUIL_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
LIST(APPEND LMMS_SRCS "${RINGBUFFER_DIR}/src/lib/ringbuffer.cpp")
|
||||
|
||||
# Use libraries in non-standard directories (e.g., another version of Qt)
|
||||
IF(LMMS_BUILD_LINUX)
|
||||
LINK_LIBRARIES(-Wl,--enable-new-dtags)
|
||||
@@ -151,21 +158,35 @@ IF(LMMS_BUILD_HAIKU)
|
||||
SET(EXTRA_LIBRARIES "-lnetwork")
|
||||
ENDIF()
|
||||
|
||||
if(LMMS_HAVE_LIBRT)
|
||||
list(APPEND EXTRA_LIBRARIES "rt")
|
||||
endif()
|
||||
|
||||
if(LMMS_HAVE_PORTAUDIO)
|
||||
list(APPEND EXTRA_LIBRARIES portaudio)
|
||||
endif()
|
||||
|
||||
if(LMMS_HAVE_MP3LAME)
|
||||
list(APPEND EXTRA_LIBRARIES mp3lame::mp3lame)
|
||||
endif()
|
||||
|
||||
SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${QT_LIBRARIES}
|
||||
${ASOUND_LIBRARY}
|
||||
${SDL_LIBRARY}
|
||||
${SDL2_LIBRARY}
|
||||
${PORTAUDIO_LIBRARIES}
|
||||
${SOUNDIO_LIBRARY}
|
||||
${SNDIO_LIBRARIES}
|
||||
${PULSEAUDIO_LIBRARIES}
|
||||
${JACK_LIBRARIES}
|
||||
${OGGVORBIS_LIBRARIES}
|
||||
${LAME_LIBRARIES}
|
||||
${LV2_LIBRARIES}
|
||||
${SUIL_LIBRARIES}
|
||||
${LILV_LIBRARIES}
|
||||
${SAMPLERATE_LIBRARIES}
|
||||
${SNDFILE_LIBRARIES}
|
||||
${FFTW3F_LIBRARIES}
|
||||
${EXTRA_LIBRARIES}
|
||||
rpmalloc
|
||||
)
|
||||
@@ -186,210 +207,35 @@ FOREACH(LIB ${LMMS_REQUIRED_LIBS})
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
|
||||
|
||||
# Install
|
||||
IF(NOT MSVC)
|
||||
IF(LMMS_BUILD_WIN32)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES
|
||||
LINK_FLAGS "${LINK_FLAGS} -mwindows"
|
||||
ENABLE_EXPORTS ON
|
||||
IF(LMMS_BUILD_WIN32)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES
|
||||
ENABLE_EXPORTS ON
|
||||
)
|
||||
IF(NOT MSVC)
|
||||
SET_PROPERTY(TARGET lmms
|
||||
APPEND_STRING PROPERTY LINK_FLAGS " -mwindows"
|
||||
)
|
||||
|
||||
IF(LMMS_BUILD_MSYS)
|
||||
# ENABLE_EXPORTS property has no effect in some MSYS2 configurations.
|
||||
# Add the linker flag manually to create liblmms.dll.a import library
|
||||
SET_PROPERTY(TARGET lmms
|
||||
APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a"
|
||||
)
|
||||
ENDIF()
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
ADD_CUSTOM_COMMAND(TARGET lmms POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:lmms>")
|
||||
ENDIF()
|
||||
|
||||
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/Qt5Core.dll"
|
||||
"${MINGW_PREFIX}/bin/Qt5Gui.dll"
|
||||
"${MINGW_PREFIX}/bin/Qt5Widgets.dll"
|
||||
"${MINGW_PREFIX}/bin/Qt5Xml.dll"
|
||||
DESTINATION .)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/lib/qt5/plugins/platforms/qwindows.dll"
|
||||
DESTINATION ./platforms)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/libsamplerate-0.dll"
|
||||
"${MINGW_PREFIX}/bin/libsndfile-1.dll"
|
||||
"${MINGW_PREFIX}/bin/libvorbis-0.dll"
|
||||
"${MINGW_PREFIX}/bin/libvorbisenc-2.dll"
|
||||
"${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"
|
||||
"${MINGW_PREFIX}/bin/SDL.dll"
|
||||
"${MINGW_PREFIX}/bin/libglib-2.0-0.dll"
|
||||
"${MINGW_PREFIX}/bin/libgthread-2.0-0.dll"
|
||||
"${MINGW_PREFIX}/bin/zlib1.dll"
|
||||
"${MINGW_PREFIX}/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32/bin/libwinpthread-1.dll"
|
||||
DESTINATION .)
|
||||
IF(LMMS_BUILD_MSYS)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/libwinpthread-1.dll"
|
||||
"${MINGW_PREFIX}/bin/libgcc_s_seh-1.dll"
|
||||
"${MINGW_PREFIX}/bin/libstdc++-6.dll"
|
||||
DESTINATION .)
|
||||
ELSE()
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32/bin/libwinpthread-1.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
IF(FLTK_FOUND)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/libfltk.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
IF(FLUIDSYNTH_FOUND)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/libfluidsynth.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
IF(GIG_FOUND)
|
||||
# Handle libgig-*.dll
|
||||
FILE(GLOB GIG_LIBRARY "${MINGW_PREFIX}/bin/libgig-*.dll")
|
||||
INSTALL(FILES
|
||||
${GIG_LIBRARY}
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
IF(PORTAUDIO_FOUND)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/bin/libportaudio-2.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
IF(SOUNDIO_FOUND)
|
||||
INSTALL(FILES
|
||||
"${MINGW_PREFIX}/lib/libsoundio.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
|
||||
ELSE(LMMS_BUILD_WIN32)
|
||||
IF(NOT LMMS_BUILD_APPLE)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E")
|
||||
ENDIF(NOT LMMS_BUILD_APPLE)
|
||||
|
||||
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
|
||||
INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/man/man1/" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
ENDIF(LMMS_BUILD_WIN32)
|
||||
ELSE(NOT MSVC)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES
|
||||
ENABLE_EXPORTS ON
|
||||
)
|
||||
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
|
||||
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES
|
||||
LINK_FLAGS "${LINK_FLAGS} -mwindows"
|
||||
ENABLE_EXPORTS ON
|
||||
ENDIF()
|
||||
IF(LMMS_BUILD_MSYS)
|
||||
# ENABLE_EXPORTS property has no effect in some MSYS2 configurations.
|
||||
# Add the linker flag manually to create liblmms.dll.a import library
|
||||
SET_PROPERTY(TARGET lmms
|
||||
APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a"
|
||||
)
|
||||
ENDIF()
|
||||
ELSE()
|
||||
IF(NOT LMMS_BUILD_APPLE)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E")
|
||||
ENDIF(NOT LMMS_BUILD_APPLE)
|
||||
|
||||
#CMAKE_FIND_ROOT_PATH
|
||||
SET(VCPKG_ROOT ${CMAKE_FIND_ROOT_PATH})
|
||||
|
||||
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
|
||||
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/Qt5Core.dll"
|
||||
"${VCPKG_ROOT}/bin/Qt5Gui.dll"
|
||||
"${VCPKG_ROOT}/bin/Qt5Widgets.dll"
|
||||
"${VCPKG_ROOT}/bin/Qt5Xml.dll"
|
||||
if(CMAKE_INSTALL_MANDIR)
|
||||
SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR})
|
||||
ELSE(CMAKE_INSTALL_MANDIR)
|
||||
SET(INSTALL_MANDIR ${CMAKE_INSTALL_PREFIX}/share/man)
|
||||
ENDIF(CMAKE_INSTALL_MANDIR)
|
||||
INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz"
|
||||
DESTINATION "${INSTALL_MANDIR}/man1/"
|
||||
PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
|
||||
ENDIF()
|
||||
|
||||
"${VCPKG_ROOT}/bin/zlib1.dll"
|
||||
"${VCPKG_ROOT}/bin/jpeg62.dll"
|
||||
"${VCPKG_ROOT}/bin/libpng16.dll"
|
||||
"${VCPKG_ROOT}/bin/gthread-2.dll"
|
||||
"${VCPKG_ROOT}/bin/glib-2.dll"
|
||||
"${VCPKG_ROOT}/bin/harfbuzz.dll"
|
||||
"${VCPKG_ROOT}/bin/pcre2-16.dll"
|
||||
"${VCPKG_ROOT}/bin/double-conversion.dll"
|
||||
"${VCPKG_ROOT}/bin/freetype.dll"
|
||||
"${VCPKG_ROOT}/bin/libbz2.dll"
|
||||
"${VCPKG_ROOT}/bin/pcre.dll"
|
||||
"${VCPKG_ROOT}/bin/libiconv.dll"
|
||||
"${VCPKG_ROOT}/bin/libcharset.dll"
|
||||
"${VCPKG_ROOT}/bin/libintl.dll"
|
||||
DESTINATION .)
|
||||
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/plugins/platforms/qwindows.dll"
|
||||
DESTINATION ./platforms)
|
||||
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/libsndfile-1.dll"
|
||||
"${VCPKG_ROOT}/bin/ogg.dll"
|
||||
"${VCPKG_ROOT}/bin/vorbis.dll"
|
||||
"${VCPKG_ROOT}/bin/vorbisenc.dll"
|
||||
"${VCPKG_ROOT}/bin/FLAC.dll"
|
||||
"${VCPKG_ROOT}/bin/vorbisfile.dll"
|
||||
|
||||
"${VCPKG_ROOT}/bin/libsamplerate-0.dll"
|
||||
"${VCPKG_ROOT}/bin/SDL2.dll"
|
||||
"${VCPKG_ROOT}/bin/fftw3f.dll"
|
||||
DESTINATION .)
|
||||
|
||||
#not yet in vcpkg
|
||||
#IF(LAME_FOUND)
|
||||
# INSTALL(FILES
|
||||
# "${VCPKG_ROOT}/bin/libmp3lame-0.dll"
|
||||
# DESTINATION .)
|
||||
#ENDIF(LAME_FOUND)
|
||||
|
||||
IF(FLTK_FOUND)
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/libfltk_SHARED.dll"
|
||||
|
||||
"${VCPKG_ROOT}/bin/zlib1.dll"
|
||||
"${VCPKG_ROOT}/bin/jpeg62.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
|
||||
IF(FLUIDSYNTH_FOUND)
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/libfluidsynth-1.dll"
|
||||
"${VCPKG_ROOT}/bin/glib-2.dll"
|
||||
"${VCPKG_ROOT}/bin/pcre.dll"
|
||||
"${VCPKG_ROOT}/bin/libiconv.dll"
|
||||
"${VCPKG_ROOT}/bin/libcharset.dll"
|
||||
"${VCPKG_ROOT}/bin/libintl.dll"
|
||||
DESTINATION .)
|
||||
ENDIF()
|
||||
|
||||
#not yet included in vcpkg
|
||||
#IF(GIG_FOUND)
|
||||
# # Handle libgig-*.dll
|
||||
# FILE(GLOB GIG_LIBRARY "${VCPKG_ROOT}/bin/libgig-*.dll")
|
||||
# INSTALL(FILES
|
||||
# ${GIG_LIBRARY}
|
||||
# DESTINATION .)
|
||||
#ENDIF()
|
||||
|
||||
IF(PORTAUDIO_FOUND)
|
||||
IF(LMMS_BUILD_WIN64)
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/portaudio-x64.dll"
|
||||
DESTINATION .)
|
||||
ELSE(LMMS_BUILD_WIN64)
|
||||
INSTALL(FILES
|
||||
"${VCPKG_ROOT}/bin/portaudio-x86.dll"
|
||||
DESTINATION .)
|
||||
ENDIF(LMMS_BUILD_WIN64)
|
||||
ENDIF()
|
||||
|
||||
#not yet in vcpkg
|
||||
#IF(SOUNDIO_FOUND)
|
||||
# INSTALL(FILES
|
||||
# "${VCPKG_ROOT}/bin/libsoundio.dll"
|
||||
# DESTINATION .)
|
||||
#ENDIF()
|
||||
ENDIF(NOT MSVC)
|
||||
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
|
||||
|
||||
10
src/common/CMakeLists.txt
Normal file
10
src/common/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
set(COMMON_SRCS
|
||||
RemotePluginBase.cpp
|
||||
SharedMemory.cpp
|
||||
)
|
||||
|
||||
foreach(SRC ${COMMON_SRCS})
|
||||
list(APPEND LMMS_COMMON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}")
|
||||
endforeach()
|
||||
|
||||
set(LMMS_COMMON_SRCS ${LMMS_COMMON_SRCS} PARENT_SCOPE)
|
||||
194
src/common/RemotePluginBase.cpp
Normal file
194
src/common/RemotePluginBase.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* RemotePluginBase.cpp - base class providing RPC like mechanisms
|
||||
*
|
||||
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "RemotePluginBase.h"
|
||||
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
#include <QCoreApplication>
|
||||
#endif
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
RemotePluginBase::RemotePluginBase( shmFifo * _in, shmFifo * _out ) :
|
||||
m_in( _in ),
|
||||
m_out( _out )
|
||||
#else
|
||||
RemotePluginBase::RemotePluginBase() :
|
||||
m_socket( -1 ),
|
||||
m_invalid( false )
|
||||
#endif
|
||||
{
|
||||
#ifdef LMMS_HAVE_LOCALE_H
|
||||
// make sure, we're using common ways to print/scan
|
||||
// floats to/from strings (',' vs. '.' for decimal point etc.)
|
||||
setlocale( LC_NUMERIC, "C" );
|
||||
#endif
|
||||
#ifndef SYNC_WITH_SHM_FIFO
|
||||
pthread_mutex_init( &m_receiveMutex, nullptr );
|
||||
pthread_mutex_init( &m_sendMutex, nullptr );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
RemotePluginBase::~RemotePluginBase()
|
||||
{
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
delete m_in;
|
||||
delete m_out;
|
||||
#else
|
||||
pthread_mutex_destroy( &m_receiveMutex );
|
||||
pthread_mutex_destroy( &m_sendMutex );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int RemotePluginBase::sendMessage( const message & _m )
|
||||
{
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
m_out->lock();
|
||||
m_out->writeInt( _m.id );
|
||||
m_out->writeInt( _m.data.size() );
|
||||
int j = 8;
|
||||
for( unsigned int i = 0; i < _m.data.size(); ++i )
|
||||
{
|
||||
m_out->writeString( _m.data[i] );
|
||||
j += 4 + _m.data[i].size();
|
||||
}
|
||||
m_out->unlock();
|
||||
m_out->messageSent();
|
||||
#else
|
||||
pthread_mutex_lock( &m_sendMutex );
|
||||
writeInt( _m.id );
|
||||
writeInt( _m.data.size() );
|
||||
int j = 8;
|
||||
for (const auto& str : _m.data)
|
||||
{
|
||||
writeString(str);
|
||||
j += 4 + str.size();
|
||||
}
|
||||
pthread_mutex_unlock( &m_sendMutex );
|
||||
#endif
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
RemotePluginBase::message RemotePluginBase::receiveMessage()
|
||||
{
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
m_in->waitForMessage();
|
||||
m_in->lock();
|
||||
message m;
|
||||
m.id = m_in->readInt();
|
||||
const int s = m_in->readInt();
|
||||
for( int i = 0; i < s; ++i )
|
||||
{
|
||||
m.data.push_back( m_in->readString() );
|
||||
}
|
||||
m_in->unlock();
|
||||
#else
|
||||
pthread_mutex_lock( &m_receiveMutex );
|
||||
message m;
|
||||
m.id = readInt();
|
||||
const int s = readInt();
|
||||
for( int i = 0; i < s; ++i )
|
||||
{
|
||||
m.data.push_back( readString() );
|
||||
}
|
||||
pthread_mutex_unlock( &m_receiveMutex );
|
||||
#endif
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
RemotePluginBase::message RemotePluginBase::waitForMessage(
|
||||
const message & _wm,
|
||||
bool _busy_waiting )
|
||||
{
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
if( _busy_waiting )
|
||||
{
|
||||
// No point processing events outside of the main thread
|
||||
_busy_waiting = QThread::currentThread() ==
|
||||
QCoreApplication::instance()->thread();
|
||||
}
|
||||
|
||||
struct WaitDepthCounter
|
||||
{
|
||||
WaitDepthCounter( int & depth, bool busy ) :
|
||||
m_depth( depth ),
|
||||
m_busy( busy )
|
||||
{
|
||||
if( m_busy ) { ++m_depth; }
|
||||
}
|
||||
|
||||
~WaitDepthCounter()
|
||||
{
|
||||
if( m_busy ) { --m_depth; }
|
||||
}
|
||||
|
||||
int & m_depth;
|
||||
bool m_busy;
|
||||
};
|
||||
|
||||
WaitDepthCounter wdc( waitDepthCounter(), _busy_waiting );
|
||||
#endif
|
||||
while( !isInvalid() )
|
||||
{
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
if( _busy_waiting && !messagesLeft() )
|
||||
{
|
||||
QCoreApplication::processEvents(
|
||||
QEventLoop::ExcludeUserInputEvents, 50 );
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
message m = receiveMessage();
|
||||
processMessage( m );
|
||||
if( m.id == _wm.id )
|
||||
{
|
||||
return m;
|
||||
}
|
||||
else if( m.id == IdUndefined )
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
return message();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
202
src/common/SharedMemory.cpp
Normal file
202
src/common/SharedMemory.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* SharedMemory.cpp
|
||||
*
|
||||
* Copyright (c) 2022 Dominic Clark <mrdomclark/at/gmail.com>
|
||||
*
|
||||
* 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 "SharedMemory.h"
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
# include <system_error>
|
||||
#
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
#
|
||||
# include "RaiiHelpers.h"
|
||||
#else
|
||||
# include <stdexcept>
|
||||
#
|
||||
# include <QtGlobal>
|
||||
# include <QSharedMemory>
|
||||
#endif
|
||||
|
||||
|
||||
namespace lmms::detail {
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename F>
|
||||
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
|
||||
{
|
||||
int result;
|
||||
do
|
||||
{
|
||||
result = function();
|
||||
}
|
||||
while (result == -1 && errno == EINTR);
|
||||
return result;
|
||||
}
|
||||
|
||||
void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); }
|
||||
void deleteShmObject(const char* name) noexcept { shm_unlink(name); }
|
||||
|
||||
using FileDescriptor = UniqueNullableResource<int, -1, deleteFileDescriptor>;
|
||||
using ShmObject = UniqueNullableResource<const char*, nullptr, deleteShmObject>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SharedMemoryImpl
|
||||
{
|
||||
public:
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly) :
|
||||
m_key{"/" + key}
|
||||
{
|
||||
const auto openFlags = readOnly ? O_RDONLY : O_RDWR;
|
||||
const auto fd = FileDescriptor{
|
||||
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); })
|
||||
};
|
||||
if (!fd)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
|
||||
}
|
||||
auto stat = (struct stat){};
|
||||
if (fstat(fd.get(), &stat) == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"};
|
||||
}
|
||||
m_size = stat.st_size;
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
m_key{"/" + key},
|
||||
m_size{size}
|
||||
{
|
||||
const auto fd = FileDescriptor{
|
||||
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); })
|
||||
};
|
||||
if (fd.get() == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
|
||||
}
|
||||
m_object.reset(m_key.c_str());
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"};
|
||||
}
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
|
||||
|
||||
~SharedMemoryImpl()
|
||||
{
|
||||
munmap(m_mapping, m_size);
|
||||
}
|
||||
|
||||
void* get() { return m_mapping; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
std::size_t m_size;
|
||||
void* m_mapping;
|
||||
ShmObject m_object;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class SharedMemoryImpl
|
||||
{
|
||||
public:
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.attach(mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"};
|
||||
}
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.create(size, mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"};
|
||||
}
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
|
||||
|
||||
void* get() { return m_shm.data(); }
|
||||
|
||||
private:
|
||||
QSharedMemory m_shm;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
SharedMemoryData::SharedMemoryData() noexcept = default;
|
||||
|
||||
SharedMemoryData::SharedMemoryData(std::string&& key, bool readOnly) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<SharedMemoryImpl>(m_key, readOnly)},
|
||||
m_ptr{m_impl->get()}
|
||||
{ }
|
||||
|
||||
SharedMemoryData::SharedMemoryData(std::string&& key, std::size_t size, bool readOnly) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<SharedMemoryImpl>(m_key, std::max(size, std::size_t{1}), readOnly)},
|
||||
m_ptr{m_impl->get()}
|
||||
{ }
|
||||
|
||||
SharedMemoryData::~SharedMemoryData() = default;
|
||||
|
||||
SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept :
|
||||
m_key{std::move(other.m_key)},
|
||||
m_impl{std::move(other.m_impl)},
|
||||
m_ptr{other.m_ptr}
|
||||
{ }
|
||||
|
||||
} // namespace lmms::detail
|
||||
1292
src/core/AudioEngine.cpp
Normal file
1292
src/core/AudioEngine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MixerProfiler.cpp - class for profiling performance of Mixer
|
||||
* AudioEngineProfiler.cpp - class for profiling performance of AudioEngine
|
||||
*
|
||||
* Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
@@ -22,10 +22,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MixerProfiler.h"
|
||||
#include "AudioEngineProfiler.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
MixerProfiler::MixerProfiler() :
|
||||
AudioEngineProfiler::AudioEngineProfiler() :
|
||||
m_periodTimer(),
|
||||
m_cpuLoad( 0 ),
|
||||
m_outputFile()
|
||||
@@ -34,12 +36,7 @@ MixerProfiler::MixerProfiler() :
|
||||
|
||||
|
||||
|
||||
MixerProfiler::~MixerProfiler()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void MixerProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
|
||||
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
|
||||
{
|
||||
int periodElapsed = m_periodTimer.elapsed();
|
||||
|
||||
@@ -54,10 +51,11 @@ void MixerProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPerio
|
||||
|
||||
|
||||
|
||||
void MixerProfiler::setOutputFile( const QString& outputFile )
|
||||
void AudioEngineProfiler::setOutputFile( const QString& outputFile )
|
||||
{
|
||||
m_outputFile.close();
|
||||
m_outputFile.setFileName( outputFile );
|
||||
m_outputFile.open( QFile::WriteOnly | QFile::Truncate );
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* MixerWorkerThread.cpp - implementation of MixerWorkerThread
|
||||
* AudioEngineWorkerThread.cpp - implementation of AudioEngineWorkerThread
|
||||
*
|
||||
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
@@ -22,26 +22,30 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "AudioEngineWorkerThread.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "denormals.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "ThreadableJob.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64)
|
||||
#if __SSE__
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue;
|
||||
QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL;
|
||||
QList<MixerWorkerThread *> MixerWorkerThread::workerThreads;
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioEngineWorkerThread::JobQueue AudioEngineWorkerThread::globalJobQueue;
|
||||
QWaitCondition * AudioEngineWorkerThread::queueReadyWaitCond = nullptr;
|
||||
QList<AudioEngineWorkerThread *> AudioEngineWorkerThread::workerThreads;
|
||||
|
||||
// implementation of internal JobQueue
|
||||
void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
|
||||
void AudioEngineWorkerThread::JobQueue::reset( OperationMode _opMode )
|
||||
{
|
||||
m_writeIndex = 0;
|
||||
m_itemsDone = 0;
|
||||
@@ -51,7 +55,7 @@ void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
void AudioEngineWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
{
|
||||
if( _job->requiresProcessing() )
|
||||
{
|
||||
@@ -70,7 +74,7 @@ void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::run()
|
||||
void AudioEngineWorkerThread::JobQueue::run()
|
||||
{
|
||||
bool processedJob = true;
|
||||
while (processedJob && m_itemsDone < m_writeIndex)
|
||||
@@ -94,11 +98,11 @@ void MixerWorkerThread::JobQueue::run()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::wait()
|
||||
void AudioEngineWorkerThread::JobQueue::wait()
|
||||
{
|
||||
while (m_itemsDone < m_writeIndex)
|
||||
{
|
||||
#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64)
|
||||
#ifdef __SSE__
|
||||
_mm_pause();
|
||||
#endif
|
||||
}
|
||||
@@ -110,19 +114,19 @@ void MixerWorkerThread::JobQueue::wait()
|
||||
|
||||
// implementation of worker threads
|
||||
|
||||
MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
|
||||
QThread( mixer ),
|
||||
AudioEngineWorkerThread::AudioEngineWorkerThread( AudioEngine* audioEngine ) :
|
||||
QThread( audioEngine ),
|
||||
m_quit( false )
|
||||
{
|
||||
// initialize global static data
|
||||
if( queueReadyWaitCond == NULL )
|
||||
if( queueReadyWaitCond == nullptr )
|
||||
{
|
||||
queueReadyWaitCond = new QWaitCondition;
|
||||
}
|
||||
|
||||
// keep track of all instantiated worker threads - this is used for
|
||||
// processing the last worker thread "inline", see comments in
|
||||
// MixerWorkerThread::startAndWaitForJobs() for details
|
||||
// AudioEngineWorkerThread::startAndWaitForJobs() for details
|
||||
workerThreads << this;
|
||||
|
||||
resetJobQueue();
|
||||
@@ -131,7 +135,7 @@ MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
|
||||
|
||||
|
||||
|
||||
MixerWorkerThread::~MixerWorkerThread()
|
||||
AudioEngineWorkerThread::~AudioEngineWorkerThread()
|
||||
{
|
||||
workerThreads.removeAll( this );
|
||||
}
|
||||
@@ -139,7 +143,7 @@ MixerWorkerThread::~MixerWorkerThread()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::quit()
|
||||
void AudioEngineWorkerThread::quit()
|
||||
{
|
||||
m_quit = true;
|
||||
resetJobQueue();
|
||||
@@ -148,11 +152,11 @@ void MixerWorkerThread::quit()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::startAndWaitForJobs()
|
||||
void AudioEngineWorkerThread::startAndWaitForJobs()
|
||||
{
|
||||
queueReadyWaitCond->wakeAll();
|
||||
// The last worker-thread is never started. Instead it's processed "inline"
|
||||
// i.e. within the global Mixer thread. This way we can reduce latencies
|
||||
// i.e. within the global AudioEngine thread. This way we can reduce latencies
|
||||
// that otherwise would be caused by synchronizing with another thread.
|
||||
globalJobQueue.run();
|
||||
globalJobQueue.wait();
|
||||
@@ -161,7 +165,7 @@ void MixerWorkerThread::startAndWaitForJobs()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::run()
|
||||
void AudioEngineWorkerThread::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
disable_denormals();
|
||||
@@ -176,4 +180,4 @@ void MixerWorkerThread::run()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -26,13 +26,16 @@
|
||||
|
||||
#include "lmms_math.h"
|
||||
|
||||
#include "AutomationPattern.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "AutomationClip.h"
|
||||
#include "ControllerConnection.h"
|
||||
#include "LocaleHelper.h"
|
||||
#include "Mixer.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
long AutomatableModel::s_periodCounter = 0;
|
||||
|
||||
|
||||
@@ -50,10 +53,11 @@ AutomatableModel::AutomatableModel(
|
||||
m_valueChanged( false ),
|
||||
m_setValueDepth( 0 ),
|
||||
m_hasStrictStepSize( false ),
|
||||
m_controllerConnection( NULL ),
|
||||
m_valueBuffer( static_cast<int>( Engine::mixer()->framesPerPeriod() ) ),
|
||||
m_controllerConnection( nullptr ),
|
||||
m_valueBuffer( static_cast<int>( Engine::audioEngine()->framesPerPeriod() ) ),
|
||||
m_lastUpdatedPeriod( -1 ),
|
||||
m_hasSampleExactData( false )
|
||||
m_hasSampleExactData(false),
|
||||
m_useControllerValue(true)
|
||||
|
||||
{
|
||||
m_value = fittedValue( val );
|
||||
@@ -86,7 +90,7 @@ AutomatableModel::~AutomatableModel()
|
||||
|
||||
bool AutomatableModel::isAutomated() const
|
||||
{
|
||||
return AutomationPattern::isAutomated( this );
|
||||
return AutomationClip::isAutomated( this );
|
||||
}
|
||||
|
||||
|
||||
@@ -175,13 +179,13 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
|
||||
void AutomatableModel::loadSettings( const QDomElement& element, const QString& name )
|
||||
{
|
||||
// compat code
|
||||
QDomNode node = element.namedItem( AutomationPattern::classNodeName() );
|
||||
QDomNode node = element.namedItem( AutomationClip::classNodeName() );
|
||||
if( node.isElement() )
|
||||
{
|
||||
node = node.namedItem( name );
|
||||
if( node.isElement() )
|
||||
{
|
||||
AutomationPattern * p = AutomationPattern::globalAutomationPattern( this );
|
||||
AutomationClip * p = AutomationClip::globalAutomationClip( this );
|
||||
p->loadSettings( node.toElement() );
|
||||
setValue( p->valueAt( 0 ) );
|
||||
// in older projects we sometimes have odd automations
|
||||
@@ -214,7 +218,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
|
||||
}
|
||||
if( thisConnection.isElement() )
|
||||
{
|
||||
setControllerConnection( new ControllerConnection( (Controller*)NULL ) );
|
||||
setControllerConnection(new ControllerConnection(nullptr));
|
||||
m_controllerConnection->loadSettings( thisConnection.toElement() );
|
||||
//m_controllerConnection->setTargetName( displayName() );
|
||||
}
|
||||
@@ -301,13 +305,13 @@ void AutomatableModel::setValue( const float value )
|
||||
addJournalCheckPoint();
|
||||
|
||||
// notify linked models
|
||||
for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it )
|
||||
for (const auto& linkedModel : m_linkedModels)
|
||||
{
|
||||
if( (*it)->m_setValueDepth < 1 && (*it)->fittedValue( value ) != (*it)->m_value )
|
||||
if (linkedModel->m_setValueDepth < 1 && linkedModel->fittedValue(value) != linkedModel->m_value)
|
||||
{
|
||||
bool journalling = (*it)->testAndSetJournalling( isJournalling() );
|
||||
(*it)->setValue( value );
|
||||
(*it)->setJournalling( journalling );
|
||||
bool journalling = linkedModel->testAndSetJournalling(isJournalling());
|
||||
linkedModel->setValue(value);
|
||||
linkedModel->setJournalling(journalling);
|
||||
}
|
||||
}
|
||||
m_valueChanged = true;
|
||||
@@ -325,7 +329,7 @@ void AutomatableModel::setValue( const float value )
|
||||
|
||||
template<class T> T AutomatableModel::logToLinearScale( T value ) const
|
||||
{
|
||||
return castValue<T>( ::logToLinearScale( minValue<float>(), maxValue<float>(), static_cast<float>( value ) ) );
|
||||
return castValue<T>( lmms::logToLinearScale( minValue<float>(), maxValue<float>(), static_cast<float>( value ) ) );
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +345,7 @@ float AutomatableModel::inverseScaledValue( float value ) const
|
||||
{
|
||||
return m_scaleType == Linear
|
||||
? value
|
||||
: ::linearToLogScale( minValue<float>(), maxValue<float>(), value );
|
||||
: lmms::linearToLogScale( minValue<float>(), maxValue<float>(), value );
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +367,7 @@ void roundAt( T& value, const T& where, const T& step_size )
|
||||
template<class T>
|
||||
void AutomatableModel::roundAt( T& value, const T& where ) const
|
||||
{
|
||||
::roundAt(value, where, m_step);
|
||||
lmms::roundAt(value, where, m_step);
|
||||
}
|
||||
|
||||
|
||||
@@ -371,6 +375,8 @@ void AutomatableModel::roundAt( T& value, const T& where ) const
|
||||
|
||||
void AutomatableModel::setAutomatedValue( const float value )
|
||||
{
|
||||
setUseControllerValue(false);
|
||||
|
||||
m_oldValue = m_value;
|
||||
++m_setValueDepth;
|
||||
const float oldValue = m_value;
|
||||
@@ -382,13 +388,12 @@ void AutomatableModel::setAutomatedValue( const float value )
|
||||
if( oldValue != m_value )
|
||||
{
|
||||
// notify linked models
|
||||
for( AutoModelVector::Iterator it = m_linkedModels.begin();
|
||||
it != m_linkedModels.end(); ++it )
|
||||
for (const auto& linkedModel : m_linkedModels)
|
||||
{
|
||||
if( (*it)->m_setValueDepth < 1 &&
|
||||
(*it)->fittedValue( m_value ) != (*it)->m_value )
|
||||
if (!(linkedModel->controllerConnection()) && linkedModel->m_setValueDepth < 1 &&
|
||||
linkedModel->fittedValue(m_value) != linkedModel->m_value)
|
||||
{
|
||||
(*it)->setAutomatedValue( value );
|
||||
linkedModel->setAutomatedValue(value);
|
||||
}
|
||||
}
|
||||
m_valueChanged = true;
|
||||
@@ -474,8 +479,8 @@ void AutomatableModel::linkModel( AutomatableModel* model )
|
||||
|
||||
if( !model->hasLinkedModels() )
|
||||
{
|
||||
QObject::connect( this, SIGNAL( dataChanged() ),
|
||||
model, SIGNAL( dataChanged() ), Qt::DirectConnection );
|
||||
QObject::connect( this, SIGNAL(dataChanged()),
|
||||
model, SIGNAL(dataChanged()), Qt::DirectConnection );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,8 +504,23 @@ void AutomatableModel::unlinkModel( AutomatableModel* model )
|
||||
|
||||
void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 )
|
||||
{
|
||||
if (!model1->m_linkedModels.contains( model2 ) && model1 != model2)
|
||||
{
|
||||
// copy data
|
||||
model1->m_value = model2->m_value;
|
||||
if (model1->valueBuffer() && model2->valueBuffer())
|
||||
{
|
||||
std::copy_n(model2->valueBuffer()->data(),
|
||||
model1->valueBuffer()->length(),
|
||||
model1->valueBuffer()->data());
|
||||
}
|
||||
// send dataChanged() before linking (because linking will
|
||||
// connect the two dataChanged() signals)
|
||||
emit model1->dataChanged();
|
||||
// finally: link the models
|
||||
model1->linkModel( model2 );
|
||||
model2->linkModel( model1 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -531,9 +551,9 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c )
|
||||
m_controllerConnection = c;
|
||||
if( c )
|
||||
{
|
||||
QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ),
|
||||
this, SIGNAL( dataChanged() ), Qt::DirectConnection );
|
||||
QObject::connect( m_controllerConnection, SIGNAL( destroyed() ), this, SLOT( unlinkControllerConnection() ) );
|
||||
QObject::connect( m_controllerConnection, SIGNAL(valueChanged()),
|
||||
this, SIGNAL(dataChanged()), Qt::DirectConnection );
|
||||
QObject::connect( m_controllerConnection, SIGNAL(destroyed()), this, SLOT(unlinkControllerConnection()));
|
||||
m_valueChanged = true;
|
||||
emit dataChanged();
|
||||
}
|
||||
@@ -569,7 +589,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const
|
||||
}
|
||||
|
||||
AutomatableModel* lm = m_linkedModels.first();
|
||||
if( lm->controllerConnection() )
|
||||
if (lm->controllerConnection() && lm->useControllerValue())
|
||||
{
|
||||
return fittedValue( lm->controllerValue( frameOffset ) );
|
||||
}
|
||||
@@ -586,13 +606,13 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
{
|
||||
return m_hasSampleExactData
|
||||
? &m_valueBuffer
|
||||
: NULL;
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
float val = m_value; // make sure our m_value doesn't change midway
|
||||
|
||||
ValueBuffer * vb;
|
||||
if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() )
|
||||
if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact())
|
||||
{
|
||||
vb = m_controllerConnection->valueBuffer();
|
||||
if( vb )
|
||||
@@ -623,23 +643,28 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
}
|
||||
AutomatableModel* lm = NULL;
|
||||
if( hasLinkedModels() )
|
||||
|
||||
if (!m_controllerConnection)
|
||||
{
|
||||
lm = m_linkedModels.first();
|
||||
}
|
||||
if( lm && lm->controllerConnection() && lm->controllerConnection()->getController()->isSampleExact() )
|
||||
{
|
||||
vb = lm->valueBuffer();
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
for( int i = 0; i < vb->length(); i++ )
|
||||
AutomatableModel* lm = nullptr;
|
||||
if (hasLinkedModels())
|
||||
{
|
||||
nvalues[i] = fittedValue( values[i] );
|
||||
lm = m_linkedModels.first();
|
||||
}
|
||||
if (lm && lm->controllerConnection() && lm->useControllerValue() &&
|
||||
lm->controllerConnection()->getController()->isSampleExact())
|
||||
{
|
||||
vb = lm->valueBuffer();
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
for (int i = 0; i < vb->length(); i++)
|
||||
{
|
||||
nvalues[i] = fittedValue(values[i]);
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
|
||||
if( m_oldValue != val )
|
||||
@@ -655,7 +680,7 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
// in which case the recipient knows to use the static value() instead
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = false;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -666,7 +691,7 @@ void AutomatableModel::unlinkControllerConnection()
|
||||
m_controllerConnection->disconnect( this );
|
||||
}
|
||||
|
||||
m_controllerConnection = NULL;
|
||||
m_controllerConnection = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -693,64 +718,78 @@ void AutomatableModel::reset()
|
||||
|
||||
|
||||
|
||||
float AutomatableModel::globalAutomationValueAt( const MidiTime& time )
|
||||
float AutomatableModel::globalAutomationValueAt( const TimePos& time )
|
||||
{
|
||||
// get patterns that connect to this model
|
||||
QVector<AutomationPattern *> patterns = AutomationPattern::patternsForModel( this );
|
||||
if( patterns.isEmpty() )
|
||||
// get clips that connect to this model
|
||||
QVector<AutomationClip *> clips = AutomationClip::clipsForModel( this );
|
||||
if( clips.isEmpty() )
|
||||
{
|
||||
// if no such patterns exist, return current value
|
||||
// if no such clips exist, return current value
|
||||
return m_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// of those patterns:
|
||||
// find the patterns which overlap with the miditime position
|
||||
QVector<AutomationPattern *> patternsInRange;
|
||||
for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ )
|
||||
// of those clips:
|
||||
// find the clips which overlap with the time position
|
||||
QVector<AutomationClip *> clipsInRange;
|
||||
for (const auto& clip : clips)
|
||||
{
|
||||
int s = ( *it )->startPosition();
|
||||
int e = ( *it )->endPosition();
|
||||
if( s <= time && e >= time ) { patternsInRange += ( *it ); }
|
||||
int s = clip->startPosition();
|
||||
int e = clip->endPosition();
|
||||
if (s <= time && e >= time) { clipsInRange += clip; }
|
||||
}
|
||||
|
||||
AutomationPattern * latestPattern = NULL;
|
||||
AutomationClip * latestClip = nullptr;
|
||||
|
||||
if( ! patternsInRange.isEmpty() )
|
||||
if( ! clipsInRange.isEmpty() )
|
||||
{
|
||||
// if there are more than one overlapping patterns, just use the first one because
|
||||
// multiple pattern behaviour is undefined anyway
|
||||
latestPattern = patternsInRange[0];
|
||||
// if there are more than one overlapping clips, just use the first one because
|
||||
// multiple clip behaviour is undefined anyway
|
||||
latestClip = clipsInRange[0];
|
||||
}
|
||||
else
|
||||
// if we find no patterns at the exact miditime, we need to search for the last pattern before time and use that
|
||||
// if we find no clips at the exact time, we need to search for the last clip before time and use that
|
||||
{
|
||||
int latestPosition = 0;
|
||||
|
||||
for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ )
|
||||
for (const auto& clip : clips)
|
||||
{
|
||||
int e = ( *it )->endPosition();
|
||||
if( e <= time && e > latestPosition )
|
||||
int e = clip->endPosition();
|
||||
if (e <= time && e > latestPosition)
|
||||
{
|
||||
latestPosition = e;
|
||||
latestPattern = ( *it );
|
||||
latestClip = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( latestPattern )
|
||||
if( latestClip )
|
||||
{
|
||||
// scale/fit the value appropriately and return it
|
||||
const float value = latestPattern->valueAt( time - latestPattern->startPosition() );
|
||||
const float value = latestClip->valueAt( time - latestClip->startPosition() );
|
||||
const float scaled_value = scaledValue( value );
|
||||
return fittedValue( scaled_value );
|
||||
}
|
||||
// if we still find no pattern, the value at that time is undefined so
|
||||
// if we still find no clip, the value at that time is undefined so
|
||||
// just return current value as the best we can do
|
||||
else return m_value;
|
||||
}
|
||||
}
|
||||
|
||||
void AutomatableModel::setUseControllerValue(bool b)
|
||||
{
|
||||
if (b)
|
||||
{
|
||||
m_useControllerValue = true;
|
||||
emit dataChanged();
|
||||
}
|
||||
else if (m_controllerConnection && m_useControllerValue)
|
||||
{
|
||||
m_useControllerValue = false;
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
float FloatModel::getRoundedValue() const
|
||||
{
|
||||
return qRound( value() / step<float>() ) * step<float>();
|
||||
@@ -761,7 +800,7 @@ float FloatModel::getRoundedValue() const
|
||||
|
||||
int FloatModel::getDigitCount() const
|
||||
{
|
||||
float steptemp = step<float>();
|
||||
auto steptemp = step<float>();
|
||||
int digits = 0;
|
||||
while ( steptemp < 1 )
|
||||
{
|
||||
@@ -787,3 +826,6 @@ QString BoolModel::displayValue( const float val ) const
|
||||
{
|
||||
return QString::number( castValue<bool>( scaledValue( val ) ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
1176
src/core/AutomationClip.cpp
Normal file
1176
src/core/AutomationClip.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
src/core/AutomationNode.cpp
Normal file
114
src/core/AutomationNode.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* AutomationClip.cpp - Implementation of class AutomationNode which
|
||||
* holds information on a single automation clip node
|
||||
*
|
||||
* Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.com>
|
||||
*
|
||||
* 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 "AutomationNode.h"
|
||||
#include "AutomationClip.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
// Dummy constructor for the QMap
|
||||
AutomationNode::AutomationNode() :
|
||||
m_clip(nullptr),
|
||||
m_pos(0),
|
||||
m_inValue(0),
|
||||
m_outValue(0),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
AutomationNode::AutomationNode(AutomationClip* clip, float value, int pos) :
|
||||
m_clip(clip),
|
||||
m_pos(pos),
|
||||
m_inValue(value),
|
||||
m_outValue(value),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
AutomationNode::AutomationNode(AutomationClip* clip, float inValue, float outValue, int pos) :
|
||||
m_clip(clip),
|
||||
m_pos(pos),
|
||||
m_inValue(inValue),
|
||||
m_outValue(outValue),
|
||||
m_inTangent(0),
|
||||
m_outTangent(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the inValue of an automation node
|
||||
* @param Float value to be assigned
|
||||
*/
|
||||
void AutomationNode::setInValue(float value)
|
||||
{
|
||||
m_inValue = value;
|
||||
|
||||
// Recalculate the tangents from neighbor nodes
|
||||
AutomationClip::timeMap & tm = m_clip->getTimeMap();
|
||||
|
||||
// Get an iterator pointing to this node
|
||||
AutomationClip::timeMap::iterator it = tm.lowerBound(m_pos);
|
||||
// If it's not the first node, get the one immediately behind it
|
||||
if (it != tm.begin()) { --it; }
|
||||
|
||||
// Generate tangents from the previously, current and next nodes
|
||||
m_clip->generateTangents(it, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the outValue of an automation node
|
||||
* @param Float value to be assigned
|
||||
*/
|
||||
void AutomationNode::setOutValue(float value)
|
||||
{
|
||||
m_outValue = value;
|
||||
|
||||
// Recalculate the tangents from neighbor nodes
|
||||
AutomationClip::timeMap & tm = m_clip->getTimeMap();
|
||||
|
||||
// Get an iterator pointing to this node
|
||||
AutomationClip::timeMap::iterator it = tm.lowerBound(m_pos);
|
||||
// If it's not the first node, get the one immediately behind it
|
||||
if (it != tm.begin()) { --it; }
|
||||
|
||||
// Generate tangents from the previously, current and next nodes
|
||||
m_clip->generateTangents(it, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the outValue so it matches inValue
|
||||
*/
|
||||
void AutomationNode::resetOutValue()
|
||||
{
|
||||
// Calls setOutValue so it also takes care of generating
|
||||
// the tangents
|
||||
setOutValue(m_inValue);
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -1,905 +0,0 @@
|
||||
/*
|
||||
* AutomationPattern.cpp - implementation of class AutomationPattern which
|
||||
* holds dynamic values
|
||||
*
|
||||
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "AutomationPattern.h"
|
||||
|
||||
#include "AutomationPatternView.h"
|
||||
#include "AutomationTrack.h"
|
||||
#include "LocaleHelper.h"
|
||||
#include "Note.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
int AutomationPattern::s_quantization = 1;
|
||||
const float AutomationPattern::DEFAULT_MIN_VALUE = 0;
|
||||
const float AutomationPattern::DEFAULT_MAX_VALUE = 1;
|
||||
|
||||
|
||||
AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
|
||||
TrackContentObject( _auto_track ),
|
||||
m_autoTrack( _auto_track ),
|
||||
m_objects(),
|
||||
m_tension( 1.0 ),
|
||||
m_progressionType( DiscreteProgression ),
|
||||
m_dragging( false ),
|
||||
m_isRecording( false ),
|
||||
m_lastRecordedValue( 0 )
|
||||
{
|
||||
changeLength( MidiTime( 1, 0 ) );
|
||||
if( getTrack() )
|
||||
{
|
||||
switch( getTrack()->trackContainer()->type() )
|
||||
{
|
||||
case TrackContainer::BBContainer:
|
||||
setAutoResize( true );
|
||||
break;
|
||||
|
||||
case TrackContainer::SongContainer:
|
||||
// move down
|
||||
default:
|
||||
setAutoResize( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
|
||||
TrackContentObject( _pat_to_copy.m_autoTrack ),
|
||||
m_autoTrack( _pat_to_copy.m_autoTrack ),
|
||||
m_objects( _pat_to_copy.m_objects ),
|
||||
m_tension( _pat_to_copy.m_tension ),
|
||||
m_progressionType( _pat_to_copy.m_progressionType )
|
||||
{
|
||||
for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
|
||||
it != _pat_to_copy.m_timeMap.end(); ++it )
|
||||
{
|
||||
m_timeMap[it.key()] = it.value();
|
||||
m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
|
||||
}
|
||||
switch( getTrack()->trackContainer()->type() )
|
||||
{
|
||||
case TrackContainer::BBContainer:
|
||||
setAutoResize( true );
|
||||
break;
|
||||
|
||||
case TrackContainer::SongContainer:
|
||||
// move down
|
||||
default:
|
||||
setAutoResize( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
|
||||
{
|
||||
if( _search_dup && m_objects.contains(_obj) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the automation track is unconnected and there is nothing in the track
|
||||
if( m_objects.isEmpty() && hasAutomation() == false )
|
||||
{
|
||||
// then initialize first value
|
||||
putValue( MidiTime(0), _obj->inverseScaledValue( _obj->value<float>() ), false );
|
||||
}
|
||||
|
||||
m_objects += _obj;
|
||||
|
||||
connect( _obj, SIGNAL( destroyed( jo_id_t ) ),
|
||||
this, SLOT( objectDestroyed( jo_id_t ) ),
|
||||
Qt::DirectConnection );
|
||||
|
||||
emit dataChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::setProgressionType(
|
||||
ProgressionTypes _new_progression_type )
|
||||
{
|
||||
if ( _new_progression_type == DiscreteProgression ||
|
||||
_new_progression_type == LinearProgression ||
|
||||
_new_progression_type == CubicHermiteProgression )
|
||||
{
|
||||
m_progressionType = _new_progression_type;
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::setTension( QString _new_tension )
|
||||
{
|
||||
bool ok;
|
||||
float nt = LocaleHelper::toFloat(_new_tension, & ok);
|
||||
|
||||
if( ok && nt > -0.01 && nt < 1.01 )
|
||||
{
|
||||
m_tension = nt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const AutomatableModel * AutomationPattern::firstObject() const
|
||||
{
|
||||
AutomatableModel * m;
|
||||
if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL )
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 );
|
||||
return &_fm;
|
||||
}
|
||||
|
||||
const AutomationPattern::objectVector& AutomationPattern::objects() const
|
||||
{
|
||||
return m_objects;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MidiTime AutomationPattern::timeMapLength() const
|
||||
{
|
||||
if( m_timeMap.isEmpty() ) return 0;
|
||||
timeMap::const_iterator it = m_timeMap.end();
|
||||
return MidiTime( MidiTime( (it-1).key() ).nextFullTact(), 0 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::updateLength()
|
||||
{
|
||||
changeLength( timeMapLength() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MidiTime AutomationPattern::putValue( const MidiTime & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool ignoreSurroundingPoints )
|
||||
{
|
||||
cleanObjects();
|
||||
|
||||
MidiTime newTime = quantPos ?
|
||||
Note::quantized( time, quantization() ) :
|
||||
time;
|
||||
|
||||
m_timeMap[ newTime ] = value;
|
||||
timeMap::const_iterator it = m_timeMap.find( newTime );
|
||||
|
||||
// Remove control points that are covered by the new points
|
||||
// quantization value. Control Key to override
|
||||
if( ! ignoreSurroundingPoints )
|
||||
{
|
||||
for( int i = newTime + 1; i < newTime + quantization(); ++i )
|
||||
{
|
||||
AutomationPattern::removeValue( i );
|
||||
}
|
||||
}
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
--it;
|
||||
}
|
||||
generateTangents( it, 3 );
|
||||
|
||||
// we need to maximize our length in case we're part of a hidden
|
||||
// automation track as the user can't resize this pattern
|
||||
if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
|
||||
{
|
||||
updateLength();
|
||||
}
|
||||
|
||||
emit dataChanged();
|
||||
|
||||
return newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::removeValue( const MidiTime & time )
|
||||
{
|
||||
cleanObjects();
|
||||
|
||||
m_timeMap.remove( time );
|
||||
m_tangents.remove( time );
|
||||
timeMap::const_iterator it = m_timeMap.lowerBound( time );
|
||||
if( it != m_timeMap.begin() )
|
||||
{
|
||||
--it;
|
||||
}
|
||||
generateTangents(it, 3);
|
||||
|
||||
if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
|
||||
{
|
||||
updateLength();
|
||||
}
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::recordValue(MidiTime time, float value)
|
||||
{
|
||||
if( value != m_lastRecordedValue )
|
||||
{
|
||||
putValue( time, value, true );
|
||||
m_lastRecordedValue = value;
|
||||
}
|
||||
else if( valueAt( time ) != value )
|
||||
{
|
||||
removeValue( time );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set the position of the point that is being dragged.
|
||||
* Calling this function will also automatically set m_dragging to true,
|
||||
* which applyDragValue() have to be called to m_dragging.
|
||||
* @param the time(x position) of the point being dragged
|
||||
* @param the value(y position) of the point being dragged
|
||||
* @param true to snip x position
|
||||
* @return
|
||||
*/
|
||||
MidiTime AutomationPattern::setDragValue( const MidiTime & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool controlKey )
|
||||
{
|
||||
if( m_dragging == false )
|
||||
{
|
||||
MidiTime newTime = quantPos ?
|
||||
Note::quantized( time, quantization() ) :
|
||||
time;
|
||||
this->removeValue( newTime );
|
||||
m_oldTimeMap = m_timeMap;
|
||||
m_dragging = true;
|
||||
}
|
||||
|
||||
//Restore to the state before it the point were being dragged
|
||||
m_timeMap = m_oldTimeMap;
|
||||
|
||||
for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
|
||||
{
|
||||
generateTangents( it, 3 );
|
||||
}
|
||||
|
||||
return this->putValue( time, value, quantPos, controlKey );
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief After the point is dragged, this function is called to apply the change.
|
||||
*/
|
||||
void AutomationPattern::applyDragValue()
|
||||
{
|
||||
m_dragging = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float AutomationPattern::valueAt( const MidiTime & _time ) const
|
||||
{
|
||||
if( m_timeMap.isEmpty() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( m_timeMap.contains( _time ) )
|
||||
{
|
||||
return m_timeMap[_time];
|
||||
}
|
||||
|
||||
// lowerBound returns next value with greater key, therefore we take
|
||||
// the previous element to get the current value
|
||||
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
|
||||
|
||||
if( v == m_timeMap.begin() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if( v == m_timeMap.end() )
|
||||
{
|
||||
return (v-1).value();
|
||||
}
|
||||
|
||||
return valueAt( v-1, _time - (v-1).key() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
|
||||
{
|
||||
if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
|
||||
{
|
||||
return v.value();
|
||||
}
|
||||
else if( m_progressionType == LinearProgression )
|
||||
{
|
||||
float slope = ((v+1).value() - v.value()) /
|
||||
((v+1).key() - v.key());
|
||||
return v.value() + offset * slope;
|
||||
}
|
||||
else /* CubicHermiteProgression */
|
||||
{
|
||||
// Implements a Cubic Hermite spline as explained at:
|
||||
// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
|
||||
//
|
||||
// Note that we are not interpolating a 2 dimensional point over
|
||||
// time as the article describes. We are interpolating a single
|
||||
// value: y. To make this work we map the values of x that this
|
||||
// segment spans to values of t for t = 0.0 -> 1.0 and scale the
|
||||
// tangents _m1 and _m2
|
||||
int numValues = ((v+1).key() - v.key());
|
||||
float t = (float) offset / (float) numValues;
|
||||
float m1 = (m_tangents[v.key()]) * numValues * m_tension;
|
||||
float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension;
|
||||
|
||||
return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
|
||||
+ ( pow(t,3) - 2*pow(t,2) + t) * m1
|
||||
+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
|
||||
+ ( pow(t,3) - pow(t,2) ) * m2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float *AutomationPattern::valuesAfter( const MidiTime & _time ) const
|
||||
{
|
||||
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
|
||||
if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int numValues = (v+1).key() - v.key();
|
||||
float *ret = new float[numValues];
|
||||
|
||||
for( int i = 0; i < numValues; i++ )
|
||||
{
|
||||
ret[i] = valueAt( v, i );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::flipY( int min, int max )
|
||||
{
|
||||
timeMap tempMap = m_timeMap;
|
||||
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
|
||||
float tempValue = 0;
|
||||
|
||||
int numPoints = 0;
|
||||
|
||||
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
|
||||
{
|
||||
numPoints++;
|
||||
}
|
||||
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
|
||||
if ( min < 0 )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() ) * -1;
|
||||
putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempValue = max - valueAt( ( iterate + i ).key() );
|
||||
putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
|
||||
}
|
||||
}
|
||||
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::flipY()
|
||||
{
|
||||
flipY(getMin(), getMax());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::flipX( int length )
|
||||
{
|
||||
timeMap tempMap;
|
||||
|
||||
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
|
||||
float tempValue = 0;
|
||||
int numPoints = 0;
|
||||
|
||||
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
|
||||
{
|
||||
numPoints++;
|
||||
}
|
||||
|
||||
float realLength = ( iterate + numPoints ).key();
|
||||
|
||||
if ( length != -1 && length != realLength)
|
||||
{
|
||||
if ( realLength < length )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + numPoints ).key() );
|
||||
putValue( MidiTime( length ) , tempValue, false);
|
||||
numPoints++;
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
MidiTime newTime = MidiTime( length - ( iterate + i ).key() );
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
MidiTime newTime;
|
||||
|
||||
if ( ( iterate + i ).key() <= length )
|
||||
{
|
||||
newTime = MidiTime( length - ( iterate + i ).key() );
|
||||
}
|
||||
else
|
||||
{
|
||||
newTime = MidiTime( ( iterate + i ).key() );
|
||||
}
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
cleanObjects();
|
||||
MidiTime newTime = MidiTime( realLength - ( iterate + i ).key() );
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
m_timeMap.clear();
|
||||
|
||||
m_timeMap = tempMap;
|
||||
|
||||
generateTangents();
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
_this.setAttribute( "pos", startPosition() );
|
||||
_this.setAttribute( "len", length() );
|
||||
_this.setAttribute( "name", name() );
|
||||
_this.setAttribute( "prog", QString::number( progressionType() ) );
|
||||
_this.setAttribute( "tens", QString::number( getTension() ) );
|
||||
_this.setAttribute( "mute", QString::number( isMuted() ) );
|
||||
|
||||
for( timeMap::const_iterator it = m_timeMap.begin();
|
||||
it != m_timeMap.end(); ++it )
|
||||
{
|
||||
QDomElement element = _doc.createElement( "time" );
|
||||
element.setAttribute( "pos", it.key() );
|
||||
element.setAttribute( "value", it.value() );
|
||||
_this.appendChild( element );
|
||||
}
|
||||
|
||||
for( objectVector::const_iterator it = m_objects.begin();
|
||||
it != m_objects.end(); ++it )
|
||||
{
|
||||
if( *it )
|
||||
{
|
||||
QDomElement element = _doc.createElement( "object" );
|
||||
element.setAttribute( "id",
|
||||
ProjectJournal::idToSave( ( *it )->id() ) );
|
||||
_this.appendChild( element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
clear();
|
||||
|
||||
movePosition( _this.attribute( "pos" ).toInt() );
|
||||
setName( _this.attribute( "name" ) );
|
||||
setProgressionType( static_cast<ProgressionTypes>( _this.attribute(
|
||||
"prog" ).toInt() ) );
|
||||
setTension( _this.attribute( "tens" ) );
|
||||
setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() );
|
||||
|
||||
for( QDomNode node = _this.firstChild(); !node.isNull();
|
||||
node = node.nextSibling() )
|
||||
{
|
||||
QDomElement element = node.toElement();
|
||||
if( element.isNull() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if( element.tagName() == "time" )
|
||||
{
|
||||
m_timeMap[element.attribute( "pos" ).toInt()]
|
||||
= LocaleHelper::toFloat(element.attribute("value"));
|
||||
}
|
||||
else if( element.tagName() == "object" )
|
||||
{
|
||||
m_idsToResolve << element.attribute( "id" ).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
int len = _this.attribute( "len" ).toInt();
|
||||
if( len <= 0 )
|
||||
{
|
||||
// TODO: Handle with an upgrade method
|
||||
updateLength();
|
||||
}
|
||||
else
|
||||
{
|
||||
changeLength( len );
|
||||
}
|
||||
generateTangents();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const QString AutomationPattern::name() const
|
||||
{
|
||||
if( !TrackContentObject::name().isEmpty() )
|
||||
{
|
||||
return TrackContentObject::name();
|
||||
}
|
||||
if( !m_objects.isEmpty() && m_objects.first() != NULL )
|
||||
{
|
||||
return m_objects.first()->fullDisplayName();
|
||||
}
|
||||
return tr( "Drag a control while pressing <%1>" ).arg(UI_CTRL_KEY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
|
||||
{
|
||||
return new AutomationPatternView( this, _tv );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool AutomationPattern::isAutomated( const AutomatableModel * _m )
|
||||
{
|
||||
TrackContainer::TrackList l;
|
||||
l += Engine::getSong()->tracks();
|
||||
l += Engine::getBBTrackContainer()->tracks();
|
||||
l += Engine::getSong()->globalAutomationTrack();
|
||||
|
||||
for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
|
||||
{
|
||||
if( ( *it )->type() == Track::AutomationTrack ||
|
||||
( *it )->type() == Track::HiddenAutomationTrack )
|
||||
{
|
||||
const Track::tcoVector & v = ( *it )->getTCOs();
|
||||
for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
|
||||
{
|
||||
const AutomationPattern * a = dynamic_cast<const AutomationPattern *>( *j );
|
||||
if( a && a->hasAutomation() )
|
||||
{
|
||||
for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
|
||||
{
|
||||
if( *k == _m )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief returns a list of all the automation patterns everywhere that are connected to a specific model
|
||||
* \param _m the model we want to look for
|
||||
*/
|
||||
QVector<AutomationPattern *> AutomationPattern::patternsForModel( const AutomatableModel * _m )
|
||||
{
|
||||
QVector<AutomationPattern *> patterns;
|
||||
TrackContainer::TrackList l;
|
||||
l += Engine::getSong()->tracks();
|
||||
l += Engine::getBBTrackContainer()->tracks();
|
||||
l += Engine::getSong()->globalAutomationTrack();
|
||||
|
||||
// go through all tracks...
|
||||
for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
|
||||
{
|
||||
// we want only automation tracks...
|
||||
if( ( *it )->type() == Track::AutomationTrack ||
|
||||
( *it )->type() == Track::HiddenAutomationTrack )
|
||||
{
|
||||
// get patterns in those tracks....
|
||||
const Track::tcoVector & v = ( *it )->getTCOs();
|
||||
// go through all the patterns...
|
||||
for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
|
||||
{
|
||||
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
|
||||
// check that the pattern has automation
|
||||
if( a && a->hasAutomation() )
|
||||
{
|
||||
// now check is the pattern is connected to the model we want by going through all the connections
|
||||
// of the pattern
|
||||
bool has_object = false;
|
||||
for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
|
||||
{
|
||||
if( *k == _m )
|
||||
{
|
||||
has_object = true;
|
||||
}
|
||||
}
|
||||
// if the patterns is connected to the model, add it to the list
|
||||
if( has_object ) { patterns += a; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return patterns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AutomationPattern * AutomationPattern::globalAutomationPattern(
|
||||
AutomatableModel * _m )
|
||||
{
|
||||
AutomationTrack * t = Engine::getSong()->globalAutomationTrack();
|
||||
Track::tcoVector v = t->getTCOs();
|
||||
for( Track::tcoVector::const_iterator j = v.begin(); j != v.end(); ++j )
|
||||
{
|
||||
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
|
||||
if( a )
|
||||
{
|
||||
for( objectVector::const_iterator k = a->m_objects.begin();
|
||||
k != a->m_objects.end(); ++k )
|
||||
{
|
||||
if( *k == _m )
|
||||
{
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutomationPattern * a = new AutomationPattern( t );
|
||||
a->addObject( _m, false );
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::resolveAllIDs()
|
||||
{
|
||||
TrackContainer::TrackList l = Engine::getSong()->tracks() +
|
||||
Engine::getBBTrackContainer()->tracks();
|
||||
l += Engine::getSong()->globalAutomationTrack();
|
||||
for( TrackContainer::TrackList::iterator it = l.begin();
|
||||
it != l.end(); ++it )
|
||||
{
|
||||
if( ( *it )->type() == Track::AutomationTrack ||
|
||||
( *it )->type() == Track::HiddenAutomationTrack )
|
||||
{
|
||||
Track::tcoVector v = ( *it )->getTCOs();
|
||||
for( Track::tcoVector::iterator j = v.begin();
|
||||
j != v.end(); ++j )
|
||||
{
|
||||
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
|
||||
if( a )
|
||||
{
|
||||
for( QVector<jo_id_t>::Iterator k = a->m_idsToResolve.begin();
|
||||
k != a->m_idsToResolve.end(); ++k )
|
||||
{
|
||||
JournallingObject * o = Engine::projectJournal()->
|
||||
journallingObject( *k );
|
||||
if( o && dynamic_cast<AutomatableModel *>( o ) )
|
||||
{
|
||||
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: Remove this block once the automation system gets fixed
|
||||
// This is a temporary fix for https://github.com/LMMS/lmms/issues/3781
|
||||
o = Engine::projectJournal()->journallingObject(ProjectJournal::idFromSave(*k));
|
||||
if( o && dynamic_cast<AutomatableModel *>( o ) )
|
||||
{
|
||||
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: Remove this block once the automation system gets fixed
|
||||
// This is a temporary fix for https://github.com/LMMS/lmms/issues/4781
|
||||
o = Engine::projectJournal()->journallingObject(ProjectJournal::idToSave(*k));
|
||||
if( o && dynamic_cast<AutomatableModel *>( o ) )
|
||||
{
|
||||
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
a->m_idsToResolve.clear();
|
||||
a->dataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::clear()
|
||||
{
|
||||
m_timeMap.clear();
|
||||
m_tangents.clear();
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::objectDestroyed( jo_id_t _id )
|
||||
{
|
||||
// TODO: distict between temporary removal (e.g. LADSPA controls
|
||||
// when switching samplerate) and real deletions because in the latter
|
||||
// case we had to remove ourselves if we're the global automation
|
||||
// pattern of the destroyed object
|
||||
m_idsToResolve += _id;
|
||||
|
||||
for( objectVector::Iterator objIt = m_objects.begin();
|
||||
objIt != m_objects.end(); objIt++ )
|
||||
{
|
||||
Q_ASSERT( !(*objIt).isNull() );
|
||||
if( (*objIt)->id() == _id )
|
||||
{
|
||||
//Assign to objIt so that this loop work even break; is removed.
|
||||
objIt = m_objects.erase( objIt );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::cleanObjects()
|
||||
{
|
||||
for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
|
||||
{
|
||||
if( *it )
|
||||
{
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
it = m_objects.erase( it );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::generateTangents()
|
||||
{
|
||||
generateTangents(m_timeMap.begin(), m_timeMap.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::generateTangents( timeMap::const_iterator it,
|
||||
int numToGenerate )
|
||||
{
|
||||
if( m_timeMap.size() < 2 && numToGenerate > 0 )
|
||||
{
|
||||
m_tangents[it.key()] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for( int i = 0; i < numToGenerate; i++ )
|
||||
{
|
||||
if( it == m_timeMap.begin() )
|
||||
{
|
||||
m_tangents[it.key()] =
|
||||
( (it+1).value() - (it).value() ) /
|
||||
( (it+1).key() - (it).key() );
|
||||
}
|
||||
else if( it+1 == m_timeMap.end() )
|
||||
{
|
||||
m_tangents[it.key()] = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tangents[it.key()] =
|
||||
( (it+1).value() - (it-1).value() ) /
|
||||
( (it+1).key() - (it-1).key() );
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* BBTrackContainer.cpp - model-component of BB-Editor
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "BBTrackContainer.h"
|
||||
#include "BBTrack.h"
|
||||
#include "Engine.h"
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
|
||||
BBTrackContainer::BBTrackContainer() :
|
||||
TrackContainer(),
|
||||
m_bbComboBoxModel( this )
|
||||
{
|
||||
connect( &m_bbComboBoxModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( currentBBChanged() ) );
|
||||
// we *always* want to receive updates even in case BB actually did
|
||||
// not change upon setCurrentBB()-call
|
||||
connect( &m_bbComboBoxModel, SIGNAL( dataUnchanged() ),
|
||||
this, SLOT( currentBBChanged() ) );
|
||||
setType( BBContainer );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
BBTrackContainer::~BBTrackContainer()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool BBTrackContainer::play( MidiTime _start, fpp_t _frames,
|
||||
f_cnt_t _offset, int _tco_num )
|
||||
{
|
||||
bool played_a_note = false;
|
||||
if( lengthOfBB( _tco_num ) <= 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_start = _start % ( lengthOfBB( _tco_num ) * MidiTime::ticksPerTact() );
|
||||
|
||||
TrackList tl = tracks();
|
||||
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
|
||||
{
|
||||
if( ( *it )->play( _start, _frames, _offset, _tco_num ) )
|
||||
{
|
||||
played_a_note = true;
|
||||
}
|
||||
}
|
||||
|
||||
return played_a_note;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::updateAfterTrackAdd()
|
||||
{
|
||||
if( numOfBBs() == 0 && !Engine::getSong()->isLoadingProject() )
|
||||
{
|
||||
Engine::getSong()->addBBTrack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
tact_t BBTrackContainer::lengthOfBB( int _bb ) const
|
||||
{
|
||||
MidiTime max_length = MidiTime::ticksPerTact();
|
||||
|
||||
const TrackList & tl = tracks();
|
||||
for (Track* t : tl)
|
||||
{
|
||||
// Don't create TCOs here if not exist
|
||||
if (_bb < t->numOfTCOs())
|
||||
{
|
||||
max_length = qMax(max_length, t->getTCO( _bb )->length());
|
||||
}
|
||||
}
|
||||
|
||||
return max_length.nextFullTact();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int BBTrackContainer::numOfBBs() const
|
||||
{
|
||||
return Engine::getSong()->countTracks( Track::BBTrack );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::removeBB( int _bb )
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
|
||||
{
|
||||
delete ( *it )->getTCO( _bb );
|
||||
( *it )->removeTact( _bb * DefaultTicksPerTact );
|
||||
}
|
||||
if( _bb <= currentBB() )
|
||||
{
|
||||
setCurrentBB( qMax( currentBB() - 1, 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::swapBB( int _bb1, int _bb2 )
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
|
||||
{
|
||||
( *it )->swapPositionOfTCOs( _bb1, _bb2 );
|
||||
}
|
||||
updateComboBox();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::updateBBTrack( TrackContentObject * _tco )
|
||||
{
|
||||
BBTrack * t = BBTrack::findBBTrack( _tco->startPosition() /
|
||||
DefaultTicksPerTact );
|
||||
if( t != NULL )
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::fixIncorrectPositions()
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
|
||||
{
|
||||
for( int i = 0; i < numOfBBs(); ++i )
|
||||
{
|
||||
( *it )->getTCO( i )->movePosition( MidiTime( i, 0 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::play()
|
||||
{
|
||||
if( Engine::getSong()->playMode() != Song::Mode_PlayBB )
|
||||
{
|
||||
Engine::getSong()->playBB();
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine::getSong()->togglePause();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::stop()
|
||||
{
|
||||
Engine::getSong()->stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::updateComboBox()
|
||||
{
|
||||
const int cur_bb = currentBB();
|
||||
|
||||
m_bbComboBoxModel.clear();
|
||||
|
||||
for( int i = 0; i < numOfBBs(); ++i )
|
||||
{
|
||||
BBTrack * bbt = BBTrack::findBBTrack( i );
|
||||
m_bbComboBoxModel.addItem( bbt->name() );
|
||||
}
|
||||
setCurrentBB( cur_bb );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::currentBBChanged()
|
||||
{
|
||||
// now update all track-labels (the current one has to become white,
|
||||
// the others gray)
|
||||
TrackList tl = Engine::getSong()->tracks();
|
||||
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
|
||||
{
|
||||
if( ( *it )->type() == Track::BBTrack )
|
||||
{
|
||||
( *it )->dataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::createTCOsForBB( int _bb )
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for( int i = 0; i < tl.size(); ++i )
|
||||
{
|
||||
tl[i]->createTCOsForBB( _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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
WaveMipMap BandLimitedWave::s_waveforms[4] = { };
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
std::array<WaveMipMap, BandLimitedWave::Waveforms::NumBLWaveforms> BandLimitedWave::s_waveforms = { };
|
||||
bool BandLimitedWave::s_wavesGenerated = false;
|
||||
QString BandLimitedWave::s_wavetableDir = "";
|
||||
|
||||
@@ -270,3 +273,5 @@ moogfile.close();
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -26,21 +26,24 @@
|
||||
|
||||
#include "BufferManager.h"
|
||||
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include <cstring>
|
||||
|
||||
#include "MemoryManager.h"
|
||||
|
||||
static fpp_t framesPerPeriod;
|
||||
|
||||
void BufferManager::init( fpp_t framesPerPeriod )
|
||||
namespace lmms
|
||||
{
|
||||
::framesPerPeriod = framesPerPeriod;
|
||||
|
||||
fpp_t BufferManager::s_framesPerPeriod;
|
||||
|
||||
void BufferManager::init( fpp_t fpp )
|
||||
{
|
||||
s_framesPerPeriod = fpp;
|
||||
}
|
||||
|
||||
|
||||
sampleFrame * BufferManager::acquire()
|
||||
{
|
||||
return MM_ALLOC( sampleFrame, ::framesPerPeriod );
|
||||
return MM_ALLOC<sampleFrame>( s_framesPerPeriod );
|
||||
}
|
||||
|
||||
void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset )
|
||||
@@ -62,3 +65,4 @@ void BufferManager::release( sampleFrame * buf )
|
||||
MM_FREE( buf );
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
set(LMMS_SRCS
|
||||
${LMMS_SRCS}
|
||||
|
||||
core/AudioEngine.cpp
|
||||
core/AudioEngineProfiler.cpp
|
||||
core/AudioEngineWorkerThread.cpp
|
||||
core/AutomatableModel.cpp
|
||||
core/AutomationPattern.cpp
|
||||
core/AutomationClip.cpp
|
||||
core/AutomationNode.cpp
|
||||
core/BandLimitedWave.cpp
|
||||
core/base64.cpp
|
||||
core/BBTrackContainer.cpp
|
||||
core/BufferManager.cpp
|
||||
core/Clipboard.cpp
|
||||
core/ComboBoxModel.cpp
|
||||
@@ -18,7 +22,7 @@ set(LMMS_SRCS
|
||||
core/Engine.cpp
|
||||
core/EnvelopeAndLfoParameters.cpp
|
||||
core/fft_helpers.cpp
|
||||
core/FxMixer.cpp
|
||||
core/Mixer.cpp
|
||||
core/ImportFilter.cpp
|
||||
core/InlineAutomation.cpp
|
||||
core/Instrument.cpp
|
||||
@@ -26,24 +30,27 @@ set(LMMS_SRCS
|
||||
core/InstrumentPlayHandle.cpp
|
||||
core/InstrumentSoundShaping.cpp
|
||||
core/JournallingObject.cpp
|
||||
core/Keymap.cpp
|
||||
core/Ladspa2LMMS.cpp
|
||||
core/LadspaControl.cpp
|
||||
core/LadspaManager.cpp
|
||||
core/LfoController.cpp
|
||||
core/LinkedModelGroups.cpp
|
||||
core/LocklessAllocator.cpp
|
||||
core/MemoryHelper.cpp
|
||||
core/MemoryManager.cpp
|
||||
core/MeterModel.cpp
|
||||
core/MicroTimer.cpp
|
||||
core/Mixer.cpp
|
||||
core/MixerProfiler.cpp
|
||||
core/MixerWorkerThread.cpp
|
||||
core/Microtuner.cpp
|
||||
core/MixHelpers.cpp
|
||||
core/Model.cpp
|
||||
core/ModelVisitor.cpp
|
||||
core/Note.cpp
|
||||
core/NotePlayHandle.cpp
|
||||
core/Oscillator.cpp
|
||||
core/PathUtil.cpp
|
||||
core/PatternClip.cpp
|
||||
core/PatternStore.cpp
|
||||
core/PeakController.cpp
|
||||
core/PerfLog.cpp
|
||||
core/Piano.cpp
|
||||
@@ -59,14 +66,18 @@ set(LMMS_SRCS
|
||||
core/RenderManager.cpp
|
||||
core/RingBuffer.cpp
|
||||
core/SampleBuffer.cpp
|
||||
core/SampleClip.cpp
|
||||
core/SamplePlayHandle.cpp
|
||||
core/SampleRecordHandle.cpp
|
||||
core/Scale.cpp
|
||||
core/SerializingObject.cpp
|
||||
core/Song.cpp
|
||||
core/TempoSyncKnobModel.cpp
|
||||
core/TimePos.cpp
|
||||
core/ToolPlugin.cpp
|
||||
core/Track.cpp
|
||||
core/TrackContainer.cpp
|
||||
core/Clip.cpp
|
||||
core/ValueBuffer.cpp
|
||||
core/VstSyncController.cpp
|
||||
core/StepRecorder.cpp
|
||||
@@ -88,16 +99,28 @@ set(LMMS_SRCS
|
||||
core/audio/AudioSampleRecorder.cpp
|
||||
core/audio/AudioSdl.cpp
|
||||
|
||||
core/lv2/Lv2Basics.cpp
|
||||
core/lv2/Lv2ControlBase.cpp
|
||||
core/lv2/Lv2Evbuf.cpp
|
||||
core/lv2/Lv2Features.cpp
|
||||
core/lv2/Lv2Ports.cpp
|
||||
core/lv2/Lv2Proc.cpp
|
||||
core/lv2/Lv2Manager.cpp
|
||||
core/lv2/Lv2Options.cpp
|
||||
core/lv2/Lv2SubPluginFeatures.cpp
|
||||
core/lv2/Lv2UridCache.cpp
|
||||
core/lv2/Lv2UridMap.cpp
|
||||
|
||||
core/midi/MidiAlsaRaw.cpp
|
||||
core/midi/MidiAlsaSeq.cpp
|
||||
core/midi/MidiClient.cpp
|
||||
core/midi/MidiController.cpp
|
||||
core/midi/MidiEventToByteSeq.cpp
|
||||
core/midi/MidiJack.cpp
|
||||
core/midi/MidiOss.cpp
|
||||
core/midi/MidiSndio.cpp
|
||||
core/midi/MidiApple.cpp
|
||||
core/midi/MidiPort.cpp
|
||||
core/midi/MidiTime.cpp
|
||||
core/midi/MidiWinMM.cpp
|
||||
|
||||
PARENT_SCOPE
|
||||
|
||||
203
src/core/Clip.cpp
Normal file
203
src/core/Clip.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Clip.cpp - implementation of Clip class
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "Clip.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
|
||||
#include "AutomationEditor.h"
|
||||
#include "AutomationClip.h"
|
||||
#include "Engine.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
/*! \brief Create a new Clip
|
||||
*
|
||||
* Creates a new clip for the given track.
|
||||
*
|
||||
* \param _track The track that will contain the new object
|
||||
*/
|
||||
Clip::Clip( Track * track ) :
|
||||
Model( track ),
|
||||
m_track( track ),
|
||||
m_startPosition(),
|
||||
m_length(),
|
||||
m_mutedModel( false, this, tr( "Mute" ) ),
|
||||
m_selectViewOnCreate( false ),
|
||||
m_color( 128, 128, 128 ),
|
||||
m_useCustomClipColor( false )
|
||||
{
|
||||
if( getTrack() )
|
||||
{
|
||||
getTrack()->addClip( this );
|
||||
}
|
||||
setJournalling( false );
|
||||
movePosition( 0 );
|
||||
changeLength( 0 );
|
||||
setJournalling( true );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Destroy a Clip
|
||||
*
|
||||
* Destroys the given clip.
|
||||
*
|
||||
*/
|
||||
Clip::~Clip()
|
||||
{
|
||||
emit destroyedClip();
|
||||
|
||||
if( getTrack() )
|
||||
{
|
||||
getTrack()->removeClip( this );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Move this Clip's position in time
|
||||
*
|
||||
* If the clip has moved, update its position. We
|
||||
* also add a journal entry for undo and update the display.
|
||||
*
|
||||
* \param _pos The new position of the clip.
|
||||
*/
|
||||
void Clip::movePosition( const TimePos & pos )
|
||||
{
|
||||
TimePos newPos = qMax(0, pos.getTicks());
|
||||
if (m_startPosition != newPos)
|
||||
{
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
m_startPosition = newPos;
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
Engine::getSong()->updateLength();
|
||||
emit positionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Change the length of this Clip
|
||||
*
|
||||
* If the clip's length has changed, update it. We
|
||||
* also add a journal entry for undo and update the display.
|
||||
*
|
||||
* \param _length The new length of the clip.
|
||||
*/
|
||||
void Clip::changeLength( const TimePos & length )
|
||||
{
|
||||
m_length = length;
|
||||
Engine::getSong()->updateLength();
|
||||
emit lengthChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool Clip::comparePosition(const Clip *a, const Clip *b)
|
||||
{
|
||||
return a->startPosition() < b->startPosition();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Copies the state of a Clip to another Clip
|
||||
*
|
||||
* This method copies the state of a Clip to another Clip
|
||||
*/
|
||||
void Clip::copyStateTo( Clip *src, Clip *dst )
|
||||
{
|
||||
// If the node names match we copy the state
|
||||
if( src->nodeName() == dst->nodeName() ){
|
||||
QDomDocument doc;
|
||||
QDomElement parent = doc.createElement( "StateCopy" );
|
||||
src->saveState( doc, parent );
|
||||
|
||||
const TimePos pos = dst->startPosition();
|
||||
dst->restoreState( parent.firstChild().toElement() );
|
||||
dst->movePosition( pos );
|
||||
|
||||
AutomationClip::resolveAllIDs();
|
||||
gui::getGUI()->automationEditor()->m_editor->updateAfterClipChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Mutes this Clip
|
||||
*
|
||||
* Restore the previous state of this clip. This will
|
||||
* restore the position or the length of the clip
|
||||
* depending on what was changed.
|
||||
*
|
||||
* \param _je The journal entry to undo
|
||||
*/
|
||||
void Clip::toggleMute()
|
||||
{
|
||||
m_mutedModel.setValue( !m_mutedModel.value() );
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos Clip::startTimeOffset() const
|
||||
{
|
||||
return m_startTimeOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Clip::setStartTimeOffset( const TimePos &startTimeOffset )
|
||||
{
|
||||
m_startTimeOffset = startTimeOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Clip::useCustomClipColor( bool b )
|
||||
{
|
||||
if (b == m_useCustomClipColor) { return; }
|
||||
m_useCustomClipColor = b;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
|
||||
bool Clip::hasColor()
|
||||
{
|
||||
return usesCustomClipColor() || getTrack()->useColor();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Clipboard.cpp - the clipboard for patterns, notes etc.
|
||||
* Clipboard.cpp - the clipboard for clips, notes etc.
|
||||
*
|
||||
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
@@ -22,32 +22,75 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "Clipboard.h"
|
||||
#include "JournallingObject.h"
|
||||
|
||||
|
||||
Clipboard::Map Clipboard::content;
|
||||
|
||||
|
||||
void Clipboard::copy( JournallingObject * _obj )
|
||||
namespace lmms::Clipboard
|
||||
{
|
||||
QDomDocument doc;
|
||||
QDomElement parent = doc.createElement( "Clipboard" );
|
||||
_obj->saveState( doc, parent );
|
||||
content[_obj->nodeName()] = parent.firstChild().toElement();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const QDomElement * Clipboard::getContent( const QString & _node_name )
|
||||
{
|
||||
if( content.find( _node_name ) != content.end() )
|
||||
const QMimeData * getMimeData()
|
||||
{
|
||||
return &content[_node_name];
|
||||
return QApplication::clipboard()->mimeData( QClipboard::Clipboard );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool hasFormat( MimeType mT )
|
||||
{
|
||||
return getMimeData()->hasFormat( mimeType( mT ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void copyString( const QString & str, MimeType mT )
|
||||
{
|
||||
auto content = new QMimeData;
|
||||
|
||||
content->setData( mimeType( mT ), str.toUtf8() );
|
||||
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString getString( MimeType mT )
|
||||
{
|
||||
return QString( getMimeData()->data( mimeType( mT ) ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void copyStringPair( const QString & key, const QString & value )
|
||||
{
|
||||
QString finalString = key + ":" + value;
|
||||
|
||||
auto content = new QMimeData;
|
||||
content->setData( mimeType( MimeType::StringPair ), finalString.toUtf8() );
|
||||
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString decodeKey( const QMimeData * mimeData )
|
||||
{
|
||||
return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 0, 0 ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString decodeValue( const QMimeData * mimeData )
|
||||
{
|
||||
return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 1, -1 ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms::Clipboard
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
*/
|
||||
|
||||
#include "ComboBoxModel.h"
|
||||
#include "embed.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::move;
|
||||
@@ -35,6 +39,12 @@ void ComboBoxModel::addItem( QString item, unique_ptr<PixmapLoader> loader )
|
||||
}
|
||||
|
||||
|
||||
void ComboBoxModel::replaceItem(std::size_t index, QString item, unique_ptr<PixmapLoader> loader)
|
||||
{
|
||||
assert(index < m_items.size());
|
||||
m_items[index] = Item(move(item), move(loader));
|
||||
emit propertiesChanged();
|
||||
}
|
||||
|
||||
|
||||
void ComboBoxModel::clear()
|
||||
@@ -62,6 +72,6 @@ int ComboBoxModel::findText( const QString& txt ) const
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,18 +25,19 @@
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
|
||||
#include "Song.h"
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ControllerConnection.h"
|
||||
#include "ControllerDialog.h"
|
||||
#include "LfoController.h"
|
||||
#include "MidiController.h"
|
||||
#include "PeakController.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
long Controller::s_periods = 0;
|
||||
QVector<Controller *> Controller::s_controllers;
|
||||
@@ -47,7 +48,7 @@ Controller::Controller( ControllerTypes _type, Model * _parent,
|
||||
const QString & _display_name ) :
|
||||
Model( _parent, _display_name ),
|
||||
JournallingObject(),
|
||||
m_valueBuffer( Engine::mixer()->framesPerPeriod() ),
|
||||
m_valueBuffer( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_bufferLastUpdated( -1 ),
|
||||
m_connectionCount( 0 ),
|
||||
m_type( _type )
|
||||
@@ -140,7 +141,7 @@ void Controller::updateValueBuffer()
|
||||
// Get position in frames
|
||||
unsigned int Controller::runningFrames()
|
||||
{
|
||||
return s_periods * Engine::mixer()->framesPerPeriod();
|
||||
return s_periods * Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +149,7 @@ unsigned int Controller::runningFrames()
|
||||
// Get position in seconds
|
||||
float Controller::runningTime()
|
||||
{
|
||||
return runningFrames() / Engine::mixer()->processingSampleRate();
|
||||
return runningFrames() / Engine::audioEngine()->processingSampleRate();
|
||||
}
|
||||
|
||||
|
||||
@@ -183,20 +184,20 @@ void Controller::resetFrameCounter()
|
||||
|
||||
Controller * Controller::create( ControllerTypes _ct, Model * _parent )
|
||||
{
|
||||
static Controller * dummy = NULL;
|
||||
Controller * c = NULL;
|
||||
static Controller * dummy = nullptr;
|
||||
Controller * c = nullptr;
|
||||
|
||||
switch( _ct )
|
||||
{
|
||||
case Controller::DummyController:
|
||||
if (!dummy)
|
||||
dummy = new Controller( DummyController, NULL,
|
||||
dummy = new Controller( DummyController, nullptr,
|
||||
QString() );
|
||||
c = dummy;
|
||||
break;
|
||||
|
||||
case Controller::LfoController:
|
||||
c = new ::LfoController( _parent );
|
||||
c = new class LfoController( _parent );
|
||||
break;
|
||||
|
||||
case Controller::PeakController:
|
||||
@@ -205,7 +206,7 @@ Controller * Controller::create( ControllerTypes _ct, Model * _parent )
|
||||
break;
|
||||
|
||||
case Controller::MidiController:
|
||||
c = new ::MidiController( _parent );
|
||||
c = new class MidiController( _parent );
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -231,7 +232,7 @@ Controller * Controller::create( const QDomElement & _this, Model * _parent )
|
||||
_parent );
|
||||
}
|
||||
|
||||
if( c != NULL )
|
||||
if( c != nullptr )
|
||||
{
|
||||
c->restoreState( _this );
|
||||
}
|
||||
@@ -245,8 +246,8 @@ bool Controller::hasModel( const Model * m ) const
|
||||
{
|
||||
for (QObject * c : children())
|
||||
{
|
||||
AutomatableModel * am = qobject_cast<AutomatableModel*>(c);
|
||||
if( am != NULL )
|
||||
auto am = qobject_cast<AutomatableModel*>(c);
|
||||
if( am != nullptr )
|
||||
{
|
||||
if( am == m )
|
||||
{
|
||||
@@ -254,7 +255,7 @@ bool Controller::hasModel( const Model * m ) const
|
||||
}
|
||||
|
||||
ControllerConnection * cc = am->controllerConnection();
|
||||
if( cc != NULL && cc->getController()->hasModel( m ) )
|
||||
if( cc != nullptr && cc->getController()->hasModel( m ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -293,9 +294,9 @@ QString Controller::nodeName() const
|
||||
|
||||
|
||||
|
||||
ControllerDialog * Controller::createDialog( QWidget * _parent )
|
||||
gui::ControllerDialog * Controller::createDialog( QWidget * _parent )
|
||||
{
|
||||
ControllerDialog * d = new ControllerDialog( this, _parent );
|
||||
auto d = new gui::ControllerDialog(this, _parent);
|
||||
|
||||
return d;
|
||||
}
|
||||
@@ -325,7 +326,7 @@ int Controller::connectionCount() const{
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,24 +31,27 @@
|
||||
#include "Song.h"
|
||||
#include "ControllerConnection.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
ControllerConnectionVector ControllerConnection::s_connections;
|
||||
|
||||
|
||||
|
||||
ControllerConnection::ControllerConnection( Controller * _controller ) :
|
||||
m_controller( NULL ),
|
||||
ControllerConnection::ControllerConnection(Controller * _controller) :
|
||||
m_controller( nullptr ),
|
||||
m_controllerId( -1 ),
|
||||
m_ownsController( false )
|
||||
m_ownsController(false)
|
||||
{
|
||||
if( _controller != NULL )
|
||||
if( _controller != nullptr )
|
||||
{
|
||||
setController( _controller );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_controller = Controller::create( Controller::DummyController,
|
||||
NULL );
|
||||
nullptr );
|
||||
}
|
||||
s_connections.append( this );
|
||||
}
|
||||
@@ -57,7 +60,7 @@ ControllerConnection::ControllerConnection( Controller * _controller ) :
|
||||
|
||||
|
||||
ControllerConnection::ControllerConnection( int _controllerId ) :
|
||||
m_controller( Controller::create( Controller::DummyController, NULL ) ),
|
||||
m_controller( Controller::create( Controller::DummyController, nullptr ) ),
|
||||
m_controllerId( _controllerId ),
|
||||
m_ownsController( false )
|
||||
{
|
||||
@@ -95,7 +98,7 @@ void ControllerConnection::setController( Controller * _controller )
|
||||
if( m_ownsController && m_controller )
|
||||
{
|
||||
delete m_controller;
|
||||
m_controller = NULL;
|
||||
m_controller = nullptr;
|
||||
}
|
||||
|
||||
if( m_controller && m_controller->type() != Controller::DummyController )
|
||||
@@ -105,7 +108,7 @@ void ControllerConnection::setController( Controller * _controller )
|
||||
|
||||
if( !_controller )
|
||||
{
|
||||
m_controller = Controller::create( Controller::DummyController, NULL );
|
||||
m_controller = Controller::create( Controller::DummyController, nullptr );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,18 +119,18 @@ void ControllerConnection::setController( Controller * _controller )
|
||||
if( _controller->type() != Controller::DummyController )
|
||||
{
|
||||
_controller->addConnection( this );
|
||||
QObject::connect( _controller, SIGNAL( valueChanged() ),
|
||||
this, SIGNAL( valueChanged() ), Qt::DirectConnection );
|
||||
QObject::connect( _controller, SIGNAL(valueChanged()),
|
||||
this, SIGNAL(valueChanged()), Qt::DirectConnection );
|
||||
}
|
||||
|
||||
m_ownsController =
|
||||
( _controller->type() == Controller::MidiController );
|
||||
(_controller->type() == Controller::MidiController);
|
||||
|
||||
// If we don't own the controller, allow deletion of controller
|
||||
// to delete the connection
|
||||
if( !m_ownsController ) {
|
||||
QObject::connect( _controller, SIGNAL( destroyed() ),
|
||||
this, SLOT( deleteConnection() ) );
|
||||
QObject::connect( _controller, SIGNAL(destroyed()),
|
||||
this, SLOT(deleteConnection()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +223,7 @@ void ControllerConnection::loadSettings( const QDomElement & _this )
|
||||
}
|
||||
else
|
||||
{
|
||||
m_controller = Controller::create( Controller::DummyController, NULL );
|
||||
m_controller = Controller::create( Controller::DummyController, nullptr );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,4 +236,4 @@ void ControllerConnection::deleteConnection()
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,26 +29,22 @@
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#include <math.h> //sin(), exp(), etc.
|
||||
#include <cmath> //sin(), exp(), etc.
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
#define powf pow
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
//not #if LMMS_BUILD_WIN32 because we have strncasecmp in mingw
|
||||
#define strcasecmp _stricmp
|
||||
#endif
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define WORD __u16
|
||||
#define DWORD __u32
|
||||
#define WAVE_FORMAT_PCM 0x0001
|
||||
|
||||
// const int Fs = 44100;
|
||||
const float TwoPi = 6.2831853f;
|
||||
const int MAX = 0;
|
||||
@@ -71,7 +67,7 @@ long wavewords, wavemode=0;
|
||||
float mem_t=1.0f, mem_o=1.0f, mem_n=1.0f, mem_b=1.0f, mem_tune=1.0f, mem_time=1.0f;
|
||||
|
||||
|
||||
int DrumSynth::LongestEnv(void)
|
||||
int DrumSynth::LongestEnv()
|
||||
{
|
||||
long e, eon, p;
|
||||
float l=0.f;
|
||||
@@ -90,7 +86,7 @@ int DrumSynth::LongestEnv(void)
|
||||
}
|
||||
|
||||
|
||||
float DrumSynth::LoudestEnv(void)
|
||||
float DrumSynth::LoudestEnv()
|
||||
{
|
||||
float loudest=0.f;
|
||||
int i=0;
|
||||
@@ -206,7 +202,7 @@ int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const c
|
||||
break;
|
||||
|
||||
k = strtok(line, " \t=");
|
||||
b = strtok(NULL, "\n\r\0");
|
||||
b = strtok(nullptr, "\n\r\0");
|
||||
|
||||
if (k != 0 && strcasecmp(k, key)==0) {
|
||||
if (b==0) {
|
||||
@@ -325,15 +321,17 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
timestretch = .01f * mem_time * GetPrivateProfileFloat(sec,"Stretch",100.0,dsfile);
|
||||
if(timestretch<0.2f) timestretch=0.2f;
|
||||
if(timestretch>10.f) timestretch=10.f;
|
||||
// the unit of envelope lengths is a sample in 44100Hz sample rate, so correct it
|
||||
timestretch *= Fs / 44100.f;
|
||||
|
||||
DGain = 1.0f; //leave this here!
|
||||
DGain = (float)powf(10.0, 0.05 * GetPrivateProfileFloat(sec,"Level",0,dsfile));
|
||||
DGain = static_cast<float>(std::pow(10.0, 0.05 * GetPrivateProfileFloat(sec,"Level",0,dsfile)));
|
||||
|
||||
MasterTune = GetPrivateProfileFloat(sec,"Tuning",0.0,dsfile);
|
||||
MasterTune = (float)powf(1.0594631f, MasterTune + mem_tune);
|
||||
MasterTune = static_cast<float>(std::pow(1.0594631f, MasterTune + mem_tune));
|
||||
MainFilter = 2 * GetPrivateProfileInt(sec,"Filter",0,dsfile);
|
||||
MFres = 0.0101f * GetPrivateProfileFloat(sec,"Resonance",0.0,dsfile);
|
||||
MFres = (float)powf(MFres, 0.5f);
|
||||
MFres = static_cast<float>(std::pow(MFres, 0.5f));
|
||||
|
||||
HighPass = GetPrivateProfileInt(sec,"HighPass",0,dsfile);
|
||||
GetEnv(7, sec, "FilterEnv", dsfile);
|
||||
@@ -368,7 +366,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
TDroopRate = GetPrivateProfileFloat(sec,"Droop",0.f,dsfile);
|
||||
if(TDroopRate>0.f)
|
||||
{
|
||||
TDroopRate = (float)powf(10.0f, (TDroopRate - 20.0f) / 30.0f);
|
||||
TDroopRate = static_cast<float>(std::pow(10.0f, (TDroopRate - 20.0f) / 30.0f));
|
||||
TDroopRate = TDroopRate * -4.f / envData[1][MAX];
|
||||
TDroop = 1;
|
||||
F2 = F1+((F2-F1)/(1.f-(float)exp(TDroopRate * envData[1][MAX])));
|
||||
@@ -391,7 +389,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
OW1 = GetPrivateProfileInt(sec,"Wave1",0,dsfile);
|
||||
OW2 = GetPrivateProfileInt(sec,"Wave2",0,dsfile);
|
||||
OBal2 = (float)GetPrivateProfileInt(sec,"Param",50,dsfile);
|
||||
ODrive = (float)powf(OBal2, 3.0f) / (float)powf(50.0f, 3.0f);
|
||||
ODrive = static_cast<float>(std::pow(OBal2, 3.0f)) / std::pow(50.0f, 3.0f);
|
||||
OBal2 *= 0.01f;
|
||||
OBal1 = 1.f - OBal2;
|
||||
Ophi1 = Tphi;
|
||||
@@ -451,8 +449,8 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
{
|
||||
DAtten = DGain * (short)LoudestEnv();
|
||||
if(DAtten>32700) clippoint=32700; else clippoint=(short)DAtten;
|
||||
DAtten = (float)powf(2.0, 2.0 * GetPrivateProfileInt(sec,"Bits",0,dsfile));
|
||||
DGain = DAtten * DGain * (float)powf(10.0, 0.05 * GetPrivateProfileInt(sec,"Clipping",0,dsfile));
|
||||
DAtten = static_cast<float>(std::pow(2.0, 2.0 * GetPrivateProfileInt(sec,"Bits",0,dsfile)));
|
||||
DGain = DAtten * DGain * static_cast<float>(std::pow(10.0, 0.05 * GetPrivateProfileInt(sec,"Clipping",0,dsfile)));
|
||||
}
|
||||
|
||||
//prepare envelopes
|
||||
@@ -464,7 +462,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
//if(wave!=NULL) free(wave);
|
||||
//wave = new int16_t[channels * (Length + 1280)]; //wave memory buffer
|
||||
wave = new int16_t[channels * Length]; //wave memory buffer
|
||||
if(wave==NULL) {return 0;}
|
||||
if(wave==nullptr) {return 0;}
|
||||
wavewords = 0;
|
||||
|
||||
/*
|
||||
@@ -662,7 +660,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
|
||||
MFtmp = envData[7][ENV];
|
||||
if(MFtmp >0.2f)
|
||||
MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1);
|
||||
MFfb = 1.001f - static_cast<float>(std::pow(10.0f, MFtmp - 1));
|
||||
else
|
||||
MFfb = 0.999f - 0.7824f * MFtmp;
|
||||
|
||||
@@ -680,7 +678,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
|
||||
MFtmp = envData[7][ENV];
|
||||
if(MFtmp >0.2f)
|
||||
MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1);
|
||||
MFfb = 1.001f - static_cast<float>(std::pow(10.0f, MFtmp - 1));
|
||||
else
|
||||
MFfb = 0.999f - 0.7824f * MFtmp;
|
||||
|
||||
@@ -746,3 +744,5 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
|
||||
return Length;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -32,12 +32,15 @@
|
||||
|
||||
#include "ConfigManager.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Effect::Effect( const Plugin::Descriptor * _desc,
|
||||
Model * _parent,
|
||||
const Descriptor::SubPluginFeatures::Key * _key ) :
|
||||
Plugin( _desc, _parent, _key ),
|
||||
m_parent( NULL ),
|
||||
m_parent( nullptr ),
|
||||
m_processors( 1 ),
|
||||
m_okay( true ),
|
||||
m_noRun( false ),
|
||||
@@ -49,7 +52,7 @@ Effect::Effect( const Plugin::Descriptor * _desc,
|
||||
m_autoQuitModel( 1.0f, 1.0f, 8000.0f, 100.0f, 1.0f, this, tr( "Decay" ) ),
|
||||
m_autoQuitDisabled( false )
|
||||
{
|
||||
m_srcState[0] = m_srcState[1] = NULL;
|
||||
m_srcState[0] = m_srcState[1] = nullptr;
|
||||
reinitSRC();
|
||||
|
||||
if( ConfigManager::inst()->value( "ui", "disableautoquit").toInt() )
|
||||
@@ -63,11 +66,11 @@ Effect::Effect( const Plugin::Descriptor * _desc,
|
||||
|
||||
Effect::~Effect()
|
||||
{
|
||||
for( int i = 0; i < 2; ++i )
|
||||
for (const auto& state : m_srcState)
|
||||
{
|
||||
if( m_srcState[i] != NULL )
|
||||
if (state != nullptr)
|
||||
{
|
||||
src_delete( m_srcState[i] );
|
||||
src_delete(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,10 +121,10 @@ Effect * Effect::instantiate( const QString& pluginName,
|
||||
{
|
||||
Plugin * p = Plugin::instantiateWithKey( pluginName, _parent, _key );
|
||||
// check whether instantiated plugin is an effect
|
||||
if( dynamic_cast<Effect *>( p ) != NULL )
|
||||
if( dynamic_cast<Effect *>( p ) != nullptr )
|
||||
{
|
||||
// everything ok, so return pointer
|
||||
Effect * effect = dynamic_cast<Effect *>( p );
|
||||
auto effect = dynamic_cast<Effect*>(p);
|
||||
effect->m_parent = dynamic_cast<EffectChain *>(_parent);
|
||||
return effect;
|
||||
}
|
||||
@@ -129,7 +132,7 @@ Effect * Effect::instantiate( const QString& pluginName,
|
||||
// not quite... so delete plugin and leave it up to the caller to instantiate a DummyEffect
|
||||
delete p;
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -162,9 +165,9 @@ void Effect::checkGate( double _out_sum )
|
||||
|
||||
|
||||
|
||||
PluginView * Effect::instantiateView( QWidget * _parent )
|
||||
gui::PluginView * Effect::instantiateView( QWidget * _parent )
|
||||
{
|
||||
return new EffectView( this, _parent );
|
||||
return new gui::EffectView( this, _parent );
|
||||
}
|
||||
|
||||
|
||||
@@ -172,17 +175,15 @@ PluginView * Effect::instantiateView( QWidget * _parent )
|
||||
|
||||
void Effect::reinitSRC()
|
||||
{
|
||||
for( int i = 0; i < 2; ++i )
|
||||
for (auto& state : m_srcState)
|
||||
{
|
||||
if( m_srcState[i] != NULL )
|
||||
if (state != nullptr)
|
||||
{
|
||||
src_delete( m_srcState[i] );
|
||||
src_delete(state);
|
||||
}
|
||||
int error;
|
||||
if( ( m_srcState[i] = src_new(
|
||||
Engine::mixer()->currentQualitySettings().
|
||||
libsrcInterpolation(),
|
||||
DEFAULT_CHANNELS, &error ) ) == NULL )
|
||||
const int currentInterpolation = Engine::audioEngine()->currentQualitySettings().libsrcInterpolation();
|
||||
if((state = src_new(currentInterpolation, DEFAULT_CHANNELS, &error)) == nullptr)
|
||||
{
|
||||
qFatal( "Error: src_new() failed in effect.cpp!\n" );
|
||||
}
|
||||
@@ -197,14 +198,14 @@ void Effect::resample( int _i, const sampleFrame * _src_buf,
|
||||
sampleFrame * _dst_buf, sample_rate_t _dst_sr,
|
||||
f_cnt_t _frames )
|
||||
{
|
||||
if( m_srcState[_i] == NULL )
|
||||
if( m_srcState[_i] == nullptr )
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_srcData[_i].input_frames = _frames;
|
||||
m_srcData[_i].output_frames = Engine::mixer()->framesPerPeriod();
|
||||
m_srcData[_i].data_in = (float *) _src_buf[0];
|
||||
m_srcData[_i].data_out = _dst_buf[0];
|
||||
m_srcData[_i].output_frames = Engine::audioEngine()->framesPerPeriod();
|
||||
m_srcData[_i].data_in = const_cast<float*>(_src_buf[0].data());
|
||||
m_srcData[_i].data_out = _dst_buf[0].data ();
|
||||
m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr;
|
||||
m_srcData[_i].end_of_input = 0;
|
||||
int error;
|
||||
@@ -215,3 +216,4 @@ void Effect::resample( int _i, const sampleFrame * _src_buf,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -30,13 +30,15 @@
|
||||
#include "Effect.h"
|
||||
#include "DummyEffect.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
EffectChain::EffectChain( Model * _parent ) :
|
||||
Model( _parent ),
|
||||
SerializingObject(),
|
||||
m_enabledModel( false, NULL, tr( "Effects enabled" ) )
|
||||
m_enabledModel( false, nullptr, tr( "Effects enabled" ) )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -58,10 +60,7 @@ void EffectChain::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
|
||||
for( Effect* effect : m_effects)
|
||||
{
|
||||
if( DummyEffect* dummy = dynamic_cast<DummyEffect*>(effect) )
|
||||
{
|
||||
_this.appendChild( dummy->originalPluginData() );
|
||||
}
|
||||
if (auto dummy = dynamic_cast<DummyEffect*>(effect)) { _this.appendChild(dummy->originalPluginData()); }
|
||||
else
|
||||
{
|
||||
QDomElement ef = effect->saveState( _doc, _this );
|
||||
@@ -78,7 +77,7 @@ void EffectChain::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
clear();
|
||||
|
||||
// TODO This method should probably also lock the mixer
|
||||
// TODO This method should probably also lock the audio engine
|
||||
|
||||
m_enabledModel.loadSettings( _this, "enabled" );
|
||||
|
||||
@@ -97,7 +96,7 @@ void EffectChain::loadSettings( const QDomElement & _this )
|
||||
|
||||
Effect* e = Effect::instantiate( name.toUtf8(), this, &key );
|
||||
|
||||
if( e != NULL && e->isOkay() && e->nodeName() == node.nodeName() )
|
||||
if( e != nullptr && e->isOkay() && e->nodeName() == node.nodeName() )
|
||||
{
|
||||
e->restoreState( effectData );
|
||||
}
|
||||
@@ -121,9 +120,9 @@ void EffectChain::loadSettings( const QDomElement & _this )
|
||||
|
||||
void EffectChain::appendEffect( Effect * _effect )
|
||||
{
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
m_effects.append( _effect );
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
m_enabledModel.setValue( true );
|
||||
|
||||
@@ -135,17 +134,17 @@ void EffectChain::appendEffect( Effect * _effect )
|
||||
|
||||
void EffectChain::removeEffect( Effect * _effect )
|
||||
{
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
|
||||
Effect ** found = std::find( m_effects.begin(), m_effects.end(), _effect );
|
||||
if( found == m_effects.end() )
|
||||
{
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
return;
|
||||
}
|
||||
m_effects.erase( found );
|
||||
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
if( m_effects.isEmpty() )
|
||||
{
|
||||
@@ -192,12 +191,12 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b
|
||||
MixHelpers::sanitize( _buf, _frames );
|
||||
|
||||
bool moreEffects = false;
|
||||
for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); ++it )
|
||||
for (const auto& effect : m_effects)
|
||||
{
|
||||
if( hasInputNoise || ( *it )->isRunning() )
|
||||
if (hasInputNoise || effect->isRunning())
|
||||
{
|
||||
moreEffects |= ( *it )->processAudioBuffer( _buf, _frames );
|
||||
MixHelpers::sanitize( _buf, _frames );
|
||||
moreEffects |= effect->processAudioBuffer(_buf, _frames);
|
||||
MixHelpers::sanitize(_buf, _frames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +213,9 @@ void EffectChain::startRunning()
|
||||
return;
|
||||
}
|
||||
|
||||
for( EffectList::Iterator it = m_effects.begin();
|
||||
it != m_effects.end(); it++ )
|
||||
for (const auto& effect : m_effects)
|
||||
{
|
||||
( *it )->startRunning();
|
||||
effect->startRunning();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +226,7 @@ void EffectChain::clear()
|
||||
{
|
||||
emit aboutToClear();
|
||||
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
|
||||
while( m_effects.count() )
|
||||
{
|
||||
@@ -237,7 +235,10 @@ void EffectChain::clear()
|
||||
delete e;
|
||||
}
|
||||
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
m_enabledModel.setValue( false );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,77 +24,91 @@
|
||||
|
||||
|
||||
#include "Engine.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "FxMixer.h"
|
||||
#include "Ladspa2LMMS.h"
|
||||
#include "Mixer.h"
|
||||
#include "Ladspa2LMMS.h"
|
||||
#include "Lv2Manager.h"
|
||||
#include "PatternStore.h"
|
||||
#include "Plugin.h"
|
||||
#include "PresetPreviewPlayHandle.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "Song.h"
|
||||
#include "BandLimitedWave.h"
|
||||
#include "Oscillator.h"
|
||||
|
||||
float LmmsCore::s_framesPerTick;
|
||||
Mixer* LmmsCore::s_mixer = NULL;
|
||||
FxMixer * LmmsCore::s_fxMixer = NULL;
|
||||
BBTrackContainer * LmmsCore::s_bbTrackContainer = NULL;
|
||||
Song * LmmsCore::s_song = NULL;
|
||||
ProjectJournal * LmmsCore::s_projectJournal = NULL;
|
||||
Ladspa2LMMS * LmmsCore::s_ladspaManager = NULL;
|
||||
void* LmmsCore::s_dndPluginKey = nullptr;
|
||||
DummyTrackContainer * LmmsCore::s_dummyTC = NULL;
|
||||
|
||||
|
||||
|
||||
|
||||
void LmmsCore::init( bool renderOnly )
|
||||
namespace lmms
|
||||
{
|
||||
LmmsCore *engine = inst();
|
||||
|
||||
float Engine::s_framesPerTick;
|
||||
AudioEngine* Engine::s_audioEngine = nullptr;
|
||||
Mixer * Engine::s_mixer = nullptr;
|
||||
PatternStore * Engine::s_patternStore = nullptr;
|
||||
Song * Engine::s_song = nullptr;
|
||||
ProjectJournal * Engine::s_projectJournal = nullptr;
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
Lv2Manager * Engine::s_lv2Manager = nullptr;
|
||||
#endif
|
||||
Ladspa2LMMS * Engine::s_ladspaManager = nullptr;
|
||||
void* Engine::s_dndPluginKey = nullptr;
|
||||
|
||||
|
||||
|
||||
|
||||
void Engine::init( bool renderOnly )
|
||||
{
|
||||
Engine *engine = inst();
|
||||
|
||||
emit engine->initProgress(tr("Generating wavetables"));
|
||||
// generate (load from file) bandlimited wavetables
|
||||
BandLimitedWave::generateWaves();
|
||||
//initilize oscillators
|
||||
Oscillator::waveTableInit();
|
||||
|
||||
emit engine->initProgress(tr("Initializing data structures"));
|
||||
s_projectJournal = new ProjectJournal;
|
||||
s_mixer = new Mixer( renderOnly );
|
||||
s_audioEngine = new AudioEngine( renderOnly );
|
||||
s_song = new Song;
|
||||
s_fxMixer = new FxMixer;
|
||||
s_bbTrackContainer = new BBTrackContainer;
|
||||
s_mixer = new Mixer;
|
||||
s_patternStore = new PatternStore;
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
s_lv2Manager = new Lv2Manager;
|
||||
s_lv2Manager->initPlugins();
|
||||
#endif
|
||||
s_ladspaManager = new Ladspa2LMMS;
|
||||
|
||||
s_projectJournal->setJournalling( true );
|
||||
|
||||
emit engine->initProgress(tr("Opening audio and midi devices"));
|
||||
s_mixer->initDevices();
|
||||
s_audioEngine->initDevices();
|
||||
|
||||
PresetPreviewPlayHandle::init();
|
||||
s_dummyTC = new DummyTrackContainer;
|
||||
|
||||
emit engine->initProgress(tr("Launching mixer threads"));
|
||||
s_mixer->startProcessing();
|
||||
emit engine->initProgress(tr("Launching audio engine threads"));
|
||||
s_audioEngine->startProcessing();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LmmsCore::destroy()
|
||||
void Engine::destroy()
|
||||
{
|
||||
s_projectJournal->stopAllJournalling();
|
||||
s_mixer->stopProcessing();
|
||||
s_audioEngine->stopProcessing();
|
||||
|
||||
PresetPreviewPlayHandle::cleanup();
|
||||
|
||||
s_song->clearProject();
|
||||
|
||||
deleteHelper( &s_bbTrackContainer );
|
||||
deleteHelper( &s_dummyTC );
|
||||
deleteHelper( &s_patternStore );
|
||||
|
||||
deleteHelper( &s_fxMixer );
|
||||
deleteHelper( &s_mixer );
|
||||
deleteHelper( &s_audioEngine );
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
deleteHelper( &s_lv2Manager );
|
||||
#endif
|
||||
deleteHelper( &s_ladspaManager );
|
||||
|
||||
//delete ConfigManager::inst();
|
||||
@@ -103,27 +117,42 @@ void LmmsCore::destroy()
|
||||
deleteHelper( &s_song );
|
||||
|
||||
delete ConfigManager::inst();
|
||||
|
||||
// The oscillator FFT plans remain throughout the application lifecycle
|
||||
// due to being expensive to create, and being used whenever a userwave form is changed
|
||||
Oscillator::destroyFFTPlans();
|
||||
}
|
||||
|
||||
float LmmsCore::framesPerTick(sample_rate_t sample_rate)
|
||||
|
||||
|
||||
|
||||
bool Engine::ignorePluginBlacklist()
|
||||
{
|
||||
return sample_rate * 60.0f * 4 /
|
||||
DefaultTicksPerTact / s_song->getTempo();
|
||||
const char* envVar = getenv("LMMS_IGNORE_BLACKLIST");
|
||||
return (envVar && *envVar);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LmmsCore::updateFramesPerTick()
|
||||
float Engine::framesPerTick(sample_rate_t sampleRate)
|
||||
{
|
||||
s_framesPerTick = s_mixer->processingSampleRate() * 60.0f * 4 /
|
||||
DefaultTicksPerTact / s_song->getTempo();
|
||||
return sampleRate * 60.0f * 4 /
|
||||
DefaultTicksPerBar / s_song->getTempo();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LmmsCore::setDndPluginKey(void *newKey)
|
||||
void Engine::updateFramesPerTick()
|
||||
{
|
||||
s_framesPerTick = s_audioEngine->processingSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Engine::setDndPluginKey(void *newKey)
|
||||
{
|
||||
Q_ASSERT(static_cast<Plugin::Descriptor::SubPluginFeatures::Key*>(newKey));
|
||||
s_dndPluginKey = newKey;
|
||||
@@ -132,7 +161,7 @@ void LmmsCore::setDndPluginKey(void *newKey)
|
||||
|
||||
|
||||
|
||||
void *LmmsCore::pickDndPluginKey()
|
||||
void *Engine::pickDndPluginKey()
|
||||
{
|
||||
return s_dndPluginKey;
|
||||
}
|
||||
@@ -140,4 +169,6 @@ void *LmmsCore::pickDndPluginKey()
|
||||
|
||||
|
||||
|
||||
LmmsCore * LmmsCore::s_instanceOfMe = NULL;
|
||||
Engine * Engine::s_instanceOfMe = nullptr;
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,11 +25,14 @@
|
||||
#include <QDomElement>
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "Oscillator.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
// how long should be each envelope-segment maximal (e.g. attack)?
|
||||
extern const float SECS_PER_ENV_SEGMENT = 5.0f;
|
||||
// how long should be one LFO-oscillation maximal?
|
||||
@@ -38,18 +41,16 @@ extern const float SECS_PER_LFO_OSCILLATION = 20.0f;
|
||||
const f_cnt_t minimumFrames = 1;
|
||||
|
||||
|
||||
EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = NULL;
|
||||
EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = nullptr;
|
||||
|
||||
|
||||
void EnvelopeAndLfoParameters::LfoInstances::trigger()
|
||||
{
|
||||
QMutexLocker m( &m_lfoListMutex );
|
||||
for( LfoList::Iterator it = m_lfos.begin();
|
||||
it != m_lfos.end(); ++it )
|
||||
for (const auto& lfo : m_lfos)
|
||||
{
|
||||
( *it )->m_lfoFrame +=
|
||||
Engine::mixer()->framesPerPeriod();
|
||||
( *it )->m_bad_lfoShapeData = true;
|
||||
lfo->m_lfoFrame += Engine::audioEngine()->framesPerPeriod();
|
||||
lfo->m_bad_lfoShapeData = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +60,10 @@ void EnvelopeAndLfoParameters::LfoInstances::trigger()
|
||||
void EnvelopeAndLfoParameters::LfoInstances::reset()
|
||||
{
|
||||
QMutexLocker m( &m_lfoListMutex );
|
||||
for( LfoList::Iterator it = m_lfos.begin();
|
||||
it != m_lfos.end(); ++it )
|
||||
for (const auto& lfo : m_lfos)
|
||||
{
|
||||
( *it )->m_lfoFrame = 0;
|
||||
( *it )->m_bad_lfoShapeData = true;
|
||||
lfo->m_lfoFrame = 0;
|
||||
lfo->m_bad_lfoShapeData = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
|
||||
m_valueForZeroAmount( _value_for_zero_amount ),
|
||||
m_pahdFrames( 0 ),
|
||||
m_rFrames( 0 ),
|
||||
m_pahdEnv( NULL ),
|
||||
m_rEnv( NULL ),
|
||||
m_pahdEnv( nullptr ),
|
||||
m_rEnv( nullptr ),
|
||||
m_pahdBufSize( 0 ),
|
||||
m_rBufSize( 0 ),
|
||||
m_lfoPredelayModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO pre-delay" ) ),
|
||||
@@ -118,52 +118,52 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
|
||||
m_controlEnvAmountModel( false, this, tr( "Modulate env amount" ) ),
|
||||
m_lfoFrame( 0 ),
|
||||
m_lfoAmountIsZero( false ),
|
||||
m_lfoShapeData( NULL )
|
||||
m_lfoShapeData( nullptr )
|
||||
{
|
||||
m_amountModel.setCenterValue( 0 );
|
||||
m_lfoAmountModel.setCenterValue( 0 );
|
||||
|
||||
if( s_lfoInstances == NULL )
|
||||
if( s_lfoInstances == nullptr )
|
||||
{
|
||||
s_lfoInstances = new LfoInstances();
|
||||
}
|
||||
|
||||
instances()->add( this );
|
||||
|
||||
connect( &m_predelayModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_attackModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_holdModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_decayModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_sustainModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_releaseModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_amountModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_predelayModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_attackModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_holdModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_decayModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_sustainModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_releaseModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_amountModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
|
||||
connect( &m_lfoPredelayModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_lfoAttackModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_lfoSpeedModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_lfoAmountModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_lfoWaveModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_x100Model, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
|
||||
connect( &m_lfoPredelayModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_lfoAttackModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_lfoSpeedModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_lfoAmountModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_lfoWaveModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
connect( &m_x100Model, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleVars()), Qt::DirectConnection );
|
||||
|
||||
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
|
||||
this, SLOT( updateSampleVars() ) );
|
||||
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()),
|
||||
this, SLOT(updateSampleVars()));
|
||||
|
||||
|
||||
m_lfoShapeData =
|
||||
new sample_t[Engine::mixer()->framesPerPeriod()];
|
||||
new sample_t[Engine::audioEngine()->framesPerPeriod()];
|
||||
|
||||
updateSampleVars();
|
||||
}
|
||||
@@ -196,7 +196,7 @@ EnvelopeAndLfoParameters::~EnvelopeAndLfoParameters()
|
||||
if( instances()->isEmpty() )
|
||||
{
|
||||
delete instances();
|
||||
s_lfoInstances = NULL;
|
||||
s_lfoInstances = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset )
|
||||
|
||||
void EnvelopeAndLfoParameters::updateLfoShapeData()
|
||||
{
|
||||
const fpp_t frames = Engine::mixer()->framesPerPeriod();
|
||||
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
|
||||
for( fpp_t offset = 0; offset < frames; ++offset )
|
||||
{
|
||||
m_lfoShapeData[offset] = lfoShapeSample( offset );
|
||||
@@ -399,19 +399,16 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
QMutexLocker m(&m_paramMutex);
|
||||
|
||||
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
|
||||
Engine::mixer()->processingSampleRate();
|
||||
Engine::audioEngine()->processingSampleRate();
|
||||
|
||||
// TODO: Remove the expKnobVals, time should be linear
|
||||
const f_cnt_t predelay_frames = static_cast<f_cnt_t>(
|
||||
frames_per_env_seg *
|
||||
expKnobVal( m_predelayModel.value() ) );
|
||||
const auto predelay_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_predelayModel.value()));
|
||||
|
||||
const f_cnt_t attack_frames = qMax( minimumFrames,
|
||||
static_cast<f_cnt_t>( frames_per_env_seg *
|
||||
expKnobVal( m_attackModel.value() ) ) );
|
||||
|
||||
const f_cnt_t hold_frames = static_cast<f_cnt_t>( frames_per_env_seg *
|
||||
expKnobVal( m_holdModel.value() ) );
|
||||
const auto hold_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_holdModel.value()));
|
||||
|
||||
const f_cnt_t decay_frames = qMax( minimumFrames,
|
||||
static_cast<f_cnt_t>( frames_per_env_seg *
|
||||
@@ -501,7 +498,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
|
||||
|
||||
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
|
||||
Engine::mixer()->processingSampleRate();
|
||||
Engine::audioEngine()->processingSampleRate();
|
||||
m_lfoPredelayFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
|
||||
expKnobVal( m_lfoPredelayModel.value() ) );
|
||||
m_lfoAttackFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
|
||||
@@ -541,4 +538,4 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,812 +0,0 @@
|
||||
/*
|
||||
* FxMixer.cpp - effect mixer for LMMS
|
||||
*
|
||||
* Copyright (c) 2008-2011 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QDomElement>
|
||||
|
||||
#include "BufferManager.h"
|
||||
#include "FxMixer.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "BBTrackContainer.h"
|
||||
|
||||
FxRoute::FxRoute( FxChannel * from, FxChannel * to, float amount ) :
|
||||
m_from( from ),
|
||||
m_to( to ),
|
||||
m_amount( amount, 0, 1, 0.001, NULL,
|
||||
tr( "Amount to send from channel %1 to channel %2" ).arg( m_from->m_channelIndex ).arg( m_to->m_channelIndex ) )
|
||||
{
|
||||
//qDebug( "created: %d to %d", m_from->m_channelIndex, m_to->m_channelIndex );
|
||||
// create send amount model
|
||||
}
|
||||
|
||||
|
||||
FxRoute::~FxRoute()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void FxRoute::updateName()
|
||||
{
|
||||
m_amount.setDisplayName(
|
||||
tr( "Amount to send from channel %1 to channel %2" ).arg( m_from->m_channelIndex ).arg( m_to->m_channelIndex ) );
|
||||
}
|
||||
|
||||
|
||||
FxChannel::FxChannel( int idx, Model * _parent ) :
|
||||
m_fxChain( NULL ),
|
||||
m_hasInput( false ),
|
||||
m_stillRunning( false ),
|
||||
m_peakLeft( 0.0f ),
|
||||
m_peakRight( 0.0f ),
|
||||
m_buffer( new sampleFrame[Engine::mixer()->framesPerPeriod()] ),
|
||||
m_muteModel( false, _parent ),
|
||||
m_soloModel( false, _parent ),
|
||||
m_volumeModel( 1.0, 0.0, 2.0, 0.001, _parent ),
|
||||
m_name(),
|
||||
m_lock(),
|
||||
m_channelIndex( idx ),
|
||||
m_queued( false ),
|
||||
m_dependenciesMet(0)
|
||||
{
|
||||
BufferManager::clear( m_buffer, Engine::mixer()->framesPerPeriod() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
FxChannel::~FxChannel()
|
||||
{
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
|
||||
inline void FxChannel::processed()
|
||||
{
|
||||
for( const FxRoute * receiverRoute : m_sends )
|
||||
{
|
||||
if( receiverRoute->receiver()->m_muted == false )
|
||||
{
|
||||
receiverRoute->receiver()->incrementDeps();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FxChannel::incrementDeps()
|
||||
{
|
||||
int i = m_dependenciesMet++ + 1;
|
||||
if( i >= m_receives.size() && ! m_queued )
|
||||
{
|
||||
m_queued = true;
|
||||
MixerWorkerThread::addJob( this );
|
||||
}
|
||||
}
|
||||
|
||||
void FxChannel::unmuteForSolo()
|
||||
{
|
||||
//TODO: Recursively activate every channel, this channel sends to
|
||||
m_muteModel.setValue(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxChannel::doProcessing()
|
||||
{
|
||||
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
|
||||
|
||||
if( m_muted == false )
|
||||
{
|
||||
for( FxRoute * senderRoute : m_receives )
|
||||
{
|
||||
FxChannel * sender = senderRoute->sender();
|
||||
FloatModel * sendModel = senderRoute->amount();
|
||||
if( ! sendModel ) qFatal( "Error: no send model found from %d to %d", senderRoute->senderIndex(), m_channelIndex );
|
||||
|
||||
if( sender->m_hasInput || sender->m_stillRunning )
|
||||
{
|
||||
// figure out if we're getting sample-exact input
|
||||
ValueBuffer * sendBuf = sendModel->valueBuffer();
|
||||
ValueBuffer * volBuf = sender->m_volumeModel.valueBuffer();
|
||||
|
||||
// mix it's output with this one's output
|
||||
sampleFrame * ch_buf = sender->m_buffer;
|
||||
|
||||
// use sample-exact mixing if sample-exact values are available
|
||||
if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data...
|
||||
{
|
||||
const float v = sender->m_volumeModel.value() * sendModel->value();
|
||||
MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp );
|
||||
}
|
||||
else if( volBuf && sendBuf ) // both volume and send have sample-exact data
|
||||
{
|
||||
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();
|
||||
MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp );
|
||||
}
|
||||
else // vice versa
|
||||
{
|
||||
const float v = sender->m_volumeModel.value();
|
||||
MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp );
|
||||
}
|
||||
m_hasInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const float v = m_volumeModel.value();
|
||||
|
||||
if( m_hasInput )
|
||||
{
|
||||
// only start fxchain when we have input...
|
||||
m_fxChain.startRunning();
|
||||
}
|
||||
|
||||
m_stillRunning = m_fxChain.processAudioBuffer( m_buffer, fpp, m_hasInput );
|
||||
|
||||
Mixer::StereoSample peakSamples = Engine::mixer()->getPeakValues(m_buffer, fpp);
|
||||
m_peakLeft = qMax( m_peakLeft, peakSamples.left * v );
|
||||
m_peakRight = qMax( m_peakRight, peakSamples.right * v );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_peakLeft = m_peakRight = 0.0f;
|
||||
}
|
||||
|
||||
// increment dependency counter of all receivers
|
||||
processed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
FxMixer::FxMixer() :
|
||||
Model( NULL ),
|
||||
JournallingObject(),
|
||||
m_fxChannels()
|
||||
{
|
||||
// create master channel
|
||||
createChannel();
|
||||
m_lastSoloed = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FxMixer::~FxMixer()
|
||||
{
|
||||
while( ! m_fxRoutes.isEmpty() )
|
||||
{
|
||||
deleteChannelSend( m_fxRoutes.first() );
|
||||
}
|
||||
while( m_fxChannels.size() )
|
||||
{
|
||||
FxChannel * f = m_fxChannels[m_fxChannels.size() - 1];
|
||||
m_fxChannels.pop_back();
|
||||
delete f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int FxMixer::createChannel()
|
||||
{
|
||||
const int index = m_fxChannels.size();
|
||||
// create new channel
|
||||
m_fxChannels.push_back( new FxChannel( index, this ) );
|
||||
|
||||
// reset channel state
|
||||
clearChannel( index );
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void FxMixer::activateSolo()
|
||||
{
|
||||
for (int i = 1; i < m_fxChannels.size(); ++i)
|
||||
{
|
||||
m_fxChannels[i]->m_muteBeforeSolo = m_fxChannels[i]->m_muteModel.value();
|
||||
m_fxChannels[i]->m_muteModel.setValue( true );
|
||||
}
|
||||
}
|
||||
|
||||
void FxMixer::deactivateSolo()
|
||||
{
|
||||
for (int i = 1; i < m_fxChannels.size(); ++i)
|
||||
{
|
||||
m_fxChannels[i]->m_muteModel.setValue( m_fxChannels[i]->m_muteBeforeSolo );
|
||||
}
|
||||
}
|
||||
|
||||
void FxMixer::toggledSolo()
|
||||
{
|
||||
int soloedChan = -1;
|
||||
bool resetSolo = m_lastSoloed != -1;
|
||||
//untoggle if lastsoloed is entered
|
||||
if (resetSolo)
|
||||
{
|
||||
m_fxChannels[m_lastSoloed]->m_soloModel.setValue( false );
|
||||
}
|
||||
//determine the soloed channel
|
||||
for (int i = 0; i < m_fxChannels.size(); ++i)
|
||||
{
|
||||
if (m_fxChannels[i]->m_soloModel.value() == true)
|
||||
soloedChan = i;
|
||||
}
|
||||
// if no channel is soloed, unmute everything, else mute everything
|
||||
if (soloedChan != -1)
|
||||
{
|
||||
if (resetSolo)
|
||||
{
|
||||
deactivateSolo();
|
||||
activateSolo();
|
||||
} else {
|
||||
activateSolo();
|
||||
}
|
||||
// unmute the soloed chan and every channel it sends to
|
||||
m_fxChannels[soloedChan]->unmuteForSolo();
|
||||
} else {
|
||||
deactivateSolo();
|
||||
}
|
||||
m_lastSoloed = soloedChan;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::deleteChannel( int index )
|
||||
{
|
||||
// channel deletion is performed between mixer rounds
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
TrackContainer::TrackList tracks;
|
||||
tracks += Engine::getSong()->tracks();
|
||||
tracks += Engine::getBBTrackContainer()->tracks();
|
||||
|
||||
for( Track* t : tracks )
|
||||
{
|
||||
if( t->type() == Track::InstrumentTrack )
|
||||
{
|
||||
InstrumentTrack* inst = dynamic_cast<InstrumentTrack *>( t );
|
||||
int val = inst->effectChannelModel()->value(0);
|
||||
if( val == index )
|
||||
{
|
||||
// we are deleting this track's fx send
|
||||
// send to master
|
||||
inst->effectChannelModel()->setValue(0);
|
||||
}
|
||||
else if( val > index )
|
||||
{
|
||||
// subtract 1 to make up for the missing channel
|
||||
inst->effectChannelModel()->setValue(val-1);
|
||||
}
|
||||
}
|
||||
else if( t->type() == Track::SampleTrack )
|
||||
{
|
||||
SampleTrack* strk = dynamic_cast<SampleTrack *>( t );
|
||||
int val = strk->effectChannelModel()->value(0);
|
||||
if( val == index )
|
||||
{
|
||||
// we are deleting this track's fx send
|
||||
// send to master
|
||||
strk->effectChannelModel()->setValue(0);
|
||||
}
|
||||
else if( val > index )
|
||||
{
|
||||
// subtract 1 to make up for the missing channel
|
||||
strk->effectChannelModel()->setValue(val-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FxChannel * ch = m_fxChannels[index];
|
||||
|
||||
// delete all of this channel's sends and receives
|
||||
while( ! ch->m_sends.isEmpty() )
|
||||
{
|
||||
deleteChannelSend( ch->m_sends.first() );
|
||||
}
|
||||
while( ! ch->m_receives.isEmpty() )
|
||||
{
|
||||
deleteChannelSend( ch->m_receives.first() );
|
||||
}
|
||||
|
||||
// actually delete the channel
|
||||
m_fxChannels.remove(index);
|
||||
delete ch;
|
||||
|
||||
for( int i = index; i < m_fxChannels.size(); ++i )
|
||||
{
|
||||
validateChannelName( i, i + 1 );
|
||||
|
||||
// set correct channel index
|
||||
m_fxChannels[i]->m_channelIndex = i;
|
||||
|
||||
// now check all routes and update names of the send models
|
||||
for( FxRoute * r : m_fxChannels[i]->m_sends )
|
||||
{
|
||||
r->updateName();
|
||||
}
|
||||
for( FxRoute * r : m_fxChannels[i]->m_receives )
|
||||
{
|
||||
r->updateName();
|
||||
}
|
||||
}
|
||||
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::moveChannelLeft( int index )
|
||||
{
|
||||
// can't move master or first channel
|
||||
if( index <= 1 || index >= m_fxChannels.size() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
// channels to swap
|
||||
int a = index - 1, b = index;
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
QVector<Track *> songTrackList = Engine::getSong()->tracks();
|
||||
QVector<Track *> bbTrackList = Engine::getBBTrackContainer()->tracks();
|
||||
|
||||
QVector<Track *> trackLists[] = {songTrackList, bbTrackList};
|
||||
for(int tl=0; tl<2; ++tl)
|
||||
{
|
||||
QVector<Track *> trackList = trackLists[tl];
|
||||
for(int i=0; i<trackList.size(); ++i)
|
||||
{
|
||||
if( trackList[i]->type() == Track::InstrumentTrack )
|
||||
{
|
||||
InstrumentTrack * inst = (InstrumentTrack *) trackList[i];
|
||||
int val = inst->effectChannelModel()->value(0);
|
||||
if( val == a )
|
||||
{
|
||||
inst->effectChannelModel()->setValue(b);
|
||||
}
|
||||
else if( val == b )
|
||||
{
|
||||
inst->effectChannelModel()->setValue(a);
|
||||
}
|
||||
}
|
||||
else if( trackList[i]->type() == Track::SampleTrack )
|
||||
{
|
||||
SampleTrack * strk = (SampleTrack *) trackList[i];
|
||||
int val = strk->effectChannelModel()->value(0);
|
||||
if( val == a )
|
||||
{
|
||||
strk->effectChannelModel()->setValue(b);
|
||||
}
|
||||
else if( val == b )
|
||||
{
|
||||
strk->effectChannelModel()->setValue(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Swap positions in array
|
||||
qSwap(m_fxChannels[index], m_fxChannels[index - 1]);
|
||||
|
||||
// Update m_channelIndex of both channels
|
||||
m_fxChannels[index]->m_channelIndex = index;
|
||||
m_fxChannels[index - 1]->m_channelIndex = index -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::moveChannelRight( int index )
|
||||
{
|
||||
moveChannelLeft( index + 1 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
FxRoute * FxMixer::createChannelSend( fx_ch_t fromChannel, fx_ch_t toChannel,
|
||||
float amount )
|
||||
{
|
||||
// qDebug( "requested: %d to %d", fromChannel, toChannel );
|
||||
// find the existing connection
|
||||
FxChannel * from = m_fxChannels[fromChannel];
|
||||
FxChannel * to = m_fxChannels[toChannel];
|
||||
|
||||
for( int i=0; i<from->m_sends.size(); ++i )
|
||||
{
|
||||
if( from->m_sends[i]->receiver() == to )
|
||||
{
|
||||
// simply adjust the amount
|
||||
from->m_sends[i]->amount()->setValue( amount );
|
||||
return from->m_sends[i];
|
||||
}
|
||||
}
|
||||
|
||||
// connection does not exist. create a new one
|
||||
return createRoute( from, to, amount );
|
||||
}
|
||||
|
||||
|
||||
FxRoute * FxMixer::createRoute( FxChannel * from, FxChannel * to, float amount )
|
||||
{
|
||||
if( from == to )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
FxRoute * route = new FxRoute( from, to, amount );
|
||||
|
||||
// add us to from's sends
|
||||
from->m_sends.append( route );
|
||||
|
||||
// add us to to's receives
|
||||
to->m_receives.append( route );
|
||||
|
||||
// add us to fxmixer's list
|
||||
Engine::fxMixer()->m_fxRoutes.append( route );
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
|
||||
// delete the connection made by createChannelSend
|
||||
void FxMixer::deleteChannelSend( fx_ch_t fromChannel, fx_ch_t toChannel )
|
||||
{
|
||||
// delete the send
|
||||
FxChannel * from = m_fxChannels[fromChannel];
|
||||
FxChannel * to = m_fxChannels[toChannel];
|
||||
|
||||
// find and delete the send entry
|
||||
for( int i = 0; i < from->m_sends.size(); ++i )
|
||||
{
|
||||
if( from->m_sends[i]->receiver() == to )
|
||||
{
|
||||
deleteChannelSend( from->m_sends[i] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FxMixer::deleteChannelSend( FxRoute * route )
|
||||
{
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
// remove us from from's sends
|
||||
route->sender()->m_sends.remove( route->sender()->m_sends.indexOf( route ) );
|
||||
// remove us from to's receives
|
||||
route->receiver()->m_receives.remove( route->receiver()->m_receives.indexOf( route ) );
|
||||
// remove us from fxmixer's list
|
||||
Engine::fxMixer()->m_fxRoutes.remove( Engine::fxMixer()->m_fxRoutes.indexOf( route ) );
|
||||
delete route;
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
}
|
||||
|
||||
|
||||
bool FxMixer::isInfiniteLoop( fx_ch_t sendFrom, fx_ch_t sendTo )
|
||||
{
|
||||
if( sendFrom == sendTo ) return true;
|
||||
FxChannel * from = m_fxChannels[sendFrom];
|
||||
FxChannel * to = m_fxChannels[sendTo];
|
||||
bool b = checkInfiniteLoop( from, to );
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
bool FxMixer::checkInfiniteLoop( FxChannel * from, FxChannel * to )
|
||||
{
|
||||
// can't send master to anything
|
||||
if( from == m_fxChannels[0] )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// can't send channel to itself
|
||||
if( from == to )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// follow sendTo's outputs recursively looking for something that sends
|
||||
// to sendFrom
|
||||
for( int i=0; i < to->m_sends.size(); ++i )
|
||||
{
|
||||
if( checkInfiniteLoop( from, to->m_sends[i]->receiver() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// how much does fromChannel send its output to the input of toChannel?
|
||||
FloatModel * FxMixer::channelSendModel( fx_ch_t fromChannel, fx_ch_t toChannel )
|
||||
{
|
||||
if( fromChannel == toChannel )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
const FxChannel * from = m_fxChannels[fromChannel];
|
||||
const FxChannel * to = m_fxChannels[toChannel];
|
||||
|
||||
for( FxRoute * route : from->m_sends )
|
||||
{
|
||||
if( route->receiver() == to )
|
||||
{
|
||||
return route->amount();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
|
||||
{
|
||||
if( m_fxChannels[_ch]->m_muteModel.value() == false )
|
||||
{
|
||||
m_fxChannels[_ch]->m_lock.lock();
|
||||
MixHelpers::add( m_fxChannels[_ch]->m_buffer, _buf, Engine::mixer()->framesPerPeriod() );
|
||||
m_fxChannels[_ch]->m_hasInput = true;
|
||||
m_fxChannels[_ch]->m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixer::prepareMasterMix()
|
||||
{
|
||||
BufferManager::clear( m_fxChannels[0]->m_buffer,
|
||||
Engine::mixer()->framesPerPeriod() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::masterMix( sampleFrame * _buf )
|
||||
{
|
||||
const int fpp = Engine::mixer()->framesPerPeriod();
|
||||
|
||||
// add the channels that have no dependencies (no incoming senders, ie.
|
||||
// no receives) to the jobqueue. The channels that have receives get
|
||||
// added when their senders get processed, which is detected by
|
||||
// dependency counting.
|
||||
// also instantly add all muted channels as they don't need to care
|
||||
// about their senders, and can just increment the deps of their
|
||||
// recipients right away.
|
||||
MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic );
|
||||
for( FxChannel * ch : m_fxChannels )
|
||||
{
|
||||
ch->m_muted = ch->m_muteModel.value();
|
||||
if( ch->m_muted ) // instantly "process" muted channels
|
||||
{
|
||||
ch->processed();
|
||||
ch->done();
|
||||
}
|
||||
else if( ch->m_receives.size() == 0 )
|
||||
{
|
||||
ch->m_queued = true;
|
||||
MixerWorkerThread::addJob( ch );
|
||||
}
|
||||
}
|
||||
while (m_fxChannels[0]->state() != ThreadableJob::ProcessingState::Done)
|
||||
{
|
||||
bool found = false;
|
||||
for( FxChannel * ch : m_fxChannels )
|
||||
{
|
||||
const auto s = ch->state();
|
||||
if (s == ThreadableJob::ProcessingState::Queued
|
||||
|| s == ThreadableJob::ProcessingState::InProgress)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !found )
|
||||
{
|
||||
break;
|
||||
}
|
||||
MixerWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
|
||||
// handle sample-exact data in master volume fader
|
||||
ValueBuffer * volBuf = m_fxChannels[0]->m_volumeModel.valueBuffer();
|
||||
|
||||
if( volBuf )
|
||||
{
|
||||
for( int f = 0; f < fpp; f++ )
|
||||
{
|
||||
m_fxChannels[0]->m_buffer[f][0] *= volBuf->values()[f];
|
||||
m_fxChannels[0]->m_buffer[f][1] *= volBuf->values()[f];
|
||||
}
|
||||
}
|
||||
|
||||
const float v = volBuf
|
||||
? 1.0f
|
||||
: m_fxChannels[0]->m_volumeModel.value();
|
||||
MixHelpers::addSanitizedMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp );
|
||||
|
||||
// clear all channel buffers and
|
||||
// reset channel process state
|
||||
for( int i = 0; i < numChannels(); ++i)
|
||||
{
|
||||
BufferManager::clear( m_fxChannels[i]->m_buffer,
|
||||
Engine::mixer()->framesPerPeriod() );
|
||||
m_fxChannels[i]->reset();
|
||||
m_fxChannels[i]->m_queued = false;
|
||||
// also reset hasInput
|
||||
m_fxChannels[i]->m_hasInput = false;
|
||||
m_fxChannels[i]->m_dependenciesMet = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FxMixer::clear()
|
||||
{
|
||||
while( m_fxChannels.size() > 1 )
|
||||
{
|
||||
deleteChannel(1);
|
||||
}
|
||||
|
||||
clearChannel(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FxMixer::clearChannel(fx_ch_t index)
|
||||
{
|
||||
FxChannel * ch = m_fxChannels[index];
|
||||
ch->m_fxChain.clear();
|
||||
ch->m_volumeModel.setValue( 1.0f );
|
||||
ch->m_muteModel.setValue( false );
|
||||
ch->m_soloModel.setValue( false );
|
||||
ch->m_name = ( index == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( index );
|
||||
ch->m_volumeModel.setDisplayName( ch->m_name + ">" + tr( "Volume" ) );
|
||||
ch->m_muteModel.setDisplayName( ch->m_name + ">" + tr( "Mute" ) );
|
||||
ch->m_soloModel.setDisplayName( ch->m_name + ">" + tr( "Solo" ) );
|
||||
|
||||
// send only to master
|
||||
if( index > 0)
|
||||
{
|
||||
// delete existing sends
|
||||
while( ! ch->m_sends.isEmpty() )
|
||||
{
|
||||
deleteChannelSend( ch->m_sends.first() );
|
||||
}
|
||||
|
||||
// add send to master
|
||||
createChannelSend( index, 0 );
|
||||
}
|
||||
|
||||
// delete receives
|
||||
while( ! ch->m_receives.isEmpty() )
|
||||
{
|
||||
deleteChannelSend( ch->m_receives.first() );
|
||||
}
|
||||
}
|
||||
|
||||
void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
// save channels
|
||||
for( int i = 0; i < m_fxChannels.size(); ++i )
|
||||
{
|
||||
FxChannel * ch = m_fxChannels[i];
|
||||
|
||||
QDomElement fxch = _doc.createElement( QString( "fxchannel" ) );
|
||||
_this.appendChild( fxch );
|
||||
|
||||
ch->m_fxChain.saveState( _doc, fxch );
|
||||
ch->m_volumeModel.saveSettings( _doc, fxch, "volume" );
|
||||
ch->m_muteModel.saveSettings( _doc, fxch, "muted" );
|
||||
ch->m_soloModel.saveSettings( _doc, fxch, "soloed" );
|
||||
fxch.setAttribute( "num", i );
|
||||
fxch.setAttribute( "name", ch->m_name );
|
||||
|
||||
// add the channel sends
|
||||
for( int si = 0; si < ch->m_sends.size(); ++si )
|
||||
{
|
||||
QDomElement sendsDom = _doc.createElement( QString( "send" ) );
|
||||
fxch.appendChild( sendsDom );
|
||||
|
||||
sendsDom.setAttribute( "channel", ch->m_sends[si]->receiverIndex() );
|
||||
ch->m_sends[si]->amount()->saveSettings( _doc, sendsDom, "amount" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we have at least num channels
|
||||
void FxMixer::allocateChannelsTo(int num)
|
||||
{
|
||||
while( num > m_fxChannels.size() - 1 )
|
||||
{
|
||||
createChannel();
|
||||
|
||||
// delete the default send to master
|
||||
deleteChannelSend( m_fxChannels.size()-1, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FxMixer::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
clear();
|
||||
QDomNode node = _this.firstChild();
|
||||
|
||||
while( ! node.isNull() )
|
||||
{
|
||||
QDomElement fxch = node.toElement();
|
||||
|
||||
// index of the channel we are about to load
|
||||
int num = fxch.attribute( "num" ).toInt();
|
||||
|
||||
// allocate enough channels
|
||||
allocateChannelsTo( num );
|
||||
|
||||
m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" );
|
||||
m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" );
|
||||
m_fxChannels[num]->m_soloModel.loadSettings( fxch, "soloed" );
|
||||
m_fxChannels[num]->m_name = fxch.attribute( "name" );
|
||||
|
||||
m_fxChannels[num]->m_fxChain.restoreState( fxch.firstChildElement(
|
||||
m_fxChannels[num]->m_fxChain.nodeName() ) );
|
||||
|
||||
// mixer sends
|
||||
QDomNodeList chData = fxch.childNodes();
|
||||
for( unsigned int i=0; i<chData.length(); ++i )
|
||||
{
|
||||
QDomElement chDataItem = chData.at(i).toElement();
|
||||
if( chDataItem.nodeName() == QString( "send" ) )
|
||||
{
|
||||
int sendTo = chDataItem.attribute( "channel" ).toInt();
|
||||
allocateChannelsTo( sendTo ) ;
|
||||
FxRoute * fxr = createChannelSend( num, sendTo, 1.0f );
|
||||
if( fxr ) fxr->amount()->loadSettings( chDataItem, "amount" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
node = node.nextSibling();
|
||||
}
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
void FxMixer::validateChannelName( int index, int oldIndex )
|
||||
{
|
||||
if( m_fxChannels[index]->m_name == tr( "FX %1" ).arg( oldIndex ) )
|
||||
{
|
||||
m_fxChannels[index]->m_name = tr( "FX %1" ).arg( index );
|
||||
}
|
||||
}
|
||||
@@ -33,11 +33,14 @@
|
||||
#include "ProjectJournal.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
ImportFilter::ImportFilter( const QString & _file_name,
|
||||
const Descriptor * _descriptor ) :
|
||||
Plugin( _descriptor, NULL ),
|
||||
Plugin( _descriptor, nullptr ),
|
||||
m_file( _file_name )
|
||||
{
|
||||
}
|
||||
@@ -45,12 +48,6 @@ ImportFilter::ImportFilter( const QString & _file_name,
|
||||
|
||||
|
||||
|
||||
ImportFilter::~ImportFilter()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void ImportFilter::import( const QString & _file_to_import,
|
||||
TrackContainer* tc )
|
||||
@@ -64,10 +61,10 @@ void ImportFilter::import( const QString & _file_to_import,
|
||||
const bool j = Engine::projectJournal()->isJournalling();
|
||||
Engine::projectJournal()->setJournalling( false );
|
||||
|
||||
for (const Plugin::Descriptor* desc : pluginFactory->descriptors(Plugin::ImportFilter))
|
||||
for (const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::ImportFilter))
|
||||
{
|
||||
unique_ptr<Plugin> p(Plugin::instantiate( desc->name, NULL, s.data() ));
|
||||
if( dynamic_cast<ImportFilter *>( p.get() ) != NULL &&
|
||||
unique_ptr<Plugin> p(Plugin::instantiate( desc->name, nullptr, s.data() ));
|
||||
if( dynamic_cast<ImportFilter *>( p.get() ) != nullptr &&
|
||||
dynamic_cast<ImportFilter *>( p.get() )->tryImport( tc ) )
|
||||
{
|
||||
successful = true;
|
||||
@@ -79,7 +76,7 @@ void ImportFilter::import( const QString & _file_to_import,
|
||||
|
||||
if( successful == false )
|
||||
{
|
||||
QMessageBox::information( NULL,
|
||||
QMessageBox::information( nullptr,
|
||||
TrackContainer::tr( "Couldn't import file" ),
|
||||
TrackContainer::tr( "Couldn't find a filter for "
|
||||
"importing file %1.\n"
|
||||
@@ -99,7 +96,7 @@ bool ImportFilter::openFile()
|
||||
{
|
||||
if( m_file.open( QFile::ReadOnly ) == false )
|
||||
{
|
||||
QMessageBox::critical( NULL,
|
||||
QMessageBox::critical( nullptr,
|
||||
TrackContainer::tr( "Couldn't open file" ),
|
||||
TrackContainer::tr( "Couldn't open file %1 "
|
||||
"for reading.\nPlease make "
|
||||
@@ -117,3 +114,4 @@ bool ImportFilter::openFile()
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -27,15 +27,18 @@
|
||||
#include "InlineAutomation.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
void InlineAutomation::saveSettings( QDomDocument & _doc,
|
||||
QDomElement & _parent )
|
||||
{
|
||||
if( hasAutomation() )
|
||||
{
|
||||
QDomElement ap = _doc.createElement(
|
||||
AutomationPattern::classNodeName() );
|
||||
AutomationClip::classNodeName() );
|
||||
QDomElement v = _doc.createElement( nodeName() );
|
||||
automationPattern()->saveSettings( _doc, v );
|
||||
automationClip()->saveSettings( _doc, v );
|
||||
ap.appendChild( v );
|
||||
_parent.appendChild( ap );
|
||||
}
|
||||
@@ -46,15 +49,17 @@ void InlineAutomation::saveSettings( QDomDocument & _doc,
|
||||
|
||||
void InlineAutomation::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
QDomNode node = _this.namedItem( AutomationPattern::classNodeName() );
|
||||
QDomNode node = _this.namedItem( AutomationClip::classNodeName() );
|
||||
if( node.isElement() )
|
||||
{
|
||||
node = node.namedItem( nodeName() );
|
||||
if( node.isElement() )
|
||||
{
|
||||
automationPattern()->loadSettings(
|
||||
automationClip()->loadSettings(
|
||||
node.toElement() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -23,14 +23,22 @@
|
||||
*/
|
||||
|
||||
#include "Instrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "DummyInstrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "lmms_constants.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Instrument::Instrument(InstrumentTrack * _instrument_track,
|
||||
const Descriptor * _descriptor,
|
||||
const Descriptor::SubPluginFeatures::Key *key) :
|
||||
Plugin(_descriptor, NULL/* _instrument_track*/, key),
|
||||
Plugin(_descriptor, nullptr/* _instrument_track*/, key),
|
||||
m_instrumentTrack( _instrument_track )
|
||||
{
|
||||
}
|
||||
@@ -78,13 +86,101 @@ bool Instrument::isFromTrack( const Track * _track ) const
|
||||
return( m_instrumentTrack == _track );
|
||||
}
|
||||
|
||||
// helper function for Instrument::applyFadeIn
|
||||
static int countZeroCrossings(sampleFrame *buf, fpp_t start, fpp_t frames)
|
||||
{
|
||||
// zero point crossing counts of all channels
|
||||
int zeroCrossings[DEFAULT_CHANNELS] = {0};
|
||||
// maximum zero point crossing of all channels
|
||||
int maxZeroCrossings = 0;
|
||||
|
||||
// determine the zero point crossing counts
|
||||
for (fpp_t f = start; f < frames; ++f)
|
||||
{
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
|
||||
{
|
||||
// we don't want to count [-1, 0, 1] as two crossings
|
||||
if ((buf[f - 1][ch] <= 0.0 && buf[f][ch] > 0.0) ||
|
||||
(buf[f - 1][ch] >= 0.0 && buf[f][ch] < 0.0))
|
||||
{
|
||||
++zeroCrossings[ch];
|
||||
if (zeroCrossings[ch] > maxZeroCrossings)
|
||||
{
|
||||
maxZeroCrossings = zeroCrossings[ch];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxZeroCrossings;
|
||||
}
|
||||
|
||||
// helper function for Instrument::applyFadeIn
|
||||
fpp_t getFadeInLength(float maxLength, fpp_t frames, int zeroCrossings)
|
||||
{
|
||||
// calculate the length of the fade in
|
||||
// Length is inversely proportional to the max of zeroCrossings,
|
||||
// because for low frequencies, we need a longer fade in to
|
||||
// prevent clicking.
|
||||
return (fpp_t) (maxLength / ((float) zeroCrossings / ((float) frames / 128.0f) + 1.0f));
|
||||
}
|
||||
|
||||
|
||||
void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n)
|
||||
{
|
||||
const static float MAX_FADE_IN_LENGTH = 85.0;
|
||||
f_cnt_t total = n->totalFramesPlayed();
|
||||
if (total == 0)
|
||||
{
|
||||
const fpp_t frames = n->framesLeftForCurrentPeriod();
|
||||
const f_cnt_t offset = n->offset();
|
||||
|
||||
// We need to skip the first sample because it almost always
|
||||
// produces a zero crossing; it's not helpful while
|
||||
// determining the fade in length. Hence 1
|
||||
int maxZeroCrossings = countZeroCrossings(buf, offset + 1, offset + frames);
|
||||
|
||||
fpp_t length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, maxZeroCrossings);
|
||||
n->m_fadeInLength = length;
|
||||
|
||||
// apply fade in
|
||||
length = length < frames ? length : frames;
|
||||
for (fpp_t f = 0; f < length; ++f)
|
||||
{
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
|
||||
{
|
||||
buf[offset + f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) f / (float) n->m_fadeInLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (total < n->m_fadeInLength)
|
||||
{
|
||||
const fpp_t frames = n->framesLeftForCurrentPeriod();
|
||||
|
||||
int new_zc = countZeroCrossings(buf, 1, frames);
|
||||
fpp_t new_length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, new_zc);
|
||||
|
||||
for (fpp_t f = 0; f < frames; ++f)
|
||||
{
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
|
||||
{
|
||||
float currentLength = n->m_fadeInLength * (1.0f - (float) f / frames) + new_length * ((float) f / frames);
|
||||
buf[f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) (total + f) / currentLength);
|
||||
if (total + f >= currentLength)
|
||||
{
|
||||
n->m_fadeInLength = currentLength;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
n->m_fadeInLength = new_length;
|
||||
}
|
||||
}
|
||||
|
||||
void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n )
|
||||
{
|
||||
const fpp_t frames = _n->framesLeftForCurrentPeriod();
|
||||
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
|
||||
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();
|
||||
const f_cnt_t fl = _n->framesLeft();
|
||||
if( fl <= desiredReleaseFrames()+fpp )
|
||||
{
|
||||
@@ -111,4 +207,4 @@ QString Instrument::fullDisplayName() const
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
#include <QDomElement>
|
||||
|
||||
#include "InstrumentFunctions.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "embed.h"
|
||||
#include "Engine.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "PresetPreviewPlayHandle.h"
|
||||
#include "stdshims.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
InstrumentFunctionNoteStacking::ChordTable::Init InstrumentFunctionNoteStacking::ChordTable::s_initTable[] =
|
||||
@@ -177,11 +179,9 @@ bool InstrumentFunctionNoteStacking::Chord::hasSemiTone( int8_t semi_tone ) cons
|
||||
InstrumentFunctionNoteStacking::ChordTable::ChordTable() :
|
||||
QVector<Chord>()
|
||||
{
|
||||
for( int i = 0;
|
||||
i < static_cast<int>( sizeof s_initTable / sizeof *s_initTable );
|
||||
i++ )
|
||||
for (const auto& chord : s_initTable)
|
||||
{
|
||||
push_back( Chord( s_initTable[i].m_name, s_initTable[i].m_semiTones ) );
|
||||
push_back(Chord(chord.m_name, chord.m_semiTones));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,10 +219,6 @@ InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent
|
||||
|
||||
|
||||
|
||||
InstrumentFunctionNoteStacking::~InstrumentFunctionNoteStacking()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -262,7 +258,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n )
|
||||
|
||||
// create sub-note-play-handle, only note is
|
||||
// different
|
||||
Engine::mixer()->addPlayHandle(
|
||||
Engine::audioEngine()->addPlayHandle(
|
||||
NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy,
|
||||
_n, -1, NotePlayHandle::OriginNoteStacking )
|
||||
);
|
||||
@@ -302,6 +298,7 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
|
||||
m_arpEnabledModel( false ),
|
||||
m_arpModel( this, tr( "Arpeggio type" ) ),
|
||||
m_arpRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Arpeggio range" ) ),
|
||||
m_arpRepeatsModel( 1.0f, 1.0f, 8.0f, 1.0f, this, tr( "Note repeats" ) ),
|
||||
m_arpCycleModel( 0.0f, 0.0f, 6.0f, 1.0f, this, tr( "Cycle steps" ) ),
|
||||
m_arpSkipModel( 0.0f, 0.0f, 100.0f, 1.0f, this, tr( "Skip rate" ) ),
|
||||
m_arpMissModel( 0.0f, 0.0f, 100.0f, 1.0f, this, tr( "Miss rate" ) ),
|
||||
@@ -316,26 +313,21 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
|
||||
m_arpModel.addItem( chord_table[i].getName() );
|
||||
}
|
||||
|
||||
m_arpDirectionModel.addItem( tr( "Up" ), make_unique<PixmapLoader>( "arp_up" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Down" ), make_unique<PixmapLoader>( "arp_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Up and down" ), make_unique<PixmapLoader>( "arp_up_and_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Down and up" ), make_unique<PixmapLoader>( "arp_up_and_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Random" ), make_unique<PixmapLoader>( "arp_random" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Up" ), std::make_unique<PixmapLoader>( "arp_up" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Down" ), std::make_unique<PixmapLoader>( "arp_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Up and down" ), std::make_unique<PixmapLoader>( "arp_up_and_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Down and up" ), std::make_unique<PixmapLoader>( "arp_up_and_down" ) );
|
||||
m_arpDirectionModel.addItem( tr( "Random" ), std::make_unique<PixmapLoader>( "arp_random" ) );
|
||||
m_arpDirectionModel.setInitValue( ArpDirUp );
|
||||
|
||||
m_arpModeModel.addItem( tr( "Free" ), make_unique<PixmapLoader>( "arp_free" ) );
|
||||
m_arpModeModel.addItem( tr( "Sort" ), make_unique<PixmapLoader>( "arp_sort" ) );
|
||||
m_arpModeModel.addItem( tr( "Sync" ), make_unique<PixmapLoader>( "arp_sync" ) );
|
||||
m_arpModeModel.addItem( tr( "Free" ), std::make_unique<PixmapLoader>( "arp_free" ) );
|
||||
m_arpModeModel.addItem( tr( "Sort" ), std::make_unique<PixmapLoader>( "arp_sort" ) );
|
||||
m_arpModeModel.addItem( tr( "Sync" ), std::make_unique<PixmapLoader>( "arp_sync" ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
InstrumentFunctionArpeggio::~InstrumentFunctionArpeggio()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
@@ -370,12 +362,12 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
|
||||
const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance();
|
||||
const int cur_chord_size = chord_table[selected_arp].size();
|
||||
const int range = (int)( cur_chord_size * m_arpRangeModel.value() );
|
||||
const int range = static_cast<int>(cur_chord_size * m_arpRangeModel.value() * m_arpRepeatsModel.value());
|
||||
const int total_range = range * cnphv.size();
|
||||
|
||||
// number of frames that every note should be played
|
||||
const f_cnt_t arp_frames = (f_cnt_t)( m_arpTimeModel.value() / 1000.0f * Engine::mixer()->processingSampleRate() );
|
||||
const f_cnt_t gated_frames = (f_cnt_t)( m_arpGateModel.value() * arp_frames / 100.0f );
|
||||
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->processingSampleRate());
|
||||
const auto gated_frames = (f_cnt_t)(m_arpGateModel.value() * arp_frames / 100.0f);
|
||||
|
||||
// used for calculating remaining frames for arp-note, we have to add
|
||||
// arp_frames-1, otherwise the first arp-note will not be setup
|
||||
@@ -386,11 +378,13 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
// used for loop
|
||||
f_cnt_t frames_processed = ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->noteOffset() : _n->noteOffset();
|
||||
|
||||
while( frames_processed < Engine::mixer()->framesPerPeriod() )
|
||||
while( frames_processed < Engine::audioEngine()->framesPerPeriod() )
|
||||
{
|
||||
const f_cnt_t remaining_frames_for_cur_arp = arp_frames - ( cur_frame % arp_frames );
|
||||
// does current arp-note fill whole audio-buffer?
|
||||
if( remaining_frames_for_cur_arp > Engine::mixer()->framesPerPeriod() )
|
||||
// does current arp-note fill whole audio-buffer or is the remaining time just
|
||||
// a short bit that we can discard?
|
||||
if( remaining_frames_for_cur_arp > Engine::audioEngine()->framesPerPeriod() ||
|
||||
_n->frames() - _n->totalFramesPlayed() < arp_frames / 5 )
|
||||
{
|
||||
// then we don't have to do something!
|
||||
break;
|
||||
@@ -478,11 +472,14 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
cur_arp_idx = (int)( range * ( (float) rand() / (float) RAND_MAX ) );
|
||||
}
|
||||
|
||||
// Divide cur_arp_idx with wanted repeats. The repeat feature will not affect random notes.
|
||||
cur_arp_idx = static_cast<int>(cur_arp_idx / m_arpRepeatsModel.value());
|
||||
|
||||
// Cycle notes
|
||||
if( m_arpCycleModel.value() && dir != ArpDirRandom )
|
||||
{
|
||||
cur_arp_idx *= m_arpCycleModel.value() + 1;
|
||||
cur_arp_idx %= range;
|
||||
cur_arp_idx %= static_cast<int>( range / m_arpRepeatsModel.value() );
|
||||
}
|
||||
|
||||
// now calculate final key for our arp-note
|
||||
@@ -492,7 +489,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
// range-checking
|
||||
if( sub_note_key >= NumKeys ||
|
||||
sub_note_key < 0 ||
|
||||
Engine::mixer()->criticalXRuns() )
|
||||
Engine::audioEngine()->criticalXRuns() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -501,11 +498,11 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
|
||||
// create sub-note-play-handle, only ptr to note is different
|
||||
// and is_arp_note=true
|
||||
Engine::mixer()->addPlayHandle(
|
||||
Engine::audioEngine()->addPlayHandle(
|
||||
NotePlayHandleManager::acquire( _n->instrumentTrack(),
|
||||
frames_processed,
|
||||
gated_frames,
|
||||
Note( MidiTime( 0 ), MidiTime( 0 ), sub_note_key, _n->getVolume(),
|
||||
Note( TimePos( 0 ), TimePos( 0 ), sub_note_key, _n->getVolume(),
|
||||
_n->getPanning(), _n->detuning() ),
|
||||
_n, -1, NotePlayHandle::OriginArpeggio )
|
||||
);
|
||||
@@ -524,6 +521,7 @@ void InstrumentFunctionArpeggio::saveSettings( QDomDocument & _doc, QDomElement
|
||||
m_arpEnabledModel.saveSettings( _doc, _this, "arp-enabled" );
|
||||
m_arpModel.saveSettings( _doc, _this, "arp" );
|
||||
m_arpRangeModel.saveSettings( _doc, _this, "arprange" );
|
||||
m_arpRepeatsModel.saveSettings( _doc, _this, "arprepeats" );
|
||||
m_arpCycleModel.saveSettings( _doc, _this, "arpcycle" );
|
||||
m_arpSkipModel.saveSettings( _doc, _this, "arpskip" );
|
||||
m_arpMissModel.saveSettings( _doc, _this, "arpmiss" );
|
||||
@@ -541,6 +539,7 @@ void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this )
|
||||
m_arpEnabledModel.loadSettings( _this, "arp-enabled" );
|
||||
m_arpModel.loadSettings( _this, "arp" );
|
||||
m_arpRangeModel.loadSettings( _this, "arprange" );
|
||||
m_arpRepeatsModel.loadSettings( _this, "arprepeats" );
|
||||
m_arpCycleModel.loadSettings( _this, "arpcycle" );
|
||||
m_arpSkipModel.loadSettings( _this, "arpskip" );
|
||||
m_arpMissModel.loadSettings( _this, "arpmiss" );
|
||||
@@ -549,3 +548,6 @@ void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this )
|
||||
m_arpDirectionModel.loadSettings( _this, "arpdir" );
|
||||
m_arpModeModel.loadSettings( _this, "arpmode" );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -26,9 +26,16 @@
|
||||
#include "InstrumentPlayHandle.h"
|
||||
#include "InstrumentTrack.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) :
|
||||
PlayHandle( TypeInstrumentPlayHandle ),
|
||||
m_instrument( instrument )
|
||||
{
|
||||
setAudioPort( instrumentTrack->audioPort() );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -22,18 +22,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtCore/QVarLengthArray>
|
||||
#include <QVarLengthArray>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "InstrumentSoundShaping.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "BasicFilters.h"
|
||||
#include "embed.h"
|
||||
#include "Engine.h"
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "Instrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "stdshims.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
const float CUT_FREQ_MULTIPLIER = 6000.0f;
|
||||
@@ -43,16 +45,14 @@ const float RES_PRECISION = 1000.0f;
|
||||
|
||||
// names for env- and lfo-targets - first is name being displayed to user
|
||||
// and second one is used internally, e.g. for saving/restoring settings
|
||||
const QString InstrumentSoundShaping::targetNames[InstrumentSoundShaping::NumTargets][3] =
|
||||
const char *const InstrumentSoundShaping::targetNames[InstrumentSoundShaping::NumTargets][3] =
|
||||
{
|
||||
{ InstrumentSoundShaping::tr( "VOLUME" ), "vol",
|
||||
InstrumentSoundShaping::tr( "Volume" ) },
|
||||
/* InstrumentSoundShaping::tr( "Pan" ),
|
||||
InstrumentSoundShaping::tr( "Pitch" ),*/
|
||||
{ InstrumentSoundShaping::tr( "CUTOFF" ), "cut",
|
||||
InstrumentSoundShaping::tr( "Cutoff frequency" ) },
|
||||
{ InstrumentSoundShaping::tr( "RESO" ), "res",
|
||||
InstrumentSoundShaping::tr( "Resonance" ) }
|
||||
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "VOLUME"), "vol",
|
||||
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Volume") },
|
||||
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "CUTOFF"), "cut",
|
||||
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Cutoff frequency") },
|
||||
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "RESO"), "res",
|
||||
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Resonance") }
|
||||
} ;
|
||||
|
||||
|
||||
@@ -77,41 +77,36 @@ InstrumentSoundShaping::InstrumentSoundShaping(
|
||||
value_for_zero_amount,
|
||||
this );
|
||||
m_envLfoParameters[i]->setDisplayName(
|
||||
tr( targetNames[i][2].toUtf8().constData() ) );
|
||||
tr( targetNames[i][2] ) );
|
||||
}
|
||||
|
||||
m_filterModel.addItem( tr( "Low-pass" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "Hi-pass" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Band-pass csg" ), make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "Band-pass czpg" ), make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "Notch" ), make_unique<PixmapLoader>( "filter_notch" ) );
|
||||
m_filterModel.addItem( tr( "All-pass" ), make_unique<PixmapLoader>( "filter_ap" ) );
|
||||
m_filterModel.addItem( tr( "Moog" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "2x Low-pass" ), make_unique<PixmapLoader>( "filter_2lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Low-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Band-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "RC High-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "RC Low-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Band-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "RC High-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Vocal Formant" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "2x Moog" ), make_unique<PixmapLoader>( "filter_2lp" ) );
|
||||
m_filterModel.addItem( tr( "SV Low-pass" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "SV Band-pass" ), make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "SV High-pass" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "SV Notch" ), make_unique<PixmapLoader>( "filter_notch" ) );
|
||||
m_filterModel.addItem( tr( "Fast Formant" ), make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Tripole" ), make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "Low-pass" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "Hi-pass" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Band-pass csg" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "Band-pass czpg" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "Notch" ), std::make_unique<PixmapLoader>( "filter_notch" ) );
|
||||
m_filterModel.addItem( tr( "All-pass" ), std::make_unique<PixmapLoader>( "filter_ap" ) );
|
||||
m_filterModel.addItem( tr( "Moog" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "2x Low-pass" ), std::make_unique<PixmapLoader>( "filter_2lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Low-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Band-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "RC High-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "RC Low-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "RC Band-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "RC High-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Vocal Formant" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "2x Moog" ), std::make_unique<PixmapLoader>( "filter_2lp" ) );
|
||||
m_filterModel.addItem( tr( "SV Low-pass" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
m_filterModel.addItem( tr( "SV Band-pass" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
|
||||
m_filterModel.addItem( tr( "SV High-pass" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "SV Notch" ), std::make_unique<PixmapLoader>( "filter_notch" ) );
|
||||
m_filterModel.addItem( tr( "Fast Formant" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
|
||||
m_filterModel.addItem( tr( "Tripole" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
InstrumentSoundShaping::~InstrumentSoundShaping()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame )
|
||||
@@ -120,7 +115,7 @@ float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t fram
|
||||
|
||||
if( n->isReleased() == false )
|
||||
{
|
||||
envReleaseBegin += Engine::mixer()->framesPerPeriod();
|
||||
envReleaseBegin += Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
|
||||
float level;
|
||||
@@ -163,7 +158,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
|
||||
if( n->m_filter == nullptr )
|
||||
{
|
||||
n->m_filter = make_unique<BasicFilters<>>( Engine::mixer()->processingSampleRate() );
|
||||
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->processingSampleRate() );
|
||||
}
|
||||
n->m_filter->setFilterType( m_filterModel.value() );
|
||||
|
||||
@@ -380,4 +375,4 @@ void InstrumentSoundShaping::loadSettings( const QDomElement & _this )
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "ProjectJournal.h"
|
||||
#include "Engine.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
JournallingObject::JournallingObject() :
|
||||
@@ -118,7 +120,7 @@ void JournallingObject::changeID( jo_id_t _id )
|
||||
{
|
||||
JournallingObject * jo = Engine::projectJournal()->
|
||||
journallingObject( _id );
|
||||
if( jo != NULL )
|
||||
if( jo != nullptr )
|
||||
{
|
||||
QString used_by = jo->nodeName();
|
||||
if( used_by == "automatablemodel" &&
|
||||
@@ -140,3 +142,4 @@ void JournallingObject::changeID( jo_id_t _id )
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
158
src/core/Keymap.cpp
Normal file
158
src/core/Keymap.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Keymap.cpp - implementation of keymap class
|
||||
*
|
||||
* Copyright (c) 2020 Martin Pavelek <he29.HS/at/gmail.com>
|
||||
*
|
||||
* 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 "Keymap.h"
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "Note.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Keymap::Keymap() :
|
||||
m_description(tr("empty")),
|
||||
m_firstKey(0),
|
||||
m_lastKey(NumKeys - 1),
|
||||
m_middleKey(DefaultMiddleKey),
|
||||
m_baseKey(DefaultBaseKey),
|
||||
m_baseFreq(DefaultBaseFreq)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Keymap::Keymap(
|
||||
QString description,
|
||||
std::vector<int> newMap,
|
||||
int newFirst,
|
||||
int newLast,
|
||||
int newMiddle,
|
||||
int newBaseKey,
|
||||
float newBaseFreq
|
||||
) :
|
||||
m_description(description),
|
||||
m_map(std::move(newMap)),
|
||||
m_firstKey(newFirst),
|
||||
m_lastKey(newLast),
|
||||
m_middleKey(newMiddle),
|
||||
m_baseKey(newBaseKey),
|
||||
m_baseFreq(newBaseFreq)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Return scale degree for a given key, based on current map and first/middle/last notes
|
||||
* \param MIDI key to be mapped
|
||||
* \return Scale degree defined by the mapping on success, -1 if key isn't mapped
|
||||
*/
|
||||
int Keymap::getDegree(int key) const
|
||||
{
|
||||
if (key < m_firstKey || key > m_lastKey) {return -1;}
|
||||
if (m_map.empty()) {return key;} // exception: empty mapping table means linear (1:1) mapping
|
||||
|
||||
const int keyOffset = key - m_middleKey; // -127..127
|
||||
const int key_rem = keyOffset % static_cast<int>(m_map.size()); // remainder
|
||||
const int key_mod = key_rem >= 0 ? key_rem : key_rem + m_map.size(); // true modulo
|
||||
return m_map[key_mod];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Return octave offset for a given key, based on current map and the middle note
|
||||
* \param MIDI key to be mapped
|
||||
* \return Octave offset defined by the mapping on success, 0 if key isn't mapped
|
||||
*/
|
||||
int Keymap::getOctave(int key) const
|
||||
{
|
||||
// The keymap wraparound cannot cause an octave transition if a key isn't mapped or the map is empty → return 0
|
||||
if (m_map.empty() || getDegree(key) == -1) {return 0;}
|
||||
|
||||
const int keyOffset = key - m_middleKey;
|
||||
if (keyOffset >= 0)
|
||||
{
|
||||
return keyOffset / static_cast<int>(m_map.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
return (keyOffset + 1) / static_cast<int>(m_map.size()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString Keymap::getDescription() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
|
||||
void Keymap::setDescription(QString description)
|
||||
{
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
|
||||
void Keymap::saveSettings(QDomDocument &document, QDomElement &element)
|
||||
{
|
||||
element.setAttribute("description", m_description);
|
||||
|
||||
element.setAttribute("first_key", m_firstKey);
|
||||
element.setAttribute("last_key", m_lastKey);
|
||||
element.setAttribute("middle_key", m_middleKey);
|
||||
element.setAttribute("base_key", m_baseKey);
|
||||
element.setAttribute("base_freq", m_baseFreq);
|
||||
|
||||
for (int value : m_map)
|
||||
{
|
||||
QDomElement degree = document.createElement("degree");
|
||||
element.appendChild(degree);
|
||||
degree.setAttribute("value", value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Keymap::loadSettings(const QDomElement &element)
|
||||
{
|
||||
m_description = element.attribute("description");
|
||||
|
||||
m_firstKey = element.attribute("first_key").toInt();
|
||||
m_lastKey = element.attribute("last_key").toInt();
|
||||
m_middleKey = element.attribute("middle_key").toInt();
|
||||
m_baseKey = element.attribute("base_key").toInt();
|
||||
m_baseFreq = element.attribute("base_freq").toDouble();
|
||||
|
||||
QDomNode node = element.firstChild();
|
||||
m_map.clear();
|
||||
|
||||
for (int i = 0; !node.isNull(); i++)
|
||||
{
|
||||
m_map.push_back(node.toElement().attribute("value").toInt());
|
||||
node = node.nextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -27,15 +27,18 @@
|
||||
#include "Ladspa2LMMS.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Ladspa2LMMS::Ladspa2LMMS()
|
||||
{
|
||||
l_sortable_plugin_t plugins = getSortedPlugins();
|
||||
|
||||
for( l_sortable_plugin_t::iterator it = plugins.begin();
|
||||
it != plugins.end(); ++it )
|
||||
for (const auto& plugin : plugins)
|
||||
{
|
||||
ladspa_key_t key = (*it).second;
|
||||
ladspaManagerDescription * desc = getDescription( key );
|
||||
ladspa_key_t key = plugin.second;
|
||||
LadspaManagerDescription * desc = getDescription( key );
|
||||
|
||||
if( desc->type == SOURCE )
|
||||
{
|
||||
@@ -78,12 +81,6 @@ Ladspa2LMMS::Ladspa2LMMS()
|
||||
|
||||
|
||||
|
||||
Ladspa2LMMS::~Ladspa2LMMS()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
QString Ladspa2LMMS::getShortName( const ladspa_key_t & _key )
|
||||
{
|
||||
QString name = getName( _key );
|
||||
@@ -126,3 +123,6 @@ QString Ladspa2LMMS::getShortName( const ladspa_key_t & _key )
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,9 +25,14 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "LadspaControl.h"
|
||||
#include "LadspaBase.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
bool _link ) :
|
||||
@@ -41,8 +46,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
{
|
||||
if( m_link )
|
||||
{
|
||||
connect( &m_linkEnabledModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( linkStateChanged() ),
|
||||
connect( &m_linkEnabledModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(linkStateChanged()),
|
||||
Qt::DirectConnection );
|
||||
}
|
||||
|
||||
@@ -51,8 +56,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
case TOGGLED:
|
||||
m_toggledModel.setInitValue(
|
||||
static_cast<bool>( m_port->def ) );
|
||||
connect( &m_toggledModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( ledChanged() ) );
|
||||
connect( &m_toggledModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(ledChanged()));
|
||||
if( m_port->def == 1.0f )
|
||||
{
|
||||
m_toggledModel.setValue( true );
|
||||
@@ -69,8 +74,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
m_port->min ) / 400 );
|
||||
m_knobModel.setInitValue(
|
||||
static_cast<int>( m_port->def ) );
|
||||
connect( &m_knobModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( knobChanged() ) );
|
||||
connect( &m_knobModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(knobChanged()));
|
||||
// TODO: careful: we must prevent saved scales
|
||||
m_knobModel.setScaleLogarithmic( m_port->suggests_logscale );
|
||||
break;
|
||||
@@ -80,10 +85,10 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
( m_port->max - m_port->min )
|
||||
/ ( m_port->name.toUpper() == "GAIN"
|
||||
&& m_port->max == 10.0f ? 4000.0f :
|
||||
( m_port->suggests_logscale ? 8000.0f : 800.0f ) ) );
|
||||
( m_port->suggests_logscale ? 8000000.0f : 800000.0f ) ) );
|
||||
m_knobModel.setInitValue( m_port->def );
|
||||
connect( &m_knobModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( knobChanged() ) );
|
||||
connect( &m_knobModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(knobChanged()));
|
||||
// TODO: careful: we must prevent saved scales
|
||||
m_knobModel.setScaleLogarithmic( m_port->suggests_logscale );
|
||||
break;
|
||||
@@ -93,8 +98,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
( m_port->max -
|
||||
m_port->min ) / 800.0f );
|
||||
m_tempoSyncKnobModel.setInitValue( m_port->def );
|
||||
connect( &m_tempoSyncKnobModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( tempoKnobChanged() ) );
|
||||
connect( &m_tempoSyncKnobModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(tempoKnobChanged()));
|
||||
// TODO: careful: we must prevent saved scales
|
||||
m_tempoSyncKnobModel.setScaleLogarithmic( m_port->suggests_logscale );
|
||||
break;
|
||||
@@ -107,13 +112,6 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
|
||||
|
||||
|
||||
LadspaControl::~LadspaControl()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
LADSPA_Data LadspaControl::value()
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
@@ -142,7 +140,7 @@ ValueBuffer * LadspaControl::valueBuffer()
|
||||
case TOGGLED:
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
return NULL;
|
||||
return nullptr;
|
||||
case FLOATING:
|
||||
return m_knobModel.valueBuffer();
|
||||
case TIME:
|
||||
@@ -152,7 +150,7 @@ ValueBuffer * LadspaControl::valueBuffer()
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -377,5 +375,4 @@ void LadspaControl::setLink( bool _state )
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -29,13 +29,16 @@
|
||||
#include <QDir>
|
||||
#include <QLibrary>
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "LadspaManager.h"
|
||||
#include "PluginFactory.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
LadspaManager::LadspaManager()
|
||||
{
|
||||
@@ -56,17 +59,15 @@ LadspaManager::LadspaManager()
|
||||
ladspaDirectories.push_back( "/Library/Audio/Plug-Ins/LADSPA" );
|
||||
#endif
|
||||
|
||||
for( QStringList::iterator it = ladspaDirectories.begin();
|
||||
it != ladspaDirectories.end(); ++it )
|
||||
for (const auto& ladspaDirectory : ladspaDirectories)
|
||||
{
|
||||
QDir directory( ( *it ) );
|
||||
// Skip empty entries as QDir will interpret it as the working directory
|
||||
if (ladspaDirectory.isEmpty()) { continue; }
|
||||
QDir directory(ladspaDirectory);
|
||||
QFileInfoList list = directory.entryInfoList();
|
||||
for( QFileInfoList::iterator file = list.begin();
|
||||
file != list.end(); ++file )
|
||||
for (const auto& f : list)
|
||||
{
|
||||
const QFileInfo & f = *file;
|
||||
if( !f.isFile() ||
|
||||
f.fileName().right( 3 ).toLower() !=
|
||||
if(!f.isFile() || f.fileName().right( 3 ).toLower() !=
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
"dll"
|
||||
#else
|
||||
@@ -81,10 +82,8 @@ LadspaManager::LadspaManager()
|
||||
|
||||
if( plugin_lib.load() == true )
|
||||
{
|
||||
LADSPA_Descriptor_Function descriptorFunction =
|
||||
( LADSPA_Descriptor_Function ) plugin_lib.resolve(
|
||||
"ladspa_descriptor" );
|
||||
if( descriptorFunction != NULL )
|
||||
auto descriptorFunction = (LADSPA_Descriptor_Function)plugin_lib.resolve("ladspa_descriptor");
|
||||
if( descriptorFunction != nullptr )
|
||||
{
|
||||
addPlugins( descriptorFunction,
|
||||
f.fileName() );
|
||||
@@ -98,10 +97,9 @@ LadspaManager::LadspaManager()
|
||||
}
|
||||
|
||||
l_ladspa_key_t keys = m_ladspaManagerMap.keys();
|
||||
for( l_ladspa_key_t::iterator it = keys.begin();
|
||||
it != keys.end(); ++it )
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
m_sortedPlugins.append( qMakePair( getName( *it ), *it ) );
|
||||
m_sortedPlugins.append(qMakePair(getName(key), key));
|
||||
}
|
||||
std::sort( m_sortedPlugins.begin(), m_sortedPlugins.end() );
|
||||
}
|
||||
@@ -111,7 +109,7 @@ LadspaManager::LadspaManager()
|
||||
|
||||
LadspaManager::~LadspaManager()
|
||||
{
|
||||
for( ladspaManagerMapType::iterator it = m_ladspaManagerMap.begin();
|
||||
for( LadspaManagerMapType::iterator it = m_ladspaManagerMap.begin();
|
||||
it != m_ladspaManagerMap.end(); ++it )
|
||||
{
|
||||
delete it.value();
|
||||
@@ -121,7 +119,7 @@ LadspaManager::~LadspaManager()
|
||||
|
||||
|
||||
|
||||
ladspaManagerDescription * LadspaManager::getDescription(
|
||||
LadspaManagerDescription * LadspaManager::getDescription(
|
||||
const ladspa_key_t & _plugin )
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin ) )
|
||||
@@ -130,7 +128,7 @@ ladspaManagerDescription * LadspaManager::getDescription(
|
||||
}
|
||||
else
|
||||
{
|
||||
return( NULL );
|
||||
return( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +142,7 @@ void LadspaManager::addPlugins(
|
||||
const LADSPA_Descriptor * descriptor;
|
||||
|
||||
for( long pluginIndex = 0;
|
||||
( descriptor = _descriptor_func( pluginIndex ) ) != NULL;
|
||||
( descriptor = _descriptor_func( pluginIndex ) ) != nullptr;
|
||||
++pluginIndex )
|
||||
{
|
||||
ladspa_key_t key( _file, QString( descriptor->Label ) );
|
||||
@@ -153,8 +151,7 @@ void LadspaManager::addPlugins(
|
||||
continue;
|
||||
}
|
||||
|
||||
ladspaManagerDescription * plugIn =
|
||||
new ladspaManagerDescription;
|
||||
auto plugIn = new LadspaManagerDescription;
|
||||
plugIn->descriptorFunction = _descriptor_func;
|
||||
plugIn->index = pluginIndex;
|
||||
plugIn->inputChannels = getPluginInputs( descriptor );
|
||||
@@ -242,7 +239,7 @@ const LADSPA_PortDescriptor* LadspaManager::getPortDescriptor(const ladspa_key_t
|
||||
{
|
||||
return( & descriptor->PortDescriptors[_port] );
|
||||
}
|
||||
return( NULL );
|
||||
return( nullptr );
|
||||
}
|
||||
|
||||
const LADSPA_PortRangeHint *LadspaManager::getPortRangeHint(const ladspa_key_t &_plugin, uint32_t _port)
|
||||
@@ -252,7 +249,7 @@ const LADSPA_PortRangeHint *LadspaManager::getPortRangeHint(const ladspa_key_t &
|
||||
{
|
||||
return( & descriptor->PortRangeHints[_port] );
|
||||
}
|
||||
return( NULL );
|
||||
return( nullptr );
|
||||
}
|
||||
|
||||
|
||||
@@ -563,7 +560,7 @@ const void * LadspaManager::getImplementationData(
|
||||
const ladspa_key_t & _plugin )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
return( descriptor ? descriptor->ImplementationData : NULL );
|
||||
return( descriptor ? descriptor->ImplementationData : nullptr );
|
||||
}
|
||||
|
||||
|
||||
@@ -583,7 +580,7 @@ const LADSPA_Descriptor * LadspaManager::getDescriptor(
|
||||
}
|
||||
else
|
||||
{
|
||||
return( NULL );
|
||||
return( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +594,7 @@ LADSPA_Handle LadspaManager::instantiate(
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
return( descriptor ?
|
||||
( descriptor->instantiate )( descriptor, _sample_rate ) :
|
||||
NULL );
|
||||
nullptr );
|
||||
}
|
||||
|
||||
|
||||
@@ -609,7 +606,7 @@ bool LadspaManager::connectPort( const ladspa_key_t & _plugin,
|
||||
LADSPA_Data * _data_location )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->connect_port != NULL &&
|
||||
if( descriptor && descriptor->connect_port != nullptr &&
|
||||
_port < getPortCount( _plugin ) )
|
||||
{
|
||||
( descriptor->connect_port )
|
||||
@@ -626,7 +623,7 @@ bool LadspaManager::activate( const ladspa_key_t & _plugin,
|
||||
LADSPA_Handle _instance )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->activate != NULL )
|
||||
if( descriptor && descriptor->activate != nullptr )
|
||||
{
|
||||
( descriptor->activate ) ( _instance );
|
||||
return( true );
|
||||
@@ -642,7 +639,7 @@ bool LadspaManager::run( const ladspa_key_t & _plugin,
|
||||
uint32_t _sample_count )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->run!= NULL )
|
||||
if( descriptor && descriptor->run!= nullptr )
|
||||
{
|
||||
( descriptor->run ) ( _instance, _sample_count );
|
||||
return( true );
|
||||
@@ -658,8 +655,8 @@ bool LadspaManager::runAdding( const ladspa_key_t & _plugin,
|
||||
uint32_t _sample_count )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->run_adding!= NULL
|
||||
&& descriptor->set_run_adding_gain != NULL )
|
||||
if( descriptor && descriptor->run_adding!= nullptr
|
||||
&& descriptor->set_run_adding_gain != nullptr )
|
||||
{
|
||||
( descriptor->run_adding ) ( _instance, _sample_count );
|
||||
return( true );
|
||||
@@ -675,8 +672,8 @@ bool LadspaManager::setRunAddingGain( const ladspa_key_t & _plugin,
|
||||
LADSPA_Data _gain )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->run_adding!= NULL
|
||||
&& descriptor->set_run_adding_gain != NULL )
|
||||
if( descriptor && descriptor->run_adding!= nullptr
|
||||
&& descriptor->set_run_adding_gain != nullptr )
|
||||
{
|
||||
( descriptor->set_run_adding_gain ) ( _instance, _gain );
|
||||
return( true );
|
||||
@@ -691,7 +688,7 @@ bool LadspaManager::deactivate( const ladspa_key_t & _plugin,
|
||||
LADSPA_Handle _instance )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->deactivate!= NULL )
|
||||
if( descriptor && descriptor->deactivate!= nullptr )
|
||||
{
|
||||
( descriptor->deactivate ) ( _instance );
|
||||
return( true );
|
||||
@@ -706,10 +703,13 @@ bool LadspaManager::cleanup( const ladspa_key_t & _plugin,
|
||||
LADSPA_Handle _instance )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
if( descriptor && descriptor->cleanup!= NULL )
|
||||
if( descriptor && descriptor->cleanup!= nullptr )
|
||||
{
|
||||
( descriptor->cleanup ) ( _instance );
|
||||
return( true );
|
||||
}
|
||||
return( false );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,12 +24,15 @@
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QObject>
|
||||
|
||||
|
||||
#include "Song.h"
|
||||
#include "Mixer.h"
|
||||
#include "LfoController.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
LfoController::LfoController( Model * _parent ) :
|
||||
@@ -48,20 +51,20 @@ LfoController::LfoController( Model * _parent ) :
|
||||
m_userDefSampleBuffer( new SampleBuffer )
|
||||
{
|
||||
setSampleExact( true );
|
||||
connect( &m_waveModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateSampleFunction() ), Qt::DirectConnection );
|
||||
connect( &m_waveModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateSampleFunction()), Qt::DirectConnection );
|
||||
|
||||
connect( &m_speedModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateDuration() ), Qt::DirectConnection );
|
||||
connect( &m_multiplierModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateDuration() ), Qt::DirectConnection );
|
||||
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
|
||||
this, SLOT( updateDuration() ) );
|
||||
connect( &m_speedModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateDuration()), Qt::DirectConnection );
|
||||
connect( &m_multiplierModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateDuration()), Qt::DirectConnection );
|
||||
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()),
|
||||
this, SLOT(updateDuration()));
|
||||
|
||||
connect( Engine::getSong(), SIGNAL( playbackStateChanged() ),
|
||||
this, SLOT( updatePhase() ) );
|
||||
connect( Engine::getSong(), SIGNAL( playbackPositionChanged() ),
|
||||
this, SLOT( updatePhase() ) );
|
||||
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
|
||||
this, SLOT(updatePhase()));
|
||||
connect( Engine::getSong(), SIGNAL(playbackPositionChanged()),
|
||||
this, SLOT(updatePhase()));
|
||||
|
||||
updateDuration();
|
||||
}
|
||||
@@ -91,7 +94,7 @@ void LfoController::updateValueBuffer()
|
||||
if( m_bufferLastUpdated < s_periods )
|
||||
{
|
||||
int diff = s_periods - m_bufferLastUpdated;
|
||||
phase += static_cast<float>( Engine::mixer()->framesPerPeriod() * diff ) / m_duration;
|
||||
phase += static_cast<float>( Engine::audioEngine()->framesPerPeriod() * diff ) / m_duration;
|
||||
m_bufferLastUpdated += diff;
|
||||
}
|
||||
|
||||
@@ -102,7 +105,7 @@ void LfoController::updateValueBuffer()
|
||||
|
||||
for( float& f : m_valueBuffer )
|
||||
{
|
||||
const float currentSample = m_sampleFunction != NULL
|
||||
const float currentSample = m_sampleFunction != nullptr
|
||||
? m_sampleFunction( phase )
|
||||
: m_userDefSampleBuffer->userWaveSample( phase );
|
||||
|
||||
@@ -125,7 +128,7 @@ void LfoController::updatePhase()
|
||||
|
||||
void LfoController::updateDuration()
|
||||
{
|
||||
float newDurationF = Engine::mixer()->processingSampleRate() * m_speedModel.value();
|
||||
float newDurationF = Engine::audioEngine()->processingSampleRate() * m_speedModel.value();
|
||||
|
||||
switch(m_multiplierModel.value() )
|
||||
{
|
||||
@@ -170,7 +173,7 @@ void LfoController::updateSampleFunction()
|
||||
m_sampleFunction = &Oscillator::noiseSample;
|
||||
break;
|
||||
case Oscillator::UserDefinedWave:
|
||||
m_sampleFunction = NULL;
|
||||
m_sampleFunction = nullptr;
|
||||
/*TODO: If C++11 is allowed, should change the type of
|
||||
m_sampleFunction be std::function<sample_t(const float)>
|
||||
and use the line below:
|
||||
@@ -221,12 +224,10 @@ QString LfoController::nodeName() const
|
||||
|
||||
|
||||
|
||||
ControllerDialog * LfoController::createDialog( QWidget * _parent )
|
||||
gui::ControllerDialog * LfoController::createDialog( QWidget * _parent )
|
||||
{
|
||||
return new LfoControllerDialog( this, _parent );
|
||||
return new gui::LfoControllerDialog( this, _parent );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
184
src/core/LinkedModelGroups.cpp
Normal file
184
src/core/LinkedModelGroups.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* LinkedModelGroups.cpp - base classes for groups of linked models
|
||||
*
|
||||
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
*
|
||||
* 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 <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "LinkedModelGroups.h"
|
||||
|
||||
|
||||
#include "AutomatableModel.h"
|
||||
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
/*
|
||||
LinkedModelGroup
|
||||
*/
|
||||
|
||||
|
||||
void LinkedModelGroup::linkControls(LinkedModelGroup *other)
|
||||
{
|
||||
foreach_model([&other](const std::string& id, ModelInfo& inf)
|
||||
{
|
||||
auto itr2 = other->m_models.find(id);
|
||||
Q_ASSERT(itr2 != other->m_models.end());
|
||||
AutomatableModel::linkModels(inf.m_model, itr2->second.m_model);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::saveValues(QDomDocument &doc, QDomElement &that)
|
||||
{
|
||||
foreach_model([&doc, &that](const std::string& , ModelInfo& inf)
|
||||
{
|
||||
inf.m_model->saveSettings(doc, that, /*m_models[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::loadValues(const QDomElement &that)
|
||||
{
|
||||
foreach_model([&that](const std::string& , ModelInfo& inf)
|
||||
{
|
||||
// try to load, if it fails, this will load a sane initial value
|
||||
inf.m_model->loadSettings(that, /*m_models()[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::addModel(AutomatableModel *model, const QString &name)
|
||||
{
|
||||
model->setObjectName(name);
|
||||
m_models.emplace(std::string(name.toUtf8().data()), ModelInfo(name, model));
|
||||
connect(model, &AutomatableModel::destroyed,
|
||||
this, [this, model](jo_id_t){
|
||||
if(containsModel(model->objectName()))
|
||||
{
|
||||
emit modelRemoved(model);
|
||||
eraseModel(model->objectName());
|
||||
}
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
|
||||
// View needs to create another child view, e.g. a new knob:
|
||||
emit modelAdded(model);
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::removeControl(AutomatableModel* mdl)
|
||||
{
|
||||
if(containsModel(mdl->objectName()))
|
||||
{
|
||||
emit modelRemoved(mdl);
|
||||
eraseModel(mdl->objectName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool LinkedModelGroup::eraseModel(const QString& name)
|
||||
{
|
||||
return m_models.erase(name.toStdString()) > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroup::clearModels()
|
||||
{
|
||||
m_models.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool LinkedModelGroup::containsModel(const QString &name) const
|
||||
{
|
||||
return m_models.find(name.toStdString()) != m_models.end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
LinkedModelGroups
|
||||
*/
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::linkAllModels()
|
||||
{
|
||||
LinkedModelGroup* first = getGroup(0);
|
||||
LinkedModelGroup* cur;
|
||||
|
||||
for (std::size_t i = 1; (cur = getGroup(i)); ++i)
|
||||
{
|
||||
first->linkControls(cur);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that)
|
||||
{
|
||||
LinkedModelGroup* grp0 = getGroup(0);
|
||||
if (grp0)
|
||||
{
|
||||
QDomElement models = doc.createElement("models");
|
||||
that.appendChild(models);
|
||||
grp0->saveValues(doc, models);
|
||||
}
|
||||
else { /* don't even add a "models" node */ }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::loadSettings(const QDomElement& that)
|
||||
{
|
||||
QDomElement models = that.firstChildElement("models");
|
||||
LinkedModelGroup* grp0;
|
||||
if (!models.isNull() && (grp0 = getGroup(0)))
|
||||
{
|
||||
// only load the first group, the others are linked to the first
|
||||
grp0->loadValues(models);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "LocklessAllocator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
static const size_t SIZEOF_SET = sizeof( int ) * 8;
|
||||
|
||||
|
||||
@@ -113,7 +116,7 @@ void * LocklessAllocator::alloc()
|
||||
if( !available )
|
||||
{
|
||||
fprintf( stderr, "LocklessAllocator: No free space\n" );
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
while (!m_available.compare_exchange_weak(available, available - 1));
|
||||
@@ -162,3 +165,6 @@ invalid:
|
||||
}
|
||||
++m_available;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -21,11 +21,15 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "lmms_basics.h"
|
||||
#include "MemoryHelper.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Allocate a number of bytes and return them.
|
||||
* @param byteNum is the number of bytes
|
||||
@@ -33,14 +37,14 @@
|
||||
void* MemoryHelper::alignedMalloc( size_t byteNum )
|
||||
{
|
||||
char *ptr, *ptr2, *aligned_ptr;
|
||||
int align_mask = ALIGN_SIZE - 1;
|
||||
int align_mask = LMMS_ALIGN_SIZE - 1;
|
||||
|
||||
ptr = static_cast<char*>( malloc( byteNum + ALIGN_SIZE + sizeof( int ) ) );
|
||||
ptr = static_cast<char*>( malloc( byteNum + LMMS_ALIGN_SIZE + sizeof( int ) ) );
|
||||
|
||||
if( ptr == NULL ) return NULL;
|
||||
if( ptr == nullptr ) return nullptr;
|
||||
|
||||
ptr2 = ptr + sizeof( int );
|
||||
aligned_ptr = ptr2 + ( ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
|
||||
aligned_ptr = ptr2 + ( LMMS_ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
|
||||
|
||||
ptr2 = aligned_ptr - sizeof( int );
|
||||
*( ( int* ) ptr2 ) = ( int )( aligned_ptr - ptr );
|
||||
@@ -63,3 +67,5 @@ void MemoryHelper::alignedFree( void* _buffer )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,9 +25,13 @@
|
||||
|
||||
#include "MemoryManager.h"
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtGlobal>
|
||||
#include "rpmalloc.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
/// Global static object handling rpmalloc intializing and finalizing
|
||||
struct MemoryManagerGlobalGuard {
|
||||
MemoryManagerGlobalGuard() {
|
||||
@@ -53,7 +57,7 @@ MemoryManager::ThreadGuard::ThreadGuard()
|
||||
MemoryManager::ThreadGuard::~ThreadGuard()
|
||||
{
|
||||
if (--thread_guard_depth == 0) {
|
||||
rpmalloc_thread_finalize();
|
||||
rpmalloc_thread_finalize(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,3 +79,6 @@ void MemoryManager::free(void * ptr)
|
||||
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::free", "Thread not initialized");
|
||||
return rpfree(ptr);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,37 +24,31 @@
|
||||
|
||||
|
||||
#include "MeterModel.h"
|
||||
#include "AutomationPattern.h"
|
||||
#include "AutomationClip.h"
|
||||
|
||||
|
||||
MeterModel::MeterModel( ::Model * _parent ) :
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
MeterModel::MeterModel( Model * _parent ) :
|
||||
Model( _parent ),
|
||||
m_numeratorModel( 4, 1, 32, this, tr( "Numerator" ) ),
|
||||
m_denominatorModel( 4, 1, 32, this, tr( "Denominator" ) )
|
||||
{
|
||||
connect( &m_numeratorModel, SIGNAL( dataChanged() ),
|
||||
this, SIGNAL( dataChanged() ), Qt::DirectConnection );
|
||||
connect( &m_denominatorModel, SIGNAL( dataChanged() ),
|
||||
this, SIGNAL( dataChanged() ), Qt::DirectConnection );
|
||||
connect( &m_numeratorModel, SIGNAL(dataChanged()),
|
||||
this, SIGNAL(dataChanged()), Qt::DirectConnection );
|
||||
connect( &m_denominatorModel, SIGNAL(dataChanged()),
|
||||
this, SIGNAL(dataChanged()), Qt::DirectConnection );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MeterModel::~MeterModel()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void MeterModel::reset()
|
||||
{
|
||||
m_numeratorModel.setValue( 4 );
|
||||
m_denominatorModel.setValue( 4 );
|
||||
|
||||
AutomationPattern::globalAutomationPattern( &m_numeratorModel )->clear();
|
||||
AutomationPattern::globalAutomationPattern( &m_denominatorModel )->clear();
|
||||
AutomationClip::globalAutomationClip( &m_numeratorModel )->clear();
|
||||
AutomationClip::globalAutomationClip( &m_denominatorModel )->clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +72,4 @@ void MeterModel::loadSettings( const QDomElement & _this,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "MicroTimer.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
@@ -11,10 +14,6 @@ MicroTimer::MicroTimer()
|
||||
reset();
|
||||
}
|
||||
|
||||
MicroTimer::~MicroTimer()
|
||||
{
|
||||
}
|
||||
|
||||
void MicroTimer::reset()
|
||||
{
|
||||
begin = steady_clock::now();
|
||||
@@ -25,3 +24,5 @@ int MicroTimer::elapsed() const
|
||||
auto now = steady_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::duration<int, std::micro>>(now - begin).count();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
184
src/core/Microtuner.cpp
Normal file
184
src/core/Microtuner.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Microtuner.cpp - manage tuning and scale information of an instrument
|
||||
*
|
||||
* Copyright (c) 2020 Martin Pavelek <he29.HS/at/gmail.com>
|
||||
*
|
||||
* 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 "Microtuner.h"
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "Keymap.h"
|
||||
#include "Note.h"
|
||||
#include "Scale.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Microtuner::Microtuner() :
|
||||
Model(nullptr, tr("Microtuner")),
|
||||
m_enabledModel(false, this, tr("Microtuner on / off")),
|
||||
m_scaleModel(this, tr("Selected scale")),
|
||||
m_keymapModel(this, tr("Selected keyboard mapping")),
|
||||
m_keyRangeImportModel(true)
|
||||
{
|
||||
for (unsigned int i = 0; i < MaxScaleCount; i++)
|
||||
{
|
||||
m_scaleModel.addItem(QString::number(i) + ": " + Engine::getSong()->getScale(i)->getDescription());
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < MaxKeymapCount; i++)
|
||||
{
|
||||
m_keymapModel.addItem(QString::number(i) + ": " + Engine::getSong()->getKeymap(i)->getDescription());
|
||||
}
|
||||
connect(Engine::getSong(), SIGNAL(scaleListChanged(int)), this, SLOT(updateScaleList(int)));
|
||||
connect(Engine::getSong(), SIGNAL(keymapListChanged(int)), this, SLOT(updateKeymapList(int)));
|
||||
}
|
||||
|
||||
|
||||
/** \brief Return frequency for a given MIDI key, using the active mapping and scale.
|
||||
* \param key A MIDI key number ranging from 0 to 127.
|
||||
* \return Frequency in Hz; 0 if key is out of range or not mapped.
|
||||
*/
|
||||
float Microtuner::keyToFreq(int key, int userBaseNote) const
|
||||
{
|
||||
if (key < 0 || key >= NumKeys) {return 0;}
|
||||
Song *song = Engine::getSong();
|
||||
if (!song) {return 0;}
|
||||
|
||||
// Get keymap and scale selected at this moment
|
||||
std::shared_ptr<const Keymap> keymap = song->getKeymap(m_keymapModel.value());
|
||||
std::shared_ptr<const Scale> scale = song->getScale(m_scaleModel.value());
|
||||
const std::vector<Interval> &intervals = scale->getIntervals();
|
||||
|
||||
// Convert MIDI key to scale degree + octave offset.
|
||||
// The octaves are primarily driven by the keymap wraparound: octave count is increased or decreased if the key
|
||||
// goes over or under keymap range. In case the keymap refers to a degree that does not exist in the scale, it is
|
||||
// assumed the keymap is non-repeating or just really big, so the octaves are also driven by the scale wraparound.
|
||||
const int keymapDegree = keymap->getDegree(key); // which interval should be used according to the keymap
|
||||
if (keymapDegree == -1) {return 0;} // key is not mapped, abort
|
||||
const int keymapOctave = keymap->getOctave(key); // how many times did the keymap repeat
|
||||
const int octaveDegree = intervals.size() - 1; // index of the interval with octave ratio
|
||||
if (octaveDegree == 0) { // octave interval is 1/1, i.e. constant base frequency
|
||||
return keymap->getBaseFreq(); // → return the baseFreq directly
|
||||
}
|
||||
const int scaleOctave = keymapDegree / octaveDegree;
|
||||
|
||||
// which interval should be used according to the scale and keymap together
|
||||
const int degree_rem = keymapDegree % octaveDegree;
|
||||
const int scaleDegree = degree_rem >= 0 ? degree_rem : degree_rem + octaveDegree; // get true modulo
|
||||
|
||||
// Compute base note (the "A4 reference") degree and octave
|
||||
const int baseNote = m_keyRangeImportModel.value() ? keymap->getBaseKey() : userBaseNote;
|
||||
const int baseKeymapDegree = keymap->getDegree(baseNote);
|
||||
if (baseKeymapDegree == -1) {return 0;} // base key is not mapped, umm...
|
||||
const int baseKeymapOctave = keymap->getOctave(baseNote);
|
||||
const int baseScaleOctave = baseKeymapDegree / octaveDegree;
|
||||
|
||||
const int baseDegree_rem = baseKeymapDegree % octaveDegree;
|
||||
const int baseScaleDegree = baseDegree_rem >= 0 ? baseDegree_rem : baseDegree_rem + octaveDegree;
|
||||
|
||||
// Compute frequency of the middle note and return the final frequency
|
||||
const double octaveRatio = intervals[octaveDegree].getRatio();
|
||||
const float middleFreq = (keymap->getBaseFreq() / pow(octaveRatio, (baseScaleOctave + baseKeymapOctave)))
|
||||
/ intervals[baseScaleDegree].getRatio();
|
||||
|
||||
return middleFreq * intervals[scaleDegree].getRatio() * pow(octaveRatio, keymapOctave + scaleOctave);
|
||||
}
|
||||
|
||||
int Microtuner::octaveSize() const
|
||||
{
|
||||
const int keymapSize = Engine::getSong()->getKeymap(currentKeymap())->getSize();
|
||||
if (keymapSize > 0)
|
||||
{
|
||||
return keymapSize;
|
||||
}
|
||||
|
||||
// Determine octave size from the scale if the keymap isn't in use.
|
||||
return Engine::getSong()->getScale(currentScale())->getIntervals().size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update scale name displayed in the microtuner scale list.
|
||||
* \param index Index of the scale to update; update all scales if -1 or out of range.
|
||||
*/
|
||||
void Microtuner::updateScaleList(int index)
|
||||
{
|
||||
if (index >= 0 && index < MaxScaleCount)
|
||||
{
|
||||
m_scaleModel.replaceItem(index,
|
||||
QString::number(index) + ": " + Engine::getSong()->getScale(index)->getDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < MaxScaleCount; i++)
|
||||
{
|
||||
m_scaleModel.replaceItem(i,
|
||||
QString::number(i) + ": " + Engine::getSong()->getScale(i)->getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update keymap name displayed in the microtuner scale list.
|
||||
* \param index Index of the keymap to update; update all keymaps if -1 or out of range.
|
||||
*/
|
||||
void Microtuner::updateKeymapList(int index)
|
||||
{
|
||||
if (index >= 0 && index < MaxKeymapCount)
|
||||
{
|
||||
m_keymapModel.replaceItem(index,
|
||||
QString::number(index) + ": " + Engine::getSong()->getKeymap(index)->getDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < MaxKeymapCount; i++)
|
||||
{
|
||||
m_keymapModel.replaceItem(i,
|
||||
QString::number(i) + ": " + Engine::getSong()->getKeymap(i)->getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Microtuner::saveSettings(QDomDocument &document, QDomElement &element)
|
||||
{
|
||||
m_enabledModel.saveSettings(document, element, "enabled");
|
||||
m_scaleModel.saveSettings(document, element, "scale");
|
||||
m_keymapModel.saveSettings(document, element, "keymap");
|
||||
m_keyRangeImportModel.saveSettings(document, element, "range_import");
|
||||
}
|
||||
|
||||
|
||||
void Microtuner::loadSettings(const QDomElement &element)
|
||||
{
|
||||
m_enabledModel.loadSettings(element, "enabled");
|
||||
m_scaleModel.loadSettings(element, "scale");
|
||||
m_keymapModel.loadSettings(element, "keymap");
|
||||
m_keyRangeImportModel.loadSettings(element, "range_import");
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -24,18 +24,21 @@
|
||||
|
||||
#include "MixHelpers.h"
|
||||
|
||||
#ifdef LMMS_DEBUG
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "lmms_math.h"
|
||||
#include "ValueBuffer.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
static bool s_NaNHandler;
|
||||
|
||||
|
||||
namespace MixHelpers
|
||||
namespace lmms::MixHelpers
|
||||
{
|
||||
|
||||
/*! \brief Function for applying MIXOP on all sample frames */
|
||||
@@ -99,9 +102,10 @@ bool sanitize( sampleFrame * src, int frames )
|
||||
{
|
||||
for( int c = 0; c < 2; ++c )
|
||||
{
|
||||
if( isinf( src[f][c] ) || isnan( src[f][c] ) )
|
||||
if( std::isinf( src[f][c] ) || std::isnan( src[f][c] ) )
|
||||
{
|
||||
#ifdef LMMS_DEBUG
|
||||
// TODO don't use printf here
|
||||
printf("Bad data, clearing buffer. frame: ");
|
||||
printf("%d: value %f\n", f, src[f][c]);
|
||||
#endif
|
||||
@@ -210,8 +214,8 @@ void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, f
|
||||
|
||||
for( int f = 0; f < frames; ++f )
|
||||
{
|
||||
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f];
|
||||
dst[f][1] += ( isinf( src[f][1] ) || isnan( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f];
|
||||
dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f];
|
||||
dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +230,10 @@ void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src,
|
||||
|
||||
for( int f = 0; f < frames; ++f )
|
||||
{
|
||||
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) )
|
||||
dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) )
|
||||
? 0.0f
|
||||
: src[f][0] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f];
|
||||
dst[f][1] += ( isinf( src[f][1] ) || isnan( src[f][1] ) )
|
||||
dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) )
|
||||
? 0.0f
|
||||
: src[f][1] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f];
|
||||
}
|
||||
@@ -243,8 +247,8 @@ struct AddSanitizedMultipliedOp
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
{
|
||||
dst[0] += ( isinf( src[0] ) || isnan( src[0] ) ) ? 0.0f : src[0] * m_coeff;
|
||||
dst[1] += ( isinf( src[1] ) || isnan( src[1] ) ) ? 0.0f : src[1] * m_coeff;
|
||||
dst[0] += ( std::isinf( src[0] ) || std::isnan( src[0] ) ) ? 0.0f : src[0] * m_coeff;
|
||||
dst[1] += ( std::isinf( src[1] ) || std::isnan( src[1] ) ) ? 0.0f : src[1] * m_coeff;
|
||||
}
|
||||
|
||||
const float m_coeff;
|
||||
@@ -324,5 +328,5 @@ void multiplyAndAddMultipliedJoined( sampleFrame* dst,
|
||||
run<>( dst, srcLeft, srcRight, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) );
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace lmms::MixHelpers
|
||||
|
||||
|
||||
1899
src/core/Mixer.cpp
1899
src/core/Mixer.cpp
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "Model.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
QString Model::fullDisplayName() const
|
||||
{
|
||||
@@ -46,5 +48,5 @@ QString Model::fullDisplayName() const
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
#include "ComboBoxModel.h"
|
||||
#include "TempoSyncKnobModel.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
void ModelVisitor::visit(BoolModel &m) { up(m); }
|
||||
void ModelVisitor::visit(IntModel &m) { up(m); }
|
||||
void ModelVisitor::visit(FloatModel &m) { up(m); }
|
||||
@@ -40,5 +44,4 @@ void ConstModelVisitor::visit(const FloatModel &m) { up(m); }
|
||||
void ConstModelVisitor::visit(const ComboBoxModel &m) { up<IntModel>(m); }
|
||||
void ConstModelVisitor::visit(const TempoSyncKnobModel &m) { up<FloatModel>(m); }
|
||||
|
||||
ModelVisitor::~ModelVisitor() {}
|
||||
ConstModelVisitor::~ConstModelVisitor() {}
|
||||
} // namespace lmms
|
||||
@@ -25,13 +25,16 @@
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "Note.h"
|
||||
#include "DetuningHelper.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
Note::Note( const MidiTime & length, const MidiTime & pos,
|
||||
|
||||
Note::Note( const TimePos & length, const TimePos & pos,
|
||||
int key, volume_t volume, panning_t panning,
|
||||
DetuningHelper * detuning ) :
|
||||
m_selected( false ),
|
||||
@@ -44,7 +47,7 @@ Note::Note( const MidiTime & length, const MidiTime & pos,
|
||||
m_panning( qBound( PanningLeft, panning, PanningRight ) ),
|
||||
m_length( length ),
|
||||
m_pos( pos ),
|
||||
m_detuning( NULL )
|
||||
m_detuning( nullptr )
|
||||
{
|
||||
if( detuning )
|
||||
{
|
||||
@@ -71,7 +74,7 @@ Note::Note( const Note & note ) :
|
||||
m_panning( note.m_panning ),
|
||||
m_length( note.m_length ),
|
||||
m_pos( note.m_pos ),
|
||||
m_detuning( NULL )
|
||||
m_detuning( nullptr )
|
||||
{
|
||||
if( note.m_detuning )
|
||||
{
|
||||
@@ -93,7 +96,7 @@ Note::~Note()
|
||||
|
||||
|
||||
|
||||
void Note::setLength( const MidiTime & length )
|
||||
void Note::setLength( const TimePos & length )
|
||||
{
|
||||
m_length = length;
|
||||
}
|
||||
@@ -101,7 +104,7 @@ void Note::setLength( const MidiTime & length )
|
||||
|
||||
|
||||
|
||||
void Note::setPos( const MidiTime & pos )
|
||||
void Note::setPos( const TimePos & pos )
|
||||
{
|
||||
m_pos = pos;
|
||||
}
|
||||
@@ -136,7 +139,7 @@ void Note::setPanning( panning_t panning )
|
||||
|
||||
|
||||
|
||||
MidiTime Note::quantized( const MidiTime & m, const int qGrid )
|
||||
TimePos Note::quantized( const TimePos & m, const int qGrid )
|
||||
{
|
||||
float p = ( (float) m / qGrid );
|
||||
if( p - floorf( p ) < 0.5f )
|
||||
@@ -208,12 +211,12 @@ void Note::loadSettings( const QDomElement & _this )
|
||||
|
||||
void Note::createDetuning()
|
||||
{
|
||||
if( m_detuning == NULL )
|
||||
if( m_detuning == nullptr )
|
||||
{
|
||||
m_detuning = new DetuningHelper;
|
||||
(void) m_detuning->automationPattern();
|
||||
(void) m_detuning->automationClip();
|
||||
m_detuning->setRange( -MaxDetuning, MaxDetuning, 0.5f );
|
||||
m_detuning->automationPattern()->setProgressionType( AutomationPattern::LinearProgression );
|
||||
m_detuning->automationClip()->setProgressionType( AutomationClip::LinearProgression );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,3 +235,35 @@ bool Note::withinRange(int tickStart, int tickEnd) const
|
||||
return pos().getTicks() >= tickStart && pos().getTicks() <= tickEnd
|
||||
&& length().getTicks() != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Get the start/end/bottom/top positions of notes in a vector
|
||||
*
|
||||
* Returns no value if there are no notes
|
||||
*/
|
||||
std::optional<NoteBounds> boundsForNotes(const NoteVector& notes)
|
||||
{
|
||||
if (notes.empty()) { return std::nullopt; }
|
||||
|
||||
TimePos start = notes.front()->pos();
|
||||
TimePos end = start;
|
||||
int lower = notes.front()->key();
|
||||
int upper = lower;
|
||||
|
||||
for (const Note* note: notes)
|
||||
{
|
||||
// TODO should we assume that NoteVector is always sorted correctly,
|
||||
// so first() always has the lowest time position?
|
||||
start = std::min(start, note->pos());
|
||||
end = std::max(end, note->endPos());
|
||||
lower = std::min(lower, note->key());
|
||||
upper = std::max(upper, note->key());
|
||||
}
|
||||
|
||||
return NoteBounds{start, end, lower, upper};
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,17 +24,20 @@
|
||||
*/
|
||||
|
||||
#include "NotePlayHandle.h"
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "BasicFilters.h"
|
||||
#include "DetuningHelper.h"
|
||||
#include "InstrumentSoundShaping.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Instrument.h"
|
||||
#include "Mixer.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
NotePlayHandle::BaseDetuning::BaseDetuning( DetuningHelper *detuning ) :
|
||||
m_value( detuning ? detuning->automationPattern()->valueAt( 0 ) : 0 )
|
||||
m_value( detuning ? detuning->automationClip()->valueAt( 0 ) : 0 )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,7 +55,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
Origin origin ) :
|
||||
PlayHandle( TypeNotePlayHandle, _offset ),
|
||||
Note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ),
|
||||
m_pluginData( NULL ),
|
||||
m_pluginData( nullptr ),
|
||||
m_instrumentTrack( instrumentTrack ),
|
||||
m_frames( 0 ),
|
||||
m_totalFramesPlayed( 0 ),
|
||||
@@ -63,16 +66,16 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
m_released( false ),
|
||||
m_releaseStarted( false ),
|
||||
m_hasMidiNote( false ),
|
||||
m_hasParent( parent != NULL ),
|
||||
m_hasParent( parent != nullptr ),
|
||||
m_parent( parent ),
|
||||
m_hadChildren( false ),
|
||||
m_muted( false ),
|
||||
m_bbTrack( NULL ),
|
||||
m_patternTrack( nullptr ),
|
||||
m_origTempo( Engine::getSong()->getTempo() ),
|
||||
m_origBaseNote( instrumentTrack->baseNote() ),
|
||||
m_frequency( 0 ),
|
||||
m_unpitchedFrequency( 0 ),
|
||||
m_baseDetuning( NULL ),
|
||||
m_baseDetuning( nullptr ),
|
||||
m_songGlobalParentOffset( 0 ),
|
||||
m_midiChannel( midiEventChannel >= 0 ? midiEventChannel : instrumentTrack->midiPort()->realOutputChannel() ),
|
||||
m_origin( origin ),
|
||||
@@ -91,7 +94,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
parent->m_subNotes.push_back( this );
|
||||
parent->m_hadChildren = true;
|
||||
|
||||
m_bbTrack = parent->m_bbTrack;
|
||||
m_patternTrack = parent->m_patternTrack;
|
||||
|
||||
parent->setUsesBuffer( false );
|
||||
}
|
||||
@@ -106,7 +109,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
m_instrumentTrack->midiNoteOn( *this );
|
||||
}
|
||||
|
||||
if( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed )
|
||||
if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed )
|
||||
{
|
||||
setUsesBuffer( false );
|
||||
}
|
||||
@@ -132,14 +135,14 @@ NotePlayHandle::~NotePlayHandle()
|
||||
m_parent->m_subNotes.removeOne( this );
|
||||
}
|
||||
|
||||
if( m_pluginData != NULL )
|
||||
if( m_pluginData != nullptr )
|
||||
{
|
||||
m_instrumentTrack->deleteNotePluginData( this );
|
||||
}
|
||||
|
||||
if( m_instrumentTrack->m_notes[key()] == this )
|
||||
{
|
||||
m_instrumentTrack->m_notes[key()] = NULL;
|
||||
m_instrumentTrack->m_notes[key()] = nullptr;
|
||||
}
|
||||
|
||||
m_subNotes.clear();
|
||||
@@ -182,21 +185,42 @@ int NotePlayHandle::midiKey() const
|
||||
|
||||
void NotePlayHandle::play( sampleFrame * _working_buffer )
|
||||
{
|
||||
if( m_muted )
|
||||
if (m_muted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if the note offset falls over to next period, then don't start playback yet
|
||||
if( offset() >= Engine::mixer()->framesPerPeriod() )
|
||||
if( offset() >= Engine::audioEngine()->framesPerPeriod() )
|
||||
{
|
||||
setOffset( offset() - Engine::mixer()->framesPerPeriod() );
|
||||
setOffset( offset() - Engine::audioEngine()->framesPerPeriod() );
|
||||
return;
|
||||
}
|
||||
|
||||
lock();
|
||||
|
||||
if( m_totalFramesPlayed == 0 && !m_hasMidiNote
|
||||
// Don't play the note if it falls outside of the user defined key range
|
||||
// TODO: handle the range check by Microtuner, and if the key becomes "not mapped", save the current frequency
|
||||
// so that the note release can finish playing using a valid frequency instead of a 1 Hz placeholder
|
||||
if (key() < m_instrumentTrack->m_firstKeyModel.value() ||
|
||||
key() > m_instrumentTrack->m_lastKeyModel.value())
|
||||
{
|
||||
// Release the note in case it started playing before going out of range
|
||||
noteOff(0);
|
||||
// Exit if the note did not start playing before going out of range (i.e. there is no need to play release)
|
||||
if (m_totalFramesPlayed == 0)
|
||||
{
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* It is possible for NotePlayHandle::noteOff to be called before NotePlayHandle::play,
|
||||
* which results in a note-on message being sent without a subsequent note-off message.
|
||||
* Therefore, we check here whether the note has already been released before sending
|
||||
* the note-on message. */
|
||||
if( !m_released
|
||||
&& m_totalFramesPlayed == 0 && !m_hasMidiNote
|
||||
&& ( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) )
|
||||
{
|
||||
m_hasMidiNote = true;
|
||||
@@ -206,7 +230,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
|
||||
// send MidiNoteOn event
|
||||
m_instrumentTrack->processOutEvent(
|
||||
MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ),
|
||||
MidiTime::fromFrames( offset(), Engine::framesPerTick() ),
|
||||
TimePos::fromFrames( offset(), Engine::framesPerTick() ),
|
||||
offset() );
|
||||
}
|
||||
|
||||
@@ -217,8 +241,8 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
|
||||
|
||||
// number of frames that can be played this period
|
||||
f_cnt_t framesThisPeriod = m_totalFramesPlayed == 0
|
||||
? Engine::mixer()->framesPerPeriod() - offset()
|
||||
: Engine::mixer()->framesPerPeriod();
|
||||
? Engine::audioEngine()->framesPerPeriod() - offset()
|
||||
: Engine::audioEngine()->framesPerPeriod();
|
||||
|
||||
// check if we start release during this period
|
||||
if( m_released == false &&
|
||||
@@ -253,7 +277,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
|
||||
// are inserted by arpAndChordsTabWidget::processNote()
|
||||
if( ! m_subNotes.isEmpty() )
|
||||
{
|
||||
m_releaseFramesToDo = m_releaseFramesDone + 2 * Engine::mixer()->framesPerPeriod();
|
||||
m_releaseFramesToDo = m_releaseFramesDone + 2 * Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
// look whether we have frames left to be done before release
|
||||
if( m_framesBeforeRelease )
|
||||
@@ -306,7 +330,7 @@ f_cnt_t NotePlayHandle::framesLeft() const
|
||||
{
|
||||
if( instrumentTrack()->isSustainPedalPressed() )
|
||||
{
|
||||
return 4*Engine::mixer()->framesPerPeriod();
|
||||
return 4 * Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
else if( m_released && actualReleaseFramesToDo() == 0 )
|
||||
{
|
||||
@@ -326,9 +350,9 @@ fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const
|
||||
{
|
||||
if( m_totalFramesPlayed == 0 )
|
||||
{
|
||||
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::mixer()->framesPerPeriod() - offset() );
|
||||
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::audioEngine()->framesPerPeriod() - offset() );
|
||||
}
|
||||
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::mixer()->framesPerPeriod() );
|
||||
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::audioEngine()->framesPerPeriod() );
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +360,7 @@ fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const
|
||||
|
||||
bool NotePlayHandle::isFromTrack( const Track * _track ) const
|
||||
{
|
||||
return m_instrumentTrack == _track || m_bbTrack == _track;
|
||||
return m_instrumentTrack == _track || m_patternTrack == _track;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,7 +393,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
|
||||
// send MidiNoteOff event
|
||||
m_instrumentTrack->processOutEvent(
|
||||
MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ),
|
||||
MidiTime::fromFrames( _s, Engine::framesPerTick() ),
|
||||
TimePos::fromFrames( _s, Engine::framesPerTick() ),
|
||||
_s );
|
||||
}
|
||||
|
||||
@@ -378,7 +402,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
|
||||
{
|
||||
if( m_origin == OriginMidiInput )
|
||||
{
|
||||
setLength( MidiTime( static_cast<f_cnt_t>( totalFramesPlayed() / Engine::framesPerTick() ) ) );
|
||||
setLength( TimePos( static_cast<f_cnt_t>( totalFramesPlayed() / Engine::framesPerTick() ) ) );
|
||||
m_instrumentTrack->midiNoteOff( *this );
|
||||
}
|
||||
}
|
||||
@@ -419,9 +443,9 @@ float NotePlayHandle::volumeLevel( const f_cnt_t _frame )
|
||||
void NotePlayHandle::mute()
|
||||
{
|
||||
// mute all sub-notes
|
||||
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
|
||||
for (const auto& subNote : m_subNotes)
|
||||
{
|
||||
( *it )->mute();
|
||||
subNote->mute();
|
||||
}
|
||||
m_muted = true;
|
||||
}
|
||||
@@ -431,12 +455,12 @@ void NotePlayHandle::mute()
|
||||
|
||||
int NotePlayHandle::index() const
|
||||
{
|
||||
const PlayHandleList & playHandles = Engine::mixer()->playHandles();
|
||||
const PlayHandleList & playHandles = Engine::audioEngine()->playHandles();
|
||||
int idx = 0;
|
||||
for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it )
|
||||
for (const auto& playHandle : playHandles)
|
||||
{
|
||||
const NotePlayHandle * nph = dynamic_cast<const NotePlayHandle *>( *it );
|
||||
if( nph == NULL || nph->m_instrumentTrack != m_instrumentTrack || nph->isReleased() || nph->hasParent() )
|
||||
const auto nph = dynamic_cast<const NotePlayHandle*>(playHandle);
|
||||
if( nph == nullptr || nph->m_instrumentTrack != m_instrumentTrack || nph->isReleased() || nph->hasParent() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -454,13 +478,13 @@ int NotePlayHandle::index() const
|
||||
|
||||
ConstNotePlayHandleList NotePlayHandle::nphsOfInstrumentTrack( const InstrumentTrack * _it, bool _all_ph )
|
||||
{
|
||||
const PlayHandleList & playHandles = Engine::mixer()->playHandles();
|
||||
const PlayHandleList & playHandles = Engine::audioEngine()->playHandles();
|
||||
ConstNotePlayHandleList cnphv;
|
||||
|
||||
for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it )
|
||||
for (const auto& playHandle : playHandles)
|
||||
{
|
||||
const NotePlayHandle * nph = dynamic_cast<const NotePlayHandle *>( *it );
|
||||
if( nph != NULL && nph->m_instrumentTrack == _it && ( ( nph->isReleased() == false && nph->hasParent() == false ) || _all_ph == true ) )
|
||||
const auto nph = dynamic_cast<const NotePlayHandle*>(playHandle);
|
||||
if( nph != nullptr && nph->m_instrumentTrack == _it && ( ( nph->isReleased() == false && nph->hasParent() == false ) || _all_ph == true ) )
|
||||
{
|
||||
cnphv.push_back( nph );
|
||||
}
|
||||
@@ -495,30 +519,49 @@ bool NotePlayHandle::operator==( const NotePlayHandle & _nph ) const
|
||||
|
||||
void NotePlayHandle::updateFrequency()
|
||||
{
|
||||
int mp = m_instrumentTrack->m_useMasterPitchModel.value() ? Engine::getSong()->masterPitch() : 0;
|
||||
const float pitch =
|
||||
( key() -
|
||||
m_instrumentTrack->baseNoteModel()->value() +
|
||||
mp +
|
||||
m_baseDetuning->value() )
|
||||
/ 12.0f;
|
||||
m_frequency = BaseFreq * powf( 2.0f, pitch + m_instrumentTrack->pitchModel()->value() / ( 100 * 12.0f ) );
|
||||
m_unpitchedFrequency = BaseFreq * powf( 2.0f, pitch );
|
||||
int masterPitch = m_instrumentTrack->m_useMasterPitchModel.value() ? Engine::getSong()->masterPitch() : 0;
|
||||
int baseNote = m_instrumentTrack->baseNoteModel()->value();
|
||||
float detune = m_baseDetuning->value();
|
||||
float instrumentPitch = m_instrumentTrack->pitchModel()->value();
|
||||
|
||||
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
|
||||
if (m_instrumentTrack->m_microtuner.enabled())
|
||||
{
|
||||
( *it )->updateFrequency();
|
||||
// custom key mapping and scale: get frequency from the microtuner
|
||||
const auto transposedKey = key() + masterPitch;
|
||||
|
||||
if (m_instrumentTrack->isKeyMapped(transposedKey))
|
||||
{
|
||||
const auto frequency = m_instrumentTrack->m_microtuner.keyToFreq(transposedKey, baseNote);
|
||||
m_frequency = frequency * powf(2.f, (detune + instrumentPitch / 100) / 12.f);
|
||||
m_unpitchedFrequency = frequency * powf(2.f, detune / 12.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frequency = m_unpitchedFrequency = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// default key mapping and 12-TET frequency computation with default 440 Hz base note frequency
|
||||
const float pitch = (key() - baseNote + masterPitch + detune) / 12.0f;
|
||||
m_frequency = DefaultBaseFreq * powf(2.0f, pitch + instrumentPitch / (100 * 12.0f));
|
||||
m_unpitchedFrequency = DefaultBaseFreq * powf(2.0f, pitch);
|
||||
}
|
||||
|
||||
for (auto it : m_subNotes)
|
||||
{
|
||||
it->updateFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void NotePlayHandle::processMidiTime( const MidiTime& time )
|
||||
void NotePlayHandle::processTimePos( const TimePos& time )
|
||||
{
|
||||
if( detuning() && time >= songGlobalParentOffset()+pos() )
|
||||
{
|
||||
const float v = detuning()->automationPattern()->valueAt( time - songGlobalParentOffset() - pos() );
|
||||
const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() );
|
||||
if( !typeInfo<float>::isEqual( v, m_baseDetuning->value() ) )
|
||||
{
|
||||
m_baseDetuning->setValue( v );
|
||||
@@ -532,14 +575,23 @@ void NotePlayHandle::processMidiTime( const MidiTime& time )
|
||||
|
||||
void NotePlayHandle::resize( const bpm_t _new_tempo )
|
||||
{
|
||||
if (origin() == OriginMidiInput ||
|
||||
(origin() == OriginNoteStacking && m_parent->origin() == OriginMidiInput))
|
||||
{
|
||||
// Don't resize notes from MIDI input - they should continue to play
|
||||
// until the key is released, and their large duration can cause
|
||||
// overflows in this method.
|
||||
return;
|
||||
}
|
||||
|
||||
double completed = m_totalFramesPlayed / (double) m_frames;
|
||||
double new_frames = m_origFrames * m_origTempo / (double) _new_tempo;
|
||||
m_frames = (f_cnt_t)new_frames;
|
||||
m_totalFramesPlayed = (f_cnt_t)( completed * new_frames );
|
||||
|
||||
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
|
||||
for (const auto& subNote : m_subNotes)
|
||||
{
|
||||
( *it )->resize( _new_tempo );
|
||||
subNote->resize(_new_tempo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,9 +604,9 @@ int NotePlayHandleManager::s_size;
|
||||
|
||||
void NotePlayHandleManager::init()
|
||||
{
|
||||
s_available = MM_ALLOC( NotePlayHandle*, INITIAL_NPH_CACHE );
|
||||
s_available = MM_ALLOC<NotePlayHandle*>( INITIAL_NPH_CACHE );
|
||||
|
||||
NotePlayHandle * n = MM_ALLOC( NotePlayHandle, INITIAL_NPH_CACHE );
|
||||
auto n = MM_ALLOC<NotePlayHandle>(INITIAL_NPH_CACHE);
|
||||
|
||||
for( int i=0; i < INITIAL_NPH_CACHE; ++i )
|
||||
{
|
||||
@@ -597,11 +649,11 @@ void NotePlayHandleManager::release( NotePlayHandle * nph )
|
||||
void NotePlayHandleManager::extend( int c )
|
||||
{
|
||||
s_size += c;
|
||||
NotePlayHandle ** tmp = MM_ALLOC( NotePlayHandle*, s_size );
|
||||
auto tmp = MM_ALLOC<NotePlayHandle*>(s_size);
|
||||
MM_FREE( s_available );
|
||||
s_available = tmp;
|
||||
|
||||
NotePlayHandle * n = MM_ALLOC( NotePlayHandle, c );
|
||||
auto n = MM_ALLOC<NotePlayHandle>(c);
|
||||
|
||||
for( int i=0; i < c; ++i )
|
||||
{
|
||||
@@ -609,3 +661,11 @@ void NotePlayHandleManager::extend( int c )
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
void NotePlayHandleManager::free()
|
||||
{
|
||||
MM_FREE(s_available);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Oscillator.cpp - implementation of powerful oscillator-class
|
||||
*
|
||||
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* 2018 Dave French <dave/dot/french3/at/googlemail/dot/com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -24,71 +25,290 @@
|
||||
|
||||
#include "Oscillator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#if !defined(__MINGW32__) && !defined(__MINGW64__)
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
#include "BufferManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "AutomatableModel.h"
|
||||
#include "fftw3.h"
|
||||
#include "fft_helpers.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
Oscillator::Oscillator( const IntModel * _wave_shape_model,
|
||||
const IntModel * _mod_algo_model,
|
||||
const float & _freq,
|
||||
const float & _detuning,
|
||||
const float & _phase_offset,
|
||||
const float & _volume,
|
||||
Oscillator * _sub_osc ) :
|
||||
m_waveShapeModel( _wave_shape_model ),
|
||||
m_modulationAlgoModel( _mod_algo_model ),
|
||||
m_freq( _freq ),
|
||||
m_detuning( _detuning ),
|
||||
m_volume( _volume ),
|
||||
m_ext_phaseOffset( _phase_offset ),
|
||||
m_subOsc( _sub_osc ),
|
||||
m_phaseOffset( _phase_offset ),
|
||||
m_phase( _phase_offset ),
|
||||
m_userWave( NULL )
|
||||
|
||||
void Oscillator::waveTableInit()
|
||||
{
|
||||
createFFTPlans();
|
||||
generateWaveTables();
|
||||
// The oscillator FFT plans remain throughout the application lifecycle
|
||||
// due to being expensive to create, and being used whenever a userwave form is changed
|
||||
// deleted in main.cpp main()
|
||||
|
||||
}
|
||||
|
||||
Oscillator::Oscillator(const IntModel *wave_shape_model,
|
||||
const IntModel *mod_algo_model,
|
||||
const float &freq,
|
||||
const float &detuning_div_samplerate,
|
||||
const float &phase_offset,
|
||||
const float &volume,
|
||||
Oscillator *sub_osc) :
|
||||
m_waveShapeModel(wave_shape_model),
|
||||
m_modulationAlgoModel(mod_algo_model),
|
||||
m_freq(freq),
|
||||
m_detuning_div_samplerate(detuning_div_samplerate),
|
||||
m_volume(volume),
|
||||
m_ext_phaseOffset(phase_offset),
|
||||
m_subOsc(sub_osc),
|
||||
m_phaseOffset(phase_offset),
|
||||
m_phase(phase_offset),
|
||||
m_userWave(nullptr),
|
||||
m_useWaveTable(false),
|
||||
m_isModulator(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Oscillator::update( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator)
|
||||
{
|
||||
if( m_freq >= Engine::mixer()->processingSampleRate() / 2 )
|
||||
if (m_freq >= Engine::audioEngine()->processingSampleRate() / 2)
|
||||
{
|
||||
BufferManager::clear( _ab, _frames );
|
||||
BufferManager::clear(ab, frames);
|
||||
return;
|
||||
}
|
||||
if( m_subOsc != NULL )
|
||||
// If this oscillator is used to PM or PF modulate another oscillator, take a note.
|
||||
// The sampling functions will check this variable and avoid using band-limited
|
||||
// wavetables, since they contain ringing that would lead to unexpected results.
|
||||
m_isModulator = modulator;
|
||||
if (m_subOsc != nullptr)
|
||||
{
|
||||
switch( m_modulationAlgoModel->value() )
|
||||
switch (m_modulationAlgoModel->value())
|
||||
{
|
||||
case PhaseModulation:
|
||||
updatePM( _ab, _frames, _chnl );
|
||||
updatePM(ab, frames, chnl);
|
||||
break;
|
||||
case AmplitudeModulation:
|
||||
updateAM( _ab, _frames, _chnl );
|
||||
updateAM(ab, frames, chnl);
|
||||
break;
|
||||
case SignalMix:
|
||||
updateMix( _ab, _frames, _chnl );
|
||||
updateMix(ab, frames, chnl);
|
||||
break;
|
||||
case SynchronizedBySubOsc:
|
||||
updateSync( _ab, _frames, _chnl );
|
||||
updateSync(ab, frames, chnl);
|
||||
break;
|
||||
case FrequencyModulation:
|
||||
updateFM( _ab, _frames, _chnl );
|
||||
updateFM(ab, frames, chnl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
updateNoSub( _ab, _frames, _chnl );
|
||||
updateNoSub(ab, frames, chnl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Oscillator::generateSawWaveTable(int bands, sample_t* table, int firstBand)
|
||||
{
|
||||
// sawtooth wave contain both even and odd harmonics
|
||||
// hence sinewaves are added for all bands
|
||||
// https://en.wikipedia.org/wiki/Sawtooth_wave
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
|
||||
{
|
||||
// add offset to the position index to match phase of the non-wavetable saw wave; precompute "/ period"
|
||||
const float imod = (i - OscillatorConstants::WAVETABLE_LENGTH / 2.f) / OscillatorConstants::WAVETABLE_LENGTH;
|
||||
for (int n = firstBand; n <= bands; n++)
|
||||
{
|
||||
table[i] += (n % 2 ? 1.0f : -1.0f) / n * sinf(F_2PI * n * imod) / F_PI_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Oscillator::generateTriangleWaveTable(int bands, sample_t* table, int firstBand)
|
||||
{
|
||||
// triangle waves contain only odd harmonics
|
||||
// hence sinewaves are added for alternate bands
|
||||
// https://en.wikipedia.org/wiki/Triangle_wave
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
|
||||
{
|
||||
for (int n = firstBand | 1; n <= bands; n += 2)
|
||||
{
|
||||
table[i] += (n & 2 ? -1.0f : 1.0f) / powf(n, 2.0f) *
|
||||
sinf(F_2PI * n * i / (float)OscillatorConstants::WAVETABLE_LENGTH) / (F_PI_SQR / 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Oscillator::generateSquareWaveTable(int bands, sample_t* table, int firstBand)
|
||||
{
|
||||
// square waves only contain odd harmonics,
|
||||
// at diffrent levels when compared to triangle waves
|
||||
// https://en.wikipedia.org/wiki/Square_wave
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
|
||||
{
|
||||
for (int n = firstBand | 1; n <= bands; n += 2)
|
||||
{
|
||||
table[i] += (1.0f / n) * sinf(F_2PI * i * n / OscillatorConstants::WAVETABLE_LENGTH) / (F_PI / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Expects waveform converted to frequency domain to be present in the spectrum buffer
|
||||
void Oscillator::generateFromFFT(int bands, sample_t* table)
|
||||
{
|
||||
// Keep only specified number of bands, set the rest to zero.
|
||||
// Add a +1 offset to the requested number of bands, since the first "useful" frequency falls into bin 1.
|
||||
// I.e., for bands = 1, keeping just bin 0 (center 0 Hz, +- 4 Hz) makes no sense, it would not produce any tone.
|
||||
for (int i = bands + 1; i < OscillatorConstants::WAVETABLE_LENGTH * 2 - bands; i++)
|
||||
{
|
||||
s_specBuf[i][0] = 0.0f;
|
||||
s_specBuf[i][1] = 0.0f;
|
||||
}
|
||||
//ifft
|
||||
fftwf_execute(s_ifftPlan);
|
||||
//normalize and copy to result buffer
|
||||
normalize(s_sampleBuffer, table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1);
|
||||
}
|
||||
|
||||
void Oscillator::generateAntiAliasUserWaveTable(SampleBuffer *sampleBuffer)
|
||||
{
|
||||
if (sampleBuffer->m_userAntiAliasWaveTable == nullptr) {return;}
|
||||
|
||||
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
|
||||
{
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
|
||||
{
|
||||
s_sampleBuffer[i] = sampleBuffer->userWaveSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
|
||||
}
|
||||
fftwf_execute(s_fftPlan);
|
||||
Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*(sampleBuffer->m_userAntiAliasWaveTable))[i].data());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
sample_t Oscillator::s_waveTables
|
||||
[Oscillator::WaveShapes::NumWaveShapeTables]
|
||||
[OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT]
|
||||
[OscillatorConstants::WAVETABLE_LENGTH];
|
||||
fftwf_plan Oscillator::s_fftPlan;
|
||||
fftwf_plan Oscillator::s_ifftPlan;
|
||||
fftwf_complex * Oscillator::s_specBuf;
|
||||
float Oscillator::s_sampleBuffer[OscillatorConstants::WAVETABLE_LENGTH];
|
||||
|
||||
|
||||
|
||||
void Oscillator::createFFTPlans()
|
||||
{
|
||||
Oscillator::s_specBuf = ( fftwf_complex * ) fftwf_malloc( ( OscillatorConstants::WAVETABLE_LENGTH * 2 + 1 ) * sizeof( fftwf_complex ) );
|
||||
Oscillator::s_fftPlan = fftwf_plan_dft_r2c_1d(OscillatorConstants::WAVETABLE_LENGTH, s_sampleBuffer, s_specBuf, FFTW_MEASURE );
|
||||
Oscillator::s_ifftPlan = fftwf_plan_dft_c2r_1d(OscillatorConstants::WAVETABLE_LENGTH, s_specBuf, s_sampleBuffer, FFTW_MEASURE);
|
||||
// initialize s_specBuf content to zero, since the values are used in a condition inside generateFromFFT()
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH * 2 + 1; i++)
|
||||
{
|
||||
s_specBuf[i][0] = 0.0f;
|
||||
s_specBuf[i][1] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void Oscillator::destroyFFTPlans()
|
||||
{
|
||||
fftwf_destroy_plan(s_fftPlan);
|
||||
fftwf_destroy_plan(s_ifftPlan);
|
||||
fftwf_free(s_specBuf);
|
||||
}
|
||||
|
||||
void Oscillator::generateWaveTables()
|
||||
{
|
||||
// Generate tables for simple shaped (constructed by summing sine waves).
|
||||
// Start from the table that contains the least number of bands, and re-use each table in the following
|
||||
// iteration, adding more bands in each step and avoiding repeated computation of earlier bands.
|
||||
using generator_t = void (*)(int, sample_t*, int);
|
||||
auto simpleGen = [](WaveShapes shape, generator_t generator)
|
||||
{
|
||||
const int shapeID = shape - FirstWaveShapeTable;
|
||||
int lastBands = 0;
|
||||
|
||||
// Clear the first wave table
|
||||
std::fill(
|
||||
std::begin(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]),
|
||||
std::end(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]),
|
||||
0.f);
|
||||
|
||||
for (int i = OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1; i >= 0; i--)
|
||||
{
|
||||
const int bands = OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i);
|
||||
generator(bands, s_waveTables[shapeID][i], lastBands + 1);
|
||||
lastBands = bands;
|
||||
if (i)
|
||||
{
|
||||
std::copy(
|
||||
s_waveTables[shapeID][i],
|
||||
s_waveTables[shapeID][i] + OscillatorConstants::WAVETABLE_LENGTH,
|
||||
s_waveTables[shapeID][i - 1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FFT-based wave shapes: make standard wave table without band limit, convert to frequency domain, remove bands
|
||||
// above maximum frequency and convert back to time domain.
|
||||
auto fftGen = []()
|
||||
{
|
||||
// Generate moogSaw tables
|
||||
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
|
||||
{
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
|
||||
{
|
||||
Oscillator::s_sampleBuffer[i] = moogSawSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
|
||||
}
|
||||
fftwf_execute(s_fftPlan);
|
||||
generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable][i]);
|
||||
}
|
||||
|
||||
// Generate exponential tables
|
||||
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
|
||||
{
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
|
||||
{
|
||||
s_sampleBuffer[i] = expSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
|
||||
}
|
||||
fftwf_execute(s_fftPlan);
|
||||
generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable][i]);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Mingw compilers currently do not support std::thread. There are some 3rd-party workarounds available,
|
||||
// but since threading is not essential in this case, it is easier and more reliable to simply generate
|
||||
// the wavetables serially. Remove the the check and #else branch once std::thread is well supported.
|
||||
#if !defined(__MINGW32__) && !defined(__MINGW64__)
|
||||
std::thread sawThread(simpleGen, WaveShapes::SawWave, generateSawWaveTable);
|
||||
std::thread squareThread(simpleGen, WaveShapes::SquareWave, generateSquareWaveTable);
|
||||
std::thread triangleThread(simpleGen, WaveShapes::TriangleWave, generateTriangleWaveTable);
|
||||
std::thread fftThread(fftGen);
|
||||
sawThread.join();
|
||||
squareThread.join();
|
||||
triangleThread.join();
|
||||
fftThread.join();
|
||||
#else
|
||||
simpleGen(WaveShapes::SawWave, generateSawWaveTable);
|
||||
simpleGen(WaveShapes::SquareWave, generateSquareWaveTable);
|
||||
simpleGen(WaveShapes::TriangleWave, generateTriangleWaveTable);
|
||||
fftGen();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
@@ -316,8 +536,7 @@ inline void Oscillator::recalcPhase()
|
||||
m_phaseOffset = m_ext_phaseOffset;
|
||||
m_phase += m_phaseOffset;
|
||||
}
|
||||
m_phase = absFraction( m_phase )+2; // make sure we're not running
|
||||
// negative when doing PM
|
||||
m_phase = absFraction( m_phase );
|
||||
}
|
||||
|
||||
|
||||
@@ -337,12 +556,12 @@ inline bool Oscillator::syncOk( float _osc_coeff )
|
||||
float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
if( m_subOsc != NULL )
|
||||
if( m_subOsc != nullptr )
|
||||
{
|
||||
m_subOsc->update( _ab, _frames, _chnl );
|
||||
}
|
||||
recalcPhase();
|
||||
return( m_freq * m_detuning );
|
||||
return m_freq * m_detuning_div_samplerate;
|
||||
}
|
||||
|
||||
|
||||
@@ -354,7 +573,7 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -371,9 +590,9 @@ template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
m_subOsc->update( _ab, _frames, _chnl );
|
||||
m_subOsc->update( _ab, _frames, _chnl, true );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -392,9 +611,9 @@ template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
m_subOsc->update( _ab, _frames, _chnl );
|
||||
m_subOsc->update( _ab, _frames, _chnl, false );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -411,9 +630,9 @@ template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
m_subOsc->update( _ab, _frames, _chnl );
|
||||
m_subOsc->update( _ab, _frames, _chnl, false );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -433,7 +652,7 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
|
||||
{
|
||||
const float sub_osc_coeff = m_subOsc->syncInit( _ab, _frames, _chnl );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames ; ++frame )
|
||||
{
|
||||
@@ -454,11 +673,10 @@ template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
m_subOsc->update( _ab, _frames, _chnl );
|
||||
m_subOsc->update( _ab, _frames, _chnl, true );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning;
|
||||
const float sampleRateCorrection = 44100.0f /
|
||||
Engine::mixer()->processingSampleRate();
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->processingSampleRate();
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -472,10 +690,18 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::SineWave>(
|
||||
const float _sample )
|
||||
inline sample_t Oscillator::getSample<Oscillator::SineWave>(const float sample)
|
||||
{
|
||||
return( sinSample( _sample ) );
|
||||
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate();
|
||||
|
||||
if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ)
|
||||
{
|
||||
return sinSample(sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -483,9 +709,16 @@ inline sample_t Oscillator::getSample<Oscillator::SineWave>(
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::TriangleWave>(
|
||||
const float _sample )
|
||||
const float _sample )
|
||||
{
|
||||
return( triangleSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[WaveShapes::TriangleWave - FirstWaveShapeTable],_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return triangleSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -493,9 +726,16 @@ inline sample_t Oscillator::getSample<Oscillator::TriangleWave>(
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::SawWave>(
|
||||
const float _sample )
|
||||
const float _sample )
|
||||
{
|
||||
return( sawSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[WaveShapes::SawWave - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return sawSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -503,9 +743,16 @@ inline sample_t Oscillator::getSample<Oscillator::SawWave>(
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::SquareWave>(
|
||||
const float _sample )
|
||||
const float _sample )
|
||||
{
|
||||
return( squareSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[WaveShapes::SquareWave - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return squareSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -515,7 +762,14 @@ template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::MoogSawWave>(
|
||||
const float _sample )
|
||||
{
|
||||
return( moogSawSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return moogSawSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -525,7 +779,14 @@ template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::ExponentialWave>(
|
||||
const float _sample )
|
||||
{
|
||||
return( expSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return expSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -545,9 +806,15 @@ template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::UserDefinedWave>(
|
||||
const float _sample )
|
||||
{
|
||||
return( userWaveSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(m_userWave->m_userAntiAliasWaveTable, _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return userWaveSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
191
src/core/PathUtil.cpp
Normal file
191
src/core/PathUtil.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "PathUtil.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms::PathUtil
|
||||
{
|
||||
Base relativeBases[] = { Base::ProjectDir, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset,
|
||||
Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG,
|
||||
Base::LocalDir };
|
||||
|
||||
QString baseLocation(const Base base, bool* error /* = nullptr*/)
|
||||
{
|
||||
// error is false unless something goes wrong
|
||||
if (error) { *error = false; }
|
||||
|
||||
QString loc = "";
|
||||
switch (base)
|
||||
{
|
||||
case Base::ProjectDir : loc = ConfigManager::inst()->userProjectsDir(); break;
|
||||
case Base::FactorySample :
|
||||
{
|
||||
QDir fsd = QDir(ConfigManager::inst()->factorySamplesDir());
|
||||
loc = fsd.absolutePath(); break;
|
||||
}
|
||||
case Base::UserSample : loc = ConfigManager::inst()->userSamplesDir(); break;
|
||||
case Base::UserVST : loc = ConfigManager::inst()->userVstDir(); break;
|
||||
case Base::Preset : loc = ConfigManager::inst()->userPresetsDir(); break;
|
||||
case Base::UserLADSPA : loc = ConfigManager::inst()->ladspaDir(); break;
|
||||
case Base::DefaultLADSPA : loc = ConfigManager::inst()->userLadspaDir(); break;
|
||||
case Base::UserSoundfont : loc = ConfigManager::inst()->sf2Dir(); break;
|
||||
case Base::DefaultSoundfont : loc = ConfigManager::inst()->userSf2Dir(); break;
|
||||
case Base::UserGIG : loc = ConfigManager::inst()->gigDir(); break;
|
||||
case Base::DefaultGIG : loc = ConfigManager::inst()->userGigDir(); break;
|
||||
case Base::LocalDir:
|
||||
{
|
||||
const Song* s = Engine::getSong();
|
||||
QString projectPath;
|
||||
if (s)
|
||||
{
|
||||
projectPath = s->projectFileName();
|
||||
loc = QFileInfo(projectPath).path();
|
||||
}
|
||||
// We resolved it properly if we had an open Song and the project
|
||||
// filename wasn't empty
|
||||
if (error) { *error = (!s || projectPath.isEmpty()); }
|
||||
break;
|
||||
}
|
||||
default : return QString("");
|
||||
}
|
||||
return QDir::cleanPath(loc) + "/";
|
||||
}
|
||||
|
||||
QDir baseQDir (const Base base, bool* error /* = nullptr*/)
|
||||
{
|
||||
if (base == Base::Absolute)
|
||||
{
|
||||
if (error) { *error = false; }
|
||||
return QDir::root();
|
||||
}
|
||||
return QDir(baseLocation(base, error));
|
||||
}
|
||||
|
||||
QString basePrefix(const Base base)
|
||||
{
|
||||
switch (base)
|
||||
{
|
||||
case Base::ProjectDir : return QStringLiteral("userprojects:");
|
||||
case Base::FactorySample : return QStringLiteral("factorysample:");
|
||||
case Base::UserSample : return QStringLiteral("usersample:");
|
||||
case Base::UserVST : return QStringLiteral("uservst:");
|
||||
case Base::Preset : return QStringLiteral("preset:");
|
||||
case Base::UserLADSPA : return QStringLiteral("userladspa:");
|
||||
case Base::DefaultLADSPA : return QStringLiteral("defaultladspa:");
|
||||
case Base::UserSoundfont : return QStringLiteral("usersoundfont:");
|
||||
case Base::DefaultSoundfont : return QStringLiteral("defaultsoundfont:");
|
||||
case Base::UserGIG : return QStringLiteral("usergig:");
|
||||
case Base::DefaultGIG : return QStringLiteral("defaultgig:");
|
||||
case Base::LocalDir : return QStringLiteral("local:");
|
||||
default : return QStringLiteral("");
|
||||
}
|
||||
}
|
||||
|
||||
Base baseLookup(const QString & path)
|
||||
{
|
||||
for (auto base: relativeBases)
|
||||
{
|
||||
QString prefix = basePrefix(base);
|
||||
if ( path.startsWith(prefix) ) { return base; }
|
||||
}
|
||||
return Base::Absolute;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString stripPrefix(const QString & path)
|
||||
{
|
||||
return path.mid( basePrefix(baseLookup(path)).length() );
|
||||
}
|
||||
|
||||
QString cleanName(const QString & path)
|
||||
{
|
||||
return stripPrefix(QFileInfo(path).baseName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString oldRelativeUpgrade(const QString & input)
|
||||
{
|
||||
if (input.isEmpty()) { return input; }
|
||||
|
||||
//Start by assuming that the file is a user sample
|
||||
Base assumedBase = Base::UserSample;
|
||||
|
||||
//Check if it's a factory sample
|
||||
QString factoryPath = baseLocation(Base::FactorySample) + input;
|
||||
QFileInfo factoryInfo(factoryPath);
|
||||
if (factoryInfo.exists()) { assumedBase = Base::FactorySample; }
|
||||
|
||||
//Check if it's a VST
|
||||
QString vstPath = baseLocation(Base::UserVST) + input;
|
||||
QFileInfo vstInfo(vstPath);
|
||||
if (vstInfo.exists()) { assumedBase = Base::UserVST; }
|
||||
|
||||
//Assume we've found the correct base location, return the full path
|
||||
return basePrefix(assumedBase) + input;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString toAbsolute(const QString & input, bool* error /* = nullptr*/)
|
||||
{
|
||||
//First, do no harm to absolute paths
|
||||
QFileInfo inputFileInfo = QFileInfo(input);
|
||||
if (inputFileInfo.isAbsolute())
|
||||
{
|
||||
if (error) { *error = false; }
|
||||
return input;
|
||||
}
|
||||
//Next, handle old relative paths with no prefix
|
||||
QString upgraded = input.contains(":") ? input : oldRelativeUpgrade(input);
|
||||
|
||||
Base base = baseLookup(upgraded);
|
||||
return baseLocation(base, error) + upgraded.remove(0, basePrefix(base).length());
|
||||
}
|
||||
|
||||
QString relativeOrAbsolute(const QString & input, const Base base)
|
||||
{
|
||||
if (input.isEmpty()) { return input; }
|
||||
QString absolutePath = toAbsolute(input);
|
||||
if (base == Base::Absolute) { return absolutePath; }
|
||||
bool error;
|
||||
QString relativePath = baseQDir(base, &error).relativeFilePath(absolutePath);
|
||||
// Return the relative path if it didn't result in a path starting with ..
|
||||
// and the baseQDir was resolved properly
|
||||
return (relativePath.startsWith("..") || error)
|
||||
? absolutePath
|
||||
: relativePath;
|
||||
}
|
||||
|
||||
QString toShortestRelative(const QString & input, bool allowLocal /* = false*/)
|
||||
{
|
||||
QFileInfo inputFileInfo = QFileInfo(input);
|
||||
QString absolutePath = inputFileInfo.isAbsolute() ? input : toAbsolute(input);
|
||||
|
||||
Base shortestBase = Base::Absolute;
|
||||
QString shortestPath = relativeOrAbsolute(absolutePath, shortestBase);
|
||||
for (auto base: relativeBases)
|
||||
{
|
||||
// Skip local paths when searching for the shortest relative if those
|
||||
// are not allowed for that resource
|
||||
if (base == Base::LocalDir && !allowLocal) { continue; }
|
||||
|
||||
QString otherPath = relativeOrAbsolute(absolutePath, base);
|
||||
if (otherPath.length() < shortestPath.length())
|
||||
{
|
||||
shortestBase = base;
|
||||
shortestPath = otherPath;
|
||||
}
|
||||
}
|
||||
return basePrefix(shortestBase) + relativeOrAbsolute(absolutePath, shortestBase);
|
||||
}
|
||||
|
||||
} // namespace lmms::PathUtil
|
||||
122
src/core/PatternClip.cpp
Normal file
122
src/core/PatternClip.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* PatternClip.cpp - implementation of class PatternClip
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "PatternClip.h"
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "PatternClipView.h"
|
||||
#include "PatternStore.h"
|
||||
#include "PatternTrack.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
PatternClip::PatternClip(Track* track) :
|
||||
Clip(track)
|
||||
{
|
||||
bar_t t = Engine::patternStore()->lengthOfPattern(patternIndex());
|
||||
if( t > 0 )
|
||||
{
|
||||
saveJournallingState( false );
|
||||
changeLength( TimePos( t, 0 ) );
|
||||
restoreJournallingState();
|
||||
}
|
||||
setAutoResize( false );
|
||||
}
|
||||
|
||||
void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element)
|
||||
{
|
||||
element.setAttribute( "name", name() );
|
||||
if( element.parentNode().nodeName() == "clipboard" )
|
||||
{
|
||||
element.setAttribute( "pos", -1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
element.setAttribute( "pos", startPosition() );
|
||||
}
|
||||
element.setAttribute( "len", length() );
|
||||
element.setAttribute( "muted", isMuted() );
|
||||
if( usesCustomClipColor() )
|
||||
{
|
||||
element.setAttribute( "color", color().name() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternClip::loadSettings(const QDomElement& element)
|
||||
{
|
||||
setName( element.attribute( "name" ) );
|
||||
if( element.attribute( "pos" ).toInt() >= 0 )
|
||||
{
|
||||
movePosition( element.attribute( "pos" ).toInt() );
|
||||
}
|
||||
changeLength( element.attribute( "len" ).toInt() );
|
||||
if( element.attribute( "muted" ).toInt() != isMuted() )
|
||||
{
|
||||
toggleMute();
|
||||
}
|
||||
|
||||
if (element.hasAttribute("color"))
|
||||
{
|
||||
if (!element.hasAttribute("usestyle"))
|
||||
{
|
||||
// for colors saved in 1.3-onwards
|
||||
setColor(element.attribute("color"));
|
||||
useCustomClipColor(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for colors saved before 1.3
|
||||
setColor(QColor(element.attribute("color").toUInt()));
|
||||
useCustomClipColor(element.attribute("usestyle").toUInt() == 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useCustomClipColor(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int PatternClip::patternIndex()
|
||||
{
|
||||
return dynamic_cast<PatternTrack*>(getTrack())->patternIndex();
|
||||
}
|
||||
|
||||
|
||||
|
||||
gui::ClipView* PatternClip::createView(gui::TrackView* tv)
|
||||
{
|
||||
return new gui::PatternClipView(this, tv);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
256
src/core/PatternStore.cpp
Normal file
256
src/core/PatternStore.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* PatternStore.cpp - model-component of Pattern Editor
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "PatternStore.h"
|
||||
|
||||
#include "Clip.h"
|
||||
#include "Engine.h"
|
||||
#include "PatternTrack.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
PatternStore::PatternStore() :
|
||||
TrackContainer(),
|
||||
m_patternComboBoxModel(this)
|
||||
{
|
||||
connect(&m_patternComboBoxModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(currentPatternChanged()));
|
||||
// we *always* want to receive updates even in case pattern actually did
|
||||
// not change upon setCurrentPattern()-call
|
||||
connect(&m_patternComboBoxModel, SIGNAL(dataUnchanged()),
|
||||
this, SLOT(currentPatternChanged()));
|
||||
setType(PatternContainer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum)
|
||||
{
|
||||
bool notePlayed = false;
|
||||
|
||||
if (lengthOfPattern(clipNum) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar());
|
||||
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
if (t->play(start, frames, offset, clipNum))
|
||||
{
|
||||
notePlayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return notePlayed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::updateAfterTrackAdd()
|
||||
{
|
||||
if (numOfPatterns() == 0 && !Engine::getSong()->isLoadingProject())
|
||||
{
|
||||
Engine::getSong()->addPatternTrack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bar_t PatternStore::lengthOfPattern(int pattern) const
|
||||
{
|
||||
TimePos maxLength = TimePos::ticksPerBar();
|
||||
|
||||
const TrackList & tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
// Don't create Clips here if they don't exist
|
||||
if (pattern < t->numOfClips())
|
||||
{
|
||||
maxLength = qMax(maxLength, t->getClip(pattern)->length());
|
||||
}
|
||||
}
|
||||
|
||||
return maxLength.nextFullBar();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int PatternStore::numOfPatterns() const
|
||||
{
|
||||
return Engine::getSong()->countTracks(Track::PatternTrack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::removePattern(int pattern)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
delete t->getClip(pattern);
|
||||
t->removeBar(pattern * DefaultTicksPerBar);
|
||||
}
|
||||
if (pattern <= currentPattern())
|
||||
{
|
||||
setCurrentPattern(qMax(currentPattern() - 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::swapPattern(int pattern1, int pattern2)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
t->swapPositionOfClips(pattern1, pattern2);
|
||||
}
|
||||
updateComboBox();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::updatePatternTrack(Clip* clip)
|
||||
{
|
||||
PatternTrack * t = PatternTrack::findPatternTrack(clip->startPosition() / DefaultTicksPerBar);
|
||||
if (t != nullptr)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::fixIncorrectPositions()
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
for (int i = 0; i < numOfPatterns(); ++i)
|
||||
{
|
||||
t->getClip(i)->movePosition(TimePos(i, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::play()
|
||||
{
|
||||
if (Engine::getSong()->playMode() != Song::Mode_PlayPattern)
|
||||
{
|
||||
Engine::getSong()->playPattern();
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine::getSong()->togglePause();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::stop()
|
||||
{
|
||||
Engine::getSong()->stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::updateComboBox()
|
||||
{
|
||||
const int curPattern = currentPattern();
|
||||
|
||||
m_patternComboBoxModel.clear();
|
||||
|
||||
for (int i = 0; i < numOfPatterns(); ++i)
|
||||
{
|
||||
PatternTrack* pt = PatternTrack::findPatternTrack(i);
|
||||
m_patternComboBoxModel.addItem(pt->name());
|
||||
}
|
||||
setCurrentPattern(curPattern);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::currentPatternChanged()
|
||||
{
|
||||
// now update all track-labels (the current one has to become white, the others gray)
|
||||
TrackList tl = Engine::getSong()->tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
if (t->type() == Track::PatternTrack)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::createClipsForPattern(int pattern)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
t->createClipsForPattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
AutomatedValueMap PatternStore::automatedValuesAt(TimePos time, int clipNum) const
|
||||
{
|
||||
Q_ASSERT(clipNum >= 0);
|
||||
Q_ASSERT(time.getTicks() >= 0);
|
||||
|
||||
auto lengthBars = lengthOfPattern(clipNum);
|
||||
auto lengthTicks = lengthBars * TimePos::ticksPerBar();
|
||||
if (time > lengthTicks)
|
||||
{
|
||||
time = lengthTicks;
|
||||
}
|
||||
|
||||
return TrackContainer::automatedValuesAt(time + (TimePos::ticksPerBar() * clipNum), clipNum);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -30,9 +30,13 @@
|
||||
#include <QDomElement>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "EffectChain.h"
|
||||
#include "plugins/peak_controller_effect/peak_controller_effect.h"
|
||||
#include "plugins/PeakControllerEffect/PeakControllerEffect.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
PeakControllerEffectVector PeakController::s_effects;
|
||||
int PeakController::m_getCount;
|
||||
@@ -49,14 +53,14 @@ PeakController::PeakController( Model * _parent,
|
||||
setSampleExact( true );
|
||||
if( m_peakEffect )
|
||||
{
|
||||
connect( m_peakEffect, SIGNAL( destroyed( ) ),
|
||||
this, SLOT( handleDestroyedEffect( ) ) );
|
||||
connect( m_peakEffect, SIGNAL(destroyed()),
|
||||
this, SLOT(handleDestroyedEffect()));
|
||||
}
|
||||
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateCoeffs() ) );
|
||||
connect( m_peakEffect->attackModel(), SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateCoeffs() ), Qt::DirectConnection );
|
||||
connect( m_peakEffect->decayModel(), SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateCoeffs() ), Qt::DirectConnection );
|
||||
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateCoeffs()));
|
||||
connect( m_peakEffect->attackModel(), SIGNAL(dataChanged()),
|
||||
this, SLOT(updateCoeffs()), Qt::DirectConnection );
|
||||
connect( m_peakEffect->decayModel(), SIGNAL(dataChanged()),
|
||||
this, SLOT(updateCoeffs()), Qt::DirectConnection );
|
||||
m_coeffNeedsUpdate = true;
|
||||
}
|
||||
|
||||
@@ -65,7 +69,7 @@ PeakController::PeakController( Model * _parent,
|
||||
|
||||
PeakController::~PeakController()
|
||||
{
|
||||
if( m_peakEffect != NULL && m_peakEffect->effectChain() != NULL )
|
||||
if( m_peakEffect != nullptr && m_peakEffect->effectChain() != nullptr )
|
||||
{
|
||||
m_peakEffect->effectChain()->removeEffect( m_peakEffect );
|
||||
}
|
||||
@@ -76,7 +80,7 @@ void PeakController::updateValueBuffer()
|
||||
{
|
||||
if( m_coeffNeedsUpdate )
|
||||
{
|
||||
const float ratio = 44100.0f / Engine::mixer()->processingSampleRate();
|
||||
const float ratio = 44100.0f / Engine::audioEngine()->processingSampleRate();
|
||||
m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio );
|
||||
m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio );
|
||||
m_coeffNeedsUpdate = false;
|
||||
@@ -87,7 +91,7 @@ void PeakController::updateValueBuffer()
|
||||
float targetSample = m_peakEffect->lastSample();
|
||||
if( m_currentSample != targetSample )
|
||||
{
|
||||
const f_cnt_t frames = Engine::mixer()->framesPerPeriod();
|
||||
const f_cnt_t frames = Engine::audioEngine()->framesPerPeriod();
|
||||
float * values = m_valueBuffer.values();
|
||||
|
||||
for( f_cnt_t f = 0; f < frames; ++f )
|
||||
@@ -123,12 +127,12 @@ void PeakController::updateCoeffs()
|
||||
}
|
||||
|
||||
|
||||
void PeakController::handleDestroyedEffect( )
|
||||
void PeakController::handleDestroyedEffect()
|
||||
{
|
||||
// possible race condition...
|
||||
//printf("disconnecting effect\n");
|
||||
disconnect( m_peakEffect );
|
||||
m_peakEffect = NULL;
|
||||
m_peakEffect = nullptr;
|
||||
//deleteLater();
|
||||
delete this;
|
||||
}
|
||||
@@ -235,7 +239,7 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,11 +251,10 @@ QString PeakController::nodeName() const
|
||||
|
||||
|
||||
|
||||
ControllerDialog * PeakController::createDialog( QWidget * _parent )
|
||||
gui::ControllerDialog * PeakController::createDialog( QWidget * _parent )
|
||||
{
|
||||
return new PeakControllerDialog( this, _parent );
|
||||
return new gui::PeakControllerDialog( this, _parent );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
# include <sys/times.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
PerfTime::PerfTime()
|
||||
: m_real(-1)
|
||||
{
|
||||
@@ -129,3 +134,6 @@ void PerfLogTimer::end()
|
||||
// Invalidate so destructor won't call print another log entry
|
||||
begin_time = PerfTime();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -41,6 +41,10 @@
|
||||
#include "InstrumentTrack.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
/*! The black / white order of keys as they appear on the keyboard.
|
||||
*/
|
||||
static const Piano::KeyTypes KEY_ORDER[] =
|
||||
@@ -59,15 +63,10 @@ static const Piano::KeyTypes KEY_ORDER[] =
|
||||
* \param _it the InstrumentTrack window to attach to
|
||||
*/
|
||||
Piano::Piano( InstrumentTrack* track ) :
|
||||
Model( NULL ), /*!< base class ctor */
|
||||
Model( nullptr ), /*!< base class ctor */
|
||||
m_instrumentTrack( track ),
|
||||
m_midiEvProc( track ) /*!< the InstrumentTrack Model */
|
||||
{
|
||||
for( int i = 0; i < NumKeys; ++i )
|
||||
{
|
||||
m_pressedKeys[i] = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*! \brief Turn a key on or off
|
||||
@@ -137,3 +136,5 @@ bool Piano::isWhiteKey( int key )
|
||||
return !isBlackKey( key );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -23,14 +23,15 @@
|
||||
*/
|
||||
|
||||
#include "PlayHandle.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "BufferManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
PlayHandle::PlayHandle(const Type type, f_cnt_t offset) :
|
||||
m_type(type),
|
||||
@@ -54,12 +55,12 @@ void PlayHandle::doProcessing()
|
||||
if( m_usesBuffer )
|
||||
{
|
||||
m_bufferReleased = false;
|
||||
BufferManager::clear(m_playHandleBuffer, Engine::mixer()->framesPerPeriod());
|
||||
BufferManager::clear(m_playHandleBuffer, Engine::audioEngine()->framesPerPeriod());
|
||||
play( buffer() );
|
||||
}
|
||||
else
|
||||
{
|
||||
play( NULL );
|
||||
play( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,3 +74,5 @@ sampleFrame* PlayHandle::buffer()
|
||||
{
|
||||
return m_bufferReleased ? nullptr : reinterpret_cast<sampleFrame*>(m_playHandleBuffer);
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,9 +25,8 @@
|
||||
#include "Plugin.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QLibrary>
|
||||
#include <QDomElement>
|
||||
#include <QLibrary>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "embed.h"
|
||||
@@ -36,6 +35,10 @@
|
||||
#include "DummyPlugin.h"
|
||||
#include "AutomatableModel.h"
|
||||
#include "Song.h"
|
||||
#include "PluginFactory.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
static PixmapLoader dummyLoader;
|
||||
@@ -44,12 +47,12 @@ static Plugin::Descriptor dummyPluginDescriptor =
|
||||
{
|
||||
"dummy",
|
||||
"dummy",
|
||||
QT_TRANSLATE_NOOP( "pluginBrowser", "no description" ),
|
||||
QT_TRANSLATE_NOOP( "PluginBrowser", "no description" ),
|
||||
"Tobias Doerffel <tobydox/at/users.sf.net>",
|
||||
0x0100,
|
||||
Plugin::Undefined,
|
||||
&dummyLoader,
|
||||
NULL
|
||||
nullptr
|
||||
} ;
|
||||
|
||||
|
||||
@@ -62,7 +65,7 @@ Plugin::Plugin(const Descriptor * descriptor, Model * parent, const
|
||||
m_descriptor(descriptor),
|
||||
m_key(key ? *key : Descriptor::SubPluginFeatures::Key(m_descriptor))
|
||||
{
|
||||
if( m_descriptor == NULL )
|
||||
if( m_descriptor == nullptr )
|
||||
{
|
||||
m_descriptor = &dummyPluginDescriptor;
|
||||
}
|
||||
@@ -71,13 +74,6 @@ Plugin::Plugin(const Descriptor * descriptor, Model * parent, const
|
||||
|
||||
|
||||
|
||||
Plugin::~Plugin()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<class T>
|
||||
T use_this_or(T this_param, T or_param)
|
||||
{
|
||||
@@ -184,7 +180,6 @@ AutomatableModel * Plugin::childModel( const QString & )
|
||||
|
||||
|
||||
|
||||
#include "PluginFactory.h"
|
||||
Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
|
||||
const Descriptor::SubPluginFeatures::Key *key,
|
||||
bool keyFromDnd)
|
||||
@@ -194,7 +189,7 @@ Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
|
||||
const Descriptor::SubPluginFeatures::Key *keyPtr = keyFromDnd
|
||||
? static_cast<Plugin::Descriptor::SubPluginFeatures::Key*>(Engine::pickDndPluginKey())
|
||||
: key;
|
||||
const PluginFactory::PluginInfo& pi = pluginFactory->pluginInfo(pluginName.toUtf8());
|
||||
const PluginFactory::PluginInfo& pi = getPluginFactory()->pluginInfo(pluginName.toUtf8());
|
||||
if(keyPtr)
|
||||
{
|
||||
// descriptor is not yet set when loading - set it now
|
||||
@@ -214,17 +209,17 @@ Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
|
||||
Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
|
||||
void *data)
|
||||
{
|
||||
const PluginFactory::PluginInfo& pi = pluginFactory->pluginInfo(pluginName.toUtf8());
|
||||
const PluginFactory::PluginInfo& pi = getPluginFactory()->pluginInfo(pluginName.toUtf8());
|
||||
|
||||
Plugin* inst;
|
||||
if( pi.isNull() )
|
||||
{
|
||||
if( gui )
|
||||
if (gui::getGUI() != nullptr)
|
||||
{
|
||||
QMessageBox::information( NULL,
|
||||
QMessageBox::information( nullptr,
|
||||
tr( "Plugin not found" ),
|
||||
tr( "The plugin \"%1\" wasn't found or could not be loaded!\nReason: \"%2\"" ).
|
||||
arg( pluginName ).arg( pluginFactory->errorString(pluginName) ),
|
||||
arg( pluginName ).arg( getPluginFactory()->errorString(pluginName) ),
|
||||
QMessageBox::Ok | QMessageBox::Default );
|
||||
}
|
||||
inst = new DummyPlugin();
|
||||
@@ -241,9 +236,9 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
|
||||
}
|
||||
else
|
||||
{
|
||||
if( gui )
|
||||
if (gui::getGUI() != nullptr)
|
||||
{
|
||||
QMessageBox::information( NULL,
|
||||
QMessageBox::information( nullptr,
|
||||
tr( "Error while loading plugin" ),
|
||||
tr( "Failed to load plugin \"%1\"!").arg( pluginName ),
|
||||
QMessageBox::Ok | QMessageBox::Default );
|
||||
@@ -266,10 +261,10 @@ void Plugin::collectErrorForUI( QString errMsg )
|
||||
|
||||
|
||||
|
||||
PluginView * Plugin::createView( QWidget * parent )
|
||||
gui::PluginView * Plugin::createView( QWidget * parent )
|
||||
{
|
||||
PluginView * pv = instantiateView( parent );
|
||||
if( pv != NULL )
|
||||
gui::PluginView * pv = instantiateView( parent );
|
||||
if( pv != nullptr )
|
||||
{
|
||||
pv->setModel( this );
|
||||
}
|
||||
@@ -280,7 +275,7 @@ PluginView * Plugin::createView( QWidget * parent )
|
||||
|
||||
|
||||
Plugin::Descriptor::SubPluginFeatures::Key::Key( const QDomElement & key ) :
|
||||
desc( NULL ),
|
||||
desc( nullptr ),
|
||||
name( key.attribute( "key" ) ),
|
||||
attributes()
|
||||
{
|
||||
@@ -313,3 +308,4 @@ QDomElement Plugin::Descriptor::SubPluginFeatures::Key::saveXML(
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,15 +24,25 @@
|
||||
|
||||
#include "PluginFactory.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QLibrary>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QLibrary>
|
||||
#include <memory>
|
||||
#include "lmmsconfig.h"
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "Plugin.h"
|
||||
#include "embed.h"
|
||||
|
||||
// QT qHash specialization, needs to be in global namespace
|
||||
qint64 qHash(const QFileInfo& fi)
|
||||
{
|
||||
return qHash(fi.absoluteFilePath());
|
||||
}
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
QStringList nameFilters("*.dll");
|
||||
@@ -40,11 +50,6 @@
|
||||
QStringList nameFilters("lib*.so");
|
||||
#endif
|
||||
|
||||
qint64 qHash(const QFileInfo& fi)
|
||||
{
|
||||
return qHash(fi.absoluteFilePath());
|
||||
}
|
||||
|
||||
std::unique_ptr<PluginFactory> PluginFactory::s_instance;
|
||||
|
||||
PluginFactory::PluginFactory()
|
||||
@@ -53,10 +58,6 @@ PluginFactory::PluginFactory()
|
||||
discoverPlugins();
|
||||
}
|
||||
|
||||
PluginFactory::~PluginFactory()
|
||||
{
|
||||
}
|
||||
|
||||
void PluginFactory::setupSearchPaths()
|
||||
{
|
||||
// Adds a search path relative to the main executable if the path exists.
|
||||
@@ -94,11 +95,16 @@ void PluginFactory::setupSearchPaths()
|
||||
PluginFactory* PluginFactory::instance()
|
||||
{
|
||||
if (s_instance == nullptr)
|
||||
s_instance.reset(new PluginFactory());
|
||||
s_instance = std::make_unique<PluginFactory>();
|
||||
|
||||
return s_instance.get();
|
||||
}
|
||||
|
||||
PluginFactory* getPluginFactory()
|
||||
{
|
||||
return PluginFactory::instance();
|
||||
}
|
||||
|
||||
const Plugin::DescriptorList PluginFactory::descriptors() const
|
||||
{
|
||||
return m_descriptors.values();
|
||||
@@ -144,7 +150,12 @@ void PluginFactory::discoverPlugins()
|
||||
QSet<QFileInfo> files;
|
||||
for (const QString& searchPath : QDir::searchPaths("plugins"))
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
|
||||
auto discoveredPluginList = QDir(searchPath).entryInfoList(nameFilters);
|
||||
files.unite(QSet<QFileInfo>(discoveredPluginList.begin(), discoveredPluginList.end()));
|
||||
#else
|
||||
files.unite(QDir(searchPath).entryInfoList(nameFilters).toSet());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Cheap dependency handling: zynaddsubfx needs ZynAddSubFxCore. By loading
|
||||
@@ -241,3 +252,6 @@ const QString PluginFactory::PluginInfo::name() const
|
||||
{
|
||||
return descriptor ? descriptor->name : QString();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
|
||||
#include "PluginIssue.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
const char *PluginIssue::msgFor(const PluginIssueType &it)
|
||||
{
|
||||
switch (it)
|
||||
@@ -38,6 +42,10 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
|
||||
return "too many audio input channels";
|
||||
case tooManyOutputChannels:
|
||||
return "too many audio output channels";
|
||||
case tooManyMidiInputChannels:
|
||||
return "too many MIDI input channels";
|
||||
case tooManyMidiOutputChannels:
|
||||
return "too many MIDI output channels";
|
||||
case noOutputChannel:
|
||||
return "no audio output channel";
|
||||
case portHasNoDef:
|
||||
@@ -46,10 +54,22 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
|
||||
return "port is missing min value";
|
||||
case portHasNoMax:
|
||||
return "port is missing max value";
|
||||
case minGreaterMax:
|
||||
return "port minimum is greater than maximum";
|
||||
case defaultValueNotInRange:
|
||||
return "default value is not in range [min, max]";
|
||||
case logScaleMinMissing:
|
||||
return "logscale requires minimum value";
|
||||
case logScaleMaxMissing:
|
||||
return "logscale requires maximum value";
|
||||
case logScaleMinMaxDifferentSigns:
|
||||
return "logscale with min < 0 < max";
|
||||
case featureNotSupported:
|
||||
return "required feature not supported";
|
||||
case badPortType:
|
||||
return "unsupported port type";
|
||||
case blacklisted:
|
||||
return "blacklisted plugin";
|
||||
case noIssue:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -59,6 +79,22 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
|
||||
|
||||
|
||||
|
||||
bool PluginIssue::operator==(const PluginIssue &other) const
|
||||
{
|
||||
return (m_issueType == other.m_issueType) && (m_info == other.m_info);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool PluginIssue::operator<(const PluginIssue &other) const
|
||||
{
|
||||
return (m_issueType != other.m_issueType)
|
||||
? m_issueType < other.m_issueType
|
||||
: m_info < other.m_info;
|
||||
}
|
||||
|
||||
|
||||
QDebug operator<<(QDebug stream, const PluginIssue &iss)
|
||||
{
|
||||
stream << PluginIssue::msgFor(iss.m_issueType);
|
||||
@@ -69,4 +105,7 @@ QDebug operator<<(QDebug stream, const PluginIssue &iss)
|
||||
return stream;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,23 +25,26 @@
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "PresetPreviewPlayHandle.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Instrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "PluginFactory.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "TrackContainer.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
// invisible track-container which is needed as parent for preview-channels
|
||||
class PreviewTrackContainer : public TrackContainer
|
||||
{
|
||||
public:
|
||||
PreviewTrackContainer() :
|
||||
m_previewInstrumentTrack( NULL ),
|
||||
m_previewNote( NULL ),
|
||||
m_previewInstrumentTrack( nullptr ),
|
||||
m_previewNote( nullptr ),
|
||||
m_dataMutex()
|
||||
{
|
||||
setJournalling( false );
|
||||
@@ -50,11 +53,9 @@ public:
|
||||
m_previewInstrumentTrack->setPreviewMode( true );
|
||||
}
|
||||
|
||||
virtual ~PreviewTrackContainer()
|
||||
{
|
||||
}
|
||||
~PreviewTrackContainer() override = default;
|
||||
|
||||
virtual QString nodeName() const
|
||||
QString nodeName() const override
|
||||
{
|
||||
return "previewtrackcontainer";
|
||||
}
|
||||
@@ -122,10 +123,10 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
|
||||
s_previewTC->lockData();
|
||||
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
s_previewTC->setPreviewNote( nullptr );
|
||||
s_previewTC->previewInstrumentTrack()->silenceAllNotes();
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
const bool j = Engine::projectJournal()->isJournalling();
|
||||
Engine::projectJournal()->setJournalling( false );
|
||||
@@ -135,14 +136,14 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
Instrument * i = s_previewTC->previewInstrumentTrack()->instrument();
|
||||
const QString ext = QFileInfo( _preset_file ).
|
||||
suffix().toLower();
|
||||
if( i == NULL || !i->descriptor()->supportsFileType( ext ) )
|
||||
if( i == nullptr || !i->descriptor()->supportsFileType( ext ) )
|
||||
{
|
||||
const PluginFactory::PluginInfoAndKey& infoAndKey =
|
||||
pluginFactory->pluginSupportingExtension(ext);
|
||||
getPluginFactory()->pluginSupportingExtension(ext);
|
||||
i = s_previewTC->previewInstrumentTrack()->
|
||||
loadInstrument(infoAndKey.info.name(), &infoAndKey.key);
|
||||
}
|
||||
if( i != NULL )
|
||||
if( i != nullptr )
|
||||
{
|
||||
i->loadFile( _preset_file );
|
||||
}
|
||||
@@ -156,19 +157,9 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
dataFileCreated = true;
|
||||
}
|
||||
|
||||
// vestige previews are bug prone; fallback on 3xosc with volume of 0
|
||||
// without an instrument in preview track, it will segfault
|
||||
if(dataFile->content().elementsByTagName( "vestige" ).length() == 0 )
|
||||
{
|
||||
s_previewTC->previewInstrumentTrack()->
|
||||
loadTrackSpecificSettings(
|
||||
dataFile->content().firstChild().toElement() );
|
||||
}
|
||||
else
|
||||
{
|
||||
s_previewTC->previewInstrumentTrack()->loadInstrument("tripleoscillator");
|
||||
s_previewTC->previewInstrumentTrack()->setVolume( 0 );
|
||||
}
|
||||
s_previewTC->previewInstrumentTrack()->loadTrackSpecificSettings(
|
||||
dataFile->content().firstChild().toElement());
|
||||
|
||||
if( dataFileCreated )
|
||||
{
|
||||
delete dataFile;
|
||||
@@ -180,7 +171,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
s_previewTC->previewInstrumentTrack()->
|
||||
midiPort()->setMode( MidiPort::Disabled );
|
||||
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
// create note-play-handle for it
|
||||
m_previewNote = NotePlayHandleManager::acquire(
|
||||
s_previewTC->previewInstrumentTrack(), 0,
|
||||
@@ -191,9 +182,9 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
|
||||
s_previewTC->setPreviewNote( m_previewNote );
|
||||
|
||||
Engine::mixer()->addPlayHandle( m_previewNote );
|
||||
Engine::audioEngine()->addPlayHandle( m_previewNote );
|
||||
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
s_previewTC->unlockData();
|
||||
Engine::projectJournal()->setJournalling( j );
|
||||
}
|
||||
@@ -203,13 +194,13 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
|
||||
PresetPreviewPlayHandle::~PresetPreviewPlayHandle()
|
||||
{
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
// not muted by other preset-preview-handle?
|
||||
if (s_previewTC->testAndSetPreviewNote(m_previewNote, nullptr))
|
||||
{
|
||||
m_previewNote->noteOff();
|
||||
}
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +209,7 @@ PresetPreviewPlayHandle::~PresetPreviewPlayHandle()
|
||||
void PresetPreviewPlayHandle::play( sampleFrame * _working_buffer )
|
||||
{
|
||||
// Do nothing; the preview instrument is played by m_previewNote, which
|
||||
// has been added to the mixer
|
||||
// has been added to the audio engine
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +245,7 @@ void PresetPreviewPlayHandle::init()
|
||||
void PresetPreviewPlayHandle::cleanup()
|
||||
{
|
||||
delete s_previewTC;
|
||||
s_previewTC = NULL;
|
||||
s_previewTC = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +255,7 @@ ConstNotePlayHandleList PresetPreviewPlayHandle::nphsOfInstrumentTrack(
|
||||
const InstrumentTrack * _it )
|
||||
{
|
||||
ConstNotePlayHandleList cnphv;
|
||||
if( s_previewTC->previewNote() != NULL &&
|
||||
if( s_previewTC->previewNote() != nullptr &&
|
||||
s_previewTC->previewNote()->instrumentTrack() == _it )
|
||||
{
|
||||
cnphv.push_back( s_previewTC->previewNote() );
|
||||
@@ -284,4 +275,4 @@ bool PresetPreviewPlayHandle::isPreviewing()
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "JournallingObject.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
//! Avoid clashes between loaded IDs (have the bit cleared)
|
||||
//! and newly created IDs (have the bit set)
|
||||
static const int EO_ID_MSB = 1 << 23;
|
||||
@@ -46,13 +49,6 @@ ProjectJournal::ProjectJournal() :
|
||||
|
||||
|
||||
|
||||
ProjectJournal::~ProjectJournal()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void ProjectJournal::undo()
|
||||
{
|
||||
while( !m_undoCheckPoints.isEmpty() )
|
||||
@@ -181,7 +177,7 @@ void ProjectJournal::clearJournal()
|
||||
|
||||
for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); )
|
||||
{
|
||||
if( it.value() == NULL )
|
||||
if( it.value() == nullptr )
|
||||
{
|
||||
it = m_joIDs.erase( it );
|
||||
}
|
||||
@@ -196,7 +192,7 @@ void ProjectJournal::stopAllJournalling()
|
||||
{
|
||||
for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); ++it)
|
||||
{
|
||||
if( it.value() != NULL )
|
||||
if( it.value() != nullptr )
|
||||
{
|
||||
it.value()->setJournalling(false);
|
||||
}
|
||||
@@ -206,3 +202,4 @@ void ProjectJournal::stopAllJournalling()
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -34,9 +34,10 @@
|
||||
#include "AudioFileMP3.h"
|
||||
#include "AudioFileFlac.h"
|
||||
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
#include "sched.h"
|
||||
#endif
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
|
||||
{
|
||||
@@ -55,7 +56,7 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
&AudioFileOgg::getInst
|
||||
#else
|
||||
NULL
|
||||
nullptr
|
||||
#endif
|
||||
},
|
||||
{ ProjectRenderer::MP3File,
|
||||
@@ -64,25 +65,25 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
|
||||
#ifdef LMMS_HAVE_MP3LAME
|
||||
&AudioFileMP3::getInst
|
||||
#else
|
||||
NULL
|
||||
nullptr
|
||||
#endif
|
||||
},
|
||||
// Insert your own file-encoder infos here.
|
||||
// Maybe one day the user can add own encoders inside the program.
|
||||
|
||||
{ ProjectRenderer::NumFileFormats, NULL, NULL, NULL }
|
||||
{ ProjectRenderer::NumFileFormats, nullptr, nullptr, nullptr }
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings,
|
||||
ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ExportFileFormats exportFileFormat,
|
||||
const QString & outputFilename ) :
|
||||
QThread( Engine::mixer() ),
|
||||
m_fileDev( NULL ),
|
||||
QThread( Engine::audioEngine() ),
|
||||
m_fileDev( nullptr ),
|
||||
m_qualitySettings( qualitySettings ),
|
||||
m_progress( 0 ),
|
||||
m_abort( false )
|
||||
@@ -95,11 +96,11 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings
|
||||
|
||||
m_fileDev = audioEncoderFactory(
|
||||
outputFilename, outputSettings, DEFAULT_CHANNELS,
|
||||
Engine::mixer(), successful );
|
||||
Engine::audioEngine(), successful );
|
||||
if( !successful )
|
||||
{
|
||||
delete m_fileDev;
|
||||
m_fileDev = NULL;
|
||||
m_fileDev = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,13 +108,6 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings
|
||||
|
||||
|
||||
|
||||
ProjectRenderer::~ProjectRenderer()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Little help function for getting file format from a file extension
|
||||
// (only for registered file-encoders).
|
||||
ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension(
|
||||
@@ -149,10 +143,9 @@ void ProjectRenderer::startProcessing()
|
||||
|
||||
if( isReady() )
|
||||
{
|
||||
// Have to do mixer stuff with GUI-thread affinity in order to
|
||||
// Have to do audio engine stuff with GUI-thread affinity in order to
|
||||
// make slots connected to sampleRateChanged()-signals being called immediately.
|
||||
Engine::mixer()->setAudioDevice( m_fileDev,
|
||||
m_qualitySettings, false, false );
|
||||
Engine::audioEngine()->setAudioDevice( m_fileDev, m_qualitySettings, false, false );
|
||||
|
||||
start(
|
||||
#ifndef LMMS_BUILD_WIN32
|
||||
@@ -168,7 +161,7 @@ void ProjectRenderer::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
#if 0
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD)
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO( &mask );
|
||||
@@ -181,14 +174,13 @@ void ProjectRenderer::run()
|
||||
PerfLogTimer perfLog("Project Render");
|
||||
|
||||
Engine::getSong()->startExport();
|
||||
Engine::getSong()->updateLength();
|
||||
// Skip first empty buffer.
|
||||
Engine::mixer()->nextBuffer();
|
||||
Engine::audioEngine()->nextBuffer();
|
||||
|
||||
m_progress = 0;
|
||||
|
||||
// Now start processing
|
||||
Engine::mixer()->startProcessing(false);
|
||||
Engine::audioEngine()->startProcessing(false);
|
||||
|
||||
// Continually track and emit progress percentage to listeners.
|
||||
while (!Engine::getSong()->isExportDone() && !m_abort)
|
||||
@@ -202,8 +194,8 @@ void ProjectRenderer::run()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify mixer of the end of processing.
|
||||
Engine::mixer()->stopProcessing();
|
||||
// Notify the audio engine of the end of processing.
|
||||
Engine::audioEngine()->stopProcessing();
|
||||
|
||||
Engine::getSong()->stopExport();
|
||||
|
||||
@@ -241,7 +233,7 @@ void ProjectRenderer::updateConsoleProgress()
|
||||
}
|
||||
prog[cols] = 0;
|
||||
|
||||
const char * activity = (const char *) "|/-\\";
|
||||
const auto activity = (const char*)"|/-\\";
|
||||
memset( buf, 0, sizeof( buf ) );
|
||||
sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress,
|
||||
activity[rot] );
|
||||
@@ -252,3 +244,4 @@ void ProjectRenderer::updateConsoleProgress()
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -27,123 +27,107 @@
|
||||
|
||||
#include "ProjectVersion.h"
|
||||
|
||||
int parseMajor(QString & version) {
|
||||
return version.section( '.', 0, 0 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseMinor(QString & version) {
|
||||
return version.section( '.', 1, 1 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseRelease(QString & version) {
|
||||
return version.section( '.', 2, 2 ).section( '-', 0, 0 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString parseStage(QString & version) {
|
||||
return version.section( '.', 2, 2 ).section( '-', 1 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseBuild(QString & version) {
|
||||
return version.section( '.', 3 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(QString version, CompareType c) :
|
||||
m_version(version),
|
||||
m_major(parseMajor(m_version)),
|
||||
m_minor(parseMinor(m_version)),
|
||||
m_release(parseRelease(m_version)),
|
||||
m_stage(parseStage(m_version)),
|
||||
m_build(parseBuild(m_version)),
|
||||
m_compareType(c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(const char* version, CompareType c) :
|
||||
m_version(QString(version)),
|
||||
m_major(parseMajor(m_version)),
|
||||
m_minor(parseMinor(m_version)),
|
||||
m_release(parseRelease(m_version)),
|
||||
m_stage(parseStage(m_version)),
|
||||
m_build(parseBuild(m_version)),
|
||||
m_compareType(c)
|
||||
{
|
||||
// Version numbers may have build data, prefixed with a '+',
|
||||
// but this mustn't affect version precedence in comparisons
|
||||
QString metadataStripped = version.split("+").first();
|
||||
// They must have an obligatory initial segement, and may have
|
||||
// optional identifiers prefaced by a '-'. Both parts affect precedence
|
||||
QString obligatorySegment = metadataStripped.section('-', 0, 0);
|
||||
QString prereleaseSegment = metadataStripped.section('-', 1);
|
||||
|
||||
// The obligatory segment consists of three identifiers: MAJOR.MINOR.PATCH
|
||||
QStringList mainVersion = obligatorySegment.split(".");
|
||||
// HACK: Pad invalid versions in order to prevent crashes
|
||||
while (mainVersion.size() < 3){ mainVersion.append("0"); }
|
||||
m_major = mainVersion.at(0).toInt();
|
||||
m_minor = mainVersion.at(1).toInt();
|
||||
m_patch = mainVersion.at(2).toInt();
|
||||
|
||||
// Any # of optional pre-release identifiers may follow, separated by '.'s
|
||||
if (!prereleaseSegment.isEmpty()){ m_labels = prereleaseSegment.split("."); }
|
||||
|
||||
// HACK: Handle old (1.2.2 and earlier), non-standard versions of the form
|
||||
// MAJOR.MINOR.PATCH.COMMITS, used for non-release builds from source.
|
||||
if (mainVersion.size() >= 4 && m_major <= 1 && m_minor <= 2 && m_patch <= 2){
|
||||
// Drop the standard version identifiers. erase(a, b) removes [a,b)
|
||||
mainVersion.erase(mainVersion.begin(), mainVersion.begin() + 3);
|
||||
// Prepend the remaining identifiers as prerelease versions
|
||||
m_labels = mainVersion + m_labels;
|
||||
// Bump the patch version. x.y.z-a < x.y.z, but we want x.y.z.a > x.y.z
|
||||
m_patch += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(const char* version, CompareType c) : ProjectVersion(QString(version), c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//! @param c Determines the number of identifiers to check when comparing
|
||||
int ProjectVersion::compare(const ProjectVersion & a, const ProjectVersion & b, CompareType c)
|
||||
{
|
||||
if(a.getMajor() != b.getMajor())
|
||||
{
|
||||
return a.getMajor() - b.getMajor();
|
||||
}
|
||||
if(c == Major)
|
||||
{
|
||||
return 0;
|
||||
// How many identifiers to compare before we consider the versions equal
|
||||
const int limit = static_cast<int>(c);
|
||||
|
||||
// Use the value of limit to zero out identifiers we don't care about
|
||||
int aMaj = 0, bMaj = 0, aMin = 0, bMin = 0, aPat = 0, bPat = 0;
|
||||
if (limit >= 1){ aMaj = a.getMajor(); bMaj = b.getMajor(); }
|
||||
if (limit >= 2){ aMin = a.getMinor(); bMin = b.getMinor(); }
|
||||
if (limit >= 3){ aPat = a.getPatch(); bPat = b.getPatch(); }
|
||||
|
||||
// Then we can compare as if we care about every identifier
|
||||
if(aMaj != bMaj){ return aMaj - bMaj; }
|
||||
if(aMin != bMin){ return aMin - bMin; }
|
||||
if(aPat != bPat){ return aPat - bPat; }
|
||||
|
||||
// Decide how many optional identifiers we care about
|
||||
const int maxLabels = qMax(0, limit - 3);
|
||||
const auto aLabels = a.getLabels().mid(0, maxLabels);
|
||||
const auto bLabels = b.getLabels().mid(0, maxLabels);
|
||||
|
||||
// We can only compare identifiers if both versions have them
|
||||
const int commonLabels = qMin(aLabels.size(), bLabels.size());
|
||||
// If one version has optional labels and the other doesn't,
|
||||
// the one without them is bigger
|
||||
if (commonLabels == 0){ return bLabels.size() - aLabels.size(); }
|
||||
|
||||
// Otherwise, compare as many labels as we can
|
||||
for (int i = 0; i < commonLabels; i++){
|
||||
const QString& labelA = aLabels.at(i);
|
||||
const QString& labelB = bLabels.at(i);
|
||||
// If both labels are the same, skip
|
||||
if (labelA == labelB){ continue; }
|
||||
// Numeric and non-numeric identifiers compare differently
|
||||
bool aIsNumeric = false, bIsNumeric = false;
|
||||
const int numA = labelA.toInt(&aIsNumeric);
|
||||
const int numB = labelB.toInt(&bIsNumeric);
|
||||
// toInt reads '-x' as a negative number, semver says it's non-numeric
|
||||
aIsNumeric &= !labelA.startsWith("-");
|
||||
bIsNumeric &= !labelB.startsWith("-");
|
||||
// If only one identifier is numeric, that one is smaller
|
||||
if (aIsNumeric != bIsNumeric){ return aIsNumeric ? -1 : 1; }
|
||||
// If both are numeric, compare as numbers
|
||||
if (aIsNumeric && bIsNumeric){ return numA - numB; }
|
||||
// Otherwise, compare lexically
|
||||
return labelA.compare(labelB);
|
||||
}
|
||||
|
||||
if(a.getMinor() != b.getMinor())
|
||||
{
|
||||
return a.getMinor() - b.getMinor();
|
||||
}
|
||||
if(c == Minor)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(a.getRelease() != b.getRelease())
|
||||
{
|
||||
return a.getRelease() - b.getRelease();
|
||||
}
|
||||
if(c == Release)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!(a.getStage().isEmpty() && b.getStage().isEmpty()))
|
||||
{
|
||||
// make sure 0.x.y > 0.x.y-alpha
|
||||
if(a.getStage().isEmpty())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if(b.getStage().isEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 0.x.y-beta > 0.x.y-alpha
|
||||
int cmp = QString::compare(a.getStage(), b.getStage());
|
||||
if(cmp)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
if(c == Stage)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getBuild() - b.getBuild();
|
||||
// If everything else matches, the version with more labels is bigger
|
||||
return aLabels.size() - bLabels.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -155,4 +139,4 @@ int ProjectVersion::compare(ProjectVersion v1, ProjectVersion v2)
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -22,26 +22,58 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#define COMPILE_REMOTE_PLUGIN_BASE
|
||||
#include "RemotePlugin.h"
|
||||
|
||||
//#define DEBUG_REMOTE_PLUGIN
|
||||
#ifdef DEBUG_REMOTE_PLUGIN
|
||||
#include <QDebug>
|
||||
#endif
|
||||
|
||||
#include "BufferManager.h"
|
||||
#include "RemotePlugin.h"
|
||||
#include "Mixer.h"
|
||||
#include "Engine.h"
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "BufferManager.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QUuid>
|
||||
|
||||
#ifndef SYNC_WITH_SHM_FIFO
|
||||
#include <QtCore/QUuid>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
|
||||
namespace {
|
||||
|
||||
HANDLE getRemotePluginJob()
|
||||
{
|
||||
static const auto job = []
|
||||
{
|
||||
const auto job = CreateJobObject(nullptr, nullptr);
|
||||
|
||||
auto limitInfo = JOBOBJECT_EXTENDED_LIMIT_INFORMATION{};
|
||||
limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limitInfo, sizeof(limitInfo));
|
||||
|
||||
return job;
|
||||
}();
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // LMMS_BUILD_WIN32
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
// simple helper thread monitoring our RemotePlugin - if process terminates
|
||||
// unexpectedly invalidate plugin so LMMS doesn't lock up
|
||||
@@ -55,18 +87,41 @@ ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) :
|
||||
|
||||
void ProcessWatcher::run()
|
||||
{
|
||||
m_plugin->m_process.start( m_plugin->m_exec, m_plugin->m_args );
|
||||
exec();
|
||||
m_plugin->m_process.moveToThread( m_plugin->thread() );
|
||||
while( !m_quit && m_plugin->messagesLeft() )
|
||||
{
|
||||
msleep( 200 );
|
||||
}
|
||||
if( !m_quit )
|
||||
{
|
||||
fprintf( stderr,
|
||||
"remote plugin died! invalidating now.\n" );
|
||||
auto& process = m_plugin->m_process;
|
||||
process.start(m_plugin->m_exec, m_plugin->m_args);
|
||||
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
// Add the process to our job so it is killed if we crash
|
||||
if (process.waitForStarted(-1))
|
||||
{
|
||||
if (const auto processHandle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, process.processId()))
|
||||
{
|
||||
// Ensure the process is still running, otherwise the handle we
|
||||
// obtained may be for a different process that happened to reuse
|
||||
// the same process id.
|
||||
// QProcess::state() alone is insufficient as it only returns a
|
||||
// cached state variable that is updated asynchronously. To query
|
||||
// the process itself, we can use QProcess::waitForFinished() with a
|
||||
// zero timeout, but that too is insufficient as it fails if the
|
||||
// process has already finished. Therefore, we check both.
|
||||
if (!process.waitForFinished(0) && process.state() == QProcess::Running)
|
||||
{
|
||||
AssignProcessToJobObject(getRemotePluginJob(), processHandle);
|
||||
}
|
||||
CloseHandle(processHandle);
|
||||
}
|
||||
}
|
||||
#endif // LMMS_BUILD_WIN32
|
||||
|
||||
exec();
|
||||
process.moveToThread(m_plugin->thread());
|
||||
while (!m_quit && m_plugin->messagesLeft())
|
||||
{
|
||||
msleep(200);
|
||||
}
|
||||
if (!m_quit)
|
||||
{
|
||||
fprintf(stderr, "remote plugin died! invalidating now.\n");
|
||||
m_plugin->invalidate();
|
||||
}
|
||||
}
|
||||
@@ -84,15 +139,11 @@ RemotePlugin::RemotePlugin() :
|
||||
#endif
|
||||
m_failed( true ),
|
||||
m_watcher( this ),
|
||||
m_commMutex( QMutex::Recursive ),
|
||||
m_splitChannels( false ),
|
||||
#ifdef USE_QT_SHMEM
|
||||
m_shmObj(),
|
||||
#else
|
||||
m_shmID( 0 ),
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5,14,0))
|
||||
m_commMutex(QMutex::Recursive),
|
||||
#endif
|
||||
m_shmSize( 0 ),
|
||||
m_shm( NULL ),
|
||||
m_splitChannels( false ),
|
||||
m_audioBufferSize( 0 ),
|
||||
m_inputCount( DEFAULT_CHANNELS ),
|
||||
m_outputCount( DEFAULT_CHANNELS )
|
||||
{
|
||||
@@ -102,14 +153,14 @@ RemotePlugin::RemotePlugin() :
|
||||
|
||||
m_socketFile = QDir::tempPath() + QDir::separator() +
|
||||
QUuid::createUuid().toString();
|
||||
const char * path = m_socketFile.toUtf8().constData();
|
||||
size_t length = strlen( path );
|
||||
auto path = m_socketFile.toUtf8();
|
||||
size_t length = path.length();
|
||||
if ( length >= sizeof sa.sun_path )
|
||||
{
|
||||
length = sizeof sa.sun_path - 1;
|
||||
qWarning( "Socket path too long." );
|
||||
}
|
||||
memcpy( sa.sun_path, path, length );
|
||||
memcpy(sa.sun_path, path.constData(), length );
|
||||
sa.sun_path[length] = '\0';
|
||||
|
||||
m_server = socket( PF_LOCAL, SOCK_STREAM, 0 );
|
||||
@@ -117,7 +168,7 @@ RemotePlugin::RemotePlugin() :
|
||||
{
|
||||
qWarning( "Unable to start the server." );
|
||||
}
|
||||
remove( path );
|
||||
remove(path.constData());
|
||||
int ret = bind( m_server, (struct sockaddr *) &sa, sizeof sa );
|
||||
if ( ret == -1 || listen( m_server, 1 ) == -1 )
|
||||
{
|
||||
@@ -125,14 +176,14 @@ RemotePlugin::RemotePlugin() :
|
||||
}
|
||||
#endif
|
||||
|
||||
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
|
||||
this, SLOT( processFinished( int, QProcess::ExitStatus ) ),
|
||||
connect( &m_process, SIGNAL(finished(int,QProcess::ExitStatus)),
|
||||
this, SLOT(processFinished(int,QProcess::ExitStatus)),
|
||||
Qt::DirectConnection );
|
||||
connect( &m_process, SIGNAL( errorOccurred( QProcess::ProcessError ) ),
|
||||
this, SLOT( processErrored( QProcess::ProcessError ) ),
|
||||
connect( &m_process, SIGNAL(errorOccurred(QProcess::ProcessError)),
|
||||
this, SLOT(processErrored(QProcess::ProcessError)),
|
||||
Qt::DirectConnection );
|
||||
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
|
||||
&m_watcher, SLOT( quit() ), Qt::DirectConnection );
|
||||
connect( &m_process, SIGNAL(finished(int,QProcess::ExitStatus)),
|
||||
&m_watcher, SLOT(quit()), Qt::DirectConnection );
|
||||
}
|
||||
|
||||
|
||||
@@ -158,11 +209,6 @@ RemotePlugin::~RemotePlugin()
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
#ifndef USE_QT_SHMEM
|
||||
shmdt( m_shm );
|
||||
shmctl( m_shmID, IPC_RMID, NULL );
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef SYNC_WITH_SHM_FIFO
|
||||
@@ -222,8 +268,8 @@ bool RemotePlugin::init(const QString &pluginExecutable,
|
||||
QStringList args;
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
// swap in and out for bidirectional communication
|
||||
args << QString::number( out()->shmKey() );
|
||||
args << QString::number( in()->shmKey() );
|
||||
args << QString::fromStdString(out()->shmKey());
|
||||
args << QString::fromStdString(in()->shmKey());
|
||||
#else
|
||||
args << m_socketFile;
|
||||
#endif
|
||||
@@ -256,7 +302,7 @@ bool RemotePlugin::init(const QString &pluginExecutable,
|
||||
break;
|
||||
|
||||
default:
|
||||
m_socket = accept( m_server, NULL, NULL );
|
||||
m_socket = accept( m_server, nullptr, nullptr );
|
||||
if ( m_socket == -1 )
|
||||
{
|
||||
qWarning( "Unexpected socket error." );
|
||||
@@ -264,6 +310,7 @@ bool RemotePlugin::init(const QString &pluginExecutable,
|
||||
}
|
||||
#endif
|
||||
|
||||
sendMessage(message(IdSyncKey).addString(Engine::getSong()->syncKey()));
|
||||
resizeSharedProcessingMemory();
|
||||
|
||||
if( waitForInitDoneMsg )
|
||||
@@ -278,44 +325,43 @@ bool RemotePlugin::init(const QString &pluginExecutable,
|
||||
|
||||
|
||||
|
||||
bool RemotePlugin::process( const sampleFrame * _in_buf,
|
||||
sampleFrame * _out_buf )
|
||||
bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf )
|
||||
{
|
||||
const fpp_t frames = Engine::mixer()->framesPerPeriod();
|
||||
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
|
||||
|
||||
if( m_failed || !isRunning() )
|
||||
{
|
||||
if( _out_buf != NULL )
|
||||
if( _out_buf != nullptr )
|
||||
{
|
||||
BufferManager::clear( _out_buf, frames );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if( m_shm == NULL )
|
||||
if (!m_audioBuffer)
|
||||
{
|
||||
// m_shm being zero means we didn't initialize everything so
|
||||
// m_audioBuffer being zero means we didn't initialize everything so
|
||||
// far so process one message each time (and hope we get
|
||||
// information like SHM-key etc.) until we process messages
|
||||
// in a later stage of this procedure
|
||||
if( m_shmSize == 0 )
|
||||
if( m_audioBufferSize == 0 )
|
||||
{
|
||||
lock();
|
||||
fetchAndProcessAllMessages();
|
||||
unlock();
|
||||
}
|
||||
if( _out_buf != NULL )
|
||||
if( _out_buf != nullptr )
|
||||
{
|
||||
BufferManager::clear( _out_buf, frames );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
memset( m_shm, 0, m_shmSize );
|
||||
memset( m_audioBuffer.get(), 0, m_audioBufferSize );
|
||||
|
||||
ch_cnt_t inputs = qMin<ch_cnt_t>( m_inputCount, DEFAULT_CHANNELS );
|
||||
|
||||
if( _in_buf != NULL && inputs > 0 )
|
||||
if( _in_buf != nullptr && inputs > 0 )
|
||||
{
|
||||
if( m_splitChannels )
|
||||
{
|
||||
@@ -323,18 +369,18 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
m_shm[ch * frames + frame] =
|
||||
m_audioBuffer[ch * frames + frame] =
|
||||
_in_buf[frame][ch];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( inputs == DEFAULT_CHANNELS )
|
||||
{
|
||||
memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME );
|
||||
memcpy( m_audioBuffer.get(), _in_buf, frames * BYTES_PER_FRAME );
|
||||
}
|
||||
else
|
||||
{
|
||||
sampleFrame * o = (sampleFrame *) m_shm;
|
||||
auto o = (sampleFrame*)m_audioBuffer.get();
|
||||
for( ch_cnt_t ch = 0; ch < inputs; ++ch )
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
@@ -348,7 +394,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
|
||||
lock();
|
||||
sendMessage( IdStartProcessing );
|
||||
|
||||
if( m_failed || _out_buf == NULL || m_outputCount == 0 )
|
||||
if( m_failed || _out_buf == nullptr || m_outputCount == 0 )
|
||||
{
|
||||
unlock();
|
||||
return false;
|
||||
@@ -365,20 +411,19 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
_out_buf[frame][ch] = m_shm[( m_inputCount+ch )*
|
||||
_out_buf[frame][ch] = m_audioBuffer[( m_inputCount+ch )*
|
||||
frames + frame];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( outputs == DEFAULT_CHANNELS )
|
||||
{
|
||||
memcpy( _out_buf, m_shm + m_inputCount * frames,
|
||||
memcpy( _out_buf, m_audioBuffer.get() + m_inputCount * frames,
|
||||
frames * BYTES_PER_FRAME );
|
||||
}
|
||||
else
|
||||
{
|
||||
sampleFrame * o = (sampleFrame *) ( m_shm +
|
||||
m_inputCount*frames );
|
||||
auto o = (sampleFrame*)(m_audioBuffer.get() + m_inputCount * frames);
|
||||
// clear buffer, if plugin didn't fill up both channels
|
||||
BufferManager::clear( _out_buf, frames );
|
||||
|
||||
@@ -431,39 +476,19 @@ void RemotePlugin::hideUI()
|
||||
|
||||
void RemotePlugin::resizeSharedProcessingMemory()
|
||||
{
|
||||
const size_t s = ( m_inputCount+m_outputCount ) *
|
||||
Engine::mixer()->framesPerPeriod() *
|
||||
sizeof( float );
|
||||
if( m_shm != NULL )
|
||||
const size_t s = (m_inputCount + m_outputCount) * Engine::audioEngine()->framesPerPeriod();
|
||||
try
|
||||
{
|
||||
#ifdef USE_QT_SHMEM
|
||||
m_shmObj.detach();
|
||||
#else
|
||||
shmdt( m_shm );
|
||||
shmctl( m_shmID, IPC_RMID, NULL );
|
||||
#endif
|
||||
m_audioBuffer.create(QUuid::createUuid().toString().toStdString(), s);
|
||||
}
|
||||
|
||||
static int shm_key = 0;
|
||||
#ifdef USE_QT_SHMEM
|
||||
do
|
||||
{
|
||||
m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) );
|
||||
m_shmObj.create( s );
|
||||
} while( m_shmObj.error() != QSharedMemory::NoError );
|
||||
|
||||
m_shm = (float *) m_shmObj.data();
|
||||
#else
|
||||
while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL |
|
||||
0600 ) ) == -1 )
|
||||
catch (const std::runtime_error& error)
|
||||
{
|
||||
qCritical() << "Failed to allocate shared audio buffer:" << error.what();
|
||||
m_audioBuffer.detach();
|
||||
return;
|
||||
}
|
||||
|
||||
m_shm = (float *) shmat( m_shmID, 0, 0 );
|
||||
#endif
|
||||
m_shmSize = s;
|
||||
sendMessage( message( IdChangeSharedMemoryKey ).
|
||||
addInt( shm_key ).addInt( m_shmSize ) );
|
||||
m_audioBufferSize = s * sizeof(float);
|
||||
sendMessage(message(IdChangeSharedMemoryKey).addString(m_audioBuffer.key()));
|
||||
}
|
||||
|
||||
|
||||
@@ -510,12 +535,12 @@ bool RemotePlugin::processMessage( const message & _m )
|
||||
|
||||
case IdSampleRateInformation:
|
||||
reply = true;
|
||||
reply_message.addInt( Engine::mixer()->processingSampleRate() );
|
||||
reply_message.addInt( Engine::audioEngine()->processingSampleRate() );
|
||||
break;
|
||||
|
||||
case IdBufferSizeInformation:
|
||||
reply = true;
|
||||
reply_message.addInt( Engine::mixer()->framesPerPeriod() );
|
||||
reply_message.addInt( Engine::audioEngine()->framesPerPeriod() );
|
||||
break;
|
||||
|
||||
case IdChangeInputCount:
|
||||
@@ -552,3 +577,6 @@ bool RemotePlugin::processMessage( const message & _m )
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -22,41 +22,43 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
#include "RenderManager.h"
|
||||
|
||||
#include "PatternStore.h"
|
||||
#include "Song.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "BBTrack.h"
|
||||
#include "stdshims.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
RenderManager::RenderManager(
|
||||
const Mixer::qualitySettings & qualitySettings,
|
||||
const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ProjectRenderer::ExportFileFormats fmt,
|
||||
QString outputPath) :
|
||||
m_qualitySettings(qualitySettings),
|
||||
m_oldQualitySettings( Engine::mixer()->currentQualitySettings() ),
|
||||
m_oldQualitySettings( Engine::audioEngine()->currentQualitySettings() ),
|
||||
m_outputSettings(outputSettings),
|
||||
m_format(fmt),
|
||||
m_outputPath(outputPath)
|
||||
{
|
||||
Engine::mixer()->storeAudioDevice();
|
||||
Engine::audioEngine()->storeAudioDevice();
|
||||
}
|
||||
|
||||
RenderManager::~RenderManager()
|
||||
{
|
||||
Engine::mixer()->restoreAudioDevice(); // Also deletes audio dev.
|
||||
Engine::mixer()->changeQuality( m_oldQualitySettings );
|
||||
Engine::audioEngine()->restoreAudioDevice(); // Also deletes audio dev.
|
||||
Engine::audioEngine()->changeQuality( m_oldQualitySettings );
|
||||
}
|
||||
|
||||
void RenderManager::abortProcessing()
|
||||
{
|
||||
if ( m_activeRenderer ) {
|
||||
disconnect( m_activeRenderer.get(), SIGNAL( finished() ),
|
||||
this, SLOT( renderNextTrack() ) );
|
||||
disconnect( m_activeRenderer.get(), SIGNAL(finished()),
|
||||
this, SLOT(renderNextTrack()));
|
||||
m_activeRenderer->abortProcessing();
|
||||
}
|
||||
restoreMutedState();
|
||||
@@ -80,9 +82,9 @@ void RenderManager::renderNextTrack()
|
||||
m_tracksToRender.pop_back();
|
||||
|
||||
// mute everything but the track we are about to render
|
||||
for( auto it = m_unmuted.begin(); it != m_unmuted.end(); ++it )
|
||||
for (auto track : m_unmuted)
|
||||
{
|
||||
(*it)->setMuted( (*it) != renderTrack );
|
||||
track->setMuted(track != renderTrack);
|
||||
}
|
||||
|
||||
// for multi-render, prefix each output file with a different number
|
||||
@@ -98,9 +100,8 @@ void RenderManager::renderTracks()
|
||||
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
|
||||
|
||||
// find all currently unnmuted tracks -- we want to render these.
|
||||
for( auto it = tl.begin(); it != tl.end(); ++it )
|
||||
for (const auto& tk : tl)
|
||||
{
|
||||
Track* tk = (*it);
|
||||
Track::TrackTypes type = tk->type();
|
||||
|
||||
// Don't render automation tracks
|
||||
@@ -111,10 +112,9 @@ void RenderManager::renderTracks()
|
||||
}
|
||||
}
|
||||
|
||||
const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks();
|
||||
for( auto it = t2.begin(); it != t2.end(); ++it )
|
||||
const TrackContainer::TrackList t2 = Engine::patternStore()->tracks();
|
||||
for (const auto& tk : t2)
|
||||
{
|
||||
Track* tk = (*it);
|
||||
Track::TrackTypes type = tk->type();
|
||||
|
||||
// Don't render automation tracks
|
||||
@@ -140,7 +140,7 @@ void RenderManager::renderProject()
|
||||
|
||||
void RenderManager::render(QString outputPath)
|
||||
{
|
||||
m_activeRenderer = make_unique<ProjectRenderer>(
|
||||
m_activeRenderer = std::make_unique<ProjectRenderer>(
|
||||
m_qualitySettings,
|
||||
m_outputSettings,
|
||||
m_format,
|
||||
@@ -149,13 +149,13 @@ void RenderManager::render(QString outputPath)
|
||||
if( m_activeRenderer->isReady() )
|
||||
{
|
||||
// pass progress signals through
|
||||
connect( m_activeRenderer.get(), SIGNAL( progressChanged( int ) ),
|
||||
this, SIGNAL( progressChanged( int ) ) );
|
||||
connect( m_activeRenderer.get(), SIGNAL(progressChanged(int)),
|
||||
this, SIGNAL(progressChanged(int)));
|
||||
|
||||
// when it is finished, render the next track.
|
||||
// if we have not queued any tracks, renderNextTrack will just clean up
|
||||
connect( m_activeRenderer.get(), SIGNAL( finished() ),
|
||||
this, SLOT( renderNextTrack() ) );
|
||||
connect( m_activeRenderer.get(), SIGNAL(finished()),
|
||||
this, SLOT(renderNextTrack()));
|
||||
|
||||
m_activeRenderer->startProcessing();
|
||||
}
|
||||
@@ -182,7 +182,7 @@ QString RenderManager::pathForTrack(const Track *track, int num)
|
||||
{
|
||||
QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
|
||||
QString name = track->name();
|
||||
name = name.remove(QRegExp("[^a-zA-Z]"));
|
||||
name = name.remove(QRegExp(FILENAME_FILTER));
|
||||
name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
|
||||
return QDir(m_outputPath).filePath(name);
|
||||
}
|
||||
@@ -202,3 +202,6 @@ void RenderManager::updateConsoleProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -24,14 +24,17 @@
|
||||
*/
|
||||
|
||||
#include "RingBuffer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixHelpers.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
RingBuffer::RingBuffer( f_cnt_t size ) :
|
||||
m_fpp( Engine::mixer()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::mixer()->processingSampleRate() ),
|
||||
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::audioEngine()->processingSampleRate() ),
|
||||
m_size( size + m_fpp )
|
||||
{
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
@@ -41,8 +44,8 @@ RingBuffer::RingBuffer( f_cnt_t size ) :
|
||||
|
||||
|
||||
RingBuffer::RingBuffer( float size ) :
|
||||
m_fpp( Engine::mixer()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::mixer()->processingSampleRate() )
|
||||
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::audioEngine()->processingSampleRate() )
|
||||
{
|
||||
m_size = msToFrames( size ) + m_fpp;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
@@ -88,11 +91,11 @@ void RingBuffer::setSamplerateAware( bool b )
|
||||
{
|
||||
if( b )
|
||||
{
|
||||
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ), Qt::UniqueConnection );
|
||||
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateSamplerate()), Qt::UniqueConnection );
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ) );
|
||||
disconnect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateSamplerate()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,9 +307,9 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset,
|
||||
|
||||
void RingBuffer::updateSamplerate()
|
||||
{
|
||||
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::mixer()->processingSampleRate() ) / m_samplerate;
|
||||
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->processingSampleRate() ) / m_samplerate;
|
||||
m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
|
||||
m_samplerate = Engine::mixer()->processingSampleRate();
|
||||
m_samplerate = Engine::audioEngine()->processingSampleRate();
|
||||
delete[] m_buffer;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
@@ -314,3 +317,4 @@ void RingBuffer::updateSamplerate()
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
330
src/core/SampleClip.cpp
Normal file
330
src/core/SampleClip.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* SampleClip.cpp
|
||||
*
|
||||
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 "SampleClip.h"
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "SampleBuffer.h"
|
||||
#include "SampleClipView.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "TimeLineWidget.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SampleClip::SampleClip( Track * _track ) :
|
||||
Clip( _track ),
|
||||
m_sampleBuffer( new SampleBuffer ),
|
||||
m_isPlaying( false )
|
||||
{
|
||||
saveJournallingState( false );
|
||||
setSampleFile( "" );
|
||||
restoreJournallingState();
|
||||
|
||||
// we need to receive bpm-change-events, because then we have to
|
||||
// change length of this Clip
|
||||
connect( Engine::getSong(), SIGNAL(tempoChanged(lmms::bpm_t)),
|
||||
this, SLOT(updateLength()), Qt::DirectConnection );
|
||||
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
|
||||
this, SLOT(updateLength()));
|
||||
|
||||
//care about positionmarker
|
||||
gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine;
|
||||
if( timeLine )
|
||||
{
|
||||
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()), Qt::DirectConnection );
|
||||
//care about loops
|
||||
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
//care about mute Clips
|
||||
connect( this, SIGNAL(dataChanged()), this, SLOT(playbackPositionChanged()));
|
||||
//care about mute track
|
||||
connect( getTrack()->getMutedModel(), SIGNAL(dataChanged()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
//care about Clip position
|
||||
connect( this, SIGNAL(positionChanged()), this, SLOT(updateTrackClips()));
|
||||
|
||||
switch( getTrack()->trackContainer()->type() )
|
||||
{
|
||||
case TrackContainer::PatternContainer:
|
||||
setAutoResize( true );
|
||||
break;
|
||||
|
||||
case TrackContainer::SongContainer:
|
||||
// move down
|
||||
default:
|
||||
setAutoResize( false );
|
||||
break;
|
||||
}
|
||||
updateTrackClips();
|
||||
}
|
||||
|
||||
SampleClip::SampleClip(const SampleClip& orig) :
|
||||
SampleClip(orig.getTrack())
|
||||
{
|
||||
// TODO: This creates a new SampleBuffer for the new Clip, eating up memory
|
||||
// & eventually causing performance issues. Letting tracks share buffers
|
||||
// when they're identical would fix this, but isn't possible right now.
|
||||
*m_sampleBuffer = *orig.m_sampleBuffer;
|
||||
m_isPlaying = orig.m_isPlaying;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SampleClip::~SampleClip()
|
||||
{
|
||||
auto sampletrack = dynamic_cast<SampleTrack*>(getTrack());
|
||||
if ( sampletrack )
|
||||
{
|
||||
sampletrack->updateClips();
|
||||
}
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::changeLength( const TimePos & _length )
|
||||
{
|
||||
Clip::changeLength( qMax( static_cast<int>( _length ), 1 ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const QString & SampleClip::sampleFile() const
|
||||
{
|
||||
return m_sampleBuffer->audioFile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleBuffer( SampleBuffer* sb )
|
||||
{
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
m_sampleBuffer = sb;
|
||||
updateLength();
|
||||
|
||||
emit sampleChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleFile( const QString & _sf )
|
||||
{
|
||||
int length;
|
||||
if ( _sf.isEmpty() )
|
||||
{ //When creating an empty sample clip make it a bar long
|
||||
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
|
||||
float den = Engine::getSong()->getTimeSigModel().getDenominator();
|
||||
length = DefaultTicksPerBar * ( nom / den );
|
||||
}
|
||||
else
|
||||
{ //Otherwise set it to the sample's length
|
||||
m_sampleBuffer->setAudioFile( _sf );
|
||||
length = sampleLength();
|
||||
}
|
||||
changeLength(length);
|
||||
|
||||
setStartTimeOffset( 0 );
|
||||
|
||||
emit sampleChanged();
|
||||
emit playbackPositionChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::toggleRecord()
|
||||
{
|
||||
m_recordModel.setValue( !m_recordModel.value() );
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::playbackPositionChanged()
|
||||
{
|
||||
Engine::audioEngine()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle );
|
||||
auto st = dynamic_cast<SampleTrack*>(getTrack());
|
||||
st->setPlayingClips( false );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::updateTrackClips()
|
||||
{
|
||||
auto sampletrack = dynamic_cast<SampleTrack*>(getTrack());
|
||||
if( sampletrack)
|
||||
{
|
||||
sampletrack->updateClips();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool SampleClip::isPlaying() const
|
||||
{
|
||||
return m_isPlaying;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::setIsPlaying(bool isPlaying)
|
||||
{
|
||||
m_isPlaying = isPlaying;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::updateLength()
|
||||
{
|
||||
emit sampleChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos SampleClip::sampleLength() const
|
||||
{
|
||||
return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleStartFrame(f_cnt_t startFrame)
|
||||
{
|
||||
m_sampleBuffer->setStartFrame( startFrame );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSamplePlayLength(f_cnt_t length)
|
||||
{
|
||||
m_sampleBuffer->setEndFrame( length );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
if( _this.parentNode().nodeName() == "clipboard" )
|
||||
{
|
||||
_this.setAttribute( "pos", -1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
_this.setAttribute( "pos", startPosition() );
|
||||
}
|
||||
_this.setAttribute( "len", length() );
|
||||
_this.setAttribute( "muted", isMuted() );
|
||||
_this.setAttribute( "src", sampleFile() );
|
||||
_this.setAttribute( "off", startTimeOffset() );
|
||||
if( sampleFile() == "" )
|
||||
{
|
||||
QString s;
|
||||
_this.setAttribute( "data", m_sampleBuffer->toBase64( s ) );
|
||||
}
|
||||
|
||||
_this.setAttribute( "sample_rate", m_sampleBuffer->sampleRate());
|
||||
if( usesCustomClipColor() )
|
||||
{
|
||||
_this.setAttribute( "color", color().name() );
|
||||
}
|
||||
if (m_sampleBuffer->reversed())
|
||||
{
|
||||
_this.setAttribute("reversed", "true");
|
||||
}
|
||||
// TODO: start- and end-frame
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
if( _this.attribute( "pos" ).toInt() >= 0 )
|
||||
{
|
||||
movePosition( _this.attribute( "pos" ).toInt() );
|
||||
}
|
||||
setSampleFile( _this.attribute( "src" ) );
|
||||
if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) )
|
||||
{
|
||||
m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) );
|
||||
}
|
||||
changeLength( _this.attribute( "len" ).toInt() );
|
||||
setMuted( _this.attribute( "muted" ).toInt() );
|
||||
setStartTimeOffset( _this.attribute( "off" ).toInt() );
|
||||
|
||||
if ( _this.hasAttribute( "sample_rate" ) ) {
|
||||
m_sampleBuffer->setSampleRate( _this.attribute( "sample_rate" ).toInt() );
|
||||
}
|
||||
|
||||
if( _this.hasAttribute( "color" ) )
|
||||
{
|
||||
useCustomClipColor( true );
|
||||
setColor( _this.attribute( "color" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
useCustomClipColor(false);
|
||||
}
|
||||
|
||||
if(_this.hasAttribute("reversed"))
|
||||
{
|
||||
m_sampleBuffer->setReversed(true);
|
||||
emit wasReversed(); // tell SampleClipView to update the view
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gui::ClipView * SampleClip::createView( gui::TrackView * _tv )
|
||||
{
|
||||
return new gui::SampleClipView( this, _tv );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -23,13 +23,16 @@
|
||||
*/
|
||||
|
||||
#include "SamplePlayHandle.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "AudioPort.h"
|
||||
#include "BBTrack.h"
|
||||
#include "Engine.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "Note.h"
|
||||
#include "PatternTrack.h"
|
||||
#include "SampleClip.h"
|
||||
#include "SampleTrack.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) :
|
||||
@@ -40,8 +43,8 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo
|
||||
m_ownAudioPort( ownAudioPort ),
|
||||
m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ),
|
||||
m_volumeModel( &m_defaultVolumeModel ),
|
||||
m_track( NULL ),
|
||||
m_bbTrack( NULL )
|
||||
m_track( nullptr ),
|
||||
m_patternTrack( nullptr )
|
||||
{
|
||||
if (ownAudioPort)
|
||||
{
|
||||
@@ -61,11 +64,11 @@ SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) :
|
||||
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) :
|
||||
SamplePlayHandle( tco->sampleBuffer() , false)
|
||||
SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) :
|
||||
SamplePlayHandle( clip->sampleBuffer() , false)
|
||||
{
|
||||
m_track = tco->getTrack();
|
||||
setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() );
|
||||
m_track = clip->getTrack();
|
||||
setAudioPort( ( (SampleTrack *)clip->getTrack() )->audioPort() );
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +88,7 @@ SamplePlayHandle::~SamplePlayHandle()
|
||||
|
||||
void SamplePlayHandle::play( sampleFrame * buffer )
|
||||
{
|
||||
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
|
||||
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();
|
||||
//play( 0, _try_parallelizing );
|
||||
if( framesDone() >= totalFrames() )
|
||||
{
|
||||
@@ -105,15 +108,16 @@ void SamplePlayHandle::play( sampleFrame * buffer )
|
||||
}
|
||||
|
||||
if( !( m_track && m_track->isMuted() )
|
||||
&& !( m_bbTrack && m_bbTrack->isMuted() ) )
|
||||
&& !(m_patternTrack && m_patternTrack->isMuted()))
|
||||
{
|
||||
/* stereoVolumeVector v =
|
||||
/* StereoVolumeVector v =
|
||||
{ { m_volumeModel->value() / DefaultVolume,
|
||||
m_volumeModel->value() / DefaultVolume } };*/
|
||||
if( ! m_sampleBuffer->play( workingBuffer, &m_state, frames,
|
||||
BaseFreq ) )
|
||||
// SamplePlayHandle always plays the sample at its original pitch;
|
||||
// it is used only for previews, SampleTracks and the metronome.
|
||||
if (!m_sampleBuffer->play(workingBuffer, &m_state, frames, DefaultBaseFreq))
|
||||
{
|
||||
memset( workingBuffer, 0, frames * sizeof( sampleFrame ) );
|
||||
memset(workingBuffer, 0, frames * sizeof(sampleFrame));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +137,7 @@ bool SamplePlayHandle::isFinished() const
|
||||
|
||||
bool SamplePlayHandle::isFromTrack( const Track * _track ) const
|
||||
{
|
||||
return m_track == _track || m_bbTrack == _track;
|
||||
return m_track == _track || m_patternTrack == _track;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,9 +145,9 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const
|
||||
|
||||
f_cnt_t SamplePlayHandle::totalFrames() const
|
||||
{
|
||||
return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / m_sampleBuffer->sampleRate() );
|
||||
return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) *
|
||||
( Engine::audioEngine()->processingSampleRate() / m_sampleBuffer->sampleRate() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,22 +24,25 @@
|
||||
|
||||
|
||||
#include "SampleRecordHandle.h"
|
||||
#include "BBTrack.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "PatternTrack.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "SampleClip.h"
|
||||
#include "debug.h"
|
||||
|
||||
|
||||
SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) :
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SampleRecordHandle::SampleRecordHandle( SampleClip* clip ) :
|
||||
PlayHandle( TypeSamplePlayHandle ),
|
||||
m_framesRecorded( 0 ),
|
||||
m_minLength( tco->length() ),
|
||||
m_track( tco->getTrack() ),
|
||||
m_bbTrack( NULL ),
|
||||
m_tco( tco )
|
||||
m_minLength( clip->length() ),
|
||||
m_track( clip->getTrack() ),
|
||||
m_patternTrack( nullptr ),
|
||||
m_clip( clip )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,7 +55,7 @@ SampleRecordHandle::~SampleRecordHandle()
|
||||
{
|
||||
SampleBuffer* sb;
|
||||
createSampleBuffer( &sb );
|
||||
m_tco->setSampleBuffer( sb );
|
||||
m_clip->setSampleBuffer( sb );
|
||||
}
|
||||
|
||||
while( !m_buffers.empty() )
|
||||
@@ -60,7 +63,7 @@ SampleRecordHandle::~SampleRecordHandle()
|
||||
delete[] m_buffers.front().first;
|
||||
m_buffers.erase( m_buffers.begin() );
|
||||
}
|
||||
m_tco->setRecord( false );
|
||||
m_clip->setRecord( false );
|
||||
}
|
||||
|
||||
|
||||
@@ -68,15 +71,15 @@ SampleRecordHandle::~SampleRecordHandle()
|
||||
|
||||
void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ )
|
||||
{
|
||||
const sampleFrame * recbuf = Engine::mixer()->inputBuffer();
|
||||
const f_cnt_t frames = Engine::mixer()->inputBufferFrames();
|
||||
const sampleFrame * recbuf = Engine::audioEngine()->inputBuffer();
|
||||
const f_cnt_t frames = Engine::audioEngine()->inputBufferFrames();
|
||||
writeBuffer( recbuf, frames );
|
||||
m_framesRecorded += frames;
|
||||
|
||||
MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() );
|
||||
TimePos len = (tick_t)( m_framesRecorded / Engine::framesPerTick() );
|
||||
if( len > m_minLength )
|
||||
{
|
||||
// m_tco->changeLength( len );
|
||||
// m_clip->changeLength( len );
|
||||
m_minLength = len;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +97,7 @@ bool SampleRecordHandle::isFinished() const
|
||||
|
||||
bool SampleRecordHandle::isFromTrack( const Track * _track ) const
|
||||
{
|
||||
return( m_track == _track || m_bbTrack == _track );
|
||||
return (m_track == _track || m_patternTrack == _track);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,16 +115,15 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
|
||||
{
|
||||
const f_cnt_t frames = framesRecorded();
|
||||
// create buffer to store all recorded buffers in
|
||||
sampleFrame * data = new sampleFrame[frames];
|
||||
auto data = new sampleFrame[frames];
|
||||
// make sure buffer is cleaned up properly at the end...
|
||||
sampleFrame * data_ptr = data;
|
||||
|
||||
|
||||
assert( data != NULL );
|
||||
assert( data != nullptr );
|
||||
|
||||
// now copy all buffers into big buffer
|
||||
for( bufferList::const_iterator it = m_buffers.begin();
|
||||
it != m_buffers.end(); ++it )
|
||||
for( bufferList::const_iterator it = m_buffers.begin(); it != m_buffers.end(); ++it )
|
||||
{
|
||||
memcpy( data_ptr, ( *it ).first, ( *it ).second *
|
||||
sizeof( sampleFrame ) );
|
||||
@@ -129,17 +131,16 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
|
||||
}
|
||||
// create according sample-buffer out of big buffer
|
||||
*sampleBuf = new SampleBuffer( data, frames );
|
||||
( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() );
|
||||
( *sampleBuf)->setSampleRate( Engine::audioEngine()->inputSampleRate() );
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
|
||||
const f_cnt_t _frames )
|
||||
void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames )
|
||||
{
|
||||
sampleFrame * buf = new sampleFrame[_frames];
|
||||
auto buf = new sampleFrame[_frames];
|
||||
for( f_cnt_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
|
||||
@@ -151,4 +152,4 @@ void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
129
src/core/Scale.cpp
Normal file
129
src/core/Scale.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Scale.cpp - implementation of scale class
|
||||
*
|
||||
* Copyright (c) 2020 Martin Pavelek <he29.HS/at/gmail.com>
|
||||
*
|
||||
* 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 "Scale.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QDomElement>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Interval::Interval(float cents) :
|
||||
m_numerator(0),
|
||||
m_denominator(0),
|
||||
m_cents(cents)
|
||||
{
|
||||
m_ratio = powf(2.f, m_cents / 1200.f);
|
||||
}
|
||||
|
||||
Interval::Interval(uint32_t numerator, uint32_t denominator) :
|
||||
m_numerator(numerator),
|
||||
m_denominator(denominator > 0 ? denominator : 1),
|
||||
m_cents(0)
|
||||
{
|
||||
m_ratio = static_cast<float>(m_numerator) / m_denominator;
|
||||
}
|
||||
|
||||
|
||||
void Interval::saveSettings(QDomDocument &document, QDomElement &element)
|
||||
{
|
||||
if (m_denominator > 0)
|
||||
{
|
||||
element.setAttribute("num", QString::number(m_numerator));
|
||||
element.setAttribute("den", QString::number(m_denominator));
|
||||
}
|
||||
else
|
||||
{
|
||||
element.setAttribute("cents", QString::number(m_cents));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Interval::loadSettings(const QDomElement &element)
|
||||
{
|
||||
m_numerator = element.attribute("num", "0").toULong();
|
||||
m_denominator = element.attribute("den", "0").toULong();
|
||||
m_cents = element.attribute("cents", "0").toDouble();
|
||||
if (m_denominator) {m_ratio = static_cast<float>(m_numerator) / m_denominator;}
|
||||
else {m_ratio = powf(2.f, m_cents / 1200.f);}
|
||||
}
|
||||
|
||||
|
||||
Scale::Scale() :
|
||||
m_description(tr("empty"))
|
||||
{
|
||||
m_intervals.emplace_back(1, 1);
|
||||
}
|
||||
|
||||
Scale::Scale(QString description, std::vector<Interval> intervals) :
|
||||
m_description(description),
|
||||
m_intervals(std::move(intervals))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QString Scale::getDescription() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
|
||||
void Scale::setDescription(QString description)
|
||||
{
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
|
||||
void Scale::saveSettings(QDomDocument &document, QDomElement &element)
|
||||
{
|
||||
element.setAttribute("description", m_description);
|
||||
|
||||
for (auto& interval : m_intervals)
|
||||
{
|
||||
interval.saveState(document, element);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Scale::loadSettings(const QDomElement &element)
|
||||
{
|
||||
m_description = element.attribute("description");
|
||||
|
||||
QDomNode node = element.firstChild();
|
||||
m_intervals.clear();
|
||||
|
||||
for (int i = 0; !node.isNull(); i++)
|
||||
{
|
||||
Interval temp;
|
||||
temp.restoreState(node.toElement());
|
||||
m_intervals.push_back(temp);
|
||||
node = node.nextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -26,10 +26,11 @@
|
||||
|
||||
#include "SerializingObject.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
SerializingObject::SerializingObject() :
|
||||
m_hook( NULL )
|
||||
m_hook( nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ SerializingObject::~SerializingObject()
|
||||
{
|
||||
if( m_hook )
|
||||
{
|
||||
m_hook->m_hookedIn = NULL;
|
||||
m_hook->m_hookedIn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ void SerializingObject::setHook( SerializingObjectHook* hook )
|
||||
{
|
||||
if( m_hook )
|
||||
{
|
||||
m_hook->m_hookedIn = NULL;
|
||||
m_hook->m_hookedIn = nullptr;
|
||||
}
|
||||
|
||||
m_hook = hook;
|
||||
@@ -110,4 +111,4 @@ void SerializingObject::loadSettings( const QDomElement& element )
|
||||
Q_UNUSED(element)
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,20 +19,27 @@
|
||||
*/
|
||||
|
||||
#include "StepRecorder.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "MidiClip.h"
|
||||
#include "StepRecorderWidget.h"
|
||||
#include "PianoRoll.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <climits>
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
using std::min;
|
||||
using std::max;
|
||||
|
||||
const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70;
|
||||
|
||||
StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget):
|
||||
StepRecorder::StepRecorder(gui::PianoRoll& pianoRoll, gui::StepRecorderWidget& stepRecorderWidget):
|
||||
m_pianoRoll(pianoRoll),
|
||||
m_stepRecorderWidget(stepRecorderWidget)
|
||||
m_stepRecorderWidget(stepRecorderWidget),
|
||||
m_midiClip(nullptr)
|
||||
{
|
||||
m_stepRecorderWidget.hide();
|
||||
}
|
||||
@@ -42,7 +49,7 @@ void StepRecorder::initialize()
|
||||
connect(&m_updateReleasedTimer, SIGNAL(timeout()), this, SLOT(removeNotesReleasedForTooLong()));
|
||||
}
|
||||
|
||||
void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength)
|
||||
void StepRecorder::start(const TimePos& currentPosition, const TimePos& stepLength)
|
||||
{
|
||||
m_isRecording = true;
|
||||
|
||||
@@ -52,7 +59,7 @@ void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLe
|
||||
const int q = m_pianoRoll.quantization();
|
||||
const int curPosTicks = currentPosition.getTicks();
|
||||
const int QuantizedPosTicks = (curPosTicks / q) * q;
|
||||
const MidiTime& QuantizedPos = MidiTime(QuantizedPosTicks);
|
||||
const TimePos& QuantizedPos = TimePos(QuantizedPosTicks);
|
||||
|
||||
m_curStepStartPos = QuantizedPos;
|
||||
m_curStepLength = 0;
|
||||
@@ -108,7 +115,7 @@ void StepRecorder::noteReleased(const Note & n)
|
||||
m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS);
|
||||
}
|
||||
|
||||
//check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step
|
||||
//check if all note are released, apply notes to clips (or dimiss if length is zero) and prepare to record next step
|
||||
if(allCurStepNotesReleased())
|
||||
{
|
||||
if(m_curStepLength > 0)
|
||||
@@ -153,7 +160,7 @@ bool StepRecorder::keyPressEvent(QKeyEvent* ke)
|
||||
return event_handled;
|
||||
}
|
||||
|
||||
void StepRecorder::setStepsLength(const MidiTime& newLength)
|
||||
void StepRecorder::setStepsLength(const TimePos& newLength)
|
||||
{
|
||||
if(m_isStepInProgress)
|
||||
{
|
||||
@@ -225,16 +232,16 @@ void StepRecorder::stepBackwards()
|
||||
|
||||
void StepRecorder::applyStep()
|
||||
{
|
||||
m_pattern->addJournalCheckPoint();
|
||||
m_midiClip->addJournalCheckPoint();
|
||||
|
||||
for (const StepNote* stepNote : m_curStepNotes)
|
||||
{
|
||||
m_pattern->addNote(stepNote->m_note, false);
|
||||
m_midiClip->addNote(stepNote->m_note, false);
|
||||
}
|
||||
|
||||
m_pattern->rearrangeAllNotes();
|
||||
m_pattern->updateLength();
|
||||
m_pattern->dataChanged();
|
||||
m_midiClip->rearrangeAllNotes();
|
||||
m_midiClip->updateLength();
|
||||
m_midiClip->dataChanged();
|
||||
Engine::getSong()->setModified();
|
||||
|
||||
prepareNewStep();
|
||||
@@ -266,14 +273,14 @@ void StepRecorder::prepareNewStep()
|
||||
updateWidget();
|
||||
}
|
||||
|
||||
void StepRecorder::setCurrentPattern( Pattern* newPattern )
|
||||
void StepRecorder::setCurrentMidiClip( MidiClip* newMidiClip )
|
||||
{
|
||||
if(m_pattern != NULL && m_pattern != newPattern)
|
||||
if(m_midiClip != nullptr && m_midiClip != newMidiClip)
|
||||
{
|
||||
dismissStep();
|
||||
}
|
||||
|
||||
m_pattern = newPattern;
|
||||
m_midiClip = newMidiClip;
|
||||
}
|
||||
|
||||
void StepRecorder::removeNotesReleasedForTooLong()
|
||||
@@ -318,7 +325,7 @@ void StepRecorder::removeNotesReleasedForTooLong()
|
||||
}
|
||||
}
|
||||
|
||||
MidiTime StepRecorder::getCurStepEndPos()
|
||||
TimePos StepRecorder::getCurStepEndPos()
|
||||
{
|
||||
return m_curStepStartPos + m_curStepLength;
|
||||
}
|
||||
@@ -364,3 +371,5 @@ StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -31,6 +31,10 @@
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
|
||||
const float _max, const float _step,
|
||||
const float _scale, Model * _parent,
|
||||
@@ -41,21 +45,14 @@ TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
|
||||
m_scale( _scale ),
|
||||
m_custom( _parent )
|
||||
{
|
||||
connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ),
|
||||
this, SLOT( calculateTempoSyncTime( bpm_t ) ),
|
||||
connect( Engine::getSong(), SIGNAL(tempoChanged(lmms::bpm_t)),
|
||||
this, SLOT(calculateTempoSyncTime(lmms::bpm_t)),
|
||||
Qt::DirectConnection );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TempoSyncKnobModel::~TempoSyncKnobModel()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TempoSyncKnobModel::setTempoSync( QAction * _item )
|
||||
{
|
||||
setTempoSync( _item->data().toInt() );
|
||||
@@ -154,8 +151,8 @@ void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode )
|
||||
m_tempoSyncMode = _new_mode;
|
||||
if( _new_mode == SyncCustom )
|
||||
{
|
||||
connect( &m_custom, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateCustom() ),
|
||||
connect( &m_custom, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateCustom()),
|
||||
Qt::DirectConnection );
|
||||
}
|
||||
}
|
||||
@@ -181,8 +178,4 @@ void TempoSyncKnobModel::updateCustom()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
224
src/core/TimePos.cpp
Normal file
224
src/core/TimePos.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* TimePos.cpp - Class that encapsulates the position of a note/event in terms of
|
||||
* its bar, beat and tick.
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net
|
||||
*
|
||||
* 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 "TimePos.h"
|
||||
|
||||
#include "MeterModel.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
TimeSig::TimeSig( int num, int denom ) :
|
||||
m_num(num),
|
||||
m_denom(denom)
|
||||
{
|
||||
}
|
||||
|
||||
TimeSig::TimeSig( const MeterModel &model ) :
|
||||
m_num(model.getNumerator()),
|
||||
m_denom(model.getDenominator())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int TimeSig::numerator() const
|
||||
{
|
||||
return m_num;
|
||||
}
|
||||
|
||||
int TimeSig::denominator() const
|
||||
{
|
||||
return m_denom;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos::TimePos( const bar_t bar, const tick_t ticks ) :
|
||||
m_ticks( bar * s_ticksPerBar + ticks )
|
||||
{
|
||||
}
|
||||
|
||||
TimePos::TimePos( const tick_t ticks ) :
|
||||
m_ticks( ticks )
|
||||
{
|
||||
}
|
||||
|
||||
TimePos TimePos::quantize(float bars) const
|
||||
{
|
||||
//The intervals we should snap to, our new position should be a factor of this
|
||||
int interval = s_ticksPerBar * bars;
|
||||
//The lower position we could snap to
|
||||
int lowPos = m_ticks / interval;
|
||||
//Offset from the lower position
|
||||
int offset = m_ticks % interval;
|
||||
//1 if we should snap up, 0 if we shouldn't
|
||||
// Ternary expression is making sure that the snap happens in the direction to
|
||||
// the right even if m_ticks is negative and the offset is exactly half-way
|
||||
// More details on issue #5840 and PR #5847
|
||||
int snapUp = ((2 * offset) == -interval)
|
||||
? 0
|
||||
: (2 * offset) / interval;
|
||||
|
||||
return (lowPos + snapUp) * interval;
|
||||
}
|
||||
|
||||
|
||||
TimePos TimePos::toAbsoluteBar() const
|
||||
{
|
||||
return getBar() * s_ticksPerBar;
|
||||
}
|
||||
|
||||
|
||||
TimePos& TimePos::operator+=( const TimePos& time )
|
||||
{
|
||||
m_ticks += time.m_ticks;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
TimePos& TimePos::operator-=( const TimePos& time )
|
||||
{
|
||||
m_ticks -= time.m_ticks;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bar_t TimePos::getBar() const
|
||||
{
|
||||
return m_ticks / s_ticksPerBar;
|
||||
}
|
||||
|
||||
|
||||
bar_t TimePos::nextFullBar() const
|
||||
{
|
||||
return ( m_ticks + ( s_ticksPerBar - 1 ) ) / s_ticksPerBar;
|
||||
}
|
||||
|
||||
|
||||
void TimePos::setTicks( tick_t ticks )
|
||||
{
|
||||
m_ticks = ticks;
|
||||
}
|
||||
|
||||
|
||||
tick_t TimePos::getTicks() const
|
||||
{
|
||||
return m_ticks;
|
||||
}
|
||||
|
||||
|
||||
TimePos::operator int() const
|
||||
{
|
||||
return m_ticks;
|
||||
}
|
||||
|
||||
|
||||
tick_t TimePos::ticksPerBeat( const TimeSig &sig ) const
|
||||
{
|
||||
// (number of ticks per bar) divided by (number of beats per bar)
|
||||
return ticksPerBar(sig) / sig.numerator();
|
||||
}
|
||||
|
||||
|
||||
tick_t TimePos::getTickWithinBar( const TimeSig &sig ) const
|
||||
{
|
||||
return m_ticks % ticksPerBar( sig );
|
||||
}
|
||||
|
||||
tick_t TimePos::getBeatWithinBar( const TimeSig &sig ) const
|
||||
{
|
||||
return getTickWithinBar( sig ) / ticksPerBeat( sig );
|
||||
}
|
||||
|
||||
tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const
|
||||
{
|
||||
return getTickWithinBar( sig ) % ticksPerBeat( sig );
|
||||
}
|
||||
|
||||
|
||||
f_cnt_t TimePos::frames( const float framesPerTick ) const
|
||||
{
|
||||
if( m_ticks >= 0 )
|
||||
{
|
||||
return static_cast<f_cnt_t>( m_ticks * framesPerTick );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const
|
||||
{
|
||||
return ticksToMilliseconds( getTicks(), beatsPerMinute );
|
||||
}
|
||||
|
||||
TimePos TimePos::fromFrames( const f_cnt_t frames, const float framesPerTick )
|
||||
{
|
||||
return TimePos( static_cast<int>( frames / framesPerTick ) );
|
||||
}
|
||||
|
||||
|
||||
tick_t TimePos::ticksPerBar()
|
||||
{
|
||||
return s_ticksPerBar;
|
||||
}
|
||||
|
||||
|
||||
tick_t TimePos::ticksPerBar( const TimeSig &sig )
|
||||
{
|
||||
return DefaultTicksPerBar * sig.numerator() / sig.denominator();
|
||||
}
|
||||
|
||||
|
||||
int TimePos::stepsPerBar()
|
||||
{
|
||||
int steps = ticksPerBar() / DefaultBeatsPerBar;
|
||||
return qMax( 1, steps );
|
||||
}
|
||||
|
||||
|
||||
void TimePos::setTicksPerBar( tick_t tpb )
|
||||
{
|
||||
s_ticksPerBar = tpb;
|
||||
}
|
||||
|
||||
|
||||
TimePos TimePos::stepPosition( int step )
|
||||
{
|
||||
return step * ticksPerBar() / stepsPerBar();
|
||||
}
|
||||
|
||||
double TimePos::ticksToMilliseconds( tick_t ticks, bpm_t beatsPerMinute )
|
||||
{
|
||||
return TimePos::ticksToMilliseconds( static_cast<double>(ticks), beatsPerMinute );
|
||||
}
|
||||
|
||||
double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute)
|
||||
{
|
||||
// 60 * 1000 / 48 = 1250
|
||||
return ( ticks * 1250 ) / beatsPerMinute;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -26,6 +26,10 @@
|
||||
#include "ToolPlugin.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
ToolPlugin::ToolPlugin( const Descriptor * _descriptor, Model * _parent ) :
|
||||
Plugin( _descriptor, _parent )
|
||||
{
|
||||
@@ -34,16 +38,9 @@ ToolPlugin::ToolPlugin( const Descriptor * _descriptor, Model * _parent ) :
|
||||
|
||||
|
||||
|
||||
ToolPlugin::~ToolPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _parent )
|
||||
{
|
||||
Plugin * p = Plugin::instantiate( _plugin_name, _parent, NULL );
|
||||
Plugin * p = Plugin::instantiate( _plugin_name, _parent, nullptr );
|
||||
// check whether instantiated plugin is a tool
|
||||
if( p->type() == Plugin::Tool )
|
||||
{
|
||||
@@ -53,6 +50,8 @@ ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _par
|
||||
|
||||
// not quite... so delete plugin
|
||||
delete p;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
2798
src/core/Track.cpp
2798
src/core/Track.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* TrackContainer.cpp - implementation of base class for all trackcontainers
|
||||
* like Song-Editor, BB-Editor...
|
||||
* like Song-Editor, Pattern Editor...
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
@@ -24,26 +24,29 @@
|
||||
*/
|
||||
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QProgressDialog>
|
||||
#include <QDomElement>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "AutomationPattern.h"
|
||||
#include "AutomationTrack.h"
|
||||
#include "BBTrack.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "AutomationClip.h"
|
||||
#include "embed.h"
|
||||
#include "TrackContainer.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "PatternClip.h"
|
||||
#include "PatternStore.h"
|
||||
#include "PatternTrack.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include "GuiApplication.h"
|
||||
#include "MainWindow.h"
|
||||
#include "TextFloat.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
TrackContainer::TrackContainer() :
|
||||
Model( NULL ),
|
||||
Model( nullptr ),
|
||||
JournallingObject(),
|
||||
m_tracksMutex(),
|
||||
m_tracks()
|
||||
@@ -68,9 +71,9 @@ void TrackContainer::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
|
||||
// save settings of each track
|
||||
m_tracksMutex.lockForRead();
|
||||
for( int i = 0; i < m_tracks.size(); ++i )
|
||||
for (const auto& track : m_tracks)
|
||||
{
|
||||
m_tracks[i]->saveState( _doc, _this );
|
||||
track->saveState(_doc, _this);
|
||||
}
|
||||
m_tracksMutex.unlock();
|
||||
}
|
||||
@@ -86,16 +89,16 @@ void TrackContainer::loadSettings( const QDomElement & _this )
|
||||
clearAllTracks();
|
||||
}
|
||||
|
||||
static QProgressDialog * pd = NULL;
|
||||
bool was_null = ( pd == NULL );
|
||||
if( !journalRestore && gui != nullptr )
|
||||
static QProgressDialog * pd = nullptr;
|
||||
bool was_null = ( pd == nullptr );
|
||||
if (!journalRestore && gui::getGUI() != nullptr)
|
||||
{
|
||||
if( pd == NULL )
|
||||
if( pd == nullptr )
|
||||
{
|
||||
pd = new QProgressDialog( tr( "Loading project..." ),
|
||||
tr( "Cancel" ), 0,
|
||||
Engine::getSong()->getLoadingTrackCount(),
|
||||
gui->mainWindow() );
|
||||
gui::getGUI()->mainWindow());
|
||||
pd->setWindowModality( Qt::ApplicationModal );
|
||||
pd->setWindowTitle( tr( "Please wait..." ) );
|
||||
pd->show();
|
||||
@@ -105,16 +108,16 @@ void TrackContainer::loadSettings( const QDomElement & _this )
|
||||
QDomNode node = _this.firstChild();
|
||||
while( !node.isNull() )
|
||||
{
|
||||
if( pd != NULL )
|
||||
if( pd != nullptr )
|
||||
{
|
||||
pd->setValue( pd->value() + 1 );
|
||||
QCoreApplication::instance()->processEvents(
|
||||
QEventLoop::AllEvents, 100 );
|
||||
if( pd->wasCanceled() )
|
||||
{
|
||||
if ( gui )
|
||||
if (gui::getGUI() != nullptr)
|
||||
{
|
||||
TextFloat::displayMessage( tr( "Loading cancelled" ),
|
||||
gui::TextFloat::displayMessage( tr( "Loading cancelled" ),
|
||||
tr( "Project loading was cancelled." ),
|
||||
embed::getIconPixmap( "project_file", 24, 24 ),
|
||||
2000 );
|
||||
@@ -130,7 +133,7 @@ void TrackContainer::loadSettings( const QDomElement & _this )
|
||||
QString trackName = node.toElement().hasAttribute( "name" ) ?
|
||||
node.toElement().attribute( "name" ) :
|
||||
node.firstChild().toElement().attribute( "name" );
|
||||
if( pd != NULL )
|
||||
if( pd != nullptr )
|
||||
{
|
||||
pd->setLabelText( tr("Loading Track %1 (%2/Total %3)").arg( trackName ).
|
||||
arg( pd->value() + 1 ).arg( Engine::getSong()->getLoadingTrackCount() ) );
|
||||
@@ -140,12 +143,12 @@ void TrackContainer::loadSettings( const QDomElement & _this )
|
||||
node = node.nextSibling();
|
||||
}
|
||||
|
||||
if( pd != NULL )
|
||||
if( pd != nullptr )
|
||||
{
|
||||
if( was_null )
|
||||
{
|
||||
delete pd;
|
||||
pd = NULL;
|
||||
pd = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,9 +160,9 @@ int TrackContainer::countTracks( Track::TrackTypes _tt ) const
|
||||
{
|
||||
int cnt = 0;
|
||||
m_tracksMutex.lockForRead();
|
||||
for( int i = 0; i < m_tracks.size(); ++i )
|
||||
for (const auto& track : m_tracks)
|
||||
{
|
||||
if( m_tracks[i]->type() == _tt || _tt == Track::NumTrackTypes )
|
||||
if (track->type() == _tt || _tt == Track::NumTrackTypes)
|
||||
{
|
||||
++cnt;
|
||||
}
|
||||
@@ -235,10 +238,9 @@ void TrackContainer::clearAllTracks()
|
||||
|
||||
bool TrackContainer::isEmpty() const
|
||||
{
|
||||
for( TrackList::const_iterator it = m_tracks.begin();
|
||||
it != m_tracks.end(); ++it )
|
||||
for (const auto& track : m_tracks)
|
||||
{
|
||||
if( !( *it )->getTCOs().isEmpty() )
|
||||
if (!track->getClips().isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -248,15 +250,15 @@ bool TrackContainer::isEmpty() const
|
||||
|
||||
|
||||
|
||||
AutomatedValueMap TrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const
|
||||
AutomatedValueMap TrackContainer::automatedValuesAt(TimePos time, int clipNum) const
|
||||
{
|
||||
return automatedValuesFromTracks(tracks(), time, tcoNum);
|
||||
return automatedValuesFromTracks(tracks(), time, clipNum);
|
||||
}
|
||||
|
||||
|
||||
AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, MidiTime time, int tcoNum)
|
||||
AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, TimePos time, int clipNum)
|
||||
{
|
||||
Track::tcoVector tcos;
|
||||
Track::clipVector clips;
|
||||
|
||||
for (Track* track: tracks)
|
||||
{
|
||||
@@ -268,12 +270,12 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
|
||||
{
|
||||
case Track::AutomationTrack:
|
||||
case Track::HiddenAutomationTrack:
|
||||
case Track::BBTrack:
|
||||
if (tcoNum < 0) {
|
||||
track->getTCOsInRange(tcos, 0, time);
|
||||
case Track::PatternTrack:
|
||||
if (clipNum < 0) {
|
||||
track->getClipsInRange(clips, 0, time);
|
||||
} else {
|
||||
Q_ASSERT(track->numOfTCOs() > tcoNum);
|
||||
tcos << track->getTCO(tcoNum);
|
||||
Q_ASSERT(track->numOfClips() > clipNum);
|
||||
clips << track->getClip(clipNum);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -282,20 +284,20 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
|
||||
|
||||
AutomatedValueMap valueMap;
|
||||
|
||||
Q_ASSERT(std::is_sorted(tcos.begin(), tcos.end(), TrackContentObject::comparePosition));
|
||||
Q_ASSERT(std::is_sorted(clips.begin(), clips.end(), Clip::comparePosition));
|
||||
|
||||
for(TrackContentObject* tco : tcos)
|
||||
for(Clip* clip : clips)
|
||||
{
|
||||
if (tco->isMuted() || tco->startPosition() > time) {
|
||||
if (clip->isMuted() || clip->startPosition() > time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* p = dynamic_cast<AutomationPattern *>(tco))
|
||||
if (auto* p = dynamic_cast<AutomationClip *>(clip))
|
||||
{
|
||||
if (! p->hasAutomation()) {
|
||||
continue;
|
||||
}
|
||||
MidiTime relTime = time - p->startPosition();
|
||||
TimePos relTime = time - p->startPosition();
|
||||
if (! p->getAutoResize()) {
|
||||
relTime = qMin(relTime, p->length());
|
||||
}
|
||||
@@ -306,19 +308,19 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
|
||||
valueMap[model] = value;
|
||||
}
|
||||
}
|
||||
else if (auto* bb = dynamic_cast<BBTCO *>(tco))
|
||||
else if (auto* pattern = dynamic_cast<PatternClip*>(clip))
|
||||
{
|
||||
auto bbIndex = dynamic_cast<class BBTrack*>(bb->getTrack())->index();
|
||||
auto bbContainer = Engine::getBBTrackContainer();
|
||||
auto patIndex = dynamic_cast<class PatternTrack*>(pattern->getTrack())->patternIndex();
|
||||
auto patStore = Engine::patternStore();
|
||||
|
||||
MidiTime bbTime = time - tco->startPosition();
|
||||
bbTime = std::min(bbTime, tco->length());
|
||||
bbTime = bbTime % (bbContainer->lengthOfBB(bbIndex) * MidiTime::ticksPerTact());
|
||||
TimePos patTime = time - clip->startPosition();
|
||||
patTime = std::min(patTime, clip->length());
|
||||
patTime = patTime % (patStore->lengthOfPattern(patIndex) * TimePos::ticksPerBar());
|
||||
|
||||
auto bbValues = bbContainer->automatedValuesAt(bbTime, bbIndex);
|
||||
for (auto it=bbValues.begin(); it != bbValues.end(); it++)
|
||||
auto patValues = patStore->automatedValuesAt(patTime, patIndex);
|
||||
for (auto it=patValues.begin(); it != patValues.end(); it++)
|
||||
{
|
||||
// override old values, bb track with the highest index takes precedence
|
||||
// override old values, pattern track with the highest index takes precedence
|
||||
valueMap[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
@@ -332,20 +334,4 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
|
||||
};
|
||||
|
||||
|
||||
|
||||
DummyTrackContainer::DummyTrackContainer() :
|
||||
TrackContainer(),
|
||||
m_dummyInstrumentTrack( NULL )
|
||||
{
|
||||
setJournalling( false );
|
||||
m_dummyInstrumentTrack = dynamic_cast<InstrumentTrack *>(
|
||||
Track::create( Track::InstrumentTrack,
|
||||
this ) );
|
||||
m_dummyInstrumentTrack->setJournalling( false );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
#include "interpolation.h"
|
||||
|
||||
ValueBuffer::ValueBuffer()
|
||||
{}
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
ValueBuffer::ValueBuffer(int length)
|
||||
: std::vector<float>(length)
|
||||
@@ -41,3 +42,6 @@ void ValueBuffer::interpolate(float start, float end_)
|
||||
return linearInterpolate( start, end_, i++ / length());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -23,121 +23,50 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include "VstSyncController.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "VstSyncController.h"
|
||||
#include "RemotePlugin.h"
|
||||
|
||||
#ifndef USE_QT_SHMEM
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#endif
|
||||
|
||||
|
||||
VstSyncController::VstSyncController() :
|
||||
m_syncData( NULL ),
|
||||
m_shmID( -1 ),
|
||||
m_shm( "/usr/bin/lmms" )
|
||||
namespace lmms
|
||||
{
|
||||
if( ConfigManager::inst()->value( "ui", "syncvstplugins" ).toInt() )
|
||||
{
|
||||
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
|
||||
|
||||
#ifdef USE_QT_SHMEM
|
||||
if ( m_shm.create( sizeof( VstSyncData ) ) )
|
||||
{
|
||||
m_syncData = (VstSyncData*) m_shm.data();
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << QString( "Failed to allocate shared memory for VST sync: %1" ).arg( m_shm.errorString() );
|
||||
}
|
||||
#else
|
||||
key_t key; // make the key:
|
||||
if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 )
|
||||
{
|
||||
qWarning( "VstSyncController: ftok() failed" );
|
||||
}
|
||||
else
|
||||
{ // connect to shared memory segment
|
||||
if( ( m_shmID = shmget( key, sizeof( VstSyncData ), 0644 | IPC_CREAT ) ) == -1 )
|
||||
{
|
||||
qWarning( "VstSyncController: shmget() failed" );
|
||||
}
|
||||
else
|
||||
{ // attach segment
|
||||
m_syncData = (VstSyncData *)shmat( m_shmID, 0, 0 );
|
||||
if( m_syncData == (VstSyncData *)( -1 ) )
|
||||
{
|
||||
qWarning( "VstSyncController: shmat() failed" );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning( "VST sync support disabled in your configuration" );
|
||||
}
|
||||
|
||||
if( m_syncData == NULL )
|
||||
VstSyncController::VstSyncController()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_syncData = new VstSyncData;
|
||||
m_syncData->hasSHM = false;
|
||||
m_syncData.create(QUuid::createUuid().toString().toStdString());
|
||||
}
|
||||
else
|
||||
catch (const std::runtime_error& error)
|
||||
{
|
||||
m_syncData->hasSHM = true;
|
||||
qCritical() << "Failed to allocate shared memory for VST sync:" << error.what();
|
||||
return;
|
||||
}
|
||||
|
||||
m_syncData->isPlaying = false;
|
||||
m_syncData->m_bufferSize = Engine::mixer()->framesPerPeriod();
|
||||
m_syncData->m_bufferSize = Engine::audioEngine()->framesPerPeriod();
|
||||
m_syncData->timeSigNumer = 4;
|
||||
m_syncData->timeSigDenom = 4;
|
||||
|
||||
connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, this, &VstSyncController::updateSampleRate);
|
||||
updateSampleRate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
VstSyncController::~VstSyncController()
|
||||
void VstSyncController::setAbsolutePosition(double ticks)
|
||||
{
|
||||
if( m_syncData->hasSHM == false )
|
||||
{
|
||||
delete m_syncData;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef USE_QT_SHMEM
|
||||
if( m_shm.data() )
|
||||
{
|
||||
// detach shared memory, delete it:
|
||||
m_shm.detach();
|
||||
}
|
||||
#else
|
||||
if( shmdt( m_syncData ) != -1 )
|
||||
{
|
||||
shmctl( m_shmID, IPC_RMID, NULL );
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning( "VstSyncController: shmdt() failed" );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
|
||||
|
||||
void VstSyncController::setAbsolutePosition( double ticks )
|
||||
{
|
||||
#ifdef VST_SNC_LATENCY
|
||||
m_syncData->ppqPos = ( ( ticks + 0 ) / 48.0 ) - m_syncData->m_latency;
|
||||
#else
|
||||
@@ -147,8 +76,19 @@ void VstSyncController::setAbsolutePosition( double ticks )
|
||||
|
||||
|
||||
|
||||
void VstSyncController::setTempo( int newTempo )
|
||||
void VstSyncController::setPlaybackState(bool enabled)
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->isPlaying = enabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VstSyncController::setTempo(int newTempo)
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->m_bpm = newTempo;
|
||||
|
||||
#ifdef VST_SNC_LATENCY
|
||||
@@ -159,8 +99,20 @@ void VstSyncController::setTempo( int newTempo )
|
||||
|
||||
|
||||
|
||||
void VstSyncController::startCycle( int startTick, int endTick )
|
||||
void VstSyncController::setTimeSignature(int num, int denom)
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->timeSigNumer = num;
|
||||
m_syncData->timeSigDenom = denom;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VstSyncController::startCycle(int startTick, int endTick)
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->isCycle = true;
|
||||
m_syncData->cycleStart = startTick / (float)48;
|
||||
m_syncData->cycleEnd = endTick / (float)48;
|
||||
@@ -168,9 +120,29 @@ void VstSyncController::startCycle( int startTick, int endTick )
|
||||
|
||||
|
||||
|
||||
void VstSyncController::stopCycle()
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->isCycle = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VstSyncController::setPlaybackJumped(bool jumped)
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->m_playbackJumped = jumped;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VstSyncController::update()
|
||||
{
|
||||
m_syncData->m_bufferSize = Engine::mixer()->framesPerPeriod();
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->m_bufferSize = Engine::audioEngine()->framesPerPeriod();
|
||||
|
||||
#ifdef VST_SNC_LATENCY
|
||||
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );
|
||||
@@ -181,7 +153,9 @@ void VstSyncController::update()
|
||||
|
||||
void VstSyncController::updateSampleRate()
|
||||
{
|
||||
m_syncData->m_sampleRate = Engine::mixer()->processingSampleRate();
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->m_sampleRate = Engine::audioEngine()->processingSampleRate();
|
||||
|
||||
#ifdef VST_SNC_LATENCY
|
||||
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );
|
||||
@@ -189,6 +163,4 @@ void VstSyncController::updateSampleRate()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -22,28 +22,27 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
|
||||
#include "AudioAlsa.h"
|
||||
|
||||
#ifdef LMMS_HAVE_ALSA
|
||||
|
||||
#include "endian_handling.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioAlsa::AudioAlsa( bool & _success_ful, Mixer* _mixer ) :
|
||||
AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
AudioDevice( qBound<ch_cnt_t>(
|
||||
DEFAULT_CHANNELS,
|
||||
ConfigManager::inst()->value( "audioalsa", "channels" ).toInt(),
|
||||
SURROUND_CHANNELS ), _mixer ),
|
||||
m_handle( NULL ),
|
||||
m_hwParams( NULL ),
|
||||
m_swParams( NULL ),
|
||||
SURROUND_CHANNELS ), _audioEngine ),
|
||||
m_handle( nullptr ),
|
||||
m_hwParams( nullptr ),
|
||||
m_swParams( nullptr ),
|
||||
m_convertEndian( false )
|
||||
{
|
||||
_success_ful = false;
|
||||
@@ -107,17 +106,17 @@ AudioAlsa::AudioAlsa( bool & _success_ful, Mixer* _mixer ) :
|
||||
AudioAlsa::~AudioAlsa()
|
||||
{
|
||||
stopProcessing();
|
||||
if( m_handle != NULL )
|
||||
if( m_handle != nullptr )
|
||||
{
|
||||
snd_pcm_close( m_handle );
|
||||
}
|
||||
|
||||
if( m_hwParams != NULL )
|
||||
if( m_hwParams != nullptr )
|
||||
{
|
||||
snd_pcm_hw_params_free( m_hwParams );
|
||||
}
|
||||
|
||||
if( m_swParams != NULL )
|
||||
if( m_swParams != nullptr )
|
||||
{
|
||||
snd_pcm_sw_params_free( m_swParams );
|
||||
}
|
||||
@@ -131,7 +130,7 @@ QString AudioAlsa::probeDevice()
|
||||
QString dev = ConfigManager::inst()->value( "audioalsa", "device" );
|
||||
if( dev == "" )
|
||||
{
|
||||
if( getenv( "AUDIODEV" ) != NULL )
|
||||
if( getenv( "AUDIODEV" ) != nullptr )
|
||||
{
|
||||
return getenv( "AUDIODEV" );
|
||||
}
|
||||
@@ -171,7 +170,7 @@ AudioAlsa::DeviceInfoCollection AudioAlsa::getAvailableDevices()
|
||||
}
|
||||
|
||||
char** n = hints;
|
||||
while (*n != NULL)
|
||||
while (*n != nullptr)
|
||||
{
|
||||
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||
char *description = snd_device_name_get_hint(*n, "DESC");
|
||||
@@ -255,9 +254,9 @@ void AudioAlsa::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::mixer()->processingSampleRate() );
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
if( m_handle != NULL )
|
||||
if( m_handle != nullptr )
|
||||
{
|
||||
snd_pcm_close( m_handle );
|
||||
}
|
||||
@@ -296,14 +295,11 @@ void AudioAlsa::applyQualitySettings()
|
||||
|
||||
void AudioAlsa::run()
|
||||
{
|
||||
surroundSampleFrame * temp =
|
||||
new surroundSampleFrame[mixer()->framesPerPeriod()];
|
||||
int_sample_t * outbuf =
|
||||
new int_sample_t[mixer()->framesPerPeriod() *
|
||||
channels()];
|
||||
int_sample_t * pcmbuf = new int_sample_t[m_periodSize * channels()];
|
||||
auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()];
|
||||
auto outbuf = new int_sample_t[audioEngine()->framesPerPeriod() * channels()];
|
||||
auto pcmbuf = new int_sample_t[m_periodSize * channels()];
|
||||
|
||||
int outbuf_size = mixer()->framesPerPeriod() * channels();
|
||||
int outbuf_size = audioEngine()->framesPerPeriod() * channels();
|
||||
int outbuf_pos = 0;
|
||||
int pcmbuf_size = m_periodSize * channels();
|
||||
|
||||
@@ -328,7 +324,7 @@ void AudioAlsa::run()
|
||||
outbuf_size = frames * channels();
|
||||
|
||||
convertToS16( temp, frames,
|
||||
mixer()->masterGain(),
|
||||
audioEngine()->masterGain(),
|
||||
outbuf,
|
||||
m_convertEndian );
|
||||
}
|
||||
@@ -429,7 +425,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
sampleRate(), 0 ) ) < 0 )
|
||||
{
|
||||
if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams,
|
||||
mixer()->baseSampleRate(), 0 ) ) < 0 )
|
||||
audioEngine()->baseSampleRate(), 0 ) ) < 0 )
|
||||
{
|
||||
printf( "Could not set sample rate: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -437,7 +433,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
}
|
||||
}
|
||||
|
||||
m_periodSize = mixer()->framesPerPeriod();
|
||||
m_periodSize = audioEngine()->framesPerPeriod();
|
||||
m_bufferSize = m_periodSize * 8;
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams,
|
||||
@@ -544,4 +540,6 @@ int AudioAlsa::setSWParams()
|
||||
return 0; // all ok
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_ALSA
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* AudioDevice.cpp - base-class for audio-devices used by LMMS-mixer
|
||||
* AudioDevice.cpp - base-class for audio-devices used by LMMS audio engine
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
@@ -25,23 +25,24 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "AudioDevice.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "debug.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
AudioDevice::AudioDevice( const ch_cnt_t _channels, Mixer* _mixer ) :
|
||||
AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) :
|
||||
m_supportsCapture( false ),
|
||||
m_sampleRate( _mixer->processingSampleRate() ),
|
||||
m_sampleRate( _audioEngine->processingSampleRate() ),
|
||||
m_channels( _channels ),
|
||||
m_mixer( _mixer ),
|
||||
m_buffer( new surroundSampleFrame[mixer()->framesPerPeriod()] )
|
||||
m_audioEngine( _audioEngine ),
|
||||
m_buffer( new surroundSampleFrame[audioEngine()->framesPerPeriod()] )
|
||||
{
|
||||
int error;
|
||||
if( ( m_srcState = src_new(
|
||||
mixer()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == NULL )
|
||||
audioEngine()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == nullptr )
|
||||
{
|
||||
printf( "Error: src_new() failed in audio_device.cpp!\n" );
|
||||
}
|
||||
@@ -67,7 +68,7 @@ void AudioDevice::processNextBuffer()
|
||||
const fpp_t frames = getNextBuffer( m_buffer );
|
||||
if( frames )
|
||||
{
|
||||
writeBuffer( m_buffer, frames, mixer()->masterGain() );
|
||||
writeBuffer( m_buffer, frames, audioEngine()->masterGain() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -80,8 +81,8 @@ void AudioDevice::processNextBuffer()
|
||||
|
||||
fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
|
||||
{
|
||||
fpp_t frames = mixer()->framesPerPeriod();
|
||||
const surroundSampleFrame * b = mixer()->nextBuffer();
|
||||
fpp_t frames = audioEngine()->framesPerPeriod();
|
||||
const surroundSampleFrame * b = audioEngine()->nextBuffer();
|
||||
if( !b )
|
||||
{
|
||||
return 0;
|
||||
@@ -91,12 +92,9 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
|
||||
lock();
|
||||
|
||||
// resample if necessary
|
||||
if( mixer()->processingSampleRate() != m_sampleRate )
|
||||
if( audioEngine()->processingSampleRate() != m_sampleRate )
|
||||
{
|
||||
resample( b, frames, _ab, mixer()->processingSampleRate(),
|
||||
m_sampleRate );
|
||||
frames = frames * m_sampleRate /
|
||||
mixer()->processingSampleRate();
|
||||
frames = resample( b, frames, _ab, audioEngine()->processingSampleRate(), m_sampleRate );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -106,7 +104,7 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
|
||||
// release lock
|
||||
unlock();
|
||||
|
||||
if( mixer()->hasFifoWriter() )
|
||||
if( audioEngine()->hasFifoWriter() )
|
||||
{
|
||||
delete[] b;
|
||||
}
|
||||
@@ -119,7 +117,7 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
|
||||
|
||||
void AudioDevice::stopProcessing()
|
||||
{
|
||||
if( mixer()->hasFifoWriter() )
|
||||
if( audioEngine()->hasFifoWriter() )
|
||||
{
|
||||
while( m_inProcess )
|
||||
{
|
||||
@@ -153,8 +151,8 @@ void AudioDevice::applyQualitySettings()
|
||||
|
||||
int error;
|
||||
if( ( m_srcState = src_new(
|
||||
mixer()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == NULL )
|
||||
audioEngine()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == nullptr )
|
||||
{
|
||||
printf( "Error: src_new() failed in audio_device.cpp!\n" );
|
||||
}
|
||||
@@ -184,20 +182,20 @@ void AudioDevice::renamePort( AudioPort * )
|
||||
|
||||
|
||||
|
||||
void AudioDevice::resample( const surroundSampleFrame * _src,
|
||||
fpp_t AudioDevice::resample( const surroundSampleFrame * _src,
|
||||
const fpp_t _frames,
|
||||
surroundSampleFrame * _dst,
|
||||
const sample_rate_t _src_sr,
|
||||
const sample_rate_t _dst_sr )
|
||||
{
|
||||
if( m_srcState == NULL )
|
||||
if( m_srcState == nullptr )
|
||||
{
|
||||
return;
|
||||
return _frames;
|
||||
}
|
||||
m_srcData.input_frames = _frames;
|
||||
m_srcData.output_frames = _frames;
|
||||
m_srcData.data_in = (float *) _src[0];
|
||||
m_srcData.data_out = _dst[0];
|
||||
m_srcData.data_in = const_cast<float*>(_src[0].data());
|
||||
m_srcData.data_out = _dst[0].data ();
|
||||
m_srcData.src_ratio = (double) _dst_sr / _src_sr;
|
||||
m_srcData.end_of_input = 0;
|
||||
int error;
|
||||
@@ -206,6 +204,7 @@ void AudioDevice::resample( const surroundSampleFrame * _src,
|
||||
printf( "AudioDevice::resample(): error while resampling: %s\n",
|
||||
src_strerror( error ) );
|
||||
}
|
||||
return static_cast<fpp_t>(m_srcData.output_frames_gen);
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +222,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
temp = static_cast<int_sample_t>( Mixer::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER );
|
||||
temp = static_cast<int_sample_t>( AudioEngine::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER );
|
||||
|
||||
( _output_buffer + frame * channels() )[chnl] =
|
||||
( temp & 0x00ff ) << 8 |
|
||||
@@ -239,7 +238,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
|
||||
{
|
||||
( _output_buffer + frame * channels() )[chnl] =
|
||||
static_cast<int_sample_t>(
|
||||
Mixer::clip( _ab[frame][chnl] *
|
||||
AudioEngine::clip( _ab[frame][chnl] *
|
||||
_master_gain ) *
|
||||
OUTPUT_SAMPLE_MULTIPLIER );
|
||||
}
|
||||
@@ -255,7 +254,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
|
||||
void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames )
|
||||
{
|
||||
|
||||
assert( _outbuf != NULL );
|
||||
assert( _outbuf != nullptr );
|
||||
|
||||
memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE );
|
||||
}
|
||||
@@ -265,7 +264,8 @@ void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames )
|
||||
|
||||
bool AudioDevice::hqAudio() const
|
||||
{
|
||||
return ConfigManager::inst()->value( "mixer", "hqaudio" ).toInt();
|
||||
return ConfigManager::inst()->value( "audioengine", "hqaudio" ).toInt();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -29,15 +29,19 @@
|
||||
#include "ExportProjectDialog.h"
|
||||
#include "GuiApplication.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioFileDevice::AudioFileDevice( OutputSettings const & outputSettings,
|
||||
const ch_cnt_t _channels,
|
||||
const QString & _file,
|
||||
Mixer* _mixer ) :
|
||||
AudioDevice( _channels, _mixer ),
|
||||
AudioEngine* _audioEngine ) :
|
||||
AudioDevice( _channels, _audioEngine ),
|
||||
m_outputFile( _file ),
|
||||
m_outputSettings(outputSettings)
|
||||
{
|
||||
using gui::ExportProjectDialog;
|
||||
|
||||
setSampleRate( outputSettings.getSampleRate() );
|
||||
|
||||
if( m_outputFile.open( QFile::WriteOnly | QFile::Truncate ) == false )
|
||||
@@ -52,9 +56,9 @@ AudioFileDevice::AudioFileDevice( OutputSettings const & outputSettings,
|
||||
"file and try again!"
|
||||
).arg( _file );
|
||||
|
||||
if( gui )
|
||||
if (gui::getGUI() != nullptr)
|
||||
{
|
||||
QMessageBox::critical( NULL, title, message,
|
||||
QMessageBox::critical( nullptr, title, message,
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::NoButton );
|
||||
}
|
||||
@@ -87,3 +91,4 @@ int AudioFileDevice::writeData( const void* data, int len )
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -22,14 +22,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
#include "AudioFileFlac.h"
|
||||
#include "endian_handling.h"
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
AudioFileFlac::AudioFileFlac(OutputSettings const& outputSettings, ch_cnt_t const channels, bool& successful, QString const& file, Mixer* mixer):
|
||||
AudioFileDevice(outputSettings,channels,file,mixer),
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioFileFlac::AudioFileFlac(OutputSettings const& outputSettings, ch_cnt_t const channels, bool& successful, QString const& file, AudioEngine* audioEngine):
|
||||
AudioFileDevice(outputSettings,channels,file,audioEngine),
|
||||
m_sf(nullptr)
|
||||
{
|
||||
successful = outputFileOpened() && startEncoding();
|
||||
@@ -44,7 +50,7 @@ bool AudioFileFlac::startEncoding()
|
||||
{
|
||||
m_sfinfo.samplerate=sampleRate();
|
||||
m_sfinfo.channels=channels();
|
||||
m_sfinfo.frames = mixer()->framesPerPeriod();
|
||||
m_sfinfo.frames = audioEngine()->framesPerPeriod();
|
||||
m_sfinfo.sections=1;
|
||||
m_sfinfo.seekable=0;
|
||||
|
||||
@@ -86,6 +92,7 @@ bool AudioFileFlac::startEncoding()
|
||||
void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames, float master_gain)
|
||||
{
|
||||
OutputSettings::BitDepth depth = getOutputSettings().getBitDepth();
|
||||
float clipvalue = std::nextafterf( -1.0f, 0.0f );
|
||||
|
||||
if (depth == OutputSettings::Depth_24Bit || depth == OutputSettings::Depth_32Bit) // Float encoding
|
||||
{
|
||||
@@ -94,7 +101,10 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram
|
||||
{
|
||||
for(ch_cnt_t channel=0; channel<channels(); ++channel)
|
||||
{
|
||||
buf[frame*channels() + channel] = _ab[frame][channel] * master_gain;
|
||||
// Clip the negative side to just above -1.0 in order to prevent it from changing sign
|
||||
// Upstream issue: https://github.com/erikd/libsndfile/issues/309
|
||||
// When this commit is reverted libsndfile-1.0.29 must be made a requirement for FLAC
|
||||
buf[frame*channels() + channel] = qMax( clipvalue, _ab[frame][channel] * master_gain );
|
||||
}
|
||||
}
|
||||
sf_writef_float(m_sf,static_cast<float*>(buf.get()),frames);
|
||||
@@ -117,3 +127,5 @@ void AudioFileFlac::finishEncoding()
|
||||
sf_close(m_sf);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -27,17 +27,18 @@
|
||||
|
||||
#ifdef LMMS_HAVE_MP3LAME
|
||||
|
||||
#include "Mixer.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioFileMP3::AudioFileMP3( OutputSettings const & outputSettings,
|
||||
const ch_cnt_t channels,
|
||||
bool & successful,
|
||||
const QString & file,
|
||||
Mixer* mixer ) :
|
||||
AudioFileDevice( outputSettings, channels, file, mixer )
|
||||
AudioEngine* audioEngine ) :
|
||||
AudioFileDevice( outputSettings, channels, file, audioEngine )
|
||||
{
|
||||
successful = true;
|
||||
// For now only accept stereo sources
|
||||
@@ -131,4 +132,6 @@ void AudioFileMP3::tearDownEncoder()
|
||||
lame_close(m_lame);
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_MP3LAME
|
||||
|
||||
@@ -30,19 +30,23 @@
|
||||
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
#include <QRandomGenerator>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioFileOgg::AudioFileOgg( OutputSettings const & outputSettings,
|
||||
const ch_cnt_t channels,
|
||||
bool & successful,
|
||||
const QString & file,
|
||||
Mixer* mixer ) :
|
||||
AudioFileDevice( outputSettings, channels, file, mixer )
|
||||
AudioEngine* audioEngine ) :
|
||||
AudioFileDevice( outputSettings, channels, file, audioEngine )
|
||||
{
|
||||
m_ok = successful = outputFileOpened() && startEncoding();
|
||||
}
|
||||
@@ -79,7 +83,7 @@ bool AudioFileOgg::startEncoding()
|
||||
vc.user_comments = &user_comments;
|
||||
vc.comment_lengths = &comment_length;
|
||||
vc.comments = 1;
|
||||
vc.vendor = NULL;
|
||||
vc.vendor = nullptr;
|
||||
|
||||
m_channels = channels();
|
||||
|
||||
@@ -120,11 +124,11 @@ bool AudioFileOgg::startEncoding()
|
||||
if( useVariableBitRate )
|
||||
{
|
||||
// Turn off management entirely (if it was turned on).
|
||||
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, NULL );
|
||||
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, nullptr );
|
||||
}
|
||||
else
|
||||
{
|
||||
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, NULL );
|
||||
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, nullptr );
|
||||
}
|
||||
|
||||
vorbis_encode_setup_init( &m_vi );
|
||||
@@ -136,8 +140,13 @@ bool AudioFileOgg::startEncoding()
|
||||
|
||||
// We give our ogg file a random serial number and avoid
|
||||
// 0 and UINT32_MAX which can get you into trouble.
|
||||
qsrand( time( 0 ) );
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
// QRandomGenerator::global() is already initialized, and we can't seed() it.
|
||||
m_serialNo = 0xD0000000 + QRandomGenerator::global()->generate() % 0x0FFFFFFF;
|
||||
#else
|
||||
qsrand(time(0));
|
||||
m_serialNo = 0xD0000000 + qrand() % 0x0FFFFFFF;
|
||||
#endif
|
||||
ogg_stream_init( &m_os, m_serialNo );
|
||||
|
||||
// Now, build the three header packets and send through to the stream
|
||||
@@ -203,7 +212,7 @@ void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab,
|
||||
while( vorbis_analysis_blockout( &m_vd, &m_vb ) == 1 )
|
||||
{
|
||||
// Do the main analysis, creating a packet
|
||||
vorbis_analysis( &m_vb, NULL );
|
||||
vorbis_analysis( &m_vb, nullptr );
|
||||
vorbis_bitrate_addblock( &m_vb );
|
||||
|
||||
while( vorbis_bitrate_flushpacket( &m_vd, &m_op ) )
|
||||
@@ -249,7 +258,7 @@ void AudioFileOgg::finishEncoding()
|
||||
if( m_ok )
|
||||
{
|
||||
// just for flushing buffers...
|
||||
writeBuffer( NULL, 0, 0.0f );
|
||||
writeBuffer( nullptr, 0, 0.0f );
|
||||
|
||||
// clean up
|
||||
ogg_stream_clear( &m_os );
|
||||
@@ -261,6 +270,8 @@ void AudioFileOgg::finishEncoding()
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_OGGVORBIS
|
||||
|
||||
|
||||
|
||||
@@ -25,18 +25,18 @@
|
||||
|
||||
#include "AudioFileWave.h"
|
||||
#include "endian_handling.h"
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
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 )
|
||||
AudioEngine* audioEngine ) :
|
||||
AudioFileDevice( outputSettings, channels, file, audioEngine ),
|
||||
m_sf( nullptr )
|
||||
{
|
||||
successful = outputFileOpened() && startEncoding();
|
||||
}
|
||||
@@ -56,7 +56,7 @@ bool AudioFileWave::startEncoding()
|
||||
{
|
||||
m_si.samplerate = sampleRate();
|
||||
m_si.channels = channels();
|
||||
m_si.frames = mixer()->framesPerPeriod();
|
||||
m_si.frames = audioEngine()->framesPerPeriod();
|
||||
m_si.sections = 1;
|
||||
m_si.seekable = 0;
|
||||
|
||||
@@ -86,7 +86,7 @@ bool AudioFileWave::startEncoding()
|
||||
}
|
||||
|
||||
// Prevent fold overs when encountering clipped data
|
||||
sf_command(m_sf, SFC_SET_CLIPPING, NULL, SF_TRUE);
|
||||
sf_command(m_sf, SFC_SET_CLIPPING, nullptr, SF_TRUE);
|
||||
|
||||
sf_set_string ( m_sf, SF_STR_SOFTWARE, "LMMS" );
|
||||
|
||||
@@ -104,7 +104,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
|
||||
|
||||
if( bitDepth == OutputSettings::Depth_32Bit || bitDepth == OutputSettings::Depth_24Bit )
|
||||
{
|
||||
float * buf = new float[_frames*channels()];
|
||||
auto buf = new float[_frames * channels()];
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
@@ -118,7 +118,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
|
||||
}
|
||||
else
|
||||
{
|
||||
int_sample_t * buf = new int_sample_t[_frames * channels()];
|
||||
auto buf = new int_sample_t[_frames * channels()];
|
||||
convertToS16( _ab, _frames, _master_gain, buf,
|
||||
!isLittleEndian() );
|
||||
|
||||
@@ -138,3 +138,4 @@ void AudioFileWave::finishEncoding()
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -35,31 +35,34 @@
|
||||
#include "gui_templates.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "LcdSpinBox.h"
|
||||
#include "AudioPort.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Mixer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "MidiJack.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) :
|
||||
AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
AudioDevice( qBound<int>(
|
||||
DEFAULT_CHANNELS,
|
||||
ConfigManager::inst()->value( "audiojack", "channels" ).toInt(),
|
||||
SURROUND_CHANNELS ), _mixer ),
|
||||
m_client( NULL ),
|
||||
SURROUND_CHANNELS ), _audioEngine ),
|
||||
m_client( nullptr ),
|
||||
m_active( false ),
|
||||
m_midiClient( NULL ),
|
||||
m_midiClient( nullptr ),
|
||||
m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ),
|
||||
m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ),
|
||||
m_outBuf( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ),
|
||||
m_framesDoneInCurBuf( 0 ),
|
||||
m_framesToDoInCurBuf( 0 )
|
||||
{
|
||||
m_stopped = true;
|
||||
|
||||
_success_ful = initJackClient();
|
||||
if( _success_ful )
|
||||
{
|
||||
connect( this, SIGNAL( zombified() ),
|
||||
this, SLOT( restartAfterZombified() ),
|
||||
connect( this, SIGNAL(zombified()),
|
||||
this, SLOT(restartAfterZombified()),
|
||||
Qt::QueuedConnection );
|
||||
}
|
||||
|
||||
@@ -78,7 +81,7 @@ AudioJack::~AudioJack()
|
||||
}
|
||||
#endif
|
||||
|
||||
if( m_client != NULL )
|
||||
if( m_client != nullptr )
|
||||
{
|
||||
if( m_active )
|
||||
{
|
||||
@@ -101,7 +104,7 @@ void AudioJack::restartAfterZombified()
|
||||
{
|
||||
m_active = false;
|
||||
startProcessing();
|
||||
QMessageBox::information( gui->mainWindow(),
|
||||
QMessageBox::information(gui::getGUI()->mainWindow(),
|
||||
tr( "JACK client restarted" ),
|
||||
tr( "LMMS was kicked by JACK for some reason. "
|
||||
"Therefore the JACK backend of LMMS has been "
|
||||
@@ -110,7 +113,7 @@ void AudioJack::restartAfterZombified()
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information( gui->mainWindow(),
|
||||
QMessageBox::information(gui::getGUI()->mainWindow(),
|
||||
tr( "JACK server down" ),
|
||||
tr( "The JACK server seems to have been shutdown "
|
||||
"and starting a new instance failed. "
|
||||
@@ -124,8 +127,8 @@ void AudioJack::restartAfterZombified()
|
||||
|
||||
AudioJack* AudioJack::addMidiClient(MidiJack *midiClient)
|
||||
{
|
||||
if( m_client == NULL )
|
||||
return NULL;
|
||||
if( m_client == nullptr )
|
||||
return nullptr;
|
||||
|
||||
m_midiClient = midiClient;
|
||||
|
||||
@@ -141,12 +144,12 @@ bool AudioJack::initJackClient()
|
||||
clientName = "lmms";
|
||||
}
|
||||
|
||||
const char * serverName = NULL;
|
||||
const char * serverName = nullptr;
|
||||
jack_status_t status;
|
||||
m_client = jack_client_open( clientName.toLatin1().constData(),
|
||||
JackNullOption, &status,
|
||||
serverName );
|
||||
if( m_client == NULL )
|
||||
if( m_client == nullptr )
|
||||
{
|
||||
printf( "jack_client_open() failed, status 0x%2.0x\n", status );
|
||||
if( status & JackServerFailed )
|
||||
@@ -185,7 +188,7 @@ bool AudioJack::initJackClient()
|
||||
name.toLatin1().constData(),
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0 ) );
|
||||
if( m_outputPorts.back() == NULL )
|
||||
if( m_outputPorts.back() == nullptr )
|
||||
{
|
||||
printf( "no more JACK-ports available!\n" );
|
||||
return false;
|
||||
@@ -200,10 +203,9 @@ bool AudioJack::initJackClient()
|
||||
|
||||
void AudioJack::startProcessing()
|
||||
{
|
||||
m_stopped = false;
|
||||
|
||||
if( m_active || m_client == NULL )
|
||||
if( m_active || m_client == nullptr )
|
||||
{
|
||||
m_stopped = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,14 +219,14 @@ void AudioJack::startProcessing()
|
||||
|
||||
|
||||
// try to sync JACK's and LMMS's buffer-size
|
||||
// jack_set_buffer_size( m_client, mixer()->framesPerPeriod() );
|
||||
// jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() );
|
||||
|
||||
|
||||
|
||||
const char * * ports = jack_get_ports( m_client, NULL, NULL,
|
||||
const char * * ports = jack_get_ports( m_client, nullptr, nullptr,
|
||||
JackPortIsPhysical |
|
||||
JackPortIsInput );
|
||||
if( ports == NULL )
|
||||
if( ports == nullptr )
|
||||
{
|
||||
printf( "no physical playback ports. you'll have to do "
|
||||
"connections at your own!\n" );
|
||||
@@ -244,6 +246,7 @@ void AudioJack::startProcessing()
|
||||
}
|
||||
}
|
||||
|
||||
m_stopped = false;
|
||||
free( ports );
|
||||
}
|
||||
|
||||
@@ -262,7 +265,7 @@ void AudioJack::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::mixer()->processingSampleRate() );
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
if( jack_get_sample_rate( m_client ) != sampleRate() )
|
||||
{
|
||||
@@ -304,7 +307,7 @@ void AudioJack::unregisterPort( AudioPort * _port )
|
||||
{
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
{
|
||||
if( m_portMap[_port].ports[ch] != NULL )
|
||||
if( m_portMap[_port].ports[ch] != nullptr )
|
||||
{
|
||||
jack_port_unregister( m_client,
|
||||
m_portMap[_port].ports[ch] );
|
||||
@@ -327,11 +330,16 @@ void AudioJack::renamePort( AudioPort * _port )
|
||||
_port->name() + " R" };
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
{
|
||||
#ifdef LMMS_HAVE_JACK_PRENAME
|
||||
jack_port_rename( m_client, m_portMap[_port].ports[ch],
|
||||
name[ch].toLatin1().constData() );
|
||||
#else
|
||||
jack_port_set_name( m_portMap[_port].ports[ch],
|
||||
name[ch].toLatin1().constData() );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // AUDIO_PORT_SUPPORT
|
||||
}
|
||||
|
||||
|
||||
@@ -344,8 +352,8 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
// add to the following sound processing
|
||||
if( m_midiClient && _nframes > 0 )
|
||||
{
|
||||
m_midiClient->JackMidiRead(_nframes);
|
||||
m_midiClient->JackMidiWrite(_nframes);
|
||||
m_midiClient.load()->JackMidiRead(_nframes);
|
||||
m_midiClient.load()->JackMidiWrite(_nframes);
|
||||
}
|
||||
|
||||
for( int c = 0; c < channels(); ++c )
|
||||
@@ -356,23 +364,23 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
}
|
||||
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
const int frames = qMin<int>( _nframes, mixer()->framesPerPeriod() );
|
||||
for( jackPortMap::iterator it = m_portMap.begin();
|
||||
const int frames = qMin<int>( _nframes, audioEngine()->framesPerPeriod() );
|
||||
for( JackPortMap::iterator it = m_portMap.begin();
|
||||
it != m_portMap.end(); ++it )
|
||||
{
|
||||
for( ch_cnt_t ch = 0; ch < channels(); ++ch )
|
||||
{
|
||||
if( it.data().ports[ch] == NULL )
|
||||
if( it.value().ports[ch] == nullptr )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
jack_default_audio_sample_t * buf =
|
||||
(jack_default_audio_sample_t *) jack_port_get_buffer(
|
||||
it.data().ports[ch],
|
||||
it.value().ports[ch],
|
||||
_nframes );
|
||||
for( int frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
buf[frame] = it.key()->firstBuffer()[frame][ch];
|
||||
buf[frame] = it.key()->buffer()[frame][ch];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,7 +393,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
_nframes,
|
||||
m_framesToDoInCurBuf -
|
||||
m_framesDoneInCurBuf );
|
||||
const float gain = mixer()->masterGain();
|
||||
const float gain = audioEngine()->masterGain();
|
||||
for( int c = 0; c < channels(); ++c )
|
||||
{
|
||||
jack_default_audio_sample_t * o = m_tempOutBufs[c];
|
||||
@@ -434,8 +442,8 @@ int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata )
|
||||
|
||||
void AudioJack::shutdownCallback( void * _udata )
|
||||
{
|
||||
AudioJack * _this = static_cast<AudioJack *>( _udata );
|
||||
_this->m_client = NULL;
|
||||
auto _this = static_cast<AudioJack*>(_udata);
|
||||
_this->m_client = nullptr;
|
||||
_this->zombified();
|
||||
}
|
||||
|
||||
@@ -454,19 +462,19 @@ AudioJack::setupWidget::setupWidget( QWidget * _parent ) :
|
||||
m_clientName = new QLineEdit( cn, this );
|
||||
m_clientName->setGeometry( 10, 20, 160, 20 );
|
||||
|
||||
QLabel * cn_lbl = new QLabel( tr( "CLIENT-NAME" ), this );
|
||||
auto cn_lbl = new QLabel(tr("Client name"), this);
|
||||
cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) );
|
||||
cn_lbl->setGeometry( 10, 40, 160, 10 );
|
||||
|
||||
LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ );
|
||||
auto m = new gui::LcdSpinBoxModel(/* this */);
|
||||
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
|
||||
m->setStep( 2 );
|
||||
m->setValue( ConfigManager::inst()->value( "audiojack",
|
||||
"channels" ).toInt() );
|
||||
|
||||
m_channels = new LcdSpinBox( 1, this );
|
||||
m_channels = new gui::LcdSpinBox( 1, this );
|
||||
m_channels->setModel( m );
|
||||
m_channels->setLabel( tr( "CHANNELS" ) );
|
||||
m_channels->setLabel( tr( "Channels" ) );
|
||||
m_channels->move( 180, 20 );
|
||||
|
||||
}
|
||||
@@ -492,6 +500,6 @@ void AudioJack::setupWidget::saveSettings()
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
#endif
|
||||
#endif // LMMS_HAVE_JACK
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user