Merge branch 'master' into refac/memory

This commit is contained in:
Hyunjin Song
2024-10-03 17:05:29 +09:00
2625 changed files with 674162 additions and 293334 deletions

View File

@@ -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
View 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

Submodule src/3rdparty/hiir/hiir added at 4a9a1e67fa

View File

@@ -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()

View File

@@ -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()

View File

@@ -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
View 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)

View 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
View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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

View 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

117
src/core/AutomationNode.cpp Normal file
View 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

View File

@@ -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++;
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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 );
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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 );
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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
View 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 );
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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
View 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