Merge branch 'master' into dynamic-effect-dialog

This commit is contained in:
Hyunjin Song
2022-11-11 11:33:17 +09:00
2534 changed files with 532640 additions and 215432 deletions

View File

@@ -1,8 +1,3 @@
set(CMAKE_C_FLAGS "")
set(CMAKE_CXX_FLAGS "")
set(CMAKE_C_FLAGS_DEBUG "")
set(CMAKE_CXX_FLAGS_DEBUG "")
IF(LMMS_BUILD_LINUX AND WANT_VST)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(qt5-x11embed)
@@ -10,3 +5,22 @@ ENDIF()
ADD_SUBDIRECTORY(rpmalloc)
ADD_SUBDIRECTORY(weakjack)
# The lockless ring buffer library is compiled as part of the core
SET(RINGBUFFER_DIR "${CMAKE_SOURCE_DIR}/src/3rdparty/ringbuffer/")
SET(RINGBUFFER_DIR ${RINGBUFFER_DIR} PARENT_SCOPE)
# Create a dummy ringbuffer_export.h, since ringbuffer is not compiled as a library
FILE(WRITE ${CMAKE_BINARY_DIR}/src/ringbuffer_export.h
"#include \"${CMAKE_BINARY_DIR}/src/lmms_export.h\"\n
#define RINGBUFFER_EXPORT LMMS_EXPORT")
# Enable MLOCK support for ringbuffer if available
INCLUDE(CheckIncludeFiles)
CHECK_INCLUDE_FILES(sys/mman.h HAVE_SYS_MMAN)
IF(HAVE_SYS_MMAN AND NOT CMAKE_SYSTEM_NAME MATCHES "Haiku")
SET(USE_MLOCK ON)
ELSE()
SET(USE_MLOCK OFF)
ENDIF()
# Generate ringbuffer configuration headers
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-config.h.in ${CMAKE_BINARY_DIR}/src/ringbuffer-config.h)
CONFIGURE_FILE(${RINGBUFFER_DIR}/src/ringbuffer-version.h.in ${CMAKE_BINARY_DIR}/src/ringbuffer-version.h)

1
src/3rdparty/jack2 vendored Submodule

Submodule src/3rdparty/jack2 added at db76dd6bb8

1
src/3rdparty/ringbuffer vendored Submodule

Submodule src/3rdparty/ringbuffer added at 1c46ef34a2

View File

@@ -1,5 +1,3 @@
set(CMAKE_C_FLAGS "-std=c11")
add_library(rpmalloc STATIC
rpmalloc/rpmalloc/rpmalloc.c
rpmalloc/rpmalloc/rpmalloc.h
@@ -9,12 +7,26 @@ target_include_directories(rpmalloc PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc
)
set_property(TARGET rpmalloc PROPERTY C_STANDARD 11)
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(rpmalloc
PRIVATE -Wno-unused-variable
)
endif()
if (NOT LMMS_BUILD_WIN32)
target_compile_definitions(rpmalloc
PRIVATE -D_GNU_SOURCE
)
endif()
if(MINGW)
target_compile_definitions(rpmalloc
PRIVATE -D_WIN32_WINNT=0x600
)
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
# rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS,
# which is only available starting with GCC 5

View File

@@ -9,17 +9,20 @@ SET(LMMS_UIS "")
SET(CMAKE_AUTOMOC ON)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
# Enable C++11
SET(CMAKE_CXX_STANDARD 11)
# Enable C++17
SET(CMAKE_CXX_STANDARD 17)
IF(LMMS_BUILD_APPLE)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
ENDIF()
ADD_SUBDIRECTORY(common)
ADD_SUBDIRECTORY(core)
ADD_SUBDIRECTORY(gui)
ADD_SUBDIRECTORY(tracks)
LIST(APPEND LMMS_SRCS ${LMMS_COMMON_SRCS})
QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS})
INCLUDE_DIRECTORIES(
"${CMAKE_CURRENT_BINARY_DIR}"
@@ -27,6 +30,7 @@ INCLUDE_DIRECTORIES(
"${CMAKE_BINARY_DIR}/include"
"${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/include"
"${RINGBUFFER_DIR}/include"
)
IF(WIN32 AND MSVC)
@@ -51,7 +55,7 @@ ADD_GEN_QRC(LMMS_RCC_OUT lmms.qrc
# Paths relative to lmms executable
FILE(RELATIVE_PATH LIB_DIR_RELATIVE "/${BIN_DIR}" "/${LIB_DIR}")
FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}")
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS} ${PORTAUDIO_DEFINITIONS})
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS})
INCLUDE_DIRECTORIES(
${JACK_INCLUDE_DIRS}
${SAMPLERATE_INCLUDE_DIRS}
@@ -60,9 +64,7 @@ INCLUDE_DIRECTORIES(
${FFTW3F_INCLUDE_DIRS}
)
IF(NOT ("${SDL2_INCLUDE_DIR}" STREQUAL ""))
INCLUDE_DIRECTORIES("${SDL2_INCLUDE_DIR}")
ELSEIF(NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
IF(NOT LMMS_HAVE_SDL2 AND NOT ("${SDL_INCLUDE_DIR}" STREQUAL ""))
INCLUDE_DIRECTORIES("${SDL_INCLUDE_DIR}")
ENDIF()
@@ -73,10 +75,6 @@ IF(LMMS_HAVE_WEAKJACK)
ADD_DEFINITIONS(-DUSE_WEAK_JACK=1 -DNO_JACK_METADATA=1)
ENDIF()
IF(NOT ("${PORTAUDIO_INCLUDE_DIR}" STREQUAL ""))
INCLUDE_DIRECTORIES("${PORTAUDIO_INCLUDE_DIR}")
ENDIF()
IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL ""))
INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}")
ENDIF()
@@ -85,10 +83,19 @@ IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL ""))
INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}")
ENDIF()
IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}")
IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
ENDIF()
IF(NOT ("${LILV_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
ENDIF()
IF(NOT ("${SUIL_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
ENDIF()
LIST(APPEND LMMS_SRCS "${RINGBUFFER_DIR}/src/lib/ringbuffer.cpp")
# Use libraries in non-standard directories (e.g., another version of Qt)
IF(LMMS_BUILD_LINUX)
LINK_LIBRARIES(-Wl,--enable-new-dtags)
@@ -151,21 +158,35 @@ IF(LMMS_BUILD_HAIKU)
SET(EXTRA_LIBRARIES "-lnetwork")
ENDIF()
if(LMMS_HAVE_LIBRT)
list(APPEND EXTRA_LIBRARIES "rt")
endif()
if(LMMS_HAVE_PORTAUDIO)
list(APPEND EXTRA_LIBRARIES portaudio)
endif()
if(LMMS_HAVE_MP3LAME)
list(APPEND EXTRA_LIBRARIES mp3lame::mp3lame)
endif()
SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
${CMAKE_THREAD_LIBS_INIT}
${QT_LIBRARIES}
${ASOUND_LIBRARY}
${SDL_LIBRARY}
${SDL2_LIBRARY}
${PORTAUDIO_LIBRARIES}
${SOUNDIO_LIBRARY}
${SNDIO_LIBRARIES}
${PULSEAUDIO_LIBRARIES}
${JACK_LIBRARIES}
${OGGVORBIS_LIBRARIES}
${LAME_LIBRARIES}
${LV2_LIBRARIES}
${SUIL_LIBRARIES}
${LILV_LIBRARIES}
${SAMPLERATE_LIBRARIES}
${SNDFILE_LIBRARIES}
${FFTW3F_LIBRARIES}
${EXTRA_LIBRARIES}
rpmalloc
)
@@ -186,210 +207,35 @@ FOREACH(LIB ${LMMS_REQUIRED_LIBS})
ENDIF()
ENDFOREACH()
# Install
IF(NOT MSVC)
IF(LMMS_BUILD_WIN32)
SET_TARGET_PROPERTIES(lmms PROPERTIES
LINK_FLAGS "${LINK_FLAGS} -mwindows"
ENABLE_EXPORTS ON
IF(LMMS_BUILD_WIN32)
SET_TARGET_PROPERTIES(lmms PROPERTIES
ENABLE_EXPORTS ON
)
IF(NOT MSVC)
SET_PROPERTY(TARGET lmms
APPEND_STRING PROPERTY LINK_FLAGS " -mwindows"
)
IF(LMMS_BUILD_MSYS)
# ENABLE_EXPORTS property has no effect in some MSYS2 configurations.
# Add the linker flag manually to create liblmms.dll.a import library
SET_PROPERTY(TARGET lmms
APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a"
)
ENDIF()
IF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
ADD_CUSTOM_COMMAND(TARGET lmms POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:lmms>")
ENDIF()
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
INSTALL(FILES
"${MINGW_PREFIX}/bin/Qt5Core.dll"
"${MINGW_PREFIX}/bin/Qt5Gui.dll"
"${MINGW_PREFIX}/bin/Qt5Widgets.dll"
"${MINGW_PREFIX}/bin/Qt5Xml.dll"
DESTINATION .)
INSTALL(FILES
"${MINGW_PREFIX}/lib/qt5/plugins/platforms/qwindows.dll"
DESTINATION ./platforms)
INSTALL(FILES
"${MINGW_PREFIX}/bin/libsamplerate-0.dll"
"${MINGW_PREFIX}/bin/libsndfile-1.dll"
"${MINGW_PREFIX}/bin/libvorbis-0.dll"
"${MINGW_PREFIX}/bin/libvorbisenc-2.dll"
"${MINGW_PREFIX}/bin/libvorbisfile-3.dll"
"${MINGW_PREFIX}/bin/libjpeg-9.dll"
"${MINGW_PREFIX}/bin/libogg-0.dll"
"${MINGW_PREFIX}/bin/libmp3lame-0.dll"
"${MINGW_PREFIX}/bin/libfftw3f-3.dll"
"${MINGW_PREFIX}/bin/libFLAC-8.dll"
"${MINGW_PREFIX}/bin/libpng16-16.dll"
"${MINGW_PREFIX}/bin/SDL.dll"
"${MINGW_PREFIX}/bin/libglib-2.0-0.dll"
"${MINGW_PREFIX}/bin/libgthread-2.0-0.dll"
"${MINGW_PREFIX}/bin/zlib1.dll"
"${MINGW_PREFIX}/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32/bin/libwinpthread-1.dll"
DESTINATION .)
IF(LMMS_BUILD_MSYS)
INSTALL(FILES
"${MINGW_PREFIX}/bin/libwinpthread-1.dll"
"${MINGW_PREFIX}/bin/libgcc_s_seh-1.dll"
"${MINGW_PREFIX}/bin/libstdc++-6.dll"
DESTINATION .)
ELSE()
INSTALL(FILES
"${MINGW_PREFIX}/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32/bin/libwinpthread-1.dll"
DESTINATION .)
ENDIF()
IF(FLTK_FOUND)
INSTALL(FILES
"${MINGW_PREFIX}/bin/libfltk.dll"
DESTINATION .)
ENDIF()
IF(FLUIDSYNTH_FOUND)
INSTALL(FILES
"${MINGW_PREFIX}/bin/libfluidsynth.dll"
DESTINATION .)
ENDIF()
IF(GIG_FOUND)
# Handle libgig-*.dll
FILE(GLOB GIG_LIBRARY "${MINGW_PREFIX}/bin/libgig-*.dll")
INSTALL(FILES
${GIG_LIBRARY}
DESTINATION .)
ENDIF()
IF(PORTAUDIO_FOUND)
INSTALL(FILES
"${MINGW_PREFIX}/bin/libportaudio-2.dll"
DESTINATION .)
ENDIF()
IF(SOUNDIO_FOUND)
INSTALL(FILES
"${MINGW_PREFIX}/lib/libsoundio.dll"
DESTINATION .)
ENDIF()
ELSE(LMMS_BUILD_WIN32)
IF(NOT LMMS_BUILD_APPLE)
SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E")
ENDIF(NOT LMMS_BUILD_APPLE)
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/man/man1/" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
ENDIF(LMMS_BUILD_WIN32)
ELSE(NOT MSVC)
SET_TARGET_PROPERTIES(lmms PROPERTIES
ENABLE_EXPORTS ON
)
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
SET_TARGET_PROPERTIES(lmms PROPERTIES
LINK_FLAGS "${LINK_FLAGS} -mwindows"
ENABLE_EXPORTS ON
ENDIF()
IF(LMMS_BUILD_MSYS)
# ENABLE_EXPORTS property has no effect in some MSYS2 configurations.
# Add the linker flag manually to create liblmms.dll.a import library
SET_PROPERTY(TARGET lmms
APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a"
)
ENDIF()
ELSE()
IF(NOT LMMS_BUILD_APPLE)
SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E")
ENDIF(NOT LMMS_BUILD_APPLE)
#CMAKE_FIND_ROOT_PATH
SET(VCPKG_ROOT ${CMAKE_FIND_ROOT_PATH})
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")
INSTALL(FILES
"${VCPKG_ROOT}/bin/Qt5Core.dll"
"${VCPKG_ROOT}/bin/Qt5Gui.dll"
"${VCPKG_ROOT}/bin/Qt5Widgets.dll"
"${VCPKG_ROOT}/bin/Qt5Xml.dll"
if(CMAKE_INSTALL_MANDIR)
SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR})
ELSE(CMAKE_INSTALL_MANDIR)
SET(INSTALL_MANDIR ${CMAKE_INSTALL_PREFIX}/share/man)
ENDIF(CMAKE_INSTALL_MANDIR)
INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz"
DESTINATION "${INSTALL_MANDIR}/man1/"
PERMISSIONS OWNER_READ GROUP_READ WORLD_READ)
ENDIF()
"${VCPKG_ROOT}/bin/zlib1.dll"
"${VCPKG_ROOT}/bin/jpeg62.dll"
"${VCPKG_ROOT}/bin/libpng16.dll"
"${VCPKG_ROOT}/bin/gthread-2.dll"
"${VCPKG_ROOT}/bin/glib-2.dll"
"${VCPKG_ROOT}/bin/harfbuzz.dll"
"${VCPKG_ROOT}/bin/pcre2-16.dll"
"${VCPKG_ROOT}/bin/double-conversion.dll"
"${VCPKG_ROOT}/bin/freetype.dll"
"${VCPKG_ROOT}/bin/libbz2.dll"
"${VCPKG_ROOT}/bin/pcre.dll"
"${VCPKG_ROOT}/bin/libiconv.dll"
"${VCPKG_ROOT}/bin/libcharset.dll"
"${VCPKG_ROOT}/bin/libintl.dll"
DESTINATION .)
INSTALL(FILES
"${VCPKG_ROOT}/plugins/platforms/qwindows.dll"
DESTINATION ./platforms)
INSTALL(FILES
"${VCPKG_ROOT}/bin/libsndfile-1.dll"
"${VCPKG_ROOT}/bin/ogg.dll"
"${VCPKG_ROOT}/bin/vorbis.dll"
"${VCPKG_ROOT}/bin/vorbisenc.dll"
"${VCPKG_ROOT}/bin/FLAC.dll"
"${VCPKG_ROOT}/bin/vorbisfile.dll"
"${VCPKG_ROOT}/bin/libsamplerate-0.dll"
"${VCPKG_ROOT}/bin/SDL2.dll"
"${VCPKG_ROOT}/bin/fftw3f.dll"
DESTINATION .)
#not yet in vcpkg
#IF(LAME_FOUND)
# INSTALL(FILES
# "${VCPKG_ROOT}/bin/libmp3lame-0.dll"
# DESTINATION .)
#ENDIF(LAME_FOUND)
IF(FLTK_FOUND)
INSTALL(FILES
"${VCPKG_ROOT}/bin/libfltk_SHARED.dll"
"${VCPKG_ROOT}/bin/zlib1.dll"
"${VCPKG_ROOT}/bin/jpeg62.dll"
DESTINATION .)
ENDIF()
IF(FLUIDSYNTH_FOUND)
INSTALL(FILES
"${VCPKG_ROOT}/bin/libfluidsynth-1.dll"
"${VCPKG_ROOT}/bin/glib-2.dll"
"${VCPKG_ROOT}/bin/pcre.dll"
"${VCPKG_ROOT}/bin/libiconv.dll"
"${VCPKG_ROOT}/bin/libcharset.dll"
"${VCPKG_ROOT}/bin/libintl.dll"
DESTINATION .)
ENDIF()
#not yet included in vcpkg
#IF(GIG_FOUND)
# # Handle libgig-*.dll
# FILE(GLOB GIG_LIBRARY "${VCPKG_ROOT}/bin/libgig-*.dll")
# INSTALL(FILES
# ${GIG_LIBRARY}
# DESTINATION .)
#ENDIF()
IF(PORTAUDIO_FOUND)
IF(LMMS_BUILD_WIN64)
INSTALL(FILES
"${VCPKG_ROOT}/bin/portaudio-x64.dll"
DESTINATION .)
ELSE(LMMS_BUILD_WIN64)
INSTALL(FILES
"${VCPKG_ROOT}/bin/portaudio-x86.dll"
DESTINATION .)
ENDIF(LMMS_BUILD_WIN64)
ENDIF()
#not yet in vcpkg
#IF(SOUNDIO_FOUND)
# INSTALL(FILES
# "${VCPKG_ROOT}/bin/libsoundio.dll"
# DESTINATION .)
#ENDIF()
ENDIF(NOT MSVC)
INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}")

10
src/common/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
set(COMMON_SRCS
RemotePluginBase.cpp
SharedMemory.cpp
)
foreach(SRC ${COMMON_SRCS})
list(APPEND LMMS_COMMON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}")
endforeach()
set(LMMS_COMMON_SRCS ${LMMS_COMMON_SRCS} PARENT_SCOPE)

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

202
src/common/SharedMemory.cpp Normal file
View File

@@ -0,0 +1,202 @@
/*
* SharedMemory.cpp
*
* Copyright (c) 2022 Dominic Clark <mrdomclark/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#include "SharedMemory.h"
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_UNISTD_H
# include <unistd.h>
#endif
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
# include <system_error>
#
# include <sys/mman.h>
# include <sys/stat.h>
# include <fcntl.h>
#
# include "RaiiHelpers.h"
#else
# include <stdexcept>
#
# include <QtGlobal>
# include <QSharedMemory>
#endif
namespace lmms::detail {
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
namespace {
template<typename F>
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
{
int result;
do
{
result = function();
}
while (result == -1 && errno == EINTR);
return result;
}
void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); }
void deleteShmObject(const char* name) noexcept { shm_unlink(name); }
using FileDescriptor = UniqueNullableResource<int, -1, deleteFileDescriptor>;
using ShmObject = UniqueNullableResource<const char*, nullptr, deleteShmObject>;
} // namespace
class SharedMemoryImpl
{
public:
SharedMemoryImpl(const std::string& key, bool readOnly) :
m_key{"/" + key}
{
const auto openFlags = readOnly ? O_RDONLY : O_RDWR;
const auto fd = FileDescriptor{
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); })
};
if (!fd)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
}
auto stat = (struct stat){};
if (fstat(fd.get(), &stat) == -1)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"};
}
m_size = stat.st_size;
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
if (m_mapping == MAP_FAILED)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
}
}
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
m_key{"/" + key},
m_size{size}
{
const auto fd = FileDescriptor{
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); })
};
if (fd.get() == -1)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
}
m_object.reset(m_key.c_str());
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"};
}
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
if (m_mapping == MAP_FAILED)
{
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
}
}
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
~SharedMemoryImpl()
{
munmap(m_mapping, m_size);
}
void* get() { return m_mapping; }
private:
std::string m_key;
std::size_t m_size;
void* m_mapping;
ShmObject m_object;
};
#else
class SharedMemoryImpl
{
public:
SharedMemoryImpl(const std::string& key, bool readOnly) :
m_shm{QString::fromStdString(key)}
{
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
if (!m_shm.attach(mode))
{
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"};
}
}
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
m_shm{QString::fromStdString(key)}
{
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
if (!m_shm.create(size, mode))
{
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"};
}
}
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
void* get() { return m_shm.data(); }
private:
QSharedMemory m_shm;
};
#endif
SharedMemoryData::SharedMemoryData() noexcept = default;
SharedMemoryData::SharedMemoryData(std::string&& key, bool readOnly) :
m_key{std::move(key)},
m_impl{std::make_unique<SharedMemoryImpl>(m_key, readOnly)},
m_ptr{m_impl->get()}
{ }
SharedMemoryData::SharedMemoryData(std::string&& key, std::size_t size, bool readOnly) :
m_key{std::move(key)},
m_impl{std::make_unique<SharedMemoryImpl>(m_key, std::max(size, std::size_t{1}), readOnly)},
m_ptr{m_impl->get()}
{ }
SharedMemoryData::~SharedMemoryData() = default;
SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept :
m_key{std::move(other.m_key)},
m_impl{std::move(other.m_impl)},
m_ptr{other.m_ptr}
{ }
} // namespace lmms::detail

1292
src/core/AudioEngine.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* MixerProfiler.cpp - class for profiling performance of Mixer
* AudioEngineProfiler.cpp - class for profiling performance of AudioEngine
*
* Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
@@ -22,10 +22,12 @@
*
*/
#include "MixerProfiler.h"
#include "AudioEngineProfiler.h"
namespace lmms
{
MixerProfiler::MixerProfiler() :
AudioEngineProfiler::AudioEngineProfiler() :
m_periodTimer(),
m_cpuLoad( 0 ),
m_outputFile()
@@ -34,12 +36,7 @@ MixerProfiler::MixerProfiler() :
MixerProfiler::~MixerProfiler()
{
}
void MixerProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
{
int periodElapsed = m_periodTimer.elapsed();
@@ -54,10 +51,11 @@ void MixerProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPerio
void MixerProfiler::setOutputFile( const QString& outputFile )
void AudioEngineProfiler::setOutputFile( const QString& outputFile )
{
m_outputFile.close();
m_outputFile.setFileName( outputFile );
m_outputFile.open( QFile::WriteOnly | QFile::Truncate );
}
} // namespace lmms

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,30 @@
*
*/
#include "MixerWorkerThread.h"
#include "AudioEngineWorkerThread.h"
#include <QDebug>
#include <QMutex>
#include <QWaitCondition>
#include "denormals.h"
#include "AudioEngine.h"
#include "MemoryManager.h"
#include "ThreadableJob.h"
#include "Mixer.h"
#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64)
#if __SSE__
#include <xmmintrin.h>
#endif
MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue;
QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL;
QList<MixerWorkerThread *> MixerWorkerThread::workerThreads;
namespace lmms
{
AudioEngineWorkerThread::JobQueue AudioEngineWorkerThread::globalJobQueue;
QWaitCondition * AudioEngineWorkerThread::queueReadyWaitCond = nullptr;
QList<AudioEngineWorkerThread *> AudioEngineWorkerThread::workerThreads;
// implementation of internal JobQueue
void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
void AudioEngineWorkerThread::JobQueue::reset( OperationMode _opMode )
{
m_writeIndex = 0;
m_itemsDone = 0;
@@ -51,7 +55,7 @@ void MixerWorkerThread::JobQueue::reset( OperationMode _opMode )
void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
void AudioEngineWorkerThread::JobQueue::addJob( ThreadableJob * _job )
{
if( _job->requiresProcessing() )
{
@@ -70,7 +74,7 @@ void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job )
void MixerWorkerThread::JobQueue::run()
void AudioEngineWorkerThread::JobQueue::run()
{
bool processedJob = true;
while (processedJob && m_itemsDone < m_writeIndex)
@@ -94,11 +98,11 @@ void MixerWorkerThread::JobQueue::run()
void MixerWorkerThread::JobQueue::wait()
void AudioEngineWorkerThread::JobQueue::wait()
{
while (m_itemsDone < m_writeIndex)
{
#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64)
#ifdef __SSE__
_mm_pause();
#endif
}
@@ -110,19 +114,19 @@ void MixerWorkerThread::JobQueue::wait()
// implementation of worker threads
MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
QThread( mixer ),
AudioEngineWorkerThread::AudioEngineWorkerThread( AudioEngine* audioEngine ) :
QThread( audioEngine ),
m_quit( false )
{
// initialize global static data
if( queueReadyWaitCond == NULL )
if( queueReadyWaitCond == nullptr )
{
queueReadyWaitCond = new QWaitCondition;
}
// keep track of all instantiated worker threads - this is used for
// processing the last worker thread "inline", see comments in
// MixerWorkerThread::startAndWaitForJobs() for details
// AudioEngineWorkerThread::startAndWaitForJobs() for details
workerThreads << this;
resetJobQueue();
@@ -131,7 +135,7 @@ MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) :
MixerWorkerThread::~MixerWorkerThread()
AudioEngineWorkerThread::~AudioEngineWorkerThread()
{
workerThreads.removeAll( this );
}
@@ -139,7 +143,7 @@ MixerWorkerThread::~MixerWorkerThread()
void MixerWorkerThread::quit()
void AudioEngineWorkerThread::quit()
{
m_quit = true;
resetJobQueue();
@@ -148,11 +152,11 @@ void MixerWorkerThread::quit()
void MixerWorkerThread::startAndWaitForJobs()
void AudioEngineWorkerThread::startAndWaitForJobs()
{
queueReadyWaitCond->wakeAll();
// The last worker-thread is never started. Instead it's processed "inline"
// i.e. within the global Mixer thread. This way we can reduce latencies
// i.e. within the global AudioEngine thread. This way we can reduce latencies
// that otherwise would be caused by synchronizing with another thread.
globalJobQueue.run();
globalJobQueue.wait();
@@ -161,7 +165,7 @@ void MixerWorkerThread::startAndWaitForJobs()
void MixerWorkerThread::run()
void AudioEngineWorkerThread::run()
{
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
disable_denormals();
@@ -176,4 +180,4 @@ void MixerWorkerThread::run()
}
}
} // namespace lmms

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;
@@ -50,10 +53,11 @@ AutomatableModel::AutomatableModel(
m_valueChanged( false ),
m_setValueDepth( 0 ),
m_hasStrictStepSize( false ),
m_controllerConnection( NULL ),
m_valueBuffer( static_cast<int>( Engine::mixer()->framesPerPeriod() ) ),
m_controllerConnection( nullptr ),
m_valueBuffer( static_cast<int>( Engine::audioEngine()->framesPerPeriod() ) ),
m_lastUpdatedPeriod( -1 ),
m_hasSampleExactData( false )
m_hasSampleExactData(false),
m_useControllerValue(true)
{
m_value = fittedValue( val );
@@ -86,7 +90,7 @@ AutomatableModel::~AutomatableModel()
bool AutomatableModel::isAutomated() const
{
return AutomationPattern::isAutomated( this );
return AutomationClip::isAutomated( this );
}
@@ -175,13 +179,13 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
void AutomatableModel::loadSettings( const QDomElement& element, const QString& name )
{
// compat code
QDomNode node = element.namedItem( AutomationPattern::classNodeName() );
QDomNode node = element.namedItem( AutomationClip::classNodeName() );
if( node.isElement() )
{
node = node.namedItem( name );
if( node.isElement() )
{
AutomationPattern * p = AutomationPattern::globalAutomationPattern( this );
AutomationClip * p = AutomationClip::globalAutomationClip( this );
p->loadSettings( node.toElement() );
setValue( p->valueAt( 0 ) );
// in older projects we sometimes have odd automations
@@ -214,7 +218,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
}
if( thisConnection.isElement() )
{
setControllerConnection( new ControllerConnection( (Controller*)NULL ) );
setControllerConnection(new ControllerConnection(nullptr));
m_controllerConnection->loadSettings( thisConnection.toElement() );
//m_controllerConnection->setTargetName( displayName() );
}
@@ -301,13 +305,13 @@ void AutomatableModel::setValue( const float value )
addJournalCheckPoint();
// notify linked models
for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it )
for (const auto& linkedModel : m_linkedModels)
{
if( (*it)->m_setValueDepth < 1 && (*it)->fittedValue( value ) != (*it)->m_value )
if (linkedModel->m_setValueDepth < 1 && linkedModel->fittedValue(value) != linkedModel->m_value)
{
bool journalling = (*it)->testAndSetJournalling( isJournalling() );
(*it)->setValue( value );
(*it)->setJournalling( journalling );
bool journalling = linkedModel->testAndSetJournalling(isJournalling());
linkedModel->setValue(value);
linkedModel->setJournalling(journalling);
}
}
m_valueChanged = true;
@@ -325,7 +329,7 @@ void AutomatableModel::setValue( const float value )
template<class T> T AutomatableModel::logToLinearScale( T value ) const
{
return castValue<T>( ::logToLinearScale( minValue<float>(), maxValue<float>(), static_cast<float>( value ) ) );
return castValue<T>( lmms::logToLinearScale( minValue<float>(), maxValue<float>(), static_cast<float>( value ) ) );
}
@@ -341,7 +345,7 @@ float AutomatableModel::inverseScaledValue( float value ) const
{
return m_scaleType == Linear
? value
: ::linearToLogScale( minValue<float>(), maxValue<float>(), value );
: lmms::linearToLogScale( minValue<float>(), maxValue<float>(), value );
}
@@ -363,7 +367,7 @@ void roundAt( T& value, const T& where, const T& step_size )
template<class T>
void AutomatableModel::roundAt( T& value, const T& where ) const
{
::roundAt(value, where, m_step);
lmms::roundAt(value, where, m_step);
}
@@ -371,6 +375,8 @@ void AutomatableModel::roundAt( T& value, const T& where ) const
void AutomatableModel::setAutomatedValue( const float value )
{
setUseControllerValue(false);
m_oldValue = m_value;
++m_setValueDepth;
const float oldValue = m_value;
@@ -382,13 +388,12 @@ void AutomatableModel::setAutomatedValue( const float value )
if( oldValue != m_value )
{
// notify linked models
for( AutoModelVector::Iterator it = m_linkedModels.begin();
it != m_linkedModels.end(); ++it )
for (const auto& linkedModel : m_linkedModels)
{
if( (*it)->m_setValueDepth < 1 &&
(*it)->fittedValue( m_value ) != (*it)->m_value )
if (!(linkedModel->controllerConnection()) && linkedModel->m_setValueDepth < 1 &&
linkedModel->fittedValue(m_value) != linkedModel->m_value)
{
(*it)->setAutomatedValue( value );
linkedModel->setAutomatedValue(value);
}
}
m_valueChanged = true;
@@ -474,8 +479,8 @@ void AutomatableModel::linkModel( AutomatableModel* model )
if( !model->hasLinkedModels() )
{
QObject::connect( this, SIGNAL( dataChanged() ),
model, SIGNAL( dataChanged() ), Qt::DirectConnection );
QObject::connect( this, SIGNAL(dataChanged()),
model, SIGNAL(dataChanged()), Qt::DirectConnection );
}
}
}
@@ -499,8 +504,23 @@ void AutomatableModel::unlinkModel( AutomatableModel* model )
void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 )
{
if (!model1->m_linkedModels.contains( model2 ) && model1 != model2)
{
// copy data
model1->m_value = model2->m_value;
if (model1->valueBuffer() && model2->valueBuffer())
{
std::copy_n(model2->valueBuffer()->data(),
model1->valueBuffer()->length(),
model1->valueBuffer()->data());
}
// send dataChanged() before linking (because linking will
// connect the two dataChanged() signals)
emit model1->dataChanged();
// finally: link the models
model1->linkModel( model2 );
model2->linkModel( model1 );
}
}
@@ -531,9 +551,9 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c )
m_controllerConnection = c;
if( c )
{
QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ),
this, SIGNAL( dataChanged() ), Qt::DirectConnection );
QObject::connect( m_controllerConnection, SIGNAL( destroyed() ), this, SLOT( unlinkControllerConnection() ) );
QObject::connect( m_controllerConnection, SIGNAL(valueChanged()),
this, SIGNAL(dataChanged()), Qt::DirectConnection );
QObject::connect( m_controllerConnection, SIGNAL(destroyed()), this, SLOT(unlinkControllerConnection()));
m_valueChanged = true;
emit dataChanged();
}
@@ -569,7 +589,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const
}
AutomatableModel* lm = m_linkedModels.first();
if( lm->controllerConnection() )
if (lm->controllerConnection() && lm->useControllerValue())
{
return fittedValue( lm->controllerValue( frameOffset ) );
}
@@ -586,13 +606,13 @@ ValueBuffer * AutomatableModel::valueBuffer()
{
return m_hasSampleExactData
? &m_valueBuffer
: NULL;
: nullptr;
}
float val = m_value; // make sure our m_value doesn't change midway
ValueBuffer * vb;
if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() )
if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact())
{
vb = m_controllerConnection->valueBuffer();
if( vb )
@@ -623,23 +643,28 @@ ValueBuffer * AutomatableModel::valueBuffer()
return &m_valueBuffer;
}
}
AutomatableModel* lm = NULL;
if( hasLinkedModels() )
if (!m_controllerConnection)
{
lm = m_linkedModels.first();
}
if( lm && lm->controllerConnection() && lm->controllerConnection()->getController()->isSampleExact() )
{
vb = lm->valueBuffer();
float * values = vb->values();
float * nvalues = m_valueBuffer.values();
for( int i = 0; i < vb->length(); i++ )
AutomatableModel* lm = nullptr;
if (hasLinkedModels())
{
nvalues[i] = fittedValue( values[i] );
lm = m_linkedModels.first();
}
if (lm && lm->controllerConnection() && lm->useControllerValue() &&
lm->controllerConnection()->getController()->isSampleExact())
{
vb = lm->valueBuffer();
float * values = vb->values();
float * nvalues = m_valueBuffer.values();
for (int i = 0; i < vb->length(); i++)
{
nvalues[i] = fittedValue(values[i]);
}
m_lastUpdatedPeriod = s_periodCounter;
m_hasSampleExactData = true;
return &m_valueBuffer;
}
m_lastUpdatedPeriod = s_periodCounter;
m_hasSampleExactData = true;
return &m_valueBuffer;
}
if( m_oldValue != val )
@@ -655,7 +680,7 @@ ValueBuffer * AutomatableModel::valueBuffer()
// in which case the recipient knows to use the static value() instead
m_lastUpdatedPeriod = s_periodCounter;
m_hasSampleExactData = false;
return NULL;
return nullptr;
}
@@ -666,7 +691,7 @@ void AutomatableModel::unlinkControllerConnection()
m_controllerConnection->disconnect( this );
}
m_controllerConnection = NULL;
m_controllerConnection = nullptr;
}
@@ -693,64 +718,78 @@ void AutomatableModel::reset()
float AutomatableModel::globalAutomationValueAt( const MidiTime& time )
float AutomatableModel::globalAutomationValueAt( const TimePos& time )
{
// get patterns that connect to this model
QVector<AutomationPattern *> patterns = AutomationPattern::patternsForModel( this );
if( patterns.isEmpty() )
// get clips that connect to this model
QVector<AutomationClip *> clips = AutomationClip::clipsForModel( this );
if( clips.isEmpty() )
{
// if no such patterns exist, return current value
// if no such clips exist, return current value
return m_value;
}
else
{
// of those patterns:
// find the patterns which overlap with the miditime position
QVector<AutomationPattern *> patternsInRange;
for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ )
// of those clips:
// find the clips which overlap with the time position
QVector<AutomationClip *> clipsInRange;
for (const auto& clip : clips)
{
int s = ( *it )->startPosition();
int e = ( *it )->endPosition();
if( s <= time && e >= time ) { patternsInRange += ( *it ); }
int s = clip->startPosition();
int e = clip->endPosition();
if (s <= time && e >= time) { clipsInRange += clip; }
}
AutomationPattern * latestPattern = NULL;
AutomationClip * latestClip = nullptr;
if( ! patternsInRange.isEmpty() )
if( ! clipsInRange.isEmpty() )
{
// if there are more than one overlapping patterns, just use the first one because
// multiple pattern behaviour is undefined anyway
latestPattern = patternsInRange[0];
// if there are more than one overlapping clips, just use the first one because
// multiple clip behaviour is undefined anyway
latestClip = clipsInRange[0];
}
else
// if we find no patterns at the exact miditime, we need to search for the last pattern before time and use that
// if we find no clips at the exact time, we need to search for the last clip before time and use that
{
int latestPosition = 0;
for( QVector<AutomationPattern *>::ConstIterator it = patterns.begin(); it != patterns.end(); it++ )
for (const auto& clip : clips)
{
int e = ( *it )->endPosition();
if( e <= time && e > latestPosition )
int e = clip->endPosition();
if (e <= time && e > latestPosition)
{
latestPosition = e;
latestPattern = ( *it );
latestClip = clip;
}
}
}
if( latestPattern )
if( latestClip )
{
// scale/fit the value appropriately and return it
const float value = latestPattern->valueAt( time - latestPattern->startPosition() );
const float value = latestClip->valueAt( time - latestClip->startPosition() );
const float scaled_value = scaledValue( value );
return fittedValue( scaled_value );
}
// if we still find no pattern, the value at that time is undefined so
// if we still find no clip, the value at that time is undefined so
// just return current value as the best we can do
else return m_value;
}
}
void AutomatableModel::setUseControllerValue(bool b)
{
if (b)
{
m_useControllerValue = true;
emit dataChanged();
}
else if (m_controllerConnection && m_useControllerValue)
{
m_useControllerValue = false;
emit dataChanged();
}
}
float FloatModel::getRoundedValue() const
{
return qRound( value() / step<float>() ) * step<float>();
@@ -761,7 +800,7 @@ float FloatModel::getRoundedValue() const
int FloatModel::getDigitCount() const
{
float steptemp = step<float>();
auto steptemp = step<float>();
int digits = 0;
while ( steptemp < 1 )
{
@@ -787,3 +826,6 @@ QString BoolModel::displayValue( const float val ) const
{
return QString::number( castValue<bool>( scaledValue( val ) ) );
}
} // namespace lmms

1176
src/core/AutomationClip.cpp Normal file

File diff suppressed because it is too large Load Diff

114
src/core/AutomationNode.cpp Normal file
View File

@@ -0,0 +1,114 @@
/*
* AutomationClip.cpp - Implementation of class AutomationNode which
* holds information on a single automation clip node
*
* Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "AutomationNode.h"
#include "AutomationClip.h"
namespace lmms
{
// Dummy constructor for the QMap
AutomationNode::AutomationNode() :
m_clip(nullptr),
m_pos(0),
m_inValue(0),
m_outValue(0),
m_inTangent(0),
m_outTangent(0)
{
}
AutomationNode::AutomationNode(AutomationClip* clip, float value, int pos) :
m_clip(clip),
m_pos(pos),
m_inValue(value),
m_outValue(value),
m_inTangent(0),
m_outTangent(0)
{
}
AutomationNode::AutomationNode(AutomationClip* clip, float inValue, float outValue, int pos) :
m_clip(clip),
m_pos(pos),
m_inValue(inValue),
m_outValue(outValue),
m_inTangent(0),
m_outTangent(0)
{
}
/**
* @brief Sets the inValue of an automation node
* @param Float value to be assigned
*/
void AutomationNode::setInValue(float value)
{
m_inValue = value;
// Recalculate the tangents from neighbor nodes
AutomationClip::timeMap & tm = m_clip->getTimeMap();
// Get an iterator pointing to this node
AutomationClip::timeMap::iterator it = tm.lowerBound(m_pos);
// If it's not the first node, get the one immediately behind it
if (it != tm.begin()) { --it; }
// Generate tangents from the previously, current and next nodes
m_clip->generateTangents(it, 3);
}
/**
* @brief Sets the outValue of an automation node
* @param Float value to be assigned
*/
void AutomationNode::setOutValue(float value)
{
m_outValue = value;
// Recalculate the tangents from neighbor nodes
AutomationClip::timeMap & tm = m_clip->getTimeMap();
// Get an iterator pointing to this node
AutomationClip::timeMap::iterator it = tm.lowerBound(m_pos);
// If it's not the first node, get the one immediately behind it
if (it != tm.begin()) { --it; }
// Generate tangents from the previously, current and next nodes
m_clip->generateTangents(it, 3);
}
/**
* @brief Resets the outValue so it matches inValue
*/
void AutomationNode::resetOutValue()
{
// Calls setOutValue so it also takes care of generating
// the tangents
setOutValue(m_inValue);
}
} // namespace lmms

View File

@@ -1,905 +0,0 @@
/*
* AutomationPattern.cpp - implementation of class AutomationPattern which
* holds dynamic values
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "AutomationPattern.h"
#include "AutomationPatternView.h"
#include "AutomationTrack.h"
#include "LocaleHelper.h"
#include "Note.h"
#include "ProjectJournal.h"
#include "BBTrackContainer.h"
#include "Song.h"
#include <cmath>
int AutomationPattern::s_quantization = 1;
const float AutomationPattern::DEFAULT_MIN_VALUE = 0;
const float AutomationPattern::DEFAULT_MAX_VALUE = 1;
AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
TrackContentObject( _auto_track ),
m_autoTrack( _auto_track ),
m_objects(),
m_tension( 1.0 ),
m_progressionType( DiscreteProgression ),
m_dragging( false ),
m_isRecording( false ),
m_lastRecordedValue( 0 )
{
changeLength( MidiTime( 1, 0 ) );
if( getTrack() )
{
switch( getTrack()->trackContainer()->type() )
{
case TrackContainer::BBContainer:
setAutoResize( true );
break;
case TrackContainer::SongContainer:
// move down
default:
setAutoResize( false );
break;
}
}
}
AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
TrackContentObject( _pat_to_copy.m_autoTrack ),
m_autoTrack( _pat_to_copy.m_autoTrack ),
m_objects( _pat_to_copy.m_objects ),
m_tension( _pat_to_copy.m_tension ),
m_progressionType( _pat_to_copy.m_progressionType )
{
for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
it != _pat_to_copy.m_timeMap.end(); ++it )
{
m_timeMap[it.key()] = it.value();
m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
}
switch( getTrack()->trackContainer()->type() )
{
case TrackContainer::BBContainer:
setAutoResize( true );
break;
case TrackContainer::SongContainer:
// move down
default:
setAutoResize( false );
break;
}
}
bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
{
if( _search_dup && m_objects.contains(_obj) )
{
return false;
}
// the automation track is unconnected and there is nothing in the track
if( m_objects.isEmpty() && hasAutomation() == false )
{
// then initialize first value
putValue( MidiTime(0), _obj->inverseScaledValue( _obj->value<float>() ), false );
}
m_objects += _obj;
connect( _obj, SIGNAL( destroyed( jo_id_t ) ),
this, SLOT( objectDestroyed( jo_id_t ) ),
Qt::DirectConnection );
emit dataChanged();
return true;
}
void AutomationPattern::setProgressionType(
ProgressionTypes _new_progression_type )
{
if ( _new_progression_type == DiscreteProgression ||
_new_progression_type == LinearProgression ||
_new_progression_type == CubicHermiteProgression )
{
m_progressionType = _new_progression_type;
emit dataChanged();
}
}
void AutomationPattern::setTension( QString _new_tension )
{
bool ok;
float nt = LocaleHelper::toFloat(_new_tension, & ok);
if( ok && nt > -0.01 && nt < 1.01 )
{
m_tension = nt;
}
}
const AutomatableModel * AutomationPattern::firstObject() const
{
AutomatableModel * m;
if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL )
{
return m;
}
static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 );
return &_fm;
}
const AutomationPattern::objectVector& AutomationPattern::objects() const
{
return m_objects;
}
MidiTime AutomationPattern::timeMapLength() const
{
if( m_timeMap.isEmpty() ) return 0;
timeMap::const_iterator it = m_timeMap.end();
return MidiTime( MidiTime( (it-1).key() ).nextFullTact(), 0 );
}
void AutomationPattern::updateLength()
{
changeLength( timeMapLength() );
}
MidiTime AutomationPattern::putValue( const MidiTime & time,
const float value,
const bool quantPos,
const bool ignoreSurroundingPoints )
{
cleanObjects();
MidiTime newTime = quantPos ?
Note::quantized( time, quantization() ) :
time;
m_timeMap[ newTime ] = value;
timeMap::const_iterator it = m_timeMap.find( newTime );
// Remove control points that are covered by the new points
// quantization value. Control Key to override
if( ! ignoreSurroundingPoints )
{
for( int i = newTime + 1; i < newTime + quantization(); ++i )
{
AutomationPattern::removeValue( i );
}
}
if( it != m_timeMap.begin() )
{
--it;
}
generateTangents( it, 3 );
// we need to maximize our length in case we're part of a hidden
// automation track as the user can't resize this pattern
if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
{
updateLength();
}
emit dataChanged();
return newTime;
}
void AutomationPattern::removeValue( const MidiTime & time )
{
cleanObjects();
m_timeMap.remove( time );
m_tangents.remove( time );
timeMap::const_iterator it = m_timeMap.lowerBound( time );
if( it != m_timeMap.begin() )
{
--it;
}
generateTangents(it, 3);
if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
{
updateLength();
}
emit dataChanged();
}
void AutomationPattern::recordValue(MidiTime time, float value)
{
if( value != m_lastRecordedValue )
{
putValue( time, value, true );
m_lastRecordedValue = value;
}
else if( valueAt( time ) != value )
{
removeValue( time );
}
}
/**
* @brief Set the position of the point that is being dragged.
* Calling this function will also automatically set m_dragging to true,
* which applyDragValue() have to be called to m_dragging.
* @param the time(x position) of the point being dragged
* @param the value(y position) of the point being dragged
* @param true to snip x position
* @return
*/
MidiTime AutomationPattern::setDragValue( const MidiTime & time,
const float value,
const bool quantPos,
const bool controlKey )
{
if( m_dragging == false )
{
MidiTime newTime = quantPos ?
Note::quantized( time, quantization() ) :
time;
this->removeValue( newTime );
m_oldTimeMap = m_timeMap;
m_dragging = true;
}
//Restore to the state before it the point were being dragged
m_timeMap = m_oldTimeMap;
for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
{
generateTangents( it, 3 );
}
return this->putValue( time, value, quantPos, controlKey );
}
/**
* @brief After the point is dragged, this function is called to apply the change.
*/
void AutomationPattern::applyDragValue()
{
m_dragging = false;
}
float AutomationPattern::valueAt( const MidiTime & _time ) const
{
if( m_timeMap.isEmpty() )
{
return 0;
}
if( m_timeMap.contains( _time ) )
{
return m_timeMap[_time];
}
// lowerBound returns next value with greater key, therefore we take
// the previous element to get the current value
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
if( v == m_timeMap.begin() )
{
return 0;
}
if( v == m_timeMap.end() )
{
return (v-1).value();
}
return valueAt( v-1, _time - (v-1).key() );
}
float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
{
if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
{
return v.value();
}
else if( m_progressionType == LinearProgression )
{
float slope = ((v+1).value() - v.value()) /
((v+1).key() - v.key());
return v.value() + offset * slope;
}
else /* CubicHermiteProgression */
{
// Implements a Cubic Hermite spline as explained at:
// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
//
// Note that we are not interpolating a 2 dimensional point over
// time as the article describes. We are interpolating a single
// value: y. To make this work we map the values of x that this
// segment spans to values of t for t = 0.0 -> 1.0 and scale the
// tangents _m1 and _m2
int numValues = ((v+1).key() - v.key());
float t = (float) offset / (float) numValues;
float m1 = (m_tangents[v.key()]) * numValues * m_tension;
float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension;
return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
+ ( pow(t,3) - 2*pow(t,2) + t) * m1
+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
+ ( pow(t,3) - pow(t,2) ) * m2;
}
}
float *AutomationPattern::valuesAfter( const MidiTime & _time ) const
{
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
{
return NULL;
}
int numValues = (v+1).key() - v.key();
float *ret = new float[numValues];
for( int i = 0; i < numValues; i++ )
{
ret[i] = valueAt( v, i );
}
return ret;
}
void AutomationPattern::flipY( int min, int max )
{
timeMap tempMap = m_timeMap;
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
float tempValue = 0;
int numPoints = 0;
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
{
numPoints++;
}
for( int i = 0; i <= numPoints; i++ )
{
if ( min < 0 )
{
tempValue = valueAt( ( iterate + i ).key() ) * -1;
putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
}
else
{
tempValue = max - valueAt( ( iterate + i ).key() );
putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
}
}
generateTangents();
emit dataChanged();
}
void AutomationPattern::flipY()
{
flipY(getMin(), getMax());
}
void AutomationPattern::flipX( int length )
{
timeMap tempMap;
timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
float tempValue = 0;
int numPoints = 0;
for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
{
numPoints++;
}
float realLength = ( iterate + numPoints ).key();
if ( length != -1 && length != realLength)
{
if ( realLength < length )
{
tempValue = valueAt( ( iterate + numPoints ).key() );
putValue( MidiTime( length ) , tempValue, false);
numPoints++;
for( int i = 0; i <= numPoints; i++ )
{
tempValue = valueAt( ( iterate + i ).key() );
MidiTime newTime = MidiTime( length - ( iterate + i ).key() );
tempMap[newTime] = tempValue;
}
}
else
{
for( int i = 0; i <= numPoints; i++ )
{
tempValue = valueAt( ( iterate + i ).key() );
MidiTime newTime;
if ( ( iterate + i ).key() <= length )
{
newTime = MidiTime( length - ( iterate + i ).key() );
}
else
{
newTime = MidiTime( ( iterate + i ).key() );
}
tempMap[newTime] = tempValue;
}
}
}
else
{
for( int i = 0; i <= numPoints; i++ )
{
tempValue = valueAt( ( iterate + i ).key() );
cleanObjects();
MidiTime newTime = MidiTime( realLength - ( iterate + i ).key() );
tempMap[newTime] = tempValue;
}
}
m_timeMap.clear();
m_timeMap = tempMap;
generateTangents();
emit dataChanged();
}
void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
_this.setAttribute( "pos", startPosition() );
_this.setAttribute( "len", length() );
_this.setAttribute( "name", name() );
_this.setAttribute( "prog", QString::number( progressionType() ) );
_this.setAttribute( "tens", QString::number( getTension() ) );
_this.setAttribute( "mute", QString::number( isMuted() ) );
for( timeMap::const_iterator it = m_timeMap.begin();
it != m_timeMap.end(); ++it )
{
QDomElement element = _doc.createElement( "time" );
element.setAttribute( "pos", it.key() );
element.setAttribute( "value", it.value() );
_this.appendChild( element );
}
for( objectVector::const_iterator it = m_objects.begin();
it != m_objects.end(); ++it )
{
if( *it )
{
QDomElement element = _doc.createElement( "object" );
element.setAttribute( "id",
ProjectJournal::idToSave( ( *it )->id() ) );
_this.appendChild( element );
}
}
}
void AutomationPattern::loadSettings( const QDomElement & _this )
{
clear();
movePosition( _this.attribute( "pos" ).toInt() );
setName( _this.attribute( "name" ) );
setProgressionType( static_cast<ProgressionTypes>( _this.attribute(
"prog" ).toInt() ) );
setTension( _this.attribute( "tens" ) );
setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() );
for( QDomNode node = _this.firstChild(); !node.isNull();
node = node.nextSibling() )
{
QDomElement element = node.toElement();
if( element.isNull() )
{
continue;
}
if( element.tagName() == "time" )
{
m_timeMap[element.attribute( "pos" ).toInt()]
= LocaleHelper::toFloat(element.attribute("value"));
}
else if( element.tagName() == "object" )
{
m_idsToResolve << element.attribute( "id" ).toInt();
}
}
int len = _this.attribute( "len" ).toInt();
if( len <= 0 )
{
// TODO: Handle with an upgrade method
updateLength();
}
else
{
changeLength( len );
}
generateTangents();
}
const QString AutomationPattern::name() const
{
if( !TrackContentObject::name().isEmpty() )
{
return TrackContentObject::name();
}
if( !m_objects.isEmpty() && m_objects.first() != NULL )
{
return m_objects.first()->fullDisplayName();
}
return tr( "Drag a control while pressing <%1>" ).arg(UI_CTRL_KEY);
}
TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
{
return new AutomationPatternView( this, _tv );
}
bool AutomationPattern::isAutomated( const AutomatableModel * _m )
{
TrackContainer::TrackList l;
l += Engine::getSong()->tracks();
l += Engine::getBBTrackContainer()->tracks();
l += Engine::getSong()->globalAutomationTrack();
for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
{
if( ( *it )->type() == Track::AutomationTrack ||
( *it )->type() == Track::HiddenAutomationTrack )
{
const Track::tcoVector & v = ( *it )->getTCOs();
for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
{
const AutomationPattern * a = dynamic_cast<const AutomationPattern *>( *j );
if( a && a->hasAutomation() )
{
for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
{
if( *k == _m )
{
return true;
}
}
}
}
}
}
return false;
}
/*! \brief returns a list of all the automation patterns everywhere that are connected to a specific model
* \param _m the model we want to look for
*/
QVector<AutomationPattern *> AutomationPattern::patternsForModel( const AutomatableModel * _m )
{
QVector<AutomationPattern *> patterns;
TrackContainer::TrackList l;
l += Engine::getSong()->tracks();
l += Engine::getBBTrackContainer()->tracks();
l += Engine::getSong()->globalAutomationTrack();
// go through all tracks...
for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
{
// we want only automation tracks...
if( ( *it )->type() == Track::AutomationTrack ||
( *it )->type() == Track::HiddenAutomationTrack )
{
// get patterns in those tracks....
const Track::tcoVector & v = ( *it )->getTCOs();
// go through all the patterns...
for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
{
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
// check that the pattern has automation
if( a && a->hasAutomation() )
{
// now check is the pattern is connected to the model we want by going through all the connections
// of the pattern
bool has_object = false;
for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
{
if( *k == _m )
{
has_object = true;
}
}
// if the patterns is connected to the model, add it to the list
if( has_object ) { patterns += a; }
}
}
}
}
return patterns;
}
AutomationPattern * AutomationPattern::globalAutomationPattern(
AutomatableModel * _m )
{
AutomationTrack * t = Engine::getSong()->globalAutomationTrack();
Track::tcoVector v = t->getTCOs();
for( Track::tcoVector::const_iterator j = v.begin(); j != v.end(); ++j )
{
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
if( a )
{
for( objectVector::const_iterator k = a->m_objects.begin();
k != a->m_objects.end(); ++k )
{
if( *k == _m )
{
return a;
}
}
}
}
AutomationPattern * a = new AutomationPattern( t );
a->addObject( _m, false );
return a;
}
void AutomationPattern::resolveAllIDs()
{
TrackContainer::TrackList l = Engine::getSong()->tracks() +
Engine::getBBTrackContainer()->tracks();
l += Engine::getSong()->globalAutomationTrack();
for( TrackContainer::TrackList::iterator it = l.begin();
it != l.end(); ++it )
{
if( ( *it )->type() == Track::AutomationTrack ||
( *it )->type() == Track::HiddenAutomationTrack )
{
Track::tcoVector v = ( *it )->getTCOs();
for( Track::tcoVector::iterator j = v.begin();
j != v.end(); ++j )
{
AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
if( a )
{
for( QVector<jo_id_t>::Iterator k = a->m_idsToResolve.begin();
k != a->m_idsToResolve.end(); ++k )
{
JournallingObject * o = Engine::projectJournal()->
journallingObject( *k );
if( o && dynamic_cast<AutomatableModel *>( o ) )
{
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
}
else
{
// FIXME: Remove this block once the automation system gets fixed
// This is a temporary fix for https://github.com/LMMS/lmms/issues/3781
o = Engine::projectJournal()->journallingObject(ProjectJournal::idFromSave(*k));
if( o && dynamic_cast<AutomatableModel *>( o ) )
{
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
}
else
{
// FIXME: Remove this block once the automation system gets fixed
// This is a temporary fix for https://github.com/LMMS/lmms/issues/4781
o = Engine::projectJournal()->journallingObject(ProjectJournal::idToSave(*k));
if( o && dynamic_cast<AutomatableModel *>( o ) )
{
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
}
}
}
}
a->m_idsToResolve.clear();
a->dataChanged();
}
}
}
}
}
void AutomationPattern::clear()
{
m_timeMap.clear();
m_tangents.clear();
emit dataChanged();
}
void AutomationPattern::objectDestroyed( jo_id_t _id )
{
// TODO: distict between temporary removal (e.g. LADSPA controls
// when switching samplerate) and real deletions because in the latter
// case we had to remove ourselves if we're the global automation
// pattern of the destroyed object
m_idsToResolve += _id;
for( objectVector::Iterator objIt = m_objects.begin();
objIt != m_objects.end(); objIt++ )
{
Q_ASSERT( !(*objIt).isNull() );
if( (*objIt)->id() == _id )
{
//Assign to objIt so that this loop work even break; is removed.
objIt = m_objects.erase( objIt );
break;
}
}
emit dataChanged();
}
void AutomationPattern::cleanObjects()
{
for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
{
if( *it )
{
++it;
}
else
{
it = m_objects.erase( it );
}
}
}
void AutomationPattern::generateTangents()
{
generateTangents(m_timeMap.begin(), m_timeMap.size());
}
void AutomationPattern::generateTangents( timeMap::const_iterator it,
int numToGenerate )
{
if( m_timeMap.size() < 2 && numToGenerate > 0 )
{
m_tangents[it.key()] = 0;
return;
}
for( int i = 0; i < numToGenerate; i++ )
{
if( it == m_timeMap.begin() )
{
m_tangents[it.key()] =
( (it+1).value() - (it).value() ) /
( (it+1).key() - (it).key() );
}
else if( it+1 == m_timeMap.end() )
{
m_tangents[it.key()] = 0;
return;
}
else
{
m_tangents[it.key()] =
( (it+1).value() - (it-1).value() ) /
( (it+1).key() - (it-1).key() );
}
it++;
}
}

View File

@@ -1,262 +0,0 @@
/*
* BBTrackContainer.cpp - model-component of BB-Editor
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "BBTrackContainer.h"
#include "BBTrack.h"
#include "Engine.h"
#include "Song.h"
BBTrackContainer::BBTrackContainer() :
TrackContainer(),
m_bbComboBoxModel( this )
{
connect( &m_bbComboBoxModel, SIGNAL( dataChanged() ),
this, SLOT( currentBBChanged() ) );
// we *always* want to receive updates even in case BB actually did
// not change upon setCurrentBB()-call
connect( &m_bbComboBoxModel, SIGNAL( dataUnchanged() ),
this, SLOT( currentBBChanged() ) );
setType( BBContainer );
}
BBTrackContainer::~BBTrackContainer()
{
}
bool BBTrackContainer::play( MidiTime _start, fpp_t _frames,
f_cnt_t _offset, int _tco_num )
{
bool played_a_note = false;
if( lengthOfBB( _tco_num ) <= 0 )
{
return false;
}
_start = _start % ( lengthOfBB( _tco_num ) * MidiTime::ticksPerTact() );
TrackList tl = tracks();
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
{
if( ( *it )->play( _start, _frames, _offset, _tco_num ) )
{
played_a_note = true;
}
}
return played_a_note;
}
void BBTrackContainer::updateAfterTrackAdd()
{
if( numOfBBs() == 0 && !Engine::getSong()->isLoadingProject() )
{
Engine::getSong()->addBBTrack();
}
}
tact_t BBTrackContainer::lengthOfBB( int _bb ) const
{
MidiTime max_length = MidiTime::ticksPerTact();
const TrackList & tl = tracks();
for (Track* t : tl)
{
// Don't create TCOs here if not exist
if (_bb < t->numOfTCOs())
{
max_length = qMax(max_length, t->getTCO( _bb )->length());
}
}
return max_length.nextFullTact();
}
int BBTrackContainer::numOfBBs() const
{
return Engine::getSong()->countTracks( Track::BBTrack );
}
void BBTrackContainer::removeBB( int _bb )
{
TrackList tl = tracks();
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
{
delete ( *it )->getTCO( _bb );
( *it )->removeTact( _bb * DefaultTicksPerTact );
}
if( _bb <= currentBB() )
{
setCurrentBB( qMax( currentBB() - 1, 0 ) );
}
}
void BBTrackContainer::swapBB( int _bb1, int _bb2 )
{
TrackList tl = tracks();
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
{
( *it )->swapPositionOfTCOs( _bb1, _bb2 );
}
updateComboBox();
}
void BBTrackContainer::updateBBTrack( TrackContentObject * _tco )
{
BBTrack * t = BBTrack::findBBTrack( _tco->startPosition() /
DefaultTicksPerTact );
if( t != NULL )
{
t->dataChanged();
}
}
void BBTrackContainer::fixIncorrectPositions()
{
TrackList tl = tracks();
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
{
for( int i = 0; i < numOfBBs(); ++i )
{
( *it )->getTCO( i )->movePosition( MidiTime( i, 0 ) );
}
}
}
void BBTrackContainer::play()
{
if( Engine::getSong()->playMode() != Song::Mode_PlayBB )
{
Engine::getSong()->playBB();
}
else
{
Engine::getSong()->togglePause();
}
}
void BBTrackContainer::stop()
{
Engine::getSong()->stop();
}
void BBTrackContainer::updateComboBox()
{
const int cur_bb = currentBB();
m_bbComboBoxModel.clear();
for( int i = 0; i < numOfBBs(); ++i )
{
BBTrack * bbt = BBTrack::findBBTrack( i );
m_bbComboBoxModel.addItem( bbt->name() );
}
setCurrentBB( cur_bb );
}
void BBTrackContainer::currentBBChanged()
{
// now update all track-labels (the current one has to become white,
// the others gray)
TrackList tl = Engine::getSong()->tracks();
for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it )
{
if( ( *it )->type() == Track::BBTrack )
{
( *it )->dataChanged();
}
}
}
void BBTrackContainer::createTCOsForBB( int _bb )
{
TrackList tl = tracks();
for( int i = 0; i < tl.size(); ++i )
{
tl[i]->createTCOsForBB( _bb );
}
}
AutomatedValueMap BBTrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const
{
Q_ASSERT(tcoNum >= 0);
Q_ASSERT(time.getTicks() >= 0);
auto length_tacts = lengthOfBB(tcoNum);
auto length_ticks = length_tacts * MidiTime::ticksPerTact();
if (time > length_ticks) {
time = length_ticks;
}
return TrackContainer::automatedValuesAt(time + (MidiTime::ticksPerTact() * tcoNum), tcoNum);
}

View File

@@ -27,7 +27,10 @@
#include <QDataStream>
WaveMipMap BandLimitedWave::s_waveforms[4] = { };
namespace lmms
{
std::array<WaveMipMap, BandLimitedWave::Waveforms::NumBLWaveforms> BandLimitedWave::s_waveforms = { };
bool BandLimitedWave::s_wavesGenerated = false;
QString BandLimitedWave::s_wavetableDir = "";
@@ -270,3 +273,5 @@ moogfile.close();
*/
}
} // namespace lmms

View File

@@ -26,21 +26,24 @@
#include "BufferManager.h"
#include "Engine.h"
#include "Mixer.h"
#include <cstring>
#include "MemoryManager.h"
static fpp_t framesPerPeriod;
void BufferManager::init( fpp_t framesPerPeriod )
namespace lmms
{
::framesPerPeriod = framesPerPeriod;
fpp_t BufferManager::s_framesPerPeriod;
void BufferManager::init( fpp_t fpp )
{
s_framesPerPeriod = fpp;
}
sampleFrame * BufferManager::acquire()
{
return MM_ALLOC( sampleFrame, ::framesPerPeriod );
return MM_ALLOC<sampleFrame>( s_framesPerPeriod );
}
void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset )
@@ -62,3 +65,4 @@ void BufferManager::release( sampleFrame * buf )
MM_FREE( buf );
}
} // namespace lmms

View File

@@ -1,10 +1,14 @@
set(LMMS_SRCS
${LMMS_SRCS}
core/AudioEngine.cpp
core/AudioEngineProfiler.cpp
core/AudioEngineWorkerThread.cpp
core/AutomatableModel.cpp
core/AutomationPattern.cpp
core/AutomationClip.cpp
core/AutomationNode.cpp
core/BandLimitedWave.cpp
core/base64.cpp
core/BBTrackContainer.cpp
core/BufferManager.cpp
core/Clipboard.cpp
core/ComboBoxModel.cpp
@@ -18,7 +22,7 @@ set(LMMS_SRCS
core/Engine.cpp
core/EnvelopeAndLfoParameters.cpp
core/fft_helpers.cpp
core/FxMixer.cpp
core/Mixer.cpp
core/ImportFilter.cpp
core/InlineAutomation.cpp
core/Instrument.cpp
@@ -26,24 +30,27 @@ set(LMMS_SRCS
core/InstrumentPlayHandle.cpp
core/InstrumentSoundShaping.cpp
core/JournallingObject.cpp
core/Keymap.cpp
core/Ladspa2LMMS.cpp
core/LadspaControl.cpp
core/LadspaManager.cpp
core/LfoController.cpp
core/LinkedModelGroups.cpp
core/LocklessAllocator.cpp
core/MemoryHelper.cpp
core/MemoryManager.cpp
core/MeterModel.cpp
core/MicroTimer.cpp
core/Mixer.cpp
core/MixerProfiler.cpp
core/MixerWorkerThread.cpp
core/Microtuner.cpp
core/MixHelpers.cpp
core/Model.cpp
core/ModelVisitor.cpp
core/Note.cpp
core/NotePlayHandle.cpp
core/Oscillator.cpp
core/PathUtil.cpp
core/PatternClip.cpp
core/PatternStore.cpp
core/PeakController.cpp
core/PerfLog.cpp
core/Piano.cpp
@@ -59,14 +66,18 @@ set(LMMS_SRCS
core/RenderManager.cpp
core/RingBuffer.cpp
core/SampleBuffer.cpp
core/SampleClip.cpp
core/SamplePlayHandle.cpp
core/SampleRecordHandle.cpp
core/Scale.cpp
core/SerializingObject.cpp
core/Song.cpp
core/TempoSyncKnobModel.cpp
core/TimePos.cpp
core/ToolPlugin.cpp
core/Track.cpp
core/TrackContainer.cpp
core/Clip.cpp
core/ValueBuffer.cpp
core/VstSyncController.cpp
core/StepRecorder.cpp
@@ -88,16 +99,28 @@ set(LMMS_SRCS
core/audio/AudioSampleRecorder.cpp
core/audio/AudioSdl.cpp
core/lv2/Lv2Basics.cpp
core/lv2/Lv2ControlBase.cpp
core/lv2/Lv2Evbuf.cpp
core/lv2/Lv2Features.cpp
core/lv2/Lv2Ports.cpp
core/lv2/Lv2Proc.cpp
core/lv2/Lv2Manager.cpp
core/lv2/Lv2Options.cpp
core/lv2/Lv2SubPluginFeatures.cpp
core/lv2/Lv2UridCache.cpp
core/lv2/Lv2UridMap.cpp
core/midi/MidiAlsaRaw.cpp
core/midi/MidiAlsaSeq.cpp
core/midi/MidiClient.cpp
core/midi/MidiController.cpp
core/midi/MidiEventToByteSeq.cpp
core/midi/MidiJack.cpp
core/midi/MidiOss.cpp
core/midi/MidiSndio.cpp
core/midi/MidiApple.cpp
core/midi/MidiPort.cpp
core/midi/MidiTime.cpp
core/midi/MidiWinMM.cpp
PARENT_SCOPE

203
src/core/Clip.cpp Normal file
View File

@@ -0,0 +1,203 @@
/*
* Clip.cpp - implementation of Clip class
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "Clip.h"
#include <QDomDocument>
#include "AutomationEditor.h"
#include "AutomationClip.h"
#include "Engine.h"
#include "GuiApplication.h"
#include "Song.h"
namespace lmms
{
/*! \brief Create a new Clip
*
* Creates a new clip for the given track.
*
* \param _track The track that will contain the new object
*/
Clip::Clip( Track * track ) :
Model( track ),
m_track( track ),
m_startPosition(),
m_length(),
m_mutedModel( false, this, tr( "Mute" ) ),
m_selectViewOnCreate( false ),
m_color( 128, 128, 128 ),
m_useCustomClipColor( false )
{
if( getTrack() )
{
getTrack()->addClip( this );
}
setJournalling( false );
movePosition( 0 );
changeLength( 0 );
setJournalling( true );
}
/*! \brief Destroy a Clip
*
* Destroys the given clip.
*
*/
Clip::~Clip()
{
emit destroyedClip();
if( getTrack() )
{
getTrack()->removeClip( this );
}
}
/*! \brief Move this Clip's position in time
*
* If the clip has moved, update its position. We
* also add a journal entry for undo and update the display.
*
* \param _pos The new position of the clip.
*/
void Clip::movePosition( const TimePos & pos )
{
TimePos newPos = qMax(0, pos.getTicks());
if (m_startPosition != newPos)
{
Engine::audioEngine()->requestChangeInModel();
m_startPosition = newPos;
Engine::audioEngine()->doneChangeInModel();
Engine::getSong()->updateLength();
emit positionChanged();
}
}
/*! \brief Change the length of this Clip
*
* If the clip's length has changed, update it. We
* also add a journal entry for undo and update the display.
*
* \param _length The new length of the clip.
*/
void Clip::changeLength( const TimePos & length )
{
m_length = length;
Engine::getSong()->updateLength();
emit lengthChanged();
}
bool Clip::comparePosition(const Clip *a, const Clip *b)
{
return a->startPosition() < b->startPosition();
}
/*! \brief Copies the state of a Clip to another Clip
*
* This method copies the state of a Clip to another Clip
*/
void Clip::copyStateTo( Clip *src, Clip *dst )
{
// If the node names match we copy the state
if( src->nodeName() == dst->nodeName() ){
QDomDocument doc;
QDomElement parent = doc.createElement( "StateCopy" );
src->saveState( doc, parent );
const TimePos pos = dst->startPosition();
dst->restoreState( parent.firstChild().toElement() );
dst->movePosition( pos );
AutomationClip::resolveAllIDs();
gui::getGUI()->automationEditor()->m_editor->updateAfterClipChange();
}
}
/*! \brief Mutes this Clip
*
* Restore the previous state of this clip. This will
* restore the position or the length of the clip
* depending on what was changed.
*
* \param _je The journal entry to undo
*/
void Clip::toggleMute()
{
m_mutedModel.setValue( !m_mutedModel.value() );
emit dataChanged();
}
TimePos Clip::startTimeOffset() const
{
return m_startTimeOffset;
}
void Clip::setStartTimeOffset( const TimePos &startTimeOffset )
{
m_startTimeOffset = startTimeOffset;
}
void Clip::useCustomClipColor( bool b )
{
if (b == m_useCustomClipColor) { return; }
m_useCustomClipColor = b;
emit colorChanged();
}
bool Clip::hasColor()
{
return usesCustomClipColor() || getTrack()->useColor();
}
} // namespace lmms

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>
*
@@ -22,32 +22,75 @@
*
*/
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include "Clipboard.h"
#include "JournallingObject.h"
Clipboard::Map Clipboard::content;
void Clipboard::copy( JournallingObject * _obj )
namespace lmms::Clipboard
{
QDomDocument doc;
QDomElement parent = doc.createElement( "Clipboard" );
_obj->saveState( doc, parent );
content[_obj->nodeName()] = parent.firstChild().toElement();
}
const QDomElement * Clipboard::getContent( const QString & _node_name )
{
if( content.find( _node_name ) != content.end() )
const QMimeData * getMimeData()
{
return &content[_node_name];
return QApplication::clipboard()->mimeData( QClipboard::Clipboard );
}
return NULL;
}
bool hasFormat( MimeType mT )
{
return getMimeData()->hasFormat( mimeType( mT ) );
}
void copyString( const QString & str, MimeType mT )
{
auto content = new QMimeData;
content->setData( mimeType( mT ), str.toUtf8() );
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
}
QString getString( MimeType mT )
{
return QString( getMimeData()->data( mimeType( mT ) ) );
}
void copyStringPair( const QString & key, const QString & value )
{
QString finalString = key + ":" + value;
auto content = new QMimeData;
content->setData( mimeType( MimeType::StringPair ), finalString.toUtf8() );
QApplication::clipboard()->setMimeData( content, QClipboard::Clipboard );
}
QString decodeKey( const QMimeData * mimeData )
{
return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 0, 0 ) );
}
QString decodeValue( const QMimeData * mimeData )
{
return( QString::fromUtf8( mimeData->data( mimeType( MimeType::StringPair ) ) ).section( ':', 1, -1 ) );
}
} // namespace lmms::Clipboard

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

File diff suppressed because it is too large Load Diff

View File

@@ -25,18 +25,19 @@
*/
#include <QDomElement>
#include <QObject>
#include <QVector>
#include "Song.h"
#include "Mixer.h"
#include "AudioEngine.h"
#include "ControllerConnection.h"
#include "ControllerDialog.h"
#include "LfoController.h"
#include "MidiController.h"
#include "PeakController.h"
namespace lmms
{
long Controller::s_periods = 0;
QVector<Controller *> Controller::s_controllers;
@@ -47,7 +48,7 @@ Controller::Controller( ControllerTypes _type, Model * _parent,
const QString & _display_name ) :
Model( _parent, _display_name ),
JournallingObject(),
m_valueBuffer( Engine::mixer()->framesPerPeriod() ),
m_valueBuffer( Engine::audioEngine()->framesPerPeriod() ),
m_bufferLastUpdated( -1 ),
m_connectionCount( 0 ),
m_type( _type )
@@ -140,7 +141,7 @@ void Controller::updateValueBuffer()
// Get position in frames
unsigned int Controller::runningFrames()
{
return s_periods * Engine::mixer()->framesPerPeriod();
return s_periods * Engine::audioEngine()->framesPerPeriod();
}
@@ -148,7 +149,7 @@ unsigned int Controller::runningFrames()
// Get position in seconds
float Controller::runningTime()
{
return runningFrames() / Engine::mixer()->processingSampleRate();
return runningFrames() / Engine::audioEngine()->processingSampleRate();
}
@@ -183,20 +184,20 @@ void Controller::resetFrameCounter()
Controller * Controller::create( ControllerTypes _ct, Model * _parent )
{
static Controller * dummy = NULL;
Controller * c = NULL;
static Controller * dummy = nullptr;
Controller * c = nullptr;
switch( _ct )
{
case Controller::DummyController:
if (!dummy)
dummy = new Controller( DummyController, NULL,
dummy = new Controller( DummyController, nullptr,
QString() );
c = dummy;
break;
case Controller::LfoController:
c = new ::LfoController( _parent );
c = new class LfoController( _parent );
break;
case Controller::PeakController:
@@ -205,7 +206,7 @@ Controller * Controller::create( ControllerTypes _ct, Model * _parent )
break;
case Controller::MidiController:
c = new ::MidiController( _parent );
c = new class MidiController( _parent );
break;
default:
@@ -231,7 +232,7 @@ Controller * Controller::create( const QDomElement & _this, Model * _parent )
_parent );
}
if( c != NULL )
if( c != nullptr )
{
c->restoreState( _this );
}
@@ -245,8 +246,8 @@ bool Controller::hasModel( const Model * m ) const
{
for (QObject * c : children())
{
AutomatableModel * am = qobject_cast<AutomatableModel*>(c);
if( am != NULL )
auto am = qobject_cast<AutomatableModel*>(c);
if( am != nullptr )
{
if( am == m )
{
@@ -254,7 +255,7 @@ bool Controller::hasModel( const Model * m ) const
}
ControllerConnection * cc = am->controllerConnection();
if( cc != NULL && cc->getController()->hasModel( m ) )
if( cc != nullptr && cc->getController()->hasModel( m ) )
{
return true;
}
@@ -293,9 +294,9 @@ QString Controller::nodeName() const
ControllerDialog * Controller::createDialog( QWidget * _parent )
gui::ControllerDialog * Controller::createDialog( QWidget * _parent )
{
ControllerDialog * d = new ControllerDialog( this, _parent );
auto d = new gui::ControllerDialog(this, _parent);
return d;
}
@@ -325,7 +326,7 @@ int Controller::connectionCount() const{
}
} // namespace lmms

View File

@@ -31,24 +31,27 @@
#include "Song.h"
#include "ControllerConnection.h"
namespace lmms
{
ControllerConnectionVector ControllerConnection::s_connections;
ControllerConnection::ControllerConnection( Controller * _controller ) :
m_controller( NULL ),
ControllerConnection::ControllerConnection(Controller * _controller) :
m_controller( nullptr ),
m_controllerId( -1 ),
m_ownsController( false )
m_ownsController(false)
{
if( _controller != NULL )
if( _controller != nullptr )
{
setController( _controller );
}
else
{
m_controller = Controller::create( Controller::DummyController,
NULL );
nullptr );
}
s_connections.append( this );
}
@@ -57,7 +60,7 @@ ControllerConnection::ControllerConnection( Controller * _controller ) :
ControllerConnection::ControllerConnection( int _controllerId ) :
m_controller( Controller::create( Controller::DummyController, NULL ) ),
m_controller( Controller::create( Controller::DummyController, nullptr ) ),
m_controllerId( _controllerId ),
m_ownsController( false )
{
@@ -95,7 +98,7 @@ void ControllerConnection::setController( Controller * _controller )
if( m_ownsController && m_controller )
{
delete m_controller;
m_controller = NULL;
m_controller = nullptr;
}
if( m_controller && m_controller->type() != Controller::DummyController )
@@ -105,7 +108,7 @@ void ControllerConnection::setController( Controller * _controller )
if( !_controller )
{
m_controller = Controller::create( Controller::DummyController, NULL );
m_controller = Controller::create( Controller::DummyController, nullptr );
}
else
{
@@ -116,18 +119,18 @@ void ControllerConnection::setController( Controller * _controller )
if( _controller->type() != Controller::DummyController )
{
_controller->addConnection( this );
QObject::connect( _controller, SIGNAL( valueChanged() ),
this, SIGNAL( valueChanged() ), Qt::DirectConnection );
QObject::connect( _controller, SIGNAL(valueChanged()),
this, SIGNAL(valueChanged()), Qt::DirectConnection );
}
m_ownsController =
( _controller->type() == Controller::MidiController );
(_controller->type() == Controller::MidiController);
// If we don't own the controller, allow deletion of controller
// to delete the connection
if( !m_ownsController ) {
QObject::connect( _controller, SIGNAL( destroyed() ),
this, SLOT( deleteConnection() ) );
QObject::connect( _controller, SIGNAL(destroyed()),
this, SLOT(deleteConnection()));
}
}
@@ -220,7 +223,7 @@ void ControllerConnection::loadSettings( const QDomElement & _this )
}
else
{
m_controller = Controller::create( Controller::DummyController, NULL );
m_controller = Controller::create( Controller::DummyController, nullptr );
}
}
}
@@ -233,4 +236,4 @@ void ControllerConnection::deleteConnection()
} // namespace lmms

File diff suppressed because it is too large Load Diff

View File

@@ -29,26 +29,22 @@
#include <sstream>
#include <cstring>
#include <math.h> //sin(), exp(), etc.
#include <cmath> //sin(), exp(), etc.
#include <QFile>
#ifdef LMMS_BUILD_WIN32
#define powf pow
#endif
#ifdef _MSC_VER
//not #if LMMS_BUILD_WIN32 because we have strncasecmp in mingw
#define strcasecmp _stricmp
#endif
#endif // _MSC_VER
namespace lmms
{
using namespace std;
#define WORD __u16
#define DWORD __u32
#define WAVE_FORMAT_PCM 0x0001
// const int Fs = 44100;
const float TwoPi = 6.2831853f;
const int MAX = 0;
@@ -71,7 +67,7 @@ long wavewords, wavemode=0;
float mem_t=1.0f, mem_o=1.0f, mem_n=1.0f, mem_b=1.0f, mem_tune=1.0f, mem_time=1.0f;
int DrumSynth::LongestEnv(void)
int DrumSynth::LongestEnv()
{
long e, eon, p;
float l=0.f;
@@ -90,7 +86,7 @@ int DrumSynth::LongestEnv(void)
}
float DrumSynth::LoudestEnv(void)
float DrumSynth::LoudestEnv()
{
float loudest=0.f;
int i=0;
@@ -206,7 +202,7 @@ int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const c
break;
k = strtok(line, " \t=");
b = strtok(NULL, "\n\r\0");
b = strtok(nullptr, "\n\r\0");
if (k != 0 && strcasecmp(k, key)==0) {
if (b==0) {
@@ -325,15 +321,17 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
timestretch = .01f * mem_time * GetPrivateProfileFloat(sec,"Stretch",100.0,dsfile);
if(timestretch<0.2f) timestretch=0.2f;
if(timestretch>10.f) timestretch=10.f;
// the unit of envelope lengths is a sample in 44100Hz sample rate, so correct it
timestretch *= Fs / 44100.f;
DGain = 1.0f; //leave this here!
DGain = (float)powf(10.0, 0.05 * GetPrivateProfileFloat(sec,"Level",0,dsfile));
DGain = static_cast<float>(std::pow(10.0, 0.05 * GetPrivateProfileFloat(sec,"Level",0,dsfile)));
MasterTune = GetPrivateProfileFloat(sec,"Tuning",0.0,dsfile);
MasterTune = (float)powf(1.0594631f, MasterTune + mem_tune);
MasterTune = static_cast<float>(std::pow(1.0594631f, MasterTune + mem_tune));
MainFilter = 2 * GetPrivateProfileInt(sec,"Filter",0,dsfile);
MFres = 0.0101f * GetPrivateProfileFloat(sec,"Resonance",0.0,dsfile);
MFres = (float)powf(MFres, 0.5f);
MFres = static_cast<float>(std::pow(MFres, 0.5f));
HighPass = GetPrivateProfileInt(sec,"HighPass",0,dsfile);
GetEnv(7, sec, "FilterEnv", dsfile);
@@ -368,7 +366,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
TDroopRate = GetPrivateProfileFloat(sec,"Droop",0.f,dsfile);
if(TDroopRate>0.f)
{
TDroopRate = (float)powf(10.0f, (TDroopRate - 20.0f) / 30.0f);
TDroopRate = static_cast<float>(std::pow(10.0f, (TDroopRate - 20.0f) / 30.0f));
TDroopRate = TDroopRate * -4.f / envData[1][MAX];
TDroop = 1;
F2 = F1+((F2-F1)/(1.f-(float)exp(TDroopRate * envData[1][MAX])));
@@ -391,7 +389,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
OW1 = GetPrivateProfileInt(sec,"Wave1",0,dsfile);
OW2 = GetPrivateProfileInt(sec,"Wave2",0,dsfile);
OBal2 = (float)GetPrivateProfileInt(sec,"Param",50,dsfile);
ODrive = (float)powf(OBal2, 3.0f) / (float)powf(50.0f, 3.0f);
ODrive = static_cast<float>(std::pow(OBal2, 3.0f)) / std::pow(50.0f, 3.0f);
OBal2 *= 0.01f;
OBal1 = 1.f - OBal2;
Ophi1 = Tphi;
@@ -451,8 +449,8 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
{
DAtten = DGain * (short)LoudestEnv();
if(DAtten>32700) clippoint=32700; else clippoint=(short)DAtten;
DAtten = (float)powf(2.0, 2.0 * GetPrivateProfileInt(sec,"Bits",0,dsfile));
DGain = DAtten * DGain * (float)powf(10.0, 0.05 * GetPrivateProfileInt(sec,"Clipping",0,dsfile));
DAtten = static_cast<float>(std::pow(2.0, 2.0 * GetPrivateProfileInt(sec,"Bits",0,dsfile)));
DGain = DAtten * DGain * static_cast<float>(std::pow(10.0, 0.05 * GetPrivateProfileInt(sec,"Clipping",0,dsfile)));
}
//prepare envelopes
@@ -464,7 +462,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
//if(wave!=NULL) free(wave);
//wave = new int16_t[channels * (Length + 1280)]; //wave memory buffer
wave = new int16_t[channels * Length]; //wave memory buffer
if(wave==NULL) {return 0;}
if(wave==nullptr) {return 0;}
wavewords = 0;
/*
@@ -662,7 +660,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
MFtmp = envData[7][ENV];
if(MFtmp >0.2f)
MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1);
MFfb = 1.001f - static_cast<float>(std::pow(10.0f, MFtmp - 1));
else
MFfb = 0.999f - 0.7824f * MFtmp;
@@ -680,7 +678,7 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
MFtmp = envData[7][ENV];
if(MFtmp >0.2f)
MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1);
MFfb = 1.001f - static_cast<float>(std::pow(10.0f, MFtmp - 1));
else
MFfb = 0.999f - 0.7824f * MFtmp;
@@ -746,3 +744,5 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
return Length;
}
} // namespace lmms

View File

@@ -32,12 +32,15 @@
#include "ConfigManager.h"
namespace lmms
{
Effect::Effect( const Plugin::Descriptor * _desc,
Model * _parent,
const Descriptor::SubPluginFeatures::Key * _key ) :
Plugin( _desc, _parent, _key ),
m_parent( NULL ),
m_parent( nullptr ),
m_processors( 1 ),
m_okay( true ),
m_noRun( false ),
@@ -49,7 +52,7 @@ Effect::Effect( const Plugin::Descriptor * _desc,
m_autoQuitModel( 1.0f, 1.0f, 8000.0f, 100.0f, 1.0f, this, tr( "Decay" ) ),
m_autoQuitDisabled( false )
{
m_srcState[0] = m_srcState[1] = NULL;
m_srcState[0] = m_srcState[1] = nullptr;
reinitSRC();
if( ConfigManager::inst()->value( "ui", "disableautoquit").toInt() )
@@ -63,11 +66,11 @@ Effect::Effect( const Plugin::Descriptor * _desc,
Effect::~Effect()
{
for( int i = 0; i < 2; ++i )
for (const auto& state : m_srcState)
{
if( m_srcState[i] != NULL )
if (state != nullptr)
{
src_delete( m_srcState[i] );
src_delete(state);
}
}
}
@@ -118,10 +121,10 @@ Effect * Effect::instantiate( const QString& pluginName,
{
Plugin * p = Plugin::instantiateWithKey( pluginName, _parent, _key );
// check whether instantiated plugin is an effect
if( dynamic_cast<Effect *>( p ) != NULL )
if( dynamic_cast<Effect *>( p ) != nullptr )
{
// everything ok, so return pointer
Effect * effect = dynamic_cast<Effect *>( p );
auto effect = dynamic_cast<Effect*>(p);
effect->m_parent = dynamic_cast<EffectChain *>(_parent);
return effect;
}
@@ -129,7 +132,7 @@ Effect * Effect::instantiate( const QString& pluginName,
// not quite... so delete plugin and leave it up to the caller to instantiate a DummyEffect
delete p;
return NULL;
return nullptr;
}
@@ -162,9 +165,9 @@ void Effect::checkGate( double _out_sum )
PluginView * Effect::instantiateView( QWidget * _parent )
gui::PluginView * Effect::instantiateView( QWidget * _parent )
{
return new EffectView( this, _parent );
return new gui::EffectView( this, _parent );
}
@@ -172,17 +175,15 @@ PluginView * Effect::instantiateView( QWidget * _parent )
void Effect::reinitSRC()
{
for( int i = 0; i < 2; ++i )
for (auto& state : m_srcState)
{
if( m_srcState[i] != NULL )
if (state != nullptr)
{
src_delete( m_srcState[i] );
src_delete(state);
}
int error;
if( ( m_srcState[i] = src_new(
Engine::mixer()->currentQualitySettings().
libsrcInterpolation(),
DEFAULT_CHANNELS, &error ) ) == NULL )
const int currentInterpolation = Engine::audioEngine()->currentQualitySettings().libsrcInterpolation();
if((state = src_new(currentInterpolation, DEFAULT_CHANNELS, &error)) == nullptr)
{
qFatal( "Error: src_new() failed in effect.cpp!\n" );
}
@@ -197,14 +198,14 @@ void Effect::resample( int _i, const sampleFrame * _src_buf,
sampleFrame * _dst_buf, sample_rate_t _dst_sr,
f_cnt_t _frames )
{
if( m_srcState[_i] == NULL )
if( m_srcState[_i] == nullptr )
{
return;
}
m_srcData[_i].input_frames = _frames;
m_srcData[_i].output_frames = Engine::mixer()->framesPerPeriod();
m_srcData[_i].data_in = (float *) _src_buf[0];
m_srcData[_i].data_out = _dst_buf[0];
m_srcData[_i].output_frames = Engine::audioEngine()->framesPerPeriod();
m_srcData[_i].data_in = const_cast<float*>(_src_buf[0].data());
m_srcData[_i].data_out = _dst_buf[0].data ();
m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr;
m_srcData[_i].end_of_input = 0;
int error;
@@ -215,3 +216,4 @@ void Effect::resample( int _i, const sampleFrame * _src_buf,
}
}
} // namespace lmms

View File

@@ -30,13 +30,15 @@
#include "Effect.h"
#include "DummyEffect.h"
#include "MixHelpers.h"
#include "Song.h"
namespace lmms
{
EffectChain::EffectChain( Model * _parent ) :
Model( _parent ),
SerializingObject(),
m_enabledModel( false, NULL, tr( "Effects enabled" ) )
m_enabledModel( false, nullptr, tr( "Effects enabled" ) )
{
}
@@ -58,10 +60,7 @@ void EffectChain::saveSettings( QDomDocument & _doc, QDomElement & _this )
for( Effect* effect : m_effects)
{
if( DummyEffect* dummy = dynamic_cast<DummyEffect*>(effect) )
{
_this.appendChild( dummy->originalPluginData() );
}
if (auto dummy = dynamic_cast<DummyEffect*>(effect)) { _this.appendChild(dummy->originalPluginData()); }
else
{
QDomElement ef = effect->saveState( _doc, _this );
@@ -78,7 +77,7 @@ void EffectChain::loadSettings( const QDomElement & _this )
{
clear();
// TODO This method should probably also lock the mixer
// TODO This method should probably also lock the audio engine
m_enabledModel.loadSettings( _this, "enabled" );
@@ -97,7 +96,7 @@ void EffectChain::loadSettings( const QDomElement & _this )
Effect* e = Effect::instantiate( name.toUtf8(), this, &key );
if( e != NULL && e->isOkay() && e->nodeName() == node.nodeName() )
if( e != nullptr && e->isOkay() && e->nodeName() == node.nodeName() )
{
e->restoreState( effectData );
}
@@ -121,9 +120,9 @@ void EffectChain::loadSettings( const QDomElement & _this )
void EffectChain::appendEffect( Effect * _effect )
{
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
m_effects.append( _effect );
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
m_enabledModel.setValue( true );
@@ -135,17 +134,17 @@ void EffectChain::appendEffect( Effect * _effect )
void EffectChain::removeEffect( Effect * _effect )
{
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
Effect ** found = std::find( m_effects.begin(), m_effects.end(), _effect );
if( found == m_effects.end() )
{
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
return;
}
m_effects.erase( found );
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
if( m_effects.isEmpty() )
{
@@ -192,12 +191,12 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b
MixHelpers::sanitize( _buf, _frames );
bool moreEffects = false;
for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); ++it )
for (const auto& effect : m_effects)
{
if( hasInputNoise || ( *it )->isRunning() )
if (hasInputNoise || effect->isRunning())
{
moreEffects |= ( *it )->processAudioBuffer( _buf, _frames );
MixHelpers::sanitize( _buf, _frames );
moreEffects |= effect->processAudioBuffer(_buf, _frames);
MixHelpers::sanitize(_buf, _frames);
}
}
@@ -214,10 +213,9 @@ void EffectChain::startRunning()
return;
}
for( EffectList::Iterator it = m_effects.begin();
it != m_effects.end(); it++ )
for (const auto& effect : m_effects)
{
( *it )->startRunning();
effect->startRunning();
}
}
@@ -228,7 +226,7 @@ void EffectChain::clear()
{
emit aboutToClear();
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
while( m_effects.count() )
{
@@ -237,7 +235,10 @@ void EffectChain::clear()
delete e;
}
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
m_enabledModel.setValue( false );
}
} // namespace lmms

View File

@@ -24,77 +24,91 @@
#include "Engine.h"
#include "BBTrackContainer.h"
#include "AudioEngine.h"
#include "ConfigManager.h"
#include "FxMixer.h"
#include "Ladspa2LMMS.h"
#include "Mixer.h"
#include "Ladspa2LMMS.h"
#include "Lv2Manager.h"
#include "PatternStore.h"
#include "Plugin.h"
#include "PresetPreviewPlayHandle.h"
#include "ProjectJournal.h"
#include "Song.h"
#include "BandLimitedWave.h"
#include "Oscillator.h"
float LmmsCore::s_framesPerTick;
Mixer* LmmsCore::s_mixer = NULL;
FxMixer * LmmsCore::s_fxMixer = NULL;
BBTrackContainer * LmmsCore::s_bbTrackContainer = NULL;
Song * LmmsCore::s_song = NULL;
ProjectJournal * LmmsCore::s_projectJournal = NULL;
Ladspa2LMMS * LmmsCore::s_ladspaManager = NULL;
void* LmmsCore::s_dndPluginKey = nullptr;
DummyTrackContainer * LmmsCore::s_dummyTC = NULL;
void LmmsCore::init( bool renderOnly )
namespace lmms
{
LmmsCore *engine = inst();
float Engine::s_framesPerTick;
AudioEngine* Engine::s_audioEngine = nullptr;
Mixer * Engine::s_mixer = nullptr;
PatternStore * Engine::s_patternStore = nullptr;
Song * Engine::s_song = nullptr;
ProjectJournal * Engine::s_projectJournal = nullptr;
#ifdef LMMS_HAVE_LV2
Lv2Manager * Engine::s_lv2Manager = nullptr;
#endif
Ladspa2LMMS * Engine::s_ladspaManager = nullptr;
void* Engine::s_dndPluginKey = nullptr;
void Engine::init( bool renderOnly )
{
Engine *engine = inst();
emit engine->initProgress(tr("Generating wavetables"));
// generate (load from file) bandlimited wavetables
BandLimitedWave::generateWaves();
//initilize oscillators
Oscillator::waveTableInit();
emit engine->initProgress(tr("Initializing data structures"));
s_projectJournal = new ProjectJournal;
s_mixer = new Mixer( renderOnly );
s_audioEngine = new AudioEngine( renderOnly );
s_song = new Song;
s_fxMixer = new FxMixer;
s_bbTrackContainer = new BBTrackContainer;
s_mixer = new Mixer;
s_patternStore = new PatternStore;
#ifdef LMMS_HAVE_LV2
s_lv2Manager = new Lv2Manager;
s_lv2Manager->initPlugins();
#endif
s_ladspaManager = new Ladspa2LMMS;
s_projectJournal->setJournalling( true );
emit engine->initProgress(tr("Opening audio and midi devices"));
s_mixer->initDevices();
s_audioEngine->initDevices();
PresetPreviewPlayHandle::init();
s_dummyTC = new DummyTrackContainer;
emit engine->initProgress(tr("Launching mixer threads"));
s_mixer->startProcessing();
emit engine->initProgress(tr("Launching audio engine threads"));
s_audioEngine->startProcessing();
}
void LmmsCore::destroy()
void Engine::destroy()
{
s_projectJournal->stopAllJournalling();
s_mixer->stopProcessing();
s_audioEngine->stopProcessing();
PresetPreviewPlayHandle::cleanup();
s_song->clearProject();
deleteHelper( &s_bbTrackContainer );
deleteHelper( &s_dummyTC );
deleteHelper( &s_patternStore );
deleteHelper( &s_fxMixer );
deleteHelper( &s_mixer );
deleteHelper( &s_audioEngine );
#ifdef LMMS_HAVE_LV2
deleteHelper( &s_lv2Manager );
#endif
deleteHelper( &s_ladspaManager );
//delete ConfigManager::inst();
@@ -103,27 +117,42 @@ void LmmsCore::destroy()
deleteHelper( &s_song );
delete ConfigManager::inst();
// The oscillator FFT plans remain throughout the application lifecycle
// due to being expensive to create, and being used whenever a userwave form is changed
Oscillator::destroyFFTPlans();
}
float LmmsCore::framesPerTick(sample_rate_t sample_rate)
bool Engine::ignorePluginBlacklist()
{
return sample_rate * 60.0f * 4 /
DefaultTicksPerTact / s_song->getTempo();
const char* envVar = getenv("LMMS_IGNORE_BLACKLIST");
return (envVar && *envVar);
}
void LmmsCore::updateFramesPerTick()
float Engine::framesPerTick(sample_rate_t sampleRate)
{
s_framesPerTick = s_mixer->processingSampleRate() * 60.0f * 4 /
DefaultTicksPerTact / s_song->getTempo();
return sampleRate * 60.0f * 4 /
DefaultTicksPerBar / s_song->getTempo();
}
void LmmsCore::setDndPluginKey(void *newKey)
void Engine::updateFramesPerTick()
{
s_framesPerTick = s_audioEngine->processingSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
}
void Engine::setDndPluginKey(void *newKey)
{
Q_ASSERT(static_cast<Plugin::Descriptor::SubPluginFeatures::Key*>(newKey));
s_dndPluginKey = newKey;
@@ -132,7 +161,7 @@ void LmmsCore::setDndPluginKey(void *newKey)
void *LmmsCore::pickDndPluginKey()
void *Engine::pickDndPluginKey()
{
return s_dndPluginKey;
}
@@ -140,4 +169,6 @@ void *LmmsCore::pickDndPluginKey()
LmmsCore * LmmsCore::s_instanceOfMe = NULL;
Engine * Engine::s_instanceOfMe = nullptr;
} // namespace lmms

View File

@@ -25,11 +25,14 @@
#include <QDomElement>
#include "EnvelopeAndLfoParameters.h"
#include "AudioEngine.h"
#include "Engine.h"
#include "Mixer.h"
#include "Oscillator.h"
namespace lmms
{
// how long should be each envelope-segment maximal (e.g. attack)?
extern const float SECS_PER_ENV_SEGMENT = 5.0f;
// how long should be one LFO-oscillation maximal?
@@ -38,18 +41,16 @@ extern const float SECS_PER_LFO_OSCILLATION = 20.0f;
const f_cnt_t minimumFrames = 1;
EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = NULL;
EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = nullptr;
void EnvelopeAndLfoParameters::LfoInstances::trigger()
{
QMutexLocker m( &m_lfoListMutex );
for( LfoList::Iterator it = m_lfos.begin();
it != m_lfos.end(); ++it )
for (const auto& lfo : m_lfos)
{
( *it )->m_lfoFrame +=
Engine::mixer()->framesPerPeriod();
( *it )->m_bad_lfoShapeData = true;
lfo->m_lfoFrame += Engine::audioEngine()->framesPerPeriod();
lfo->m_bad_lfoShapeData = true;
}
}
@@ -59,11 +60,10 @@ void EnvelopeAndLfoParameters::LfoInstances::trigger()
void EnvelopeAndLfoParameters::LfoInstances::reset()
{
QMutexLocker m( &m_lfoListMutex );
for( LfoList::Iterator it = m_lfos.begin();
it != m_lfos.end(); ++it )
for (const auto& lfo : m_lfos)
{
( *it )->m_lfoFrame = 0;
( *it )->m_bad_lfoShapeData = true;
lfo->m_lfoFrame = 0;
lfo->m_bad_lfoShapeData = true;
}
}
@@ -103,8 +103,8 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
m_valueForZeroAmount( _value_for_zero_amount ),
m_pahdFrames( 0 ),
m_rFrames( 0 ),
m_pahdEnv( NULL ),
m_rEnv( NULL ),
m_pahdEnv( nullptr ),
m_rEnv( nullptr ),
m_pahdBufSize( 0 ),
m_rBufSize( 0 ),
m_lfoPredelayModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO pre-delay" ) ),
@@ -118,52 +118,52 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
m_controlEnvAmountModel( false, this, tr( "Modulate env amount" ) ),
m_lfoFrame( 0 ),
m_lfoAmountIsZero( false ),
m_lfoShapeData( NULL )
m_lfoShapeData( nullptr )
{
m_amountModel.setCenterValue( 0 );
m_lfoAmountModel.setCenterValue( 0 );
if( s_lfoInstances == NULL )
if( s_lfoInstances == nullptr )
{
s_lfoInstances = new LfoInstances();
}
instances()->add( this );
connect( &m_predelayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_attackModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_holdModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_decayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_sustainModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_releaseModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_amountModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_predelayModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_attackModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_holdModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_decayModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_sustainModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_releaseModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_amountModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_lfoPredelayModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_lfoAttackModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_lfoSpeedModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_lfoAmountModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_lfoWaveModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_x100Model, SIGNAL( dataChanged() ),
this, SLOT( updateSampleVars() ), Qt::DirectConnection );
connect( &m_lfoPredelayModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_lfoAttackModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_lfoSpeedModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_lfoAmountModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_lfoWaveModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( &m_x100Model, SIGNAL(dataChanged()),
this, SLOT(updateSampleVars()), Qt::DirectConnection );
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
this, SLOT( updateSampleVars() ) );
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()),
this, SLOT(updateSampleVars()));
m_lfoShapeData =
new sample_t[Engine::mixer()->framesPerPeriod()];
new sample_t[Engine::audioEngine()->framesPerPeriod()];
updateSampleVars();
}
@@ -196,7 +196,7 @@ EnvelopeAndLfoParameters::~EnvelopeAndLfoParameters()
if( instances()->isEmpty() )
{
delete instances();
s_lfoInstances = NULL;
s_lfoInstances = nullptr;
}
}
@@ -243,7 +243,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset )
void EnvelopeAndLfoParameters::updateLfoShapeData()
{
const fpp_t frames = Engine::mixer()->framesPerPeriod();
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
for( fpp_t offset = 0; offset < frames; ++offset )
{
m_lfoShapeData[offset] = lfoShapeSample( offset );
@@ -399,19 +399,16 @@ void EnvelopeAndLfoParameters::updateSampleVars()
QMutexLocker m(&m_paramMutex);
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
Engine::mixer()->processingSampleRate();
Engine::audioEngine()->processingSampleRate();
// TODO: Remove the expKnobVals, time should be linear
const f_cnt_t predelay_frames = static_cast<f_cnt_t>(
frames_per_env_seg *
expKnobVal( m_predelayModel.value() ) );
const auto predelay_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_predelayModel.value()));
const f_cnt_t attack_frames = qMax( minimumFrames,
static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_attackModel.value() ) ) );
const f_cnt_t hold_frames = static_cast<f_cnt_t>( frames_per_env_seg *
expKnobVal( m_holdModel.value() ) );
const auto hold_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_holdModel.value()));
const f_cnt_t decay_frames = qMax( minimumFrames,
static_cast<f_cnt_t>( frames_per_env_seg *
@@ -501,7 +498,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
Engine::mixer()->processingSampleRate();
Engine::audioEngine()->processingSampleRate();
m_lfoPredelayFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
expKnobVal( m_lfoPredelayModel.value() ) );
m_lfoAttackFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
@@ -541,4 +538,4 @@ void EnvelopeAndLfoParameters::updateSampleVars()
} // namespace lmms

View File

@@ -1,812 +0,0 @@
/*
* FxMixer.cpp - effect mixer for LMMS
*
* Copyright (c) 2008-2011 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QDomElement>
#include "BufferManager.h"
#include "FxMixer.h"
#include "Mixer.h"
#include "MixerWorkerThread.h"
#include "MixHelpers.h"
#include "Song.h"
#include "InstrumentTrack.h"
#include "SampleTrack.h"
#include "BBTrackContainer.h"
FxRoute::FxRoute( FxChannel * from, FxChannel * to, float amount ) :
m_from( from ),
m_to( to ),
m_amount( amount, 0, 1, 0.001, NULL,
tr( "Amount to send from channel %1 to channel %2" ).arg( m_from->m_channelIndex ).arg( m_to->m_channelIndex ) )
{
//qDebug( "created: %d to %d", m_from->m_channelIndex, m_to->m_channelIndex );
// create send amount model
}
FxRoute::~FxRoute()
{
}
void FxRoute::updateName()
{
m_amount.setDisplayName(
tr( "Amount to send from channel %1 to channel %2" ).arg( m_from->m_channelIndex ).arg( m_to->m_channelIndex ) );
}
FxChannel::FxChannel( int idx, Model * _parent ) :
m_fxChain( NULL ),
m_hasInput( false ),
m_stillRunning( false ),
m_peakLeft( 0.0f ),
m_peakRight( 0.0f ),
m_buffer( new sampleFrame[Engine::mixer()->framesPerPeriod()] ),
m_muteModel( false, _parent ),
m_soloModel( false, _parent ),
m_volumeModel( 1.0, 0.0, 2.0, 0.001, _parent ),
m_name(),
m_lock(),
m_channelIndex( idx ),
m_queued( false ),
m_dependenciesMet(0)
{
BufferManager::clear( m_buffer, Engine::mixer()->framesPerPeriod() );
}
FxChannel::~FxChannel()
{
delete[] m_buffer;
}
inline void FxChannel::processed()
{
for( const FxRoute * receiverRoute : m_sends )
{
if( receiverRoute->receiver()->m_muted == false )
{
receiverRoute->receiver()->incrementDeps();
}
}
}
void FxChannel::incrementDeps()
{
int i = m_dependenciesMet++ + 1;
if( i >= m_receives.size() && ! m_queued )
{
m_queued = true;
MixerWorkerThread::addJob( this );
}
}
void FxChannel::unmuteForSolo()
{
//TODO: Recursively activate every channel, this channel sends to
m_muteModel.setValue(false);
}
void FxChannel::doProcessing()
{
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
if( m_muted == false )
{
for( FxRoute * senderRoute : m_receives )
{
FxChannel * sender = senderRoute->sender();
FloatModel * sendModel = senderRoute->amount();
if( ! sendModel ) qFatal( "Error: no send model found from %d to %d", senderRoute->senderIndex(), m_channelIndex );
if( sender->m_hasInput || sender->m_stillRunning )
{
// figure out if we're getting sample-exact input
ValueBuffer * sendBuf = sendModel->valueBuffer();
ValueBuffer * volBuf = sender->m_volumeModel.valueBuffer();
// mix it's output with this one's output
sampleFrame * ch_buf = sender->m_buffer;
// use sample-exact mixing if sample-exact values are available
if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data...
{
const float v = sender->m_volumeModel.value() * sendModel->value();
MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp );
}
else if( volBuf && sendBuf ) // both volume and send have sample-exact data
{
MixHelpers::addSanitizedMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp );
}
else if( volBuf ) // volume has sample-exact data but send does not
{
const float v = sendModel->value();
MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp );
}
else // vice versa
{
const float v = sender->m_volumeModel.value();
MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp );
}
m_hasInput = true;
}
}
const float v = m_volumeModel.value();
if( m_hasInput )
{
// only start fxchain when we have input...
m_fxChain.startRunning();
}
m_stillRunning = m_fxChain.processAudioBuffer( m_buffer, fpp, m_hasInput );
Mixer::StereoSample peakSamples = Engine::mixer()->getPeakValues(m_buffer, fpp);
m_peakLeft = qMax( m_peakLeft, peakSamples.left * v );
m_peakRight = qMax( m_peakRight, peakSamples.right * v );
}
else
{
m_peakLeft = m_peakRight = 0.0f;
}
// increment dependency counter of all receivers
processed();
}
FxMixer::FxMixer() :
Model( NULL ),
JournallingObject(),
m_fxChannels()
{
// create master channel
createChannel();
m_lastSoloed = -1;
}
FxMixer::~FxMixer()
{
while( ! m_fxRoutes.isEmpty() )
{
deleteChannelSend( m_fxRoutes.first() );
}
while( m_fxChannels.size() )
{
FxChannel * f = m_fxChannels[m_fxChannels.size() - 1];
m_fxChannels.pop_back();
delete f;
}
}
int FxMixer::createChannel()
{
const int index = m_fxChannels.size();
// create new channel
m_fxChannels.push_back( new FxChannel( index, this ) );
// reset channel state
clearChannel( index );
return index;
}
void FxMixer::activateSolo()
{
for (int i = 1; i < m_fxChannels.size(); ++i)
{
m_fxChannels[i]->m_muteBeforeSolo = m_fxChannels[i]->m_muteModel.value();
m_fxChannels[i]->m_muteModel.setValue( true );
}
}
void FxMixer::deactivateSolo()
{
for (int i = 1; i < m_fxChannels.size(); ++i)
{
m_fxChannels[i]->m_muteModel.setValue( m_fxChannels[i]->m_muteBeforeSolo );
}
}
void FxMixer::toggledSolo()
{
int soloedChan = -1;
bool resetSolo = m_lastSoloed != -1;
//untoggle if lastsoloed is entered
if (resetSolo)
{
m_fxChannels[m_lastSoloed]->m_soloModel.setValue( false );
}
//determine the soloed channel
for (int i = 0; i < m_fxChannels.size(); ++i)
{
if (m_fxChannels[i]->m_soloModel.value() == true)
soloedChan = i;
}
// if no channel is soloed, unmute everything, else mute everything
if (soloedChan != -1)
{
if (resetSolo)
{
deactivateSolo();
activateSolo();
} else {
activateSolo();
}
// unmute the soloed chan and every channel it sends to
m_fxChannels[soloedChan]->unmuteForSolo();
} else {
deactivateSolo();
}
m_lastSoloed = soloedChan;
}
void FxMixer::deleteChannel( int index )
{
// channel deletion is performed between mixer rounds
Engine::mixer()->requestChangeInModel();
// go through every instrument and adjust for the channel index change
TrackContainer::TrackList tracks;
tracks += Engine::getSong()->tracks();
tracks += Engine::getBBTrackContainer()->tracks();
for( Track* t : tracks )
{
if( t->type() == Track::InstrumentTrack )
{
InstrumentTrack* inst = dynamic_cast<InstrumentTrack *>( t );
int val = inst->effectChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's fx send
// send to master
inst->effectChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
inst->effectChannelModel()->setValue(val-1);
}
}
else if( t->type() == Track::SampleTrack )
{
SampleTrack* strk = dynamic_cast<SampleTrack *>( t );
int val = strk->effectChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's fx send
// send to master
strk->effectChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
strk->effectChannelModel()->setValue(val-1);
}
}
}
FxChannel * ch = m_fxChannels[index];
// delete all of this channel's sends and receives
while( ! ch->m_sends.isEmpty() )
{
deleteChannelSend( ch->m_sends.first() );
}
while( ! ch->m_receives.isEmpty() )
{
deleteChannelSend( ch->m_receives.first() );
}
// actually delete the channel
m_fxChannels.remove(index);
delete ch;
for( int i = index; i < m_fxChannels.size(); ++i )
{
validateChannelName( i, i + 1 );
// set correct channel index
m_fxChannels[i]->m_channelIndex = i;
// now check all routes and update names of the send models
for( FxRoute * r : m_fxChannels[i]->m_sends )
{
r->updateName();
}
for( FxRoute * r : m_fxChannels[i]->m_receives )
{
r->updateName();
}
}
Engine::mixer()->doneChangeInModel();
}
void FxMixer::moveChannelLeft( int index )
{
// can't move master or first channel
if( index <= 1 || index >= m_fxChannels.size() )
{
return;
}
// channels to swap
int a = index - 1, b = index;
// go through every instrument and adjust for the channel index change
QVector<Track *> songTrackList = Engine::getSong()->tracks();
QVector<Track *> bbTrackList = Engine::getBBTrackContainer()->tracks();
QVector<Track *> trackLists[] = {songTrackList, bbTrackList};
for(int tl=0; tl<2; ++tl)
{
QVector<Track *> trackList = trackLists[tl];
for(int i=0; i<trackList.size(); ++i)
{
if( trackList[i]->type() == Track::InstrumentTrack )
{
InstrumentTrack * inst = (InstrumentTrack *) trackList[i];
int val = inst->effectChannelModel()->value(0);
if( val == a )
{
inst->effectChannelModel()->setValue(b);
}
else if( val == b )
{
inst->effectChannelModel()->setValue(a);
}
}
else if( trackList[i]->type() == Track::SampleTrack )
{
SampleTrack * strk = (SampleTrack *) trackList[i];
int val = strk->effectChannelModel()->value(0);
if( val == a )
{
strk->effectChannelModel()->setValue(b);
}
else if( val == b )
{
strk->effectChannelModel()->setValue(a);
}
}
}
}
// Swap positions in array
qSwap(m_fxChannels[index], m_fxChannels[index - 1]);
// Update m_channelIndex of both channels
m_fxChannels[index]->m_channelIndex = index;
m_fxChannels[index - 1]->m_channelIndex = index -1;
}
void FxMixer::moveChannelRight( int index )
{
moveChannelLeft( index + 1 );
}
FxRoute * FxMixer::createChannelSend( fx_ch_t fromChannel, fx_ch_t toChannel,
float amount )
{
// qDebug( "requested: %d to %d", fromChannel, toChannel );
// find the existing connection
FxChannel * from = m_fxChannels[fromChannel];
FxChannel * to = m_fxChannels[toChannel];
for( int i=0; i<from->m_sends.size(); ++i )
{
if( from->m_sends[i]->receiver() == to )
{
// simply adjust the amount
from->m_sends[i]->amount()->setValue( amount );
return from->m_sends[i];
}
}
// connection does not exist. create a new one
return createRoute( from, to, amount );
}
FxRoute * FxMixer::createRoute( FxChannel * from, FxChannel * to, float amount )
{
if( from == to )
{
return NULL;
}
Engine::mixer()->requestChangeInModel();
FxRoute * route = new FxRoute( from, to, amount );
// add us to from's sends
from->m_sends.append( route );
// add us to to's receives
to->m_receives.append( route );
// add us to fxmixer's list
Engine::fxMixer()->m_fxRoutes.append( route );
Engine::mixer()->doneChangeInModel();
return route;
}
// delete the connection made by createChannelSend
void FxMixer::deleteChannelSend( fx_ch_t fromChannel, fx_ch_t toChannel )
{
// delete the send
FxChannel * from = m_fxChannels[fromChannel];
FxChannel * to = m_fxChannels[toChannel];
// find and delete the send entry
for( int i = 0; i < from->m_sends.size(); ++i )
{
if( from->m_sends[i]->receiver() == to )
{
deleteChannelSend( from->m_sends[i] );
break;
}
}
}
void FxMixer::deleteChannelSend( FxRoute * route )
{
Engine::mixer()->requestChangeInModel();
// remove us from from's sends
route->sender()->m_sends.remove( route->sender()->m_sends.indexOf( route ) );
// remove us from to's receives
route->receiver()->m_receives.remove( route->receiver()->m_receives.indexOf( route ) );
// remove us from fxmixer's list
Engine::fxMixer()->m_fxRoutes.remove( Engine::fxMixer()->m_fxRoutes.indexOf( route ) );
delete route;
Engine::mixer()->doneChangeInModel();
}
bool FxMixer::isInfiniteLoop( fx_ch_t sendFrom, fx_ch_t sendTo )
{
if( sendFrom == sendTo ) return true;
FxChannel * from = m_fxChannels[sendFrom];
FxChannel * to = m_fxChannels[sendTo];
bool b = checkInfiniteLoop( from, to );
return b;
}
bool FxMixer::checkInfiniteLoop( FxChannel * from, FxChannel * to )
{
// can't send master to anything
if( from == m_fxChannels[0] )
{
return true;
}
// can't send channel to itself
if( from == to )
{
return true;
}
// follow sendTo's outputs recursively looking for something that sends
// to sendFrom
for( int i=0; i < to->m_sends.size(); ++i )
{
if( checkInfiniteLoop( from, to->m_sends[i]->receiver() ) )
{
return true;
}
}
return false;
}
// how much does fromChannel send its output to the input of toChannel?
FloatModel * FxMixer::channelSendModel( fx_ch_t fromChannel, fx_ch_t toChannel )
{
if( fromChannel == toChannel )
{
return NULL;
}
const FxChannel * from = m_fxChannels[fromChannel];
const FxChannel * to = m_fxChannels[toChannel];
for( FxRoute * route : from->m_sends )
{
if( route->receiver() == to )
{
return route->amount();
}
}
return NULL;
}
void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch )
{
if( m_fxChannels[_ch]->m_muteModel.value() == false )
{
m_fxChannels[_ch]->m_lock.lock();
MixHelpers::add( m_fxChannels[_ch]->m_buffer, _buf, Engine::mixer()->framesPerPeriod() );
m_fxChannels[_ch]->m_hasInput = true;
m_fxChannels[_ch]->m_lock.unlock();
}
}
void FxMixer::prepareMasterMix()
{
BufferManager::clear( m_fxChannels[0]->m_buffer,
Engine::mixer()->framesPerPeriod() );
}
void FxMixer::masterMix( sampleFrame * _buf )
{
const int fpp = Engine::mixer()->framesPerPeriod();
// add the channels that have no dependencies (no incoming senders, ie.
// no receives) to the jobqueue. The channels that have receives get
// added when their senders get processed, which is detected by
// dependency counting.
// also instantly add all muted channels as they don't need to care
// about their senders, and can just increment the deps of their
// recipients right away.
MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic );
for( FxChannel * ch : m_fxChannels )
{
ch->m_muted = ch->m_muteModel.value();
if( ch->m_muted ) // instantly "process" muted channels
{
ch->processed();
ch->done();
}
else if( ch->m_receives.size() == 0 )
{
ch->m_queued = true;
MixerWorkerThread::addJob( ch );
}
}
while (m_fxChannels[0]->state() != ThreadableJob::ProcessingState::Done)
{
bool found = false;
for( FxChannel * ch : m_fxChannels )
{
const auto s = ch->state();
if (s == ThreadableJob::ProcessingState::Queued
|| s == ThreadableJob::ProcessingState::InProgress)
{
found = true;
break;
}
}
if( !found )
{
break;
}
MixerWorkerThread::startAndWaitForJobs();
}
// handle sample-exact data in master volume fader
ValueBuffer * volBuf = m_fxChannels[0]->m_volumeModel.valueBuffer();
if( volBuf )
{
for( int f = 0; f < fpp; f++ )
{
m_fxChannels[0]->m_buffer[f][0] *= volBuf->values()[f];
m_fxChannels[0]->m_buffer[f][1] *= volBuf->values()[f];
}
}
const float v = volBuf
? 1.0f
: m_fxChannels[0]->m_volumeModel.value();
MixHelpers::addSanitizedMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp );
// clear all channel buffers and
// reset channel process state
for( int i = 0; i < numChannels(); ++i)
{
BufferManager::clear( m_fxChannels[i]->m_buffer,
Engine::mixer()->framesPerPeriod() );
m_fxChannels[i]->reset();
m_fxChannels[i]->m_queued = false;
// also reset hasInput
m_fxChannels[i]->m_hasInput = false;
m_fxChannels[i]->m_dependenciesMet = 0;
}
}
void FxMixer::clear()
{
while( m_fxChannels.size() > 1 )
{
deleteChannel(1);
}
clearChannel(0);
}
void FxMixer::clearChannel(fx_ch_t index)
{
FxChannel * ch = m_fxChannels[index];
ch->m_fxChain.clear();
ch->m_volumeModel.setValue( 1.0f );
ch->m_muteModel.setValue( false );
ch->m_soloModel.setValue( false );
ch->m_name = ( index == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( index );
ch->m_volumeModel.setDisplayName( ch->m_name + ">" + tr( "Volume" ) );
ch->m_muteModel.setDisplayName( ch->m_name + ">" + tr( "Mute" ) );
ch->m_soloModel.setDisplayName( ch->m_name + ">" + tr( "Solo" ) );
// send only to master
if( index > 0)
{
// delete existing sends
while( ! ch->m_sends.isEmpty() )
{
deleteChannelSend( ch->m_sends.first() );
}
// add send to master
createChannelSend( index, 0 );
}
// delete receives
while( ! ch->m_receives.isEmpty() )
{
deleteChannelSend( ch->m_receives.first() );
}
}
void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
// save channels
for( int i = 0; i < m_fxChannels.size(); ++i )
{
FxChannel * ch = m_fxChannels[i];
QDomElement fxch = _doc.createElement( QString( "fxchannel" ) );
_this.appendChild( fxch );
ch->m_fxChain.saveState( _doc, fxch );
ch->m_volumeModel.saveSettings( _doc, fxch, "volume" );
ch->m_muteModel.saveSettings( _doc, fxch, "muted" );
ch->m_soloModel.saveSettings( _doc, fxch, "soloed" );
fxch.setAttribute( "num", i );
fxch.setAttribute( "name", ch->m_name );
// add the channel sends
for( int si = 0; si < ch->m_sends.size(); ++si )
{
QDomElement sendsDom = _doc.createElement( QString( "send" ) );
fxch.appendChild( sendsDom );
sendsDom.setAttribute( "channel", ch->m_sends[si]->receiverIndex() );
ch->m_sends[si]->amount()->saveSettings( _doc, sendsDom, "amount" );
}
}
}
// make sure we have at least num channels
void FxMixer::allocateChannelsTo(int num)
{
while( num > m_fxChannels.size() - 1 )
{
createChannel();
// delete the default send to master
deleteChannelSend( m_fxChannels.size()-1, 0 );
}
}
void FxMixer::loadSettings( const QDomElement & _this )
{
clear();
QDomNode node = _this.firstChild();
while( ! node.isNull() )
{
QDomElement fxch = node.toElement();
// index of the channel we are about to load
int num = fxch.attribute( "num" ).toInt();
// allocate enough channels
allocateChannelsTo( num );
m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" );
m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" );
m_fxChannels[num]->m_soloModel.loadSettings( fxch, "soloed" );
m_fxChannels[num]->m_name = fxch.attribute( "name" );
m_fxChannels[num]->m_fxChain.restoreState( fxch.firstChildElement(
m_fxChannels[num]->m_fxChain.nodeName() ) );
// mixer sends
QDomNodeList chData = fxch.childNodes();
for( unsigned int i=0; i<chData.length(); ++i )
{
QDomElement chDataItem = chData.at(i).toElement();
if( chDataItem.nodeName() == QString( "send" ) )
{
int sendTo = chDataItem.attribute( "channel" ).toInt();
allocateChannelsTo( sendTo ) ;
FxRoute * fxr = createChannelSend( num, sendTo, 1.0f );
if( fxr ) fxr->amount()->loadSettings( chDataItem, "amount" );
}
}
node = node.nextSibling();
}
emit dataChanged();
}
void FxMixer::validateChannelName( int index, int oldIndex )
{
if( m_fxChannels[index]->m_name == tr( "FX %1" ).arg( oldIndex ) )
{
m_fxChannels[index]->m_name = tr( "FX %1" ).arg( index );
}
}

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

@@ -23,14 +23,22 @@
*/
#include "Instrument.h"
#include "InstrumentTrack.h"
#include <cmath>
#include "DummyInstrument.h"
#include "InstrumentTrack.h"
#include "lmms_constants.h"
namespace lmms
{
Instrument::Instrument(InstrumentTrack * _instrument_track,
const Descriptor * _descriptor,
const Descriptor::SubPluginFeatures::Key *key) :
Plugin(_descriptor, NULL/* _instrument_track*/, key),
Plugin(_descriptor, nullptr/* _instrument_track*/, key),
m_instrumentTrack( _instrument_track )
{
}
@@ -78,13 +86,101 @@ bool Instrument::isFromTrack( const Track * _track ) const
return( m_instrumentTrack == _track );
}
// helper function for Instrument::applyFadeIn
static int countZeroCrossings(sampleFrame *buf, fpp_t start, fpp_t frames)
{
// zero point crossing counts of all channels
int zeroCrossings[DEFAULT_CHANNELS] = {0};
// maximum zero point crossing of all channels
int maxZeroCrossings = 0;
// determine the zero point crossing counts
for (fpp_t f = start; f < frames; ++f)
{
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
{
// we don't want to count [-1, 0, 1] as two crossings
if ((buf[f - 1][ch] <= 0.0 && buf[f][ch] > 0.0) ||
(buf[f - 1][ch] >= 0.0 && buf[f][ch] < 0.0))
{
++zeroCrossings[ch];
if (zeroCrossings[ch] > maxZeroCrossings)
{
maxZeroCrossings = zeroCrossings[ch];
}
}
}
}
return maxZeroCrossings;
}
// helper function for Instrument::applyFadeIn
fpp_t getFadeInLength(float maxLength, fpp_t frames, int zeroCrossings)
{
// calculate the length of the fade in
// Length is inversely proportional to the max of zeroCrossings,
// because for low frequencies, we need a longer fade in to
// prevent clicking.
return (fpp_t) (maxLength / ((float) zeroCrossings / ((float) frames / 128.0f) + 1.0f));
}
void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n)
{
const static float MAX_FADE_IN_LENGTH = 85.0;
f_cnt_t total = n->totalFramesPlayed();
if (total == 0)
{
const fpp_t frames = n->framesLeftForCurrentPeriod();
const f_cnt_t offset = n->offset();
// We need to skip the first sample because it almost always
// produces a zero crossing; it's not helpful while
// determining the fade in length. Hence 1
int maxZeroCrossings = countZeroCrossings(buf, offset + 1, offset + frames);
fpp_t length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, maxZeroCrossings);
n->m_fadeInLength = length;
// apply fade in
length = length < frames ? length : frames;
for (fpp_t f = 0; f < length; ++f)
{
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
{
buf[offset + f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) f / (float) n->m_fadeInLength);
}
}
}
else if (total < n->m_fadeInLength)
{
const fpp_t frames = n->framesLeftForCurrentPeriod();
int new_zc = countZeroCrossings(buf, 1, frames);
fpp_t new_length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, new_zc);
for (fpp_t f = 0; f < frames; ++f)
{
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
{
float currentLength = n->m_fadeInLength * (1.0f - (float) f / frames) + new_length * ((float) f / frames);
buf[f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) (total + f) / currentLength);
if (total + f >= currentLength)
{
n->m_fadeInLength = currentLength;
return;
}
}
}
n->m_fadeInLength = new_length;
}
}
void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n )
{
const fpp_t frames = _n->framesLeftForCurrentPeriod();
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();
const f_cnt_t fl = _n->framesLeft();
if( fl <= desiredReleaseFrames()+fpp )
{
@@ -111,4 +207,4 @@ QString Instrument::fullDisplayName() const
}
} // namespace lmms

View File

@@ -25,12 +25,14 @@
#include <QDomElement>
#include "InstrumentFunctions.h"
#include "AudioEngine.h"
#include "embed.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "PresetPreviewPlayHandle.h"
#include "stdshims.h"
namespace lmms
{
InstrumentFunctionNoteStacking::ChordTable::Init InstrumentFunctionNoteStacking::ChordTable::s_initTable[] =
@@ -177,11 +179,9 @@ bool InstrumentFunctionNoteStacking::Chord::hasSemiTone( int8_t semi_tone ) cons
InstrumentFunctionNoteStacking::ChordTable::ChordTable() :
QVector<Chord>()
{
for( int i = 0;
i < static_cast<int>( sizeof s_initTable / sizeof *s_initTable );
i++ )
for (const auto& chord : s_initTable)
{
push_back( Chord( s_initTable[i].m_name, s_initTable[i].m_semiTones ) );
push_back(Chord(chord.m_name, chord.m_semiTones));
}
}
@@ -219,10 +219,6 @@ InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent
InstrumentFunctionNoteStacking::~InstrumentFunctionNoteStacking()
{
}
@@ -262,7 +258,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n )
// create sub-note-play-handle, only note is
// different
Engine::mixer()->addPlayHandle(
Engine::audioEngine()->addPlayHandle(
NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy,
_n, -1, NotePlayHandle::OriginNoteStacking )
);
@@ -302,6 +298,7 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
m_arpEnabledModel( false ),
m_arpModel( this, tr( "Arpeggio type" ) ),
m_arpRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Arpeggio range" ) ),
m_arpRepeatsModel( 1.0f, 1.0f, 8.0f, 1.0f, this, tr( "Note repeats" ) ),
m_arpCycleModel( 0.0f, 0.0f, 6.0f, 1.0f, this, tr( "Cycle steps" ) ),
m_arpSkipModel( 0.0f, 0.0f, 100.0f, 1.0f, this, tr( "Skip rate" ) ),
m_arpMissModel( 0.0f, 0.0f, 100.0f, 1.0f, this, tr( "Miss rate" ) ),
@@ -316,26 +313,21 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) :
m_arpModel.addItem( chord_table[i].getName() );
}
m_arpDirectionModel.addItem( tr( "Up" ), make_unique<PixmapLoader>( "arp_up" ) );
m_arpDirectionModel.addItem( tr( "Down" ), make_unique<PixmapLoader>( "arp_down" ) );
m_arpDirectionModel.addItem( tr( "Up and down" ), make_unique<PixmapLoader>( "arp_up_and_down" ) );
m_arpDirectionModel.addItem( tr( "Down and up" ), make_unique<PixmapLoader>( "arp_up_and_down" ) );
m_arpDirectionModel.addItem( tr( "Random" ), make_unique<PixmapLoader>( "arp_random" ) );
m_arpDirectionModel.addItem( tr( "Up" ), std::make_unique<PixmapLoader>( "arp_up" ) );
m_arpDirectionModel.addItem( tr( "Down" ), std::make_unique<PixmapLoader>( "arp_down" ) );
m_arpDirectionModel.addItem( tr( "Up and down" ), std::make_unique<PixmapLoader>( "arp_up_and_down" ) );
m_arpDirectionModel.addItem( tr( "Down and up" ), std::make_unique<PixmapLoader>( "arp_up_and_down" ) );
m_arpDirectionModel.addItem( tr( "Random" ), std::make_unique<PixmapLoader>( "arp_random" ) );
m_arpDirectionModel.setInitValue( ArpDirUp );
m_arpModeModel.addItem( tr( "Free" ), make_unique<PixmapLoader>( "arp_free" ) );
m_arpModeModel.addItem( tr( "Sort" ), make_unique<PixmapLoader>( "arp_sort" ) );
m_arpModeModel.addItem( tr( "Sync" ), make_unique<PixmapLoader>( "arp_sync" ) );
m_arpModeModel.addItem( tr( "Free" ), std::make_unique<PixmapLoader>( "arp_free" ) );
m_arpModeModel.addItem( tr( "Sort" ), std::make_unique<PixmapLoader>( "arp_sort" ) );
m_arpModeModel.addItem( tr( "Sync" ), std::make_unique<PixmapLoader>( "arp_sync" ) );
}
InstrumentFunctionArpeggio::~InstrumentFunctionArpeggio()
{
}
void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
@@ -370,12 +362,12 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance();
const int cur_chord_size = chord_table[selected_arp].size();
const int range = (int)( cur_chord_size * m_arpRangeModel.value() );
const int range = static_cast<int>(cur_chord_size * m_arpRangeModel.value() * m_arpRepeatsModel.value());
const int total_range = range * cnphv.size();
// number of frames that every note should be played
const f_cnt_t arp_frames = (f_cnt_t)( m_arpTimeModel.value() / 1000.0f * Engine::mixer()->processingSampleRate() );
const f_cnt_t gated_frames = (f_cnt_t)( m_arpGateModel.value() * arp_frames / 100.0f );
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->processingSampleRate());
const auto gated_frames = (f_cnt_t)(m_arpGateModel.value() * arp_frames / 100.0f);
// used for calculating remaining frames for arp-note, we have to add
// arp_frames-1, otherwise the first arp-note will not be setup
@@ -386,11 +378,13 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
// used for loop
f_cnt_t frames_processed = ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->noteOffset() : _n->noteOffset();
while( frames_processed < Engine::mixer()->framesPerPeriod() )
while( frames_processed < Engine::audioEngine()->framesPerPeriod() )
{
const f_cnt_t remaining_frames_for_cur_arp = arp_frames - ( cur_frame % arp_frames );
// does current arp-note fill whole audio-buffer?
if( remaining_frames_for_cur_arp > Engine::mixer()->framesPerPeriod() )
// does current arp-note fill whole audio-buffer or is the remaining time just
// a short bit that we can discard?
if( remaining_frames_for_cur_arp > Engine::audioEngine()->framesPerPeriod() ||
_n->frames() - _n->totalFramesPlayed() < arp_frames / 5 )
{
// then we don't have to do something!
break;
@@ -478,11 +472,14 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
cur_arp_idx = (int)( range * ( (float) rand() / (float) RAND_MAX ) );
}
// Divide cur_arp_idx with wanted repeats. The repeat feature will not affect random notes.
cur_arp_idx = static_cast<int>(cur_arp_idx / m_arpRepeatsModel.value());
// Cycle notes
if( m_arpCycleModel.value() && dir != ArpDirRandom )
{
cur_arp_idx *= m_arpCycleModel.value() + 1;
cur_arp_idx %= range;
cur_arp_idx %= static_cast<int>( range / m_arpRepeatsModel.value() );
}
// now calculate final key for our arp-note
@@ -492,7 +489,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
// range-checking
if( sub_note_key >= NumKeys ||
sub_note_key < 0 ||
Engine::mixer()->criticalXRuns() )
Engine::audioEngine()->criticalXRuns() )
{
continue;
}
@@ -501,11 +498,11 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
// create sub-note-play-handle, only ptr to note is different
// and is_arp_note=true
Engine::mixer()->addPlayHandle(
Engine::audioEngine()->addPlayHandle(
NotePlayHandleManager::acquire( _n->instrumentTrack(),
frames_processed,
gated_frames,
Note( MidiTime( 0 ), MidiTime( 0 ), sub_note_key, _n->getVolume(),
Note( TimePos( 0 ), TimePos( 0 ), sub_note_key, _n->getVolume(),
_n->getPanning(), _n->detuning() ),
_n, -1, NotePlayHandle::OriginArpeggio )
);
@@ -524,6 +521,7 @@ void InstrumentFunctionArpeggio::saveSettings( QDomDocument & _doc, QDomElement
m_arpEnabledModel.saveSettings( _doc, _this, "arp-enabled" );
m_arpModel.saveSettings( _doc, _this, "arp" );
m_arpRangeModel.saveSettings( _doc, _this, "arprange" );
m_arpRepeatsModel.saveSettings( _doc, _this, "arprepeats" );
m_arpCycleModel.saveSettings( _doc, _this, "arpcycle" );
m_arpSkipModel.saveSettings( _doc, _this, "arpskip" );
m_arpMissModel.saveSettings( _doc, _this, "arpmiss" );
@@ -541,6 +539,7 @@ void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this )
m_arpEnabledModel.loadSettings( _this, "arp-enabled" );
m_arpModel.loadSettings( _this, "arp" );
m_arpRangeModel.loadSettings( _this, "arprange" );
m_arpRepeatsModel.loadSettings( _this, "arprepeats" );
m_arpCycleModel.loadSettings( _this, "arpcycle" );
m_arpSkipModel.loadSettings( _this, "arpskip" );
m_arpMissModel.loadSettings( _this, "arpmiss" );
@@ -549,3 +548,6 @@ void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this )
m_arpDirectionModel.loadSettings( _this, "arpdir" );
m_arpModeModel.loadSettings( _this, "arpmode" );
}
} // namespace lmms

View File

@@ -26,9 +26,16 @@
#include "InstrumentPlayHandle.h"
#include "InstrumentTrack.h"
namespace lmms
{
InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) :
PlayHandle( TypeInstrumentPlayHandle ),
m_instrument( instrument )
{
setAudioPort( instrumentTrack->audioPort() );
}
} // namespace lmms

View File

@@ -22,18 +22,20 @@
*
*/
#include <QtCore/QVarLengthArray>
#include <QVarLengthArray>
#include <QDomElement>
#include "InstrumentSoundShaping.h"
#include "AudioEngine.h"
#include "BasicFilters.h"
#include "embed.h"
#include "Engine.h"
#include "EnvelopeAndLfoParameters.h"
#include "Instrument.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "stdshims.h"
namespace lmms
{
const float CUT_FREQ_MULTIPLIER = 6000.0f;
@@ -43,16 +45,14 @@ const float RES_PRECISION = 1000.0f;
// names for env- and lfo-targets - first is name being displayed to user
// and second one is used internally, e.g. for saving/restoring settings
const QString InstrumentSoundShaping::targetNames[InstrumentSoundShaping::NumTargets][3] =
const char *const InstrumentSoundShaping::targetNames[InstrumentSoundShaping::NumTargets][3] =
{
{ InstrumentSoundShaping::tr( "VOLUME" ), "vol",
InstrumentSoundShaping::tr( "Volume" ) },
/* InstrumentSoundShaping::tr( "Pan" ),
InstrumentSoundShaping::tr( "Pitch" ),*/
{ InstrumentSoundShaping::tr( "CUTOFF" ), "cut",
InstrumentSoundShaping::tr( "Cutoff frequency" ) },
{ InstrumentSoundShaping::tr( "RESO" ), "res",
InstrumentSoundShaping::tr( "Resonance" ) }
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "VOLUME"), "vol",
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Volume") },
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "CUTOFF"), "cut",
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Cutoff frequency") },
{ QT_TRANSLATE_NOOP("InstrumentSoundShaping", "RESO"), "res",
QT_TRANSLATE_NOOP("InstrumentSoundShaping", "Resonance") }
} ;
@@ -77,41 +77,36 @@ InstrumentSoundShaping::InstrumentSoundShaping(
value_for_zero_amount,
this );
m_envLfoParameters[i]->setDisplayName(
tr( targetNames[i][2].toUtf8().constData() ) );
tr( targetNames[i][2] ) );
}
m_filterModel.addItem( tr( "Low-pass" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "Hi-pass" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Band-pass csg" ), make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "Band-pass czpg" ), make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "Notch" ), make_unique<PixmapLoader>( "filter_notch" ) );
m_filterModel.addItem( tr( "All-pass" ), make_unique<PixmapLoader>( "filter_ap" ) );
m_filterModel.addItem( tr( "Moog" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "2x Low-pass" ), make_unique<PixmapLoader>( "filter_2lp" ) );
m_filterModel.addItem( tr( "RC Low-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "RC Band-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "RC High-pass 12 dB/oct" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "RC Low-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "RC Band-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "RC High-pass 24 dB/oct" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Vocal Formant" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "2x Moog" ), make_unique<PixmapLoader>( "filter_2lp" ) );
m_filterModel.addItem( tr( "SV Low-pass" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "SV Band-pass" ), make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "SV High-pass" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "SV Notch" ), make_unique<PixmapLoader>( "filter_notch" ) );
m_filterModel.addItem( tr( "Fast Formant" ), make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Tripole" ), make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "Low-pass" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "Hi-pass" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Band-pass csg" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "Band-pass czpg" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "Notch" ), std::make_unique<PixmapLoader>( "filter_notch" ) );
m_filterModel.addItem( tr( "All-pass" ), std::make_unique<PixmapLoader>( "filter_ap" ) );
m_filterModel.addItem( tr( "Moog" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "2x Low-pass" ), std::make_unique<PixmapLoader>( "filter_2lp" ) );
m_filterModel.addItem( tr( "RC Low-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "RC Band-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "RC High-pass 12 dB/oct" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "RC Low-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "RC Band-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "RC High-pass 24 dB/oct" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Vocal Formant" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "2x Moog" ), std::make_unique<PixmapLoader>( "filter_2lp" ) );
m_filterModel.addItem( tr( "SV Low-pass" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
m_filterModel.addItem( tr( "SV Band-pass" ), std::make_unique<PixmapLoader>( "filter_bp" ) );
m_filterModel.addItem( tr( "SV High-pass" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "SV Notch" ), std::make_unique<PixmapLoader>( "filter_notch" ) );
m_filterModel.addItem( tr( "Fast Formant" ), std::make_unique<PixmapLoader>( "filter_hp" ) );
m_filterModel.addItem( tr( "Tripole" ), std::make_unique<PixmapLoader>( "filter_lp" ) );
}
InstrumentSoundShaping::~InstrumentSoundShaping()
{
}
float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame )
@@ -120,7 +115,7 @@ float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t fram
if( n->isReleased() == false )
{
envReleaseBegin += Engine::mixer()->framesPerPeriod();
envReleaseBegin += Engine::audioEngine()->framesPerPeriod();
}
float level;
@@ -163,7 +158,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
if( n->m_filter == nullptr )
{
n->m_filter = make_unique<BasicFilters<>>( Engine::mixer()->processingSampleRate() );
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->processingSampleRate() );
}
n->m_filter->setFilterType( m_filterModel.value() );
@@ -380,4 +375,4 @@ void InstrumentSoundShaping::loadSettings( const QDomElement & _this )
} // namespace lmms

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,15 +27,18 @@
#include "Ladspa2LMMS.h"
namespace lmms
{
Ladspa2LMMS::Ladspa2LMMS()
{
l_sortable_plugin_t plugins = getSortedPlugins();
for( l_sortable_plugin_t::iterator it = plugins.begin();
it != plugins.end(); ++it )
for (const auto& plugin : plugins)
{
ladspa_key_t key = (*it).second;
ladspaManagerDescription * desc = getDescription( key );
ladspa_key_t key = plugin.second;
LadspaManagerDescription * desc = getDescription( key );
if( desc->type == SOURCE )
{
@@ -78,12 +81,6 @@ Ladspa2LMMS::Ladspa2LMMS()
Ladspa2LMMS::~Ladspa2LMMS()
{
}
QString Ladspa2LMMS::getShortName( const ladspa_key_t & _key )
{
QString name = getName( _key );
@@ -126,3 +123,6 @@ QString Ladspa2LMMS::getShortName( const ladspa_key_t & _key )
return name;
}
} // namespace lmms

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,8 +46,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
{
if( m_link )
{
connect( &m_linkEnabledModel, SIGNAL( dataChanged() ),
this, SLOT( linkStateChanged() ),
connect( &m_linkEnabledModel, SIGNAL(dataChanged()),
this, SLOT(linkStateChanged()),
Qt::DirectConnection );
}
@@ -51,8 +56,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
case TOGGLED:
m_toggledModel.setInitValue(
static_cast<bool>( m_port->def ) );
connect( &m_toggledModel, SIGNAL( dataChanged() ),
this, SLOT( ledChanged() ) );
connect( &m_toggledModel, SIGNAL(dataChanged()),
this, SLOT(ledChanged()));
if( m_port->def == 1.0f )
{
m_toggledModel.setValue( true );
@@ -69,8 +74,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
m_port->min ) / 400 );
m_knobModel.setInitValue(
static_cast<int>( m_port->def ) );
connect( &m_knobModel, SIGNAL( dataChanged() ),
this, SLOT( knobChanged() ) );
connect( &m_knobModel, SIGNAL(dataChanged()),
this, SLOT(knobChanged()));
// TODO: careful: we must prevent saved scales
m_knobModel.setScaleLogarithmic( m_port->suggests_logscale );
break;
@@ -80,10 +85,10 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
( m_port->max - m_port->min )
/ ( m_port->name.toUpper() == "GAIN"
&& m_port->max == 10.0f ? 4000.0f :
( m_port->suggests_logscale ? 8000.0f : 800.0f ) ) );
( m_port->suggests_logscale ? 8000000.0f : 800000.0f ) ) );
m_knobModel.setInitValue( m_port->def );
connect( &m_knobModel, SIGNAL( dataChanged() ),
this, SLOT( knobChanged() ) );
connect( &m_knobModel, SIGNAL(dataChanged()),
this, SLOT(knobChanged()));
// TODO: careful: we must prevent saved scales
m_knobModel.setScaleLogarithmic( m_port->suggests_logscale );
break;
@@ -93,8 +98,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
( m_port->max -
m_port->min ) / 800.0f );
m_tempoSyncKnobModel.setInitValue( m_port->def );
connect( &m_tempoSyncKnobModel, SIGNAL( dataChanged() ),
this, SLOT( tempoKnobChanged() ) );
connect( &m_tempoSyncKnobModel, SIGNAL(dataChanged()),
this, SLOT(tempoKnobChanged()));
// TODO: careful: we must prevent saved scales
m_tempoSyncKnobModel.setScaleLogarithmic( m_port->suggests_logscale );
break;
@@ -107,13 +112,6 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port,
LadspaControl::~LadspaControl()
{
}
LADSPA_Data LadspaControl::value()
{
switch( m_port->data_type )
@@ -142,7 +140,7 @@ ValueBuffer * LadspaControl::valueBuffer()
case TOGGLED:
case INTEGER:
case ENUM:
return NULL;
return nullptr;
case FLOATING:
return m_knobModel.valueBuffer();
case TIME:
@@ -152,7 +150,7 @@ ValueBuffer * LadspaControl::valueBuffer()
break;
}
return NULL;
return nullptr;
}
@@ -377,5 +375,4 @@ void LadspaControl::setLink( bool _state )
}
} // namespace lmms

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,17 +59,15 @@ LadspaManager::LadspaManager()
ladspaDirectories.push_back( "/Library/Audio/Plug-Ins/LADSPA" );
#endif
for( QStringList::iterator it = ladspaDirectories.begin();
it != ladspaDirectories.end(); ++it )
for (const auto& ladspaDirectory : ladspaDirectories)
{
QDir directory( ( *it ) );
// Skip empty entries as QDir will interpret it as the working directory
if (ladspaDirectory.isEmpty()) { continue; }
QDir directory(ladspaDirectory);
QFileInfoList list = directory.entryInfoList();
for( QFileInfoList::iterator file = list.begin();
file != list.end(); ++file )
for (const auto& f : list)
{
const QFileInfo & f = *file;
if( !f.isFile() ||
f.fileName().right( 3 ).toLower() !=
if(!f.isFile() || f.fileName().right( 3 ).toLower() !=
#ifdef LMMS_BUILD_WIN32
"dll"
#else
@@ -81,10 +82,8 @@ LadspaManager::LadspaManager()
if( plugin_lib.load() == true )
{
LADSPA_Descriptor_Function descriptorFunction =
( LADSPA_Descriptor_Function ) plugin_lib.resolve(
"ladspa_descriptor" );
if( descriptorFunction != NULL )
auto descriptorFunction = (LADSPA_Descriptor_Function)plugin_lib.resolve("ladspa_descriptor");
if( descriptorFunction != nullptr )
{
addPlugins( descriptorFunction,
f.fileName() );
@@ -98,10 +97,9 @@ LadspaManager::LadspaManager()
}
l_ladspa_key_t keys = m_ladspaManagerMap.keys();
for( l_ladspa_key_t::iterator it = keys.begin();
it != keys.end(); ++it )
for (const auto& key : keys)
{
m_sortedPlugins.append( qMakePair( getName( *it ), *it ) );
m_sortedPlugins.append(qMakePair(getName(key), key));
}
std::sort( m_sortedPlugins.begin(), m_sortedPlugins.end() );
}
@@ -111,7 +109,7 @@ LadspaManager::LadspaManager()
LadspaManager::~LadspaManager()
{
for( ladspaManagerMapType::iterator it = m_ladspaManagerMap.begin();
for( LadspaManagerMapType::iterator it = m_ladspaManagerMap.begin();
it != m_ladspaManagerMap.end(); ++it )
{
delete it.value();
@@ -121,7 +119,7 @@ LadspaManager::~LadspaManager()
ladspaManagerDescription * LadspaManager::getDescription(
LadspaManagerDescription * LadspaManager::getDescription(
const ladspa_key_t & _plugin )
{
if( m_ladspaManagerMap.contains( _plugin ) )
@@ -130,7 +128,7 @@ ladspaManagerDescription * LadspaManager::getDescription(
}
else
{
return( NULL );
return( nullptr );
}
}
@@ -144,7 +142,7 @@ void LadspaManager::addPlugins(
const LADSPA_Descriptor * descriptor;
for( long pluginIndex = 0;
( descriptor = _descriptor_func( pluginIndex ) ) != NULL;
( descriptor = _descriptor_func( pluginIndex ) ) != nullptr;
++pluginIndex )
{
ladspa_key_t key( _file, QString( descriptor->Label ) );
@@ -153,8 +151,7 @@ void LadspaManager::addPlugins(
continue;
}
ladspaManagerDescription * plugIn =
new ladspaManagerDescription;
auto plugIn = new LadspaManagerDescription;
plugIn->descriptorFunction = _descriptor_func;
plugIn->index = pluginIndex;
plugIn->inputChannels = getPluginInputs( descriptor );
@@ -242,7 +239,7 @@ const LADSPA_PortDescriptor* LadspaManager::getPortDescriptor(const ladspa_key_t
{
return( & descriptor->PortDescriptors[_port] );
}
return( NULL );
return( nullptr );
}
const LADSPA_PortRangeHint *LadspaManager::getPortRangeHint(const ladspa_key_t &_plugin, uint32_t _port)
@@ -252,7 +249,7 @@ const LADSPA_PortRangeHint *LadspaManager::getPortRangeHint(const ladspa_key_t &
{
return( & descriptor->PortRangeHints[_port] );
}
return( NULL );
return( nullptr );
}
@@ -563,7 +560,7 @@ const void * LadspaManager::getImplementationData(
const ladspa_key_t & _plugin )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
return( descriptor ? descriptor->ImplementationData : NULL );
return( descriptor ? descriptor->ImplementationData : nullptr );
}
@@ -583,7 +580,7 @@ const LADSPA_Descriptor * LadspaManager::getDescriptor(
}
else
{
return( NULL );
return( nullptr );
}
}
@@ -597,7 +594,7 @@ LADSPA_Handle LadspaManager::instantiate(
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
return( descriptor ?
( descriptor->instantiate )( descriptor, _sample_rate ) :
NULL );
nullptr );
}
@@ -609,7 +606,7 @@ bool LadspaManager::connectPort( const ladspa_key_t & _plugin,
LADSPA_Data * _data_location )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->connect_port != NULL &&
if( descriptor && descriptor->connect_port != nullptr &&
_port < getPortCount( _plugin ) )
{
( descriptor->connect_port )
@@ -626,7 +623,7 @@ bool LadspaManager::activate( const ladspa_key_t & _plugin,
LADSPA_Handle _instance )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->activate != NULL )
if( descriptor && descriptor->activate != nullptr )
{
( descriptor->activate ) ( _instance );
return( true );
@@ -642,7 +639,7 @@ bool LadspaManager::run( const ladspa_key_t & _plugin,
uint32_t _sample_count )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->run!= NULL )
if( descriptor && descriptor->run!= nullptr )
{
( descriptor->run ) ( _instance, _sample_count );
return( true );
@@ -658,8 +655,8 @@ bool LadspaManager::runAdding( const ladspa_key_t & _plugin,
uint32_t _sample_count )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->run_adding!= NULL
&& descriptor->set_run_adding_gain != NULL )
if( descriptor && descriptor->run_adding!= nullptr
&& descriptor->set_run_adding_gain != nullptr )
{
( descriptor->run_adding ) ( _instance, _sample_count );
return( true );
@@ -675,8 +672,8 @@ bool LadspaManager::setRunAddingGain( const ladspa_key_t & _plugin,
LADSPA_Data _gain )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->run_adding!= NULL
&& descriptor->set_run_adding_gain != NULL )
if( descriptor && descriptor->run_adding!= nullptr
&& descriptor->set_run_adding_gain != nullptr )
{
( descriptor->set_run_adding_gain ) ( _instance, _gain );
return( true );
@@ -691,7 +688,7 @@ bool LadspaManager::deactivate( const ladspa_key_t & _plugin,
LADSPA_Handle _instance )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->deactivate!= NULL )
if( descriptor && descriptor->deactivate!= nullptr )
{
( descriptor->deactivate ) ( _instance );
return( true );
@@ -706,10 +703,13 @@ bool LadspaManager::cleanup( const ladspa_key_t & _plugin,
LADSPA_Handle _instance )
{
const LADSPA_Descriptor * descriptor = getDescriptor( _plugin );
if( descriptor && descriptor->cleanup!= NULL )
if( descriptor && descriptor->cleanup!= nullptr )
{
( descriptor->cleanup ) ( _instance );
return( true );
}
return( false );
}
} // namespace lmms

View File

@@ -24,12 +24,15 @@
*/
#include <QDomElement>
#include <QObject>
#include "Song.h"
#include "Mixer.h"
#include "LfoController.h"
#include "AudioEngine.h"
#include "Song.h"
namespace lmms
{
LfoController::LfoController( Model * _parent ) :
@@ -48,20 +51,20 @@ LfoController::LfoController( Model * _parent ) :
m_userDefSampleBuffer( new SampleBuffer )
{
setSampleExact( true );
connect( &m_waveModel, SIGNAL( dataChanged() ),
this, SLOT( updateSampleFunction() ), Qt::DirectConnection );
connect( &m_waveModel, SIGNAL(dataChanged()),
this, SLOT(updateSampleFunction()), Qt::DirectConnection );
connect( &m_speedModel, SIGNAL( dataChanged() ),
this, SLOT( updateDuration() ), Qt::DirectConnection );
connect( &m_multiplierModel, SIGNAL( dataChanged() ),
this, SLOT( updateDuration() ), Qt::DirectConnection );
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ),
this, SLOT( updateDuration() ) );
connect( &m_speedModel, SIGNAL(dataChanged()),
this, SLOT(updateDuration()), Qt::DirectConnection );
connect( &m_multiplierModel, SIGNAL(dataChanged()),
this, SLOT(updateDuration()), Qt::DirectConnection );
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()),
this, SLOT(updateDuration()));
connect( Engine::getSong(), SIGNAL( playbackStateChanged() ),
this, SLOT( updatePhase() ) );
connect( Engine::getSong(), SIGNAL( playbackPositionChanged() ),
this, SLOT( updatePhase() ) );
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
this, SLOT(updatePhase()));
connect( Engine::getSong(), SIGNAL(playbackPositionChanged()),
this, SLOT(updatePhase()));
updateDuration();
}
@@ -91,7 +94,7 @@ void LfoController::updateValueBuffer()
if( m_bufferLastUpdated < s_periods )
{
int diff = s_periods - m_bufferLastUpdated;
phase += static_cast<float>( Engine::mixer()->framesPerPeriod() * diff ) / m_duration;
phase += static_cast<float>( Engine::audioEngine()->framesPerPeriod() * diff ) / m_duration;
m_bufferLastUpdated += diff;
}
@@ -102,7 +105,7 @@ void LfoController::updateValueBuffer()
for( float& f : m_valueBuffer )
{
const float currentSample = m_sampleFunction != NULL
const float currentSample = m_sampleFunction != nullptr
? m_sampleFunction( phase )
: m_userDefSampleBuffer->userWaveSample( phase );
@@ -125,7 +128,7 @@ void LfoController::updatePhase()
void LfoController::updateDuration()
{
float newDurationF = Engine::mixer()->processingSampleRate() * m_speedModel.value();
float newDurationF = Engine::audioEngine()->processingSampleRate() * m_speedModel.value();
switch(m_multiplierModel.value() )
{
@@ -170,7 +173,7 @@ void LfoController::updateSampleFunction()
m_sampleFunction = &Oscillator::noiseSample;
break;
case Oscillator::UserDefinedWave:
m_sampleFunction = NULL;
m_sampleFunction = nullptr;
/*TODO: If C++11 is allowed, should change the type of
m_sampleFunction be std::function<sample_t(const float)>
and use the line below:
@@ -221,12 +224,10 @@ QString LfoController::nodeName() const
ControllerDialog * LfoController::createDialog( QWidget * _parent )
gui::ControllerDialog * LfoController::createDialog( QWidget * _parent )
{
return new LfoControllerDialog( this, _parent );
return new gui::LfoControllerDialog( this, _parent );
}
} // namespace lmms

View File

@@ -0,0 +1,184 @@
/*
* LinkedModelGroups.cpp - base classes for groups of linked models
*
* Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QDomDocument>
#include <QDomElement>
#include "LinkedModelGroups.h"
#include "AutomatableModel.h"
namespace lmms
{
/*
LinkedModelGroup
*/
void LinkedModelGroup::linkControls(LinkedModelGroup *other)
{
foreach_model([&other](const std::string& id, ModelInfo& inf)
{
auto itr2 = other->m_models.find(id);
Q_ASSERT(itr2 != other->m_models.end());
AutomatableModel::linkModels(inf.m_model, itr2->second.m_model);
});
}
void LinkedModelGroup::saveValues(QDomDocument &doc, QDomElement &that)
{
foreach_model([&doc, &that](const std::string& , ModelInfo& inf)
{
inf.m_model->saveSettings(doc, that, /*m_models[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
});
}
void LinkedModelGroup::loadValues(const QDomElement &that)
{
foreach_model([&that](const std::string& , ModelInfo& inf)
{
// try to load, if it fails, this will load a sane initial value
inf.m_model->loadSettings(that, /*m_models()[idx].m_name*/ inf.m_name); /* TODO: m_name useful */
});
}
void LinkedModelGroup::addModel(AutomatableModel *model, const QString &name)
{
model->setObjectName(name);
m_models.emplace(std::string(name.toUtf8().data()), ModelInfo(name, model));
connect(model, &AutomatableModel::destroyed,
this, [this, model](jo_id_t){
if(containsModel(model->objectName()))
{
emit modelRemoved(model);
eraseModel(model->objectName());
}
},
Qt::DirectConnection);
// View needs to create another child view, e.g. a new knob:
emit modelAdded(model);
emit dataChanged();
}
void LinkedModelGroup::removeControl(AutomatableModel* mdl)
{
if(containsModel(mdl->objectName()))
{
emit modelRemoved(mdl);
eraseModel(mdl->objectName());
}
}
bool LinkedModelGroup::eraseModel(const QString& name)
{
return m_models.erase(name.toStdString()) > 0;
}
void LinkedModelGroup::clearModels()
{
m_models.clear();
}
bool LinkedModelGroup::containsModel(const QString &name) const
{
return m_models.find(name.toStdString()) != m_models.end();
}
/*
LinkedModelGroups
*/
void LinkedModelGroups::linkAllModels()
{
LinkedModelGroup* first = getGroup(0);
LinkedModelGroup* cur;
for (std::size_t i = 1; (cur = getGroup(i)); ++i)
{
first->linkControls(cur);
}
}
void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that)
{
LinkedModelGroup* grp0 = getGroup(0);
if (grp0)
{
QDomElement models = doc.createElement("models");
that.appendChild(models);
grp0->saveValues(doc, models);
}
else { /* don't even add a "models" node */ }
}
void LinkedModelGroups::loadSettings(const QDomElement& that)
{
QDomElement models = that.firstChildElement("models");
LinkedModelGroup* grp0;
if (!models.isNull() && (grp0 = getGroup(0)))
{
// only load the first group, the others are linked to the first
grp0->loadValues(models);
}
}
} // namespace lmms

View File

@@ -25,7 +25,7 @@
#include "LocklessAllocator.h"
#include <algorithm>
#include <stdio.h>
#include <cstdio>
#include "lmmsconfig.h"
@@ -33,6 +33,9 @@
#include <strings.h>
#endif
namespace lmms
{
static const size_t SIZEOF_SET = sizeof( int ) * 8;
@@ -113,7 +116,7 @@ void * LocklessAllocator::alloc()
if( !available )
{
fprintf( stderr, "LocklessAllocator: No free space\n" );
return NULL;
return nullptr;
}
}
while (!m_available.compare_exchange_weak(available, available - 1));
@@ -162,3 +165,6 @@ invalid:
}
++m_available;
}
} // namespace lmms

View File

@@ -21,11 +21,15 @@
*
*/
#include <stdlib.h>
#include <cstdlib>
#include "lmms_basics.h"
#include "MemoryHelper.h"
namespace lmms
{
/**
* Allocate a number of bytes and return them.
* @param byteNum is the number of bytes
@@ -33,14 +37,14 @@
void* MemoryHelper::alignedMalloc( size_t byteNum )
{
char *ptr, *ptr2, *aligned_ptr;
int align_mask = ALIGN_SIZE - 1;
int align_mask = LMMS_ALIGN_SIZE - 1;
ptr = static_cast<char*>( malloc( byteNum + ALIGN_SIZE + sizeof( int ) ) );
ptr = static_cast<char*>( malloc( byteNum + LMMS_ALIGN_SIZE + sizeof( int ) ) );
if( ptr == NULL ) return NULL;
if( ptr == nullptr ) return nullptr;
ptr2 = ptr + sizeof( int );
aligned_ptr = ptr2 + ( ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
aligned_ptr = ptr2 + ( LMMS_ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
ptr2 = aligned_ptr - sizeof( int );
*( ( int* ) ptr2 ) = ( int )( aligned_ptr - ptr );
@@ -63,3 +67,5 @@ void MemoryHelper::alignedFree( void* _buffer )
}
}
} // namespace lmms

View File

@@ -25,9 +25,13 @@
#include "MemoryManager.h"
#include <QtCore/QtGlobal>
#include <QtGlobal>
#include "rpmalloc.h"
namespace lmms
{
/// Global static object handling rpmalloc intializing and finalizing
struct MemoryManagerGlobalGuard {
MemoryManagerGlobalGuard() {
@@ -53,7 +57,7 @@ MemoryManager::ThreadGuard::ThreadGuard()
MemoryManager::ThreadGuard::~ThreadGuard()
{
if (--thread_guard_depth == 0) {
rpmalloc_thread_finalize();
rpmalloc_thread_finalize(true);
}
}
@@ -75,3 +79,6 @@ void MemoryManager::free(void * ptr)
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::free", "Thread not initialized");
return rpfree(ptr);
}
} // namespace lmms

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

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 && index < MaxScaleCount)
{
m_scaleModel.replaceItem(index,
QString::number(index) + ": " + Engine::getSong()->getScale(index)->getDescription());
}
else
{
for (int i = 0; i < MaxScaleCount; i++)
{
m_scaleModel.replaceItem(i,
QString::number(i) + ": " + Engine::getSong()->getScale(i)->getDescription());
}
}
}
/**
* \brief Update keymap name displayed in the microtuner scale list.
* \param index Index of the keymap to update; update all keymaps if -1 or out of range.
*/
void Microtuner::updateKeymapList(int index)
{
if (index >= 0 && index < MaxKeymapCount)
{
m_keymapModel.replaceItem(index,
QString::number(index) + ": " + Engine::getSong()->getKeymap(index)->getDescription());
}
else
{
for (int i = 0; i < MaxKeymapCount; i++)
{
m_keymapModel.replaceItem(i,
QString::number(i) + ": " + Engine::getSong()->getKeymap(i)->getDescription());
}
}
}
void Microtuner::saveSettings(QDomDocument &document, QDomElement &element)
{
m_enabledModel.saveSettings(document, element, "enabled");
m_scaleModel.saveSettings(document, element, "scale");
m_keymapModel.saveSettings(document, element, "keymap");
m_keyRangeImportModel.saveSettings(document, element, "range_import");
}
void Microtuner::loadSettings(const QDomElement &element)
{
m_enabledModel.loadSettings(element, "enabled");
m_scaleModel.loadSettings(element, "scale");
m_keymapModel.loadSettings(element, "keymap");
m_keyRangeImportModel.loadSettings(element, "range_import");
}
} // namespace lmms

View File

@@ -24,18 +24,21 @@
#include "MixHelpers.h"
#ifdef LMMS_DEBUG
#include <cstdio>
#endif
#include <cmath>
#include <QtGlobal>
#include "lmms_math.h"
#include "ValueBuffer.h"
#include <cstdio>
static bool s_NaNHandler;
namespace MixHelpers
namespace lmms::MixHelpers
{
/*! \brief Function for applying MIXOP on all sample frames */
@@ -99,9 +102,10 @@ bool sanitize( sampleFrame * src, int frames )
{
for( int c = 0; c < 2; ++c )
{
if( isinf( src[f][c] ) || isnan( src[f][c] ) )
if( std::isinf( src[f][c] ) || std::isnan( src[f][c] ) )
{
#ifdef LMMS_DEBUG
// TODO don't use printf here
printf("Bad data, clearing buffer. frame: ");
printf("%d: value %f\n", f, src[f][c]);
#endif
@@ -210,8 +214,8 @@ void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, f
for( int f = 0; f < frames; ++f )
{
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f];
dst[f][1] += ( isinf( src[f][1] ) || isnan( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f];
dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f];
dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f];
}
}
@@ -226,10 +230,10 @@ void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src,
for( int f = 0; f < frames; ++f )
{
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) )
dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) )
? 0.0f
: src[f][0] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f];
dst[f][1] += ( isinf( src[f][1] ) || isnan( src[f][1] ) )
dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) )
? 0.0f
: src[f][1] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f];
}
@@ -243,8 +247,8 @@ struct AddSanitizedMultipliedOp
void operator()( sampleFrame& dst, const sampleFrame& src ) const
{
dst[0] += ( isinf( src[0] ) || isnan( src[0] ) ) ? 0.0f : src[0] * m_coeff;
dst[1] += ( isinf( src[1] ) || isnan( src[1] ) ) ? 0.0f : src[1] * m_coeff;
dst[0] += ( std::isinf( src[0] ) || std::isnan( src[0] ) ) ? 0.0f : src[0] * m_coeff;
dst[1] += ( std::isinf( src[1] ) || std::isnan( src[1] ) ) ? 0.0f : src[1] * m_coeff;
}
const float m_coeff;
@@ -324,5 +328,5 @@ void multiplyAndAddMultipliedJoined( sampleFrame* dst,
run<>( dst, srcLeft, srcRight, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) );
}
}
} // namespace lmms::MixHelpers

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,8 @@
#include "Model.h"
namespace lmms
{
QString Model::fullDisplayName() const
{
@@ -46,5 +48,5 @@ QString Model::fullDisplayName() const
} // 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,13 +25,16 @@
#include <QDomElement>
#include <math.h>
#include <cmath>
#include "Note.h"
#include "DetuningHelper.h"
namespace lmms
{
Note::Note( const MidiTime & length, const MidiTime & pos,
Note::Note( const TimePos & length, const TimePos & pos,
int key, volume_t volume, panning_t panning,
DetuningHelper * detuning ) :
m_selected( false ),
@@ -44,7 +47,7 @@ Note::Note( const MidiTime & length, const MidiTime & pos,
m_panning( qBound( PanningLeft, panning, PanningRight ) ),
m_length( length ),
m_pos( pos ),
m_detuning( NULL )
m_detuning( nullptr )
{
if( detuning )
{
@@ -71,7 +74,7 @@ Note::Note( const Note & note ) :
m_panning( note.m_panning ),
m_length( note.m_length ),
m_pos( note.m_pos ),
m_detuning( NULL )
m_detuning( nullptr )
{
if( note.m_detuning )
{
@@ -93,7 +96,7 @@ Note::~Note()
void Note::setLength( const MidiTime & length )
void Note::setLength( const TimePos & length )
{
m_length = length;
}
@@ -101,7 +104,7 @@ void Note::setLength( const MidiTime & length )
void Note::setPos( const MidiTime & pos )
void Note::setPos( const TimePos & pos )
{
m_pos = pos;
}
@@ -136,7 +139,7 @@ void Note::setPanning( panning_t panning )
MidiTime Note::quantized( const MidiTime & m, const int qGrid )
TimePos Note::quantized( const TimePos & m, const int qGrid )
{
float p = ( (float) m / qGrid );
if( p - floorf( p ) < 0.5f )
@@ -208,12 +211,12 @@ void Note::loadSettings( const QDomElement & _this )
void Note::createDetuning()
{
if( m_detuning == NULL )
if( m_detuning == nullptr )
{
m_detuning = new DetuningHelper;
(void) m_detuning->automationPattern();
(void) m_detuning->automationClip();
m_detuning->setRange( -MaxDetuning, MaxDetuning, 0.5f );
m_detuning->automationPattern()->setProgressionType( AutomationPattern::LinearProgression );
m_detuning->automationClip()->setProgressionType( AutomationClip::LinearProgression );
}
}
@@ -232,3 +235,35 @@ bool Note::withinRange(int tickStart, int tickEnd) const
return pos().getTicks() >= tickStart && pos().getTicks() <= tickEnd
&& length().getTicks() != 0;
}
/*! \brief Get the start/end/bottom/top positions of notes in a vector
*
* Returns no value if there are no notes
*/
std::optional<NoteBounds> boundsForNotes(const NoteVector& notes)
{
if (notes.empty()) { return std::nullopt; }
TimePos start = notes.front()->pos();
TimePos end = start;
int lower = notes.front()->key();
int upper = lower;
for (const Note* note: notes)
{
// TODO should we assume that NoteVector is always sorted correctly,
// so first() always has the lowest time position?
start = std::min(start, note->pos());
end = std::max(end, note->endPos());
lower = std::min(lower, note->key());
upper = std::max(upper, note->key());
}
return NoteBounds{start, end, lower, upper};
}
} // namespace lmms

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 )
{
}
@@ -52,7 +55,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
Origin origin ) :
PlayHandle( TypeNotePlayHandle, _offset ),
Note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ),
m_pluginData( NULL ),
m_pluginData( nullptr ),
m_instrumentTrack( instrumentTrack ),
m_frames( 0 ),
m_totalFramesPlayed( 0 ),
@@ -63,16 +66,16 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
m_released( false ),
m_releaseStarted( false ),
m_hasMidiNote( false ),
m_hasParent( parent != NULL ),
m_hasParent( parent != nullptr ),
m_parent( parent ),
m_hadChildren( false ),
m_muted( false ),
m_bbTrack( NULL ),
m_patternTrack( nullptr ),
m_origTempo( Engine::getSong()->getTempo() ),
m_origBaseNote( instrumentTrack->baseNote() ),
m_frequency( 0 ),
m_unpitchedFrequency( 0 ),
m_baseDetuning( NULL ),
m_baseDetuning( nullptr ),
m_songGlobalParentOffset( 0 ),
m_midiChannel( midiEventChannel >= 0 ? midiEventChannel : instrumentTrack->midiPort()->realOutputChannel() ),
m_origin( origin ),
@@ -91,7 +94,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
parent->m_subNotes.push_back( this );
parent->m_hadChildren = true;
m_bbTrack = parent->m_bbTrack;
m_patternTrack = parent->m_patternTrack;
parent->setUsesBuffer( false );
}
@@ -106,7 +109,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
m_instrumentTrack->midiNoteOn( *this );
}
if( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed )
if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed )
{
setUsesBuffer( false );
}
@@ -132,14 +135,14 @@ NotePlayHandle::~NotePlayHandle()
m_parent->m_subNotes.removeOne( this );
}
if( m_pluginData != NULL )
if( m_pluginData != nullptr )
{
m_instrumentTrack->deleteNotePluginData( this );
}
if( m_instrumentTrack->m_notes[key()] == this )
{
m_instrumentTrack->m_notes[key()] = NULL;
m_instrumentTrack->m_notes[key()] = nullptr;
}
m_subNotes.clear();
@@ -182,21 +185,42 @@ int NotePlayHandle::midiKey() const
void NotePlayHandle::play( sampleFrame * _working_buffer )
{
if( m_muted )
if (m_muted)
{
return;
}
// if the note offset falls over to next period, then don't start playback yet
if( offset() >= Engine::mixer()->framesPerPeriod() )
if( offset() >= Engine::audioEngine()->framesPerPeriod() )
{
setOffset( offset() - Engine::mixer()->framesPerPeriod() );
setOffset( offset() - Engine::audioEngine()->framesPerPeriod() );
return;
}
lock();
if( m_totalFramesPlayed == 0 && !m_hasMidiNote
// Don't play the note if it falls outside of the user defined key range
// TODO: handle the range check by Microtuner, and if the key becomes "not mapped", save the current frequency
// so that the note release can finish playing using a valid frequency instead of a 1 Hz placeholder
if (key() < m_instrumentTrack->m_firstKeyModel.value() ||
key() > m_instrumentTrack->m_lastKeyModel.value())
{
// Release the note in case it started playing before going out of range
noteOff(0);
// Exit if the note did not start playing before going out of range (i.e. there is no need to play release)
if (m_totalFramesPlayed == 0)
{
unlock();
return;
}
}
/* It is possible for NotePlayHandle::noteOff to be called before NotePlayHandle::play,
* which results in a note-on message being sent without a subsequent note-off message.
* Therefore, we check here whether the note has already been released before sending
* the note-on message. */
if( !m_released
&& m_totalFramesPlayed == 0 && !m_hasMidiNote
&& ( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) )
{
m_hasMidiNote = true;
@@ -206,7 +230,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
// send MidiNoteOn event
m_instrumentTrack->processOutEvent(
MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ),
MidiTime::fromFrames( offset(), Engine::framesPerTick() ),
TimePos::fromFrames( offset(), Engine::framesPerTick() ),
offset() );
}
@@ -217,8 +241,8 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
// number of frames that can be played this period
f_cnt_t framesThisPeriod = m_totalFramesPlayed == 0
? Engine::mixer()->framesPerPeriod() - offset()
: Engine::mixer()->framesPerPeriod();
? Engine::audioEngine()->framesPerPeriod() - offset()
: Engine::audioEngine()->framesPerPeriod();
// check if we start release during this period
if( m_released == false &&
@@ -253,7 +277,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
// are inserted by arpAndChordsTabWidget::processNote()
if( ! m_subNotes.isEmpty() )
{
m_releaseFramesToDo = m_releaseFramesDone + 2 * Engine::mixer()->framesPerPeriod();
m_releaseFramesToDo = m_releaseFramesDone + 2 * Engine::audioEngine()->framesPerPeriod();
}
// look whether we have frames left to be done before release
if( m_framesBeforeRelease )
@@ -306,7 +330,7 @@ f_cnt_t NotePlayHandle::framesLeft() const
{
if( instrumentTrack()->isSustainPedalPressed() )
{
return 4*Engine::mixer()->framesPerPeriod();
return 4 * Engine::audioEngine()->framesPerPeriod();
}
else if( m_released && actualReleaseFramesToDo() == 0 )
{
@@ -326,9 +350,9 @@ fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const
{
if( m_totalFramesPlayed == 0 )
{
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::mixer()->framesPerPeriod() - offset() );
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::audioEngine()->framesPerPeriod() - offset() );
}
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::mixer()->framesPerPeriod() );
return (fpp_t) qMin<f_cnt_t>( framesLeft(), Engine::audioEngine()->framesPerPeriod() );
}
@@ -336,7 +360,7 @@ fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const
bool NotePlayHandle::isFromTrack( const Track * _track ) const
{
return m_instrumentTrack == _track || m_bbTrack == _track;
return m_instrumentTrack == _track || m_patternTrack == _track;
}
@@ -369,7 +393,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
// send MidiNoteOff event
m_instrumentTrack->processOutEvent(
MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ),
MidiTime::fromFrames( _s, Engine::framesPerTick() ),
TimePos::fromFrames( _s, Engine::framesPerTick() ),
_s );
}
@@ -378,7 +402,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s )
{
if( m_origin == OriginMidiInput )
{
setLength( MidiTime( static_cast<f_cnt_t>( totalFramesPlayed() / Engine::framesPerTick() ) ) );
setLength( TimePos( static_cast<f_cnt_t>( totalFramesPlayed() / Engine::framesPerTick() ) ) );
m_instrumentTrack->midiNoteOff( *this );
}
}
@@ -419,9 +443,9 @@ float NotePlayHandle::volumeLevel( const f_cnt_t _frame )
void NotePlayHandle::mute()
{
// mute all sub-notes
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
for (const auto& subNote : m_subNotes)
{
( *it )->mute();
subNote->mute();
}
m_muted = true;
}
@@ -431,12 +455,12 @@ void NotePlayHandle::mute()
int NotePlayHandle::index() const
{
const PlayHandleList & playHandles = Engine::mixer()->playHandles();
const PlayHandleList & playHandles = Engine::audioEngine()->playHandles();
int idx = 0;
for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it )
for (const auto& playHandle : playHandles)
{
const NotePlayHandle * nph = dynamic_cast<const NotePlayHandle *>( *it );
if( nph == NULL || nph->m_instrumentTrack != m_instrumentTrack || nph->isReleased() || nph->hasParent() )
const auto nph = dynamic_cast<const NotePlayHandle*>(playHandle);
if( nph == nullptr || nph->m_instrumentTrack != m_instrumentTrack || nph->isReleased() || nph->hasParent() )
{
continue;
}
@@ -454,13 +478,13 @@ int NotePlayHandle::index() const
ConstNotePlayHandleList NotePlayHandle::nphsOfInstrumentTrack( const InstrumentTrack * _it, bool _all_ph )
{
const PlayHandleList & playHandles = Engine::mixer()->playHandles();
const PlayHandleList & playHandles = Engine::audioEngine()->playHandles();
ConstNotePlayHandleList cnphv;
for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it )
for (const auto& playHandle : playHandles)
{
const NotePlayHandle * nph = dynamic_cast<const NotePlayHandle *>( *it );
if( nph != NULL && nph->m_instrumentTrack == _it && ( ( nph->isReleased() == false && nph->hasParent() == false ) || _all_ph == true ) )
const auto nph = dynamic_cast<const NotePlayHandle*>(playHandle);
if( nph != nullptr && nph->m_instrumentTrack == _it && ( ( nph->isReleased() == false && nph->hasParent() == false ) || _all_ph == true ) )
{
cnphv.push_back( nph );
}
@@ -495,30 +519,49 @@ bool NotePlayHandle::operator==( const NotePlayHandle & _nph ) const
void NotePlayHandle::updateFrequency()
{
int mp = m_instrumentTrack->m_useMasterPitchModel.value() ? Engine::getSong()->masterPitch() : 0;
const float pitch =
( key() -
m_instrumentTrack->baseNoteModel()->value() +
mp +
m_baseDetuning->value() )
/ 12.0f;
m_frequency = BaseFreq * powf( 2.0f, pitch + m_instrumentTrack->pitchModel()->value() / ( 100 * 12.0f ) );
m_unpitchedFrequency = BaseFreq * powf( 2.0f, pitch );
int masterPitch = m_instrumentTrack->m_useMasterPitchModel.value() ? Engine::getSong()->masterPitch() : 0;
int baseNote = m_instrumentTrack->baseNoteModel()->value();
float detune = m_baseDetuning->value();
float instrumentPitch = m_instrumentTrack->pitchModel()->value();
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
if (m_instrumentTrack->m_microtuner.enabled())
{
( *it )->updateFrequency();
// custom key mapping and scale: get frequency from the microtuner
const auto transposedKey = key() + masterPitch;
if (m_instrumentTrack->isKeyMapped(transposedKey))
{
const auto frequency = m_instrumentTrack->m_microtuner.keyToFreq(transposedKey, baseNote);
m_frequency = frequency * powf(2.f, (detune + instrumentPitch / 100) / 12.f);
m_unpitchedFrequency = frequency * powf(2.f, detune / 12.f);
}
else
{
m_frequency = m_unpitchedFrequency = 0;
}
}
else
{
// default key mapping and 12-TET frequency computation with default 440 Hz base note frequency
const float pitch = (key() - baseNote + masterPitch + detune) / 12.0f;
m_frequency = DefaultBaseFreq * powf(2.0f, pitch + instrumentPitch / (100 * 12.0f));
m_unpitchedFrequency = DefaultBaseFreq * powf(2.0f, pitch);
}
for (auto it : m_subNotes)
{
it->updateFrequency();
}
}
void NotePlayHandle::processMidiTime( const MidiTime& time )
void NotePlayHandle::processTimePos( const TimePos& time )
{
if( detuning() && time >= songGlobalParentOffset()+pos() )
{
const float v = detuning()->automationPattern()->valueAt( time - songGlobalParentOffset() - pos() );
const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() );
if( !typeInfo<float>::isEqual( v, m_baseDetuning->value() ) )
{
m_baseDetuning->setValue( v );
@@ -532,14 +575,23 @@ void NotePlayHandle::processMidiTime( const MidiTime& time )
void NotePlayHandle::resize( const bpm_t _new_tempo )
{
if (origin() == OriginMidiInput ||
(origin() == OriginNoteStacking && m_parent->origin() == OriginMidiInput))
{
// Don't resize notes from MIDI input - they should continue to play
// until the key is released, and their large duration can cause
// overflows in this method.
return;
}
double completed = m_totalFramesPlayed / (double) m_frames;
double new_frames = m_origFrames * m_origTempo / (double) _new_tempo;
m_frames = (f_cnt_t)new_frames;
m_totalFramesPlayed = (f_cnt_t)( completed * new_frames );
for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it )
for (const auto& subNote : m_subNotes)
{
( *it )->resize( _new_tempo );
subNote->resize(_new_tempo);
}
}
@@ -552,9 +604,9 @@ int NotePlayHandleManager::s_size;
void NotePlayHandleManager::init()
{
s_available = MM_ALLOC( NotePlayHandle*, INITIAL_NPH_CACHE );
s_available = MM_ALLOC<NotePlayHandle*>( INITIAL_NPH_CACHE );
NotePlayHandle * n = MM_ALLOC( NotePlayHandle, INITIAL_NPH_CACHE );
auto n = MM_ALLOC<NotePlayHandle>(INITIAL_NPH_CACHE);
for( int i=0; i < INITIAL_NPH_CACHE; ++i )
{
@@ -597,11 +649,11 @@ void NotePlayHandleManager::release( NotePlayHandle * nph )
void NotePlayHandleManager::extend( int c )
{
s_size += c;
NotePlayHandle ** tmp = MM_ALLOC( NotePlayHandle*, s_size );
auto tmp = MM_ALLOC<NotePlayHandle*>(s_size);
MM_FREE( s_available );
s_available = tmp;
NotePlayHandle * n = MM_ALLOC( NotePlayHandle, c );
auto n = MM_ALLOC<NotePlayHandle>(c);
for( int i=0; i < c; ++i )
{
@@ -609,3 +661,11 @@ void NotePlayHandleManager::extend( int c )
++n;
}
}
void NotePlayHandleManager::free()
{
MM_FREE(s_available);
}
} // namespace lmms

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,71 +25,290 @@
#include "Oscillator.h"
#include <algorithm>
#if !defined(__MINGW32__) && !defined(__MINGW64__)
#include <thread>
#endif
#include "BufferManager.h"
#include "Engine.h"
#include "Mixer.h"
#include "AudioEngine.h"
#include "AutomatableModel.h"
#include "fftw3.h"
#include "fft_helpers.h"
namespace lmms
{
Oscillator::Oscillator( const IntModel * _wave_shape_model,
const IntModel * _mod_algo_model,
const float & _freq,
const float & _detuning,
const float & _phase_offset,
const float & _volume,
Oscillator * _sub_osc ) :
m_waveShapeModel( _wave_shape_model ),
m_modulationAlgoModel( _mod_algo_model ),
m_freq( _freq ),
m_detuning( _detuning ),
m_volume( _volume ),
m_ext_phaseOffset( _phase_offset ),
m_subOsc( _sub_osc ),
m_phaseOffset( _phase_offset ),
m_phase( _phase_offset ),
m_userWave( NULL )
void Oscillator::waveTableInit()
{
createFFTPlans();
generateWaveTables();
// The oscillator FFT plans remain throughout the application lifecycle
// due to being expensive to create, and being used whenever a userwave form is changed
// deleted in main.cpp main()
}
Oscillator::Oscillator(const IntModel *wave_shape_model,
const IntModel *mod_algo_model,
const float &freq,
const float &detuning_div_samplerate,
const float &phase_offset,
const float &volume,
Oscillator *sub_osc) :
m_waveShapeModel(wave_shape_model),
m_modulationAlgoModel(mod_algo_model),
m_freq(freq),
m_detuning_div_samplerate(detuning_div_samplerate),
m_volume(volume),
m_ext_phaseOffset(phase_offset),
m_subOsc(sub_osc),
m_phaseOffset(phase_offset),
m_phase(phase_offset),
m_userWave(nullptr),
m_useWaveTable(false),
m_isModulator(false)
{
}
void Oscillator::update( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator)
{
if( m_freq >= Engine::mixer()->processingSampleRate() / 2 )
if (m_freq >= Engine::audioEngine()->processingSampleRate() / 2)
{
BufferManager::clear( _ab, _frames );
BufferManager::clear(ab, frames);
return;
}
if( m_subOsc != NULL )
// If this oscillator is used to PM or PF modulate another oscillator, take a note.
// The sampling functions will check this variable and avoid using band-limited
// wavetables, since they contain ringing that would lead to unexpected results.
m_isModulator = modulator;
if (m_subOsc != nullptr)
{
switch( m_modulationAlgoModel->value() )
switch (m_modulationAlgoModel->value())
{
case PhaseModulation:
updatePM( _ab, _frames, _chnl );
updatePM(ab, frames, chnl);
break;
case AmplitudeModulation:
updateAM( _ab, _frames, _chnl );
updateAM(ab, frames, chnl);
break;
case SignalMix:
updateMix( _ab, _frames, _chnl );
updateMix(ab, frames, chnl);
break;
case SynchronizedBySubOsc:
updateSync( _ab, _frames, _chnl );
updateSync(ab, frames, chnl);
break;
case FrequencyModulation:
updateFM( _ab, _frames, _chnl );
updateFM(ab, frames, chnl);
}
}
else
{
updateNoSub( _ab, _frames, _chnl );
updateNoSub(ab, frames, chnl);
}
}
void Oscillator::generateSawWaveTable(int bands, sample_t* table, int firstBand)
{
// sawtooth wave contain both even and odd harmonics
// hence sinewaves are added for all bands
// https://en.wikipedia.org/wiki/Sawtooth_wave
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
{
// add offset to the position index to match phase of the non-wavetable saw wave; precompute "/ period"
const float imod = (i - OscillatorConstants::WAVETABLE_LENGTH / 2.f) / OscillatorConstants::WAVETABLE_LENGTH;
for (int n = firstBand; n <= bands; n++)
{
table[i] += (n % 2 ? 1.0f : -1.0f) / n * sinf(F_2PI * n * imod) / F_PI_2;
}
}
}
void Oscillator::generateTriangleWaveTable(int bands, sample_t* table, int firstBand)
{
// triangle waves contain only odd harmonics
// hence sinewaves are added for alternate bands
// https://en.wikipedia.org/wiki/Triangle_wave
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
{
for (int n = firstBand | 1; n <= bands; n += 2)
{
table[i] += (n & 2 ? -1.0f : 1.0f) / powf(n, 2.0f) *
sinf(F_2PI * n * i / (float)OscillatorConstants::WAVETABLE_LENGTH) / (F_PI_SQR / 8);
}
}
}
void Oscillator::generateSquareWaveTable(int bands, sample_t* table, int firstBand)
{
// square waves only contain odd harmonics,
// at diffrent levels when compared to triangle waves
// https://en.wikipedia.org/wiki/Square_wave
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; i++)
{
for (int n = firstBand | 1; n <= bands; n += 2)
{
table[i] += (1.0f / n) * sinf(F_2PI * i * n / OscillatorConstants::WAVETABLE_LENGTH) / (F_PI / 4);
}
}
}
// Expects waveform converted to frequency domain to be present in the spectrum buffer
void Oscillator::generateFromFFT(int bands, sample_t* table)
{
// Keep only specified number of bands, set the rest to zero.
// Add a +1 offset to the requested number of bands, since the first "useful" frequency falls into bin 1.
// I.e., for bands = 1, keeping just bin 0 (center 0 Hz, +- 4 Hz) makes no sense, it would not produce any tone.
for (int i = bands + 1; i < OscillatorConstants::WAVETABLE_LENGTH * 2 - bands; i++)
{
s_specBuf[i][0] = 0.0f;
s_specBuf[i][1] = 0.0f;
}
//ifft
fftwf_execute(s_ifftPlan);
//normalize and copy to result buffer
normalize(s_sampleBuffer, table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1);
}
void Oscillator::generateAntiAliasUserWaveTable(SampleBuffer *sampleBuffer)
{
if (sampleBuffer->m_userAntiAliasWaveTable == nullptr) {return;}
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
{
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
{
s_sampleBuffer[i] = sampleBuffer->userWaveSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
}
fftwf_execute(s_fftPlan);
Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*(sampleBuffer->m_userAntiAliasWaveTable))[i].data());
}
}
sample_t Oscillator::s_waveTables
[Oscillator::WaveShapes::NumWaveShapeTables]
[OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT]
[OscillatorConstants::WAVETABLE_LENGTH];
fftwf_plan Oscillator::s_fftPlan;
fftwf_plan Oscillator::s_ifftPlan;
fftwf_complex * Oscillator::s_specBuf;
float Oscillator::s_sampleBuffer[OscillatorConstants::WAVETABLE_LENGTH];
void Oscillator::createFFTPlans()
{
Oscillator::s_specBuf = ( fftwf_complex * ) fftwf_malloc( ( OscillatorConstants::WAVETABLE_LENGTH * 2 + 1 ) * sizeof( fftwf_complex ) );
Oscillator::s_fftPlan = fftwf_plan_dft_r2c_1d(OscillatorConstants::WAVETABLE_LENGTH, s_sampleBuffer, s_specBuf, FFTW_MEASURE );
Oscillator::s_ifftPlan = fftwf_plan_dft_c2r_1d(OscillatorConstants::WAVETABLE_LENGTH, s_specBuf, s_sampleBuffer, FFTW_MEASURE);
// initialize s_specBuf content to zero, since the values are used in a condition inside generateFromFFT()
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH * 2 + 1; i++)
{
s_specBuf[i][0] = 0.0f;
s_specBuf[i][1] = 0.0f;
}
}
void Oscillator::destroyFFTPlans()
{
fftwf_destroy_plan(s_fftPlan);
fftwf_destroy_plan(s_ifftPlan);
fftwf_free(s_specBuf);
}
void Oscillator::generateWaveTables()
{
// Generate tables for simple shaped (constructed by summing sine waves).
// Start from the table that contains the least number of bands, and re-use each table in the following
// iteration, adding more bands in each step and avoiding repeated computation of earlier bands.
using generator_t = void (*)(int, sample_t*, int);
auto simpleGen = [](WaveShapes shape, generator_t generator)
{
const int shapeID = shape - FirstWaveShapeTable;
int lastBands = 0;
// Clear the first wave table
std::fill(
std::begin(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]),
std::end(s_waveTables[shapeID][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1]),
0.f);
for (int i = OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT - 1; i >= 0; i--)
{
const int bands = OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i);
generator(bands, s_waveTables[shapeID][i], lastBands + 1);
lastBands = bands;
if (i)
{
std::copy(
s_waveTables[shapeID][i],
s_waveTables[shapeID][i] + OscillatorConstants::WAVETABLE_LENGTH,
s_waveTables[shapeID][i - 1]);
}
}
};
// FFT-based wave shapes: make standard wave table without band limit, convert to frequency domain, remove bands
// above maximum frequency and convert back to time domain.
auto fftGen = []()
{
// Generate moogSaw tables
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
{
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
{
Oscillator::s_sampleBuffer[i] = moogSawSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
}
fftwf_execute(s_fftPlan);
generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable][i]);
}
// Generate exponential tables
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
{
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
{
s_sampleBuffer[i] = expSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
}
fftwf_execute(s_fftPlan);
generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable][i]);
}
};
// TODO: Mingw compilers currently do not support std::thread. There are some 3rd-party workarounds available,
// but since threading is not essential in this case, it is easier and more reliable to simply generate
// the wavetables serially. Remove the the check and #else branch once std::thread is well supported.
#if !defined(__MINGW32__) && !defined(__MINGW64__)
std::thread sawThread(simpleGen, WaveShapes::SawWave, generateSawWaveTable);
std::thread squareThread(simpleGen, WaveShapes::SquareWave, generateSquareWaveTable);
std::thread triangleThread(simpleGen, WaveShapes::TriangleWave, generateTriangleWaveTable);
std::thread fftThread(fftGen);
sawThread.join();
squareThread.join();
triangleThread.join();
fftThread.join();
#else
simpleGen(WaveShapes::SawWave, generateSawWaveTable);
simpleGen(WaveShapes::SquareWave, generateSquareWaveTable);
simpleGen(WaveShapes::TriangleWave, generateTriangleWaveTable);
fftGen();
#endif
}
void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
@@ -316,8 +536,7 @@ inline void Oscillator::recalcPhase()
m_phaseOffset = m_ext_phaseOffset;
m_phase += m_phaseOffset;
}
m_phase = absFraction( m_phase )+2; // make sure we're not running
// negative when doing PM
m_phase = absFraction( m_phase );
}
@@ -337,12 +556,12 @@ inline bool Oscillator::syncOk( float _osc_coeff )
float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
if( m_subOsc != NULL )
if( m_subOsc != nullptr )
{
m_subOsc->update( _ab, _frames, _chnl );
}
recalcPhase();
return( m_freq * m_detuning );
return m_freq * m_detuning_div_samplerate;
}
@@ -354,7 +573,7 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float osc_coeff = m_freq * m_detuning_div_samplerate;
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -371,9 +590,9 @@ template<Oscillator::WaveShapes W>
void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
m_subOsc->update( _ab, _frames, _chnl );
m_subOsc->update( _ab, _frames, _chnl, true );
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float osc_coeff = m_freq * m_detuning_div_samplerate;
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -392,9 +611,9 @@ template<Oscillator::WaveShapes W>
void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
m_subOsc->update( _ab, _frames, _chnl );
m_subOsc->update( _ab, _frames, _chnl, false );
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float osc_coeff = m_freq * m_detuning_div_samplerate;
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -411,9 +630,9 @@ template<Oscillator::WaveShapes W>
void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
m_subOsc->update( _ab, _frames, _chnl );
m_subOsc->update( _ab, _frames, _chnl, false );
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float osc_coeff = m_freq * m_detuning_div_samplerate;
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -433,7 +652,7 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames,
{
const float sub_osc_coeff = m_subOsc->syncInit( _ab, _frames, _chnl );
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float osc_coeff = m_freq * m_detuning_div_samplerate;
for( fpp_t frame = 0; frame < _frames ; ++frame )
{
@@ -454,11 +673,10 @@ template<Oscillator::WaveShapes W>
void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
const ch_cnt_t _chnl )
{
m_subOsc->update( _ab, _frames, _chnl );
m_subOsc->update( _ab, _frames, _chnl, true );
recalcPhase();
const float osc_coeff = m_freq * m_detuning;
const float sampleRateCorrection = 44100.0f /
Engine::mixer()->processingSampleRate();
const float osc_coeff = m_freq * m_detuning_div_samplerate;
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->processingSampleRate();
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -472,10 +690,18 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
template<>
inline sample_t Oscillator::getSample<Oscillator::SineWave>(
const float _sample )
inline sample_t Oscillator::getSample<Oscillator::SineWave>(const float sample)
{
return( sinSample( _sample ) );
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate();
if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ)
{
return sinSample(sample);
}
else
{
return 0;
}
}
@@ -483,9 +709,16 @@ inline sample_t Oscillator::getSample<Oscillator::SineWave>(
template<>
inline sample_t Oscillator::getSample<Oscillator::TriangleWave>(
const float _sample )
const float _sample )
{
return( triangleSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(s_waveTables[WaveShapes::TriangleWave - FirstWaveShapeTable],_sample);
}
else
{
return triangleSample(_sample);
}
}
@@ -493,9 +726,16 @@ inline sample_t Oscillator::getSample<Oscillator::TriangleWave>(
template<>
inline sample_t Oscillator::getSample<Oscillator::SawWave>(
const float _sample )
const float _sample )
{
return( sawSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(s_waveTables[WaveShapes::SawWave - FirstWaveShapeTable], _sample);
}
else
{
return sawSample(_sample);
}
}
@@ -503,9 +743,16 @@ inline sample_t Oscillator::getSample<Oscillator::SawWave>(
template<>
inline sample_t Oscillator::getSample<Oscillator::SquareWave>(
const float _sample )
const float _sample )
{
return( squareSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(s_waveTables[WaveShapes::SquareWave - FirstWaveShapeTable], _sample);
}
else
{
return squareSample(_sample);
}
}
@@ -515,7 +762,14 @@ template<>
inline sample_t Oscillator::getSample<Oscillator::MoogSawWave>(
const float _sample )
{
return( moogSawSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable], _sample);
}
else
{
return moogSawSample(_sample);
}
}
@@ -525,7 +779,14 @@ template<>
inline sample_t Oscillator::getSample<Oscillator::ExponentialWave>(
const float _sample )
{
return( expSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable], _sample);
}
else
{
return expSample(_sample);
}
}
@@ -545,9 +806,15 @@ template<>
inline sample_t Oscillator::getSample<Oscillator::UserDefinedWave>(
const float _sample )
{
return( userWaveSample( _sample ) );
if (m_useWaveTable && !m_isModulator)
{
return wtSample(m_userWave->m_userAntiAliasWaveTable, _sample);
}
else
{
return userWaveSample(_sample);
}
}
} // namespace lmms

191
src/core/PathUtil.cpp Normal file
View File

@@ -0,0 +1,191 @@
#include "PathUtil.h"
#include <QDir>
#include <QFileInfo>
#include "ConfigManager.h"
#include "Engine.h"
#include "Song.h"
namespace lmms::PathUtil
{
Base relativeBases[] = { Base::ProjectDir, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset,
Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG,
Base::LocalDir };
QString baseLocation(const Base base, bool* error /* = nullptr*/)
{
// error is false unless something goes wrong
if (error) { *error = false; }
QString loc = "";
switch (base)
{
case Base::ProjectDir : loc = ConfigManager::inst()->userProjectsDir(); break;
case Base::FactorySample :
{
QDir fsd = QDir(ConfigManager::inst()->factorySamplesDir());
loc = fsd.absolutePath(); break;
}
case Base::UserSample : loc = ConfigManager::inst()->userSamplesDir(); break;
case Base::UserVST : loc = ConfigManager::inst()->userVstDir(); break;
case Base::Preset : loc = ConfigManager::inst()->userPresetsDir(); break;
case Base::UserLADSPA : loc = ConfigManager::inst()->ladspaDir(); break;
case Base::DefaultLADSPA : loc = ConfigManager::inst()->userLadspaDir(); break;
case Base::UserSoundfont : loc = ConfigManager::inst()->sf2Dir(); break;
case Base::DefaultSoundfont : loc = ConfigManager::inst()->userSf2Dir(); break;
case Base::UserGIG : loc = ConfigManager::inst()->gigDir(); break;
case Base::DefaultGIG : loc = ConfigManager::inst()->userGigDir(); break;
case Base::LocalDir:
{
const Song* s = Engine::getSong();
QString projectPath;
if (s)
{
projectPath = s->projectFileName();
loc = QFileInfo(projectPath).path();
}
// We resolved it properly if we had an open Song and the project
// filename wasn't empty
if (error) { *error = (!s || projectPath.isEmpty()); }
break;
}
default : return QString("");
}
return QDir::cleanPath(loc) + "/";
}
QDir baseQDir (const Base base, bool* error /* = nullptr*/)
{
if (base == Base::Absolute)
{
if (error) { *error = false; }
return QDir::root();
}
return QDir(baseLocation(base, error));
}
QString basePrefix(const Base base)
{
switch (base)
{
case Base::ProjectDir : return QStringLiteral("userprojects:");
case Base::FactorySample : return QStringLiteral("factorysample:");
case Base::UserSample : return QStringLiteral("usersample:");
case Base::UserVST : return QStringLiteral("uservst:");
case Base::Preset : return QStringLiteral("preset:");
case Base::UserLADSPA : return QStringLiteral("userladspa:");
case Base::DefaultLADSPA : return QStringLiteral("defaultladspa:");
case Base::UserSoundfont : return QStringLiteral("usersoundfont:");
case Base::DefaultSoundfont : return QStringLiteral("defaultsoundfont:");
case Base::UserGIG : return QStringLiteral("usergig:");
case Base::DefaultGIG : return QStringLiteral("defaultgig:");
case Base::LocalDir : return QStringLiteral("local:");
default : return QStringLiteral("");
}
}
Base baseLookup(const QString & path)
{
for (auto base: relativeBases)
{
QString prefix = basePrefix(base);
if ( path.startsWith(prefix) ) { return base; }
}
return Base::Absolute;
}
QString stripPrefix(const QString & path)
{
return path.mid( basePrefix(baseLookup(path)).length() );
}
QString cleanName(const QString & path)
{
return stripPrefix(QFileInfo(path).baseName());
}
QString oldRelativeUpgrade(const QString & input)
{
if (input.isEmpty()) { return input; }
//Start by assuming that the file is a user sample
Base assumedBase = Base::UserSample;
//Check if it's a factory sample
QString factoryPath = baseLocation(Base::FactorySample) + input;
QFileInfo factoryInfo(factoryPath);
if (factoryInfo.exists()) { assumedBase = Base::FactorySample; }
//Check if it's a VST
QString vstPath = baseLocation(Base::UserVST) + input;
QFileInfo vstInfo(vstPath);
if (vstInfo.exists()) { assumedBase = Base::UserVST; }
//Assume we've found the correct base location, return the full path
return basePrefix(assumedBase) + input;
}
QString toAbsolute(const QString & input, bool* error /* = nullptr*/)
{
//First, do no harm to absolute paths
QFileInfo inputFileInfo = QFileInfo(input);
if (inputFileInfo.isAbsolute())
{
if (error) { *error = false; }
return input;
}
//Next, handle old relative paths with no prefix
QString upgraded = input.contains(":") ? input : oldRelativeUpgrade(input);
Base base = baseLookup(upgraded);
return baseLocation(base, error) + upgraded.remove(0, basePrefix(base).length());
}
QString relativeOrAbsolute(const QString & input, const Base base)
{
if (input.isEmpty()) { return input; }
QString absolutePath = toAbsolute(input);
if (base == Base::Absolute) { return absolutePath; }
bool error;
QString relativePath = baseQDir(base, &error).relativeFilePath(absolutePath);
// Return the relative path if it didn't result in a path starting with ..
// and the baseQDir was resolved properly
return (relativePath.startsWith("..") || error)
? absolutePath
: relativePath;
}
QString toShortestRelative(const QString & input, bool allowLocal /* = false*/)
{
QFileInfo inputFileInfo = QFileInfo(input);
QString absolutePath = inputFileInfo.isAbsolute() ? input : toAbsolute(input);
Base shortestBase = Base::Absolute;
QString shortestPath = relativeOrAbsolute(absolutePath, shortestBase);
for (auto base: relativeBases)
{
// Skip local paths when searching for the shortest relative if those
// are not allowed for that resource
if (base == Base::LocalDir && !allowLocal) { continue; }
QString otherPath = relativeOrAbsolute(absolutePath, base);
if (otherPath.length() < shortestPath.length())
{
shortestBase = base;
shortestPath = otherPath;
}
}
return basePrefix(shortestBase) + relativeOrAbsolute(absolutePath, shortestBase);
}
} // namespace lmms::PathUtil

122
src/core/PatternClip.cpp Normal file
View File

@@ -0,0 +1,122 @@
/*
* PatternClip.cpp - implementation of class PatternClip
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "PatternClip.h"
#include <QDomElement>
#include "Engine.h"
#include "PatternClipView.h"
#include "PatternStore.h"
#include "PatternTrack.h"
namespace lmms
{
PatternClip::PatternClip(Track* track) :
Clip(track)
{
bar_t t = Engine::patternStore()->lengthOfPattern(patternIndex());
if( t > 0 )
{
saveJournallingState( false );
changeLength( TimePos( t, 0 ) );
restoreJournallingState();
}
setAutoResize( false );
}
void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element)
{
element.setAttribute( "name", name() );
if( element.parentNode().nodeName() == "clipboard" )
{
element.setAttribute( "pos", -1 );
}
else
{
element.setAttribute( "pos", startPosition() );
}
element.setAttribute( "len", length() );
element.setAttribute( "muted", isMuted() );
if( usesCustomClipColor() )
{
element.setAttribute( "color", color().name() );
}
}
void PatternClip::loadSettings(const QDomElement& element)
{
setName( element.attribute( "name" ) );
if( element.attribute( "pos" ).toInt() >= 0 )
{
movePosition( element.attribute( "pos" ).toInt() );
}
changeLength( element.attribute( "len" ).toInt() );
if( element.attribute( "muted" ).toInt() != isMuted() )
{
toggleMute();
}
if (element.hasAttribute("color"))
{
if (!element.hasAttribute("usestyle"))
{
// for colors saved in 1.3-onwards
setColor(element.attribute("color"));
useCustomClipColor(true);
}
else
{
// for colors saved before 1.3
setColor(QColor(element.attribute("color").toUInt()));
useCustomClipColor(element.attribute("usestyle").toUInt() == 0);
}
}
else
{
useCustomClipColor(false);
}
}
int PatternClip::patternIndex()
{
return dynamic_cast<PatternTrack*>(getTrack())->patternIndex();
}
gui::ClipView* PatternClip::createView(gui::TrackView* tv)
{
return new gui::PatternClipView(this, tv);
}
} // namespace lmms

256
src/core/PatternStore.cpp Normal file
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(PatternContainer);
}
bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum)
{
bool notePlayed = false;
if (lengthOfPattern(clipNum) <= 0)
{
return false;
}
start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar());
TrackList tl = tracks();
for (Track * t : tl)
{
if (t->play(start, frames, offset, clipNum))
{
notePlayed = true;
}
}
return notePlayed;
}
void PatternStore::updateAfterTrackAdd()
{
if (numOfPatterns() == 0 && !Engine::getSong()->isLoadingProject())
{
Engine::getSong()->addPatternTrack();
}
}
bar_t PatternStore::lengthOfPattern(int pattern) const
{
TimePos maxLength = TimePos::ticksPerBar();
const TrackList & tl = tracks();
for (Track * t : tl)
{
// Don't create Clips here if they don't exist
if (pattern < t->numOfClips())
{
maxLength = qMax(maxLength, t->getClip(pattern)->length());
}
}
return maxLength.nextFullBar();
}
int PatternStore::numOfPatterns() const
{
return Engine::getSong()->countTracks(Track::PatternTrack);
}
void PatternStore::removePattern(int pattern)
{
TrackList tl = tracks();
for (Track * t : tl)
{
delete t->getClip(pattern);
t->removeBar(pattern * DefaultTicksPerBar);
}
if (pattern <= currentPattern())
{
setCurrentPattern(qMax(currentPattern() - 1, 0));
}
}
void PatternStore::swapPattern(int pattern1, int pattern2)
{
TrackList tl = tracks();
for (Track * t : tl)
{
t->swapPositionOfClips(pattern1, pattern2);
}
updateComboBox();
}
void PatternStore::updatePatternTrack(Clip* clip)
{
PatternTrack * t = PatternTrack::findPatternTrack(clip->startPosition() / DefaultTicksPerBar);
if (t != nullptr)
{
t->dataChanged();
}
}
void PatternStore::fixIncorrectPositions()
{
TrackList tl = tracks();
for (Track * t : tl)
{
for (int i = 0; i < numOfPatterns(); ++i)
{
t->getClip(i)->movePosition(TimePos(i, 0));
}
}
}
void PatternStore::play()
{
if (Engine::getSong()->playMode() != Song::Mode_PlayPattern)
{
Engine::getSong()->playPattern();
}
else
{
Engine::getSong()->togglePause();
}
}
void PatternStore::stop()
{
Engine::getSong()->stop();
}
void PatternStore::updateComboBox()
{
const int curPattern = currentPattern();
m_patternComboBoxModel.clear();
for (int i = 0; i < numOfPatterns(); ++i)
{
PatternTrack* pt = PatternTrack::findPatternTrack(i);
m_patternComboBoxModel.addItem(pt->name());
}
setCurrentPattern(curPattern);
}
void PatternStore::currentPatternChanged()
{
// now update all track-labels (the current one has to become white, the others gray)
TrackList tl = Engine::getSong()->tracks();
for (Track * t : tl)
{
if (t->type() == Track::PatternTrack)
{
t->dataChanged();
}
}
}
void PatternStore::createClipsForPattern(int pattern)
{
TrackList tl = tracks();
for (Track * t : tl)
{
t->createClipsForPattern(pattern);
}
}
AutomatedValueMap PatternStore::automatedValuesAt(TimePos time, int clipNum) const
{
Q_ASSERT(clipNum >= 0);
Q_ASSERT(time.getTicks() >= 0);
auto lengthBars = lengthOfPattern(clipNum);
auto lengthTicks = lengthBars * TimePos::ticksPerBar();
if (time > lengthTicks)
{
time = lengthTicks;
}
return TrackContainer::automatedValuesAt(time + (TimePos::ticksPerBar() * clipNum), clipNum);
}
} // namespace lmms

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;
@@ -49,14 +53,14 @@ PeakController::PeakController( Model * _parent,
setSampleExact( true );
if( m_peakEffect )
{
connect( m_peakEffect, SIGNAL( destroyed( ) ),
this, SLOT( handleDestroyedEffect( ) ) );
connect( m_peakEffect, SIGNAL(destroyed()),
this, SLOT(handleDestroyedEffect()));
}
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateCoeffs() ) );
connect( m_peakEffect->attackModel(), SIGNAL( dataChanged() ),
this, SLOT( updateCoeffs() ), Qt::DirectConnection );
connect( m_peakEffect->decayModel(), SIGNAL( dataChanged() ),
this, SLOT( updateCoeffs() ), Qt::DirectConnection );
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateCoeffs()));
connect( m_peakEffect->attackModel(), SIGNAL(dataChanged()),
this, SLOT(updateCoeffs()), Qt::DirectConnection );
connect( m_peakEffect->decayModel(), SIGNAL(dataChanged()),
this, SLOT(updateCoeffs()), Qt::DirectConnection );
m_coeffNeedsUpdate = true;
}
@@ -65,7 +69,7 @@ PeakController::PeakController( Model * _parent,
PeakController::~PeakController()
{
if( m_peakEffect != NULL && m_peakEffect->effectChain() != NULL )
if( m_peakEffect != nullptr && m_peakEffect->effectChain() != nullptr )
{
m_peakEffect->effectChain()->removeEffect( m_peakEffect );
}
@@ -76,7 +80,7 @@ void PeakController::updateValueBuffer()
{
if( m_coeffNeedsUpdate )
{
const float ratio = 44100.0f / Engine::mixer()->processingSampleRate();
const float ratio = 44100.0f / Engine::audioEngine()->processingSampleRate();
m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio );
m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio );
m_coeffNeedsUpdate = false;
@@ -87,7 +91,7 @@ void PeakController::updateValueBuffer()
float targetSample = m_peakEffect->lastSample();
if( m_currentSample != targetSample )
{
const f_cnt_t frames = Engine::mixer()->framesPerPeriod();
const f_cnt_t frames = Engine::audioEngine()->framesPerPeriod();
float * values = m_valueBuffer.values();
for( f_cnt_t f = 0; f < frames; ++f )
@@ -123,12 +127,12 @@ void PeakController::updateCoeffs()
}
void PeakController::handleDestroyedEffect( )
void PeakController::handleDestroyedEffect()
{
// possible race condition...
//printf("disconnecting effect\n");
disconnect( m_peakEffect );
m_peakEffect = NULL;
m_peakEffect = nullptr;
//deleteLater();
delete this;
}
@@ -235,7 +239,7 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi
}
}
return NULL;
return nullptr;
}
@@ -247,11 +251,10 @@ QString PeakController::nodeName() const
ControllerDialog * PeakController::createDialog( QWidget * _parent )
gui::ControllerDialog * PeakController::createDialog( QWidget * _parent )
{
return new PeakControllerDialog( this, _parent );
return new gui::PeakControllerDialog( this, _parent );
}
} // namespace lmms

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,6 +41,10 @@
#include "InstrumentTrack.h"
namespace lmms
{
/*! The black / white order of keys as they appear on the keyboard.
*/
static const Piano::KeyTypes KEY_ORDER[] =
@@ -59,15 +63,10 @@ static const Piano::KeyTypes KEY_ORDER[] =
* \param _it the InstrumentTrack window to attach to
*/
Piano::Piano( InstrumentTrack* track ) :
Model( NULL ), /*!< base class ctor */
Model( nullptr ), /*!< base class ctor */
m_instrumentTrack( track ),
m_midiEvProc( track ) /*!< the InstrumentTrack Model */
{
for( int i = 0; i < NumKeys; ++i )
{
m_pressedKeys[i] = false;
}
}
/*! \brief Turn a key on or off
@@ -137,3 +136,5 @@ bool Piano::isWhiteKey( int key )
return !isBlackKey( key );
}
} // namespace lmms

View File

@@ -23,14 +23,15 @@
*/
#include "PlayHandle.h"
#include "AudioEngine.h"
#include "BufferManager.h"
#include "Engine.h"
#include "Mixer.h"
#include <QtCore/QThread>
#include <QDebug>
#include <QThread>
#include <iterator>
namespace lmms
{
PlayHandle::PlayHandle(const Type type, f_cnt_t offset) :
m_type(type),
@@ -54,12 +55,12 @@ void PlayHandle::doProcessing()
if( m_usesBuffer )
{
m_bufferReleased = false;
BufferManager::clear(m_playHandleBuffer, Engine::mixer()->framesPerPeriod());
BufferManager::clear(m_playHandleBuffer, Engine::audioEngine()->framesPerPeriod());
play( buffer() );
}
else
{
play( NULL );
play( nullptr );
}
}
@@ -73,3 +74,5 @@ sampleFrame* PlayHandle::buffer()
{
return m_bufferReleased ? nullptr : reinterpret_cast<sampleFrame*>(m_playHandleBuffer);
};
} // namespace lmms

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;
@@ -44,12 +47,12 @@ static Plugin::Descriptor dummyPluginDescriptor =
{
"dummy",
"dummy",
QT_TRANSLATE_NOOP( "pluginBrowser", "no description" ),
QT_TRANSLATE_NOOP( "PluginBrowser", "no description" ),
"Tobias Doerffel <tobydox/at/users.sf.net>",
0x0100,
Plugin::Undefined,
&dummyLoader,
NULL
nullptr
} ;
@@ -62,7 +65,7 @@ Plugin::Plugin(const Descriptor * descriptor, Model * parent, const
m_descriptor(descriptor),
m_key(key ? *key : Descriptor::SubPluginFeatures::Key(m_descriptor))
{
if( m_descriptor == NULL )
if( m_descriptor == nullptr )
{
m_descriptor = &dummyPluginDescriptor;
}
@@ -71,13 +74,6 @@ Plugin::Plugin(const Descriptor * descriptor, Model * parent, const
Plugin::~Plugin()
{
}
template<class T>
T use_this_or(T this_param, T or_param)
{
@@ -184,7 +180,6 @@ AutomatableModel * Plugin::childModel( const QString & )
#include "PluginFactory.h"
Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
const Descriptor::SubPluginFeatures::Key *key,
bool keyFromDnd)
@@ -194,7 +189,7 @@ Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
const Descriptor::SubPluginFeatures::Key *keyPtr = keyFromDnd
? static_cast<Plugin::Descriptor::SubPluginFeatures::Key*>(Engine::pickDndPluginKey())
: key;
const PluginFactory::PluginInfo& pi = pluginFactory->pluginInfo(pluginName.toUtf8());
const PluginFactory::PluginInfo& pi = getPluginFactory()->pluginInfo(pluginName.toUtf8());
if(keyPtr)
{
// descriptor is not yet set when loading - set it now
@@ -214,17 +209,17 @@ Plugin * Plugin::instantiateWithKey(const QString& pluginName, Model * parent,
Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
void *data)
{
const PluginFactory::PluginInfo& pi = pluginFactory->pluginInfo(pluginName.toUtf8());
const PluginFactory::PluginInfo& pi = getPluginFactory()->pluginInfo(pluginName.toUtf8());
Plugin* inst;
if( pi.isNull() )
{
if( gui )
if (gui::getGUI() != nullptr)
{
QMessageBox::information( NULL,
QMessageBox::information( nullptr,
tr( "Plugin not found" ),
tr( "The plugin \"%1\" wasn't found or could not be loaded!\nReason: \"%2\"" ).
arg( pluginName ).arg( pluginFactory->errorString(pluginName) ),
arg( pluginName ).arg( getPluginFactory()->errorString(pluginName) ),
QMessageBox::Ok | QMessageBox::Default );
}
inst = new DummyPlugin();
@@ -241,9 +236,9 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
}
else
{
if( gui )
if (gui::getGUI() != nullptr)
{
QMessageBox::information( NULL,
QMessageBox::information( nullptr,
tr( "Error while loading plugin" ),
tr( "Failed to load plugin \"%1\"!").arg( pluginName ),
QMessageBox::Ok | QMessageBox::Default );
@@ -266,10 +261,10 @@ void Plugin::collectErrorForUI( QString errMsg )
PluginView * Plugin::createView( QWidget * parent )
gui::PluginView * Plugin::createView( QWidget * parent )
{
PluginView * pv = instantiateView( parent );
if( pv != NULL )
gui::PluginView * pv = instantiateView( parent );
if( pv != nullptr )
{
pv->setModel( this );
}
@@ -280,7 +275,7 @@ PluginView * Plugin::createView( QWidget * parent )
Plugin::Descriptor::SubPluginFeatures::Key::Key( const QDomElement & key ) :
desc( NULL ),
desc( nullptr ),
name( key.attribute( "key" ) ),
attributes()
{
@@ -313,3 +308,4 @@ QDomElement Plugin::Descriptor::SubPluginFeatures::Key::saveXML(
} // namespace lmms

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.
@@ -94,11 +95,16 @@ void PluginFactory::setupSearchPaths()
PluginFactory* PluginFactory::instance()
{
if (s_instance == nullptr)
s_instance.reset(new PluginFactory());
s_instance = std::make_unique<PluginFactory>();
return s_instance.get();
}
PluginFactory* getPluginFactory()
{
return PluginFactory::instance();
}
const Plugin::DescriptorList PluginFactory::descriptors() const
{
return m_descriptors.values();
@@ -144,7 +150,12 @@ void PluginFactory::discoverPlugins()
QSet<QFileInfo> files;
for (const QString& searchPath : QDir::searchPaths("plugins"))
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
auto discoveredPluginList = QDir(searchPath).entryInfoList(nameFilters);
files.unite(QSet<QFileInfo>(discoveredPluginList.begin(), discoveredPluginList.end()));
#else
files.unite(QDir(searchPath).entryInfoList(nameFilters).toSet());
#endif
}
// Cheap dependency handling: zynaddsubfx needs ZynAddSubFxCore. By loading
@@ -241,3 +252,6 @@ const QString PluginFactory::PluginInfo::name() const
{
return descriptor ? descriptor->name : QString();
}
} // namespace lmms

View File

@@ -26,6 +26,10 @@
#include "PluginIssue.h"
namespace lmms
{
const char *PluginIssue::msgFor(const PluginIssueType &it)
{
switch (it)
@@ -38,6 +42,10 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
return "too many audio input channels";
case tooManyOutputChannels:
return "too many audio output channels";
case tooManyMidiInputChannels:
return "too many MIDI input channels";
case tooManyMidiOutputChannels:
return "too many MIDI output channels";
case noOutputChannel:
return "no audio output channel";
case portHasNoDef:
@@ -46,10 +54,22 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
return "port is missing min value";
case portHasNoMax:
return "port is missing max value";
case minGreaterMax:
return "port minimum is greater than maximum";
case defaultValueNotInRange:
return "default value is not in range [min, max]";
case logScaleMinMissing:
return "logscale requires minimum value";
case logScaleMaxMissing:
return "logscale requires maximum value";
case logScaleMinMaxDifferentSigns:
return "logscale with min < 0 < max";
case featureNotSupported:
return "required feature not supported";
case badPortType:
return "unsupported port type";
case blacklisted:
return "blacklisted plugin";
case noIssue:
return nullptr;
}
@@ -59,6 +79,22 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
bool PluginIssue::operator==(const PluginIssue &other) const
{
return (m_issueType == other.m_issueType) && (m_info == other.m_info);
}
bool PluginIssue::operator<(const PluginIssue &other) const
{
return (m_issueType != other.m_issueType)
? m_issueType < other.m_issueType
: m_info < other.m_info;
}
QDebug operator<<(QDebug stream, const PluginIssue &iss)
{
stream << PluginIssue::msgFor(iss.m_issueType);
@@ -69,4 +105,7 @@ QDebug operator<<(QDebug stream, const PluginIssue &iss)
return stream;
}
} // namespace lmms

View File

@@ -25,23 +25,26 @@
#include <QFileInfo>
#include "PresetPreviewPlayHandle.h"
#include "AudioEngine.h"
#include "Engine.h"
#include "Instrument.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "PluginFactory.h"
#include "ProjectJournal.h"
#include "TrackContainer.h"
#include <atomic>
namespace lmms
{
// invisible track-container which is needed as parent for preview-channels
class PreviewTrackContainer : public TrackContainer
{
public:
PreviewTrackContainer() :
m_previewInstrumentTrack( NULL ),
m_previewNote( NULL ),
m_previewInstrumentTrack( nullptr ),
m_previewNote( nullptr ),
m_dataMutex()
{
setJournalling( false );
@@ -50,11 +53,9 @@ public:
m_previewInstrumentTrack->setPreviewMode( true );
}
virtual ~PreviewTrackContainer()
{
}
~PreviewTrackContainer() override = default;
virtual QString nodeName() const
QString nodeName() const override
{
return "previewtrackcontainer";
}
@@ -122,10 +123,10 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
s_previewTC->lockData();
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
s_previewTC->setPreviewNote( nullptr );
s_previewTC->previewInstrumentTrack()->silenceAllNotes();
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
const bool j = Engine::projectJournal()->isJournalling();
Engine::projectJournal()->setJournalling( false );
@@ -135,14 +136,14 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
Instrument * i = s_previewTC->previewInstrumentTrack()->instrument();
const QString ext = QFileInfo( _preset_file ).
suffix().toLower();
if( i == NULL || !i->descriptor()->supportsFileType( ext ) )
if( i == nullptr || !i->descriptor()->supportsFileType( ext ) )
{
const PluginFactory::PluginInfoAndKey& infoAndKey =
pluginFactory->pluginSupportingExtension(ext);
getPluginFactory()->pluginSupportingExtension(ext);
i = s_previewTC->previewInstrumentTrack()->
loadInstrument(infoAndKey.info.name(), &infoAndKey.key);
}
if( i != NULL )
if( i != nullptr )
{
i->loadFile( _preset_file );
}
@@ -156,19 +157,9 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
dataFileCreated = true;
}
// vestige previews are bug prone; fallback on 3xosc with volume of 0
// without an instrument in preview track, it will segfault
if(dataFile->content().elementsByTagName( "vestige" ).length() == 0 )
{
s_previewTC->previewInstrumentTrack()->
loadTrackSpecificSettings(
dataFile->content().firstChild().toElement() );
}
else
{
s_previewTC->previewInstrumentTrack()->loadInstrument("tripleoscillator");
s_previewTC->previewInstrumentTrack()->setVolume( 0 );
}
s_previewTC->previewInstrumentTrack()->loadTrackSpecificSettings(
dataFile->content().firstChild().toElement());
if( dataFileCreated )
{
delete dataFile;
@@ -180,7 +171,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
s_previewTC->previewInstrumentTrack()->
midiPort()->setMode( MidiPort::Disabled );
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
// create note-play-handle for it
m_previewNote = NotePlayHandleManager::acquire(
s_previewTC->previewInstrumentTrack(), 0,
@@ -191,9 +182,9 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
s_previewTC->setPreviewNote( m_previewNote );
Engine::mixer()->addPlayHandle( m_previewNote );
Engine::audioEngine()->addPlayHandle( m_previewNote );
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
s_previewTC->unlockData();
Engine::projectJournal()->setJournalling( j );
}
@@ -203,13 +194,13 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file,
PresetPreviewPlayHandle::~PresetPreviewPlayHandle()
{
Engine::mixer()->requestChangeInModel();
Engine::audioEngine()->requestChangeInModel();
// not muted by other preset-preview-handle?
if (s_previewTC->testAndSetPreviewNote(m_previewNote, nullptr))
{
m_previewNote->noteOff();
}
Engine::mixer()->doneChangeInModel();
Engine::audioEngine()->doneChangeInModel();
}
@@ -218,7 +209,7 @@ PresetPreviewPlayHandle::~PresetPreviewPlayHandle()
void PresetPreviewPlayHandle::play( sampleFrame * _working_buffer )
{
// Do nothing; the preview instrument is played by m_previewNote, which
// has been added to the mixer
// has been added to the audio engine
}
@@ -254,7 +245,7 @@ void PresetPreviewPlayHandle::init()
void PresetPreviewPlayHandle::cleanup()
{
delete s_previewTC;
s_previewTC = NULL;
s_previewTC = nullptr;
}
@@ -264,7 +255,7 @@ ConstNotePlayHandleList PresetPreviewPlayHandle::nphsOfInstrumentTrack(
const InstrumentTrack * _it )
{
ConstNotePlayHandleList cnphv;
if( s_previewTC->previewNote() != NULL &&
if( s_previewTC->previewNote() != nullptr &&
s_previewTC->previewNote()->instrumentTrack() == _it )
{
cnphv.push_back( s_previewTC->previewNote() );
@@ -284,4 +275,4 @@ bool PresetPreviewPlayHandle::isPreviewing()
}
} // namespace lmms

View File

@@ -29,6 +29,9 @@
#include "JournallingObject.h"
#include "Song.h"
namespace lmms
{
//! Avoid clashes between loaded IDs (have the bit cleared)
//! and newly created IDs (have the bit set)
static const int EO_ID_MSB = 1 << 23;
@@ -46,13 +49,6 @@ ProjectJournal::ProjectJournal() :
ProjectJournal::~ProjectJournal()
{
}
void ProjectJournal::undo()
{
while( !m_undoCheckPoints.isEmpty() )
@@ -181,7 +177,7 @@ void ProjectJournal::clearJournal()
for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); )
{
if( it.value() == NULL )
if( it.value() == nullptr )
{
it = m_joIDs.erase( it );
}
@@ -196,7 +192,7 @@ void ProjectJournal::stopAllJournalling()
{
for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); ++it)
{
if( it.value() != NULL )
if( it.value() != nullptr )
{
it.value()->setJournalling(false);
}
@@ -206,3 +202,4 @@ void ProjectJournal::stopAllJournalling()
} // namespace lmms

View File

@@ -34,9 +34,10 @@
#include "AudioFileMP3.h"
#include "AudioFileFlac.h"
#ifdef LMMS_HAVE_SCHED_H
#include "sched.h"
#endif
namespace lmms
{
const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
{
@@ -55,7 +56,7 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
#ifdef LMMS_HAVE_OGGVORBIS
&AudioFileOgg::getInst
#else
NULL
nullptr
#endif
},
{ ProjectRenderer::MP3File,
@@ -64,25 +65,25 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
#ifdef LMMS_HAVE_MP3LAME
&AudioFileMP3::getInst
#else
NULL
nullptr
#endif
},
// Insert your own file-encoder infos here.
// Maybe one day the user can add own encoders inside the program.
{ ProjectRenderer::NumFileFormats, NULL, NULL, NULL }
{ ProjectRenderer::NumFileFormats, nullptr, nullptr, nullptr }
} ;
ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings,
ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings,
const OutputSettings & outputSettings,
ExportFileFormats exportFileFormat,
const QString & outputFilename ) :
QThread( Engine::mixer() ),
m_fileDev( NULL ),
QThread( Engine::audioEngine() ),
m_fileDev( nullptr ),
m_qualitySettings( qualitySettings ),
m_progress( 0 ),
m_abort( false )
@@ -95,11 +96,11 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings
m_fileDev = audioEncoderFactory(
outputFilename, outputSettings, DEFAULT_CHANNELS,
Engine::mixer(), successful );
Engine::audioEngine(), successful );
if( !successful )
{
delete m_fileDev;
m_fileDev = NULL;
m_fileDev = nullptr;
}
}
}
@@ -107,13 +108,6 @@ ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & qualitySettings
ProjectRenderer::~ProjectRenderer()
{
}
// Little help function for getting file format from a file extension
// (only for registered file-encoders).
ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension(
@@ -149,10 +143,9 @@ void ProjectRenderer::startProcessing()
if( isReady() )
{
// Have to do mixer stuff with GUI-thread affinity in order to
// Have to do audio engine stuff with GUI-thread affinity in order to
// make slots connected to sampleRateChanged()-signals being called immediately.
Engine::mixer()->setAudioDevice( m_fileDev,
m_qualitySettings, false, false );
Engine::audioEngine()->setAudioDevice( m_fileDev, m_qualitySettings, false, false );
start(
#ifndef LMMS_BUILD_WIN32
@@ -168,7 +161,7 @@ void ProjectRenderer::run()
{
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
#if 0
#ifdef LMMS_BUILD_LINUX
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD)
#ifdef LMMS_HAVE_SCHED_H
cpu_set_t mask;
CPU_ZERO( &mask );
@@ -181,14 +174,13 @@ void ProjectRenderer::run()
PerfLogTimer perfLog("Project Render");
Engine::getSong()->startExport();
Engine::getSong()->updateLength();
// Skip first empty buffer.
Engine::mixer()->nextBuffer();
Engine::audioEngine()->nextBuffer();
m_progress = 0;
// Now start processing
Engine::mixer()->startProcessing(false);
Engine::audioEngine()->startProcessing(false);
// Continually track and emit progress percentage to listeners.
while (!Engine::getSong()->isExportDone() && !m_abort)
@@ -202,8 +194,8 @@ void ProjectRenderer::run()
}
}
// Notify mixer of the end of processing.
Engine::mixer()->stopProcessing();
// Notify the audio engine of the end of processing.
Engine::audioEngine()->stopProcessing();
Engine::getSong()->stopExport();
@@ -241,7 +233,7 @@ void ProjectRenderer::updateConsoleProgress()
}
prog[cols] = 0;
const char * activity = (const char *) "|/-\\";
const auto activity = (const char*)"|/-\\";
memset( buf, 0, sizeof( buf ) );
sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress,
activity[rot] );
@@ -252,3 +244,4 @@ void ProjectRenderer::updateConsoleProgress()
}
} // namespace lmms

View File

@@ -27,123 +27,107 @@
#include "ProjectVersion.h"
int parseMajor(QString & version) {
return version.section( '.', 0, 0 ).toInt();
}
int parseMinor(QString & version) {
return version.section( '.', 1, 1 ).toInt();
}
int parseRelease(QString & version) {
return version.section( '.', 2, 2 ).section( '-', 0, 0 ).toInt();
}
QString parseStage(QString & version) {
return version.section( '.', 2, 2 ).section( '-', 1 );
}
int parseBuild(QString & version) {
return version.section( '.', 3 ).toInt();
}
namespace lmms
{
ProjectVersion::ProjectVersion(QString version, CompareType c) :
m_version(version),
m_major(parseMajor(m_version)),
m_minor(parseMinor(m_version)),
m_release(parseRelease(m_version)),
m_stage(parseStage(m_version)),
m_build(parseBuild(m_version)),
m_compareType(c)
{
}
ProjectVersion::ProjectVersion(const char* version, CompareType c) :
m_version(QString(version)),
m_major(parseMajor(m_version)),
m_minor(parseMinor(m_version)),
m_release(parseRelease(m_version)),
m_stage(parseStage(m_version)),
m_build(parseBuild(m_version)),
m_compareType(c)
{
// Version numbers may have build data, prefixed with a '+',
// but this mustn't affect version precedence in comparisons
QString metadataStripped = version.split("+").first();
// They must have an obligatory initial segement, and may have
// optional identifiers prefaced by a '-'. Both parts affect precedence
QString obligatorySegment = metadataStripped.section('-', 0, 0);
QString prereleaseSegment = metadataStripped.section('-', 1);
// The obligatory segment consists of three identifiers: MAJOR.MINOR.PATCH
QStringList mainVersion = obligatorySegment.split(".");
// HACK: Pad invalid versions in order to prevent crashes
while (mainVersion.size() < 3){ mainVersion.append("0"); }
m_major = mainVersion.at(0).toInt();
m_minor = mainVersion.at(1).toInt();
m_patch = mainVersion.at(2).toInt();
// Any # of optional pre-release identifiers may follow, separated by '.'s
if (!prereleaseSegment.isEmpty()){ m_labels = prereleaseSegment.split("."); }
// HACK: Handle old (1.2.2 and earlier), non-standard versions of the form
// MAJOR.MINOR.PATCH.COMMITS, used for non-release builds from source.
if (mainVersion.size() >= 4 && m_major <= 1 && m_minor <= 2 && m_patch <= 2){
// Drop the standard version identifiers. erase(a, b) removes [a,b)
mainVersion.erase(mainVersion.begin(), mainVersion.begin() + 3);
// Prepend the remaining identifiers as prerelease versions
m_labels = mainVersion + m_labels;
// Bump the patch version. x.y.z-a < x.y.z, but we want x.y.z.a > x.y.z
m_patch += 1;
}
}
ProjectVersion::ProjectVersion(const char* version, CompareType c) : ProjectVersion(QString(version), c)
{
}
//! @param c Determines the number of identifiers to check when comparing
int ProjectVersion::compare(const ProjectVersion & a, const ProjectVersion & b, CompareType c)
{
if(a.getMajor() != b.getMajor())
{
return a.getMajor() - b.getMajor();
}
if(c == Major)
{
return 0;
// How many identifiers to compare before we consider the versions equal
const int limit = static_cast<int>(c);
// Use the value of limit to zero out identifiers we don't care about
int aMaj = 0, bMaj = 0, aMin = 0, bMin = 0, aPat = 0, bPat = 0;
if (limit >= 1){ aMaj = a.getMajor(); bMaj = b.getMajor(); }
if (limit >= 2){ aMin = a.getMinor(); bMin = b.getMinor(); }
if (limit >= 3){ aPat = a.getPatch(); bPat = b.getPatch(); }
// Then we can compare as if we care about every identifier
if(aMaj != bMaj){ return aMaj - bMaj; }
if(aMin != bMin){ return aMin - bMin; }
if(aPat != bPat){ return aPat - bPat; }
// Decide how many optional identifiers we care about
const int maxLabels = qMax(0, limit - 3);
const auto aLabels = a.getLabels().mid(0, maxLabels);
const auto bLabels = b.getLabels().mid(0, maxLabels);
// We can only compare identifiers if both versions have them
const int commonLabels = qMin(aLabels.size(), bLabels.size());
// If one version has optional labels and the other doesn't,
// the one without them is bigger
if (commonLabels == 0){ return bLabels.size() - aLabels.size(); }
// Otherwise, compare as many labels as we can
for (int i = 0; i < commonLabels; i++){
const QString& labelA = aLabels.at(i);
const QString& labelB = bLabels.at(i);
// If both labels are the same, skip
if (labelA == labelB){ continue; }
// Numeric and non-numeric identifiers compare differently
bool aIsNumeric = false, bIsNumeric = false;
const int numA = labelA.toInt(&aIsNumeric);
const int numB = labelB.toInt(&bIsNumeric);
// toInt reads '-x' as a negative number, semver says it's non-numeric
aIsNumeric &= !labelA.startsWith("-");
bIsNumeric &= !labelB.startsWith("-");
// If only one identifier is numeric, that one is smaller
if (aIsNumeric != bIsNumeric){ return aIsNumeric ? -1 : 1; }
// If both are numeric, compare as numbers
if (aIsNumeric && bIsNumeric){ return numA - numB; }
// Otherwise, compare lexically
return labelA.compare(labelB);
}
if(a.getMinor() != b.getMinor())
{
return a.getMinor() - b.getMinor();
}
if(c == Minor)
{
return 0;
}
if(a.getRelease() != b.getRelease())
{
return a.getRelease() - b.getRelease();
}
if(c == Release)
{
return 0;
}
if(!(a.getStage().isEmpty() && b.getStage().isEmpty()))
{
// make sure 0.x.y > 0.x.y-alpha
if(a.getStage().isEmpty())
{
return 1;
}
if(b.getStage().isEmpty())
{
return -1;
}
// 0.x.y-beta > 0.x.y-alpha
int cmp = QString::compare(a.getStage(), b.getStage());
if(cmp)
{
return cmp;
}
}
if(c == Stage)
{
return 0;
}
return a.getBuild() - b.getBuild();
// If everything else matches, the version with more labels is bigger
return aLabels.size() - bLabels.size();
}
@@ -155,4 +139,4 @@ int ProjectVersion::compare(ProjectVersion v1, ProjectVersion v2)
}
} // namespace lmms

View File

@@ -22,26 +22,58 @@
*
*/
#define COMPILE_REMOTE_PLUGIN_BASE
#include "RemotePlugin.h"
//#define DEBUG_REMOTE_PLUGIN
#ifdef DEBUG_REMOTE_PLUGIN
#include <QDebug>
#endif
#include "BufferManager.h"
#include "RemotePlugin.h"
#include "Mixer.h"
#include "Engine.h"
#ifdef LMMS_BUILD_WIN32
#include <windows.h>
#endif
#include "BufferManager.h"
#include "AudioEngine.h"
#include "Engine.h"
#include "Song.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QUuid>
#ifndef SYNC_WITH_SHM_FIFO
#include <QtCore/QUuid>
#include <sys/socket.h>
#include <sys/un.h>
#endif
#ifdef LMMS_BUILD_WIN32
namespace {
HANDLE getRemotePluginJob()
{
static const auto job = []
{
const auto job = CreateJobObject(nullptr, nullptr);
auto limitInfo = JOBOBJECT_EXTENDED_LIMIT_INFORMATION{};
limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limitInfo, sizeof(limitInfo));
return job;
}();
return job;
}
} // namespace
#endif // LMMS_BUILD_WIN32
namespace lmms
{
// simple helper thread monitoring our RemotePlugin - if process terminates
// unexpectedly invalidate plugin so LMMS doesn't lock up
@@ -55,18 +87,41 @@ ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) :
void ProcessWatcher::run()
{
m_plugin->m_process.start( m_plugin->m_exec, m_plugin->m_args );
exec();
m_plugin->m_process.moveToThread( m_plugin->thread() );
while( !m_quit && m_plugin->messagesLeft() )
{
msleep( 200 );
}
if( !m_quit )
{
fprintf( stderr,
"remote plugin died! invalidating now.\n" );
auto& process = m_plugin->m_process;
process.start(m_plugin->m_exec, m_plugin->m_args);
#ifdef LMMS_BUILD_WIN32
// Add the process to our job so it is killed if we crash
if (process.waitForStarted(-1))
{
if (const auto processHandle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, process.processId()))
{
// Ensure the process is still running, otherwise the handle we
// obtained may be for a different process that happened to reuse
// the same process id.
// QProcess::state() alone is insufficient as it only returns a
// cached state variable that is updated asynchronously. To query
// the process itself, we can use QProcess::waitForFinished() with a
// zero timeout, but that too is insufficient as it fails if the
// process has already finished. Therefore, we check both.
if (!process.waitForFinished(0) && process.state() == QProcess::Running)
{
AssignProcessToJobObject(getRemotePluginJob(), processHandle);
}
CloseHandle(processHandle);
}
}
#endif // LMMS_BUILD_WIN32
exec();
process.moveToThread(m_plugin->thread());
while (!m_quit && m_plugin->messagesLeft())
{
msleep(200);
}
if (!m_quit)
{
fprintf(stderr, "remote plugin died! invalidating now.\n");
m_plugin->invalidate();
}
}
@@ -84,15 +139,11 @@ RemotePlugin::RemotePlugin() :
#endif
m_failed( true ),
m_watcher( this ),
m_commMutex( QMutex::Recursive ),
m_splitChannels( false ),
#ifdef USE_QT_SHMEM
m_shmObj(),
#else
m_shmID( 0 ),
#if (QT_VERSION < QT_VERSION_CHECK(5,14,0))
m_commMutex(QMutex::Recursive),
#endif
m_shmSize( 0 ),
m_shm( NULL ),
m_splitChannels( false ),
m_audioBufferSize( 0 ),
m_inputCount( DEFAULT_CHANNELS ),
m_outputCount( DEFAULT_CHANNELS )
{
@@ -102,14 +153,14 @@ RemotePlugin::RemotePlugin() :
m_socketFile = QDir::tempPath() + QDir::separator() +
QUuid::createUuid().toString();
const char * path = m_socketFile.toUtf8().constData();
size_t length = strlen( path );
auto path = m_socketFile.toUtf8();
size_t length = path.length();
if ( length >= sizeof sa.sun_path )
{
length = sizeof sa.sun_path - 1;
qWarning( "Socket path too long." );
}
memcpy( sa.sun_path, path, length );
memcpy(sa.sun_path, path.constData(), length );
sa.sun_path[length] = '\0';
m_server = socket( PF_LOCAL, SOCK_STREAM, 0 );
@@ -117,7 +168,7 @@ RemotePlugin::RemotePlugin() :
{
qWarning( "Unable to start the server." );
}
remove( path );
remove(path.constData());
int ret = bind( m_server, (struct sockaddr *) &sa, sizeof sa );
if ( ret == -1 || listen( m_server, 1 ) == -1 )
{
@@ -125,14 +176,14 @@ RemotePlugin::RemotePlugin() :
}
#endif
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
this, SLOT( processFinished( int, QProcess::ExitStatus ) ),
connect( &m_process, SIGNAL(finished(int,QProcess::ExitStatus)),
this, SLOT(processFinished(int,QProcess::ExitStatus)),
Qt::DirectConnection );
connect( &m_process, SIGNAL( errorOccurred( QProcess::ProcessError ) ),
this, SLOT( processErrored( QProcess::ProcessError ) ),
connect( &m_process, SIGNAL(errorOccurred(QProcess::ProcessError)),
this, SLOT(processErrored(QProcess::ProcessError)),
Qt::DirectConnection );
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
&m_watcher, SLOT( quit() ), Qt::DirectConnection );
connect( &m_process, SIGNAL(finished(int,QProcess::ExitStatus)),
&m_watcher, SLOT(quit()), Qt::DirectConnection );
}
@@ -158,11 +209,6 @@ RemotePlugin::~RemotePlugin()
}
unlock();
}
#ifndef USE_QT_SHMEM
shmdt( m_shm );
shmctl( m_shmID, IPC_RMID, NULL );
#endif
}
#ifndef SYNC_WITH_SHM_FIFO
@@ -222,8 +268,8 @@ bool RemotePlugin::init(const QString &pluginExecutable,
QStringList args;
#ifdef SYNC_WITH_SHM_FIFO
// swap in and out for bidirectional communication
args << QString::number( out()->shmKey() );
args << QString::number( in()->shmKey() );
args << QString::fromStdString(out()->shmKey());
args << QString::fromStdString(in()->shmKey());
#else
args << m_socketFile;
#endif
@@ -256,7 +302,7 @@ bool RemotePlugin::init(const QString &pluginExecutable,
break;
default:
m_socket = accept( m_server, NULL, NULL );
m_socket = accept( m_server, nullptr, nullptr );
if ( m_socket == -1 )
{
qWarning( "Unexpected socket error." );
@@ -264,6 +310,7 @@ bool RemotePlugin::init(const QString &pluginExecutable,
}
#endif
sendMessage(message(IdSyncKey).addString(Engine::getSong()->syncKey()));
resizeSharedProcessingMemory();
if( waitForInitDoneMsg )
@@ -278,44 +325,43 @@ bool RemotePlugin::init(const QString &pluginExecutable,
bool RemotePlugin::process( const sampleFrame * _in_buf,
sampleFrame * _out_buf )
bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf )
{
const fpp_t frames = Engine::mixer()->framesPerPeriod();
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
if( m_failed || !isRunning() )
{
if( _out_buf != NULL )
if( _out_buf != nullptr )
{
BufferManager::clear( _out_buf, frames );
}
return false;
}
if( m_shm == NULL )
if (!m_audioBuffer)
{
// m_shm being zero means we didn't initialize everything so
// m_audioBuffer being zero means we didn't initialize everything so
// far so process one message each time (and hope we get
// information like SHM-key etc.) until we process messages
// in a later stage of this procedure
if( m_shmSize == 0 )
if( m_audioBufferSize == 0 )
{
lock();
fetchAndProcessAllMessages();
unlock();
}
if( _out_buf != NULL )
if( _out_buf != nullptr )
{
BufferManager::clear( _out_buf, frames );
}
return false;
}
memset( m_shm, 0, m_shmSize );
memset( m_audioBuffer.get(), 0, m_audioBufferSize );
ch_cnt_t inputs = qMin<ch_cnt_t>( m_inputCount, DEFAULT_CHANNELS );
if( _in_buf != NULL && inputs > 0 )
if( _in_buf != nullptr && inputs > 0 )
{
if( m_splitChannels )
{
@@ -323,18 +369,18 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
{
for( fpp_t frame = 0; frame < frames; ++frame )
{
m_shm[ch * frames + frame] =
m_audioBuffer[ch * frames + frame] =
_in_buf[frame][ch];
}
}
}
else if( inputs == DEFAULT_CHANNELS )
{
memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME );
memcpy( m_audioBuffer.get(), _in_buf, frames * BYTES_PER_FRAME );
}
else
{
sampleFrame * o = (sampleFrame *) m_shm;
auto o = (sampleFrame*)m_audioBuffer.get();
for( ch_cnt_t ch = 0; ch < inputs; ++ch )
{
for( fpp_t frame = 0; frame < frames; ++frame )
@@ -348,7 +394,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
lock();
sendMessage( IdStartProcessing );
if( m_failed || _out_buf == NULL || m_outputCount == 0 )
if( m_failed || _out_buf == nullptr || m_outputCount == 0 )
{
unlock();
return false;
@@ -365,20 +411,19 @@ bool RemotePlugin::process( const sampleFrame * _in_buf,
{
for( fpp_t frame = 0; frame < frames; ++frame )
{
_out_buf[frame][ch] = m_shm[( m_inputCount+ch )*
_out_buf[frame][ch] = m_audioBuffer[( m_inputCount+ch )*
frames + frame];
}
}
}
else if( outputs == DEFAULT_CHANNELS )
{
memcpy( _out_buf, m_shm + m_inputCount * frames,
memcpy( _out_buf, m_audioBuffer.get() + m_inputCount * frames,
frames * BYTES_PER_FRAME );
}
else
{
sampleFrame * o = (sampleFrame *) ( m_shm +
m_inputCount*frames );
auto o = (sampleFrame*)(m_audioBuffer.get() + m_inputCount * frames);
// clear buffer, if plugin didn't fill up both channels
BufferManager::clear( _out_buf, frames );
@@ -431,39 +476,19 @@ void RemotePlugin::hideUI()
void RemotePlugin::resizeSharedProcessingMemory()
{
const size_t s = ( m_inputCount+m_outputCount ) *
Engine::mixer()->framesPerPeriod() *
sizeof( float );
if( m_shm != NULL )
const size_t s = (m_inputCount + m_outputCount) * Engine::audioEngine()->framesPerPeriod();
try
{
#ifdef USE_QT_SHMEM
m_shmObj.detach();
#else
shmdt( m_shm );
shmctl( m_shmID, IPC_RMID, NULL );
#endif
m_audioBuffer.create(QUuid::createUuid().toString().toStdString(), s);
}
static int shm_key = 0;
#ifdef USE_QT_SHMEM
do
{
m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) );
m_shmObj.create( s );
} while( m_shmObj.error() != QSharedMemory::NoError );
m_shm = (float *) m_shmObj.data();
#else
while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL |
0600 ) ) == -1 )
catch (const std::runtime_error& error)
{
qCritical() << "Failed to allocate shared audio buffer:" << error.what();
m_audioBuffer.detach();
return;
}
m_shm = (float *) shmat( m_shmID, 0, 0 );
#endif
m_shmSize = s;
sendMessage( message( IdChangeSharedMemoryKey ).
addInt( shm_key ).addInt( m_shmSize ) );
m_audioBufferSize = s * sizeof(float);
sendMessage(message(IdChangeSharedMemoryKey).addString(m_audioBuffer.key()));
}
@@ -510,12 +535,12 @@ bool RemotePlugin::processMessage( const message & _m )
case IdSampleRateInformation:
reply = true;
reply_message.addInt( Engine::mixer()->processingSampleRate() );
reply_message.addInt( Engine::audioEngine()->processingSampleRate() );
break;
case IdBufferSizeInformation:
reply = true;
reply_message.addInt( Engine::mixer()->framesPerPeriod() );
reply_message.addInt( Engine::audioEngine()->framesPerPeriod() );
break;
case IdChangeInputCount:
@@ -552,3 +577,6 @@ bool RemotePlugin::processMessage( const message & _m )
return true;
}
} // namespace lmms

View File

@@ -22,41 +22,43 @@
*
*/
#include <QDebug>
#include <QDir>
#include "RenderManager.h"
#include "PatternStore.h"
#include "Song.h"
#include "BBTrackContainer.h"
#include "BBTrack.h"
#include "stdshims.h"
namespace lmms
{
RenderManager::RenderManager(
const Mixer::qualitySettings & qualitySettings,
const AudioEngine::qualitySettings & qualitySettings,
const OutputSettings & outputSettings,
ProjectRenderer::ExportFileFormats fmt,
QString outputPath) :
m_qualitySettings(qualitySettings),
m_oldQualitySettings( Engine::mixer()->currentQualitySettings() ),
m_oldQualitySettings( Engine::audioEngine()->currentQualitySettings() ),
m_outputSettings(outputSettings),
m_format(fmt),
m_outputPath(outputPath)
{
Engine::mixer()->storeAudioDevice();
Engine::audioEngine()->storeAudioDevice();
}
RenderManager::~RenderManager()
{
Engine::mixer()->restoreAudioDevice(); // Also deletes audio dev.
Engine::mixer()->changeQuality( m_oldQualitySettings );
Engine::audioEngine()->restoreAudioDevice(); // Also deletes audio dev.
Engine::audioEngine()->changeQuality( m_oldQualitySettings );
}
void RenderManager::abortProcessing()
{
if ( m_activeRenderer ) {
disconnect( m_activeRenderer.get(), SIGNAL( finished() ),
this, SLOT( renderNextTrack() ) );
disconnect( m_activeRenderer.get(), SIGNAL(finished()),
this, SLOT(renderNextTrack()));
m_activeRenderer->abortProcessing();
}
restoreMutedState();
@@ -80,9 +82,9 @@ void RenderManager::renderNextTrack()
m_tracksToRender.pop_back();
// mute everything but the track we are about to render
for( auto it = m_unmuted.begin(); it != m_unmuted.end(); ++it )
for (auto track : m_unmuted)
{
(*it)->setMuted( (*it) != renderTrack );
track->setMuted(track != renderTrack);
}
// for multi-render, prefix each output file with a different number
@@ -98,9 +100,8 @@ void RenderManager::renderTracks()
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
// find all currently unnmuted tracks -- we want to render these.
for( auto it = tl.begin(); it != tl.end(); ++it )
for (const auto& tk : tl)
{
Track* tk = (*it);
Track::TrackTypes type = tk->type();
// Don't render automation tracks
@@ -111,10 +112,9 @@ void RenderManager::renderTracks()
}
}
const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks();
for( auto it = t2.begin(); it != t2.end(); ++it )
const TrackContainer::TrackList t2 = Engine::patternStore()->tracks();
for (const auto& tk : t2)
{
Track* tk = (*it);
Track::TrackTypes type = tk->type();
// Don't render automation tracks
@@ -140,7 +140,7 @@ void RenderManager::renderProject()
void RenderManager::render(QString outputPath)
{
m_activeRenderer = make_unique<ProjectRenderer>(
m_activeRenderer = std::make_unique<ProjectRenderer>(
m_qualitySettings,
m_outputSettings,
m_format,
@@ -149,13 +149,13 @@ void RenderManager::render(QString outputPath)
if( m_activeRenderer->isReady() )
{
// pass progress signals through
connect( m_activeRenderer.get(), SIGNAL( progressChanged( int ) ),
this, SIGNAL( progressChanged( int ) ) );
connect( m_activeRenderer.get(), SIGNAL(progressChanged(int)),
this, SIGNAL(progressChanged(int)));
// when it is finished, render the next track.
// if we have not queued any tracks, renderNextTrack will just clean up
connect( m_activeRenderer.get(), SIGNAL( finished() ),
this, SLOT( renderNextTrack() ) );
connect( m_activeRenderer.get(), SIGNAL(finished()),
this, SLOT(renderNextTrack()));
m_activeRenderer->startProcessing();
}
@@ -182,7 +182,7 @@ QString RenderManager::pathForTrack(const Track *track, int num)
{
QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
QString name = track->name();
name = name.remove(QRegExp("[^a-zA-Z]"));
name = name.remove(QRegExp(FILENAME_FILTER));
name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
return QDir(m_outputPath).filePath(name);
}
@@ -202,3 +202,6 @@ void RenderManager::updateConsoleProgress()
}
}
}
} // namespace lmms

View File

@@ -24,14 +24,17 @@
*/
#include "RingBuffer.h"
#include "AudioEngine.h"
#include "Engine.h"
#include "Mixer.h"
#include "MixHelpers.h"
namespace lmms
{
RingBuffer::RingBuffer( f_cnt_t size ) :
m_fpp( Engine::mixer()->framesPerPeriod() ),
m_samplerate( Engine::mixer()->processingSampleRate() ),
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
m_samplerate( Engine::audioEngine()->processingSampleRate() ),
m_size( size + m_fpp )
{
m_buffer = new sampleFrame[ m_size ];
@@ -41,8 +44,8 @@ RingBuffer::RingBuffer( f_cnt_t size ) :
RingBuffer::RingBuffer( float size ) :
m_fpp( Engine::mixer()->framesPerPeriod() ),
m_samplerate( Engine::mixer()->processingSampleRate() )
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
m_samplerate( Engine::audioEngine()->processingSampleRate() )
{
m_size = msToFrames( size ) + m_fpp;
m_buffer = new sampleFrame[ m_size ];
@@ -88,11 +91,11 @@ void RingBuffer::setSamplerateAware( bool b )
{
if( b )
{
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ), Qt::UniqueConnection );
connect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateSamplerate()), Qt::UniqueConnection );
}
else
{
disconnect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ) );
disconnect( Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(updateSamplerate()));
}
}
@@ -304,9 +307,9 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset,
void RingBuffer::updateSamplerate()
{
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::mixer()->processingSampleRate() ) / m_samplerate;
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->processingSampleRate() ) / m_samplerate;
m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
m_samplerate = Engine::mixer()->processingSampleRate();
m_samplerate = Engine::audioEngine()->processingSampleRate();
delete[] m_buffer;
m_buffer = new sampleFrame[ m_size ];
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
@@ -314,3 +317,4 @@ void RingBuffer::updateSamplerate()
}
} // namespace lmms

File diff suppressed because it is too large Load Diff

330
src/core/SampleClip.cpp Normal file
View File

@@ -0,0 +1,330 @@
/*
* SampleClip.cpp
*
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SampleClip.h"
#include <QDomElement>
#include "SampleBuffer.h"
#include "SampleClipView.h"
#include "SampleTrack.h"
#include "TimeLineWidget.h"
namespace lmms
{
SampleClip::SampleClip( Track * _track ) :
Clip( _track ),
m_sampleBuffer( new SampleBuffer ),
m_isPlaying( false )
{
saveJournallingState( false );
setSampleFile( "" );
restoreJournallingState();
// we need to receive bpm-change-events, because then we have to
// change length of this Clip
connect( Engine::getSong(), SIGNAL(tempoChanged(lmms::bpm_t)),
this, SLOT(updateLength()), Qt::DirectConnection );
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
this, SLOT(updateLength()));
//care about positionmarker
gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine;
if( timeLine )
{
connect( timeLine, SIGNAL(positionMarkerMoved()), this, SLOT(playbackPositionChanged()));
}
//playbutton clicked or space key / on Export Song set isPlaying to false
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
//care about loops
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
//care about mute Clips
connect( this, SIGNAL(dataChanged()), this, SLOT(playbackPositionChanged()));
//care about mute track
connect( getTrack()->getMutedModel(), SIGNAL(dataChanged()),
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
//care about Clip position
connect( this, SIGNAL(positionChanged()), this, SLOT(updateTrackClips()));
switch( getTrack()->trackContainer()->type() )
{
case TrackContainer::PatternContainer:
setAutoResize( true );
break;
case TrackContainer::SongContainer:
// move down
default:
setAutoResize( false );
break;
}
updateTrackClips();
}
SampleClip::SampleClip(const SampleClip& orig) :
SampleClip(orig.getTrack())
{
// TODO: This creates a new SampleBuffer for the new Clip, eating up memory
// & eventually causing performance issues. Letting tracks share buffers
// when they're identical would fix this, but isn't possible right now.
*m_sampleBuffer = *orig.m_sampleBuffer;
m_isPlaying = orig.m_isPlaying;
}
SampleClip::~SampleClip()
{
auto sampletrack = dynamic_cast<SampleTrack*>(getTrack());
if ( sampletrack )
{
sampletrack->updateClips();
}
Engine::audioEngine()->requestChangeInModel();
sharedObject::unref( m_sampleBuffer );
Engine::audioEngine()->doneChangeInModel();
}
void SampleClip::changeLength( const TimePos & _length )
{
Clip::changeLength( qMax( static_cast<int>( _length ), 1 ) );
}
const QString & SampleClip::sampleFile() const
{
return m_sampleBuffer->audioFile();
}
void SampleClip::setSampleBuffer( SampleBuffer* sb )
{
Engine::audioEngine()->requestChangeInModel();
sharedObject::unref( m_sampleBuffer );
Engine::audioEngine()->doneChangeInModel();
m_sampleBuffer = sb;
updateLength();
emit sampleChanged();
}
void SampleClip::setSampleFile( const QString & _sf )
{
int length;
if ( _sf.isEmpty() )
{ //When creating an empty sample clip make it a bar long
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
float den = Engine::getSong()->getTimeSigModel().getDenominator();
length = DefaultTicksPerBar * ( nom / den );
}
else
{ //Otherwise set it to the sample's length
m_sampleBuffer->setAudioFile( _sf );
length = sampleLength();
}
changeLength(length);
setStartTimeOffset( 0 );
emit sampleChanged();
emit playbackPositionChanged();
}
void SampleClip::toggleRecord()
{
m_recordModel.setValue( !m_recordModel.value() );
emit dataChanged();
}
void SampleClip::playbackPositionChanged()
{
Engine::audioEngine()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle );
auto st = dynamic_cast<SampleTrack*>(getTrack());
st->setPlayingClips( false );
}
void SampleClip::updateTrackClips()
{
auto sampletrack = dynamic_cast<SampleTrack*>(getTrack());
if( sampletrack)
{
sampletrack->updateClips();
}
}
bool SampleClip::isPlaying() const
{
return m_isPlaying;
}
void SampleClip::setIsPlaying(bool isPlaying)
{
m_isPlaying = isPlaying;
}
void SampleClip::updateLength()
{
emit sampleChanged();
}
TimePos SampleClip::sampleLength() const
{
return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() );
}
void SampleClip::setSampleStartFrame(f_cnt_t startFrame)
{
m_sampleBuffer->setStartFrame( startFrame );
}
void SampleClip::setSamplePlayLength(f_cnt_t length)
{
m_sampleBuffer->setEndFrame( length );
}
void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
if( _this.parentNode().nodeName() == "clipboard" )
{
_this.setAttribute( "pos", -1 );
}
else
{
_this.setAttribute( "pos", startPosition() );
}
_this.setAttribute( "len", length() );
_this.setAttribute( "muted", isMuted() );
_this.setAttribute( "src", sampleFile() );
_this.setAttribute( "off", startTimeOffset() );
if( sampleFile() == "" )
{
QString s;
_this.setAttribute( "data", m_sampleBuffer->toBase64( s ) );
}
_this.setAttribute( "sample_rate", m_sampleBuffer->sampleRate());
if( usesCustomClipColor() )
{
_this.setAttribute( "color", color().name() );
}
if (m_sampleBuffer->reversed())
{
_this.setAttribute("reversed", "true");
}
// TODO: start- and end-frame
}
void SampleClip::loadSettings( const QDomElement & _this )
{
if( _this.attribute( "pos" ).toInt() >= 0 )
{
movePosition( _this.attribute( "pos" ).toInt() );
}
setSampleFile( _this.attribute( "src" ) );
if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) )
{
m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) );
}
changeLength( _this.attribute( "len" ).toInt() );
setMuted( _this.attribute( "muted" ).toInt() );
setStartTimeOffset( _this.attribute( "off" ).toInt() );
if ( _this.hasAttribute( "sample_rate" ) ) {
m_sampleBuffer->setSampleRate( _this.attribute( "sample_rate" ).toInt() );
}
if( _this.hasAttribute( "color" ) )
{
useCustomClipColor( true );
setColor( _this.attribute( "color" ) );
}
else
{
useCustomClipColor(false);
}
if(_this.hasAttribute("reversed"))
{
m_sampleBuffer->setReversed(true);
emit wasReversed(); // tell SampleClipView to update the view
}
}
gui::ClipView * SampleClip::createView( gui::TrackView * _tv )
{
return new gui::SampleClipView( this, _tv );
}
} // namespace lmms

View File

@@ -23,13 +23,16 @@
*/
#include "SamplePlayHandle.h"
#include "AudioEngine.h"
#include "AudioPort.h"
#include "BBTrack.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "Note.h"
#include "PatternTrack.h"
#include "SampleClip.h"
#include "SampleTrack.h"
namespace lmms
{
SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) :
@@ -40,8 +43,8 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo
m_ownAudioPort( ownAudioPort ),
m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ),
m_volumeModel( &m_defaultVolumeModel ),
m_track( NULL ),
m_bbTrack( NULL )
m_track( nullptr ),
m_patternTrack( nullptr )
{
if (ownAudioPort)
{
@@ -61,11 +64,11 @@ SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) :
SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) :
SamplePlayHandle( tco->sampleBuffer() , false)
SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) :
SamplePlayHandle( clip->sampleBuffer() , false)
{
m_track = tco->getTrack();
setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() );
m_track = clip->getTrack();
setAudioPort( ( (SampleTrack *)clip->getTrack() )->audioPort() );
}
@@ -85,7 +88,7 @@ SamplePlayHandle::~SamplePlayHandle()
void SamplePlayHandle::play( sampleFrame * buffer )
{
const fpp_t fpp = Engine::mixer()->framesPerPeriod();
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();
//play( 0, _try_parallelizing );
if( framesDone() >= totalFrames() )
{
@@ -105,15 +108,16 @@ void SamplePlayHandle::play( sampleFrame * buffer )
}
if( !( m_track && m_track->isMuted() )
&& !( m_bbTrack && m_bbTrack->isMuted() ) )
&& !(m_patternTrack && m_patternTrack->isMuted()))
{
/* stereoVolumeVector v =
/* StereoVolumeVector v =
{ { m_volumeModel->value() / DefaultVolume,
m_volumeModel->value() / DefaultVolume } };*/
if( ! m_sampleBuffer->play( workingBuffer, &m_state, frames,
BaseFreq ) )
// SamplePlayHandle always plays the sample at its original pitch;
// it is used only for previews, SampleTracks and the metronome.
if (!m_sampleBuffer->play(workingBuffer, &m_state, frames, DefaultBaseFreq))
{
memset( workingBuffer, 0, frames * sizeof( sampleFrame ) );
memset(workingBuffer, 0, frames * sizeof(sampleFrame));
}
}
@@ -133,7 +137,7 @@ bool SamplePlayHandle::isFinished() const
bool SamplePlayHandle::isFromTrack( const Track * _track ) const
{
return m_track == _track || m_bbTrack == _track;
return m_track == _track || m_patternTrack == _track;
}
@@ -141,9 +145,9 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const
f_cnt_t SamplePlayHandle::totalFrames() const
{
return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / m_sampleBuffer->sampleRate() );
return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) *
( Engine::audioEngine()->processingSampleRate() / m_sampleBuffer->sampleRate() );
}
} // namespace lmms

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 ) :
namespace lmms
{
SampleRecordHandle::SampleRecordHandle( SampleClip* clip ) :
PlayHandle( TypeSamplePlayHandle ),
m_framesRecorded( 0 ),
m_minLength( tco->length() ),
m_track( tco->getTrack() ),
m_bbTrack( NULL ),
m_tco( tco )
m_minLength( clip->length() ),
m_track( clip->getTrack() ),
m_patternTrack( nullptr ),
m_clip( clip )
{
}
@@ -52,7 +55,7 @@ SampleRecordHandle::~SampleRecordHandle()
{
SampleBuffer* sb;
createSampleBuffer( &sb );
m_tco->setSampleBuffer( sb );
m_clip->setSampleBuffer( sb );
}
while( !m_buffers.empty() )
@@ -60,7 +63,7 @@ SampleRecordHandle::~SampleRecordHandle()
delete[] m_buffers.front().first;
m_buffers.erase( m_buffers.begin() );
}
m_tco->setRecord( false );
m_clip->setRecord( false );
}
@@ -68,15 +71,15 @@ SampleRecordHandle::~SampleRecordHandle()
void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ )
{
const sampleFrame * recbuf = Engine::mixer()->inputBuffer();
const f_cnt_t frames = Engine::mixer()->inputBufferFrames();
const sampleFrame * recbuf = Engine::audioEngine()->inputBuffer();
const f_cnt_t frames = Engine::audioEngine()->inputBufferFrames();
writeBuffer( recbuf, frames );
m_framesRecorded += frames;
MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() );
TimePos len = (tick_t)( m_framesRecorded / Engine::framesPerTick() );
if( len > m_minLength )
{
// m_tco->changeLength( len );
// m_clip->changeLength( len );
m_minLength = len;
}
}
@@ -94,7 +97,7 @@ bool SampleRecordHandle::isFinished() const
bool SampleRecordHandle::isFromTrack( const Track * _track ) const
{
return( m_track == _track || m_bbTrack == _track );
return (m_track == _track || m_patternTrack == _track);
}
@@ -112,16 +115,15 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
{
const f_cnt_t frames = framesRecorded();
// create buffer to store all recorded buffers in
sampleFrame * data = new sampleFrame[frames];
auto data = new sampleFrame[frames];
// make sure buffer is cleaned up properly at the end...
sampleFrame * data_ptr = data;
assert( data != NULL );
assert( data != nullptr );
// now copy all buffers into big buffer
for( bufferList::const_iterator it = m_buffers.begin();
it != m_buffers.end(); ++it )
for( bufferList::const_iterator it = m_buffers.begin(); it != m_buffers.end(); ++it )
{
memcpy( data_ptr, ( *it ).first, ( *it ).second *
sizeof( sampleFrame ) );
@@ -129,17 +131,16 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
}
// create according sample-buffer out of big buffer
*sampleBuf = new SampleBuffer( data, frames );
( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() );
( *sampleBuf)->setSampleRate( Engine::audioEngine()->inputSampleRate() );
delete[] data;
}
void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
const f_cnt_t _frames )
void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames )
{
sampleFrame * buf = new sampleFrame[_frames];
auto buf = new sampleFrame[_frames];
for( f_cnt_t frame = 0; frame < _frames; ++frame )
{
for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
@@ -151,4 +152,4 @@ void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
}
} // namespace lmms

129
src/core/Scale.cpp Normal file
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,20 +19,27 @@
*/
#include "StepRecorder.h"
#include <QKeyEvent>
#include "MidiClip.h"
#include "StepRecorderWidget.h"
#include "PianoRoll.h"
#include <QPainter>
#include <climits>
namespace lmms
{
using std::min;
using std::max;
const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70;
StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget):
StepRecorder::StepRecorder(gui::PianoRoll& pianoRoll, gui::StepRecorderWidget& stepRecorderWidget):
m_pianoRoll(pianoRoll),
m_stepRecorderWidget(stepRecorderWidget)
m_stepRecorderWidget(stepRecorderWidget),
m_midiClip(nullptr)
{
m_stepRecorderWidget.hide();
}
@@ -42,7 +49,7 @@ void StepRecorder::initialize()
connect(&m_updateReleasedTimer, SIGNAL(timeout()), this, SLOT(removeNotesReleasedForTooLong()));
}
void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength)
void StepRecorder::start(const TimePos& currentPosition, const TimePos& stepLength)
{
m_isRecording = true;
@@ -52,7 +59,7 @@ void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLe
const int q = m_pianoRoll.quantization();
const int curPosTicks = currentPosition.getTicks();
const int QuantizedPosTicks = (curPosTicks / q) * q;
const MidiTime& QuantizedPos = MidiTime(QuantizedPosTicks);
const TimePos& QuantizedPos = TimePos(QuantizedPosTicks);
m_curStepStartPos = QuantizedPos;
m_curStepLength = 0;
@@ -108,7 +115,7 @@ void StepRecorder::noteReleased(const Note & n)
m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS);
}
//check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step
//check if all note are released, apply notes to clips (or dimiss if length is zero) and prepare to record next step
if(allCurStepNotesReleased())
{
if(m_curStepLength > 0)
@@ -153,7 +160,7 @@ bool StepRecorder::keyPressEvent(QKeyEvent* ke)
return event_handled;
}
void StepRecorder::setStepsLength(const MidiTime& newLength)
void StepRecorder::setStepsLength(const TimePos& newLength)
{
if(m_isStepInProgress)
{
@@ -225,16 +232,16 @@ void StepRecorder::stepBackwards()
void StepRecorder::applyStep()
{
m_pattern->addJournalCheckPoint();
m_midiClip->addJournalCheckPoint();
for (const StepNote* stepNote : m_curStepNotes)
{
m_pattern->addNote(stepNote->m_note, false);
m_midiClip->addNote(stepNote->m_note, false);
}
m_pattern->rearrangeAllNotes();
m_pattern->updateLength();
m_pattern->dataChanged();
m_midiClip->rearrangeAllNotes();
m_midiClip->updateLength();
m_midiClip->dataChanged();
Engine::getSong()->setModified();
prepareNewStep();
@@ -266,14 +273,14 @@ void StepRecorder::prepareNewStep()
updateWidget();
}
void StepRecorder::setCurrentPattern( Pattern* newPattern )
void StepRecorder::setCurrentMidiClip( MidiClip* newMidiClip )
{
if(m_pattern != NULL && m_pattern != newPattern)
if(m_midiClip != nullptr && m_midiClip != newMidiClip)
{
dismissStep();
}
m_pattern = newPattern;
m_midiClip = newMidiClip;
}
void StepRecorder::removeNotesReleasedForTooLong()
@@ -318,7 +325,7 @@ void StepRecorder::removeNotesReleasedForTooLong()
}
}
MidiTime StepRecorder::getCurStepEndPos()
TimePos StepRecorder::getCurStepEndPos()
{
return m_curStepStartPos + m_curStepLength;
}
@@ -364,3 +371,5 @@ StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
return nullptr;
}
} // namespace lmms

View File

@@ -31,6 +31,10 @@
#include "Song.h"
namespace lmms
{
TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
const float _max, const float _step,
const float _scale, Model * _parent,
@@ -41,21 +45,14 @@ TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
m_scale( _scale ),
m_custom( _parent )
{
connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ),
this, SLOT( calculateTempoSyncTime( bpm_t ) ),
connect( Engine::getSong(), SIGNAL(tempoChanged(lmms::bpm_t)),
this, SLOT(calculateTempoSyncTime(lmms::bpm_t)),
Qt::DirectConnection );
}
TempoSyncKnobModel::~TempoSyncKnobModel()
{
}
void TempoSyncKnobModel::setTempoSync( QAction * _item )
{
setTempoSync( _item->data().toInt() );
@@ -154,8 +151,8 @@ void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode )
m_tempoSyncMode = _new_mode;
if( _new_mode == SyncCustom )
{
connect( &m_custom, SIGNAL( dataChanged() ),
this, SLOT( updateCustom() ),
connect( &m_custom, SIGNAL(dataChanged()),
this, SLOT(updateCustom()),
Qt::DirectConnection );
}
}
@@ -181,8 +178,4 @@ void TempoSyncKnobModel::updateCustom()
}
} // namespace lmms

224
src/core/TimePos.cpp Normal file
View File

@@ -0,0 +1,224 @@
/*
* TimePos.cpp - Class that encapsulates the position of a note/event in terms of
* its bar, beat and tick.
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "TimePos.h"
#include "MeterModel.h"
namespace lmms
{
TimeSig::TimeSig( int num, int denom ) :
m_num(num),
m_denom(denom)
{
}
TimeSig::TimeSig( const MeterModel &model ) :
m_num(model.getNumerator()),
m_denom(model.getDenominator())
{
}
int TimeSig::numerator() const
{
return m_num;
}
int TimeSig::denominator() const
{
return m_denom;
}
TimePos::TimePos( const bar_t bar, const tick_t ticks ) :
m_ticks( bar * s_ticksPerBar + ticks )
{
}
TimePos::TimePos( const tick_t ticks ) :
m_ticks( ticks )
{
}
TimePos TimePos::quantize(float bars) const
{
//The intervals we should snap to, our new position should be a factor of this
int interval = s_ticksPerBar * bars;
//The lower position we could snap to
int lowPos = m_ticks / interval;
//Offset from the lower position
int offset = m_ticks % interval;
//1 if we should snap up, 0 if we shouldn't
// Ternary expression is making sure that the snap happens in the direction to
// the right even if m_ticks is negative and the offset is exactly half-way
// More details on issue #5840 and PR #5847
int snapUp = ((2 * offset) == -interval)
? 0
: (2 * offset) / interval;
return (lowPos + snapUp) * interval;
}
TimePos TimePos::toAbsoluteBar() const
{
return getBar() * s_ticksPerBar;
}
TimePos& TimePos::operator+=( const TimePos& time )
{
m_ticks += time.m_ticks;
return *this;
}
TimePos& TimePos::operator-=( const TimePos& time )
{
m_ticks -= time.m_ticks;
return *this;
}
bar_t TimePos::getBar() const
{
return m_ticks / s_ticksPerBar;
}
bar_t TimePos::nextFullBar() const
{
return ( m_ticks + ( s_ticksPerBar - 1 ) ) / s_ticksPerBar;
}
void TimePos::setTicks( tick_t ticks )
{
m_ticks = ticks;
}
tick_t TimePos::getTicks() const
{
return m_ticks;
}
TimePos::operator int() const
{
return m_ticks;
}
tick_t TimePos::ticksPerBeat( const TimeSig &sig ) const
{
// (number of ticks per bar) divided by (number of beats per bar)
return ticksPerBar(sig) / sig.numerator();
}
tick_t TimePos::getTickWithinBar( const TimeSig &sig ) const
{
return m_ticks % ticksPerBar( sig );
}
tick_t TimePos::getBeatWithinBar( const TimeSig &sig ) const
{
return getTickWithinBar( sig ) / ticksPerBeat( sig );
}
tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const
{
return getTickWithinBar( sig ) % ticksPerBeat( sig );
}
f_cnt_t TimePos::frames( const float framesPerTick ) const
{
if( m_ticks >= 0 )
{
return static_cast<f_cnt_t>( m_ticks * framesPerTick );
}
return 0;
}
double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const
{
return ticksToMilliseconds( getTicks(), beatsPerMinute );
}
TimePos TimePos::fromFrames( const f_cnt_t frames, const float framesPerTick )
{
return TimePos( static_cast<int>( frames / framesPerTick ) );
}
tick_t TimePos::ticksPerBar()
{
return s_ticksPerBar;
}
tick_t TimePos::ticksPerBar( const TimeSig &sig )
{
return DefaultTicksPerBar * sig.numerator() / sig.denominator();
}
int TimePos::stepsPerBar()
{
int steps = ticksPerBar() / DefaultBeatsPerBar;
return qMax( 1, steps );
}
void TimePos::setTicksPerBar( tick_t tpb )
{
s_ticksPerBar = tpb;
}
TimePos TimePos::stepPosition( int step )
{
return step * ticksPerBar() / stepsPerBar();
}
double TimePos::ticksToMilliseconds( tick_t ticks, bpm_t beatsPerMinute )
{
return TimePos::ticksToMilliseconds( static_cast<double>(ticks), beatsPerMinute );
}
double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute)
{
// 60 * 1000 / 48 = 1250
return ( ticks * 1250 ) / beatsPerMinute;
}
} // namespace lmms

View File

@@ -26,6 +26,10 @@
#include "ToolPlugin.h"
namespace lmms
{
ToolPlugin::ToolPlugin( const Descriptor * _descriptor, Model * _parent ) :
Plugin( _descriptor, _parent )
{
@@ -34,16 +38,9 @@ ToolPlugin::ToolPlugin( const Descriptor * _descriptor, Model * _parent ) :
ToolPlugin::~ToolPlugin()
{
}
ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _parent )
{
Plugin * p = Plugin::instantiate( _plugin_name, _parent, NULL );
Plugin * p = Plugin::instantiate( _plugin_name, _parent, nullptr );
// check whether instantiated plugin is a tool
if( p->type() == Plugin::Tool )
{
@@ -53,6 +50,8 @@ ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _par
// not quite... so delete plugin
delete p;
return NULL;
return nullptr;
}
} // namespace lmms

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* TrackContainer.cpp - implementation of base class for all trackcontainers
* like Song-Editor, BB-Editor...
* like Song-Editor, Pattern Editor...
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
@@ -24,26 +24,29 @@
*/
#include <QApplication>
#include <QCoreApplication>
#include <QProgressDialog>
#include <QDomElement>
#include <QWriteLocker>
#include "AutomationPattern.h"
#include "AutomationTrack.h"
#include "BBTrack.h"
#include "BBTrackContainer.h"
#include "AutomationClip.h"
#include "embed.h"
#include "TrackContainer.h"
#include "InstrumentTrack.h"
#include "PatternClip.h"
#include "PatternStore.h"
#include "PatternTrack.h"
#include "Song.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "TextFloat.h"
namespace lmms
{
TrackContainer::TrackContainer() :
Model( NULL ),
Model( nullptr ),
JournallingObject(),
m_tracksMutex(),
m_tracks()
@@ -68,9 +71,9 @@ void TrackContainer::saveSettings( QDomDocument & _doc, QDomElement & _this )
// save settings of each track
m_tracksMutex.lockForRead();
for( int i = 0; i < m_tracks.size(); ++i )
for (const auto& track : m_tracks)
{
m_tracks[i]->saveState( _doc, _this );
track->saveState(_doc, _this);
}
m_tracksMutex.unlock();
}
@@ -86,16 +89,16 @@ void TrackContainer::loadSettings( const QDomElement & _this )
clearAllTracks();
}
static QProgressDialog * pd = NULL;
bool was_null = ( pd == NULL );
if( !journalRestore && gui != nullptr )
static QProgressDialog * pd = nullptr;
bool was_null = ( pd == nullptr );
if (!journalRestore && gui::getGUI() != nullptr)
{
if( pd == NULL )
if( pd == nullptr )
{
pd = new QProgressDialog( tr( "Loading project..." ),
tr( "Cancel" ), 0,
Engine::getSong()->getLoadingTrackCount(),
gui->mainWindow() );
gui::getGUI()->mainWindow());
pd->setWindowModality( Qt::ApplicationModal );
pd->setWindowTitle( tr( "Please wait..." ) );
pd->show();
@@ -105,16 +108,16 @@ void TrackContainer::loadSettings( const QDomElement & _this )
QDomNode node = _this.firstChild();
while( !node.isNull() )
{
if( pd != NULL )
if( pd != nullptr )
{
pd->setValue( pd->value() + 1 );
QCoreApplication::instance()->processEvents(
QEventLoop::AllEvents, 100 );
if( pd->wasCanceled() )
{
if ( gui )
if (gui::getGUI() != nullptr)
{
TextFloat::displayMessage( tr( "Loading cancelled" ),
gui::TextFloat::displayMessage( tr( "Loading cancelled" ),
tr( "Project loading was cancelled." ),
embed::getIconPixmap( "project_file", 24, 24 ),
2000 );
@@ -130,7 +133,7 @@ void TrackContainer::loadSettings( const QDomElement & _this )
QString trackName = node.toElement().hasAttribute( "name" ) ?
node.toElement().attribute( "name" ) :
node.firstChild().toElement().attribute( "name" );
if( pd != NULL )
if( pd != nullptr )
{
pd->setLabelText( tr("Loading Track %1 (%2/Total %3)").arg( trackName ).
arg( pd->value() + 1 ).arg( Engine::getSong()->getLoadingTrackCount() ) );
@@ -140,12 +143,12 @@ void TrackContainer::loadSettings( const QDomElement & _this )
node = node.nextSibling();
}
if( pd != NULL )
if( pd != nullptr )
{
if( was_null )
{
delete pd;
pd = NULL;
pd = nullptr;
}
}
}
@@ -157,9 +160,9 @@ int TrackContainer::countTracks( Track::TrackTypes _tt ) const
{
int cnt = 0;
m_tracksMutex.lockForRead();
for( int i = 0; i < m_tracks.size(); ++i )
for (const auto& track : m_tracks)
{
if( m_tracks[i]->type() == _tt || _tt == Track::NumTrackTypes )
if (track->type() == _tt || _tt == Track::NumTrackTypes)
{
++cnt;
}
@@ -235,10 +238,9 @@ void TrackContainer::clearAllTracks()
bool TrackContainer::isEmpty() const
{
for( TrackList::const_iterator it = m_tracks.begin();
it != m_tracks.end(); ++it )
for (const auto& track : m_tracks)
{
if( !( *it )->getTCOs().isEmpty() )
if (!track->getClips().isEmpty())
{
return false;
}
@@ -248,15 +250,15 @@ bool TrackContainer::isEmpty() const
AutomatedValueMap TrackContainer::automatedValuesAt(MidiTime time, int tcoNum) const
AutomatedValueMap TrackContainer::automatedValuesAt(TimePos time, int clipNum) const
{
return automatedValuesFromTracks(tracks(), time, tcoNum);
return automatedValuesFromTracks(tracks(), time, clipNum);
}
AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, MidiTime time, int tcoNum)
AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tracks, TimePos time, int clipNum)
{
Track::tcoVector tcos;
Track::clipVector clips;
for (Track* track: tracks)
{
@@ -268,12 +270,12 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
{
case Track::AutomationTrack:
case Track::HiddenAutomationTrack:
case Track::BBTrack:
if (tcoNum < 0) {
track->getTCOsInRange(tcos, 0, time);
case Track::PatternTrack:
if (clipNum < 0) {
track->getClipsInRange(clips, 0, time);
} else {
Q_ASSERT(track->numOfTCOs() > tcoNum);
tcos << track->getTCO(tcoNum);
Q_ASSERT(track->numOfClips() > clipNum);
clips << track->getClip(clipNum);
}
default:
break;
@@ -282,20 +284,20 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
AutomatedValueMap valueMap;
Q_ASSERT(std::is_sorted(tcos.begin(), tcos.end(), TrackContentObject::comparePosition));
Q_ASSERT(std::is_sorted(clips.begin(), clips.end(), Clip::comparePosition));
for(TrackContentObject* tco : tcos)
for(Clip* clip : clips)
{
if (tco->isMuted() || tco->startPosition() > time) {
if (clip->isMuted() || clip->startPosition() > time) {
continue;
}
if (auto* p = dynamic_cast<AutomationPattern *>(tco))
if (auto* p = dynamic_cast<AutomationClip *>(clip))
{
if (! p->hasAutomation()) {
continue;
}
MidiTime relTime = time - p->startPosition();
TimePos relTime = time - p->startPosition();
if (! p->getAutoResize()) {
relTime = qMin(relTime, p->length());
}
@@ -306,19 +308,19 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
valueMap[model] = value;
}
}
else if (auto* bb = dynamic_cast<BBTCO *>(tco))
else if (auto* pattern = dynamic_cast<PatternClip*>(clip))
{
auto bbIndex = dynamic_cast<class BBTrack*>(bb->getTrack())->index();
auto bbContainer = Engine::getBBTrackContainer();
auto patIndex = dynamic_cast<class PatternTrack*>(pattern->getTrack())->patternIndex();
auto patStore = Engine::patternStore();
MidiTime bbTime = time - tco->startPosition();
bbTime = std::min(bbTime, tco->length());
bbTime = bbTime % (bbContainer->lengthOfBB(bbIndex) * MidiTime::ticksPerTact());
TimePos patTime = time - clip->startPosition();
patTime = std::min(patTime, clip->length());
patTime = patTime % (patStore->lengthOfPattern(patIndex) * TimePos::ticksPerBar());
auto bbValues = bbContainer->automatedValuesAt(bbTime, bbIndex);
for (auto it=bbValues.begin(); it != bbValues.end(); it++)
auto patValues = patStore->automatedValuesAt(patTime, patIndex);
for (auto it=patValues.begin(); it != patValues.end(); it++)
{
// override old values, bb track with the highest index takes precedence
// override old values, pattern track with the highest index takes precedence
valueMap[it.key()] = it.value();
}
}
@@ -332,20 +334,4 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra
};
DummyTrackContainer::DummyTrackContainer() :
TrackContainer(),
m_dummyInstrumentTrack( NULL )
{
setJournalling( false );
m_dummyInstrumentTrack = dynamic_cast<InstrumentTrack *>(
Track::create( Track::InstrumentTrack,
this ) );
m_dummyInstrumentTrack->setJournalling( false );
}
} // namespace lmms

View File

@@ -2,8 +2,9 @@
#include "interpolation.h"
ValueBuffer::ValueBuffer()
{}
namespace lmms
{
ValueBuffer::ValueBuffer(int length)
: std::vector<float>(length)
@@ -41,3 +42,6 @@ void ValueBuffer::interpolate(float start, float end_)
return linearInterpolate( start, end_, i++ / length());
});
}
} // namespace lmms

View File

@@ -23,121 +23,50 @@
*
*/
#include <QDebug>
#include "VstSyncController.h"
#include <stdexcept>
#include <QDebug>
#include <QUuid>
#include "AudioEngine.h"
#include "ConfigManager.h"
#include "Engine.h"
#include "Mixer.h"
#include "VstSyncController.h"
#include "RemotePlugin.h"
#ifndef USE_QT_SHMEM
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
VstSyncController::VstSyncController() :
m_syncData( NULL ),
m_shmID( -1 ),
m_shm( "/usr/bin/lmms" )
namespace lmms
{
if( ConfigManager::inst()->value( "ui", "syncvstplugins" ).toInt() )
{
connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
#ifdef USE_QT_SHMEM
if ( m_shm.create( sizeof( VstSyncData ) ) )
{
m_syncData = (VstSyncData*) m_shm.data();
}
else
{
qWarning() << QString( "Failed to allocate shared memory for VST sync: %1" ).arg( m_shm.errorString() );
}
#else
key_t key; // make the key:
if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 )
{
qWarning( "VstSyncController: ftok() failed" );
}
else
{ // connect to shared memory segment
if( ( m_shmID = shmget( key, sizeof( VstSyncData ), 0644 | IPC_CREAT ) ) == -1 )
{
qWarning( "VstSyncController: shmget() failed" );
}
else
{ // attach segment
m_syncData = (VstSyncData *)shmat( m_shmID, 0, 0 );
if( m_syncData == (VstSyncData *)( -1 ) )
{
qWarning( "VstSyncController: shmat() failed" );
}
}
}
#endif
}
else
{
qWarning( "VST sync support disabled in your configuration" );
}
if( m_syncData == NULL )
VstSyncController::VstSyncController()
{
try
{
m_syncData = new VstSyncData;
m_syncData->hasSHM = false;
m_syncData.create(QUuid::createUuid().toString().toStdString());
}
else
catch (const std::runtime_error& error)
{
m_syncData->hasSHM = true;
qCritical() << "Failed to allocate shared memory for VST sync:" << error.what();
return;
}
m_syncData->isPlaying = false;
m_syncData->m_bufferSize = Engine::mixer()->framesPerPeriod();
m_syncData->m_bufferSize = Engine::audioEngine()->framesPerPeriod();
m_syncData->timeSigNumer = 4;
m_syncData->timeSigDenom = 4;
connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, this, &VstSyncController::updateSampleRate);
updateSampleRate();
}
VstSyncController::~VstSyncController()
void VstSyncController::setAbsolutePosition(double ticks)
{
if( m_syncData->hasSHM == false )
{
delete m_syncData;
}
else
{
#ifdef USE_QT_SHMEM
if( m_shm.data() )
{
// detach shared memory, delete it:
m_shm.detach();
}
#else
if( shmdt( m_syncData ) != -1 )
{
shmctl( m_shmID, IPC_RMID, NULL );
}
else
{
qWarning( "VstSyncController: shmdt() failed" );
}
#endif
}
}
if (!m_syncData) { return; }
void VstSyncController::setAbsolutePosition( double ticks )
{
#ifdef VST_SNC_LATENCY
m_syncData->ppqPos = ( ( ticks + 0 ) / 48.0 ) - m_syncData->m_latency;
#else
@@ -147,8 +76,19 @@ void VstSyncController::setAbsolutePosition( double ticks )
void VstSyncController::setTempo( int newTempo )
void VstSyncController::setPlaybackState(bool enabled)
{
if (!m_syncData) { return; }
m_syncData->isPlaying = enabled;
}
void VstSyncController::setTempo(int newTempo)
{
if (!m_syncData) { return; }
m_syncData->m_bpm = newTempo;
#ifdef VST_SNC_LATENCY
@@ -159,8 +99,20 @@ void VstSyncController::setTempo( int newTempo )
void VstSyncController::startCycle( int startTick, int endTick )
void VstSyncController::setTimeSignature(int num, int denom)
{
if (!m_syncData) { return; }
m_syncData->timeSigNumer = num;
m_syncData->timeSigDenom = denom;
}
void VstSyncController::startCycle(int startTick, int endTick)
{
if (!m_syncData) { return; }
m_syncData->isCycle = true;
m_syncData->cycleStart = startTick / (float)48;
m_syncData->cycleEnd = endTick / (float)48;
@@ -168,9 +120,29 @@ void VstSyncController::startCycle( int startTick, int endTick )
void VstSyncController::stopCycle()
{
if (!m_syncData) { return; }
m_syncData->isCycle = false;
}
void VstSyncController::setPlaybackJumped(bool jumped)
{
if (!m_syncData) { return; }
m_syncData->m_playbackJumped = jumped;
}
void VstSyncController::update()
{
m_syncData->m_bufferSize = Engine::mixer()->framesPerPeriod();
if (!m_syncData) { return; }
m_syncData->m_bufferSize = Engine::audioEngine()->framesPerPeriod();
#ifdef VST_SNC_LATENCY
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );
@@ -181,7 +153,9 @@ void VstSyncController::update()
void VstSyncController::updateSampleRate()
{
m_syncData->m_sampleRate = Engine::mixer()->processingSampleRate();
if (!m_syncData) { return; }
m_syncData->m_sampleRate = Engine::audioEngine()->processingSampleRate();
#ifdef VST_SNC_LATENCY
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );
@@ -189,6 +163,4 @@ void VstSyncController::updateSampleRate()
}
} // namespace lmms

View File

@@ -22,28 +22,27 @@
*
*/
#include <QComboBox>
#include <QLineEdit>
#include "AudioAlsa.h"
#ifdef LMMS_HAVE_ALSA
#include "endian_handling.h"
#include "AudioEngine.h"
#include "ConfigManager.h"
#include "Engine.h"
#include "Mixer.h"
#include "gui_templates.h"
namespace lmms
{
AudioAlsa::AudioAlsa( bool & _success_ful, Mixer* _mixer ) :
AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) :
AudioDevice( qBound<ch_cnt_t>(
DEFAULT_CHANNELS,
ConfigManager::inst()->value( "audioalsa", "channels" ).toInt(),
SURROUND_CHANNELS ), _mixer ),
m_handle( NULL ),
m_hwParams( NULL ),
m_swParams( NULL ),
SURROUND_CHANNELS ), _audioEngine ),
m_handle( nullptr ),
m_hwParams( nullptr ),
m_swParams( nullptr ),
m_convertEndian( false )
{
_success_ful = false;
@@ -107,17 +106,17 @@ AudioAlsa::AudioAlsa( bool & _success_ful, Mixer* _mixer ) :
AudioAlsa::~AudioAlsa()
{
stopProcessing();
if( m_handle != NULL )
if( m_handle != nullptr )
{
snd_pcm_close( m_handle );
}
if( m_hwParams != NULL )
if( m_hwParams != nullptr )
{
snd_pcm_hw_params_free( m_hwParams );
}
if( m_swParams != NULL )
if( m_swParams != nullptr )
{
snd_pcm_sw_params_free( m_swParams );
}
@@ -131,7 +130,7 @@ QString AudioAlsa::probeDevice()
QString dev = ConfigManager::inst()->value( "audioalsa", "device" );
if( dev == "" )
{
if( getenv( "AUDIODEV" ) != NULL )
if( getenv( "AUDIODEV" ) != nullptr )
{
return getenv( "AUDIODEV" );
}
@@ -171,7 +170,7 @@ AudioAlsa::DeviceInfoCollection AudioAlsa::getAvailableDevices()
}
char** n = hints;
while (*n != NULL)
while (*n != nullptr)
{
char *name = snd_device_name_get_hint(*n, "NAME");
char *description = snd_device_name_get_hint(*n, "DESC");
@@ -255,9 +254,9 @@ void AudioAlsa::applyQualitySettings()
{
if( hqAudio() )
{
setSampleRate( Engine::mixer()->processingSampleRate() );
setSampleRate( Engine::audioEngine()->processingSampleRate() );
if( m_handle != NULL )
if( m_handle != nullptr )
{
snd_pcm_close( m_handle );
}
@@ -296,14 +295,11 @@ void AudioAlsa::applyQualitySettings()
void AudioAlsa::run()
{
surroundSampleFrame * temp =
new surroundSampleFrame[mixer()->framesPerPeriod()];
int_sample_t * outbuf =
new int_sample_t[mixer()->framesPerPeriod() *
channels()];
int_sample_t * pcmbuf = new int_sample_t[m_periodSize * channels()];
auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()];
auto outbuf = new int_sample_t[audioEngine()->framesPerPeriod() * channels()];
auto pcmbuf = new int_sample_t[m_periodSize * channels()];
int outbuf_size = mixer()->framesPerPeriod() * channels();
int outbuf_size = audioEngine()->framesPerPeriod() * channels();
int outbuf_pos = 0;
int pcmbuf_size = m_periodSize * channels();
@@ -328,7 +324,7 @@ void AudioAlsa::run()
outbuf_size = frames * channels();
convertToS16( temp, frames,
mixer()->masterGain(),
audioEngine()->masterGain(),
outbuf,
m_convertEndian );
}
@@ -429,7 +425,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
sampleRate(), 0 ) ) < 0 )
{
if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams,
mixer()->baseSampleRate(), 0 ) ) < 0 )
audioEngine()->baseSampleRate(), 0 ) ) < 0 )
{
printf( "Could not set sample rate: %s\n",
snd_strerror( err ) );
@@ -437,7 +433,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
}
}
m_periodSize = mixer()->framesPerPeriod();
m_periodSize = audioEngine()->framesPerPeriod();
m_bufferSize = m_periodSize * 8;
dir = 0;
err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams,
@@ -544,4 +540,6 @@ int AudioAlsa::setSWParams()
return 0; // all ok
}
#endif
} // namespace lmms
#endif // LMMS_HAVE_ALSA

View File

@@ -1,5 +1,5 @@
/*
* AudioDevice.cpp - base-class for audio-devices used by LMMS-mixer
* AudioDevice.cpp - base-class for audio-devices used by LMMS audio engine
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
@@ -25,23 +25,24 @@
#include <cstring>
#include "AudioDevice.h"
#include "AudioEngine.h"
#include "ConfigManager.h"
#include "debug.h"
#include "Mixer.h"
namespace lmms
{
AudioDevice::AudioDevice( const ch_cnt_t _channels, Mixer* _mixer ) :
AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) :
m_supportsCapture( false ),
m_sampleRate( _mixer->processingSampleRate() ),
m_sampleRate( _audioEngine->processingSampleRate() ),
m_channels( _channels ),
m_mixer( _mixer ),
m_buffer( new surroundSampleFrame[mixer()->framesPerPeriod()] )
m_audioEngine( _audioEngine ),
m_buffer( new surroundSampleFrame[audioEngine()->framesPerPeriod()] )
{
int error;
if( ( m_srcState = src_new(
mixer()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == NULL )
audioEngine()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == nullptr )
{
printf( "Error: src_new() failed in audio_device.cpp!\n" );
}
@@ -67,7 +68,7 @@ void AudioDevice::processNextBuffer()
const fpp_t frames = getNextBuffer( m_buffer );
if( frames )
{
writeBuffer( m_buffer, frames, mixer()->masterGain() );
writeBuffer( m_buffer, frames, audioEngine()->masterGain() );
}
else
{
@@ -80,8 +81,8 @@ void AudioDevice::processNextBuffer()
fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
{
fpp_t frames = mixer()->framesPerPeriod();
const surroundSampleFrame * b = mixer()->nextBuffer();
fpp_t frames = audioEngine()->framesPerPeriod();
const surroundSampleFrame * b = audioEngine()->nextBuffer();
if( !b )
{
return 0;
@@ -91,12 +92,9 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
lock();
// resample if necessary
if( mixer()->processingSampleRate() != m_sampleRate )
if( audioEngine()->processingSampleRate() != m_sampleRate )
{
resample( b, frames, _ab, mixer()->processingSampleRate(),
m_sampleRate );
frames = frames * m_sampleRate /
mixer()->processingSampleRate();
frames = resample( b, frames, _ab, audioEngine()->processingSampleRate(), m_sampleRate );
}
else
{
@@ -106,7 +104,7 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
// release lock
unlock();
if( mixer()->hasFifoWriter() )
if( audioEngine()->hasFifoWriter() )
{
delete[] b;
}
@@ -119,7 +117,7 @@ fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
void AudioDevice::stopProcessing()
{
if( mixer()->hasFifoWriter() )
if( audioEngine()->hasFifoWriter() )
{
while( m_inProcess )
{
@@ -153,8 +151,8 @@ void AudioDevice::applyQualitySettings()
int error;
if( ( m_srcState = src_new(
mixer()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == NULL )
audioEngine()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == nullptr )
{
printf( "Error: src_new() failed in audio_device.cpp!\n" );
}
@@ -184,20 +182,20 @@ void AudioDevice::renamePort( AudioPort * )
void AudioDevice::resample( const surroundSampleFrame * _src,
fpp_t AudioDevice::resample( const surroundSampleFrame * _src,
const fpp_t _frames,
surroundSampleFrame * _dst,
const sample_rate_t _src_sr,
const sample_rate_t _dst_sr )
{
if( m_srcState == NULL )
if( m_srcState == nullptr )
{
return;
return _frames;
}
m_srcData.input_frames = _frames;
m_srcData.output_frames = _frames;
m_srcData.data_in = (float *) _src[0];
m_srcData.data_out = _dst[0];
m_srcData.data_in = const_cast<float*>(_src[0].data());
m_srcData.data_out = _dst[0].data ();
m_srcData.src_ratio = (double) _dst_sr / _src_sr;
m_srcData.end_of_input = 0;
int error;
@@ -206,6 +204,7 @@ void AudioDevice::resample( const surroundSampleFrame * _src,
printf( "AudioDevice::resample(): error while resampling: %s\n",
src_strerror( error ) );
}
return static_cast<fpp_t>(m_srcData.output_frames_gen);
}
@@ -223,7 +222,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
{
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
{
temp = static_cast<int_sample_t>( Mixer::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER );
temp = static_cast<int_sample_t>( AudioEngine::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER );
( _output_buffer + frame * channels() )[chnl] =
( temp & 0x00ff ) << 8 |
@@ -239,7 +238,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
{
( _output_buffer + frame * channels() )[chnl] =
static_cast<int_sample_t>(
Mixer::clip( _ab[frame][chnl] *
AudioEngine::clip( _ab[frame][chnl] *
_master_gain ) *
OUTPUT_SAMPLE_MULTIPLIER );
}
@@ -255,7 +254,7 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames )
{
assert( _outbuf != NULL );
assert( _outbuf != nullptr );
memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE );
}
@@ -265,7 +264,8 @@ void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames )
bool AudioDevice::hqAudio() const
{
return ConfigManager::inst()->value( "mixer", "hqaudio" ).toInt();
return ConfigManager::inst()->value( "audioengine", "hqaudio" ).toInt();
}
} // namespace lmms

View File

@@ -29,15 +29,19 @@
#include "ExportProjectDialog.h"
#include "GuiApplication.h"
namespace lmms
{
AudioFileDevice::AudioFileDevice( OutputSettings const & outputSettings,
const ch_cnt_t _channels,
const QString & _file,
Mixer* _mixer ) :
AudioDevice( _channels, _mixer ),
AudioEngine* _audioEngine ) :
AudioDevice( _channels, _audioEngine ),
m_outputFile( _file ),
m_outputSettings(outputSettings)
{
using gui::ExportProjectDialog;
setSampleRate( outputSettings.getSampleRate() );
if( m_outputFile.open( QFile::WriteOnly | QFile::Truncate ) == false )
@@ -52,9 +56,9 @@ AudioFileDevice::AudioFileDevice( OutputSettings const & outputSettings,
"file and try again!"
).arg( _file );
if( gui )
if (gui::getGUI() != nullptr)
{
QMessageBox::critical( NULL, title, message,
QMessageBox::critical( nullptr, title, message,
QMessageBox::Ok,
QMessageBox::NoButton );
}
@@ -87,3 +91,4 @@ int AudioFileDevice::writeData( const void* data, int len )
return -1;
}
} // namespace lmms

View File

@@ -22,14 +22,20 @@
*
*/
#include <QtGlobal>
#include <cmath>
#include <memory>
#include "AudioFileFlac.h"
#include "endian_handling.h"
#include "Mixer.h"
#include "AudioEngine.h"
AudioFileFlac::AudioFileFlac(OutputSettings const& outputSettings, ch_cnt_t const channels, bool& successful, QString const& file, Mixer* mixer):
AudioFileDevice(outputSettings,channels,file,mixer),
namespace lmms
{
AudioFileFlac::AudioFileFlac(OutputSettings const& outputSettings, ch_cnt_t const channels, bool& successful, QString const& file, AudioEngine* audioEngine):
AudioFileDevice(outputSettings,channels,file,audioEngine),
m_sf(nullptr)
{
successful = outputFileOpened() && startEncoding();
@@ -44,7 +50,7 @@ bool AudioFileFlac::startEncoding()
{
m_sfinfo.samplerate=sampleRate();
m_sfinfo.channels=channels();
m_sfinfo.frames = mixer()->framesPerPeriod();
m_sfinfo.frames = audioEngine()->framesPerPeriod();
m_sfinfo.sections=1;
m_sfinfo.seekable=0;
@@ -86,6 +92,7 @@ bool AudioFileFlac::startEncoding()
void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames, float master_gain)
{
OutputSettings::BitDepth depth = getOutputSettings().getBitDepth();
float clipvalue = std::nextafterf( -1.0f, 0.0f );
if (depth == OutputSettings::Depth_24Bit || depth == OutputSettings::Depth_32Bit) // Float encoding
{
@@ -94,7 +101,10 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram
{
for(ch_cnt_t channel=0; channel<channels(); ++channel)
{
buf[frame*channels() + channel] = _ab[frame][channel] * master_gain;
// Clip the negative side to just above -1.0 in order to prevent it from changing sign
// Upstream issue: https://github.com/erikd/libsndfile/issues/309
// When this commit is reverted libsndfile-1.0.29 must be made a requirement for FLAC
buf[frame*channels() + channel] = qMax( clipvalue, _ab[frame][channel] * master_gain );
}
}
sf_writef_float(m_sf,static_cast<float*>(buf.get()),frames);
@@ -117,3 +127,5 @@ void AudioFileFlac::finishEncoding()
sf_close(m_sf);
}
}
} // namespace lmms

View File

@@ -27,17 +27,18 @@
#ifdef LMMS_HAVE_MP3LAME
#include "Mixer.h"
#include <cassert>
namespace lmms
{
AudioFileMP3::AudioFileMP3( OutputSettings const & outputSettings,
const ch_cnt_t channels,
bool & successful,
const QString & file,
Mixer* mixer ) :
AudioFileDevice( outputSettings, channels, file, mixer )
AudioEngine* audioEngine ) :
AudioFileDevice( outputSettings, channels, file, audioEngine )
{
successful = true;
// For now only accept stereo sources
@@ -131,4 +132,6 @@ void AudioFileMP3::tearDownEncoder()
lame_close(m_lame);
}
#endif
} // namespace lmms
#endif // LMMS_HAVE_MP3LAME

View File

@@ -30,19 +30,23 @@
#ifdef LMMS_HAVE_OGGVORBIS
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
#include <QRandomGenerator>
#endif
#include <string>
#include <vorbis/vorbisenc.h>
#include "Mixer.h"
#include "AudioEngine.h"
namespace lmms
{
AudioFileOgg::AudioFileOgg( OutputSettings const & outputSettings,
const ch_cnt_t channels,
bool & successful,
const QString & file,
Mixer* mixer ) :
AudioFileDevice( outputSettings, channels, file, mixer )
AudioEngine* audioEngine ) :
AudioFileDevice( outputSettings, channels, file, audioEngine )
{
m_ok = successful = outputFileOpened() && startEncoding();
}
@@ -79,7 +83,7 @@ bool AudioFileOgg::startEncoding()
vc.user_comments = &user_comments;
vc.comment_lengths = &comment_length;
vc.comments = 1;
vc.vendor = NULL;
vc.vendor = nullptr;
m_channels = channels();
@@ -120,11 +124,11 @@ bool AudioFileOgg::startEncoding()
if( useVariableBitRate )
{
// Turn off management entirely (if it was turned on).
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, NULL );
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, nullptr );
}
else
{
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, NULL );
vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, nullptr );
}
vorbis_encode_setup_init( &m_vi );
@@ -136,8 +140,13 @@ bool AudioFileOgg::startEncoding()
// We give our ogg file a random serial number and avoid
// 0 and UINT32_MAX which can get you into trouble.
qsrand( time( 0 ) );
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
// QRandomGenerator::global() is already initialized, and we can't seed() it.
m_serialNo = 0xD0000000 + QRandomGenerator::global()->generate() % 0x0FFFFFFF;
#else
qsrand(time(0));
m_serialNo = 0xD0000000 + qrand() % 0x0FFFFFFF;
#endif
ogg_stream_init( &m_os, m_serialNo );
// Now, build the three header packets and send through to the stream
@@ -203,7 +212,7 @@ void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab,
while( vorbis_analysis_blockout( &m_vd, &m_vb ) == 1 )
{
// Do the main analysis, creating a packet
vorbis_analysis( &m_vb, NULL );
vorbis_analysis( &m_vb, nullptr );
vorbis_bitrate_addblock( &m_vb );
while( vorbis_bitrate_flushpacket( &m_vd, &m_op ) )
@@ -249,7 +258,7 @@ void AudioFileOgg::finishEncoding()
if( m_ok )
{
// just for flushing buffers...
writeBuffer( NULL, 0, 0.0f );
writeBuffer( nullptr, 0, 0.0f );
// clean up
ogg_stream_clear( &m_os );
@@ -261,6 +270,8 @@ void AudioFileOgg::finishEncoding()
}
#endif
} // namespace lmms
#endif // LMMS_HAVE_OGGVORBIS

View File

@@ -25,18 +25,18 @@
#include "AudioFileWave.h"
#include "endian_handling.h"
#include "Mixer.h"
#include "AudioEngine.h"
#include <QFile>
#include <QDebug>
namespace lmms
{
AudioFileWave::AudioFileWave( OutputSettings const & outputSettings,
const ch_cnt_t channels, bool & successful,
const QString & file,
Mixer* mixer ) :
AudioFileDevice( outputSettings, channels, file, mixer ),
m_sf( NULL )
AudioEngine* audioEngine ) :
AudioFileDevice( outputSettings, channels, file, audioEngine ),
m_sf( nullptr )
{
successful = outputFileOpened() && startEncoding();
}
@@ -56,7 +56,7 @@ bool AudioFileWave::startEncoding()
{
m_si.samplerate = sampleRate();
m_si.channels = channels();
m_si.frames = mixer()->framesPerPeriod();
m_si.frames = audioEngine()->framesPerPeriod();
m_si.sections = 1;
m_si.seekable = 0;
@@ -86,7 +86,7 @@ bool AudioFileWave::startEncoding()
}
// Prevent fold overs when encountering clipped data
sf_command(m_sf, SFC_SET_CLIPPING, NULL, SF_TRUE);
sf_command(m_sf, SFC_SET_CLIPPING, nullptr, SF_TRUE);
sf_set_string ( m_sf, SF_STR_SOFTWARE, "LMMS" );
@@ -104,7 +104,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
if( bitDepth == OutputSettings::Depth_32Bit || bitDepth == OutputSettings::Depth_24Bit )
{
float * buf = new float[_frames*channels()];
auto buf = new float[_frames * channels()];
for( fpp_t frame = 0; frame < _frames; ++frame )
{
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
@@ -118,7 +118,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
}
else
{
int_sample_t * buf = new int_sample_t[_frames * channels()];
auto buf = new int_sample_t[_frames * channels()];
convertToS16( _ab, _frames, _master_gain, buf,
!isLittleEndian() );
@@ -138,3 +138,4 @@ void AudioFileWave::finishEncoding()
}
}
} // namespace lmms

View File

@@ -35,31 +35,34 @@
#include "gui_templates.h"
#include "ConfigManager.h"
#include "LcdSpinBox.h"
#include "AudioPort.h"
#include "MainWindow.h"
#include "Mixer.h"
#include "AudioEngine.h"
#include "MidiJack.h"
namespace lmms
{
AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) :
AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) :
AudioDevice( qBound<int>(
DEFAULT_CHANNELS,
ConfigManager::inst()->value( "audiojack", "channels" ).toInt(),
SURROUND_CHANNELS ), _mixer ),
m_client( NULL ),
SURROUND_CHANNELS ), _audioEngine ),
m_client( nullptr ),
m_active( false ),
m_midiClient( NULL ),
m_midiClient( nullptr ),
m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ),
m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ),
m_outBuf( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ),
m_framesDoneInCurBuf( 0 ),
m_framesToDoInCurBuf( 0 )
{
m_stopped = true;
_success_ful = initJackClient();
if( _success_ful )
{
connect( this, SIGNAL( zombified() ),
this, SLOT( restartAfterZombified() ),
connect( this, SIGNAL(zombified()),
this, SLOT(restartAfterZombified()),
Qt::QueuedConnection );
}
@@ -78,7 +81,7 @@ AudioJack::~AudioJack()
}
#endif
if( m_client != NULL )
if( m_client != nullptr )
{
if( m_active )
{
@@ -101,7 +104,7 @@ void AudioJack::restartAfterZombified()
{
m_active = false;
startProcessing();
QMessageBox::information( gui->mainWindow(),
QMessageBox::information(gui::getGUI()->mainWindow(),
tr( "JACK client restarted" ),
tr( "LMMS was kicked by JACK for some reason. "
"Therefore the JACK backend of LMMS has been "
@@ -110,7 +113,7 @@ void AudioJack::restartAfterZombified()
}
else
{
QMessageBox::information( gui->mainWindow(),
QMessageBox::information(gui::getGUI()->mainWindow(),
tr( "JACK server down" ),
tr( "The JACK server seems to have been shutdown "
"and starting a new instance failed. "
@@ -124,8 +127,8 @@ void AudioJack::restartAfterZombified()
AudioJack* AudioJack::addMidiClient(MidiJack *midiClient)
{
if( m_client == NULL )
return NULL;
if( m_client == nullptr )
return nullptr;
m_midiClient = midiClient;
@@ -141,12 +144,12 @@ bool AudioJack::initJackClient()
clientName = "lmms";
}
const char * serverName = NULL;
const char * serverName = nullptr;
jack_status_t status;
m_client = jack_client_open( clientName.toLatin1().constData(),
JackNullOption, &status,
serverName );
if( m_client == NULL )
if( m_client == nullptr )
{
printf( "jack_client_open() failed, status 0x%2.0x\n", status );
if( status & JackServerFailed )
@@ -185,7 +188,7 @@ bool AudioJack::initJackClient()
name.toLatin1().constData(),
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0 ) );
if( m_outputPorts.back() == NULL )
if( m_outputPorts.back() == nullptr )
{
printf( "no more JACK-ports available!\n" );
return false;
@@ -200,10 +203,9 @@ bool AudioJack::initJackClient()
void AudioJack::startProcessing()
{
m_stopped = false;
if( m_active || m_client == NULL )
if( m_active || m_client == nullptr )
{
m_stopped = false;
return;
}
@@ -217,14 +219,14 @@ void AudioJack::startProcessing()
// try to sync JACK's and LMMS's buffer-size
// jack_set_buffer_size( m_client, mixer()->framesPerPeriod() );
// jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() );
const char * * ports = jack_get_ports( m_client, NULL, NULL,
const char * * ports = jack_get_ports( m_client, nullptr, nullptr,
JackPortIsPhysical |
JackPortIsInput );
if( ports == NULL )
if( ports == nullptr )
{
printf( "no physical playback ports. you'll have to do "
"connections at your own!\n" );
@@ -244,6 +246,7 @@ void AudioJack::startProcessing()
}
}
m_stopped = false;
free( ports );
}
@@ -262,7 +265,7 @@ void AudioJack::applyQualitySettings()
{
if( hqAudio() )
{
setSampleRate( Engine::mixer()->processingSampleRate() );
setSampleRate( Engine::audioEngine()->processingSampleRate() );
if( jack_get_sample_rate( m_client ) != sampleRate() )
{
@@ -304,7 +307,7 @@ void AudioJack::unregisterPort( AudioPort * _port )
{
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
{
if( m_portMap[_port].ports[ch] != NULL )
if( m_portMap[_port].ports[ch] != nullptr )
{
jack_port_unregister( m_client,
m_portMap[_port].ports[ch] );
@@ -327,11 +330,16 @@ void AudioJack::renamePort( AudioPort * _port )
_port->name() + " R" };
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
{
#ifdef LMMS_HAVE_JACK_PRENAME
jack_port_rename( m_client, m_portMap[_port].ports[ch],
name[ch].toLatin1().constData() );
#else
jack_port_set_name( m_portMap[_port].ports[ch],
name[ch].toLatin1().constData() );
#endif
}
}
#endif
#endif // AUDIO_PORT_SUPPORT
}
@@ -344,8 +352,8 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
// add to the following sound processing
if( m_midiClient && _nframes > 0 )
{
m_midiClient->JackMidiRead(_nframes);
m_midiClient->JackMidiWrite(_nframes);
m_midiClient.load()->JackMidiRead(_nframes);
m_midiClient.load()->JackMidiWrite(_nframes);
}
for( int c = 0; c < channels(); ++c )
@@ -356,23 +364,23 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
}
#ifdef AUDIO_PORT_SUPPORT
const int frames = qMin<int>( _nframes, mixer()->framesPerPeriod() );
for( jackPortMap::iterator it = m_portMap.begin();
const int frames = qMin<int>( _nframes, audioEngine()->framesPerPeriod() );
for( JackPortMap::iterator it = m_portMap.begin();
it != m_portMap.end(); ++it )
{
for( ch_cnt_t ch = 0; ch < channels(); ++ch )
{
if( it.data().ports[ch] == NULL )
if( it.value().ports[ch] == nullptr )
{
continue;
}
jack_default_audio_sample_t * buf =
(jack_default_audio_sample_t *) jack_port_get_buffer(
it.data().ports[ch],
it.value().ports[ch],
_nframes );
for( int frame = 0; frame < frames; ++frame )
{
buf[frame] = it.key()->firstBuffer()[frame][ch];
buf[frame] = it.key()->buffer()[frame][ch];
}
}
}
@@ -385,7 +393,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
_nframes,
m_framesToDoInCurBuf -
m_framesDoneInCurBuf );
const float gain = mixer()->masterGain();
const float gain = audioEngine()->masterGain();
for( int c = 0; c < channels(); ++c )
{
jack_default_audio_sample_t * o = m_tempOutBufs[c];
@@ -434,8 +442,8 @@ int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata )
void AudioJack::shutdownCallback( void * _udata )
{
AudioJack * _this = static_cast<AudioJack *>( _udata );
_this->m_client = NULL;
auto _this = static_cast<AudioJack*>(_udata);
_this->m_client = nullptr;
_this->zombified();
}
@@ -454,19 +462,19 @@ AudioJack::setupWidget::setupWidget( QWidget * _parent ) :
m_clientName = new QLineEdit( cn, this );
m_clientName->setGeometry( 10, 20, 160, 20 );
QLabel * cn_lbl = new QLabel( tr( "CLIENT-NAME" ), this );
auto cn_lbl = new QLabel(tr("Client name"), this);
cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) );
cn_lbl->setGeometry( 10, 40, 160, 10 );
LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ );
auto m = new gui::LcdSpinBoxModel(/* this */);
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
m->setStep( 2 );
m->setValue( ConfigManager::inst()->value( "audiojack",
"channels" ).toInt() );
m_channels = new LcdSpinBox( 1, this );
m_channels = new gui::LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "CHANNELS" ) );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 180, 20 );
}
@@ -492,6 +500,6 @@ void AudioJack::setupWidget::saveSettings()
} // namespace lmms
#endif
#endif // LMMS_HAVE_JACK

Some files were not shown because too many files have changed in this diff Show More