Merge branch 'master' into refac/memory
This commit is contained in:
44
src/3rdparty/CMakeLists.txt
vendored
44
src/3rdparty/CMakeLists.txt
vendored
@@ -1,9 +1,14 @@
|
||||
IF(LMMS_BUILD_LINUX AND WANT_VST)
|
||||
set_directory_properties(PROPERTIES SYSTEM TRUE)
|
||||
|
||||
if(LMMS_BUILD_LINUX AND LMMS_HAVE_VST)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(qt5-x11embed)
|
||||
ENDIF()
|
||||
|
||||
ADD_SUBDIRECTORY(rpmalloc)
|
||||
add_library(jack_headers INTERFACE)
|
||||
target_include_directories(jack_headers INTERFACE jack2/common)
|
||||
|
||||
ADD_SUBDIRECTORY(hiir)
|
||||
ADD_SUBDIRECTORY(weakjack)
|
||||
|
||||
IF(WIN32 AND CMAKE_COMPILER_IS_GNUCXX)
|
||||
@@ -30,21 +35,13 @@ ADD_LIBRARY(cds ${CDS_LIBRARY_TYPE}
|
||||
libcds/src/dllmain.cpp
|
||||
)
|
||||
SET_TARGET_PROPERTIES(cds PROPERTIES
|
||||
CXX_STANDARD 11
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
TARGET_INCLUDE_DIRECTORIES(cds
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libcds"
|
||||
)
|
||||
|
||||
IF(NEED_MINGW_THREADS_REPLACEMENT)
|
||||
# Provide win32 threads implementation
|
||||
TARGET_INCLUDE_DIRECTORIES(cds BEFORE
|
||||
PRIVATE "${CMAKE_SOURCE_DIR}/include/mingw-std-threads"
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/mingw-std-threads"
|
||||
)
|
||||
ENDIF()
|
||||
|
||||
IF(CDS_LIBRARY_TYPE STREQUAL "STATIC")
|
||||
TARGET_COMPILE_DEFINITIONS(cds
|
||||
PUBLIC CDS_BUILD_STATIC_LIB
|
||||
@@ -57,13 +54,22 @@ ELSE()
|
||||
install(TARGETS cds RUNTIME DESTINATION .)
|
||||
ENDIF()
|
||||
|
||||
# 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)
|
||||
# The lockless ring buffer library is linked as part of the core
|
||||
add_library(ringbuffer OBJECT
|
||||
ringbuffer/src/lib/ringbuffer.cpp
|
||||
)
|
||||
target_compile_features(ringbuffer PUBLIC cxx_std_17)
|
||||
target_include_directories(ringbuffer PUBLIC
|
||||
ringbuffer/include
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
# 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")
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ringbuffer_export.h.in" [[
|
||||
#include "${CMAKE_BINARY_DIR}/src/lmms_export.h"
|
||||
#define RINGBUFFER_EXPORT LMMS_EXPORT
|
||||
]])
|
||||
configure_file("${CMAKE_CURRENT_BINARY_DIR}/ringbuffer_export.h.in" ringbuffer_export.h)
|
||||
target_compile_definitions(ringbuffer PRIVATE lmmsobjs_EXPORTS)
|
||||
# Enable MLOCK support for ringbuffer if available
|
||||
INCLUDE(CheckIncludeFiles)
|
||||
CHECK_INCLUDE_FILES(sys/mman.h HAVE_SYS_MMAN)
|
||||
@@ -73,5 +79,5 @@ 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)
|
||||
configure_file(ringbuffer/src/ringbuffer-config.h.in ringbuffer-config.h)
|
||||
configure_file(ringbuffer/src/ringbuffer-version.h.in ringbuffer-version.h)
|
||||
|
||||
3
src/3rdparty/hiir/CMakeLists.txt
vendored
Normal file
3
src/3rdparty/hiir/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(hiir INTERFACE)
|
||||
target_include_directories(hiir INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(hiir INTERFACE cxx_std_17)
|
||||
1
src/3rdparty/hiir/hiir
vendored
Submodule
1
src/3rdparty/hiir/hiir
vendored
Submodule
Submodule src/3rdparty/hiir/hiir added at 4a9a1e67fa
2
src/3rdparty/jack2
vendored
2
src/3rdparty/jack2
vendored
Submodule src/3rdparty/jack2 updated: db76dd6bb8...ac334fabfb
1
src/3rdparty/mingw-std-threads
vendored
1
src/3rdparty/mingw-std-threads
vendored
Submodule src/3rdparty/mingw-std-threads deleted from 10665829da
2
src/3rdparty/ringbuffer
vendored
2
src/3rdparty/ringbuffer
vendored
Submodule src/3rdparty/ringbuffer updated: ea00e1fc2b...1c46ef34a2
49
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
49
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
@@ -1,49 +0,0 @@
|
||||
add_library(rpmalloc STATIC
|
||||
rpmalloc/rpmalloc/rpmalloc.c
|
||||
rpmalloc/rpmalloc/rpmalloc.h
|
||||
)
|
||||
|
||||
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
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5)
|
||||
set(ENABLE_VALIDATE_ARGS OFF)
|
||||
else ()
|
||||
set(ENABLE_VALIDATE_ARGS ON)
|
||||
endif()
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS}
|
||||
)
|
||||
endif()
|
||||
|
||||
option(LMMS_ENABLE_MALLOC_STATS "Enables statistics for rpmalloc" OFF)
|
||||
|
||||
if (LMMS_ENABLE_MALLOC_STATS)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -DENABLE_STATISTICS=1
|
||||
)
|
||||
endif()
|
||||
1
src/3rdparty/rpmalloc/rpmalloc
vendored
1
src/3rdparty/rpmalloc/rpmalloc
vendored
Submodule src/3rdparty/rpmalloc/rpmalloc deleted from 8d790d2b45
22
src/3rdparty/weakjack/CMakeLists.txt
vendored
22
src/3rdparty/weakjack/CMakeLists.txt
vendored
@@ -1,12 +1,14 @@
|
||||
# Use weak jack library linking
|
||||
IF(LMMS_HAVE_WEAKJACK)
|
||||
SET(CMAKE_C_FLAGS "-std=c11")
|
||||
|
||||
if(LMMS_HAVE_WEAKJACK)
|
||||
add_library(weakjack STATIC
|
||||
weakjack/weak_libjack.c
|
||||
)
|
||||
target_include_directories(weakjack PUBLIC weakjack)
|
||||
target_link_libraries(weakjack PUBLIC jack_headers ${CMAKE_DL_LIBS})
|
||||
target_compile_features(weakjack PRIVATE c_std_11)
|
||||
# Enable weakjack, disable metadata support
|
||||
ADD_DEFINITIONS(-DUSE_WEAK_JACK=1 -DNO_JACK_METADATA=1)
|
||||
|
||||
# Library stub for AppImages running on systems without jack
|
||||
ADD_LIBRARY(weakjack MODULE weakjack/weak_libjack.c weakjack/weak_libjack.h)
|
||||
TARGET_INCLUDE_DIRECTORIES(weakjack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/weakjack ${JACK_INCLUDE_DIRS})
|
||||
INSTALL(TARGETS weakjack LIBRARY DESTINATION "${PLUGIN_DIR}/optional")
|
||||
ENDIF()
|
||||
target_compile_definitions(weakjack PUBLIC
|
||||
USE_WEAK_JACK=1
|
||||
NO_JACK_METADATA=1
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -4,30 +4,31 @@ CONFIGURE_FILE("lmmsconfig.h.in" "${CMAKE_BINARY_DIR}/lmmsconfig.h")
|
||||
CONFIGURE_FILE("lmmsversion.h.in" "${CMAKE_BINARY_DIR}/lmmsversion.h")
|
||||
|
||||
SET(LMMS_SRCS "")
|
||||
SET(LMMS_UIS "")
|
||||
|
||||
SET(CMAKE_AUTOMOC ON)
|
||||
SET(CMAKE_AUTOUIC ON)
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Enable C++14
|
||||
SET(CMAKE_CXX_STANDARD 14)
|
||||
# Enable C++17
|
||||
SET(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
IF(LMMS_BUILD_APPLE)
|
||||
IF(LMMS_BUILD_APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
ENDIF()
|
||||
|
||||
ADD_SUBDIRECTORY(common)
|
||||
ADD_SUBDIRECTORY(core)
|
||||
ADD_SUBDIRECTORY(gui)
|
||||
ADD_SUBDIRECTORY(tracks)
|
||||
|
||||
QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS})
|
||||
LIST(APPEND LMMS_SRCS ${LMMS_COMMON_SRCS})
|
||||
|
||||
INCLUDE_DIRECTORIES(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${RINGBUFFER_DIR}/include"
|
||||
)
|
||||
|
||||
IF(WIN32 AND MSVC)
|
||||
@@ -52,56 +53,28 @@ 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})
|
||||
INCLUDE_DIRECTORIES(
|
||||
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS})
|
||||
include_directories(SYSTEM
|
||||
${JACK_INCLUDE_DIRS}
|
||||
${SAMPLERATE_INCLUDE_DIRS}
|
||||
${SNDFILE_INCLUDE_DIRS}
|
||||
${SNDIO_INCLUDE_DIRS}
|
||||
${FFTW3F_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
IF(NOT ("${SDL2_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${SDL2_INCLUDE_DIR}")
|
||||
ELSEIF(NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${SDL_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(LMMS_HAVE_WEAKJACK)
|
||||
LIST(APPEND LMMS_SRCS "${WEAKJACK_INCLUDE_DIRS}/weak_libjack.c")
|
||||
LIST(APPEND LMMS_INCLUDES "${WEAKJACK_INCLUDE_DIRS}/weak_libjack.h")
|
||||
INCLUDE_DIRECTORIES("${WEAKJACK_INCLUDE_DIRS}")
|
||||
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()
|
||||
|
||||
IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}")
|
||||
include_directories(SYSTEM "${PULSEAUDIO_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${LV2_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LILV_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${LILV_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${SUIL_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${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)
|
||||
@@ -113,7 +86,6 @@ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
ADD_LIBRARY(lmmsobjs OBJECT
|
||||
${LMMS_SRCS}
|
||||
${LMMS_INCLUDES}
|
||||
${LMMS_UI_OUT}
|
||||
${LMMS_RCC_OUT}
|
||||
)
|
||||
|
||||
@@ -123,23 +95,20 @@ GENERATE_EXPORT_HEADER(lmmsobjs
|
||||
|
||||
ADD_EXECUTABLE(lmms
|
||||
core/main.cpp
|
||||
$<TARGET_OBJECTS:lmmsobjs>
|
||||
"${WINRC}"
|
||||
)
|
||||
TARGET_INCLUDE_DIRECTORIES(lmms
|
||||
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_static_libraries(lmms PUBLIC lmmsobjs)
|
||||
|
||||
# CMake doesn't define target_EXPORTS for OBJECT libraries.
|
||||
# See the documentation of DEFINE_SYMBOL for details.
|
||||
# Also add LMMS_STATIC_DEFINE for targets linking against it.
|
||||
TARGET_COMPILE_DEFINITIONS(lmmsobjs
|
||||
PRIVATE -Dlmmsobjs_EXPORTS
|
||||
INTERFACE -DLMMS_STATIC_DEFINE
|
||||
)
|
||||
TARGET_COMPILE_DEFINITIONS(lmms
|
||||
PRIVATE $<TARGET_PROPERTY:lmmsobjs,INTERFACE_COMPILE_DEFINITIONS>
|
||||
)
|
||||
target_static_definitions(lmmsobjs LMMS_STATIC_DEFINE)
|
||||
|
||||
# Set Visual Studio startup project to lmms
|
||||
# https://stackoverflow.com/a/37994396/8166701
|
||||
@@ -147,7 +116,7 @@ IF(NOT CMAKE_VERSION VERSION_LESS 3.6)
|
||||
SET_PROPERTY(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lmms)
|
||||
ENDIF()
|
||||
|
||||
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} ${LMMS_UI_OUT} lmmsconfig.h lmms.1.gz")
|
||||
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} lmmsconfig.h lmms.1.gz")
|
||||
|
||||
IF(LMMS_BUILD_WIN32)
|
||||
SET(EXTRA_LIBRARIES "winmm")
|
||||
@@ -165,61 +134,54 @@ 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()
|
||||
|
||||
if(LMMS_HAVE_OGGVORBIS)
|
||||
list(APPEND EXTRA_LIBRARIES Vorbis::vorbisenc Vorbis::vorbisfile)
|
||||
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}
|
||||
SampleRate::samplerate
|
||||
SndFile::sndfile
|
||||
${EXTRA_LIBRARIES}
|
||||
rpmalloc
|
||||
cds
|
||||
)
|
||||
|
||||
# Expose required libs for tests binary
|
||||
SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} PARENT_SCOPE)
|
||||
|
||||
TARGET_LINK_LIBRARIES(lmms
|
||||
target_link_libraries(lmmsobjs
|
||||
${LMMS_REQUIRED_LIBS}
|
||||
)
|
||||
target_static_libraries(lmmsobjs ringbuffer cds)
|
||||
|
||||
FOREACH(LIB ${LMMS_REQUIRED_LIBS})
|
||||
IF(TARGET ${LIB})
|
||||
GET_TARGET_PROPERTY(INCLUDE_DIRS ${LIB} INTERFACE_INCLUDE_DIRECTORIES)
|
||||
IF(INCLUDE_DIRS)
|
||||
TARGET_INCLUDE_DIRECTORIES(lmmsobjs PRIVATE ${INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
set_target_properties(lmms PROPERTIES
|
||||
ENABLE_EXPORTS ON
|
||||
WIN32_EXECUTABLE $<NOT:$<CONFIG:DEBUG>>
|
||||
)
|
||||
|
||||
IF(LMMS_BUILD_WIN32)
|
||||
SET_TARGET_PROPERTIES(lmms PROPERTIES
|
||||
ENABLE_EXPORTS ON
|
||||
)
|
||||
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)
|
||||
set_target_properties(lmmsobjs PROPERTIES AUTOUIC_SEARCH_PATHS "gui/modals")
|
||||
|
||||
IF(NOT WIN32)
|
||||
if(CMAKE_INSTALL_MANDIR)
|
||||
SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR})
|
||||
ELSE(CMAKE_INSTALL_MANDIR)
|
||||
|
||||
11
src/common/CMakeLists.txt
Normal file
11
src/common/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
set(COMMON_SRCS
|
||||
RemotePluginBase.cpp
|
||||
SharedMemory.cpp
|
||||
SystemSemaphore.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
|
||||
219
src/common/SharedMemory.cpp
Normal file
219
src/common/SharedMemory.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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 <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# error "No shared memory implementation available"
|
||||
#endif
|
||||
|
||||
namespace lmms::detail {
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
|
||||
{
|
||||
while (true) {
|
||||
const auto result = function();
|
||||
if (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) { throwSystemError("SharedMemoryImpl: shm_open() failed"); }
|
||||
|
||||
auto stat = (struct stat){};
|
||||
if (fstat(fd.get(), &stat) == -1) { throwSystemError("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) { throwSystemError("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) { throwSystemError("SharedMemoryImpl: shm_open() failed"); }
|
||||
m_object.reset(m_key.c_str());
|
||||
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) {
|
||||
throwSystemError("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) { throwSystemError("SharedMemoryImpl: mmap() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
|
||||
|
||||
~SharedMemoryImpl()
|
||||
{
|
||||
munmap(m_mapping, m_size);
|
||||
}
|
||||
|
||||
auto get() const noexcept -> void* { return m_mapping; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
std::size_t m_size;
|
||||
void* m_mapping;
|
||||
ShmObject m_object;
|
||||
};
|
||||
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T>
|
||||
auto sizeToHighAndLow(T size) -> std::pair<DWORD, DWORD>
|
||||
{
|
||||
if constexpr (sizeof(T) <= sizeof(DWORD)) {
|
||||
return {0, size};
|
||||
} else {
|
||||
return {static_cast<DWORD>(size >> 32), static_cast<DWORD>(size)};
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void throwLastError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
using FileView = UniqueNullableResource<void*, nullptr, UnmapViewOfFile>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SharedMemoryImpl
|
||||
{
|
||||
public:
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly)
|
||||
{
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_mapping.reset(OpenFileMappingA(access, false, key.c_str()));
|
||||
if (!m_mapping) { throwLastError("SharedMemoryImpl: OpenFileMappingA() failed"); }
|
||||
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly)
|
||||
{
|
||||
const auto [high, low] = sizeToHighAndLow(size);
|
||||
m_mapping.reset(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, high, low, key.c_str()));
|
||||
// This constructor is supposed to create a new shared memory object,
|
||||
// but passing the name of an existing object causes CreateFileMappingA
|
||||
// to succeed and return a handle to that object. Thus we have to check
|
||||
// GetLastError() too.
|
||||
if (!m_mapping || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwLastError("SharedMemoryImpl: CreateFileMappingA() failed");
|
||||
}
|
||||
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
auto operator=(const SharedMemoryImpl&) -> SharedMemoryImpl& = delete;
|
||||
|
||||
auto get() const noexcept -> void* { return m_view.get(); }
|
||||
|
||||
private:
|
||||
UniqueHandle m_mapping;
|
||||
FileView m_view;
|
||||
};
|
||||
|
||||
#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{std::exchange(other.m_ptr, nullptr)}
|
||||
{ }
|
||||
|
||||
} // namespace lmms::detail
|
||||
181
src/common/SystemSemaphore.cpp
Normal file
181
src/common/SystemSemaphore.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* SystemSemaphore.cpp
|
||||
*
|
||||
* Copyright (c) 2024 Dominic Clark
|
||||
*
|
||||
* 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 "SystemSemaphore.h"
|
||||
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
# include <fcntl.h>
|
||||
# include <semaphore.h>
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# error "No system semaphore implementation available"
|
||||
#endif
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto retryWhileInterrupted(F&& function, std::invoke_result_t<F> error = -1)
|
||||
noexcept(std::is_nothrow_invocable_v<F>) -> auto
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const auto result = function();
|
||||
if (result != error || errno != EINTR) { return result; }
|
||||
}
|
||||
}
|
||||
|
||||
using UniqueSemaphore = UniqueNullableResource<const char*, nullptr, sem_unlink>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
m_ownedSemaphore.reset(m_key.c_str());
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), 0);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
}
|
||||
|
||||
~SystemSemaphoreImpl()
|
||||
{
|
||||
// We can't use `UniqueNullableResource` for `m_sem`, as the null value
|
||||
// (`SEM_FAILED`) is not a constant expression on macOS (it's defined as
|
||||
// `(sem_t*) -1`), so can't be used as a template parameter.
|
||||
sem_close(m_sem);
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool
|
||||
{
|
||||
return retryWhileInterrupted([&]() noexcept {
|
||||
return sem_wait(m_sem);
|
||||
}) == 0;
|
||||
}
|
||||
|
||||
auto release() noexcept -> bool { return sem_post(m_sem) == 0; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
sem_t* m_sem;
|
||||
UniqueSemaphore m_ownedSemaphore;
|
||||
};
|
||||
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits<LONG>::max(), key.c_str())}
|
||||
{
|
||||
if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed");
|
||||
}
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())}
|
||||
{
|
||||
if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); }
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; }
|
||||
auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); }
|
||||
|
||||
private:
|
||||
UniqueHandle m_sem;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
SystemSemaphore::SystemSemaphore() noexcept = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key, value)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::~SystemSemaphore() = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default;
|
||||
auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default;
|
||||
|
||||
auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); }
|
||||
auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); }
|
||||
|
||||
} // namespace lmms
|
||||
1158
src/core/AudioEngine.cpp
Normal file
1158
src/core/AudioEngine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
77
src/core/AudioEngineProfiler.cpp
Normal file
77
src/core/AudioEngineProfiler.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* AudioEngineProfiler.cpp - class for profiling performance of AudioEngine
|
||||
*
|
||||
* Copyright (c) 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 "AudioEngineProfiler.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioEngineProfiler::AudioEngineProfiler() :
|
||||
m_periodTimer(),
|
||||
m_cpuLoad( 0 ),
|
||||
m_outputFile()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
|
||||
{
|
||||
// Time taken to process all data and fill the audio buffer.
|
||||
const unsigned int periodElapsed = m_periodTimer.elapsed();
|
||||
// Maximum time the processing can take before causing buffer underflow. Convert to us.
|
||||
const uint64_t timeLimit = static_cast<uint64_t>(1000000) * framesPerPeriod / sampleRate;
|
||||
|
||||
// Compute new overall CPU load and apply exponential averaging.
|
||||
// The result is used for overload detection in AudioEngine::criticalXRuns()
|
||||
// → the weight of a new sample must be high enough to allow relatively fast changes!
|
||||
const auto newCpuLoad = 100.f * periodElapsed / timeLimit;
|
||||
m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f;
|
||||
|
||||
// Compute detailed load analysis. Can use stronger averaging to get more stable readout.
|
||||
for (std::size_t i = 0; i < DetailCount; i++)
|
||||
{
|
||||
const auto newLoad = 100.f * m_detailTime[i] / timeLimit;
|
||||
const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed);
|
||||
m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if( m_outputFile.isOpen() )
|
||||
{
|
||||
m_outputFile.write( QString( "%1\n" ).arg( periodElapsed ).toLatin1() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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,29 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MixerWorkerThread.h"
|
||||
#include "AudioEngineWorkerThread.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "denormals.h"
|
||||
#include "AudioEngine.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 +54,7 @@ void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
void AudioEngineWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
{
|
||||
if( _job->requiresProcessing() )
|
||||
{
|
||||
@@ -70,13 +73,13 @@ void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::JobQueue::run()
|
||||
void AudioEngineWorkerThread::JobQueue::run()
|
||||
{
|
||||
bool processedJob = true;
|
||||
while (processedJob && m_itemsDone < m_writeIndex)
|
||||
{
|
||||
processedJob = false;
|
||||
for( int i = 0; i < m_writeIndex && i < JOB_QUEUE_SIZE; ++i )
|
||||
for (auto i = std::size_t{0}; i < m_writeIndex && i < JOB_QUEUE_SIZE; ++i)
|
||||
{
|
||||
ThreadableJob * job = m_items[i].exchange(nullptr);
|
||||
if( job )
|
||||
@@ -87,18 +90,18 @@ void MixerWorkerThread::JobQueue::run()
|
||||
}
|
||||
}
|
||||
// always exit loop if we're not in dynamic mode
|
||||
processedJob = processedJob && ( m_opMode == Dynamic );
|
||||
processedJob = processedJob && ( m_opMode == OperationMode::Dynamic );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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 +113,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 +134,7 @@ MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
|
||||
|
||||
|
||||
|
||||
MixerWorkerThread::~MixerWorkerThread()
|
||||
AudioEngineWorkerThread::~AudioEngineWorkerThread()
|
||||
{
|
||||
workerThreads.removeAll( this );
|
||||
}
|
||||
@@ -139,7 +142,7 @@ MixerWorkerThread::~MixerWorkerThread()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::quit()
|
||||
void AudioEngineWorkerThread::quit()
|
||||
{
|
||||
m_quit = true;
|
||||
resetJobQueue();
|
||||
@@ -148,11 +151,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,9 +164,8 @@ void MixerWorkerThread::startAndWaitForJobs()
|
||||
|
||||
|
||||
|
||||
void MixerWorkerThread::run()
|
||||
void AudioEngineWorkerThread::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
disable_denormals();
|
||||
|
||||
QMutex m;
|
||||
@@ -176,4 +178,4 @@ void MixerWorkerThread::run()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
69
src/core/AudioResampler.cpp
Normal file
69
src/core/AudioResampler.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* AudioResampler.cpp - wrapper for libsamplerate
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@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 "AudioResampler.h"
|
||||
|
||||
#include <samplerate.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
AudioResampler::AudioResampler(int interpolationMode, int channels)
|
||||
: m_interpolationMode(interpolationMode)
|
||||
, m_channels(channels)
|
||||
, m_state(src_new(interpolationMode, channels, &m_error))
|
||||
{
|
||||
if (!m_state)
|
||||
{
|
||||
const auto errorMessage = std::string{src_strerror(m_error)};
|
||||
const auto fullMessage = std::string{"Failed to create an AudioResampler: "} + errorMessage;
|
||||
throw std::runtime_error{fullMessage};
|
||||
}
|
||||
}
|
||||
|
||||
AudioResampler::~AudioResampler()
|
||||
{
|
||||
src_delete(m_state);
|
||||
}
|
||||
|
||||
auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio)
|
||||
-> ProcessResult
|
||||
{
|
||||
auto data = SRC_DATA{};
|
||||
data.data_in = in;
|
||||
data.input_frames = inputFrames;
|
||||
data.data_out = out;
|
||||
data.output_frames = outputFrames;
|
||||
data.src_ratio = ratio;
|
||||
data.end_of_input = 0;
|
||||
return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen};
|
||||
}
|
||||
|
||||
void AudioResampler::setRatio(double ratio)
|
||||
{
|
||||
src_set_ratio(m_state, ratio);
|
||||
}
|
||||
|
||||
} // 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;
|
||||
|
||||
|
||||
@@ -41,7 +44,7 @@ AutomatableModel::AutomatableModel(
|
||||
const float val, const float min, const float max, const float step,
|
||||
Model* parent, const QString & displayName, bool defaultConstructed ) :
|
||||
Model( parent, displayName, defaultConstructed ),
|
||||
m_scaleType( Linear ),
|
||||
m_scaleType( ScaleType::Linear ),
|
||||
m_minValue( min ),
|
||||
m_maxValue( max ),
|
||||
m_step( step ),
|
||||
@@ -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 );
|
||||
@@ -67,7 +71,7 @@ AutomatableModel::~AutomatableModel()
|
||||
{
|
||||
while( m_linkedModels.empty() == false )
|
||||
{
|
||||
m_linkedModels.last()->unlinkModel( this );
|
||||
m_linkedModels.back()->unlinkModel(this);
|
||||
m_linkedModels.erase( m_linkedModels.end() - 1 );
|
||||
}
|
||||
|
||||
@@ -86,22 +90,22 @@ AutomatableModel::~AutomatableModel()
|
||||
|
||||
bool AutomatableModel::isAutomated() const
|
||||
{
|
||||
return AutomationPattern::isAutomated( this );
|
||||
return AutomationClip::isAutomated( this );
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool AutomatableModel::mustQuoteName(const QString& name)
|
||||
{
|
||||
QRegExp reg("^[A-Za-z0-9._-]+$");
|
||||
return !reg.exactMatch(name);
|
||||
QRegularExpression reg("^[A-Za-z0-9._-]+$");
|
||||
return !reg.match(name).hasMatch();
|
||||
}
|
||||
|
||||
void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
|
||||
{
|
||||
bool mustQuote = mustQuoteName(name);
|
||||
|
||||
if( isAutomated() || m_scaleType != Linear )
|
||||
if( isAutomated() || m_scaleType != ScaleType::Linear )
|
||||
{
|
||||
// automation needs tuple of data (name, id, value)
|
||||
// scale type also needs an extra value
|
||||
@@ -110,7 +114,7 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
|
||||
QDomElement me = doc.createElement( mustQuote ? QString("automatablemodel") : name );
|
||||
me.setAttribute( "id", ProjectJournal::idToSave( id() ) );
|
||||
me.setAttribute( "value", m_value );
|
||||
me.setAttribute( "scale_type", m_scaleType == Logarithmic ? "log" : "linear" );
|
||||
me.setAttribute( "scale_type", m_scaleType == ScaleType::Logarithmic ? "log" : "linear" );
|
||||
if(mustQuote) {
|
||||
me.setAttribute( "nodename", name );
|
||||
}
|
||||
@@ -136,11 +140,11 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
|
||||
// the discardMIDIConnections option is true.
|
||||
auto controllerType = m_controllerConnection
|
||||
? m_controllerConnection->getController()->type()
|
||||
: Controller::DummyController;
|
||||
: Controller::ControllerType::Dummy;
|
||||
bool skipMidiController = Engine::getSong()->isSavingProject()
|
||||
&& Engine::getSong()->getSaveOptions().discardMIDIConnections.value();
|
||||
if (m_controllerConnection && controllerType != Controller::DummyController
|
||||
&& !(skipMidiController && controllerType == Controller::MidiController))
|
||||
if (m_controllerConnection && controllerType != Controller::ControllerType::Dummy
|
||||
&& !(skipMidiController && controllerType == Controller::ControllerType::Midi))
|
||||
{
|
||||
QDomElement controllerElement;
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
@@ -260,18 +264,18 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
|
||||
{
|
||||
if( nodeElement.attribute( "scale_type" ) == "linear" )
|
||||
{
|
||||
setScaleType( Linear );
|
||||
setScaleType( ScaleType::Linear );
|
||||
}
|
||||
else if( nodeElement.attribute( "scale_type" ) == "log" )
|
||||
{
|
||||
setScaleType( Logarithmic );
|
||||
setScaleType( ScaleType::Logarithmic );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
setScaleType( Linear );
|
||||
setScaleType( ScaleType::Linear );
|
||||
|
||||
if( element.hasAttribute( name ) )
|
||||
// attribute => read the element's value from the attribute list
|
||||
@@ -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,13 +329,13 @@ 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 ) ) );
|
||||
}
|
||||
|
||||
|
||||
float AutomatableModel::scaledValue( float value ) const
|
||||
{
|
||||
return m_scaleType == Linear
|
||||
return m_scaleType == ScaleType::Linear
|
||||
? value
|
||||
: logToLinearScale<float>( ( value - minValue<float>() ) / m_range );
|
||||
}
|
||||
@@ -339,22 +343,9 @@ float AutomatableModel::scaledValue( float value ) const
|
||||
|
||||
float AutomatableModel::inverseScaledValue( float value ) const
|
||||
{
|
||||
return m_scaleType == Linear
|
||||
return m_scaleType == ScaleType::Linear
|
||||
? value
|
||||
: ::linearToLogScale( minValue<float>(), maxValue<float>(), value );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//! @todo: this should be moved into a maths header
|
||||
template<class T>
|
||||
void roundAt( T& value, const T& where, const T& step_size )
|
||||
{
|
||||
if( qAbs<float>( value - where )
|
||||
< typeInfo<float>::minEps() * qAbs<float>( step_size ) )
|
||||
{
|
||||
value = where;
|
||||
}
|
||||
: lmms::linearToLogScale( minValue<float>(), maxValue<float>(), value );
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +354,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 +362,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 +375,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;
|
||||
@@ -439,7 +431,7 @@ void AutomatableModel::setStep( const float step )
|
||||
|
||||
float AutomatableModel::fittedValue( float value ) const
|
||||
{
|
||||
value = qBound<float>( m_minValue, value, m_maxValue );
|
||||
value = std::clamp(value, m_minValue, m_maxValue);
|
||||
|
||||
if( m_step != 0 && m_hasStrictStepSize )
|
||||
{
|
||||
@@ -468,14 +460,15 @@ float AutomatableModel::fittedValue( float value ) const
|
||||
|
||||
void AutomatableModel::linkModel( AutomatableModel* model )
|
||||
{
|
||||
if( !m_linkedModels.contains( model ) && model != this )
|
||||
auto containsModel = std::find(m_linkedModels.begin(), m_linkedModels.end(), model) != m_linkedModels.end();
|
||||
if (!containsModel && model != this)
|
||||
{
|
||||
m_linkedModels.push_back( model );
|
||||
|
||||
if( !model->hasLinkedModels() )
|
||||
{
|
||||
QObject::connect( this, SIGNAL( dataChanged() ),
|
||||
model, SIGNAL( dataChanged() ), Qt::DirectConnection );
|
||||
QObject::connect( this, SIGNAL(dataChanged()),
|
||||
model, SIGNAL(dataChanged()), Qt::DirectConnection );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,7 +478,7 @@ void AutomatableModel::linkModel( AutomatableModel* model )
|
||||
|
||||
void AutomatableModel::unlinkModel( AutomatableModel* model )
|
||||
{
|
||||
AutoModelVector::Iterator it = std::find( m_linkedModels.begin(), m_linkedModels.end(), model );
|
||||
auto it = std::find(m_linkedModels.begin(), m_linkedModels.end(), model);
|
||||
if( it != m_linkedModels.end() )
|
||||
{
|
||||
m_linkedModels.erase( it );
|
||||
@@ -499,7 +492,8 @@ void AutomatableModel::unlinkModel( AutomatableModel* model )
|
||||
|
||||
void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 )
|
||||
{
|
||||
if (!model1->m_linkedModels.contains( model2 ) && model1 != model2)
|
||||
auto model1ContainsModel2 = std::find(model1->m_linkedModels.begin(), model1->m_linkedModels.end(), model2) != model1->m_linkedModels.end();
|
||||
if (!model1ContainsModel2 && model1 != model2)
|
||||
{
|
||||
// copy data
|
||||
model1->m_value = model2->m_value;
|
||||
@@ -546,9 +540,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();
|
||||
}
|
||||
@@ -564,10 +558,10 @@ float AutomatableModel::controllerValue( int frameOffset ) const
|
||||
float v = 0;
|
||||
switch(m_scaleType)
|
||||
{
|
||||
case Linear:
|
||||
case ScaleType::Linear:
|
||||
v = minValue<float>() + ( range() * controllerConnection()->currentValue( frameOffset ) );
|
||||
break;
|
||||
case Logarithmic:
|
||||
case ScaleType::Logarithmic:
|
||||
v = logToLinearScale(
|
||||
controllerConnection()->currentValue( frameOffset ));
|
||||
break;
|
||||
@@ -576,15 +570,15 @@ float AutomatableModel::controllerValue( int frameOffset ) const
|
||||
"lacks implementation for a scale type");
|
||||
break;
|
||||
}
|
||||
if( typeInfo<float>::isEqual( m_step, 1 ) && m_hasStrictStepSize )
|
||||
if (approximatelyEqual(m_step, 1) && m_hasStrictStepSize)
|
||||
{
|
||||
return qRound( v );
|
||||
return std::round(v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
AutomatableModel* lm = m_linkedModels.first();
|
||||
if( lm->controllerConnection() )
|
||||
AutomatableModel* lm = m_linkedModels.front();
|
||||
if (lm->controllerConnection() && lm->useControllerValue())
|
||||
{
|
||||
return fittedValue( lm->controllerValue( frameOffset ) );
|
||||
}
|
||||
@@ -601,28 +595,27 @@ 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();
|
||||
auto vb = m_controllerConnection->valueBuffer();
|
||||
if( vb )
|
||||
{
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
switch( m_scaleType )
|
||||
{
|
||||
case Linear:
|
||||
case ScaleType::Linear:
|
||||
for( int i = 0; i < m_valueBuffer.length(); i++ )
|
||||
{
|
||||
nvalues[i] = minValue<float>() + ( range() * values[i] );
|
||||
}
|
||||
break;
|
||||
case Logarithmic:
|
||||
case ScaleType::Logarithmic:
|
||||
for( int i = 0; i < m_valueBuffer.length(); i++ )
|
||||
{
|
||||
nvalues[i] = logToLinearScale( values[i] );
|
||||
@@ -638,23 +631,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.front();
|
||||
}
|
||||
if (lm && lm->controllerConnection() && lm->useControllerValue() &&
|
||||
lm->controllerConnection()->getController()->isSampleExact())
|
||||
{
|
||||
auto 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 )
|
||||
@@ -670,7 +668,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -681,7 +679,7 @@ void AutomatableModel::unlinkControllerConnection()
|
||||
m_controllerConnection->disconnect( this );
|
||||
}
|
||||
|
||||
m_controllerConnection = NULL;
|
||||
m_controllerConnection = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -710,65 +708,79 @@ void AutomatableModel::reset()
|
||||
|
||||
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
|
||||
auto clips = AutomationClip::clipsForModel(this);
|
||||
if (clips.empty())
|
||||
{
|
||||
// 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 time 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
|
||||
std::vector<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.push_back(clip); }
|
||||
}
|
||||
|
||||
AutomationPattern * latestPattern = NULL;
|
||||
AutomationClip * latestClip = nullptr;
|
||||
|
||||
if( ! patternsInRange.isEmpty() )
|
||||
if (!clipsInRange.empty())
|
||||
{
|
||||
// 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 time, 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>();
|
||||
return std::round(value() / step<float>()) * step<float>();
|
||||
}
|
||||
|
||||
|
||||
@@ -776,7 +788,7 @@ float FloatModel::getRoundedValue() const
|
||||
|
||||
int FloatModel::getDigitCount() const
|
||||
{
|
||||
float steptemp = step<float>();
|
||||
auto steptemp = step<float>();
|
||||
int digits = 0;
|
||||
while ( steptemp < 1 )
|
||||
{
|
||||
@@ -802,3 +814,6 @@ QString BoolModel::displayValue( const float val ) const
|
||||
{
|
||||
return QString::number( castValue<bool>( scaledValue( val ) ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
1259
src/core/AutomationClip.cpp
Normal file
1259
src/core/AutomationClip.cpp
Normal file
File diff suppressed because it is too large
Load Diff
117
src/core/AutomationNode.cpp
Normal file
117
src/core/AutomationNode.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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),
|
||||
m_lockedTangents(false)
|
||||
{
|
||||
}
|
||||
|
||||
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),
|
||||
m_lockedTangents(false)
|
||||
{
|
||||
}
|
||||
|
||||
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),
|
||||
m_lockedTangents(false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,916 +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( TimePos( 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( TimePos(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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::timeMapLength() const
|
||||
{
|
||||
TimePos one_bar = TimePos(1, 0);
|
||||
if (m_timeMap.isEmpty()) { return one_bar; }
|
||||
|
||||
timeMap::const_iterator it = m_timeMap.end();
|
||||
tick_t last_tick = static_cast<tick_t>((it-1).key());
|
||||
// if last_tick is 0 (single item at tick 0)
|
||||
// return length as a whole bar to prevent disappearing TCO
|
||||
if (last_tick == 0) { return one_bar; }
|
||||
|
||||
return TimePos(last_tick);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::updateLength()
|
||||
{
|
||||
// Do not resize down in case user manually extended up
|
||||
changeLength(qMax(length(), timeMapLength()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos AutomationPattern::putValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool ignoreSurroundingPoints )
|
||||
{
|
||||
cleanObjects();
|
||||
|
||||
TimePos 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 );
|
||||
|
||||
updateLength();
|
||||
|
||||
emit dataChanged();
|
||||
|
||||
return newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::removeValue( const TimePos & 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);
|
||||
|
||||
updateLength();
|
||||
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AutomationPattern::recordValue(TimePos 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
|
||||
*/
|
||||
TimePos AutomationPattern::setDragValue( const TimePos & time,
|
||||
const float value,
|
||||
const bool quantPos,
|
||||
const bool controlKey )
|
||||
{
|
||||
if( m_dragging == false )
|
||||
{
|
||||
TimePos 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 TimePos & _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 TimePos & _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( TimePos( (iterate + i).key() ) , tempValue, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempValue = max - valueAt( ( iterate + i ).key() );
|
||||
putValue( TimePos( (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( TimePos( length ) , tempValue, false);
|
||||
numPoints++;
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime = TimePos( length - ( iterate + i ).key() );
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
TimePos newTime;
|
||||
|
||||
if ( ( iterate + i ).key() <= length )
|
||||
{
|
||||
newTime = TimePos( length - ( iterate + i ).key() );
|
||||
}
|
||||
else
|
||||
{
|
||||
newTime = TimePos( ( iterate + i ).key() );
|
||||
}
|
||||
tempMap[newTime] = tempValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i = 0; i <= numPoints; i++ )
|
||||
{
|
||||
tempValue = valueAt( ( iterate + i ).key() );
|
||||
cleanObjects();
|
||||
TimePos newTime = TimePos( 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() ) );
|
||||
|
||||
if( usesCustomClipColor() )
|
||||
{
|
||||
_this.setAttribute( "color", color().name() );
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
if( _this.hasAttribute( "color" ) )
|
||||
{
|
||||
useCustomClipColor( true );
|
||||
setColor( _this.attribute( "color" ) );
|
||||
}
|
||||
|
||||
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,257 +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(TimePos start, fpp_t frames, f_cnt_t offset, int tcoNum)
|
||||
{
|
||||
bool notePlayed = false;
|
||||
|
||||
if (lengthOfBB(tcoNum) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
start = start % (lengthOfBB(tcoNum) * TimePos::ticksPerBar());
|
||||
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
if (t->play(start, frames, offset, tcoNum))
|
||||
{
|
||||
notePlayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return notePlayed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::updateAfterTrackAdd()
|
||||
{
|
||||
if (numOfBBs() == 0 && !Engine::getSong()->isLoadingProject())
|
||||
{
|
||||
Engine::getSong()->addBBTrack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bar_t BBTrackContainer::lengthOfBB(int bb) const
|
||||
{
|
||||
TimePos maxLength = TimePos::ticksPerBar();
|
||||
|
||||
const TrackList & tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
// Don't create TCOs here if they don't exist
|
||||
if (bb < t->numOfTCOs())
|
||||
{
|
||||
maxLength = qMax(maxLength, t->getTCO(bb)->length());
|
||||
}
|
||||
}
|
||||
|
||||
return maxLength.nextFullBar();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int BBTrackContainer::numOfBBs() const
|
||||
{
|
||||
return Engine::getSong()->countTracks(Track::BBTrack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::removeBB(int bb)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
delete t->getTCO(bb);
|
||||
t->removeBar(bb * DefaultTicksPerBar);
|
||||
}
|
||||
if (bb <= currentBB())
|
||||
{
|
||||
setCurrentBB(qMax(currentBB() - 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::swapBB(int bb1, int bb2)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
t->swapPositionOfTCOs(bb1, bb2);
|
||||
}
|
||||
updateComboBox();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::updateBBTrack(TrackContentObject * tco)
|
||||
{
|
||||
BBTrack * t = BBTrack::findBBTrack(tco->startPosition() / DefaultTicksPerBar);
|
||||
if (t != NULL)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::fixIncorrectPositions()
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
for (int i = 0; i < numOfBBs(); ++i)
|
||||
{
|
||||
t->getTCO(i)->movePosition(TimePos(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 curBB = currentBB();
|
||||
|
||||
m_bbComboBoxModel.clear();
|
||||
|
||||
for (int i = 0; i < numOfBBs(); ++i)
|
||||
{
|
||||
BBTrack * bbt = BBTrack::findBBTrack(i);
|
||||
m_bbComboBoxModel.addItem(bbt->name());
|
||||
}
|
||||
setCurrentBB(curBB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::currentBBChanged()
|
||||
{
|
||||
// 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::BBTrack)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void BBTrackContainer::createTCOsForBB(int bb)
|
||||
{
|
||||
TrackList tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
t->createTCOsForBB(bb);
|
||||
}
|
||||
}
|
||||
|
||||
AutomatedValueMap BBTrackContainer::automatedValuesAt(TimePos time, int tcoNum) const
|
||||
{
|
||||
Q_ASSERT(tcoNum >= 0);
|
||||
Q_ASSERT(time.getTicks() >= 0);
|
||||
|
||||
auto lengthBars = lengthOfBB(tcoNum);
|
||||
auto lengthTicks = lengthBars * TimePos::ticksPerBar();
|
||||
if (time > lengthTicks)
|
||||
{
|
||||
time = lengthTicks;
|
||||
}
|
||||
|
||||
return TrackContainer::automatedValuesAt(time + (TimePos::ticksPerBar() * tcoNum), tcoNum);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
WaveMipMap BandLimitedWave::s_waveforms[4] = { };
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
std::array<WaveMipMap, BandLimitedWave::NumWaveforms> BandLimitedWave::s_waveforms = { };
|
||||
bool BandLimitedWave::s_wavesGenerated = false;
|
||||
QString BandLimitedWave::s_wavetableDir = "";
|
||||
|
||||
@@ -46,11 +49,11 @@ QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap )
|
||||
|
||||
QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap )
|
||||
{
|
||||
sample_t sample;
|
||||
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
|
||||
{
|
||||
for( int i = 0; i < TLENS[tbl]; i++ )
|
||||
{
|
||||
sample_t sample;
|
||||
in >> sample;
|
||||
waveMipMap.setSampleAt( tbl, i, sample );
|
||||
}
|
||||
@@ -64,9 +67,8 @@ void BandLimitedWave::generateWaves()
|
||||
// don't generate if they already exist
|
||||
if( s_wavesGenerated ) return;
|
||||
|
||||
int i;
|
||||
|
||||
// set wavetable directory
|
||||
// set wavetable directory
|
||||
s_wavetableDir = "data:wavetables/";
|
||||
|
||||
// set wavetable files
|
||||
@@ -81,12 +83,12 @@ void BandLimitedWave::generateWaves()
|
||||
{
|
||||
saw_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &saw_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLSaw ];
|
||||
in >> s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)];
|
||||
saw_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -105,14 +107,14 @@ void BandLimitedWave::generateWaves()
|
||||
s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm++;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)].setSampleAt( i, ph, s );
|
||||
max = std::max(max, std::abs(s));
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
|
||||
sample_t s = s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)].sampleAt( i, ph ) / max;
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,12 +125,12 @@ void BandLimitedWave::generateWaves()
|
||||
{
|
||||
sqr_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &sqr_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLSquare ];
|
||||
in >> s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSquare)];
|
||||
sqr_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -147,14 +149,14 @@ void BandLimitedWave::generateWaves()
|
||||
s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSquare)].setSampleAt( i, ph, s );
|
||||
max = std::max(max, std::abs(s));
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
|
||||
sample_t s = s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSquare)].sampleAt( i, ph ) / max;
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSquare)].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,12 +166,12 @@ void BandLimitedWave::generateWaves()
|
||||
{
|
||||
tri_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &tri_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLTriangle ];
|
||||
in >> s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)];
|
||||
tri_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -189,14 +191,14 @@ void BandLimitedWave::generateWaves()
|
||||
( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
|
||||
harm += 2;
|
||||
} while( hlen > 2.0 );
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
max = qMax( max, qAbs( s ) );
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)].setSampleAt( i, ph, s );
|
||||
max = std::max(max, std::abs(s));
|
||||
}
|
||||
// normalize
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
|
||||
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
|
||||
sample_t s = s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)].sampleAt( i, ph ) / max;
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)].setSampleAt( i, ph, s );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,21 +209,21 @@ void BandLimitedWave::generateWaves()
|
||||
{
|
||||
moog_file.open( QIODevice::ReadOnly );
|
||||
QDataStream in( &moog_file );
|
||||
in >> s_waveforms[ BandLimitedWave::BLMoog ];
|
||||
in >> s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLMoog)];
|
||||
moog_file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
|
||||
for( int ph = 0; ph < len; ph++ )
|
||||
{
|
||||
const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
|
||||
const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
|
||||
const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
|
||||
s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
|
||||
const sample_t saw = s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)].sampleAt( i, sawph );
|
||||
const sample_t tri = s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)].sampleAt( i, ph );
|
||||
s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLMoog)].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,24 +251,26 @@ QFile moogfile( "path-to-wavetables/moog.bin" );
|
||||
|
||||
sawfile.open( QIODevice::WriteOnly );
|
||||
QDataStream sawout( &sawfile );
|
||||
sawout << s_waveforms[ BandLimitedWave::BLSaw ];
|
||||
sawout << s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSaw)];
|
||||
sawfile.close();
|
||||
|
||||
sqrfile.open( QIODevice::WriteOnly );
|
||||
QDataStream sqrout( &sqrfile );
|
||||
sqrout << s_waveforms[ BandLimitedWave::BLSquare ];
|
||||
sqrout << s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLSquare)];
|
||||
sqrfile.close();
|
||||
|
||||
trifile.open( QIODevice::WriteOnly );
|
||||
QDataStream triout( &trifile );
|
||||
triout << s_waveforms[ BandLimitedWave::BLTriangle ];
|
||||
triout << s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLTriangle)];
|
||||
trifile.close();
|
||||
|
||||
moogfile.open( QIODevice::WriteOnly );
|
||||
QDataStream moogout( &moogfile );
|
||||
moogout << s_waveforms[ BandLimitedWave::BLMoog ];
|
||||
moogout << s_waveforms[static_cast<std::size_t>(BandLimitedWave::Waveform::BLMoog)];
|
||||
moogfile.close();
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -26,23 +26,30 @@
|
||||
|
||||
#include "BufferPool.h"
|
||||
|
||||
#include "SampleFrame.h"
|
||||
|
||||
#include <cstring>
|
||||
#include "MemoryPool.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
static std::unique_ptr<_MemoryPool_Base> pool;
|
||||
const int BM_INITIAL_BUFFERS = 256;
|
||||
|
||||
void BufferPool::init( fpp_t framesPerPeriod )
|
||||
{
|
||||
pool.reset(new _MemoryPool_Base(framesPerPeriod * sizeof(sampleFrame), BM_INITIAL_BUFFERS));
|
||||
pool.reset(new _MemoryPool_Base(framesPerPeriod * sizeof(SampleFrame), BM_INITIAL_BUFFERS));
|
||||
}
|
||||
|
||||
sampleFrame * BufferPool::acquire()
|
||||
SampleFrame * BufferPool::acquire()
|
||||
{
|
||||
return reinterpret_cast<sampleFrame*>(pool->allocate());
|
||||
return reinterpret_cast<SampleFrame*>(pool->allocate());
|
||||
}
|
||||
|
||||
void BufferPool::release( sampleFrame * buf )
|
||||
void BufferPool::release( SampleFrame * buf )
|
||||
{
|
||||
pool->deallocate(buf);
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
set(LMMS_SRCS
|
||||
${LMMS_SRCS}
|
||||
|
||||
core/AudioEngine.cpp
|
||||
core/AudioEngineProfiler.cpp
|
||||
core/AudioEngineWorkerThread.cpp
|
||||
core/AudioResampler.cpp
|
||||
core/AutomatableModel.cpp
|
||||
core/AutomationPattern.cpp
|
||||
core/AutomationClip.cpp
|
||||
core/AutomationNode.cpp
|
||||
core/BandLimitedWave.cpp
|
||||
core/base64.cpp
|
||||
core/BBTrackContainer.cpp
|
||||
core/BufferPool.cpp
|
||||
core/Clipboard.cpp
|
||||
core/ComboBoxModel.cpp
|
||||
@@ -19,7 +23,8 @@ set(LMMS_SRCS
|
||||
core/Engine.cpp
|
||||
core/EnvelopeAndLfoParameters.cpp
|
||||
core/fft_helpers.cpp
|
||||
core/FxMixer.cpp
|
||||
core/FileSearch.cpp
|
||||
core/Mixer.cpp
|
||||
core/ImportFilter.cpp
|
||||
core/InlineAutomation.cpp
|
||||
core/Instrument.cpp
|
||||
@@ -27,19 +32,17 @@ 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/MemoryHelper.cpp
|
||||
core/MemoryManager.cpp
|
||||
core/MemoryPool.cpp
|
||||
core/MeterModel.cpp
|
||||
core/Metronome.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
|
||||
@@ -47,6 +50,8 @@ set(LMMS_SRCS
|
||||
core/NotePlayHandle.cpp
|
||||
core/Oscillator.cpp
|
||||
core/PathUtil.cpp
|
||||
core/PatternClip.cpp
|
||||
core/PatternStore.cpp
|
||||
core/PeakController.cpp
|
||||
core/PerfLog.cpp
|
||||
core/Piano.cpp
|
||||
@@ -61,17 +66,26 @@ set(LMMS_SRCS
|
||||
core/RemotePlugin.cpp
|
||||
core/RenderManager.cpp
|
||||
core/RingBuffer.cpp
|
||||
core/Sample.cpp
|
||||
core/SampleBuffer.cpp
|
||||
core/SampleClip.cpp
|
||||
core/SampleDecoder.cpp
|
||||
core/SamplePlayHandle.cpp
|
||||
core/SampleRecordHandle.cpp
|
||||
core/Scale.cpp
|
||||
core/LmmsSemaphore.cpp
|
||||
core/SerializingObject.cpp
|
||||
core/Song.cpp
|
||||
core/TempoSyncKnobModel.cpp
|
||||
core/ThreadPool.cpp
|
||||
core/Timeline.cpp
|
||||
core/TimePos.cpp
|
||||
core/ToolPlugin.cpp
|
||||
core/Track.cpp
|
||||
core/TrackContainer.cpp
|
||||
core/TrackContentObject.cpp
|
||||
core/UpgradeExtendedNoteRange.h
|
||||
core/UpgradeExtendedNoteRange.cpp
|
||||
core/Clip.cpp
|
||||
core/ValueBuffer.cpp
|
||||
core/VstSyncController.cpp
|
||||
core/StepRecorder.cpp
|
||||
@@ -104,6 +118,7 @@ set(LMMS_SRCS
|
||||
core/lv2/Lv2SubPluginFeatures.cpp
|
||||
core/lv2/Lv2UridCache.cpp
|
||||
core/lv2/Lv2UridMap.cpp
|
||||
core/lv2/Lv2Worker.cpp
|
||||
|
||||
core/midi/MidiAlsaRaw.cpp
|
||||
core/midi/MidiAlsaSeq.cpp
|
||||
|
||||
192
src/core/Clip.cpp
Normal file
192
src/core/Clip.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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}
|
||||
{
|
||||
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 = std::max(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::setColor(const std::optional<QColor>& color)
|
||||
{
|
||||
m_color = color;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
} // 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>
|
||||
*
|
||||
@@ -27,11 +27,11 @@
|
||||
#include <QMimeData>
|
||||
|
||||
#include "Clipboard.h"
|
||||
#include "JournallingObject.h"
|
||||
|
||||
|
||||
namespace Clipboard
|
||||
namespace lmms::Clipboard
|
||||
{
|
||||
|
||||
const QMimeData * getMimeData()
|
||||
{
|
||||
return QApplication::clipboard()->mimeData( QClipboard::Clipboard );
|
||||
@@ -50,7 +50,7 @@ namespace Clipboard
|
||||
|
||||
void copyString( const QString & str, MimeType mT )
|
||||
{
|
||||
QMimeData *content = new QMimeData;
|
||||
auto content = new QMimeData;
|
||||
|
||||
content->setData( mimeType( mT ), str.toUtf8() );
|
||||
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
|
||||
@@ -71,7 +71,7 @@ namespace Clipboard
|
||||
{
|
||||
QString finalString = key + ":" + value;
|
||||
|
||||
QMimeData *content = new QMimeData;
|
||||
auto content = new QMimeData;
|
||||
content->setData( mimeType( MimeType::StringPair ), finalString.toUtf8() );
|
||||
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
|
||||
}
|
||||
@@ -91,4 +91,6 @@ namespace Clipboard
|
||||
{
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QtCore/QTextStream>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "MainWindow.h"
|
||||
@@ -37,10 +37,14 @@
|
||||
|
||||
#include "lmmsversion.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
// Vector with all the upgrade methods
|
||||
const std::vector<ConfigManager::UpgradeMethod> ConfigManager::UPGRADE_METHODS = {
|
||||
&ConfigManager::upgrade_1_1_90 , &ConfigManager::upgrade_1_1_91
|
||||
&ConfigManager::upgrade_1_1_90 , &ConfigManager::upgrade_1_1_91,
|
||||
&ConfigManager::upgrade_1_2_2
|
||||
};
|
||||
|
||||
static inline QString ensureTrailingSlash(const QString & s )
|
||||
@@ -53,7 +57,7 @@ static inline QString ensureTrailingSlash(const QString & s )
|
||||
}
|
||||
|
||||
|
||||
ConfigManager * ConfigManager::s_instanceOfMe = NULL;
|
||||
ConfigManager * ConfigManager::s_instanceOfMe = nullptr;
|
||||
|
||||
|
||||
ConfigManager::ConfigManager() :
|
||||
@@ -73,9 +77,9 @@ ConfigManager::ConfigManager() :
|
||||
m_sf2Dir = m_workingDir + SF2_PATH;
|
||||
m_gigDir = m_workingDir + GIG_PATH;
|
||||
m_themeDir = defaultThemeDir();
|
||||
if (!qgetenv("LMMS_DATA_DIR").isEmpty())
|
||||
if (std::getenv("LMMS_DATA_DIR"))
|
||||
{
|
||||
QDir::addSearchPath("data", QString::fromLocal8Bit(qgetenv("LMMS_DATA_DIR")));
|
||||
QDir::addSearchPath("data", QString::fromLocal8Bit(std::getenv("LMMS_DATA_DIR")));
|
||||
}
|
||||
initDevelopmentWorkingDir();
|
||||
|
||||
@@ -119,16 +123,35 @@ void ConfigManager::upgrade_1_1_90()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConfigManager::upgrade_1_1_91()
|
||||
{
|
||||
// rename displaydbv to displaydbfs
|
||||
if (!value("app", "displaydbv").isNull()) {
|
||||
if (!value("app", "displaydbv").isNull())
|
||||
{
|
||||
setValue("app", "displaydbfs", value("app", "displaydbv"));
|
||||
deleteValue("app", "displaydbv");
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::upgrade_1_2_2()
|
||||
{
|
||||
// Since mixer has been renamed to audioengine, we need to transfer the
|
||||
// attributes from the old element to the new one
|
||||
std::vector<QString> attrs = {
|
||||
"audiodev", "mididev", "framesperaudiobuffer", "hqaudio", "samplerate"
|
||||
};
|
||||
|
||||
for (auto attr : attrs)
|
||||
{
|
||||
if (!value("mixer", attr).isNull())
|
||||
{
|
||||
setValue("audioengine", attr, value("mixer", attr));
|
||||
deleteValue("mixer", attr);
|
||||
}
|
||||
}
|
||||
|
||||
m_settings.remove("mixer");
|
||||
}
|
||||
|
||||
void ConfigManager::upgrade()
|
||||
{
|
||||
@@ -139,7 +162,8 @@ void ConfigManager::upgrade()
|
||||
}
|
||||
|
||||
// Runs all necessary upgrade methods
|
||||
std::for_each( UPGRADE_METHODS.begin() + m_configVersion, UPGRADE_METHODS.end(),
|
||||
std::size_t max = std::min(static_cast<std::size_t>(m_configVersion), UPGRADE_METHODS.size());
|
||||
std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(),
|
||||
[this](UpgradeMethod um)
|
||||
{
|
||||
(this->*um)();
|
||||
@@ -149,7 +173,7 @@ void ConfigManager::upgrade()
|
||||
ProjectVersion createdWith = m_version;
|
||||
|
||||
// Don't use old themes as they break the UI (i.e. 0.4 != 1.0, etc)
|
||||
if (createdWith.setCompareType(ProjectVersion::Minor) != LMMS_VERSION)
|
||||
if (createdWith.setCompareType(ProjectVersion::CompareType::Minor) != LMMS_VERSION)
|
||||
{
|
||||
m_themeDir = defaultThemeDir();
|
||||
}
|
||||
@@ -164,13 +188,17 @@ QString ConfigManager::defaultVersion() const
|
||||
return LMMS_VERSION;
|
||||
}
|
||||
|
||||
bool ConfigManager::enableBlockedPlugins()
|
||||
{
|
||||
const char* envVar = getenv("LMMS_ENABLE_BLOCKED_PLUGINS");
|
||||
return (envVar && *envVar);
|
||||
}
|
||||
|
||||
QStringList ConfigManager::availableVstEmbedMethods()
|
||||
{
|
||||
QStringList methods;
|
||||
methods.append("none");
|
||||
#if QT_VERSION >= 0x050100
|
||||
methods.append("qt");
|
||||
#endif
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
methods.append("win32");
|
||||
#endif
|
||||
@@ -310,33 +338,19 @@ void ConfigManager::addRecentlyOpenedProject(const QString & file)
|
||||
|
||||
|
||||
|
||||
const QString & ConfigManager::value(const QString & cls,
|
||||
const QString & attribute) const
|
||||
QString ConfigManager::value(const QString& cls, const QString& attribute, const QString& defaultVal) const
|
||||
{
|
||||
if(m_settings.contains(cls))
|
||||
if (m_settings.find(cls) != m_settings.end())
|
||||
{
|
||||
for(stringPairVector::const_iterator it =
|
||||
m_settings[cls].begin();
|
||||
it != m_settings[cls].end(); ++it)
|
||||
for (const auto& setting : m_settings[cls])
|
||||
{
|
||||
if((*it).first == attribute)
|
||||
if (setting.first == attribute)
|
||||
{
|
||||
return (*it).second ;
|
||||
return setting.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
static QString empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const QString & ConfigManager::value(const QString & cls,
|
||||
const QString & attribute,
|
||||
const QString & defaultVal) const
|
||||
{
|
||||
const QString & val = value(cls, attribute);
|
||||
return val.isEmpty() ? defaultVal : val;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -493,10 +507,10 @@ void ConfigManager::loadConfigFile(const QString & configFile)
|
||||
#endif
|
||||
setBackgroundPicFile(value("paths", "backgroundtheme"));
|
||||
}
|
||||
else if(gui)
|
||||
else if (gui::getGUI() != nullptr)
|
||||
{
|
||||
QMessageBox::warning(NULL, MainWindow::tr("Configuration file"),
|
||||
MainWindow::tr("Error while parsing configuration file at line %1:%2: %3").
|
||||
QMessageBox::warning(nullptr, gui::MainWindow::tr("Configuration file"),
|
||||
gui::MainWindow::tr("Error while parsing configuration file at line %1:%2: %3").
|
||||
arg(errorLine).
|
||||
arg(errorCol).
|
||||
arg(errorString));
|
||||
@@ -504,7 +518,7 @@ void ConfigManager::loadConfigFile(const QString & configFile)
|
||||
cfg_file.close();
|
||||
}
|
||||
|
||||
// Plugins are searched recursively, blacklist problematic locations
|
||||
// Plugins are searched recursively, block problematic locations
|
||||
if( m_vstDir.isEmpty() || m_vstDir == QDir::separator() || m_vstDir == "/" ||
|
||||
m_vstDir == ensureTrailingSlash( QDir::homePath() ) ||
|
||||
!QDir( m_vstDir ).exists() )
|
||||
@@ -542,13 +556,13 @@ void ConfigManager::loadConfigFile(const QString & configFile)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif // LMMS_HAVE_STK
|
||||
|
||||
upgrade();
|
||||
|
||||
QStringList searchPaths;
|
||||
if(! qgetenv("LMMS_THEME_PATH").isNull())
|
||||
searchPaths << qgetenv("LMMS_THEME_PATH");
|
||||
if (std::getenv("LMMS_THEME_PATH"))
|
||||
searchPaths << std::getenv("LMMS_THEME_PATH");
|
||||
searchPaths << themeDir() << defaultThemeDir();
|
||||
QDir::setSearchPaths("resources", searchPaths);
|
||||
|
||||
@@ -585,25 +599,22 @@ void ConfigManager::saveConfigFile()
|
||||
lmms_config.setAttribute("configversion", m_configVersion);
|
||||
doc.appendChild(lmms_config);
|
||||
|
||||
for(settingsMap::iterator it = m_settings.begin();
|
||||
it != m_settings.end(); ++it)
|
||||
for (auto it = m_settings.begin(); it != m_settings.end(); ++it)
|
||||
{
|
||||
QDomElement n = doc.createElement(it.key());
|
||||
for(stringPairVector::iterator it2 = (*it).begin();
|
||||
it2 != (*it).end(); ++it2)
|
||||
for (const auto& [first, second] : *it)
|
||||
{
|
||||
n.setAttribute((*it2).first, (*it2).second);
|
||||
n.setAttribute(first, second);
|
||||
}
|
||||
lmms_config.appendChild(n);
|
||||
}
|
||||
|
||||
QDomElement recent_files = doc.createElement("recentfiles");
|
||||
|
||||
for(QStringList::iterator it = m_recentlyOpenedProjects.begin();
|
||||
it != m_recentlyOpenedProjects.end(); ++it)
|
||||
for (const auto& recentlyOpenedProject : m_recentlyOpenedProjects)
|
||||
{
|
||||
QDomElement n = doc.createElement("file");
|
||||
n.setAttribute("path", *it);
|
||||
n.setAttribute("path", recentlyOpenedProject);
|
||||
recent_files.appendChild(n);
|
||||
}
|
||||
lmms_config.appendChild(recent_files);
|
||||
@@ -613,6 +624,8 @@ void ConfigManager::saveConfigFile()
|
||||
QFile outfile(m_lmmsRcFile);
|
||||
if(!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
using gui::MainWindow;
|
||||
|
||||
QString title, message;
|
||||
title = MainWindow::tr("Could not open file");
|
||||
message = MainWindow::tr("Could not open file %1 "
|
||||
@@ -622,9 +635,9 @@ void ConfigManager::saveConfigFile()
|
||||
"the directory containing the "
|
||||
"file and try again!"
|
||||
).arg(m_lmmsRcFile);
|
||||
if(gui)
|
||||
if (gui::getGUI() != nullptr)
|
||||
{
|
||||
QMessageBox::critical(NULL, title, message,
|
||||
QMessageBox::critical(nullptr, title, message,
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::NoButton);
|
||||
}
|
||||
@@ -698,7 +711,7 @@ unsigned int ConfigManager::legacyConfigVersion()
|
||||
{
|
||||
ProjectVersion createdWith = m_version;
|
||||
|
||||
createdWith.setCompareType(ProjectVersion::Build);
|
||||
createdWith.setCompareType(ProjectVersion::CompareType::Build);
|
||||
|
||||
if( createdWith < "1.1.90" )
|
||||
{
|
||||
@@ -713,3 +726,6 @@ unsigned int ConfigManager::legacyConfigVersion()
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,36 +25,37 @@
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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;
|
||||
std::vector<Controller*> Controller::s_controllers;
|
||||
|
||||
|
||||
|
||||
Controller::Controller( ControllerTypes _type, Model * _parent,
|
||||
Controller::Controller( ControllerType _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 )
|
||||
{
|
||||
if( _type != DummyController && _type != MidiController )
|
||||
if( _type != ControllerType::Dummy && _type != ControllerType::Midi )
|
||||
{
|
||||
s_controllers.append( this );
|
||||
s_controllers.push_back(this);
|
||||
// Determine which name to use
|
||||
for ( uint i=s_controllers.size(); ; i++ )
|
||||
{
|
||||
@@ -85,10 +86,10 @@ Controller::Controller( ControllerTypes _type, Model * _parent,
|
||||
|
||||
Controller::~Controller()
|
||||
{
|
||||
int idx = s_controllers.indexOf( this );
|
||||
if( idx >= 0 )
|
||||
auto it = std::find(s_controllers.begin(), s_controllers.end(), this);
|
||||
if (it != s_controllers.end())
|
||||
{
|
||||
s_controllers.remove( idx );
|
||||
s_controllers.erase(it);
|
||||
}
|
||||
|
||||
m_valueBuffer.clear();
|
||||
@@ -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()->outputSampleRate();
|
||||
}
|
||||
|
||||
|
||||
@@ -181,31 +182,31 @@ void Controller::resetFrameCounter()
|
||||
|
||||
|
||||
|
||||
Controller * Controller::create( ControllerTypes _ct, Model * _parent )
|
||||
Controller * Controller::create( ControllerType _ct, Model * _parent )
|
||||
{
|
||||
static Controller * dummy = NULL;
|
||||
Controller * c = NULL;
|
||||
static Controller * dummy = nullptr;
|
||||
Controller * c = nullptr;
|
||||
|
||||
switch( _ct )
|
||||
{
|
||||
case Controller::DummyController:
|
||||
case ControllerType::Dummy:
|
||||
if (!dummy)
|
||||
dummy = new Controller( DummyController, NULL,
|
||||
dummy = new Controller( ControllerType::Dummy, nullptr,
|
||||
QString() );
|
||||
c = dummy;
|
||||
break;
|
||||
|
||||
case Controller::LfoController:
|
||||
c = new ::LfoController( _parent );
|
||||
case ControllerType::Lfo:
|
||||
c = new class LfoController( _parent );
|
||||
break;
|
||||
|
||||
case Controller::PeakController:
|
||||
case ControllerType::Peak:
|
||||
//Already instantiated in EffectChain::loadSettings()
|
||||
Q_ASSERT( false );
|
||||
break;
|
||||
|
||||
case Controller::MidiController:
|
||||
c = new ::MidiController( _parent );
|
||||
case ControllerType::Midi:
|
||||
c = new class MidiController( _parent );
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -219,24 +220,12 @@ Controller * Controller::create( ControllerTypes _ct, Model * _parent )
|
||||
|
||||
Controller * Controller::create( const QDomElement & _this, Model * _parent )
|
||||
{
|
||||
Controller * c;
|
||||
if( _this.attribute( "type" ).toInt() == Controller::PeakController )
|
||||
{
|
||||
c = PeakController::getControllerBySetting( _this );
|
||||
}
|
||||
else
|
||||
{
|
||||
c = create(
|
||||
static_cast<ControllerTypes>( _this.attribute( "type" ).toInt() ),
|
||||
_parent );
|
||||
}
|
||||
|
||||
if( c != NULL )
|
||||
{
|
||||
c->restoreState( _this );
|
||||
}
|
||||
|
||||
return( c );
|
||||
const auto controllerType = static_cast<ControllerType>(_this.attribute("type").toInt());
|
||||
auto controller = controllerType == ControllerType::Peak
|
||||
? PeakController::getControllerBySetting(_this)
|
||||
: create(controllerType, _parent);
|
||||
if (controller) { controller->restoreState(_this); }
|
||||
return controller;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,8 +234,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 +243,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;
|
||||
}
|
||||
@@ -268,7 +257,7 @@ bool Controller::hasModel( const Model * m ) const
|
||||
|
||||
void Controller::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
_this.setAttribute( "type", type() );
|
||||
_this.setAttribute( "type", static_cast<int>(type()) );
|
||||
_this.setAttribute( "name", name() );
|
||||
}
|
||||
|
||||
@@ -276,7 +265,7 @@ void Controller::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
|
||||
void Controller::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
if( _this.attribute( "type" ).toInt() != type() )
|
||||
if( static_cast<ControllerType>(_this.attribute( "type" ).toInt()) != type() )
|
||||
{
|
||||
qWarning( "controller-type does not match controller-type of "
|
||||
"settings-node!\n" );
|
||||
@@ -293,9 +282,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 +314,7 @@ int Controller::connectionCount() const{
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,37 +31,40 @@
|
||||
#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 );
|
||||
m_controller = Controller::create( Controller::ControllerType::Dummy,
|
||||
nullptr );
|
||||
}
|
||||
s_connections.append( this );
|
||||
s_connections.push_back(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ControllerConnection::ControllerConnection( int _controllerId ) :
|
||||
m_controller( Controller::create( Controller::DummyController, NULL ) ),
|
||||
m_controller( Controller::create( Controller::ControllerType::Dummy, nullptr ) ),
|
||||
m_controllerId( _controllerId ),
|
||||
m_ownsController( false )
|
||||
{
|
||||
s_connections.append( this );
|
||||
s_connections.push_back(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,11 +72,14 @@ ControllerConnection::ControllerConnection( int _controllerId ) :
|
||||
|
||||
ControllerConnection::~ControllerConnection()
|
||||
{
|
||||
if( m_controller && m_controller->type() != Controller::DummyController )
|
||||
if( m_controller && m_controller->type() != Controller::ControllerType::Dummy )
|
||||
{
|
||||
m_controller->removeConnection( this );
|
||||
}
|
||||
s_connections.remove( s_connections.indexOf( this ) );
|
||||
|
||||
auto it = std::find(s_connections.begin(), s_connections.end(), this);
|
||||
if (it != s_connections.end()) { s_connections.erase(it); };
|
||||
|
||||
if( m_ownsController )
|
||||
{
|
||||
delete m_controller;
|
||||
@@ -95,17 +101,17 @@ 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 )
|
||||
if( m_controller && m_controller->type() != Controller::ControllerType::Dummy )
|
||||
{
|
||||
m_controller->removeConnection( this );
|
||||
}
|
||||
|
||||
if( !_controller )
|
||||
{
|
||||
m_controller = Controller::create( Controller::DummyController, NULL );
|
||||
m_controller = Controller::create( Controller::ControllerType::Dummy, nullptr );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,21 +119,21 @@ void ControllerConnection::setController( Controller * _controller )
|
||||
}
|
||||
m_controllerId = -1;
|
||||
|
||||
if( _controller->type() != Controller::DummyController )
|
||||
if( _controller->type() != Controller::ControllerType::Dummy )
|
||||
{
|
||||
_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::ControllerType::Midi);
|
||||
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,16 +159,15 @@ inline void ControllerConnection::setTargetName( const QString & _name )
|
||||
*/
|
||||
void ControllerConnection::finalizeConnections()
|
||||
{
|
||||
for( int i = 0; i < s_connections.size(); ++i )
|
||||
for (auto i = std::size_t{0}; i < s_connections.size(); ++i)
|
||||
{
|
||||
ControllerConnection * c = s_connections[i];
|
||||
if ( !c->isFinalized() && c->m_controllerId <
|
||||
Engine::getSong()->controllers().size() )
|
||||
if (!c->isFinalized() && static_cast<std::size_t>(c->m_controllerId) < Engine::getSong()->controllers().size())
|
||||
{
|
||||
c->setController( Engine::getSong()->
|
||||
controllers().at( c->m_controllerId ) );
|
||||
}
|
||||
else if (c->getController()->type() == Controller::DummyController)
|
||||
else if (c->getController()->type() == Controller::ControllerType::Dummy)
|
||||
{
|
||||
delete c;
|
||||
--i;
|
||||
@@ -183,10 +188,12 @@ void ControllerConnection::saveSettings( QDomDocument & _doc, QDomElement & _thi
|
||||
}
|
||||
else
|
||||
{
|
||||
int id = Engine::getSong()->controllers().indexOf( m_controller );
|
||||
if( id >= 0 )
|
||||
const auto& controllers = Engine::getSong()->controllers();
|
||||
const auto it = std::find(controllers.begin(), controllers.end(), m_controller);
|
||||
if (it != controllers.end())
|
||||
{
|
||||
_this.setAttribute( "id", id );
|
||||
const int id = std::distance(controllers.begin(), it);
|
||||
_this.setAttribute("id", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,14 +220,14 @@ void ControllerConnection::loadSettings( const QDomElement & _this )
|
||||
|
||||
if (!Engine::getSong()->isLoadingProject()
|
||||
&& m_controllerId != -1
|
||||
&& m_controllerId < Engine::getSong()->controllers().size())
|
||||
&& static_cast<std::size_t>(m_controllerId) < Engine::getSong()->controllers().size())
|
||||
{
|
||||
setController( Engine::getSong()->
|
||||
controllers().at( m_controllerId ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_controller = Controller::create( Controller::DummyController, NULL );
|
||||
m_controller = Controller::create( Controller::ControllerType::Dummy, nullptr );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,4 +240,4 @@ void ControllerConnection::deleteConnection()
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,13 +31,18 @@
|
||||
#include "EffectView.h"
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "SampleFrame.h"
|
||||
#include "lmms_constants.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,13 +54,19 @@ 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_wetDryModel.setCenterValue(0);
|
||||
|
||||
m_srcState[0] = m_srcState[1] = nullptr;
|
||||
reinitSRC();
|
||||
|
||||
|
||||
if( ConfigManager::inst()->value( "ui", "disableautoquit").toInt() )
|
||||
{
|
||||
m_autoQuitDisabled = true;
|
||||
}
|
||||
|
||||
// Call the virtual method onEnabledChanged so that effects can react to changes,
|
||||
// e.g. by resetting state.
|
||||
connect(&m_enabledModel, &BoolModel::dataChanged, [this] { onEnabledChanged(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +74,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +122,41 @@ void Effect::loadSettings( const QDomElement & _this )
|
||||
|
||||
|
||||
|
||||
bool Effect::processAudioBuffer(SampleFrame* buf, const fpp_t frames)
|
||||
{
|
||||
if (!isOkay() || dontRun() || !isEnabled() || !isRunning())
|
||||
{
|
||||
processBypassedImpl();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto status = processImpl(buf, frames);
|
||||
switch (status)
|
||||
{
|
||||
case ProcessStatus::Continue:
|
||||
break;
|
||||
case ProcessStatus::ContinueIfNotQuiet:
|
||||
{
|
||||
double outSum = 0.0;
|
||||
for (std::size_t idx = 0; idx < frames; ++idx)
|
||||
{
|
||||
outSum += buf[idx].sumOfSquaredAmplitudes();
|
||||
}
|
||||
|
||||
checkGate(outSum / frames);
|
||||
break;
|
||||
}
|
||||
case ProcessStatus::Sleep:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Effect * Effect::instantiate( const QString& pluginName,
|
||||
Model * _parent,
|
||||
@@ -118,10 +164,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,13 +175,13 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Effect::checkGate( double _out_sum )
|
||||
void Effect::checkGate(double outSum)
|
||||
{
|
||||
if( m_autoQuitDisabled )
|
||||
{
|
||||
@@ -144,7 +190,7 @@ void Effect::checkGate( double _out_sum )
|
||||
|
||||
// Check whether we need to continue processing input. Restart the
|
||||
// counter if the threshold has been exceeded.
|
||||
if( _out_sum - gate() <= typeInfo<float>::minEps() )
|
||||
if (outSum - gate() <= F_EPSILON)
|
||||
{
|
||||
incrementBufferCount();
|
||||
if( bufferCount() > timeout() )
|
||||
@@ -162,9 +208,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 +218,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" );
|
||||
}
|
||||
@@ -192,26 +236,27 @@ void Effect::reinitSRC()
|
||||
|
||||
|
||||
|
||||
void Effect::resample( int _i, const sampleFrame * _src_buf,
|
||||
void Effect::resample( int _i, const SampleFrame* _src_buf,
|
||||
sample_rate_t _src_sr,
|
||||
sampleFrame * _dst_buf, sample_rate_t _dst_sr,
|
||||
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].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;
|
||||
if( ( error = src_process( m_srcState[_i], &m_srcData[_i] ) ) )
|
||||
|
||||
if (int error = src_process(m_srcState[_i], &m_srcData[_i]))
|
||||
{
|
||||
qFatal( "Effect::resample(): error while resampling: %s\n",
|
||||
src_strerror( error ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,18 +25,21 @@
|
||||
|
||||
|
||||
#include <QDomElement>
|
||||
#include <cassert>
|
||||
|
||||
#include "EffectChain.h"
|
||||
#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" ) )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -54,14 +57,11 @@ EffectChain::~EffectChain()
|
||||
void EffectChain::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
m_enabledModel.saveSettings( _doc, _this, "enabled" );
|
||||
_this.setAttribute( "numofeffects", m_effects.count() );
|
||||
_this.setAttribute("numofeffects", static_cast<int>(m_effects.size()));
|
||||
|
||||
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 +78,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 +97,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 +121,9 @@ void EffectChain::loadSettings( const QDomElement & _this )
|
||||
|
||||
void EffectChain::appendEffect( Effect * _effect )
|
||||
{
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
m_effects.append( _effect );
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
m_effects.push_back(_effect);
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
m_enabledModel.setValue( true );
|
||||
|
||||
@@ -135,19 +135,19 @@ 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 );
|
||||
auto 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() )
|
||||
if (m_effects.empty())
|
||||
{
|
||||
m_enabledModel.setValue( false );
|
||||
}
|
||||
@@ -160,10 +160,11 @@ void EffectChain::removeEffect( Effect * _effect )
|
||||
|
||||
void EffectChain::moveDown( Effect * _effect )
|
||||
{
|
||||
if( _effect != m_effects.last() )
|
||||
if (_effect != m_effects.back())
|
||||
{
|
||||
int i = m_effects.indexOf(_effect);
|
||||
std::swap(m_effects[i + 1], m_effects[i]);
|
||||
auto it = std::find(m_effects.begin(), m_effects.end(), _effect);
|
||||
assert(it != m_effects.end());
|
||||
std::swap(*std::next(it), *it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,17 +173,18 @@ void EffectChain::moveDown( Effect * _effect )
|
||||
|
||||
void EffectChain::moveUp( Effect * _effect )
|
||||
{
|
||||
if( _effect != m_effects.first() )
|
||||
if (_effect != m_effects.front())
|
||||
{
|
||||
int i = m_effects.indexOf(_effect);
|
||||
std::swap(m_effects[i - 1], m_effects[i]);
|
||||
auto it = std::find(m_effects.begin(), m_effects.end(), _effect);
|
||||
assert(it != m_effects.end());
|
||||
std::swap(*std::prev(it), *it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, bool hasInputNoise )
|
||||
bool EffectChain::processAudioBuffer( SampleFrame* _buf, const fpp_t _frames, bool hasInputNoise )
|
||||
{
|
||||
if( m_enabledModel.value() == false )
|
||||
{
|
||||
@@ -192,12 +194,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 +216,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,16 +229,19 @@ void EffectChain::clear()
|
||||
{
|
||||
emit aboutToClear();
|
||||
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
|
||||
while( m_effects.count() )
|
||||
while (m_effects.size())
|
||||
{
|
||||
Effect * e = m_effects[m_effects.count() - 1];
|
||||
auto e = m_effects[m_effects.size() - 1];
|
||||
m_effects.pop_back();
|
||||
delete e;
|
||||
}
|
||||
|
||||
Engine::mixer()->doneChangeInModel();
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
|
||||
m_enabledModel.setValue( false );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,47 +24,53 @@
|
||||
|
||||
|
||||
#include "Engine.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "FxMixer.h"
|
||||
#include "Mixer.h"
|
||||
#include "Ladspa2LMMS.h"
|
||||
#include "Lv2Manager.h"
|
||||
#include "Mixer.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;
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
Lv2Manager * LmmsCore::s_lv2Manager = nullptr;
|
||||
#endif
|
||||
Ladspa2LMMS * LmmsCore::s_ladspaManager = NULL;
|
||||
void* LmmsCore::s_dndPluginKey = nullptr;
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
@@ -75,30 +81,30 @@ void LmmsCore::init( bool renderOnly )
|
||||
s_projectJournal->setJournalling( true );
|
||||
|
||||
emit engine->initProgress(tr("Opening audio and midi devices"));
|
||||
s_mixer->initDevices();
|
||||
s_audioEngine->initDevices();
|
||||
|
||||
PresetPreviewPlayHandle::init();
|
||||
|
||||
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_patternStore );
|
||||
|
||||
deleteHelper( &s_fxMixer );
|
||||
deleteHelper( &s_mixer );
|
||||
deleteHelper( &s_audioEngine );
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
deleteHelper( &s_lv2Manager );
|
||||
@@ -111,21 +117,16 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool LmmsCore::ignorePluginBlacklist()
|
||||
{
|
||||
const char* envVar = getenv("LMMS_IGNORE_BLACKLIST");
|
||||
return (envVar && *envVar);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float LmmsCore::framesPerTick(sample_rate_t sampleRate)
|
||||
float Engine::framesPerTick(sample_rate_t sampleRate)
|
||||
{
|
||||
return sampleRate * 60.0f * 4 /
|
||||
DefaultTicksPerBar / s_song->getTempo();
|
||||
@@ -134,16 +135,15 @@ float LmmsCore::framesPerTick(sample_rate_t sampleRate)
|
||||
|
||||
|
||||
|
||||
void LmmsCore::updateFramesPerTick()
|
||||
void Engine::updateFramesPerTick()
|
||||
{
|
||||
s_framesPerTick = s_mixer->processingSampleRate() * 60.0f * 4 /
|
||||
DefaultTicksPerBar / s_song->getTempo();
|
||||
s_framesPerTick = s_audioEngine->outputSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LmmsCore::setDndPluginKey(void *newKey)
|
||||
void Engine::setDndPluginKey(void *newKey)
|
||||
{
|
||||
Q_ASSERT(static_cast<Plugin::Descriptor::SubPluginFeatures::Key*>(newKey));
|
||||
s_dndPluginKey = newKey;
|
||||
@@ -152,7 +152,7 @@ void LmmsCore::setDndPluginKey(void *newKey)
|
||||
|
||||
|
||||
|
||||
void *LmmsCore::pickDndPluginKey()
|
||||
void *Engine::pickDndPluginKey()
|
||||
{
|
||||
return s_dndPluginKey;
|
||||
}
|
||||
@@ -160,4 +160,6 @@ void *LmmsCore::pickDndPluginKey()
|
||||
|
||||
|
||||
|
||||
LmmsCore * LmmsCore::s_instanceOfMe = NULL;
|
||||
Engine * Engine::s_instanceOfMe = nullptr;
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -22,13 +22,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "Oscillator.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Oscillator.h"
|
||||
#include "PathUtil.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
// how long should be each envelope-segment maximal (e.g. attack)?
|
||||
extern const float SECS_PER_ENV_SEGMENT = 5.0f;
|
||||
@@ -38,18 +45,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 +64,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,77 +97,77 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
|
||||
Model * _parent ) :
|
||||
Model( _parent ),
|
||||
m_used( false ),
|
||||
m_predelayModel( 0.0, 0.0, 2.0, 0.001, this, tr( "Env pre-delay" ) ),
|
||||
m_attackModel( 0.0, 0.0, 2.0, 0.001, this, tr( "Env attack" ) ),
|
||||
m_holdModel( 0.5, 0.0, 2.0, 0.001, this, tr( "Env hold" ) ),
|
||||
m_decayModel( 0.5, 0.0, 2.0, 0.001, this, tr( "Env decay" ) ),
|
||||
m_sustainModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Env sustain" ) ),
|
||||
m_releaseModel( 0.1, 0.0, 2.0, 0.001, this, tr( "Env release" ) ),
|
||||
m_amountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "Env mod amount" ) ),
|
||||
m_predelayModel(0.f, 0.f, 2.f, 0.001f, this, tr("Env pre-delay")),
|
||||
m_attackModel(0.f, 0.f, 2.f, 0.001f, this, tr("Env attack")),
|
||||
m_holdModel(0.5f, 0.f, 2.f, 0.001f, this, tr("Env hold")),
|
||||
m_decayModel(0.5f, 0.f, 2.f, 0.001f, this, tr("Env decay")),
|
||||
m_sustainModel(0.5f, 0.f, 1.f, 0.001f, this, tr("Env sustain")),
|
||||
m_releaseModel(0.1f, 0.f, 2.f, 0.001f, this, tr("Env release")),
|
||||
m_amountModel(0.f, -1.f, 1.f, 0.005f, this, tr("Env mod amount")),
|
||||
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" ) ),
|
||||
m_lfoAttackModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO attack" ) ),
|
||||
m_lfoSpeedModel( 0.1, 0.001, 1.0, 0.0001,
|
||||
SECS_PER_LFO_OSCILLATION * 1000.0, this,
|
||||
tr( "LFO frequency" ) ),
|
||||
m_lfoAmountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "LFO mod amount" ) ),
|
||||
m_lfoWaveModel( SineWave, 0, NumLfoShapes, this, tr( "LFO wave shape" ) ),
|
||||
m_lfoPredelayModel(0.f, 0.f, 1.f, 0.001f, this, tr("LFO pre-delay")),
|
||||
m_lfoAttackModel(0.f, 0.f, 1.f, 0.001f, this, tr("LFO attack")),
|
||||
m_lfoSpeedModel(0.1f, 0.001f, 1.f, 0.0001f,
|
||||
SECS_PER_LFO_OSCILLATION * 1000.f, this,
|
||||
tr("LFO frequency")),
|
||||
m_lfoAmountModel(0.f, -1.f, 1.f, 0.005f, this, tr("LFO mod amount")),
|
||||
m_lfoWaveModel( static_cast<int>(LfoShape::SineWave), 0, NumLfoShapes, this, tr( "LFO wave shape" ) ),
|
||||
m_x100Model( false, this, tr( "LFO frequency x 100" ) ),
|
||||
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 +200,7 @@ EnvelopeAndLfoParameters::~EnvelopeAndLfoParameters()
|
||||
if( instances()->isEmpty() )
|
||||
{
|
||||
delete instances();
|
||||
s_lfoInstances = NULL;
|
||||
s_lfoInstances = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,28 +213,28 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset )
|
||||
const float phase = frame / static_cast<float>(
|
||||
m_lfoOscillationFrames );
|
||||
sample_t shape_sample;
|
||||
switch( m_lfoWaveModel.value() )
|
||||
switch( static_cast<LfoShape>(m_lfoWaveModel.value()) )
|
||||
{
|
||||
case TriangleWave:
|
||||
case LfoShape::TriangleWave:
|
||||
shape_sample = Oscillator::triangleSample( phase );
|
||||
break;
|
||||
case SquareWave:
|
||||
case LfoShape::SquareWave:
|
||||
shape_sample = Oscillator::squareSample( phase );
|
||||
break;
|
||||
case SawWave:
|
||||
case LfoShape::SawWave:
|
||||
shape_sample = Oscillator::sawSample( phase );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
shape_sample = m_userWave.userWaveSample( phase );
|
||||
case LfoShape::UserDefinedWave:
|
||||
shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase);
|
||||
break;
|
||||
case RandomWave:
|
||||
case LfoShape::RandomWave:
|
||||
if( frame == 0 )
|
||||
{
|
||||
m_random = Oscillator::noiseSample( 0.0f );
|
||||
}
|
||||
shape_sample = m_random;
|
||||
break;
|
||||
case SineWave:
|
||||
case LfoShape::SineWave:
|
||||
default:
|
||||
shape_sample = Oscillator::sinSample( phase );
|
||||
break;
|
||||
@@ -243,7 +247,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 );
|
||||
@@ -274,7 +278,7 @@ inline void EnvelopeAndLfoParameters::fillLfoLevel( float * _buf,
|
||||
}
|
||||
|
||||
fpp_t offset = 0;
|
||||
const float lafI = 1.0f / qMax( minimumFrames, m_lfoAttackFrames );
|
||||
const float lafI = 1.0f / std::max(minimumFrames, m_lfoAttackFrames);
|
||||
for( ; offset < _frames && _frame < m_lfoAttackFrames; ++offset,
|
||||
++_frame )
|
||||
{
|
||||
@@ -295,11 +299,6 @@ void EnvelopeAndLfoParameters::fillLevel( float * _buf, f_cnt_t _frame,
|
||||
{
|
||||
QMutexLocker m(&m_paramMutex);
|
||||
|
||||
if( _frame < 0 || _release_begin < 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fillLfoLevel( _buf, _frame, _frames );
|
||||
|
||||
for( fpp_t offset = 0; offset < _frames; ++offset, ++_buf, ++_frame )
|
||||
@@ -354,7 +353,7 @@ void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc,
|
||||
m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" );
|
||||
m_x100Model.saveSettings( _doc, _parent, "x100" );
|
||||
m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" );
|
||||
_parent.setAttribute( "userwavefile", m_userWave.audioFile() );
|
||||
_parent.setAttribute("userwavefile", m_userWave->audioFile());
|
||||
}
|
||||
|
||||
|
||||
@@ -386,7 +385,14 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this )
|
||||
m_sustainModel.setValue( 1.0 - m_sustainModel.value() );
|
||||
}
|
||||
|
||||
m_userWave.setAudioFile( _this.attribute( "userwavefile" ) );
|
||||
if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
|
||||
{
|
||||
m_userWave = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
|
||||
}
|
||||
|
||||
updateSampleVars();
|
||||
}
|
||||
@@ -399,24 +405,21 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
QMutexLocker m(&m_paramMutex);
|
||||
|
||||
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
|
||||
Engine::mixer()->processingSampleRate();
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
// 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 attack_frames = std::max(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 *
|
||||
expKnobVal( m_decayModel.value() *
|
||||
( 1 - m_sustainModel.value() ) ) ) );
|
||||
const f_cnt_t decay_frames = std::max(minimumFrames,
|
||||
static_cast<f_cnt_t>(frames_per_env_seg *
|
||||
expKnobVal(m_decayModel.value() *
|
||||
(1 - m_sustainModel.value()))));
|
||||
|
||||
m_sustainLevel = m_sustainModel.value();
|
||||
m_amount = m_amountModel.value();
|
||||
@@ -433,7 +436,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
decay_frames;
|
||||
m_rFrames = static_cast<f_cnt_t>( frames_per_env_seg *
|
||||
expKnobVal( m_releaseModel.value() ) );
|
||||
m_rFrames = qMax( minimumFrames, m_rFrames );
|
||||
m_rFrames = std::max(minimumFrames, m_rFrames);
|
||||
|
||||
if( static_cast<int>( floorf( m_amount * 1000.0f ) ) == 0 )
|
||||
{
|
||||
@@ -501,7 +504,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
|
||||
|
||||
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
|
||||
Engine::mixer()->processingSampleRate();
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
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 +544,4 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
92
src/core/FileSearch.cpp
Normal file
92
src/core/FileSearch.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* FileSearch.cpp - File system search task
|
||||
*
|
||||
* Copyright (c) 2024 saker
|
||||
*
|
||||
* 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 "FileSearch.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <lmmsconfig.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace lmms {
|
||||
FileSearch::FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions,
|
||||
const QStringList& excludedPaths, QDir::Filters dirFilters, QDir::SortFlags sortFlags)
|
||||
: m_filter(filter)
|
||||
, m_paths(paths)
|
||||
, m_extensions(extensions)
|
||||
, m_excludedPaths(excludedPaths)
|
||||
, m_dirFilters(dirFilters)
|
||||
, m_sortFlags(sortFlags)
|
||||
{
|
||||
}
|
||||
|
||||
void FileSearch::operator()()
|
||||
{
|
||||
auto stack = QFileInfoList{};
|
||||
for (const auto& path : m_paths)
|
||||
{
|
||||
if (m_excludedPaths.contains(path)) { continue; }
|
||||
|
||||
auto dir = QDir{path};
|
||||
stack.append(dir.entryInfoList(m_dirFilters, m_sortFlags));
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
if (m_cancel.load(std::memory_order_relaxed)) { return; }
|
||||
|
||||
const auto info = stack.takeFirst();
|
||||
const auto entryPath = info.absoluteFilePath();
|
||||
if (m_excludedPaths.contains(entryPath)) { continue; }
|
||||
|
||||
const auto name = info.fileName();
|
||||
const auto validFile = info.isFile() && m_extensions.contains(info.suffix(), Qt::CaseInsensitive);
|
||||
const auto passesFilter = name.contains(m_filter, Qt::CaseInsensitive);
|
||||
|
||||
if ((validFile || info.isDir()) && passesFilter)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{MillisecondsBetweenResults});
|
||||
emit foundMatch(this, entryPath);
|
||||
}
|
||||
|
||||
if (info.isDir())
|
||||
{
|
||||
dir.setPath(entryPath);
|
||||
const auto entries = dir.entryInfoList(m_dirFilters, m_sortFlags);
|
||||
|
||||
// Reverse to maintain the sorting within this directory when popped
|
||||
std::for_each(entries.rbegin(), entries.rend(), [&stack](const auto& entry) { stack.push_front(entry); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit searchCompleted(this);
|
||||
}
|
||||
|
||||
void FileSearch::cancel()
|
||||
{
|
||||
m_cancel.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -1,829 +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 "BufferPool.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"
|
||||
#include "TrackContainer.h" // For TrackContainer::TrackList typedef
|
||||
|
||||
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_hasColor( false ),
|
||||
m_dependenciesMet(0)
|
||||
{
|
||||
MixHelpers::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() );
|
||||
}
|
||||
|
||||
// if m_lastSoloed was our index, reset it
|
||||
if (m_lastSoloed == index) { m_lastSoloed = -1; }
|
||||
// if m_lastSoloed is > delete index, it will move left
|
||||
else if (m_lastSoloed > index) { --m_lastSoloed; }
|
||||
|
||||
// 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;
|
||||
|
||||
// check if m_lastSoloed is one of our swaps
|
||||
if (m_lastSoloed == a) { m_lastSoloed = b; }
|
||||
else if (m_lastSoloed == b) { m_lastSoloed = a; }
|
||||
|
||||
// go through every instrument and adjust for the channel index change
|
||||
TrackContainer::TrackList songTrackList = Engine::getSong()->tracks();
|
||||
TrackContainer::TrackList bbTrackList = Engine::getBBTrackContainer()->tracks();
|
||||
|
||||
TrackContainer::TrackList trackLists[] = {songTrackList, bbTrackList};
|
||||
for(int tl=0; tl<2; ++tl)
|
||||
{
|
||||
TrackContainer::TrackList 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()
|
||||
{
|
||||
MixHelpers::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)
|
||||
{
|
||||
MixHelpers::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 );
|
||||
if( ch->m_hasColor ) fxch.setAttribute( "color", ch->m_color.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" );
|
||||
if( fxch.hasAttribute( "color" ) )
|
||||
{
|
||||
m_fxChannels[num]->m_hasColor = true;
|
||||
m_fxChannels[num]->m_color.setNamedColor( fxch.attribute( "color" ) );
|
||||
}
|
||||
|
||||
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::Type::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
|
||||
@@ -28,18 +28,25 @@
|
||||
|
||||
#include "DummyInstrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "lmms_basics.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),
|
||||
m_instrumentTrack( _instrument_track )
|
||||
const Descriptor::SubPluginFeatures::Key *key,
|
||||
Flags flags) :
|
||||
Plugin(_descriptor, nullptr/* _instrument_track*/, key),
|
||||
m_instrumentTrack( _instrument_track ),
|
||||
m_flags(flags)
|
||||
{
|
||||
}
|
||||
|
||||
void Instrument::play( sampleFrame * )
|
||||
void Instrument::play( SampleFrame* )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -83,10 +90,10 @@ bool Instrument::isFromTrack( const Track * _track ) const
|
||||
}
|
||||
|
||||
// helper function for Instrument::applyFadeIn
|
||||
static int countZeroCrossings(sampleFrame *buf, fpp_t start, fpp_t frames)
|
||||
static int countZeroCrossings(SampleFrame* buf, fpp_t start, fpp_t frames)
|
||||
{
|
||||
// zero point crossing counts of all channels
|
||||
int zeroCrossings[DEFAULT_CHANNELS] = {0};
|
||||
auto zeroCrossings = std::array<int, DEFAULT_CHANNELS>{};
|
||||
// maximum zero point crossing of all channels
|
||||
int maxZeroCrossings = 0;
|
||||
|
||||
@@ -122,7 +129,7 @@ fpp_t getFadeInLength(float maxLength, fpp_t frames, int zeroCrossings)
|
||||
}
|
||||
|
||||
|
||||
void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n)
|
||||
void Instrument::applyFadeIn(SampleFrame* buf, NotePlayHandle * n)
|
||||
{
|
||||
const static float MAX_FADE_IN_LENGTH = 85.0;
|
||||
f_cnt_t total = n->totalFramesPlayed();
|
||||
@@ -173,31 +180,40 @@ void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n)
|
||||
}
|
||||
}
|
||||
|
||||
void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n )
|
||||
void Instrument::applyRelease( SampleFrame* buf, const NotePlayHandle * _n )
|
||||
{
|
||||
const fpp_t frames = _n->framesLeftForCurrentPeriod();
|
||||
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
|
||||
const f_cnt_t fl = _n->framesLeft();
|
||||
if( fl <= desiredReleaseFrames()+fpp )
|
||||
const auto fpp = Engine::audioEngine()->framesPerPeriod();
|
||||
const auto releaseFrames = desiredReleaseFrames();
|
||||
|
||||
const auto endFrame = _n->framesLeft();
|
||||
const auto startFrame = endFrame - std::min(endFrame, releaseFrames);
|
||||
|
||||
for (auto f = startFrame; f < endFrame && f < fpp; f++)
|
||||
{
|
||||
for( fpp_t f = (fpp_t)( ( fl > desiredReleaseFrames() ) ?
|
||||
( qMax( fpp - desiredReleaseFrames(), 0 ) +
|
||||
fl % fpp ) : 0 ); f < frames; ++f )
|
||||
const float fac = (float)(endFrame - f) / (float)releaseFrames;
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ch++)
|
||||
{
|
||||
const float fac = (float)( fl-f-1 ) /
|
||||
desiredReleaseFrames();
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
{
|
||||
buf[f][ch] *= fac;
|
||||
}
|
||||
buf[f][ch] *= fac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float Instrument::computeReleaseTimeMsByFrameCount(f_cnt_t frames) const
|
||||
{
|
||||
return frames / getSampleRate() * 1000.;
|
||||
}
|
||||
|
||||
|
||||
sample_rate_t Instrument::getSampleRate() const
|
||||
{
|
||||
return Engine::audioEngine()->outputSampleRate();
|
||||
}
|
||||
|
||||
|
||||
QString Instrument::fullDisplayName() const
|
||||
{
|
||||
return instrumentTrack()->displayName();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -25,15 +25,22 @@
|
||||
#include <QDomElement>
|
||||
|
||||
#include "InstrumentFunctions.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "embed.h"
|
||||
#include "Engine.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Mixer.h"
|
||||
#include "PresetPreviewPlayHandle.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
InstrumentFunctionNoteStacking::ChordTable::Init InstrumentFunctionNoteStacking::ChordTable::s_initTable[] =
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
std::array<InstrumentFunctionNoteStacking::ChordTable::Init, InstrumentFunctionNoteStacking::NUM_CHORD_TABLES>
|
||||
InstrumentFunctionNoteStacking::ChordTable::s_initTable =
|
||||
std::array<InstrumentFunctionNoteStacking::ChordTable::Init, NUM_CHORD_TABLES>
|
||||
{{
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "octave" ), { 0, -1 } },
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Major" ), { 0, 4, 7, -1 } },
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Majb5" ), { 0, 4, 6, -1 } },
|
||||
@@ -136,7 +143,7 @@ InstrumentFunctionNoteStacking::ChordTable::Init InstrumentFunctionNoteStacking:
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "5" ), { 0, 7, -1 } },
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Phrygian dominant" ), { 0, 1, 4, 5, 7, 8, 10, -1 } },
|
||||
{ QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Persian" ), { 0, 1, 4, 5, 6, 8, 11, -1 } }
|
||||
} ;
|
||||
}};
|
||||
|
||||
|
||||
|
||||
@@ -173,14 +180,11 @@ bool InstrumentFunctionNoteStacking::Chord::hasSemiTone( int8_t semi_tone ) cons
|
||||
|
||||
|
||||
|
||||
InstrumentFunctionNoteStacking::ChordTable::ChordTable() :
|
||||
QVector<Chord>()
|
||||
InstrumentFunctionNoteStacking::ChordTable::ChordTable()
|
||||
{
|
||||
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 ) );
|
||||
m_chords.emplace_back(chord.m_name, chord.m_semiTones);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,10 +193,12 @@ InstrumentFunctionNoteStacking::ChordTable::ChordTable() :
|
||||
|
||||
const InstrumentFunctionNoteStacking::Chord & InstrumentFunctionNoteStacking::ChordTable::getByName( const QString & name, bool is_scale ) const
|
||||
{
|
||||
for( int i = 0; i < size(); i++ )
|
||||
for (const auto& chord : m_chords)
|
||||
{
|
||||
if( at( i ).getName() == name && is_scale == at( i ).isScale() )
|
||||
return at( i );
|
||||
if (chord.getName() == name && is_scale == chord.isScale())
|
||||
{
|
||||
return chord;
|
||||
}
|
||||
}
|
||||
|
||||
static Chord empty;
|
||||
@@ -209,19 +215,16 @@ InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent
|
||||
m_chordRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Chord range" ) )
|
||||
{
|
||||
const ChordTable & chord_table = ChordTable::getInstance();
|
||||
for( int i = 0; i < chord_table.size(); ++i )
|
||||
|
||||
for (const auto& chord : chord_table.chords())
|
||||
{
|
||||
m_chordsModel.addItem( chord_table[i].getName() );
|
||||
m_chordsModel.addItem(chord.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
InstrumentFunctionNoteStacking::~InstrumentFunctionNoteStacking()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -234,7 +237,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n )
|
||||
// at the same time we only add sub-notes if nothing of the note was
|
||||
// played yet, because otherwise we would add chord-subnotes every
|
||||
// time an audio-buffer is rendered...
|
||||
if( ( _n->origin() == NotePlayHandle::OriginArpeggio || ( _n->hasParent() == false && _n->instrumentTrack()->isArpeggioEnabled() == false ) ) &&
|
||||
if( ( _n->origin() == NotePlayHandle::Origin::Arpeggio || ( _n->hasParent() == false && _n->instrumentTrack()->isArpeggioEnabled() == false ) ) &&
|
||||
_n->totalFramesPlayed() == 0 &&
|
||||
m_chordsEnabledModel.value() == true && ! _n->isReleased() )
|
||||
{
|
||||
@@ -246,10 +249,10 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n )
|
||||
const int sub_note_key_base = base_note_key + octave_cnt * KeysPerOctave;
|
||||
|
||||
// process all notes in the chord
|
||||
for( int i = 0; i < chord_table[selected_chord].size(); ++i )
|
||||
for( int i = 0; i < chord_table.chords()[selected_chord].size(); ++i )
|
||||
{
|
||||
// add interval to sub-note-key
|
||||
const int sub_note_key = sub_note_key_base + (int) chord_table[selected_chord][i];
|
||||
const int sub_note_key = sub_note_key_base + (int) chord_table.chords()[selected_chord][i];
|
||||
// maybe we're out of range -> let's get outta
|
||||
// here!
|
||||
if( sub_note_key > NumKeys )
|
||||
@@ -261,9 +264,9 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n )
|
||||
|
||||
// create sub-note-play-handle, only note is
|
||||
// different
|
||||
Engine::mixer()->addPlayHandle(
|
||||
Engine::audioEngine()->addPlayHandle(
|
||||
NotePlayHandlePool.construct( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy,
|
||||
_n, -1, NotePlayHandle::OriginNoteStacking )
|
||||
_n, -1, NotePlayHandle::Origin::NoteStacking )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -301,6 +304,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" ) ),
|
||||
@@ -310,9 +314,9 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
|
||||
m_arpModeModel( this, tr( "Arpeggio mode" ) )
|
||||
{
|
||||
const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance();
|
||||
for( int i = 0; i < chord_table.size(); ++i )
|
||||
for (auto& chord : chord_table.chords())
|
||||
{
|
||||
m_arpModel.addItem( chord_table[i].getName() );
|
||||
m_arpModel.addItem(chord.getName());
|
||||
}
|
||||
|
||||
m_arpDirectionModel.addItem( tr( "Up" ), std::make_unique<PixmapLoader>( "arp_up" ) );
|
||||
@@ -320,7 +324,7 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
|
||||
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_arpDirectionModel.setInitValue( static_cast<float>(ArpDirection::Up) );
|
||||
|
||||
m_arpModeModel.addItem( tr( "Free" ), std::make_unique<PixmapLoader>( "arp_free" ) );
|
||||
m_arpModeModel.addItem( tr( "Sort" ), std::make_unique<PixmapLoader>( "arp_sort" ) );
|
||||
@@ -330,18 +334,13 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
|
||||
|
||||
|
||||
|
||||
InstrumentFunctionArpeggio::~InstrumentFunctionArpeggio()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
{
|
||||
const int base_note_key = _n->key();
|
||||
if( _n->origin() == NotePlayHandle::OriginArpeggio ||
|
||||
_n->origin() == NotePlayHandle::OriginNoteStacking ||
|
||||
if( _n->origin() == NotePlayHandle::Origin::Arpeggio ||
|
||||
_n->origin() == NotePlayHandle::Origin::NoteStacking ||
|
||||
!m_arpEnabledModel.value() ||
|
||||
_n->isReleased() )
|
||||
{
|
||||
@@ -352,10 +351,11 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
_n->setMasterNote();
|
||||
|
||||
const int selected_arp = m_arpModel.value();
|
||||
const auto arpMode = static_cast<ArpMode>(m_arpModeModel.value());
|
||||
|
||||
ConstNotePlayHandleList cnphv = NotePlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() );
|
||||
ConstNotePlayHandleList cnphv = NotePlayHandle::nphsOfInstrumentTrack(_n->instrumentTrack());
|
||||
|
||||
if( m_arpModeModel.value() != FreeMode && cnphv.size() == 0 )
|
||||
if(arpMode != ArpMode::Free && cnphv.size() == 0 )
|
||||
{
|
||||
// maybe we're playing only a preset-preview-note?
|
||||
cnphv = PresetPreviewPlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() );
|
||||
@@ -367,30 +367,45 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
}
|
||||
}
|
||||
|
||||
// avoid playing same key for all
|
||||
// currently playing notes if sort mode is enabled
|
||||
if (arpMode == ArpMode::Sort && _n != cnphv.first()) { return; }
|
||||
|
||||
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 total_range = range * cnphv.size();
|
||||
const int cur_chord_size = chord_table.chords()[selected_arp].size();
|
||||
const int total_chord_size = cur_chord_size * cnphv.size();
|
||||
// how many notes are in a single chord (multiplied by range)
|
||||
const int singleNoteRange = static_cast<int>(cur_chord_size * m_arpRangeModel.value() * m_arpRepeatsModel.value());
|
||||
// how many notes are in the final chord
|
||||
const int range = arpMode == ArpMode::Sort ? singleNoteRange * cnphv.size() : singleNoteRange;
|
||||
|
||||
if (arpMode == ArpMode::Sort)
|
||||
{
|
||||
std::sort(cnphv.begin(), cnphv.end(), [](const NotePlayHandle* a, const NotePlayHandle* b)
|
||||
{
|
||||
return a->key() < b->key();
|
||||
});
|
||||
}
|
||||
|
||||
// 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()->outputSampleRate());
|
||||
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
|
||||
// correctly... -> arp_frames frames silence at the start of every note!
|
||||
int cur_frame = ( ( m_arpModeModel.value() != FreeMode ) ?
|
||||
int cur_frame = (arpMode != ArpMode::Free ?
|
||||
cnphv.first()->totalFramesPlayed() :
|
||||
_n->totalFramesPlayed() ) + arp_frames - 1;
|
||||
_n->totalFramesPlayed()) + arp_frames - 1;
|
||||
// used for loop
|
||||
f_cnt_t frames_processed = ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->noteOffset() : _n->noteOffset();
|
||||
f_cnt_t frames_processed = arpMode != ArpMode::Free ? 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 or is the remaining time just
|
||||
// a short bit that we can discard?
|
||||
if( remaining_frames_for_cur_arp > Engine::mixer()->framesPerPeriod() ||
|
||||
if( remaining_frames_for_cur_arp > Engine::audioEngine()->framesPerPeriod() ||
|
||||
_n->frames() - _n->totalFramesPlayed() < arp_frames / 5 )
|
||||
{
|
||||
// then we don't have to do something!
|
||||
@@ -399,21 +414,10 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
|
||||
frames_processed += remaining_frames_for_cur_arp;
|
||||
|
||||
// in sorted mode: is it our turn or do we have to be quiet for
|
||||
// now?
|
||||
if( m_arpModeModel.value() == SortMode &&
|
||||
( ( cur_frame / arp_frames ) % total_range ) / range != (f_cnt_t) _n->index() )
|
||||
{
|
||||
// update counters
|
||||
frames_processed += arp_frames;
|
||||
cur_frame += arp_frames;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip notes randomly
|
||||
if( m_arpSkipModel.value() )
|
||||
{
|
||||
if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpSkipModel.value() )
|
||||
if (100 * static_cast<float>(rand()) / (static_cast<float>(RAND_MAX) + 1.0f) < m_arpSkipModel.value())
|
||||
{
|
||||
// update counters
|
||||
frames_processed += arp_frames;
|
||||
@@ -422,78 +426,84 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
}
|
||||
}
|
||||
|
||||
int dir = m_arpDirectionModel.value();
|
||||
auto dir = static_cast<ArpDirection>(m_arpDirectionModel.value());
|
||||
|
||||
// Miss notes randomly. We intercept int dir and abuse it
|
||||
// after need. :)
|
||||
|
||||
if( m_arpMissModel.value() )
|
||||
{
|
||||
if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpMissModel.value() )
|
||||
if (100 * static_cast<float>(rand()) / (static_cast<float>(RAND_MAX) + 1.0f) < m_arpMissModel.value())
|
||||
{
|
||||
dir = ArpDirRandom;
|
||||
dir = ArpDirection::Random;
|
||||
}
|
||||
}
|
||||
|
||||
int cur_arp_idx = 0;
|
||||
// process according to arpeggio-direction...
|
||||
if( dir == ArpDirUp )
|
||||
if (dir == ArpDirection::Up || dir == ArpDirection::Down)
|
||||
{
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % range;
|
||||
cur_arp_idx = (cur_frame / arp_frames) % range;
|
||||
}
|
||||
else if( dir == ArpDirDown )
|
||||
{
|
||||
cur_arp_idx = range - ( cur_frame / arp_frames ) %
|
||||
range - 1;
|
||||
}
|
||||
else if( dir == ArpDirUpAndDown && range > 1 )
|
||||
else if ((dir == ArpDirection::UpAndDown || dir == ArpDirection::DownAndUp) && range > 1)
|
||||
{
|
||||
// imagine, we had to play the arp once up and then
|
||||
// once down -> makes 2 * range possible notes...
|
||||
// because we don't play the lower and upper notes
|
||||
// twice, we have to subtract 2
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 );
|
||||
cur_arp_idx = (cur_frame / arp_frames) % (range * 2 - (2 * static_cast<int>(m_arpRepeatsModel.value())));
|
||||
// if greater than range, we have to play down...
|
||||
// looks like the code for arp_dir==DOWN... :)
|
||||
if( cur_arp_idx >= range )
|
||||
if (cur_arp_idx >= range)
|
||||
{
|
||||
cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1;
|
||||
cur_arp_idx = range - cur_arp_idx % (range - 1) - static_cast<int>(m_arpRepeatsModel.value());
|
||||
}
|
||||
}
|
||||
else if( dir == ArpDirDownAndUp && range > 1 )
|
||||
{
|
||||
// copied from ArpDirUpAndDown above
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 );
|
||||
// if greater than range, we have to play down...
|
||||
// looks like the code for arp_dir==DOWN... :)
|
||||
if( cur_arp_idx >= range )
|
||||
{
|
||||
cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1;
|
||||
}
|
||||
// inverts direction
|
||||
cur_arp_idx = range - cur_arp_idx - 1;
|
||||
}
|
||||
else if( dir == ArpDirRandom )
|
||||
else if( dir == ArpDirection::Random )
|
||||
{
|
||||
// just pick a random chord-index
|
||||
cur_arp_idx = (int)( range * ( (float) rand() / (float) RAND_MAX ) );
|
||||
cur_arp_idx = static_cast<int>(range * static_cast<float>(rand()) / static_cast<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 )
|
||||
if( m_arpCycleModel.value() && dir != ArpDirection::Random )
|
||||
{
|
||||
cur_arp_idx *= m_arpCycleModel.value() + 1;
|
||||
cur_arp_idx %= range;
|
||||
cur_arp_idx %= static_cast<int>(range / m_arpRepeatsModel.value());
|
||||
}
|
||||
|
||||
// If ArpDirection::Down or ArpDirection::DownAndUp, invert the final range.
|
||||
if (dir == ArpDirection::Down || dir == ArpDirection::DownAndUp)
|
||||
{
|
||||
cur_arp_idx = static_cast<int>(range / m_arpRepeatsModel.value()) - cur_arp_idx - 1;
|
||||
}
|
||||
|
||||
// now calculate final key for our arp-note
|
||||
const int sub_note_key = base_note_key + (cur_arp_idx / cur_chord_size ) *
|
||||
KeysPerOctave + chord_table[selected_arp][cur_arp_idx % cur_chord_size];
|
||||
int sub_note_key = 0;
|
||||
if (arpMode != ArpMode::Sort)
|
||||
{
|
||||
sub_note_key = base_note_key + (cur_arp_idx / cur_chord_size) *
|
||||
KeysPerOctave + chord_table.chords()[selected_arp][cur_arp_idx % cur_chord_size];
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto octaveDiv = std::div(cur_arp_idx, total_chord_size);
|
||||
const int octave = octaveDiv.quot;
|
||||
const auto arpDiv = std::div(octaveDiv.rem, cnphv.size());
|
||||
const int arpIndex = arpDiv.rem;
|
||||
const int chordIndex = arpDiv.quot;
|
||||
sub_note_key = cnphv[arpIndex]->key()
|
||||
+ chord_table.chords()[selected_arp][chordIndex]
|
||||
+ octave * KeysPerOctave;
|
||||
}
|
||||
|
||||
// range-checking
|
||||
if( sub_note_key >= NumKeys ||
|
||||
sub_note_key < 0 ||
|
||||
Engine::mixer()->criticalXRuns() )
|
||||
Engine::audioEngine()->criticalXRuns() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -502,13 +512,13 @@ 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(
|
||||
NotePlayHandlePool.construct( _n->instrumentTrack(),
|
||||
frames_processed,
|
||||
gated_frames,
|
||||
Note( TimePos( 0 ), TimePos( 0 ), sub_note_key, _n->getVolume(),
|
||||
_n->getPanning(), _n->detuning() ),
|
||||
_n, -1, NotePlayHandle::OriginArpeggio )
|
||||
_n, -1, NotePlayHandle::Origin::Arpeggio )
|
||||
);
|
||||
|
||||
// update counters
|
||||
@@ -525,6 +535,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" );
|
||||
@@ -542,6 +553,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" );
|
||||
@@ -550,3 +562,6 @@ void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this )
|
||||
m_arpDirectionModel.loadSettings( _this, "arpdir" );
|
||||
m_arpModeModel.loadSettings( _this, "arpmode" );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -24,11 +24,55 @@
|
||||
|
||||
|
||||
#include "InstrumentPlayHandle.h"
|
||||
#include "Instrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Engine.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) :
|
||||
PlayHandle( TypeInstrumentPlayHandle ),
|
||||
m_instrument( instrument )
|
||||
namespace lmms
|
||||
{
|
||||
setAudioPort( instrumentTrack->audioPort() );
|
||||
|
||||
|
||||
InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack) :
|
||||
PlayHandle(Type::InstrumentPlayHandle),
|
||||
m_instrument(instrument)
|
||||
{
|
||||
setAudioPort(instrumentTrack->audioPort());
|
||||
}
|
||||
|
||||
void InstrumentPlayHandle::play(SampleFrame* working_buffer)
|
||||
{
|
||||
InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack();
|
||||
|
||||
// ensure that all our nph's have been processed first
|
||||
auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true);
|
||||
|
||||
bool nphsLeft;
|
||||
do
|
||||
{
|
||||
nphsLeft = false;
|
||||
for (const auto& handle : nphv)
|
||||
{
|
||||
if (handle->state() != ThreadableJob::ProcessingState::Done && !handle->isFinished())
|
||||
{
|
||||
nphsLeft = true;
|
||||
const_cast<NotePlayHandle*>(handle)->process();
|
||||
}
|
||||
}
|
||||
}
|
||||
while (nphsLeft);
|
||||
|
||||
m_instrument->play(working_buffer);
|
||||
|
||||
// Process the audio buffer that the instrument has just worked on...
|
||||
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
|
||||
instrumentTrack->processAudioBuffer(working_buffer, frames, nullptr);
|
||||
}
|
||||
|
||||
bool InstrumentPlayHandle::isFromTrack(const Track* track) const
|
||||
{
|
||||
return m_instrument->isFromTrack(track);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -22,17 +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"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
const float CUT_FREQ_MULTIPLIER = 6000.0f;
|
||||
@@ -61,12 +64,12 @@ InstrumentSoundShaping::InstrumentSoundShaping(
|
||||
m_filterEnabledModel( false, this ),
|
||||
m_filterModel( this, tr( "Filter type" ) ),
|
||||
m_filterCutModel( 14000.0, 1.0, 14000.0, 1.0, this, tr( "Cutoff frequency" ) ),
|
||||
m_filterResModel( 0.5, BasicFilters<>::minQ(), 10.0, 0.01, this, tr( "Q/Resonance" ) )
|
||||
m_filterResModel(0.5f, BasicFilters<>::minQ(), 10.f, 0.01f, this, tr("Q/Resonance"))
|
||||
{
|
||||
for( int i = 0; i < NumTargets; ++i )
|
||||
for (auto i = std::size_t{0}; i < NumTargets; ++i)
|
||||
{
|
||||
float value_for_zero_amount = 0.0;
|
||||
if( i == Volume )
|
||||
if( static_cast<Target>(i) == Target::Volume )
|
||||
{
|
||||
value_for_zero_amount = 1.0;
|
||||
}
|
||||
@@ -104,11 +107,6 @@ InstrumentSoundShaping::InstrumentSoundShaping(
|
||||
|
||||
|
||||
|
||||
InstrumentSoundShaping::~InstrumentSoundShaping()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame )
|
||||
@@ -117,11 +115,11 @@ float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t fram
|
||||
|
||||
if( n->isReleased() == false )
|
||||
{
|
||||
envReleaseBegin += Engine::mixer()->framesPerPeriod();
|
||||
envReleaseBegin += Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
|
||||
float level;
|
||||
m_envLfoParameters[Volume]->fillLevel( &level, frame, envReleaseBegin, 1 );
|
||||
m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->fillLevel( &level, frame, envReleaseBegin, 1 );
|
||||
|
||||
return level;
|
||||
}
|
||||
@@ -129,7 +127,7 @@ float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t fram
|
||||
|
||||
|
||||
|
||||
void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
void InstrumentSoundShaping::processAudioBuffer( SampleFrame* buffer,
|
||||
const fpp_t frames,
|
||||
NotePlayHandle* n )
|
||||
{
|
||||
@@ -160,24 +158,24 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
|
||||
if( n->m_filter == nullptr )
|
||||
{
|
||||
n->m_filter = std::make_unique<BasicFilters<>>( Engine::mixer()->processingSampleRate() );
|
||||
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->outputSampleRate() );
|
||||
}
|
||||
n->m_filter->setFilterType( m_filterModel.value() );
|
||||
n->m_filter->setFilterType( static_cast<BasicFilters<>::FilterType>(m_filterModel.value()) );
|
||||
|
||||
if( m_envLfoParameters[Cut]->isUsed() )
|
||||
if( m_envLfoParameters[static_cast<std::size_t>(Target::Cut)]->isUsed() )
|
||||
{
|
||||
m_envLfoParameters[Cut]->fillLevel( cutBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
m_envLfoParameters[static_cast<std::size_t>(Target::Cut)]->fillLevel( cutBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
}
|
||||
if( m_envLfoParameters[Resonance]->isUsed() )
|
||||
if( m_envLfoParameters[static_cast<std::size_t>(Target::Resonance)]->isUsed() )
|
||||
{
|
||||
m_envLfoParameters[Resonance]->fillLevel( resBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
m_envLfoParameters[static_cast<std::size_t>(Target::Resonance)]->fillLevel( resBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
}
|
||||
|
||||
const float fcv = m_filterCutModel.value();
|
||||
const float frv = m_filterResModel.value();
|
||||
|
||||
if( m_envLfoParameters[Cut]->isUsed() &&
|
||||
m_envLfoParameters[Resonance]->isUsed() )
|
||||
if( m_envLfoParameters[static_cast<std::size_t>(Target::Cut)]->isUsed() &&
|
||||
m_envLfoParameters[static_cast<std::size_t>(Target::Resonance)]->isUsed() )
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
@@ -198,7 +196,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
||||
}
|
||||
}
|
||||
else if( m_envLfoParameters[Cut]->isUsed() )
|
||||
else if( m_envLfoParameters[static_cast<std::size_t>(Target::Cut)]->isUsed() )
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
@@ -215,7 +213,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 );
|
||||
}
|
||||
}
|
||||
else if( m_envLfoParameters[Resonance]->isUsed() )
|
||||
else if( m_envLfoParameters[static_cast<std::size_t>(Target::Resonance)]->isUsed() )
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
@@ -243,10 +241,10 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
}
|
||||
}
|
||||
|
||||
if( m_envLfoParameters[Volume]->isUsed() )
|
||||
if( m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->isUsed() )
|
||||
{
|
||||
QVarLengthArray<float> volBuffer(frames);
|
||||
m_envLfoParameters[Volume]->fillLevel( volBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->fillLevel( volBuffer.data(), envTotalFrames, envReleaseBegin, frames );
|
||||
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
@@ -257,7 +255,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
}
|
||||
}
|
||||
|
||||
/* else if( m_envLfoParameters[Volume]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() )
|
||||
/* else if( m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() )
|
||||
{
|
||||
// only use panning-envelope...
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
@@ -277,11 +275,11 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
|
||||
f_cnt_t InstrumentSoundShaping::envFrames( const bool _only_vol ) const
|
||||
{
|
||||
f_cnt_t ret_val = m_envLfoParameters[Volume]->PAHD_Frames();
|
||||
f_cnt_t ret_val = m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->PAHD_Frames();
|
||||
|
||||
if( _only_vol == false )
|
||||
{
|
||||
for( int i = Volume+1; i < NumTargets; ++i )
|
||||
for (auto i = static_cast<std::size_t>(Target::Volume) + 1; i < NumTargets; ++i)
|
||||
{
|
||||
if( m_envLfoParameters[i]->isUsed() &&
|
||||
m_envLfoParameters[i]->PAHD_Frames() > ret_val )
|
||||
@@ -305,21 +303,21 @@ f_cnt_t InstrumentSoundShaping::releaseFrames() const
|
||||
|
||||
f_cnt_t ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames();
|
||||
|
||||
if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::IsSingleStreamed ) )
|
||||
if (m_instrumentTrack->instrument()->isSingleStreamed())
|
||||
{
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
if( m_envLfoParameters[Volume]->isUsed() )
|
||||
if( m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->isUsed() )
|
||||
{
|
||||
return m_envLfoParameters[Volume]->releaseFrames();
|
||||
return m_envLfoParameters[static_cast<std::size_t>(Target::Volume)]->releaseFrames();
|
||||
}
|
||||
|
||||
for( int i = Volume+1; i < NumTargets; ++i )
|
||||
for (auto i = static_cast<std::size_t>(Target::Volume) + 1; i < NumTargets; ++i)
|
||||
{
|
||||
if( m_envLfoParameters[i]->isUsed() )
|
||||
{
|
||||
ret_val = qMax( ret_val, m_envLfoParameters[i]->releaseFrames() );
|
||||
ret_val = std::max(ret_val, m_envLfoParameters[i]->releaseFrames());
|
||||
}
|
||||
}
|
||||
return ret_val;
|
||||
@@ -335,7 +333,7 @@ void InstrumentSoundShaping::saveSettings( QDomDocument & _doc, QDomElement & _t
|
||||
m_filterResModel.saveSettings( _doc, _this, "fres" );
|
||||
m_filterEnabledModel.saveSettings( _doc, _this, "fwet" );
|
||||
|
||||
for( int i = 0; i < NumTargets; ++i )
|
||||
for (auto i = std::size_t{0}; i < NumTargets; ++i)
|
||||
{
|
||||
m_envLfoParameters[i]->saveState( _doc, _this ).setTagName(
|
||||
m_envLfoParameters[i]->nodeName() +
|
||||
@@ -358,7 +356,7 @@ void InstrumentSoundShaping::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
if( node.isElement() )
|
||||
{
|
||||
for( int i = 0; i < NumTargets; ++i )
|
||||
for (auto i = std::size_t{0}; i < NumTargets; ++i)
|
||||
{
|
||||
if( node.nodeName() ==
|
||||
m_envLfoParameters[i]->nodeName() +
|
||||
@@ -377,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,22 +27,25 @@
|
||||
#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 )
|
||||
if( desc->type == LadspaPluginType::Source )
|
||||
{
|
||||
m_instruments.append( qMakePair( getName( key ),
|
||||
key ) );
|
||||
}
|
||||
else if( desc->type == TRANSFER &&
|
||||
else if( desc->type == LadspaPluginType::Transfer &&
|
||||
( desc->inputChannels == desc->outputChannels &&
|
||||
( desc->inputChannels == 1 ||
|
||||
desc->inputChannels == 2 ||
|
||||
@@ -52,7 +55,7 @@ Ladspa2LMMS::Ladspa2LMMS()
|
||||
m_validEffects.append( qMakePair( getName( key ),
|
||||
key ) );
|
||||
}
|
||||
else if( desc->type == TRANSFER &&
|
||||
else if( desc->type == LadspaPluginType::Transfer &&
|
||||
( desc->inputChannels != desc->outputChannels ||
|
||||
( desc->inputChannels != 1 &&
|
||||
desc->inputChannels != 2 &&
|
||||
@@ -62,12 +65,12 @@ Ladspa2LMMS::Ladspa2LMMS()
|
||||
m_invalidEffects.append( qMakePair( getName( key ),
|
||||
key ) );
|
||||
}
|
||||
else if( desc->type == SINK )
|
||||
else if( desc->type == LadspaPluginType::Sink )
|
||||
{
|
||||
m_analysisTools.append( qMakePair( getName( key ),
|
||||
key ) );
|
||||
}
|
||||
else if( desc->type == OTHER )
|
||||
else if( desc->type == LadspaPluginType::Other )
|
||||
{
|
||||
m_otherPlugins.append( qMakePair( getName( key ),
|
||||
key ) );
|
||||
@@ -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,18 +46,18 @@ 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 );
|
||||
}
|
||||
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::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 );
|
||||
@@ -61,40 +66,40 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
m_toggledModel.setScaleLogarithmic( m_port->suggests_logscale );
|
||||
break;
|
||||
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
m_knobModel.setRange( static_cast<int>( m_port->max ),
|
||||
static_cast<int>( m_port->min ),
|
||||
1 + static_cast<int>( m_port->max -
|
||||
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;
|
||||
|
||||
case FLOATING:
|
||||
case BufferDataType::Floating:
|
||||
m_knobModel.setRange( m_port->min, m_port->max,
|
||||
( m_port->max - m_port->min )
|
||||
/ ( m_port->name.toUpper() == "GAIN"
|
||||
&& m_port->max == 10.0f ? 4000.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;
|
||||
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
m_tempoSyncKnobModel.setRange( m_port->min, m_port->max,
|
||||
( 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,24 +112,17 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
|
||||
|
||||
|
||||
|
||||
LadspaControl::~LadspaControl()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
LADSPA_Data LadspaControl::value()
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
return static_cast<LADSPA_Data>( m_toggledModel.value() );
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
return static_cast<LADSPA_Data>( m_knobModel.value() );
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
return static_cast<LADSPA_Data>( m_tempoSyncKnobModel.value() );
|
||||
default:
|
||||
qWarning( "LadspaControl::value(): BAD BAD BAD\n" );
|
||||
@@ -139,20 +137,20 @@ ValueBuffer * LadspaControl::valueBuffer()
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
return NULL;
|
||||
case FLOATING:
|
||||
case BufferDataType::Toggled:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
return nullptr;
|
||||
case BufferDataType::Floating:
|
||||
return m_knobModel.valueBuffer();
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
return m_tempoSyncKnobModel.valueBuffer();
|
||||
default:
|
||||
qWarning( "LadspaControl::valueBuffer(): BAD BAD BAD\n" );
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,17 +159,17 @@ void LadspaControl::setValue( LADSPA_Data _value )
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
m_toggledModel.setValue( static_cast<bool>( _value ) );
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
m_knobModel.setValue( static_cast<int>( _value ) );
|
||||
break;
|
||||
case FLOATING:
|
||||
case BufferDataType::Floating:
|
||||
m_knobModel.setValue( static_cast<float>( _value ) );
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
m_tempoSyncKnobModel.setValue( static_cast<float>(
|
||||
_value ) );
|
||||
break;
|
||||
@@ -196,15 +194,15 @@ void LadspaControl::saveSettings( QDomDocument& doc,
|
||||
}
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
m_toggledModel.saveSettings( doc, e, "data" );
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
m_knobModel.saveSettings( doc, e, "data" );
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
m_tempoSyncKnobModel.saveSettings( doc, e, "data" );
|
||||
break;
|
||||
default:
|
||||
@@ -232,15 +230,15 @@ void LadspaControl::loadSettings( const QDomElement& parent, const QString& name
|
||||
m_linkEnabledModel.setValue(m_linkEnabledModel.initValue());
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
m_toggledModel.setValue(m_toggledModel.initValue());
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
m_knobModel.setValue(m_knobModel.initValue());
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
m_tempoSyncKnobModel.setValue(m_tempoSyncKnobModel.initValue());
|
||||
break;
|
||||
default:
|
||||
@@ -267,15 +265,15 @@ void LadspaControl::loadSettings( const QDomElement& parent, const QString& name
|
||||
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
m_toggledModel.loadSettings( e, dataModelName );
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
m_knobModel.loadSettings( e, dataModelName );
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
m_tempoSyncKnobModel.loadSettings( e, dataModelName );
|
||||
break;
|
||||
default:
|
||||
@@ -292,15 +290,15 @@ void LadspaControl::linkControls( LadspaControl * _control )
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
BoolModel::linkModels( &m_toggledModel, _control->toggledModel() );
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
FloatModel::linkModels( &m_knobModel, _control->knobModel() );
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
TempoSyncKnobModel::linkModels( &m_tempoSyncKnobModel,
|
||||
_control->tempoSyncKnobModel() );
|
||||
break;
|
||||
@@ -343,15 +341,15 @@ void LadspaControl::unlinkControls( LadspaControl * _control )
|
||||
{
|
||||
switch( m_port->data_type )
|
||||
{
|
||||
case TOGGLED:
|
||||
case BufferDataType::Toggled:
|
||||
BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() );
|
||||
break;
|
||||
case INTEGER:
|
||||
case ENUM:
|
||||
case FLOATING:
|
||||
case BufferDataType::Integer:
|
||||
case BufferDataType::Enum:
|
||||
case BufferDataType::Floating:
|
||||
FloatModel::unlinkModels( &m_knobModel, _control->knobModel() );
|
||||
break;
|
||||
case TIME:
|
||||
case BufferDataType::Time:
|
||||
TempoSyncKnobModel::unlinkModels( &m_tempoSyncKnobModel,
|
||||
_control->tempoSyncKnobModel() );
|
||||
break;
|
||||
@@ -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,19 +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)
|
||||
{
|
||||
// Skip empty entries as QDir will interpret it as the working directory
|
||||
if ((*it).isEmpty()) { continue; }
|
||||
QDir directory( ( *it ) );
|
||||
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
|
||||
@@ -83,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() );
|
||||
@@ -100,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() );
|
||||
}
|
||||
@@ -113,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();
|
||||
@@ -123,17 +119,11 @@ LadspaManager::~LadspaManager()
|
||||
|
||||
|
||||
|
||||
ladspaManagerDescription * LadspaManager::getDescription(
|
||||
LadspaManagerDescription * LadspaManager::getDescription(
|
||||
const ladspa_key_t & _plugin )
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin ) )
|
||||
{
|
||||
return( m_ladspaManagerMap[_plugin] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( NULL );
|
||||
}
|
||||
auto const it = m_ladspaManagerMap.find(_plugin);
|
||||
return it != m_ladspaManagerMap.end() ? *it : nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,11 +133,7 @@ void LadspaManager::addPlugins(
|
||||
LADSPA_Descriptor_Function _descriptor_func,
|
||||
const QString & _file )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor;
|
||||
|
||||
for( long pluginIndex = 0;
|
||||
( descriptor = _descriptor_func( pluginIndex ) ) != NULL;
|
||||
++pluginIndex )
|
||||
for (long pluginIndex = 0; const auto descriptor = _descriptor_func(pluginIndex); ++pluginIndex)
|
||||
{
|
||||
ladspa_key_t key( _file, QString( descriptor->Label ) );
|
||||
if( m_ladspaManagerMap.contains( key ) )
|
||||
@@ -155,8 +141,7 @@ void LadspaManager::addPlugins(
|
||||
continue;
|
||||
}
|
||||
|
||||
ladspaManagerDescription * plugIn =
|
||||
new ladspaManagerDescription;
|
||||
auto plugIn = new LadspaManagerDescription;
|
||||
plugIn->descriptorFunction = _descriptor_func;
|
||||
plugIn->index = pluginIndex;
|
||||
plugIn->inputChannels = getPluginInputs( descriptor );
|
||||
@@ -164,21 +149,21 @@ void LadspaManager::addPlugins(
|
||||
|
||||
if( plugIn->inputChannels == 0 && plugIn->outputChannels > 0 )
|
||||
{
|
||||
plugIn->type = SOURCE;
|
||||
plugIn->type = LadspaPluginType::Source;
|
||||
}
|
||||
else if( plugIn->inputChannels > 0 &&
|
||||
plugIn->outputChannels > 0 )
|
||||
{
|
||||
plugIn->type = TRANSFER;
|
||||
plugIn->type = LadspaPluginType::Transfer;
|
||||
}
|
||||
else if( plugIn->inputChannels > 0 &&
|
||||
plugIn->outputChannels == 0 )
|
||||
{
|
||||
plugIn->type = SINK;
|
||||
plugIn->type = LadspaPluginType::Sink;
|
||||
}
|
||||
else
|
||||
{
|
||||
plugIn->type = OTHER;
|
||||
plugIn->type = LadspaPluginType::Other;
|
||||
}
|
||||
|
||||
m_ladspaManagerMap[key] = plugIn;
|
||||
@@ -244,7 +229,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)
|
||||
@@ -254,7 +239,7 @@ const LADSPA_PortRangeHint *LadspaManager::getPortRangeHint(const ladspa_key_t &
|
||||
{
|
||||
return( & descriptor->PortRangeHints[_port] );
|
||||
}
|
||||
return( NULL );
|
||||
return( nullptr );
|
||||
}
|
||||
|
||||
|
||||
@@ -528,24 +513,16 @@ bool LadspaManager::isInteger( const ladspa_key_t & _plugin,
|
||||
|
||||
bool LadspaManager::isEnum( const ladspa_key_t & _plugin, uint32_t _port )
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin )
|
||||
&& _port < getPortCount( _plugin ) )
|
||||
auto const * desc = getDescriptor(_plugin);
|
||||
if (desc && _port < desc->PortCount)
|
||||
{
|
||||
LADSPA_Descriptor_Function descriptorFunction =
|
||||
m_ladspaManagerMap[_plugin]->descriptorFunction;
|
||||
const LADSPA_Descriptor * descriptor =
|
||||
descriptorFunction(
|
||||
m_ladspaManagerMap[_plugin]->index );
|
||||
LADSPA_PortRangeHintDescriptor hintDescriptor =
|
||||
descriptor->PortRangeHints[_port].HintDescriptor;
|
||||
desc->PortRangeHints[_port].HintDescriptor;
|
||||
// This is an LMMS extension to ladspa
|
||||
return( LADSPA_IS_HINT_INTEGER( hintDescriptor ) &&
|
||||
LADSPA_IS_HINT_TOGGLED( hintDescriptor ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( false );
|
||||
return LADSPA_IS_HINT_INTEGER(hintDescriptor) && LADSPA_IS_HINT_TOGGLED(hintDescriptor);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -565,28 +542,26 @@ 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 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const LADSPA_Descriptor * LadspaManager::getDescriptor(
|
||||
const ladspa_key_t & _plugin )
|
||||
const LADSPA_Descriptor * LadspaManager::getDescriptor(const ladspa_key_t & _plugin)
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin ) )
|
||||
auto const it = m_ladspaManagerMap.find(_plugin);
|
||||
if (it != m_ladspaManagerMap.end())
|
||||
{
|
||||
LADSPA_Descriptor_Function descriptorFunction =
|
||||
m_ladspaManagerMap[_plugin]->descriptorFunction;
|
||||
const LADSPA_Descriptor * descriptor =
|
||||
descriptorFunction(
|
||||
m_ladspaManagerMap[_plugin]->index );
|
||||
return( descriptor );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( NULL );
|
||||
auto const plugin = *it;
|
||||
|
||||
LADSPA_Descriptor_Function descriptorFunction = plugin->descriptorFunction;
|
||||
const LADSPA_Descriptor* descriptor = descriptorFunction(plugin->index);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -599,7 +574,7 @@ LADSPA_Handle LadspaManager::instantiate(
|
||||
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
|
||||
return( descriptor ?
|
||||
( descriptor->instantiate )( descriptor, _sample_rate ) :
|
||||
NULL );
|
||||
nullptr );
|
||||
}
|
||||
|
||||
|
||||
@@ -611,7 +586,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 )
|
||||
@@ -628,7 +603,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 );
|
||||
@@ -644,7 +619,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 );
|
||||
@@ -660,8 +635,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 );
|
||||
@@ -677,8 +652,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 );
|
||||
@@ -693,7 +668,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 );
|
||||
@@ -708,10 +683,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
|
||||
|
||||
@@ -23,45 +23,50 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QObject>
|
||||
|
||||
|
||||
#include "Song.h"
|
||||
#include "Mixer.h"
|
||||
#include "LfoController.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "PathUtil.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
LfoController::LfoController( Model * _parent ) :
|
||||
Controller( Controller::LfoController, _parent, tr( "LFO Controller" ) ),
|
||||
m_baseModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Base value" ) ),
|
||||
m_speedModel( 2.0, 0.01, 20.0, 0.0001, 20000.0, this, tr( "Oscillator speed" ) ),
|
||||
m_amountModel( 1.0, -1.0, 1.0, 0.005, this, tr( "Oscillator amount" ) ),
|
||||
Controller( ControllerType::Lfo, _parent, tr( "LFO Controller" ) ),
|
||||
m_baseModel(0.5f, 0.f, 1.f, 0.001f, this, tr("Base value")),
|
||||
m_speedModel(2.f, 0.01f, 20.f, 0.0001f, 20000.f, this, tr("Oscillator speed")),
|
||||
m_amountModel(1.f, -1.f, 1.f, 0.005f, this, tr("Oscillator amount")),
|
||||
m_phaseModel( 0.0, 0.0, 360.0, 4.0, this, tr( "Oscillator phase" ) ),
|
||||
m_waveModel( Oscillator::SineWave, 0, Oscillator::NumWaveShapes,
|
||||
m_waveModel( static_cast<int>(Oscillator::WaveShape::Sine), 0, Oscillator::NumWaveShapes,
|
||||
this, tr( "Oscillator waveform" ) ),
|
||||
m_multiplierModel( 0, 0, 2, this, tr( "Frequency Multiplier" ) ),
|
||||
m_duration( 1000 ),
|
||||
m_phaseOffset( 0 ),
|
||||
m_currentPhase( 0 ),
|
||||
m_sampleFunction( &Oscillator::sinSample ),
|
||||
m_userDefSampleBuffer( new SampleBuffer )
|
||||
m_userDefSampleBuffer(std::make_shared<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();
|
||||
}
|
||||
@@ -71,7 +76,6 @@ LfoController::LfoController( Model * _parent ) :
|
||||
|
||||
LfoController::~LfoController()
|
||||
{
|
||||
sharedObject::unref( m_userDefSampleBuffer );
|
||||
m_baseModel.disconnect( this );
|
||||
m_speedModel.disconnect( this );
|
||||
m_amountModel.disconnect( this );
|
||||
@@ -85,13 +89,14 @@ void LfoController::updateValueBuffer()
|
||||
{
|
||||
m_phaseOffset = m_phaseModel.value() / 360.0;
|
||||
float phase = m_currentPhase + m_phaseOffset;
|
||||
float phasePrev = 0.0f;
|
||||
|
||||
// roll phase up until we're in sync with period counter
|
||||
m_bufferLastUpdated++;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -99,20 +104,45 @@ void LfoController::updateValueBuffer()
|
||||
ValueBuffer *amountBuffer = m_amountModel.valueBuffer();
|
||||
int amountInc = amountBuffer ? 1 : 0;
|
||||
float *amountPtr = amountBuffer ? &(amountBuffer->values()[ 0 ] ) : &amount;
|
||||
Oscillator::WaveShape waveshape = static_cast<Oscillator::WaveShape>(m_waveModel.value());
|
||||
|
||||
for( float& f : m_valueBuffer )
|
||||
{
|
||||
const float currentSample = m_sampleFunction != NULL
|
||||
? m_sampleFunction( phase )
|
||||
: m_userDefSampleBuffer->userWaveSample( phase );
|
||||
float currentSample = 0;
|
||||
switch (waveshape)
|
||||
{
|
||||
case Oscillator::WaveShape::WhiteNoise:
|
||||
{
|
||||
if (absFraction(phase) < absFraction(phasePrev))
|
||||
{
|
||||
// Resample when phase period has completed
|
||||
m_heldSample = m_sampleFunction(phase);
|
||||
}
|
||||
currentSample = m_heldSample;
|
||||
break;
|
||||
}
|
||||
case Oscillator::WaveShape::UserDefined:
|
||||
{
|
||||
currentSample = Oscillator::userWaveSample(m_userDefSampleBuffer.get(), phase);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (m_sampleFunction != nullptr)
|
||||
{
|
||||
currentSample = m_sampleFunction(phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f = qBound( 0.0f, m_baseModel.value() + ( *amountPtr * currentSample / 2.0f ), 1.0f );
|
||||
f = std::clamp(m_baseModel.value() + (*amountPtr * currentSample / 2.0f), 0.0f, 1.0f);
|
||||
|
||||
phasePrev = phase;
|
||||
phase += 1.0 / m_duration;
|
||||
amountPtr += amountInc;
|
||||
}
|
||||
|
||||
m_currentPhase = absFraction( phase - m_phaseOffset );
|
||||
m_currentPhase = absFraction(phase - m_phaseOffset);
|
||||
m_bufferLastUpdated = s_periods;
|
||||
}
|
||||
|
||||
@@ -125,7 +155,7 @@ void LfoController::updatePhase()
|
||||
|
||||
void LfoController::updateDuration()
|
||||
{
|
||||
float newDurationF = Engine::mixer()->processingSampleRate() * m_speedModel.value();
|
||||
float newDurationF = Engine::audioEngine()->outputSampleRate() * m_speedModel.value();
|
||||
|
||||
switch(m_multiplierModel.value() )
|
||||
{
|
||||
@@ -146,31 +176,32 @@ void LfoController::updateDuration()
|
||||
|
||||
void LfoController::updateSampleFunction()
|
||||
{
|
||||
switch( m_waveModel.value() )
|
||||
switch( static_cast<Oscillator::WaveShape>(m_waveModel.value()) )
|
||||
{
|
||||
case Oscillator::SineWave:
|
||||
case Oscillator::WaveShape::Sine:
|
||||
default:
|
||||
m_sampleFunction = &Oscillator::sinSample;
|
||||
break;
|
||||
case Oscillator::TriangleWave:
|
||||
case Oscillator::WaveShape::Triangle:
|
||||
m_sampleFunction = &Oscillator::triangleSample;
|
||||
break;
|
||||
case Oscillator::SawWave:
|
||||
case Oscillator::WaveShape::Saw:
|
||||
m_sampleFunction = &Oscillator::sawSample;
|
||||
break;
|
||||
case Oscillator::SquareWave:
|
||||
case Oscillator::WaveShape::Square:
|
||||
m_sampleFunction = &Oscillator::squareSample;
|
||||
break;
|
||||
case Oscillator::MoogSawWave:
|
||||
case Oscillator::WaveShape::MoogSaw:
|
||||
m_sampleFunction = &Oscillator::moogSawSample;
|
||||
break;
|
||||
case Oscillator::ExponentialWave:
|
||||
case Oscillator::WaveShape::Exponential:
|
||||
m_sampleFunction = &Oscillator::expSample;
|
||||
break;
|
||||
case Oscillator::WhiteNoise:
|
||||
case Oscillator::WaveShape::WhiteNoise:
|
||||
m_sampleFunction = &Oscillator::noiseSample;
|
||||
break;
|
||||
case Oscillator::UserDefinedWave:
|
||||
m_sampleFunction = NULL;
|
||||
case Oscillator::WaveShape::UserDefined:
|
||||
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:
|
||||
@@ -192,7 +223,7 @@ void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
m_phaseModel.saveSettings( _doc, _this, "phase" );
|
||||
m_waveModel.saveSettings( _doc, _this, "wave" );
|
||||
m_multiplierModel.saveSettings( _doc, _this, "multiplier" );
|
||||
_this.setAttribute( "userwavefile" , m_userDefSampleBuffer->audioFile() );
|
||||
_this.setAttribute("userwavefile", m_userDefSampleBuffer->audioFile());
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +238,15 @@ void LfoController::loadSettings( const QDomElement & _this )
|
||||
m_phaseModel.loadSettings( _this, "phase" );
|
||||
m_waveModel.loadSettings( _this, "wave" );
|
||||
m_multiplierModel.loadSettings( _this, "multiplier" );
|
||||
m_userDefSampleBuffer->setAudioFile( _this.attribute("userwavefile" ) );
|
||||
|
||||
if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
|
||||
{
|
||||
m_userDefSampleBuffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
|
||||
}
|
||||
|
||||
updateSampleFunction();
|
||||
}
|
||||
@@ -221,12 +260,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
|
||||
|
||||
@@ -22,16 +22,18 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LinkedModelGroups.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "LinkedModelGroups.h"
|
||||
|
||||
|
||||
#include "AutomatableModel.h"
|
||||
#include "stdshims.h"
|
||||
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
/*
|
||||
LinkedModelGroup
|
||||
@@ -138,17 +140,10 @@ bool LinkedModelGroup::containsModel(const QString &name) const
|
||||
|
||||
|
||||
|
||||
LinkedModelGroups::~LinkedModelGroups() {}
|
||||
|
||||
|
||||
|
||||
|
||||
void LinkedModelGroups::linkAllModels()
|
||||
{
|
||||
LinkedModelGroup* first = getGroup(0);
|
||||
LinkedModelGroup* cur;
|
||||
|
||||
for (std::size_t i = 1; (cur = getGroup(i)); ++i)
|
||||
for (size_t i = 1; auto cur = getGroup(i); ++i)
|
||||
{
|
||||
first->linkControls(cur);
|
||||
}
|
||||
@@ -175,11 +170,12 @@ void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that)
|
||||
void LinkedModelGroups::loadSettings(const QDomElement& that)
|
||||
{
|
||||
QDomElement models = that.firstChildElement("models");
|
||||
LinkedModelGroup* grp0;
|
||||
if (!models.isNull() && (grp0 = getGroup(0)))
|
||||
if (auto grp0 = getGroup(0); !models.isNull() && grp0)
|
||||
{
|
||||
// only load the first group, the others are linked to the first
|
||||
grp0->loadValues(models);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
143
src/core/LmmsSemaphore.cpp
Normal file
143
src/core/LmmsSemaphore.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Semaphore.cpp - Semaphore implementation
|
||||
*
|
||||
* Copyright (c) 2022-2022 Johannes Lorenz <jlsf2013$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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code has been copied and adapted from https://github.com/drobilla/jalv
|
||||
* File src/zix/sem.h
|
||||
*/
|
||||
|
||||
#include "LmmsSemaphore.h"
|
||||
|
||||
#if defined(LMMS_BUILD_WIN32)
|
||||
# include <limits.h>
|
||||
#else
|
||||
# include <errno.h>
|
||||
#endif
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
#ifdef LMMS_BUILD_APPLE
|
||||
Semaphore::Semaphore(unsigned val)
|
||||
{
|
||||
kern_return_t rval = semaphore_create(mach_task_self(), &m_sem, SYNC_POLICY_FIFO, val);
|
||||
if(rval != 0) {
|
||||
throw std::system_error(rval, std::system_category(), "Could not create semaphore");
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
semaphore_destroy(mach_task_self(), m_sem);
|
||||
}
|
||||
|
||||
void Semaphore::post()
|
||||
{
|
||||
semaphore_signal(m_sem);
|
||||
}
|
||||
|
||||
void Semaphore::wait()
|
||||
{
|
||||
kern_return_t rval = semaphore_wait(m_sem);
|
||||
if (rval != KERN_SUCCESS) {
|
||||
throw std::system_error(rval, std::system_category(), "Waiting for semaphore failed");
|
||||
}
|
||||
}
|
||||
|
||||
bool Semaphore::tryWait()
|
||||
{
|
||||
const mach_timespec_t zero = { 0, 0 };
|
||||
return semaphore_timedwait(m_sem, zero) == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
Semaphore::Semaphore(unsigned initial)
|
||||
{
|
||||
if(CreateSemaphore(nullptr, initial, LONG_MAX, nullptr) == nullptr) {
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Could not create semaphore");
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
CloseHandle(m_sem);
|
||||
}
|
||||
|
||||
void Semaphore::post()
|
||||
{
|
||||
ReleaseSemaphore(m_sem, 1, nullptr);
|
||||
}
|
||||
|
||||
void Semaphore::wait()
|
||||
{
|
||||
if (WaitForSingleObject(m_sem, INFINITE) != WAIT_OBJECT_0) {
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Waiting for semaphore failed");
|
||||
}
|
||||
}
|
||||
|
||||
bool Semaphore::tryWait()
|
||||
{
|
||||
return WaitForSingleObject(m_sem, 0) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
#else /* !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_WIN32) */
|
||||
|
||||
Semaphore::Semaphore(unsigned initial)
|
||||
{
|
||||
if(sem_init(&m_sem, 0, initial) != 0) {
|
||||
throw std::system_error(errno, std::generic_category(), "Could not create semaphore");
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
sem_destroy(&m_sem);
|
||||
}
|
||||
|
||||
void Semaphore::post()
|
||||
{
|
||||
sem_post(&m_sem);
|
||||
}
|
||||
|
||||
void Semaphore::wait()
|
||||
{
|
||||
while (sem_wait(&m_sem) != 0) {
|
||||
if (errno != EINTR) {
|
||||
throw std::system_error(errno, std::generic_category(), "Waiting for semaphore failed");
|
||||
}
|
||||
/* Otherwise, interrupted, so try again. */
|
||||
}
|
||||
}
|
||||
|
||||
bool Semaphore::tryWait()
|
||||
{
|
||||
return (sem_trywait(&m_sem) == 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Simon Symeonidis <lethaljellybean/at/gmail/com>
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "lmms_basics.h"
|
||||
#include "MemoryHelper.h"
|
||||
|
||||
/**
|
||||
* Allocate a number of bytes and return them.
|
||||
* @param byteNum is the number of bytes
|
||||
*/
|
||||
void* MemoryHelper::alignedMalloc( size_t byteNum )
|
||||
{
|
||||
char *ptr, *ptr2, *aligned_ptr;
|
||||
int align_mask = ALIGN_SIZE - 1;
|
||||
|
||||
ptr = static_cast<char*>( malloc( byteNum + ALIGN_SIZE + sizeof( int ) ) );
|
||||
|
||||
if( ptr == NULL ) return NULL;
|
||||
|
||||
ptr2 = ptr + sizeof( int );
|
||||
aligned_ptr = ptr2 + ( ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
|
||||
|
||||
ptr2 = aligned_ptr - sizeof( int );
|
||||
*( ( int* ) ptr2 ) = ( int )( aligned_ptr - ptr );
|
||||
|
||||
return aligned_ptr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Free an aligned buffer
|
||||
* @param _buffer is the buffer to free
|
||||
*/
|
||||
void MemoryHelper::alignedFree( void* _buffer )
|
||||
{
|
||||
if( _buffer )
|
||||
{
|
||||
int *ptr2 = static_cast<int*>( _buffer ) - 1;
|
||||
_buffer = static_cast<char*>( _buffer ) - *ptr2;
|
||||
free( _buffer );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* MemoryManager.cpp
|
||||
*
|
||||
* Copyright (c) 2017 Lukas W <lukaswhl/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 "MemoryManager.h"
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include "rpmalloc.h"
|
||||
|
||||
/// Global static object handling rpmalloc intializing and finalizing
|
||||
struct MemoryManagerGlobalGuard {
|
||||
MemoryManagerGlobalGuard() {
|
||||
rpmalloc_initialize();
|
||||
}
|
||||
~MemoryManagerGlobalGuard() {
|
||||
rpmalloc_finalize();
|
||||
}
|
||||
} static mm_global_guard;
|
||||
|
||||
|
||||
namespace {
|
||||
static thread_local size_t thread_guard_depth;
|
||||
}
|
||||
|
||||
MemoryManager::ThreadGuard::ThreadGuard()
|
||||
{
|
||||
if (thread_guard_depth++ == 0) {
|
||||
rpmalloc_thread_initialize();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryManager::ThreadGuard::~ThreadGuard()
|
||||
{
|
||||
if (--thread_guard_depth == 0) {
|
||||
rpmalloc_thread_finalize();
|
||||
}
|
||||
}
|
||||
|
||||
static thread_local MemoryManager::ThreadGuard local_mm_thread_guard{};
|
||||
|
||||
void* MemoryManager::alloc(size_t size)
|
||||
{
|
||||
// Reference local thread guard to ensure it is initialized.
|
||||
// Compilers may optimize the instance away otherwise.
|
||||
Q_UNUSED(&local_mm_thread_guard);
|
||||
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::alloc", "Thread not initialized");
|
||||
return rpmalloc(size);
|
||||
}
|
||||
|
||||
|
||||
void MemoryManager::free(void * ptr)
|
||||
{
|
||||
Q_UNUSED(&local_mm_thread_guard);
|
||||
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::free", "Thread not initialized");
|
||||
return rpfree(ptr);
|
||||
}
|
||||
@@ -17,11 +17,11 @@
|
||||
#include "libcds.h"
|
||||
#include <cds/container/vyukov_mpmc_cycle_queue.h>
|
||||
|
||||
#include "MemoryManager.h"
|
||||
|
||||
class _MemoryPool_Private : MmAllocator<char>
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
class _MemoryPool_Private
|
||||
{
|
||||
using Alloc = MmAllocator<char>;
|
||||
public:
|
||||
_MemoryPool_Private(size_t size, size_t nmemb)
|
||||
: m_elementSize(size)
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
char* ptr = nullptr;
|
||||
while (m_freelist.pop(ptr)) {
|
||||
if (! is_from_pool(ptr)) {
|
||||
Alloc::deallocate(ptr, m_elementSize);
|
||||
delete[] ptr;
|
||||
}
|
||||
}
|
||||
delete[] m_buffer;
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
return ptr;
|
||||
} else {
|
||||
qWarning() << "MemoryPool exhausted";
|
||||
return Alloc::allocate(m_elementSize);
|
||||
return new char[m_elementSize];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +78,11 @@ public:
|
||||
private:
|
||||
void* do_allocate()
|
||||
{
|
||||
return Alloc::allocate(m_elementSize);
|
||||
return new char[m_elementSize];
|
||||
}
|
||||
void do_deallocate(void* ptr)
|
||||
{
|
||||
Alloc::deallocate(reinterpret_cast<char*>(ptr), m_elementSize);
|
||||
delete[] reinterpret_cast<char*>(ptr);
|
||||
}
|
||||
|
||||
bool is_from_pool(void* ptr)
|
||||
@@ -121,3 +121,5 @@ void _MemoryPool_Base::deallocate(void * ptr)
|
||||
return _imp->deallocate(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
|
||||
|
||||
41
src/core/Metronome.cpp
Normal file
41
src/core/Metronome.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Metronome.cpp
|
||||
*
|
||||
* Copyright (c) 2024 saker
|
||||
*
|
||||
* 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 "Metronome.h"
|
||||
|
||||
#include "Engine.h"
|
||||
#include "SamplePlayHandle.h"
|
||||
|
||||
namespace lmms {
|
||||
void Metronome::processTick(int currentTick, int ticksPerBar, int beatsPerBar, size_t bufferOffset)
|
||||
{
|
||||
const auto ticksPerBeat = ticksPerBar / beatsPerBar;
|
||||
if (currentTick % ticksPerBeat != 0 || !m_active) { return; }
|
||||
|
||||
const auto handle = currentTick % ticksPerBar == 0 ? new SamplePlayHandle("misc/metronome02.ogg")
|
||||
: new SamplePlayHandle("misc/metronome01.ogg");
|
||||
handle->setOffset(bufferOffset);
|
||||
Engine::audioEngine()->addPlayHandle(handle);
|
||||
}
|
||||
} // 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 && static_cast<std::size_t>(index) < MaxScaleCount)
|
||||
{
|
||||
m_scaleModel.replaceItem(index,
|
||||
QString::number(index) + ": " + Engine::getSong()->getScale(index)->getDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i = std::size_t{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 && static_cast<std::size_t>(index) < MaxKeymapCount)
|
||||
{
|
||||
m_keymapModel.replaceItem(index,
|
||||
QString::number(index) + ": " + Engine::getSong()->getKeymap(index)->getDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i = std::size_t{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,23 +24,27 @@
|
||||
|
||||
#include "MixHelpers.h"
|
||||
|
||||
#ifdef LMMS_DEBUG
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "lmms_math.h"
|
||||
#include "ValueBuffer.h"
|
||||
#include "SampleFrame.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
static bool s_NaNHandler;
|
||||
|
||||
|
||||
namespace MixHelpers
|
||||
namespace lmms::MixHelpers
|
||||
{
|
||||
|
||||
/*! \brief Function for applying MIXOP on all sample frames */
|
||||
template<typename MIXOP>
|
||||
static inline void run( sampleFrame* dst, const sampleFrame* src, int frames, const MIXOP& OP )
|
||||
static inline void run( SampleFrame* dst, const SampleFrame* src, int frames, const MIXOP& OP )
|
||||
{
|
||||
for( int i = 0; i < frames; ++i )
|
||||
{
|
||||
@@ -50,18 +54,18 @@ static inline void run( sampleFrame* dst, const sampleFrame* src, int frames, co
|
||||
|
||||
/*! \brief Function for applying MIXOP on all sample frames - split source */
|
||||
template<typename MIXOP>
|
||||
static inline void run( sampleFrame* dst, const sample_t* srcLeft, const sample_t* srcRight, int frames, const MIXOP& OP )
|
||||
static inline void run( SampleFrame* dst, const sample_t* srcLeft, const sample_t* srcRight, int frames, const MIXOP& OP )
|
||||
{
|
||||
for( int i = 0; i < frames; ++i )
|
||||
{
|
||||
const sampleFrame src = { srcLeft[i], srcRight[i] };
|
||||
const SampleFrame src = { srcLeft[i], srcRight[i] };
|
||||
OP( dst[i], src );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool isSilent( const sampleFrame* src, int frames )
|
||||
bool isSilent( const SampleFrame* src, int frames )
|
||||
{
|
||||
const float silenceThreshold = 0.0000001f;
|
||||
|
||||
@@ -86,71 +90,50 @@ void setNaNHandler( bool use )
|
||||
s_NaNHandler = use;
|
||||
}
|
||||
|
||||
void clear(sampleFrame * src, int frames)
|
||||
{
|
||||
for (sampleFrame* frame = src; frame < src + frames; frame++) {
|
||||
(*frame)[0] = (*frame)[1] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef LMMS_DISABLE_SURROUND
|
||||
void clear(surroundSampleFrame *src, int frames)
|
||||
{
|
||||
for (surroundSampleFrame* frame = src; frame < src + frames; frame++) {
|
||||
(*frame)[0] = (*frame)[1] = 0.0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! \brief Function for sanitizing a buffer of infs/nans - returns true if those are found */
|
||||
bool sanitize( sampleFrame * src, int frames )
|
||||
bool sanitize( SampleFrame* src, int frames )
|
||||
{
|
||||
if( !useNaNHandler() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for( int f = 0; f < frames; ++f )
|
||||
for (int f = 0; f < frames; ++f)
|
||||
{
|
||||
for( int c = 0; c < 2; ++c )
|
||||
auto& currentFrame = src[f];
|
||||
|
||||
if (currentFrame.containsInf() || currentFrame.containsNaN())
|
||||
{
|
||||
if( isinf( src[f][c] ) || isnan( src[f][c] ) )
|
||||
{
|
||||
#ifdef LMMS_DEBUG
|
||||
#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
|
||||
for( int f = 0; f < frames; ++f )
|
||||
{
|
||||
for( int c = 0; c < 2; ++c )
|
||||
{
|
||||
src[f][c] = 0.0f;
|
||||
}
|
||||
}
|
||||
found = true;
|
||||
return found;
|
||||
}
|
||||
else
|
||||
{
|
||||
src[f][c] = qBound( -1000.0f, src[f][c], 1000.0f );
|
||||
}
|
||||
printf("%d: value %f, %f\n", f, currentFrame.left(), currentFrame.right());
|
||||
#endif
|
||||
|
||||
// Clear the whole buffer if a problem is found
|
||||
zeroSampleFrames(src, frames);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
else
|
||||
{
|
||||
currentFrame.clamp(sample_t(-1000.0), sample_t(1000.0));
|
||||
}
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
struct AddOp
|
||||
{
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
void operator()( SampleFrame& dst, const SampleFrame& src ) const
|
||||
{
|
||||
dst[0] += src[0];
|
||||
dst[1] += src[1];
|
||||
dst += src;
|
||||
}
|
||||
} ;
|
||||
|
||||
void add( sampleFrame* dst, const sampleFrame* src, int frames )
|
||||
void add( SampleFrame* dst, const SampleFrame* src, int frames )
|
||||
{
|
||||
run<>( dst, src, frames, AddOp() );
|
||||
}
|
||||
@@ -161,17 +144,16 @@ struct AddMultipliedOp
|
||||
{
|
||||
AddMultipliedOp( float coeff ) : m_coeff( coeff ) { }
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
void operator()( SampleFrame& dst, const SampleFrame& src ) const
|
||||
{
|
||||
dst[0] += src[0] * m_coeff;
|
||||
dst[1] += src[1] * m_coeff;
|
||||
dst += src * m_coeff;
|
||||
}
|
||||
|
||||
const float m_coeff;
|
||||
} ;
|
||||
|
||||
|
||||
void addMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames )
|
||||
void addMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames )
|
||||
{
|
||||
run<>( dst, src, frames, AddMultipliedOp(coeffSrc) );
|
||||
}
|
||||
@@ -181,7 +163,7 @@ struct AddSwappedMultipliedOp
|
||||
{
|
||||
AddSwappedMultipliedOp( float coeff ) : m_coeff( coeff ) { }
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
void operator()( SampleFrame& dst, const SampleFrame& src ) const
|
||||
{
|
||||
dst[0] += src[1] * m_coeff;
|
||||
dst[1] += src[0] * m_coeff;
|
||||
@@ -190,13 +172,21 @@ struct AddSwappedMultipliedOp
|
||||
const float m_coeff;
|
||||
};
|
||||
|
||||
void addSwappedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames )
|
||||
void multiply(SampleFrame* dst, float coeff, int frames)
|
||||
{
|
||||
for (int i = 0; i < frames; ++i)
|
||||
{
|
||||
dst[i] *= coeff;
|
||||
}
|
||||
}
|
||||
|
||||
void addSwappedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames )
|
||||
{
|
||||
run<>( dst, src, frames, AddSwappedMultipliedOp(coeffSrc) );
|
||||
}
|
||||
|
||||
|
||||
void addMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames )
|
||||
void addMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames )
|
||||
{
|
||||
for( int f = 0; f < frames; ++f )
|
||||
{
|
||||
@@ -205,7 +195,7 @@ void addMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coef
|
||||
}
|
||||
}
|
||||
|
||||
void addMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames )
|
||||
void addMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames )
|
||||
{
|
||||
for( int f = 0; f < frames; ++f )
|
||||
{
|
||||
@@ -215,7 +205,7 @@ void addMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuff
|
||||
|
||||
}
|
||||
|
||||
void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames )
|
||||
void addSanitizedMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames )
|
||||
{
|
||||
if ( !useNaNHandler() )
|
||||
{
|
||||
@@ -226,12 +216,12 @@ 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];
|
||||
}
|
||||
}
|
||||
|
||||
void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames )
|
||||
void addSanitizedMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames )
|
||||
{
|
||||
if ( !useNaNHandler() )
|
||||
{
|
||||
@@ -242,10 +232,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];
|
||||
}
|
||||
@@ -257,16 +247,16 @@ struct AddSanitizedMultipliedOp
|
||||
{
|
||||
AddSanitizedMultipliedOp( float coeff ) : m_coeff( coeff ) { }
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
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;
|
||||
};
|
||||
|
||||
void addSanitizedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames )
|
||||
void addSanitizedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames )
|
||||
{
|
||||
if ( !useNaNHandler() )
|
||||
{
|
||||
@@ -287,17 +277,17 @@ struct AddMultipliedStereoOp
|
||||
m_coeffs[1] = coeffRight;
|
||||
}
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
void operator()( SampleFrame& dst, const SampleFrame& src ) const
|
||||
{
|
||||
dst[0] += src[0] * m_coeffs[0];
|
||||
dst[1] += src[1] * m_coeffs[1];
|
||||
}
|
||||
|
||||
float m_coeffs[2];
|
||||
std::array<float, 2> m_coeffs;
|
||||
} ;
|
||||
|
||||
|
||||
void addMultipliedStereo( sampleFrame* dst, const sampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames )
|
||||
void addMultipliedStereo( SampleFrame* dst, const SampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames )
|
||||
{
|
||||
|
||||
run<>( dst, src, frames, AddMultipliedStereoOp(coeffSrcLeft, coeffSrcRight) );
|
||||
@@ -315,24 +305,24 @@ struct MultiplyAndAddMultipliedOp
|
||||
m_coeffs[1] = coeffSrc;
|
||||
}
|
||||
|
||||
void operator()( sampleFrame& dst, const sampleFrame& src ) const
|
||||
void operator()( SampleFrame& dst, const SampleFrame& src ) const
|
||||
{
|
||||
dst[0] = dst[0]*m_coeffs[0] + src[0]*m_coeffs[1];
|
||||
dst[1] = dst[1]*m_coeffs[0] + src[1]*m_coeffs[1];
|
||||
}
|
||||
|
||||
float m_coeffs[2];
|
||||
std::array<float, 2> m_coeffs;
|
||||
} ;
|
||||
|
||||
|
||||
void multiplyAndAddMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffDst, float coeffSrc, int frames )
|
||||
void multiplyAndAddMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffDst, float coeffSrc, int frames )
|
||||
{
|
||||
run<>( dst, src, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void multiplyAndAddMultipliedJoined( sampleFrame* dst,
|
||||
void multiplyAndAddMultipliedJoined( SampleFrame* dst,
|
||||
const sample_t* srcLeft,
|
||||
const sample_t* srcRight,
|
||||
float coeffDst, float coeffSrc, int frames )
|
||||
@@ -340,5 +330,5 @@ void multiplyAndAddMultipliedJoined( sampleFrame* dst,
|
||||
run<>( dst, srcLeft, srcRight, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) );
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace lmms::MixHelpers
|
||||
|
||||
|
||||
1890
src/core/Mixer.cpp
1890
src/core/Mixer.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* MixerProfiler.cpp - class for profiling performance of Mixer
|
||||
*
|
||||
* Copyright (c) 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 "MixerProfiler.h"
|
||||
|
||||
|
||||
MixerProfiler::MixerProfiler() :
|
||||
m_periodTimer(),
|
||||
m_cpuLoad( 0 ),
|
||||
m_outputFile()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
MixerProfiler::~MixerProfiler()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void MixerProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
|
||||
{
|
||||
int periodElapsed = m_periodTimer.elapsed();
|
||||
|
||||
const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod;
|
||||
m_cpuLoad = qBound<int>( 0, ( newCpuLoad * 0.1f + m_cpuLoad * 0.9f ), 100 );
|
||||
|
||||
if( m_outputFile.isOpen() )
|
||||
{
|
||||
m_outputFile.write( QString( "%1\n" ).arg( periodElapsed ).toLatin1() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MixerProfiler::setOutputFile( const QString& outputFile )
|
||||
{
|
||||
m_outputFile.close();
|
||||
m_outputFile.setFileName( outputFile );
|
||||
m_outputFile.open( QFile::WriteOnly | QFile::Truncate );
|
||||
}
|
||||
|
||||
@@ -24,27 +24,54 @@
|
||||
|
||||
#include "Model.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
Model::Model(Model* parent, QString displayName, bool defaultConstructed) :
|
||||
QObject(parent),
|
||||
m_displayName(displayName),
|
||||
m_defaultConstructed(defaultConstructed)
|
||||
{
|
||||
}
|
||||
|
||||
bool Model::isDefaultConstructed() const
|
||||
{
|
||||
return m_defaultConstructed;
|
||||
}
|
||||
|
||||
Model* Model::parentModel() const
|
||||
{
|
||||
return dynamic_cast<Model*>(parent());
|
||||
}
|
||||
|
||||
QString Model::displayName() const
|
||||
{
|
||||
return m_displayName;
|
||||
}
|
||||
|
||||
void Model::setDisplayName(const QString& displayName)
|
||||
{
|
||||
m_displayName = displayName;
|
||||
}
|
||||
|
||||
QString Model::fullDisplayName() const
|
||||
{
|
||||
const QString & n = displayName();
|
||||
if( parentModel() )
|
||||
const QString n = displayName();
|
||||
|
||||
if (parentModel())
|
||||
{
|
||||
const QString p = parentModel()->fullDisplayName();
|
||||
if( n.isEmpty() && p.isEmpty() )
|
||||
|
||||
if (!p.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
return p + ">" + n;
|
||||
}
|
||||
else if( p.isEmpty() )
|
||||
{
|
||||
return n;
|
||||
}
|
||||
return p + ">" + n;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // 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,26 +25,29 @@
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "Note.h"
|
||||
#include "DetuningHelper.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
Note::Note( const TimePos & length, const TimePos & pos,
|
||||
int key, volume_t volume, panning_t panning,
|
||||
DetuningHelper * detuning ) :
|
||||
m_selected( false ),
|
||||
m_oldKey( qBound( 0, key, NumKeys ) ),
|
||||
m_oldKey(std::clamp(key, 0, NumKeys)),
|
||||
m_oldPos( pos ),
|
||||
m_oldLength( length ),
|
||||
m_isPlaying( false ),
|
||||
m_key( qBound( 0, key, NumKeys ) ),
|
||||
m_volume( qBound( MinVolume, volume, MaxVolume ) ),
|
||||
m_panning( qBound( PanningLeft, panning, PanningRight ) ),
|
||||
m_key(std::clamp(key, 0, NumKeys)),
|
||||
m_volume(std::clamp(volume, MinVolume, MaxVolume)),
|
||||
m_panning(std::clamp(panning, PanningLeft, PanningRight)),
|
||||
m_length( length ),
|
||||
m_pos( pos ),
|
||||
m_detuning( NULL )
|
||||
m_detuning( nullptr )
|
||||
{
|
||||
if( detuning )
|
||||
{
|
||||
@@ -71,7 +74,8 @@ 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),
|
||||
m_type(note.m_type)
|
||||
{
|
||||
if( note.m_detuning )
|
||||
{
|
||||
@@ -111,7 +115,7 @@ void Note::setPos( const TimePos & pos )
|
||||
|
||||
void Note::setKey( const int key )
|
||||
{
|
||||
const int k = qBound( 0, key, NumKeys - 1 );
|
||||
const int k = std::clamp(key, 0, NumKeys - 1);
|
||||
m_key = k;
|
||||
}
|
||||
|
||||
@@ -120,7 +124,7 @@ void Note::setKey( const int key )
|
||||
|
||||
void Note::setVolume( volume_t volume )
|
||||
{
|
||||
const volume_t v = qBound( MinVolume, volume, MaxVolume );
|
||||
const volume_t v = std::clamp(volume, MinVolume, MaxVolume);
|
||||
m_volume = v;
|
||||
}
|
||||
|
||||
@@ -129,7 +133,7 @@ void Note::setVolume( volume_t volume )
|
||||
|
||||
void Note::setPanning( panning_t panning )
|
||||
{
|
||||
const panning_t p = qBound( PanningLeft, panning, PanningRight );
|
||||
const panning_t p = std::clamp(panning, PanningLeft, PanningRight);
|
||||
m_panning = p;
|
||||
}
|
||||
|
||||
@@ -176,6 +180,7 @@ void Note::saveSettings( QDomDocument & doc, QDomElement & parent )
|
||||
parent.setAttribute( "pan", m_panning );
|
||||
parent.setAttribute( "len", m_length );
|
||||
parent.setAttribute( "pos", m_pos );
|
||||
parent.setAttribute("type", static_cast<int>(m_type));
|
||||
|
||||
if( m_detuning && m_length )
|
||||
{
|
||||
@@ -189,11 +194,14 @@ void Note::saveSettings( QDomDocument & doc, QDomElement & parent )
|
||||
void Note::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
const int oldKey = _this.attribute( "tone" ).toInt() + _this.attribute( "oct" ).toInt() * KeysPerOctave;
|
||||
m_key = qMax( oldKey, _this.attribute( "key" ).toInt() );
|
||||
m_key = std::max(oldKey, _this.attribute("key").toInt());
|
||||
m_volume = _this.attribute( "vol" ).toInt();
|
||||
m_panning = _this.attribute( "pan" ).toInt();
|
||||
m_length = _this.attribute( "len" ).toInt();
|
||||
m_pos = _this.attribute( "pos" ).toInt();
|
||||
// Default m_type value is 0, which corresponds to RegularNote
|
||||
static_assert(0 == static_cast<int>(Type::Regular));
|
||||
m_type = static_cast<Type>(_this.attribute("type", "0").toInt());
|
||||
|
||||
if( _this.hasChildNodes() )
|
||||
{
|
||||
@@ -208,12 +216,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::ProgressionType::Linear );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,3 +240,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 )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -50,9 +53,9 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
NotePlayHandle *parent,
|
||||
int midiEventChannel,
|
||||
Origin origin ) :
|
||||
PlayHandle( TypeNotePlayHandle, _offset ),
|
||||
PlayHandle( PlayHandle::Type::NotePlayHandle, _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 );
|
||||
}
|
||||
@@ -101,12 +104,12 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
setFrames( _frames );
|
||||
|
||||
// inform attached components about new MIDI note (used for recording in Piano Roll)
|
||||
if( m_origin == OriginMidiInput )
|
||||
if( m_origin == Origin::MidiInput )
|
||||
{
|
||||
m_instrumentTrack->midiNoteOn( *this );
|
||||
}
|
||||
|
||||
if( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed )
|
||||
if (m_instrumentTrack->instrument() && m_instrumentTrack->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();
|
||||
@@ -180,22 +183,38 @@ int NotePlayHandle::midiKey() const
|
||||
|
||||
|
||||
|
||||
void NotePlayHandle::play( sampleFrame * _working_buffer )
|
||||
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();
|
||||
|
||||
// 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
|
||||
@@ -222,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 &&
|
||||
@@ -258,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 )
|
||||
@@ -311,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 )
|
||||
{
|
||||
@@ -331,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 static_cast<fpp_t>(std::min<f_cnt_t>(framesLeft(), Engine::audioEngine()->framesPerPeriod() - offset()));
|
||||
}
|
||||
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::mixer()->framesPerPeriod() );
|
||||
return static_cast<fpp_t>(std::min<f_cnt_t>(framesLeft(), Engine::audioEngine()->framesPerPeriod()));
|
||||
}
|
||||
|
||||
|
||||
@@ -341,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -365,7 +384,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
|
||||
|
||||
// then set some variables indicating release-state
|
||||
m_framesBeforeRelease = _s;
|
||||
m_releaseFramesToDo = qMax<f_cnt_t>( 0, actualReleaseFramesToDo() );
|
||||
m_releaseFramesToDo = std::max<f_cnt_t>(0, actualReleaseFramesToDo());
|
||||
|
||||
if( m_hasMidiNote )
|
||||
{
|
||||
@@ -381,7 +400,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
|
||||
// inform attached components about MIDI finished (used for recording in Piano Roll)
|
||||
if (!instrumentTrack()->isSustainPedalPressed())
|
||||
{
|
||||
if( m_origin == OriginMidiInput )
|
||||
if( m_origin == Origin::MidiInput )
|
||||
{
|
||||
setLength( TimePos( static_cast<f_cnt_t>( totalFramesPlayed() / Engine::framesPerTick() ) ) );
|
||||
m_instrumentTrack->midiNoteOff( *this );
|
||||
@@ -424,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;
|
||||
}
|
||||
@@ -436,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;
|
||||
}
|
||||
@@ -459,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 );
|
||||
}
|
||||
@@ -500,33 +519,58 @@ 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::processTimePos( const TimePos& time )
|
||||
void NotePlayHandle::processTimePos(const TimePos& time, float pitchValue, bool isRecording)
|
||||
{
|
||||
if( detuning() && time >= songGlobalParentOffset()+pos() )
|
||||
if (!detuning() || time < songGlobalParentOffset() + pos()) { return; }
|
||||
|
||||
if (isRecording && m_origin == Origin::MidiInput)
|
||||
{
|
||||
const float v = detuning()->automationPattern()->valueAt( time - songGlobalParentOffset() - pos() );
|
||||
if( !typeInfo<float>::isEqual( v, m_baseDetuning->value() ) )
|
||||
detuning()->automationClip()->recordValue(time - songGlobalParentOffset() - pos(), pitchValue / 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float v = detuning()->automationClip()->valueAt(time - songGlobalParentOffset() - pos());
|
||||
if (!approximatelyEqual(v, m_baseDetuning->value()))
|
||||
{
|
||||
m_baseDetuning->setValue( v );
|
||||
m_baseDetuning->setValue(v);
|
||||
updateFrequency();
|
||||
}
|
||||
}
|
||||
@@ -537,8 +581,8 @@ void NotePlayHandle::processTimePos( const TimePos& time )
|
||||
|
||||
void NotePlayHandle::resize( const bpm_t _new_tempo )
|
||||
{
|
||||
if (origin() == OriginMidiInput ||
|
||||
(origin() == OriginNoteStacking && m_parent->origin() == OriginMidiInput))
|
||||
if (origin() == Origin::MidiInput ||
|
||||
(origin() == Origin::NoteStacking && m_parent->origin() == Origin::MidiInput))
|
||||
{
|
||||
// Don't resize notes from MIDI input - they should continue to play
|
||||
// until the key is released, and their large duration can cause
|
||||
@@ -551,11 +595,14 @@ void NotePlayHandle::resize( const bpm_t _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);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t INITIAL_NPH_CACHE = 256;
|
||||
MemoryPool<NotePlayHandle> NotePlayHandlePool{INITIAL_NPH_CACHE};
|
||||
|
||||
|
||||
} // 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,102 +25,325 @@
|
||||
|
||||
#include "Oscillator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#if !defined(__MINGW32__) && !defined(__MINGW64__)
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
#include "Engine.h"
|
||||
#include "MixHelpers.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()->outputSampleRate() / 2)
|
||||
{
|
||||
MixHelpers::clear( _ab, _frames );
|
||||
zeroSampleFrames(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 (static_cast<ModulationAlgo>(m_modulationAlgoModel->value()))
|
||||
{
|
||||
case PhaseModulation:
|
||||
updatePM( _ab, _frames, _chnl );
|
||||
case ModulationAlgo::PhaseModulation:
|
||||
updatePM(ab, frames, chnl);
|
||||
break;
|
||||
case AmplitudeModulation:
|
||||
updateAM( _ab, _frames, _chnl );
|
||||
case ModulationAlgo::AmplitudeModulation:
|
||||
updateAM(ab, frames, chnl);
|
||||
break;
|
||||
case SignalMix:
|
||||
updateMix( _ab, _frames, _chnl );
|
||||
case ModulationAlgo::SignalMix:
|
||||
default:
|
||||
updateMix(ab, frames, chnl);
|
||||
break;
|
||||
case SynchronizedBySubOsc:
|
||||
updateSync( _ab, _frames, _chnl );
|
||||
case ModulationAlgo::SynchronizedBySubOsc:
|
||||
updateSync(ab, frames, chnl);
|
||||
break;
|
||||
case FrequencyModulation:
|
||||
updateFM( _ab, _frames, _chnl );
|
||||
case ModulationAlgo::FrequencyModulation:
|
||||
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.data(), table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1);
|
||||
}
|
||||
|
||||
void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
std::unique_ptr<OscillatorConstants::waveform_t> Oscillator::generateAntiAliasUserWaveTable(const SampleBuffer* sampleBuffer)
|
||||
{
|
||||
auto userAntiAliasWaveTable = std::make_unique<OscillatorConstants::waveform_t>();
|
||||
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
|
||||
{
|
||||
// TODO: This loop seems to be doing the same thing for each iteration of the outer loop,
|
||||
// and could probably be moved out of it
|
||||
for (int j = 0; j < OscillatorConstants::WAVETABLE_LENGTH; ++j)
|
||||
{
|
||||
s_sampleBuffer[j] = Oscillator::userWaveSample(
|
||||
sampleBuffer, static_cast<float>(j) / OscillatorConstants::WAVETABLE_LENGTH);
|
||||
}
|
||||
fftwf_execute(s_fftPlan);
|
||||
Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*userAntiAliasWaveTable)[i].data());
|
||||
}
|
||||
|
||||
return userAntiAliasWaveTable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sample_t Oscillator::s_waveTables
|
||||
[Oscillator::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;
|
||||
std::array<float, OscillatorConstants::WAVETABLE_LENGTH> Oscillator::s_sampleBuffer;
|
||||
|
||||
|
||||
|
||||
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.data(), s_specBuf, FFTW_MEASURE );
|
||||
Oscillator::s_ifftPlan = fftwf_plan_dft_c2r_1d(OscillatorConstants::WAVETABLE_LENGTH, s_specBuf, s_sampleBuffer.data(), 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 = [](WaveShape shape, generator_t generator)
|
||||
{
|
||||
const int shapeID = static_cast<std::size_t>(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[static_cast<std::size_t>(WaveShape::MoogSaw) - 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[static_cast<std::size_t>(WaveShape::Exponential) - 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, WaveShape::Saw, generateSawWaveTable);
|
||||
std::thread squareThread(simpleGen, WaveShape::Square, generateSquareWaveTable);
|
||||
std::thread triangleThread(simpleGen, WaveShape::Triangle, generateTriangleWaveTable);
|
||||
std::thread fftThread(fftGen);
|
||||
sawThread.join();
|
||||
squareThread.join();
|
||||
triangleThread.join();
|
||||
fftThread.join();
|
||||
#else
|
||||
simpleGen(WaveShape::Saw, generateSawWaveTable);
|
||||
simpleGen(WaveShape::Square, generateSquareWaveTable);
|
||||
simpleGen(WaveShape::Triangle, generateTriangleWaveTable);
|
||||
fftGen();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateNoSub( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updateNoSub<SineWave>( _ab, _frames, _chnl );
|
||||
updateNoSub<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updateNoSub<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updateNoSub<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updateNoSub<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updateNoSub<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updateNoSub<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updateNoSub<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updateNoSub<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updateNoSub<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updateNoSub<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updateNoSub<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updateNoSub<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updateNoSub<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updateNoSub<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updateNoSub<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -127,35 +351,35 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
|
||||
void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
|
||||
void Oscillator::updatePM( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updatePM<SineWave>( _ab, _frames, _chnl );
|
||||
updatePM<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updatePM<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updatePM<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updatePM<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updatePM<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updatePM<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updatePM<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updatePM<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updatePM<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updatePM<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updatePM<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updatePM<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updatePM<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updatePM<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updatePM<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -163,35 +387,35 @@ void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
|
||||
void Oscillator::updateAM( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updateAM<SineWave>( _ab, _frames, _chnl );
|
||||
updateAM<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updateAM<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updateAM<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updateAM<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updateAM<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updateAM<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updateAM<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updateAM<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updateAM<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updateAM<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updateAM<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updateAM<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updateAM<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updateAM<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updateAM<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -199,35 +423,35 @@ void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
|
||||
void Oscillator::updateMix( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updateMix<SineWave>( _ab, _frames, _chnl );
|
||||
updateMix<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updateMix<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updateMix<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updateMix<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updateMix<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updateMix<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updateMix<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updateMix<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updateMix<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updateMix<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updateMix<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updateMix<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updateMix<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updateMix<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updateMix<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -235,35 +459,35 @@ void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
|
||||
void Oscillator::updateSync( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updateSync<SineWave>( _ab, _frames, _chnl );
|
||||
updateSync<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updateSync<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updateSync<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updateSync<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updateSync<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updateSync<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updateSync<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updateSync<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updateSync<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updateSync<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updateSync<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updateSync<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updateSync<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updateSync<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updateSync<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -271,35 +495,35 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
|
||||
void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
void Oscillator::updateFM( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
switch( m_waveShapeModel->value() )
|
||||
switch( static_cast<WaveShape>(m_waveShapeModel->value()) )
|
||||
{
|
||||
case SineWave:
|
||||
case WaveShape::Sine:
|
||||
default:
|
||||
updateFM<SineWave>( _ab, _frames, _chnl );
|
||||
updateFM<WaveShape::Sine>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case TriangleWave:
|
||||
updateFM<TriangleWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Triangle:
|
||||
updateFM<WaveShape::Triangle>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SawWave:
|
||||
updateFM<SawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Saw:
|
||||
updateFM<WaveShape::Saw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case SquareWave:
|
||||
updateFM<SquareWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Square:
|
||||
updateFM<WaveShape::Square>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case MoogSawWave:
|
||||
updateFM<MoogSawWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::MoogSaw:
|
||||
updateFM<WaveShape::MoogSaw>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case ExponentialWave:
|
||||
updateFM<ExponentialWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::Exponential:
|
||||
updateFM<WaveShape::Exponential>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case WhiteNoise:
|
||||
updateFM<WhiteNoise>( _ab, _frames, _chnl );
|
||||
case WaveShape::WhiteNoise:
|
||||
updateFM<WaveShape::WhiteNoise>( _ab, _frames, _chnl );
|
||||
break;
|
||||
case UserDefinedWave:
|
||||
updateFM<UserDefinedWave>( _ab, _frames, _chnl );
|
||||
case WaveShape::UserDefined:
|
||||
updateFM<WaveShape::UserDefined>( _ab, _frames, _chnl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -310,7 +534,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
// should be called every time phase-offset is changed...
|
||||
inline void Oscillator::recalcPhase()
|
||||
{
|
||||
if( !typeInfo<float>::isEqual( m_phaseOffset, m_ext_phaseOffset ) )
|
||||
if (!approximatelyEqual(m_phaseOffset, m_ext_phaseOffset))
|
||||
{
|
||||
m_phase -= m_phaseOffset;
|
||||
m_phaseOffset = m_ext_phaseOffset;
|
||||
@@ -333,27 +557,27 @@ inline bool Oscillator::syncOk( float _osc_coeff )
|
||||
|
||||
|
||||
|
||||
float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// if we have no sub-osc, we can't do any modulation... just get our samples
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape W>
|
||||
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 )
|
||||
{
|
||||
@@ -366,13 +590,13 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
// do pm by using sub-osc as modulator
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape 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 )
|
||||
{
|
||||
@@ -387,13 +611,13 @@ void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
// do am by using sub-osc as modulator
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape 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 )
|
||||
{
|
||||
@@ -406,13 +630,13 @@ void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
// do mix by using sub-osc as mix-sample
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape 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 )
|
||||
{
|
||||
@@ -426,13 +650,13 @@ void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
// sync with sub-osc (every time sub-osc starts new period, we also start new
|
||||
// period)
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape W>
|
||||
void Oscillator::updateSync( SampleFrame* _ab, const fpp_t _frames,
|
||||
const ch_cnt_t _chnl )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
@@ -449,15 +673,14 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
|
||||
|
||||
|
||||
// do fm by using sub-osc as modulator
|
||||
template<Oscillator::WaveShapes W>
|
||||
void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<Oscillator::WaveShape 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()->outputSampleRate();
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -471,67 +694,110 @@ 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::WaveShape::Sine>(const float sample)
|
||||
{
|
||||
return( sinSample( _sample ) );
|
||||
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ)
|
||||
{
|
||||
return sinSample(sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::TriangleWave>(
|
||||
const float _sample )
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Triangle>(
|
||||
const float _sample )
|
||||
{
|
||||
return( triangleSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[static_cast<std::size_t>(WaveShape::Triangle) - FirstWaveShapeTable],_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return triangleSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::SawWave>(
|
||||
const float _sample )
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Saw>(
|
||||
const float _sample )
|
||||
{
|
||||
return( sawSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[static_cast<std::size_t>(WaveShape::Saw) - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return sawSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::SquareWave>(
|
||||
const float _sample )
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Square>(
|
||||
const float _sample )
|
||||
{
|
||||
return( squareSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[static_cast<std::size_t>(WaveShape::Square) - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return squareSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::MoogSawWave>(
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::MoogSaw>(
|
||||
const float _sample )
|
||||
{
|
||||
return( moogSawSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[static_cast<std::size_t>(WaveShape::MoogSaw) - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return moogSawSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::ExponentialWave>(
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Exponential>(
|
||||
const float _sample )
|
||||
{
|
||||
return( expSample( _sample ) );
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(s_waveTables[static_cast<std::size_t>(WaveShape::Exponential) - FirstWaveShapeTable], _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return expSample(_sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::WhiteNoise>(
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::WhiteNoise>(
|
||||
const float _sample )
|
||||
{
|
||||
return( noiseSample( _sample ) );
|
||||
@@ -541,12 +807,18 @@ inline sample_t Oscillator::getSample<Oscillator::WhiteNoise>(
|
||||
|
||||
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::UserDefinedWave>(
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::UserDefined>(
|
||||
const float _sample )
|
||||
{
|
||||
return( userWaveSample( _sample ) );
|
||||
if (m_useWaveTable && m_userAntiAliasWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(m_userAntiAliasWaveTable.get(), _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return userWaveSample(m_userWave.get(), _sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#include "PathUtil.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "Engine.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace PathUtil
|
||||
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 };
|
||||
auto relativeBases = std::array{ 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)
|
||||
QString baseLocation(const Base base, bool* error /* = nullptr*/)
|
||||
{
|
||||
// error is false unless something goes wrong
|
||||
if (error) { *error = false; }
|
||||
|
||||
QString loc = "";
|
||||
switch (base)
|
||||
{
|
||||
@@ -31,15 +36,33 @@ namespace PathUtil
|
||||
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)
|
||||
QDir baseQDir (const Base base, bool* error /* = nullptr*/)
|
||||
{
|
||||
if (base == Base::Absolute) { return QDir::root(); }
|
||||
return QDir(baseLocation(base));
|
||||
if (base == Base::Absolute)
|
||||
{
|
||||
if (error) { *error = false; }
|
||||
return QDir::root();
|
||||
}
|
||||
return QDir(baseLocation(base, error));
|
||||
}
|
||||
|
||||
QString basePrefix(const Base base)
|
||||
@@ -57,7 +80,8 @@ namespace PathUtil
|
||||
case Base::DefaultSoundfont : return QStringLiteral("defaultsoundfont:");
|
||||
case Base::UserGIG : return QStringLiteral("usergig:");
|
||||
case Base::DefaultGIG : return QStringLiteral("defaultgig:");
|
||||
default : return QStringLiteral("");
|
||||
case Base::LocalDir : return QStringLiteral("local:");
|
||||
default : return QStringLiteral("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +121,7 @@ namespace PathUtil
|
||||
//Check if it's a factory sample
|
||||
QString factoryPath = baseLocation(Base::FactorySample) + input;
|
||||
QFileInfo factoryInfo(factoryPath);
|
||||
if (factoryInfo.exists()) { assumedBase = Base::FactorySample; }
|
||||
if (factoryInfo.exists()) { assumedBase = Base::FactorySample; }
|
||||
|
||||
//Check if it's a VST
|
||||
QString vstPath = baseLocation(Base::UserVST) + input;
|
||||
@@ -111,16 +135,20 @@ namespace PathUtil
|
||||
|
||||
|
||||
|
||||
QString toAbsolute(const QString & input)
|
||||
QString toAbsolute(const QString & input, bool* error /* = nullptr*/)
|
||||
{
|
||||
//First, do no harm to absolute paths
|
||||
QFileInfo inputFileInfo = QFileInfo(input);
|
||||
if (inputFileInfo.isAbsolute()) { return 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) + upgraded.remove(0, basePrefix(base).length());
|
||||
return baseLocation(base, error) + upgraded.remove(0, basePrefix(base).length());
|
||||
}
|
||||
|
||||
QString relativeOrAbsolute(const QString & input, const Base base)
|
||||
@@ -128,11 +156,16 @@ namespace PathUtil
|
||||
if (input.isEmpty()) { return input; }
|
||||
QString absolutePath = toAbsolute(input);
|
||||
if (base == Base::Absolute) { return absolutePath; }
|
||||
QString relativePath = baseQDir(base).relativeFilePath(absolutePath);
|
||||
return relativePath.startsWith("..") ? absolutePath : relativePath;
|
||||
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)
|
||||
QString toShortestRelative(const QString & input, bool allowLocal /* = false*/)
|
||||
{
|
||||
QFileInfo inputFileInfo = QFileInfo(input);
|
||||
QString absolutePath = inputFileInfo.isAbsolute() ? input : toAbsolute(input);
|
||||
@@ -141,6 +174,10 @@ namespace PathUtil
|
||||
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())
|
||||
{
|
||||
@@ -150,4 +187,5 @@ namespace PathUtil
|
||||
}
|
||||
return basePrefix(shortestBase) + relativeOrAbsolute(absolutePath, shortestBase);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms::PathUtil
|
||||
|
||||
118
src/core/PatternClip.cpp
Normal file
118
src/core/PatternClip.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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("off", startTimeOffset());
|
||||
element.setAttribute( "muted", isMuted() );
|
||||
if (const auto& c = color())
|
||||
{
|
||||
element.setAttribute("color", c->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() );
|
||||
setStartTimeOffset(element.attribute("off").toInt());
|
||||
if (static_cast<bool>(element.attribute("muted").toInt()) != isMuted())
|
||||
{
|
||||
toggleMute();
|
||||
}
|
||||
|
||||
if (element.hasAttribute("color"))
|
||||
{
|
||||
if (!element.hasAttribute("usestyle"))
|
||||
{
|
||||
// for colors saved in 1.3-onwards
|
||||
setColor(QColor{element.attribute("color")});
|
||||
}
|
||||
else if (element.attribute("usestyle").toUInt() == 0)
|
||||
{
|
||||
// for colors saved before 1.3
|
||||
setColor(QColor{element.attribute("color").toUInt()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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(Type::Pattern);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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());
|
||||
|
||||
const 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 = std::max(maxLength, t->getClip(pattern)->length());
|
||||
}
|
||||
}
|
||||
|
||||
return maxLength.nextFullBar();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int PatternStore::numOfPatterns() const
|
||||
{
|
||||
return Engine::getSong()->countTracks(Track::Type::Pattern);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::removePattern(int pattern)
|
||||
{
|
||||
const TrackList& tl = tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
delete t->getClip(pattern);
|
||||
t->removeBar(pattern * DefaultTicksPerBar);
|
||||
}
|
||||
if (pattern <= currentPattern())
|
||||
{
|
||||
setCurrentPattern(std::max(currentPattern() - 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::swapPattern(int pattern1, int pattern2)
|
||||
{
|
||||
const 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()
|
||||
{
|
||||
const 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::PlayMode::Pattern)
|
||||
{
|
||||
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)
|
||||
const TrackList& tl = Engine::getSong()->tracks();
|
||||
for (Track * t : tl)
|
||||
{
|
||||
if (t->type() == Track::Type::Pattern)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PatternStore::createClipsForPattern(int pattern)
|
||||
{
|
||||
const 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;
|
||||
@@ -42,21 +46,21 @@ bool PeakController::m_buggedFile;
|
||||
|
||||
PeakController::PeakController( Model * _parent,
|
||||
PeakControllerEffect * _peak_effect ) :
|
||||
Controller( Controller::PeakController, _parent, tr( "Peak Controller" ) ),
|
||||
Controller( ControllerType::Peak, _parent, tr( "Peak Controller" ) ),
|
||||
m_peakEffect( _peak_effect ),
|
||||
m_currentSample( 0.0f )
|
||||
{
|
||||
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()->outputSampleRate();
|
||||
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;
|
||||
}
|
||||
@@ -157,12 +161,11 @@ void PeakController::loadSettings( const QDomElement & _this )
|
||||
effectId = m_loadCount++;
|
||||
}
|
||||
|
||||
PeakControllerEffectVector::Iterator i;
|
||||
for( i = s_effects.begin(); i != s_effects.end(); ++i )
|
||||
for (const auto& effect : s_effects)
|
||||
{
|
||||
if( (*i)->m_effectId == effectId )
|
||||
if (effect->m_effectId == effectId)
|
||||
{
|
||||
m_peakEffect = *i;
|
||||
m_peakEffect = effect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -186,16 +189,14 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi
|
||||
{
|
||||
int effectId = _this.attribute( "effectId" ).toInt();
|
||||
|
||||
PeakControllerEffectVector::Iterator i;
|
||||
|
||||
//Backward compatibility for bug in <= 0.4.15 . For >= 1.0.0 ,
|
||||
//foundCount should always be 1 because m_effectId is initialized with rand()
|
||||
int foundCount = 0;
|
||||
if( m_buggedFile == false )
|
||||
{
|
||||
for( i = s_effects.begin(); i != s_effects.end(); ++i )
|
||||
for (const auto& effect : s_effects)
|
||||
{
|
||||
if( (*i)->m_effectId == effectId )
|
||||
if (effect->m_effectId == effectId)
|
||||
{
|
||||
foundCount++;
|
||||
}
|
||||
@@ -204,9 +205,9 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi
|
||||
{
|
||||
m_buggedFile = true;
|
||||
int newEffectId = 0;
|
||||
for( i = s_effects.begin(); i != s_effects.end(); ++i )
|
||||
for (const auto& effect : s_effects)
|
||||
{
|
||||
(*i)->m_effectId = newEffectId++;
|
||||
effect->m_effectId = newEffectId++;
|
||||
}
|
||||
QMessageBox msgBox;
|
||||
msgBox.setIcon( QMessageBox::Information );
|
||||
@@ -227,15 +228,15 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi
|
||||
}
|
||||
m_getCount++; //NB: m_getCount should be increased even m_buggedFile is false
|
||||
|
||||
for( i = s_effects.begin(); i != s_effects.end(); ++i )
|
||||
for (const auto& effect : s_effects)
|
||||
{
|
||||
if( (*i)->m_effectId == effectId )
|
||||
if (effect->m_effectId == effectId)
|
||||
{
|
||||
return (*i)->controller();
|
||||
return effect->controller();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,11 +248,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,16 +41,20 @@
|
||||
#include "InstrumentTrack.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
/*! The black / white order of keys as they appear on the keyboard.
|
||||
*/
|
||||
static const Piano::KeyTypes KEY_ORDER[] =
|
||||
static const auto KEY_ORDER = std::array
|
||||
{
|
||||
// C CIS D DIS
|
||||
Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey,
|
||||
// E F FIS G
|
||||
Piano::WhiteKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey,
|
||||
// GIS A AIS B
|
||||
Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey
|
||||
// C CIS D DIS
|
||||
Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White, Piano::KeyType::Black,
|
||||
// E F FIS G
|
||||
Piano::KeyType::White, Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White,
|
||||
// GIS A AIS B
|
||||
Piano::KeyType::Black, Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White
|
||||
} ;
|
||||
|
||||
|
||||
@@ -58,16 +62,11 @@ static const Piano::KeyTypes KEY_ORDER[] =
|
||||
*
|
||||
* \param _it the InstrumentTrack window to attach to
|
||||
*/
|
||||
Piano::Piano( InstrumentTrack* track ) :
|
||||
Model( NULL ), /*!< base class ctor */
|
||||
m_instrumentTrack( track ),
|
||||
m_midiEvProc( track ) /*!< the InstrumentTrack Model */
|
||||
Piano::Piano(InstrumentTrack* track) :
|
||||
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
|
||||
@@ -75,9 +74,9 @@ Piano::Piano( InstrumentTrack* track ) :
|
||||
* \param key the key number to change
|
||||
* \param state the state to set the key to
|
||||
*/
|
||||
void Piano::setKeyState( int key, bool state )
|
||||
void Piano::setKeyState(int key, bool state)
|
||||
{
|
||||
if( isValidKey( key ) )
|
||||
if (isValidKey(key))
|
||||
{
|
||||
m_pressedKeys[key] = state;
|
||||
|
||||
@@ -92,15 +91,15 @@ void Piano::setKeyState( int key, bool state )
|
||||
*
|
||||
* \param key the key being pressed
|
||||
*/
|
||||
void Piano::handleKeyPress( int key, int midiVelocity )
|
||||
void Piano::handleKeyPress(int key, int midiVelocity)
|
||||
{
|
||||
if( midiVelocity == -1 )
|
||||
if (midiVelocity == -1)
|
||||
{
|
||||
midiVelocity = m_instrumentTrack->midiPort()->baseVelocity();
|
||||
}
|
||||
if( isValidKey( key ) )
|
||||
if (isValidKey(key))
|
||||
{
|
||||
m_midiEvProc->processInEvent( MidiEvent( MidiNoteOn, -1, key, midiVelocity ) );
|
||||
m_midiEvProc->processInEvent(MidiEvent(MidiNoteOn, -1, key, midiVelocity));
|
||||
m_pressedKeys[key] = true;
|
||||
}
|
||||
}
|
||||
@@ -113,27 +112,29 @@ void Piano::handleKeyPress( int key, int midiVelocity )
|
||||
*
|
||||
* \param key the key being releassed
|
||||
*/
|
||||
void Piano::handleKeyRelease( int key )
|
||||
void Piano::handleKeyRelease(int key)
|
||||
{
|
||||
if( isValidKey( key ) )
|
||||
if (isValidKey(key))
|
||||
{
|
||||
m_midiEvProc->processInEvent( MidiEvent( MidiNoteOff, -1, key, 0 ) );
|
||||
m_midiEvProc->processInEvent(MidiEvent(MidiNoteOff, -1, key, 0));
|
||||
m_pressedKeys[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Piano::isBlackKey( int key )
|
||||
bool Piano::isBlackKey(int key)
|
||||
{
|
||||
int keyCode = key % KeysPerOctave;
|
||||
|
||||
return KEY_ORDER[keyCode] == Piano::BlackKey;
|
||||
return KEY_ORDER[keyCode] == Piano::KeyType::Black;
|
||||
}
|
||||
|
||||
|
||||
bool Piano::isWhiteKey( int key )
|
||||
bool Piano::isWhiteKey(int key)
|
||||
{
|
||||
return !isBlackKey( key );
|
||||
return !isBlackKey(key);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,13 +25,13 @@
|
||||
#include "BufferPool.h"
|
||||
#include "PlayHandle.h"
|
||||
#include "Engine.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
PlayHandle::PlayHandle(const Type type, f_cnt_t offset) :
|
||||
m_type(type),
|
||||
@@ -55,12 +55,12 @@ void PlayHandle::doProcessing()
|
||||
if( m_usesBuffer )
|
||||
{
|
||||
m_bufferReleased = false;
|
||||
MixHelpers::clear(m_playHandleBuffer, Engine::mixer()->framesPerPeriod());
|
||||
zeroSampleFrames(m_playHandleBuffer, Engine::audioEngine()->framesPerPeriod());
|
||||
play( buffer() );
|
||||
}
|
||||
else
|
||||
{
|
||||
play( NULL );
|
||||
play( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ void PlayHandle::releaseBuffer()
|
||||
m_bufferReleased = true;
|
||||
}
|
||||
|
||||
sampleFrame* PlayHandle::buffer()
|
||||
SampleFrame* PlayHandle::buffer()
|
||||
{
|
||||
return m_bufferReleased ? nullptr : reinterpret_cast<sampleFrame*>(m_playHandleBuffer);
|
||||
return m_bufferReleased ? nullptr : 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;
|
||||
@@ -47,9 +50,9 @@ static Plugin::Descriptor dummyPluginDescriptor =
|
||||
QT_TRANSLATE_NOOP( "PluginBrowser", "no description" ),
|
||||
"Tobias Doerffel <tobydox/at/users.sf.net>",
|
||||
0x0100,
|
||||
Plugin::Undefined,
|
||||
Plugin::Type::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,25 +209,25 @@ 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();
|
||||
}
|
||||
else
|
||||
{
|
||||
InstantiationHook instantiationHook;
|
||||
if ((instantiationHook = ( InstantiationHook ) pi.library->resolve( "lmms_plugin_main" )))
|
||||
auto instantiationHook = reinterpret_cast<InstantiationHook>(pi.library->resolve("lmms_plugin_main"));
|
||||
if (instantiationHook)
|
||||
{
|
||||
inst = instantiationHook(parent, data);
|
||||
if(!inst) {
|
||||
@@ -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.
|
||||
@@ -84,8 +85,7 @@ void PluginFactory::setupSearchPaths()
|
||||
addRelativeIfExists(PLUGIN_DIR);
|
||||
#endif
|
||||
// Or via an environment variable:
|
||||
QString env_path;
|
||||
if (!(env_path = qgetenv("LMMS_PLUGIN_DIR")).isEmpty())
|
||||
if (const char* env_path = std::getenv("LMMS_PLUGIN_DIR"))
|
||||
QDir::addSearchPath("plugins", env_path);
|
||||
|
||||
QDir::addSearchPath("plugins", ConfigManager::inst()->workingDir() + "plugins");
|
||||
@@ -94,17 +94,22 @@ 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();
|
||||
}
|
||||
|
||||
const Plugin::DescriptorList PluginFactory::descriptors() const
|
||||
PluginFactory* getPluginFactory()
|
||||
{
|
||||
return PluginFactory::instance();
|
||||
}
|
||||
|
||||
Plugin::DescriptorList PluginFactory::descriptors() const
|
||||
{
|
||||
return m_descriptors.values();
|
||||
}
|
||||
|
||||
const Plugin::DescriptorList PluginFactory::descriptors(Plugin::PluginTypes type) const
|
||||
Plugin::DescriptorList PluginFactory::descriptors(Plugin::Type type) const
|
||||
{
|
||||
return m_descriptors.values(type);
|
||||
}
|
||||
@@ -114,12 +119,12 @@ const PluginFactory::PluginInfoList& PluginFactory::pluginInfos() const
|
||||
return m_pluginInfos;
|
||||
}
|
||||
|
||||
const PluginFactory::PluginInfoAndKey PluginFactory::pluginSupportingExtension(const QString& ext)
|
||||
PluginFactory::PluginInfoAndKey PluginFactory::pluginSupportingExtension(const QString& ext)
|
||||
{
|
||||
return m_pluginByExt.value(ext, PluginInfoAndKey());
|
||||
}
|
||||
|
||||
const PluginFactory::PluginInfo PluginFactory::pluginInfo(const char* name) const
|
||||
PluginFactory::PluginInfo PluginFactory::pluginInfo(const char* name) const
|
||||
{
|
||||
for (const PluginInfo& info : m_pluginInfos)
|
||||
{
|
||||
@@ -242,7 +247,10 @@ void PluginFactory::discoverPlugins()
|
||||
|
||||
|
||||
|
||||
const QString PluginFactory::PluginInfo::name() const
|
||||
QString PluginFactory::PluginInfo::name() const
|
||||
{
|
||||
return descriptor ? descriptor->name : QString();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* PluginIssue.h - PluginIssue class
|
||||
* PluginIssue.cpp - PluginIssue class implementation
|
||||
*
|
||||
* Copyright (c) 2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
|
||||
* Copyright (c) 2019-2024 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -26,47 +26,51 @@
|
||||
|
||||
#include "PluginIssue.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
const char *PluginIssue::msgFor(const PluginIssueType &it)
|
||||
{
|
||||
switch (it)
|
||||
{
|
||||
case unknownPortFlow:
|
||||
case PluginIssueType::UnknownPortFlow:
|
||||
return "unknown port flow for mandatory port";
|
||||
case unknownPortType:
|
||||
case PluginIssueType::UnknownPortType:
|
||||
return "unknown port type for mandatory port";
|
||||
case tooManyInputChannels:
|
||||
case PluginIssueType::TooManyInputChannels:
|
||||
return "too many audio input channels";
|
||||
case tooManyOutputChannels:
|
||||
case PluginIssueType::TooManyOutputChannels:
|
||||
return "too many audio output channels";
|
||||
case tooManyMidiInputChannels:
|
||||
case PluginIssueType::TooManyMidiInputChannels:
|
||||
return "too many MIDI input channels";
|
||||
case tooManyMidiOutputChannels:
|
||||
case PluginIssueType::TooManyMidiOutputChannels:
|
||||
return "too many MIDI output channels";
|
||||
case noOutputChannel:
|
||||
case PluginIssueType::NoOutputChannel:
|
||||
return "no audio output channel";
|
||||
case portHasNoDef:
|
||||
case PluginIssueType::PortHasNoDef:
|
||||
return "port is missing default value";
|
||||
case portHasNoMin:
|
||||
case PluginIssueType::PortHasNoMin:
|
||||
return "port is missing min value";
|
||||
case portHasNoMax:
|
||||
case PluginIssueType::PortHasNoMax:
|
||||
return "port is missing max value";
|
||||
case minGreaterMax:
|
||||
case PluginIssueType::MinGreaterMax:
|
||||
return "port minimum is greater than maximum";
|
||||
case defaultValueNotInRange:
|
||||
case PluginIssueType::DefaultValueNotInRange:
|
||||
return "default value is not in range [min, max]";
|
||||
case logScaleMinMissing:
|
||||
case PluginIssueType::LogScaleMinMissing:
|
||||
return "logscale requires minimum value";
|
||||
case logScaleMaxMissing:
|
||||
case PluginIssueType::LogScaleMaxMissing:
|
||||
return "logscale requires maximum value";
|
||||
case logScaleMinMaxDifferentSigns:
|
||||
case PluginIssueType::LogScaleMinMaxDifferentSigns:
|
||||
return "logscale with min < 0 < max";
|
||||
case featureNotSupported:
|
||||
case PluginIssueType::FeatureNotSupported:
|
||||
return "required feature not supported";
|
||||
case badPortType:
|
||||
case PluginIssueType::BadPortType:
|
||||
return "unsupported port type";
|
||||
case blacklisted:
|
||||
return "blacklisted plugin";
|
||||
case noIssue:
|
||||
case PluginIssueType::Blocked:
|
||||
return "blocked plugin";
|
||||
case PluginIssueType::NoIssue:
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
@@ -91,8 +95,6 @@ bool PluginIssue::operator<(const PluginIssue &other) const
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QDebug operator<<(QDebug stream, const PluginIssue &iss)
|
||||
{
|
||||
stream << PluginIssue::msgFor(iss.m_issueType);
|
||||
@@ -103,4 +105,7 @@ QDebug operator<<(QDebug stream, const PluginIssue &iss)
|
||||
return stream;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,34 +25,35 @@
|
||||
#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 );
|
||||
m_previewInstrumentTrack = dynamic_cast<InstrumentTrack *>( Track::create( Track::InstrumentTrack, this ) );
|
||||
m_previewInstrumentTrack = dynamic_cast<InstrumentTrack *>( Track::create( Track::Type::Instrument, this ) );
|
||||
m_previewInstrumentTrack->setJournalling( false );
|
||||
m_previewInstrumentTrack->setPreviewMode( true );
|
||||
}
|
||||
|
||||
virtual ~PreviewTrackContainer()
|
||||
{
|
||||
}
|
||||
~PreviewTrackContainer() override = default;
|
||||
|
||||
QString nodeName() const override
|
||||
{
|
||||
@@ -115,17 +116,17 @@ PreviewTrackContainer * PresetPreviewPlayHandle::s_previewTC;
|
||||
|
||||
|
||||
PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, bool _load_by_plugin, DataFile *dataFile ) :
|
||||
PlayHandle( TypePresetPreviewHandle ),
|
||||
PlayHandle( Type::PresetPreviewHandle ),
|
||||
m_previewNote(nullptr)
|
||||
{
|
||||
setUsesBuffer( false );
|
||||
|
||||
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 );
|
||||
}
|
||||
@@ -168,22 +169,22 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
|
||||
// make sure, our preset-preview-track does not appear in any MIDI-
|
||||
// devices list, so just disable receiving/sending MIDI-events at all
|
||||
s_previewTC->previewInstrumentTrack()->
|
||||
midiPort()->setMode( MidiPort::Disabled );
|
||||
midiPort()->setMode( MidiPort::Mode::Disabled );
|
||||
|
||||
Engine::mixer()->requestChangeInModel();
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
// create note-play-handle for it
|
||||
m_previewNote = NotePlayHandlePool.construct(
|
||||
s_previewTC->previewInstrumentTrack(), 0,
|
||||
typeInfo<f_cnt_t>::max() / 2,
|
||||
std::numeric_limits<f_cnt_t>::max() / 2,
|
||||
Note( 0, 0, DefaultKey, 100 ) );
|
||||
|
||||
setAudioPort( s_previewTC->previewInstrumentTrack()->audioPort() );
|
||||
|
||||
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 );
|
||||
}
|
||||
@@ -193,22 +194,22 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void PresetPreviewPlayHandle::play( sampleFrame * _working_buffer )
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +245,7 @@ void PresetPreviewPlayHandle::init()
|
||||
void PresetPreviewPlayHandle::cleanup()
|
||||
{
|
||||
delete s_previewTC;
|
||||
s_previewTC = NULL;
|
||||
s_previewTC = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,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() );
|
||||
@@ -274,4 +275,4 @@ bool PresetPreviewPlayHandle::isPreviewing()
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -23,11 +23,16 @@
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "ProjectJournal.h"
|
||||
#include "Engine.h"
|
||||
#include "JournallingObject.h"
|
||||
#include "Song.h"
|
||||
#include "AutomationClip.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
//! Avoid clashes between loaded IDs (have the bit cleared)
|
||||
//! and newly created IDs (have the bit set)
|
||||
@@ -46,13 +51,6 @@ ProjectJournal::ProjectJournal() :
|
||||
|
||||
|
||||
|
||||
ProjectJournal::~ProjectJournal()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void ProjectJournal::undo()
|
||||
{
|
||||
while( !m_undoCheckPoints.isEmpty() )
|
||||
@@ -62,7 +60,7 @@ void ProjectJournal::undo()
|
||||
|
||||
if( jo )
|
||||
{
|
||||
DataFile curState( DataFile::JournalData );
|
||||
DataFile curState( DataFile::Type::JournalData );
|
||||
jo->saveState( curState, curState.content() );
|
||||
m_redoCheckPoints.push( CheckPoint( c.joID, curState ) );
|
||||
|
||||
@@ -71,6 +69,12 @@ void ProjectJournal::undo()
|
||||
jo->restoreState( c.data.content().firstChildElement() );
|
||||
setJournalling( prev );
|
||||
Engine::getSong()->setModified();
|
||||
|
||||
// loading AutomationClip connections correctly
|
||||
if (!c.data.content().elementsByTagName("automationclip").isEmpty())
|
||||
{
|
||||
AutomationClip::resolveAllIDs();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +91,7 @@ void ProjectJournal::redo()
|
||||
|
||||
if( jo )
|
||||
{
|
||||
DataFile curState( DataFile::JournalData );
|
||||
DataFile curState( DataFile::Type::JournalData );
|
||||
jo->saveState( curState, curState.content() );
|
||||
m_undoCheckPoints.push( CheckPoint( c.joID, curState ) );
|
||||
|
||||
@@ -119,7 +123,7 @@ void ProjectJournal::addJournalCheckPoint( JournallingObject *jo )
|
||||
{
|
||||
m_redoCheckPoints.clear();
|
||||
|
||||
DataFile dataFile( DataFile::JournalData );
|
||||
DataFile dataFile( DataFile::Type::JournalData );
|
||||
jo->saveState( dataFile, dataFile.content() );
|
||||
|
||||
m_undoCheckPoints.push( CheckPoint( jo->id(), dataFile ) );
|
||||
@@ -181,7 +185,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 +200,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 +210,4 @@ void ProjectJournal::stopAllJournalling()
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -34,60 +34,61 @@
|
||||
#include "AudioFileMP3.h"
|
||||
#include "AudioFileFlac.h"
|
||||
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
#include "sched.h"
|
||||
#endif
|
||||
|
||||
const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
{ ProjectRenderer::WaveFile,
|
||||
|
||||
const std::array<ProjectRenderer::FileEncodeDevice, 5> ProjectRenderer::fileEncodeDevices
|
||||
{
|
||||
|
||||
FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Wave,
|
||||
QT_TRANSLATE_NOOP( "ProjectRenderer", "WAV (*.wav)" ),
|
||||
".wav", &AudioFileWave::getInst },
|
||||
{ ProjectRenderer::FlacFile,
|
||||
FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Flac,
|
||||
QT_TRANSLATE_NOOP("ProjectRenderer", "FLAC (*.flac)"),
|
||||
".flac",
|
||||
&AudioFileFlac::getInst
|
||||
},
|
||||
{ ProjectRenderer::OggFile,
|
||||
FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Ogg,
|
||||
QT_TRANSLATE_NOOP( "ProjectRenderer", "OGG (*.ogg)" ),
|
||||
".ogg",
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
&AudioFileOgg::getInst
|
||||
#else
|
||||
NULL
|
||||
nullptr
|
||||
#endif
|
||||
},
|
||||
{ ProjectRenderer::MP3File,
|
||||
FileEncodeDevice{ ProjectRenderer::ExportFileFormat::MP3,
|
||||
QT_TRANSLATE_NOOP( "ProjectRenderer", "MP3 (*.mp3)" ),
|
||||
".mp3",
|
||||
#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 }
|
||||
FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Count, nullptr, nullptr, nullptr }
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings,
|
||||
ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ExportFileFormats exportFileFormat,
|
||||
ExportFileFormat 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 )
|
||||
{
|
||||
AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[exportFileFormat].m_getDevInst;
|
||||
AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[static_cast<std::size_t>(exportFileFormat)].m_getDevInst;
|
||||
|
||||
if (audioEncoderFactory)
|
||||
{
|
||||
@@ -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,20 +108,13 @@ 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(
|
||||
ProjectRenderer::ExportFileFormat ProjectRenderer::getFileFormatFromExtension(
|
||||
const QString & _ext )
|
||||
{
|
||||
int idx = 0;
|
||||
while( fileEncodeDevices[idx].m_fileFormat != NumFileFormats )
|
||||
while( fileEncodeDevices[idx].m_fileFormat != ExportFileFormat::Count )
|
||||
{
|
||||
if( QString( fileEncodeDevices[idx].m_extension ) == _ext )
|
||||
{
|
||||
@@ -129,16 +123,16 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension(
|
||||
++idx;
|
||||
}
|
||||
|
||||
return( WaveFile ); // Default.
|
||||
return( ExportFileFormat::Wave ); // Default.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString ProjectRenderer::getFileExtensionFromFormat(
|
||||
ExportFileFormats fmt )
|
||||
ExportFileFormat fmt )
|
||||
{
|
||||
return fileEncodeDevices[fmt].m_extension;
|
||||
return fileEncodeDevices[static_cast<std::size_t>(fmt)].m_extension;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -166,7 +159,6 @@ void ProjectRenderer::startProcessing()
|
||||
|
||||
void ProjectRenderer::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
#if 0
|
||||
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD)
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
@@ -182,12 +174,12 @@ void ProjectRenderer::run()
|
||||
|
||||
Engine::getSong()->startExport();
|
||||
// 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)
|
||||
@@ -201,8 +193,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();
|
||||
|
||||
@@ -231,8 +223,8 @@ void ProjectRenderer::updateConsoleProgress()
|
||||
{
|
||||
const int cols = 50;
|
||||
static int rot = 0;
|
||||
char buf[80];
|
||||
char prog[cols+1];
|
||||
auto buf = std::array<char, 80>{};
|
||||
auto prog = std::array<char, cols + 1>{};
|
||||
|
||||
for( int i = 0; i < cols; ++i )
|
||||
{
|
||||
@@ -240,14 +232,15 @@ void ProjectRenderer::updateConsoleProgress()
|
||||
}
|
||||
prog[cols] = 0;
|
||||
|
||||
const char * activity = (const char *) "|/-\\";
|
||||
memset( buf, 0, sizeof( buf ) );
|
||||
sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress,
|
||||
const auto activity = (const char*)"|/-\\";
|
||||
std::fill(buf.begin(), buf.end(), 0);
|
||||
sprintf(buf.data(), "\r|%s| %3d%% %c ", prog.data(), m_progress,
|
||||
activity[rot] );
|
||||
rot = ( rot+1 ) % 4;
|
||||
|
||||
fprintf( stderr, "%s", buf );
|
||||
fprintf( stderr, "%s", buf.data() );
|
||||
fflush( stderr );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "ProjectVersion.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(QString version, CompareType c) :
|
||||
@@ -93,12 +95,12 @@ int ProjectVersion::compare(const ProjectVersion & a, const ProjectVersion & b,
|
||||
if(aPat != bPat){ return aPat - bPat; }
|
||||
|
||||
// Decide how many optional identifiers we care about
|
||||
const int maxLabels = qMax(0, limit - 3);
|
||||
const int maxLabels = std::max(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());
|
||||
const int commonLabels = std::min(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(); }
|
||||
@@ -135,3 +137,6 @@ int ProjectVersion::compare(ProjectVersion v1, ProjectVersion v2)
|
||||
{
|
||||
return compare(v1, v2, std::min(v1.getCompareType(), v2.getCompareType()));
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -22,26 +22,57 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#define COMPILE_REMOTE_PLUGIN_BASE
|
||||
#include "RemotePlugin.h"
|
||||
|
||||
//#define DEBUG_REMOTE_PLUGIN
|
||||
#ifdef DEBUG_REMOTE_PLUGIN
|
||||
#include <QDebug>
|
||||
#endif
|
||||
|
||||
#include "RemotePlugin.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixHelpers.h"
|
||||
#include "Engine.h"
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#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 +86,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 +138,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 )
|
||||
{
|
||||
@@ -125,14 +175,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 +208,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 +267,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 +301,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 +309,7 @@ bool RemotePlugin::init(const QString &pluginExecutable,
|
||||
}
|
||||
#endif
|
||||
|
||||
sendMessage(message(IdSyncKey).addString(Engine::getSong()->syncKey()));
|
||||
resizeSharedProcessingMemory();
|
||||
|
||||
if( waitForInitDoneMsg )
|
||||
@@ -278,44 +324,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 )
|
||||
{
|
||||
MixHelpers::clear( _out_buf, frames );
|
||||
zeroSampleFrames(_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 )
|
||||
{
|
||||
MixHelpers::clear( _out_buf, frames );
|
||||
zeroSampleFrames(_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 );
|
||||
ch_cnt_t inputs = std::min<ch_cnt_t>(m_inputCount, DEFAULT_CHANNELS);
|
||||
|
||||
if( _in_buf != NULL && inputs > 0 )
|
||||
if( _in_buf != nullptr && inputs > 0 )
|
||||
{
|
||||
if( m_splitChannels )
|
||||
{
|
||||
@@ -323,18 +368,19 @@ 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 );
|
||||
auto target = m_audioBuffer.get();
|
||||
copyFromSampleFrames(target, _in_buf, frames);
|
||||
}
|
||||
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;
|
||||
@@ -357,33 +403,32 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
|
||||
waitForMessage( IdProcessingDone );
|
||||
unlock();
|
||||
|
||||
const ch_cnt_t outputs = qMin<ch_cnt_t>( m_outputCount,
|
||||
DEFAULT_CHANNELS );
|
||||
const ch_cnt_t outputs = std::min<ch_cnt_t>(m_outputCount,
|
||||
DEFAULT_CHANNELS);
|
||||
if( m_splitChannels )
|
||||
{
|
||||
for( ch_cnt_t ch = 0; ch < outputs; ++ch )
|
||||
{
|
||||
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,
|
||||
frames * BYTES_PER_FRAME );
|
||||
auto source = m_audioBuffer.get() + m_inputCount * frames;
|
||||
copyToSampleFrames(_out_buf, source, frames);
|
||||
}
|
||||
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
|
||||
MixHelpers::clear( _out_buf, frames );
|
||||
zeroSampleFrames(_out_buf, frames);
|
||||
|
||||
for( ch_cnt_t ch = 0; ch <
|
||||
qMin<int>( DEFAULT_CHANNELS, outputs ); ++ch )
|
||||
for (ch_cnt_t ch = 0; ch <
|
||||
std::min<int>(DEFAULT_CHANNELS, outputs); ++ch)
|
||||
{
|
||||
for( fpp_t frame = 0; frame < frames; ++frame )
|
||||
{
|
||||
@@ -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()->outputSampleRate() );
|
||||
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,40 +22,44 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "RenderManager.h"
|
||||
|
||||
#include "PatternStore.h"
|
||||
#include "Song.h"
|
||||
#include "BBTrackContainer.h"
|
||||
#include "BBTrack.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
RenderManager::RenderManager(
|
||||
const Mixer::qualitySettings & qualitySettings,
|
||||
const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ProjectRenderer::ExportFileFormats fmt,
|
||||
ProjectRenderer::ExportFileFormat 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();
|
||||
@@ -66,7 +70,7 @@ void RenderManager::renderNextTrack()
|
||||
{
|
||||
m_activeRenderer.reset();
|
||||
|
||||
if( m_tracksToRender.isEmpty() )
|
||||
if (m_tracksToRender.empty())
|
||||
{
|
||||
// nothing left to render
|
||||
restoreMutedState();
|
||||
@@ -94,31 +98,29 @@ void RenderManager::renderNextTrack()
|
||||
// Render the song into individual tracks
|
||||
void RenderManager::renderTracks()
|
||||
{
|
||||
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
|
||||
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();
|
||||
Track::Type type = tk->type();
|
||||
|
||||
// Don't render automation tracks
|
||||
if ( tk->isMuted() == false &&
|
||||
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
|
||||
( type == Track::Type::Instrument || type == Track::Type::Sample ) )
|
||||
{
|
||||
m_unmuted.push_back(tk);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
Track::Type type = tk->type();
|
||||
|
||||
// Don't render automation tracks
|
||||
if ( tk->isMuted() == false &&
|
||||
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
|
||||
( type == Track::Type::Instrument || type == Track::Type::Sample ) )
|
||||
{
|
||||
m_unmuted.push_back(tk);
|
||||
}
|
||||
@@ -148,13 +150,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();
|
||||
}
|
||||
@@ -168,7 +170,7 @@ void RenderManager::render(QString outputPath)
|
||||
// Unmute all tracks that were muted while rendering tracks
|
||||
void RenderManager::restoreMutedState()
|
||||
{
|
||||
while( !m_unmuted.isEmpty() )
|
||||
while (!m_unmuted.empty())
|
||||
{
|
||||
Track* restoreTrack = m_unmuted.back();
|
||||
m_unmuted.pop_back();
|
||||
@@ -181,7 +183,7 @@ QString RenderManager::pathForTrack(const Track *track, int num)
|
||||
{
|
||||
QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
|
||||
QString name = track->name();
|
||||
name = name.remove(QRegExp(FILENAME_FILTER));
|
||||
name = name.remove(QRegularExpression(FILENAME_FILTER));
|
||||
name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
|
||||
return QDir(m_outputPath).filePath(name);
|
||||
}
|
||||
@@ -201,3 +203,6 @@ void RenderManager::updateConsoleProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -24,29 +24,32 @@
|
||||
*/
|
||||
|
||||
#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()->outputSampleRate() ),
|
||||
m_size( size + m_fpp )
|
||||
{
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
m_buffer = new SampleFrame[ m_size ];
|
||||
zeroSampleFrames(m_buffer, m_size);
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
|
||||
RingBuffer::RingBuffer( float size ) :
|
||||
m_fpp( Engine::mixer()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::mixer()->processingSampleRate() )
|
||||
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::audioEngine()->outputSampleRate() )
|
||||
{
|
||||
m_size = msToFrames( size ) + m_fpp;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
m_buffer = new SampleFrame[ m_size ];
|
||||
zeroSampleFrames(m_buffer, m_size);
|
||||
m_position = 0;
|
||||
setSamplerateAware( true );
|
||||
//qDebug( "m_size %d, m_position %d", m_size, m_position );
|
||||
@@ -61,7 +64,7 @@ RingBuffer::~RingBuffer()
|
||||
|
||||
void RingBuffer::reset()
|
||||
{
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
zeroSampleFrames(m_buffer, m_size);
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
@@ -69,10 +72,10 @@ void RingBuffer::reset()
|
||||
void RingBuffer::changeSize( f_cnt_t size )
|
||||
{
|
||||
size += m_fpp;
|
||||
sampleFrame * tmp = m_buffer;
|
||||
SampleFrame* tmp = m_buffer;
|
||||
m_size = size;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
m_buffer = new SampleFrame[ m_size ];
|
||||
zeroSampleFrames(m_buffer, m_size);
|
||||
m_position = 0;
|
||||
delete[] tmp;
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,111 +118,109 @@ void RingBuffer::movePosition( float amount )
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::pop( sampleFrame * dst )
|
||||
void RingBuffer::pop( SampleFrame* dst )
|
||||
{
|
||||
if( m_position + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here
|
||||
{
|
||||
memcpy( dst, & m_buffer [ m_position ], m_fpp * sizeof( sampleFrame ) );
|
||||
memset( & m_buffer[m_position], 0, m_fpp * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [ m_position ], m_fpp * sizeof( SampleFrame ) );
|
||||
zeroSampleFrames(&m_buffer[m_position], m_fpp);
|
||||
}
|
||||
else
|
||||
{
|
||||
f_cnt_t first = m_size - m_position;
|
||||
f_cnt_t second = m_fpp - first;
|
||||
|
||||
memcpy( dst, & m_buffer [ m_position ], first * sizeof( sampleFrame ) );
|
||||
memset( & m_buffer [m_position], 0, first * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [ m_position ], first * sizeof( SampleFrame ) );
|
||||
zeroSampleFrames(&m_buffer[m_position], first);
|
||||
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
|
||||
memset( m_buffer, 0, second * sizeof( sampleFrame ) );
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( SampleFrame ) );
|
||||
zeroSampleFrames(m_buffer, second);
|
||||
}
|
||||
|
||||
m_position = ( m_position + m_fpp ) % m_size;
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::read( sampleFrame * dst, f_cnt_t offset )
|
||||
void RingBuffer::read( SampleFrame* dst, f_cnt_t offset )
|
||||
{
|
||||
f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
if( pos < 0 ) { pos += m_size; }
|
||||
|
||||
if( pos + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here
|
||||
{
|
||||
memcpy( dst, & m_buffer [pos], m_fpp * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [pos], m_fpp * sizeof( SampleFrame ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
f_cnt_t first = m_size - pos;
|
||||
f_cnt_t second = m_fpp - first;
|
||||
|
||||
memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [pos], first * sizeof( SampleFrame ) );
|
||||
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( SampleFrame ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::read( sampleFrame * dst, float offset )
|
||||
void RingBuffer::read( SampleFrame* dst, float offset )
|
||||
{
|
||||
read( dst, msToFrames( offset ) );
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::read( sampleFrame * dst, f_cnt_t offset, f_cnt_t length )
|
||||
void RingBuffer::read( SampleFrame* dst, f_cnt_t offset, f_cnt_t length )
|
||||
{
|
||||
f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
if( pos < 0 ) { pos += m_size; }
|
||||
|
||||
if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
|
||||
{
|
||||
memcpy( dst, & m_buffer [pos], length * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [pos], length * sizeof( SampleFrame ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
f_cnt_t first = m_size - pos;
|
||||
f_cnt_t second = length - first;
|
||||
|
||||
memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) );
|
||||
memcpy( dst, & m_buffer [pos], first * sizeof( SampleFrame ) );
|
||||
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
|
||||
memcpy( & dst [first], m_buffer, second * sizeof( SampleFrame ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::read( sampleFrame * dst, float offset, f_cnt_t length )
|
||||
void RingBuffer::read( SampleFrame* dst, float offset, f_cnt_t length )
|
||||
{
|
||||
read( dst, msToFrames( offset ), length );
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::write( sampleFrame * src, f_cnt_t offset, f_cnt_t length )
|
||||
void RingBuffer::write( SampleFrame* src, f_cnt_t offset, f_cnt_t length )
|
||||
{
|
||||
const f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
if( length == 0 ) { length = m_fpp; }
|
||||
|
||||
if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
|
||||
{
|
||||
memcpy( & m_buffer [pos], src, length * sizeof( sampleFrame ) );
|
||||
memcpy( & m_buffer [pos], src, length * sizeof( SampleFrame ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
f_cnt_t first = m_size - pos;
|
||||
f_cnt_t second = length - first;
|
||||
|
||||
memcpy( & m_buffer [pos], src, first * sizeof( sampleFrame ) );
|
||||
memcpy( & m_buffer [pos], src, first * sizeof( SampleFrame ) );
|
||||
|
||||
memcpy( m_buffer, & src [first], second * sizeof( sampleFrame ) );
|
||||
memcpy( m_buffer, & src [first], second * sizeof( SampleFrame ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::write( sampleFrame * src, float offset, f_cnt_t length )
|
||||
void RingBuffer::write( SampleFrame* src, float offset, f_cnt_t length )
|
||||
{
|
||||
write( src, msToFrames( offset ), length );
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeAdding( sampleFrame * src, f_cnt_t offset, f_cnt_t length )
|
||||
void RingBuffer::writeAdding( SampleFrame* src, f_cnt_t offset, f_cnt_t length )
|
||||
{
|
||||
const f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
if( length == 0 ) { length = m_fpp; }
|
||||
@@ -240,13 +241,13 @@ void RingBuffer::writeAdding( sampleFrame * src, f_cnt_t offset, f_cnt_t length
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeAdding( sampleFrame * src, float offset, f_cnt_t length )
|
||||
void RingBuffer::writeAdding( SampleFrame* src, float offset, f_cnt_t length )
|
||||
{
|
||||
writeAdding( src, msToFrames( offset ), length );
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level )
|
||||
void RingBuffer::writeAddingMultiplied( SampleFrame* src, f_cnt_t offset, f_cnt_t length, float level )
|
||||
{
|
||||
const f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
//qDebug( "pos %d m_pos %d ofs %d siz %d", pos, m_position, offset, m_size );
|
||||
@@ -268,14 +269,14 @@ void RingBuffer::writeAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level )
|
||||
void RingBuffer::writeAddingMultiplied( SampleFrame* src, float offset, f_cnt_t length, float level )
|
||||
{
|
||||
f_cnt_t ofs = msToFrames( offset );
|
||||
writeAddingMultiplied( src, ofs, length, level );
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level )
|
||||
void RingBuffer::writeSwappedAddingMultiplied( SampleFrame* src, f_cnt_t offset, f_cnt_t length, float level )
|
||||
{
|
||||
const f_cnt_t pos = ( m_position + offset ) % m_size;
|
||||
if( length == 0 ) { length = m_fpp; }
|
||||
@@ -296,7 +297,7 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, f_cnt_t offset
|
||||
}
|
||||
|
||||
|
||||
void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level )
|
||||
void RingBuffer::writeSwappedAddingMultiplied( SampleFrame* src, float offset, f_cnt_t length, float level )
|
||||
{
|
||||
writeSwappedAddingMultiplied( src, msToFrames( offset ), length, level );
|
||||
}
|
||||
@@ -304,13 +305,14 @@ 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()->outputSampleRate() ) / m_samplerate;
|
||||
m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
|
||||
m_samplerate = Engine::mixer()->processingSampleRate();
|
||||
m_samplerate = Engine::audioEngine()->outputSampleRate();
|
||||
delete[] m_buffer;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
m_buffer = new SampleFrame[ m_size ];
|
||||
zeroSampleFrames(m_buffer, m_size);
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
253
src/core/Sample.cpp
Normal file
253
src/core/Sample.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Sample.cpp - State for container-class SampleBuffer
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@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 "Sample.h"
|
||||
|
||||
#include "lmms_math.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
Sample::Sample(const QString& audioFile)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(audioFile))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const QByteArray& base64, int sampleRate)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(base64, sampleRate))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const SampleFrame* data, size_t numFrames, int sampleRate)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(data, numFrames, sampleRate))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(std::shared_ptr<const SampleBuffer> buffer)
|
||||
: m_buffer(buffer)
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const Sample& other)
|
||||
: m_buffer(other.m_buffer)
|
||||
, m_startFrame(other.startFrame())
|
||||
, m_endFrame(other.endFrame())
|
||||
, m_loopStartFrame(other.loopStartFrame())
|
||||
, m_loopEndFrame(other.loopEndFrame())
|
||||
, m_amplification(other.amplification())
|
||||
, m_frequency(other.frequency())
|
||||
, m_reversed(other.reversed())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(Sample&& other)
|
||||
: m_buffer(std::move(other.m_buffer))
|
||||
, m_startFrame(other.startFrame())
|
||||
, m_endFrame(other.endFrame())
|
||||
, m_loopStartFrame(other.loopStartFrame())
|
||||
, m_loopEndFrame(other.loopEndFrame())
|
||||
, m_amplification(other.amplification())
|
||||
, m_frequency(other.frequency())
|
||||
, m_reversed(other.reversed())
|
||||
{
|
||||
}
|
||||
|
||||
auto Sample::operator=(const Sample& other) -> Sample&
|
||||
{
|
||||
m_buffer = other.m_buffer;
|
||||
m_startFrame = other.startFrame();
|
||||
m_endFrame = other.endFrame();
|
||||
m_loopStartFrame = other.loopStartFrame();
|
||||
m_loopEndFrame = other.loopEndFrame();
|
||||
m_amplification = other.amplification();
|
||||
m_frequency = other.frequency();
|
||||
m_reversed = other.reversed();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Sample::operator=(Sample&& other) -> Sample&
|
||||
{
|
||||
m_buffer = std::move(other.m_buffer);
|
||||
m_startFrame = other.startFrame();
|
||||
m_endFrame = other.endFrame();
|
||||
m_loopStartFrame = other.loopStartFrame();
|
||||
m_loopEndFrame = other.loopEndFrame();
|
||||
m_amplification = other.amplification();
|
||||
m_frequency = other.frequency();
|
||||
m_reversed = other.reversed();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const
|
||||
{
|
||||
assert(numFrames > 0);
|
||||
assert(desiredFrequency > 0);
|
||||
|
||||
const auto pastBounds = state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards);
|
||||
if (loopMode == Loop::Off && pastBounds) { return false; }
|
||||
|
||||
const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / desiredFrequency;
|
||||
const auto inputSampleRate = m_buffer->sampleRate();
|
||||
const auto resampleRatio = outputSampleRate / inputSampleRate;
|
||||
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];
|
||||
|
||||
state->m_frameIndex = std::max<int>(m_startFrame, state->m_frameIndex);
|
||||
|
||||
auto playBuffer = std::vector<SampleFrame>(numFrames / resampleRatio + marginSize);
|
||||
playRaw(playBuffer.data(), playBuffer.size(), state, loopMode);
|
||||
|
||||
state->resampler().setRatio(resampleRatio);
|
||||
|
||||
const auto resampleResult
|
||||
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
|
||||
advance(state, resampleResult.inputFramesUsed, loopMode);
|
||||
|
||||
const auto outputFrames = static_cast<f_cnt_t>(resampleResult.outputFramesGenerated);
|
||||
if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, SampleFrame{}); }
|
||||
|
||||
if (!approximatelyEqual(m_amplification, 1.0f))
|
||||
{
|
||||
for (auto i = std::size_t{0}; i < numFrames; ++i)
|
||||
{
|
||||
dst[i][0] *= m_amplification;
|
||||
dst[i][1] *= m_amplification;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Sample::sampleDuration() const -> std::chrono::milliseconds
|
||||
{
|
||||
const auto numFrames = endFrame() - startFrame();
|
||||
const auto duration = numFrames / static_cast<float>(m_buffer->sampleRate()) * 1000;
|
||||
return std::chrono::milliseconds{static_cast<int>(duration)};
|
||||
}
|
||||
|
||||
void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame)
|
||||
{
|
||||
setStartFrame(startFrame);
|
||||
setEndFrame(endFrame);
|
||||
setLoopStartFrame(loopStartFrame);
|
||||
setLoopEndFrame(loopEndFrame);
|
||||
}
|
||||
|
||||
void Sample::playRaw(SampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const
|
||||
{
|
||||
if (m_buffer->size() < 1) { return; }
|
||||
|
||||
auto index = state->m_frameIndex;
|
||||
auto backwards = state->m_backwards;
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (index < 0 || index >= m_endFrame) { return; }
|
||||
break;
|
||||
case Loop::On:
|
||||
if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; }
|
||||
else if (index >= m_loopEndFrame) { index = m_loopStartFrame; }
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (index < m_loopStartFrame && backwards)
|
||||
{
|
||||
index = m_loopStartFrame;
|
||||
backwards = false;
|
||||
}
|
||||
else if (index >= m_loopEndFrame)
|
||||
{
|
||||
index = m_loopEndFrame - 1;
|
||||
backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index];
|
||||
backwards ? --index : ++index;
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const
|
||||
{
|
||||
state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount;
|
||||
if (loopMode == Loop::Off) { return; }
|
||||
|
||||
const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame);
|
||||
const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame);
|
||||
const auto loopSize = m_loopEndFrame - m_loopStartFrame;
|
||||
if (loopSize == 0) { return; }
|
||||
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::On:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % loopSize;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % loopSize;
|
||||
}
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize;
|
||||
state->m_backwards = false;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize;
|
||||
state->m_backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
File diff suppressed because it is too large
Load Diff
340
src/core/SampleClip.cpp
Normal file
340
src/core/SampleClip.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 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 <QFileInfo>
|
||||
|
||||
#include "PathUtil.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "SampleClipView.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "TimeLineWidget.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying)
|
||||
: Clip(_track)
|
||||
, m_sample(std::move(sample))
|
||||
, 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()));
|
||||
|
||||
//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 and jumps
|
||||
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::Type::Pattern:
|
||||
setAutoResize( true );
|
||||
break;
|
||||
|
||||
case TrackContainer::Type::Song:
|
||||
// move down
|
||||
default:
|
||||
setAutoResize( false );
|
||||
break;
|
||||
}
|
||||
updateTrackClips();
|
||||
}
|
||||
|
||||
SampleClip::SampleClip(Track* track)
|
||||
: SampleClip(track, Sample(), false)
|
||||
{
|
||||
}
|
||||
|
||||
SampleClip::SampleClip(const SampleClip& orig) :
|
||||
SampleClip(orig.getTrack(), orig.m_sample, orig.m_isPlaying)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SampleClip::~SampleClip()
|
||||
{
|
||||
auto sampletrack = dynamic_cast<SampleTrack*>(getTrack());
|
||||
if ( sampletrack )
|
||||
{
|
||||
sampletrack->updateClips();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::changeLength( const TimePos & _length )
|
||||
{
|
||||
Clip::changeLength(std::max(static_cast<int>(_length), 1));
|
||||
}
|
||||
|
||||
void SampleClip::changeLengthToSampleLength()
|
||||
{
|
||||
int length = m_sample.sampleSize() / Engine::framesPerTick();
|
||||
changeLength(length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const QString& SampleClip::sampleFile() const
|
||||
{
|
||||
return m_sample.sampleFile();
|
||||
}
|
||||
|
||||
bool SampleClip::hasSampleFileLoaded(const QString & filename) const
|
||||
{
|
||||
return m_sample.sampleFile() == filename;
|
||||
}
|
||||
|
||||
void SampleClip::setSampleBuffer(std::shared_ptr<const SampleBuffer> sb)
|
||||
{
|
||||
{
|
||||
const auto guard = Engine::audioEngine()->requestChangesGuard();
|
||||
m_sample = Sample(std::move(sb));
|
||||
}
|
||||
updateLength();
|
||||
|
||||
emit sampleChanged();
|
||||
|
||||
Engine::getSong()->setModified();
|
||||
}
|
||||
|
||||
void SampleClip::setSampleFile(const QString& sf)
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
if (!sf.isEmpty())
|
||||
{
|
||||
//Otherwise set it to the sample's length
|
||||
m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf));
|
||||
length = sampleLength();
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
//If there is no sample, make the clip a bar long
|
||||
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
|
||||
float den = Engine::getSong()->getTimeSigModel().getDenominator();
|
||||
length = DefaultTicksPerBar * (nom / den);
|
||||
}
|
||||
|
||||
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::Type::SamplePlayHandle );
|
||||
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();
|
||||
|
||||
Engine::getSong()->setModified();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimePos SampleClip::sampleLength() const
|
||||
{
|
||||
return static_cast<int>(m_sample.sampleSize() / Engine::framesPerTick(m_sample.sampleRate()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleStartFrame(f_cnt_t startFrame)
|
||||
{
|
||||
m_sample.setStartFrame(startFrame);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSamplePlayLength(f_cnt_t length)
|
||||
{
|
||||
m_sample.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_sample.toBase64());
|
||||
}
|
||||
|
||||
_this.setAttribute( "sample_rate", m_sample.sampleRate());
|
||||
if (const auto& c = color())
|
||||
{
|
||||
_this.setAttribute("color", c->name());
|
||||
}
|
||||
if (m_sample.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() );
|
||||
}
|
||||
|
||||
if (const auto srcFile = _this.attribute("src"); !srcFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists())
|
||||
{
|
||||
setSampleFile(srcFile);
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), srcFile)); }
|
||||
}
|
||||
|
||||
if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) )
|
||||
{
|
||||
auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() :
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
auto buffer = gui::SampleLoader::createBufferFromBase64(_this.attribute("data"), sampleRate);
|
||||
m_sample = Sample(std::move(buffer));
|
||||
}
|
||||
changeLength( _this.attribute( "len" ).toInt() );
|
||||
setMuted( _this.attribute( "muted" ).toInt() );
|
||||
setStartTimeOffset( _this.attribute( "off" ).toInt() );
|
||||
|
||||
if (_this.hasAttribute("color"))
|
||||
{
|
||||
setColor(QColor{_this.attribute("color")});
|
||||
}
|
||||
|
||||
if(_this.hasAttribute("reversed"))
|
||||
{
|
||||
m_sample.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
|
||||
236
src/core/SampleDecoder.cpp
Normal file
236
src/core/SampleDecoder.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* SampleDecoder.cpp - Decodes audio files in various formats
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@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 "SampleDecoder.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <sndfile.h>
|
||||
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#endif
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "DrumSynth.h"
|
||||
#include "Engine.h"
|
||||
#include "lmms_basics.h"
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace {
|
||||
|
||||
using Decoder = std::optional<SampleDecoder::Result> (*)(const QString&);
|
||||
|
||||
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
#endif
|
||||
|
||||
static constexpr std::array<Decoder, 3> decoders = {&decodeSampleSF,
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
&decodeSampleOggVorbis,
|
||||
#endif
|
||||
&decodeSampleDS};
|
||||
|
||||
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
SNDFILE* sndFile = nullptr;
|
||||
auto sfInfo = SF_INFO{};
|
||||
|
||||
// TODO: Remove use of QFile
|
||||
auto file = QFile{audioFile};
|
||||
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
|
||||
|
||||
sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false);
|
||||
if (sf_error(sndFile) != 0) { return std::nullopt; }
|
||||
|
||||
auto buf = std::vector<sample_t>(sfInfo.channels * sfInfo.frames);
|
||||
sf_read_float(sndFile, buf.data(), buf.size());
|
||||
|
||||
sf_close(sndFile);
|
||||
file.close();
|
||||
|
||||
auto result = std::vector<SampleFrame>(sfInfo.frames);
|
||||
for (int i = 0; i < static_cast<int>(result.size()); ++i)
|
||||
{
|
||||
if (sfInfo.channels == 1)
|
||||
{
|
||||
// Upmix from mono to stereo
|
||||
result[i] = {buf[i], buf[i]};
|
||||
}
|
||||
else if (sfInfo.channels > 1)
|
||||
{
|
||||
// TODO: Add support for higher number of channels (i.e., 5.1 channel systems)
|
||||
// The current behavior assumes stereo in all cases excluding mono.
|
||||
// This may not be the expected behavior, given some audio files with a higher number of channels.
|
||||
result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]};
|
||||
}
|
||||
}
|
||||
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(sfInfo.samplerate)};
|
||||
}
|
||||
|
||||
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
// Populated by DrumSynth::GetDSFileSamples
|
||||
int_sample_t* dataPtr = nullptr;
|
||||
|
||||
auto ds = DrumSynth{};
|
||||
const auto engineRate = Engine::audioEngine()->outputSampleRate();
|
||||
const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate);
|
||||
const auto data = std::unique_ptr<int_sample_t[]>{dataPtr}; // NOLINT, we have to use a C-style array here
|
||||
|
||||
if (frames <= 0 || !data) { return std::nullopt; }
|
||||
|
||||
auto result = std::vector<SampleFrame>(frames);
|
||||
src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS);
|
||||
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(engineRate)};
|
||||
}
|
||||
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
static auto s_read = [](void* buffer, size_t size, size_t count, void* stream) -> size_t {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
return file->read(static_cast<char*>(buffer), size * count);
|
||||
};
|
||||
|
||||
static auto s_seek = [](void* stream, ogg_int64_t offset, int whence) -> int {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
if (whence == SEEK_SET) { file->seek(offset); }
|
||||
else if (whence == SEEK_CUR) { file->seek(file->pos() + offset); }
|
||||
else if (whence == SEEK_END) { file->seek(file->size() + offset); }
|
||||
else { return -1; }
|
||||
return 0;
|
||||
};
|
||||
|
||||
static auto s_close = [](void* stream) -> int {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
file->close();
|
||||
return 0;
|
||||
};
|
||||
|
||||
static auto s_tell = [](void* stream) -> long {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
return file->pos();
|
||||
};
|
||||
|
||||
static ov_callbacks s_callbacks = {s_read, s_seek, s_close, s_tell};
|
||||
|
||||
// TODO: Remove use of QFile
|
||||
auto file = QFile{audioFile};
|
||||
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
|
||||
|
||||
auto vorbisFile = OggVorbis_File{};
|
||||
if (ov_open_callbacks(&file, &vorbisFile, nullptr, 0, s_callbacks) < 0) { return std::nullopt; }
|
||||
|
||||
const auto vorbisInfo = ov_info(&vorbisFile, -1);
|
||||
if (vorbisInfo == nullptr) { return std::nullopt; }
|
||||
|
||||
const auto numChannels = vorbisInfo->channels;
|
||||
const auto sampleRate = vorbisInfo->rate;
|
||||
const auto numSamples = ov_pcm_total(&vorbisFile, -1);
|
||||
if (numSamples < 0) { return std::nullopt; }
|
||||
|
||||
auto buffer = std::vector<float>(numSamples);
|
||||
auto output = static_cast<float**>(nullptr);
|
||||
|
||||
auto totalSamplesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
auto samplesRead = ov_read_float(&vorbisFile, &output, numSamples, 0);
|
||||
|
||||
if (samplesRead < 0) { return std::nullopt; }
|
||||
else if (samplesRead == 0) { break; }
|
||||
|
||||
std::copy_n(*output, samplesRead, buffer.begin() + totalSamplesRead);
|
||||
totalSamplesRead += samplesRead;
|
||||
}
|
||||
|
||||
auto result = std::vector<SampleFrame>(totalSamplesRead / numChannels);
|
||||
for (auto i = std::size_t{0}; i < result.size(); ++i)
|
||||
{
|
||||
if (numChannels == 1) { result[i] = {buffer[i], buffer[i]}; }
|
||||
else if (numChannels > 1) { result[i] = {buffer[i * numChannels], buffer[i * numChannels + 1]}; }
|
||||
}
|
||||
|
||||
ov_clear(&vorbisFile);
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(sampleRate)};
|
||||
}
|
||||
#endif // LMMS_HAVE_OGGVORBIS
|
||||
} // namespace
|
||||
|
||||
auto SampleDecoder::supportedAudioTypes() -> const std::vector<AudioType>&
|
||||
{
|
||||
static const auto s_audioTypes = [] {
|
||||
auto types = std::vector<AudioType>();
|
||||
|
||||
// Add DrumSynth by default since that support comes from us
|
||||
types.push_back(AudioType{"DrumSynth", "ds"});
|
||||
|
||||
auto sfFormatInfo = SF_FORMAT_INFO{};
|
||||
auto simpleTypeCount = 0;
|
||||
sf_command(nullptr, SFC_GET_SIMPLE_FORMAT_COUNT, &simpleTypeCount, sizeof(int));
|
||||
|
||||
// TODO: Ideally, this code should be iterating over the major formats, but some important extensions such as
|
||||
// *.ogg are not included. This is planned for future versions of sndfile.
|
||||
for (int simple = 0; simple < simpleTypeCount; ++simple)
|
||||
{
|
||||
sfFormatInfo.format = simple;
|
||||
sf_command(nullptr, SFC_GET_SIMPLE_FORMAT, &sfFormatInfo, sizeof(sfFormatInfo));
|
||||
|
||||
auto it = std::find_if(types.begin(), types.end(),
|
||||
[&](const AudioType& type) { return sfFormatInfo.extension == type.extension; });
|
||||
if (it != types.end()) { continue; }
|
||||
|
||||
auto name = std::string{sfFormatInfo.extension};
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char ch) { return std::toupper(ch); });
|
||||
|
||||
types.push_back(AudioType{std::move(name), sfFormatInfo.extension});
|
||||
}
|
||||
|
||||
std::sort(types.begin(), types.end(), [&](const AudioType& a, const AudioType& b) { return a.name < b.name; });
|
||||
return types;
|
||||
}();
|
||||
return s_audioTypes;
|
||||
}
|
||||
|
||||
auto SampleDecoder::decode(const QString& audioFile) -> std::optional<Result>
|
||||
{
|
||||
auto result = std::optional<Result>{};
|
||||
for (const auto& decoder : decoders)
|
||||
{
|
||||
result = decoder(audioFile);
|
||||
if (result) { break; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -23,25 +23,28 @@
|
||||
*/
|
||||
|
||||
#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 ) :
|
||||
PlayHandle( TypeSamplePlayHandle ),
|
||||
m_sampleBuffer( sharedObject::ref( sampleBuffer ) ),
|
||||
SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioPort) :
|
||||
PlayHandle( Type::SamplePlayHandle ),
|
||||
m_sample(sample),
|
||||
m_doneMayReturnTrue( true ),
|
||||
m_frame( 0 ),
|
||||
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)
|
||||
{
|
||||
@@ -53,19 +56,18 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) :
|
||||
SamplePlayHandle( new SampleBuffer( sampleFile ) , true)
|
||||
SamplePlayHandle(new Sample(sampleFile), true)
|
||||
{
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) :
|
||||
SamplePlayHandle( tco->sampleBuffer() , false)
|
||||
SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) :
|
||||
SamplePlayHandle(&clip->sample(), false)
|
||||
{
|
||||
m_track = tco->getTrack();
|
||||
setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() );
|
||||
m_track = clip->getTrack();
|
||||
setAudioPort( ( (SampleTrack *)clip->getTrack() )->audioPort() );
|
||||
}
|
||||
|
||||
|
||||
@@ -73,47 +75,48 @@ SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) :
|
||||
|
||||
SamplePlayHandle::~SamplePlayHandle()
|
||||
{
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
if( m_ownAudioPort )
|
||||
{
|
||||
delete audioPort();
|
||||
delete m_sample;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SamplePlayHandle::play( sampleFrame * buffer )
|
||||
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() )
|
||||
{
|
||||
memset( buffer, 0, sizeof( sampleFrame ) * fpp );
|
||||
zeroSampleFrames(buffer, fpp);
|
||||
return;
|
||||
}
|
||||
|
||||
sampleFrame * workingBuffer = buffer;
|
||||
SampleFrame* workingBuffer = buffer;
|
||||
f_cnt_t frames = fpp;
|
||||
|
||||
// apply offset for the first period
|
||||
if( framesDone() == 0 )
|
||||
{
|
||||
memset( buffer, 0, sizeof( sampleFrame ) * offset() );
|
||||
zeroSampleFrames(buffer, offset());
|
||||
workingBuffer += offset();
|
||||
frames -= offset();
|
||||
}
|
||||
|
||||
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_sample->play(workingBuffer, &m_state, frames, DefaultBaseFreq))
|
||||
{
|
||||
memset( workingBuffer, 0, frames * sizeof( sampleFrame ) );
|
||||
zeroSampleFrames(workingBuffer, frames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +136,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 +144,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_sample->endFrame() - m_sample->startFrame()) *
|
||||
(static_cast<float>(Engine::audioEngine()->outputSampleRate()) / m_sample->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 ) :
|
||||
PlayHandle( TypeSamplePlayHandle ),
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SampleRecordHandle::SampleRecordHandle( SampleClip* clip ) :
|
||||
PlayHandle( Type::SamplePlayHandle ),
|
||||
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 )
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,35 +51,30 @@ SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) :
|
||||
|
||||
SampleRecordHandle::~SampleRecordHandle()
|
||||
{
|
||||
if( !m_buffers.empty() )
|
||||
{
|
||||
SampleBuffer* sb;
|
||||
createSampleBuffer( &sb );
|
||||
m_tco->setSampleBuffer( sb );
|
||||
}
|
||||
|
||||
if (!m_buffers.empty()) { m_clip->setSampleBuffer(createSampleBuffer()); }
|
||||
|
||||
while( !m_buffers.empty() )
|
||||
{
|
||||
delete[] m_buffers.front().first;
|
||||
m_buffers.erase( m_buffers.begin() );
|
||||
}
|
||||
m_tco->setRecord( false );
|
||||
m_clip->setRecord( false );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ )
|
||||
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;
|
||||
|
||||
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 +92,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,38 +106,30 @@ f_cnt_t SampleRecordHandle::framesRecorded() const
|
||||
|
||||
|
||||
|
||||
void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
|
||||
std::shared_ptr<const SampleBuffer> SampleRecordHandle::createSampleBuffer()
|
||||
{
|
||||
const f_cnt_t frames = framesRecorded();
|
||||
// create buffer to store all recorded buffers in
|
||||
sampleFrame * data = new sampleFrame[frames];
|
||||
// make sure buffer is cleaned up properly at the end...
|
||||
sampleFrame * data_ptr = data;
|
||||
|
||||
|
||||
assert( data != NULL );
|
||||
auto bigBuffer = std::vector<SampleFrame>(frames);
|
||||
|
||||
// now copy all buffers into big buffer
|
||||
for( bufferList::const_iterator it = m_buffers.begin();
|
||||
it != m_buffers.end(); ++it )
|
||||
auto framesCopied = 0;
|
||||
for (const auto& [buf, numFrames] : m_buffers)
|
||||
{
|
||||
memcpy( data_ptr, ( *it ).first, ( *it ).second *
|
||||
sizeof( sampleFrame ) );
|
||||
data_ptr += ( *it ).second;
|
||||
std::copy_n(buf, numFrames, bigBuffer.begin() + framesCopied);
|
||||
framesCopied += numFrames;
|
||||
}
|
||||
|
||||
// create according sample-buffer out of big buffer
|
||||
*sampleBuf = new SampleBuffer( data, frames );
|
||||
( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() );
|
||||
delete[] data;
|
||||
return std::make_shared<const SampleBuffer>(std::move(bigBuffer), Engine::audioEngine()->inputSampleRate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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 +141,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,21 +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_pattern(nullptr)
|
||||
m_midiClip(nullptr)
|
||||
{
|
||||
m_stepRecorderWidget.hide();
|
||||
}
|
||||
@@ -85,7 +91,7 @@ void StepRecorder::notePressed(const Note & n)
|
||||
StepNote* stepNote = findCurStepNote(n.key());
|
||||
if(stepNote == nullptr)
|
||||
{
|
||||
m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning())));
|
||||
m_curStepNotes.push_back(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning())));
|
||||
m_pianoRoll.update();
|
||||
}
|
||||
else if (stepNote->isReleased())
|
||||
@@ -109,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)
|
||||
@@ -169,15 +175,15 @@ void StepRecorder::setStepsLength(const TimePos& newLength)
|
||||
updateWidget();
|
||||
}
|
||||
|
||||
QVector<Note*> StepRecorder::getCurStepNotes()
|
||||
std::vector<Note*> StepRecorder::getCurStepNotes()
|
||||
{
|
||||
QVector<Note*> notes;
|
||||
std::vector<Note*> notes;
|
||||
|
||||
if(m_isStepInProgress)
|
||||
{
|
||||
for(StepNote* stepNote: m_curStepNotes)
|
||||
for (StepNote* stepNote: m_curStepNotes)
|
||||
{
|
||||
notes.append(&stepNote->m_note);
|
||||
notes.push_back(&stepNote->m_note);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,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();
|
||||
@@ -267,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()
|
||||
@@ -282,18 +288,13 @@ void StepRecorder::removeNotesReleasedForTooLong()
|
||||
int nextTimout = std::numeric_limits<int>::max();
|
||||
bool notesRemoved = false;
|
||||
|
||||
QMutableVectorIterator<StepNote*> itr(m_curStepNotes);
|
||||
while (itr.hasNext())
|
||||
for (const auto& stepNote : m_curStepNotes)
|
||||
{
|
||||
StepNote* stepNote = itr.next();
|
||||
|
||||
if(stepNote->isReleased())
|
||||
if (stepNote->isReleased())
|
||||
{
|
||||
const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout
|
||||
if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS)
|
||||
{
|
||||
delete stepNote;
|
||||
itr.remove();
|
||||
notesRemoved = true;
|
||||
}
|
||||
else
|
||||
@@ -303,6 +304,17 @@ void StepRecorder::removeNotesReleasedForTooLong()
|
||||
}
|
||||
}
|
||||
|
||||
m_curStepNotes.erase(std::remove_if(m_curStepNotes.begin(), m_curStepNotes.end(), [](auto stepNote)
|
||||
{
|
||||
bool shouldRemove = stepNote->isReleased() && stepNote->timeSinceReleased() >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS;
|
||||
if (shouldRemove)
|
||||
{
|
||||
delete stepNote;
|
||||
}
|
||||
|
||||
return shouldRemove;
|
||||
}), m_curStepNotes.end());
|
||||
|
||||
if(notesRemoved)
|
||||
{
|
||||
m_pianoRoll.update();
|
||||
@@ -365,3 +377,5 @@ StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -31,42 +31,39 @@
|
||||
#include "Song.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
|
||||
const float _max, const float _step,
|
||||
const float _scale, Model * _parent,
|
||||
const QString & _display_name ) :
|
||||
FloatModel( _val, _min, _max, _step, _parent, _display_name ),
|
||||
m_tempoSyncMode( SyncNone ),
|
||||
m_tempoLastSyncMode( SyncNone ),
|
||||
m_tempoSyncMode( SyncMode::None ),
|
||||
m_tempoLastSyncMode( SyncMode::None ),
|
||||
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() );
|
||||
setTempoSync( static_cast<SyncMode>(_item->data().toInt()) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TempoSyncKnobModel::setTempoSync( int _note_type )
|
||||
void TempoSyncKnobModel::setTempoSync( SyncMode _note_type )
|
||||
{
|
||||
setSyncMode( ( TempoSyncMode ) _note_type );
|
||||
setSyncMode( _note_type );
|
||||
Engine::getSong()->setModified();
|
||||
}
|
||||
|
||||
@@ -77,34 +74,34 @@ void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm )
|
||||
{
|
||||
float conversionFactor = 1.0;
|
||||
|
||||
if( m_tempoSyncMode )
|
||||
if( m_tempoSyncMode != SyncMode::None )
|
||||
{
|
||||
switch( m_tempoSyncMode )
|
||||
{
|
||||
case SyncCustom:
|
||||
case SyncMode::Custom:
|
||||
conversionFactor =
|
||||
static_cast<float>( m_custom.getDenominator() ) /
|
||||
static_cast<float>( m_custom.getNumerator() );
|
||||
break;
|
||||
case SyncDoubleWholeNote:
|
||||
case SyncMode::DoubleWholeNote:
|
||||
conversionFactor = 0.125;
|
||||
break;
|
||||
case SyncWholeNote:
|
||||
case SyncMode::WholeNote:
|
||||
conversionFactor = 0.25;
|
||||
break;
|
||||
case SyncHalfNote:
|
||||
case SyncMode::HalfNote:
|
||||
conversionFactor = 0.5;
|
||||
break;
|
||||
case SyncQuarterNote:
|
||||
case SyncMode::QuarterNote:
|
||||
conversionFactor = 1.0;
|
||||
break;
|
||||
case SyncEighthNote:
|
||||
case SyncMode::EighthNote:
|
||||
conversionFactor = 2.0;
|
||||
break;
|
||||
case SyncSixteenthNote:
|
||||
case SyncMode::SixteenthNote:
|
||||
conversionFactor = 4.0;
|
||||
break;
|
||||
case SyncThirtysecondNote:
|
||||
case SyncMode::ThirtysecondNote:
|
||||
conversionFactor = 8.0;
|
||||
break;
|
||||
default: ;
|
||||
@@ -120,6 +117,10 @@ void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm )
|
||||
emit syncModeChanged( m_tempoSyncMode );
|
||||
m_tempoLastSyncMode = m_tempoSyncMode;
|
||||
}
|
||||
else if (m_tempoSyncMode == SyncMode::Custom)
|
||||
{
|
||||
emit syncModeChanged(m_tempoSyncMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,21 +142,21 @@ void TempoSyncKnobModel::loadSettings( const QDomElement & _this,
|
||||
{
|
||||
FloatModel::loadSettings( _this, _name );
|
||||
m_custom.loadSettings( _this, _name );
|
||||
setSyncMode( ( TempoSyncMode ) _this.attribute( _name + "_syncmode" ).toInt() );
|
||||
setSyncMode( ( SyncMode ) _this.attribute( _name + "_syncmode" ).toInt() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode )
|
||||
void TempoSyncKnobModel::setSyncMode( SyncMode _new_mode )
|
||||
{
|
||||
if( m_tempoSyncMode != _new_mode )
|
||||
{
|
||||
m_tempoSyncMode = _new_mode;
|
||||
if( _new_mode == SyncCustom )
|
||||
if( _new_mode == SyncMode::Custom )
|
||||
{
|
||||
connect( &m_custom, SIGNAL( dataChanged() ),
|
||||
this, SLOT( updateCustom() ),
|
||||
connect( &m_custom, SIGNAL(dataChanged()),
|
||||
this, SLOT(updateCustom()),
|
||||
Qt::DirectConnection );
|
||||
}
|
||||
}
|
||||
@@ -177,12 +178,8 @@ void TempoSyncKnobModel::setScale( float _new_scale )
|
||||
|
||||
void TempoSyncKnobModel::updateCustom()
|
||||
{
|
||||
setSyncMode( SyncCustom );
|
||||
setSyncMode( SyncMode::Custom );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
85
src/core/ThreadPool.cpp
Normal file
85
src/core/ThreadPool.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* ThreadPool.cpp
|
||||
*
|
||||
* Copyright (c) 2024 saker
|
||||
*
|
||||
* 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 "ThreadPool.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace lmms {
|
||||
ThreadPool::ThreadPool(size_t numWorkers)
|
||||
{
|
||||
assert(numWorkers > 0);
|
||||
|
||||
m_workers.reserve(numWorkers);
|
||||
for (size_t i = 0; i < numWorkers; ++i)
|
||||
{
|
||||
m_workers.emplace_back([this] { run(); });
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
{
|
||||
const auto lock = std::unique_lock{m_runMutex};
|
||||
m_done = true;
|
||||
}
|
||||
|
||||
m_runCond.notify_all();
|
||||
|
||||
for (auto& worker : m_workers)
|
||||
{
|
||||
if (worker.joinable()) { worker.join(); }
|
||||
}
|
||||
}
|
||||
|
||||
auto ThreadPool::numWorkers() const -> size_t
|
||||
{
|
||||
return m_workers.size();
|
||||
}
|
||||
|
||||
void ThreadPool::run()
|
||||
{
|
||||
while (!m_done)
|
||||
{
|
||||
std::function<void()> task;
|
||||
{
|
||||
auto lock = std::unique_lock{m_runMutex};
|
||||
m_runCond.wait(lock, [this] { return !m_queue.empty() || m_done; });
|
||||
|
||||
if (m_done) { break; }
|
||||
task = m_queue.front();
|
||||
m_queue.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
auto ThreadPool::instance() -> ThreadPool&
|
||||
{
|
||||
static auto s_pool = ThreadPool{s_numWorkers};
|
||||
return s_pool;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,8 +25,12 @@
|
||||
|
||||
#include "TimePos.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "MeterModel.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
TimeSig::TimeSig( int num, int denom ) :
|
||||
m_num(num),
|
||||
m_denom(denom)
|
||||
@@ -72,7 +76,12 @@ TimePos TimePos::quantize(float bars) const
|
||||
//Offset from the lower position
|
||||
int offset = m_ticks % interval;
|
||||
//1 if we should snap up, 0 if we shouldn't
|
||||
int snapUp = offset / (interval / 2);
|
||||
// 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;
|
||||
}
|
||||
@@ -153,11 +162,11 @@ tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const
|
||||
|
||||
f_cnt_t TimePos::frames( const float framesPerTick ) const
|
||||
{
|
||||
if( m_ticks >= 0 )
|
||||
{
|
||||
return static_cast<f_cnt_t>( m_ticks * framesPerTick );
|
||||
}
|
||||
return 0;
|
||||
// Before, step notes used to have negative length. This
|
||||
// assert is a safeguard against negative length being
|
||||
// introduced again (now using Note Types instead #5902)
|
||||
assert(m_ticks >= 0);
|
||||
return static_cast<f_cnt_t>(m_ticks * framesPerTick);
|
||||
}
|
||||
|
||||
double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const
|
||||
@@ -186,7 +195,7 @@ tick_t TimePos::ticksPerBar( const TimeSig &sig )
|
||||
int TimePos::stepsPerBar()
|
||||
{
|
||||
int steps = ticksPerBar() / DefaultBeatsPerBar;
|
||||
return qMax( 1, steps );
|
||||
return std::max(1, steps);
|
||||
}
|
||||
|
||||
|
||||
@@ -211,3 +220,6 @@ double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute)
|
||||
// 60 * 1000 / 48 = 1250
|
||||
return ( ticks * 1250 ) / beatsPerMinute;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
83
src/core/Timeline.cpp
Normal file
83
src/core/Timeline.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Timeline.cpp
|
||||
*
|
||||
* Copyright (c) 2023 Dominic Clark
|
||||
*
|
||||
* 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 "Timeline.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
void Timeline::setLoopBegin(TimePos begin)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd});
|
||||
}
|
||||
|
||||
void Timeline::setLoopEnd(TimePos end)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end);
|
||||
}
|
||||
|
||||
void Timeline::setLoopPoints(TimePos begin, TimePos end)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end);
|
||||
}
|
||||
|
||||
void Timeline::setLoopEnabled(bool enabled)
|
||||
{
|
||||
if (enabled != m_loopEnabled) {
|
||||
m_loopEnabled = enabled;
|
||||
emit loopEnabledChanged(m_loopEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::setStopBehaviour(StopBehaviour behaviour)
|
||||
{
|
||||
if (behaviour != m_stopBehaviour) {
|
||||
m_stopBehaviour = behaviour;
|
||||
emit stopBehaviourChanged(m_stopBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::saveSettings(QDomDocument& doc, QDomElement& element)
|
||||
{
|
||||
element.setAttribute("lp0pos", static_cast<int>(loopBegin()));
|
||||
element.setAttribute("lp1pos", static_cast<int>(loopEnd()));
|
||||
element.setAttribute("lpstate", static_cast<int>(loopEnabled()));
|
||||
element.setAttribute("stopbehaviour", static_cast<int>(stopBehaviour()));
|
||||
}
|
||||
|
||||
void Timeline::loadSettings(const QDomElement& element)
|
||||
{
|
||||
setLoopPoints(
|
||||
static_cast<TimePos>(element.attribute("lp0pos").toInt()),
|
||||
static_cast<TimePos>(element.attribute("lp1pos").toInt())
|
||||
);
|
||||
setLoopEnabled(static_cast<bool>(element.attribute("lpstate").toInt()));
|
||||
setStopBehaviour(static_cast<StopBehaviour>(element.attribute("stopbehaviour", "1").toInt()));
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user