Merge remote-tracking branch 'origin/master' into feature/recording-stage-one
Conflicts: * src/core/SampleRecordHandle.cpp Also fixed the setting of the font size in `SampleClipView::paintEvent`.
This commit is contained in:
3
src/3rdparty/CMakeLists.txt
vendored
3
src/3rdparty/CMakeLists.txt
vendored
@@ -1,10 +1,9 @@
|
||||
IF(LMMS_BUILD_LINUX AND WANT_VST)
|
||||
if(LMMS_BUILD_LINUX AND LMMS_HAVE_VST)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(qt5-x11embed)
|
||||
ENDIF()
|
||||
|
||||
ADD_SUBDIRECTORY(hiir)
|
||||
ADD_SUBDIRECTORY(rpmalloc)
|
||||
ADD_SUBDIRECTORY(weakjack)
|
||||
|
||||
if(MINGW)
|
||||
|
||||
2
src/3rdparty/jack2
vendored
2
src/3rdparty/jack2
vendored
Submodule src/3rdparty/jack2 updated: db76dd6bb8...ac334fabfb
49
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
49
src/3rdparty/rpmalloc/CMakeLists.txt
vendored
@@ -1,49 +0,0 @@
|
||||
add_library(rpmalloc STATIC
|
||||
rpmalloc/rpmalloc/rpmalloc.c
|
||||
rpmalloc/rpmalloc/rpmalloc.h
|
||||
)
|
||||
|
||||
target_include_directories(rpmalloc PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc
|
||||
)
|
||||
|
||||
set_property(TARGET rpmalloc PROPERTY C_STANDARD 11)
|
||||
|
||||
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(rpmalloc
|
||||
PRIVATE -Wno-unused-variable
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT LMMS_BUILD_WIN32)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -D_GNU_SOURCE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -D_WIN32_WINNT=0x600
|
||||
)
|
||||
endif()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
# rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS,
|
||||
# which is only available starting with GCC 5
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5)
|
||||
set(ENABLE_VALIDATE_ARGS OFF)
|
||||
else ()
|
||||
set(ENABLE_VALIDATE_ARGS ON)
|
||||
endif()
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS}
|
||||
)
|
||||
endif()
|
||||
|
||||
option(LMMS_ENABLE_MALLOC_STATS "Enables statistics for rpmalloc" OFF)
|
||||
|
||||
if (LMMS_ENABLE_MALLOC_STATS)
|
||||
target_compile_definitions(rpmalloc
|
||||
PRIVATE -DENABLE_STATISTICS=1
|
||||
)
|
||||
endif()
|
||||
1
src/3rdparty/rpmalloc/rpmalloc
vendored
1
src/3rdparty/rpmalloc/rpmalloc
vendored
Submodule src/3rdparty/rpmalloc/rpmalloc deleted from 80daac0d53
@@ -4,9 +4,9 @@ CONFIGURE_FILE("lmmsconfig.h.in" "${CMAKE_BINARY_DIR}/lmmsconfig.h")
|
||||
CONFIGURE_FILE("lmmsversion.h.in" "${CMAKE_BINARY_DIR}/lmmsversion.h")
|
||||
|
||||
SET(LMMS_SRCS "")
|
||||
SET(LMMS_UIS "")
|
||||
|
||||
SET(CMAKE_AUTOMOC ON)
|
||||
SET(CMAKE_AUTOUIC ON)
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Enable C++17
|
||||
@@ -23,7 +23,6 @@ ADD_SUBDIRECTORY(tracks)
|
||||
|
||||
LIST(APPEND LMMS_SRCS ${LMMS_COMMON_SRCS})
|
||||
|
||||
QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS})
|
||||
INCLUDE_DIRECTORIES(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
@@ -58,8 +57,6 @@ FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}")
|
||||
ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS})
|
||||
INCLUDE_DIRECTORIES(
|
||||
${JACK_INCLUDE_DIRS}
|
||||
${SAMPLERATE_INCLUDE_DIRS}
|
||||
${SNDFILE_INCLUDE_DIRS}
|
||||
${SNDIO_INCLUDE_DIRS}
|
||||
${FFTW3F_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -79,10 +76,6 @@ IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}")
|
||||
ENDIF()
|
||||
|
||||
IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
|
||||
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
|
||||
ENDIF()
|
||||
@@ -106,7 +99,6 @@ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
ADD_LIBRARY(lmmsobjs OBJECT
|
||||
${LMMS_SRCS}
|
||||
${LMMS_INCLUDES}
|
||||
${LMMS_UI_OUT}
|
||||
${LMMS_RCC_OUT}
|
||||
)
|
||||
|
||||
@@ -140,7 +132,7 @@ IF(NOT CMAKE_VERSION VERSION_LESS 3.6)
|
||||
SET_PROPERTY(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lmms)
|
||||
ENDIF()
|
||||
|
||||
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} ${LMMS_UI_OUT} lmmsconfig.h lmms.1.gz")
|
||||
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} lmmsconfig.h lmms.1.gz")
|
||||
|
||||
IF(LMMS_BUILD_WIN32)
|
||||
SET(EXTRA_LIBRARIES "winmm")
|
||||
@@ -170,6 +162,10 @@ if(LMMS_HAVE_MP3LAME)
|
||||
list(APPEND EXTRA_LIBRARIES mp3lame::mp3lame)
|
||||
endif()
|
||||
|
||||
if(LMMS_HAVE_OGGVORBIS)
|
||||
list(APPEND EXTRA_LIBRARIES Vorbis::vorbisenc Vorbis::vorbisfile)
|
||||
endif()
|
||||
|
||||
if(LMMS_USE_MINGW_STD_THREADS)
|
||||
list(APPEND EXTRA_LIBRARIES mingw_stdthreads)
|
||||
endif()
|
||||
@@ -184,15 +180,13 @@ SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
|
||||
${SNDIO_LIBRARIES}
|
||||
${PULSEAUDIO_LIBRARIES}
|
||||
${JACK_LIBRARIES}
|
||||
${OGGVORBIS_LIBRARIES}
|
||||
${LV2_LIBRARIES}
|
||||
${SUIL_LIBRARIES}
|
||||
${LILV_LIBRARIES}
|
||||
${SAMPLERATE_LIBRARIES}
|
||||
${SNDFILE_LIBRARIES}
|
||||
${FFTW3F_LIBRARIES}
|
||||
SampleRate::samplerate
|
||||
SndFile::sndfile
|
||||
${EXTRA_LIBRARIES}
|
||||
rpmalloc
|
||||
)
|
||||
|
||||
# Expose required libs for tests binary
|
||||
@@ -211,27 +205,14 @@ FOREACH(LIB ${LMMS_REQUIRED_LIBS})
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
|
||||
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"
|
||||
)
|
||||
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)
|
||||
set_target_properties(lmms PROPERTIES
|
||||
ENABLE_EXPORTS ON
|
||||
WIN32_EXECUTABLE $<NOT:$<CONFIG:DEBUG>>
|
||||
)
|
||||
|
||||
set_target_properties(lmmsobjs PROPERTIES AUTOUIC_SEARCH_PATHS "gui/modals")
|
||||
|
||||
IF(NOT WIN32)
|
||||
if(CMAKE_INSTALL_MANDIR)
|
||||
SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR})
|
||||
ELSE(CMAKE_INSTALL_MANDIR)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
set(COMMON_SRCS
|
||||
RemotePluginBase.cpp
|
||||
SharedMemory.cpp
|
||||
SystemSemaphore.cpp
|
||||
)
|
||||
|
||||
foreach(SRC ${COMMON_SRCS})
|
||||
|
||||
@@ -34,23 +34,23 @@ namespace lmms
|
||||
|
||||
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
RemotePluginBase::RemotePluginBase( shmFifo * _in, shmFifo * _out ) :
|
||||
m_in( _in ),
|
||||
m_out( _out )
|
||||
RemotePluginBase::RemotePluginBase(shmFifo * _in, shmFifo * _out) :
|
||||
m_in(_in),
|
||||
m_out(_out)
|
||||
#else
|
||||
RemotePluginBase::RemotePluginBase() :
|
||||
m_socket( -1 ),
|
||||
m_invalid( false )
|
||||
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" );
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#endif
|
||||
#ifndef SYNC_WITH_SHM_FIFO
|
||||
pthread_mutex_init( &m_receiveMutex, nullptr );
|
||||
pthread_mutex_init( &m_sendMutex, nullptr );
|
||||
pthread_mutex_init(&m_receiveMutex, nullptr);
|
||||
pthread_mutex_init(&m_sendMutex, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -63,39 +63,39 @@ RemotePluginBase::~RemotePluginBase()
|
||||
delete m_in;
|
||||
delete m_out;
|
||||
#else
|
||||
pthread_mutex_destroy( &m_receiveMutex );
|
||||
pthread_mutex_destroy( &m_sendMutex );
|
||||
pthread_mutex_destroy(&m_receiveMutex);
|
||||
pthread_mutex_destroy(&m_sendMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int RemotePluginBase::sendMessage( const message & _m )
|
||||
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() );
|
||||
m_out->writeInt(_m.id);
|
||||
m_out->writeInt(_m.data.size());
|
||||
int j = 8;
|
||||
for( unsigned int i = 0; i < _m.data.size(); ++i )
|
||||
for (unsigned int i = 0; i < _m.data.size(); ++i)
|
||||
{
|
||||
m_out->writeString( _m.data[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() );
|
||||
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 );
|
||||
pthread_mutex_unlock(&m_sendMutex);
|
||||
#endif
|
||||
|
||||
return j;
|
||||
@@ -112,21 +112,21 @@ RemotePluginBase::message RemotePluginBase::receiveMessage()
|
||||
message m;
|
||||
m.id = m_in->readInt();
|
||||
const int s = m_in->readInt();
|
||||
for( int i = 0; i < s; ++i )
|
||||
for (int i = 0; i < s; ++i)
|
||||
{
|
||||
m.data.push_back( m_in->readString() );
|
||||
m.data.push_back(m_in->readString());
|
||||
}
|
||||
m_in->unlock();
|
||||
#else
|
||||
pthread_mutex_lock( &m_receiveMutex );
|
||||
pthread_mutex_lock(&m_receiveMutex);
|
||||
message m;
|
||||
m.id = readInt();
|
||||
const int s = readInt();
|
||||
for( int i = 0; i < s; ++i )
|
||||
for (int i = 0; i < s; ++i)
|
||||
{
|
||||
m.data.push_back( readString() );
|
||||
m.data.push_back(readString());
|
||||
}
|
||||
pthread_mutex_unlock( &m_receiveMutex );
|
||||
pthread_mutex_unlock(&m_receiveMutex);
|
||||
#endif
|
||||
return m;
|
||||
}
|
||||
@@ -136,10 +136,10 @@ RemotePluginBase::message RemotePluginBase::receiveMessage()
|
||||
|
||||
RemotePluginBase::message RemotePluginBase::waitForMessage(
|
||||
const message & _wm,
|
||||
bool _busy_waiting )
|
||||
bool _busy_waiting)
|
||||
{
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
if( _busy_waiting )
|
||||
if (_busy_waiting)
|
||||
{
|
||||
// No point processing events outside of the main thread
|
||||
_busy_waiting = QThread::currentThread() ==
|
||||
@@ -148,41 +148,41 @@ RemotePluginBase::message RemotePluginBase::waitForMessage(
|
||||
|
||||
struct WaitDepthCounter
|
||||
{
|
||||
WaitDepthCounter( int & depth, bool busy ) :
|
||||
m_depth( depth ),
|
||||
m_busy( busy )
|
||||
WaitDepthCounter(int & depth, bool busy) :
|
||||
m_depth(depth),
|
||||
m_busy(busy)
|
||||
{
|
||||
if( m_busy ) { ++m_depth; }
|
||||
if (m_busy) { ++m_depth; }
|
||||
}
|
||||
|
||||
~WaitDepthCounter()
|
||||
{
|
||||
if( m_busy ) { --m_depth; }
|
||||
if (m_busy) { --m_depth; }
|
||||
}
|
||||
|
||||
int & m_depth;
|
||||
bool m_busy;
|
||||
};
|
||||
|
||||
WaitDepthCounter wdc( waitDepthCounter(), _busy_waiting );
|
||||
WaitDepthCounter wdc(waitDepthCounter(), _busy_waiting);
|
||||
#endif
|
||||
while( !isInvalid() )
|
||||
while (!isInvalid())
|
||||
{
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
if( _busy_waiting && !messagesLeft() )
|
||||
if (_busy_waiting && !messagesLeft())
|
||||
{
|
||||
QCoreApplication::processEvents(
|
||||
QEventLoop::ExcludeUserInputEvents, 50 );
|
||||
QEventLoop::ExcludeUserInputEvents, 50);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
message m = receiveMessage();
|
||||
processMessage( m );
|
||||
if( m.id == _wm.id )
|
||||
processMessage(m);
|
||||
if (m.id == _wm.id)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
else if( m.id == IdUndefined )
|
||||
else if (m.id == IdUndefined)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -23,45 +23,44 @@
|
||||
|
||||
#include "SharedMemory.h"
|
||||
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
# include <system_error>
|
||||
#
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
#
|
||||
# include "RaiiHelpers.h"
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <stdexcept>
|
||||
#
|
||||
# include <QtGlobal>
|
||||
# include <QSharedMemory>
|
||||
# error "No shared memory implementation available"
|
||||
#endif
|
||||
|
||||
|
||||
namespace lmms::detail {
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
|
||||
{
|
||||
int result;
|
||||
do
|
||||
{
|
||||
result = function();
|
||||
while (true) {
|
||||
const auto result = function();
|
||||
if (result != -1 || errno != EINTR) { return result; }
|
||||
}
|
||||
while (result == -1 && errno == EINTR);
|
||||
return result;
|
||||
}
|
||||
|
||||
void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); }
|
||||
@@ -82,22 +81,15 @@ public:
|
||||
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"};
|
||||
}
|
||||
if (!fd) { throwSystemError("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"};
|
||||
}
|
||||
if (fstat(fd.get(), &stat) == -1) { throwSystemError("SharedMemoryImpl: fstat() failed"); }
|
||||
m_size = stat.st_size;
|
||||
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
@@ -107,21 +99,16 @@ public:
|
||||
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"};
|
||||
}
|
||||
if (fd.get() == -1) { throwSystemError("SharedMemoryImpl: shm_open() failed"); }
|
||||
m_object.reset(m_key.c_str());
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"};
|
||||
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) {
|
||||
throwSystemError("SharedMemoryImpl: ftruncate() failed");
|
||||
}
|
||||
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
@@ -132,7 +119,7 @@ public:
|
||||
munmap(m_mapping, m_size);
|
||||
}
|
||||
|
||||
void* get() { return m_mapping; }
|
||||
auto get() const noexcept -> void* { return m_mapping; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
@@ -141,38 +128,67 @@ private:
|
||||
ShmObject m_object;
|
||||
};
|
||||
|
||||
#else
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
auto sizeToHighAndLow(std::size_t size) -> std::pair<DWORD, DWORD>
|
||||
{
|
||||
if constexpr(sizeof(std::size_t) <= sizeof(DWORD)) {
|
||||
return {0, size};
|
||||
} else {
|
||||
return {static_cast<DWORD>(size >> 32), static_cast<DWORD>(size)};
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void throwLastError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
using FileView = UniqueNullableResource<void*, nullptr, UnmapViewOfFile>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SharedMemoryImpl
|
||||
{
|
||||
public:
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly)
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.attach(mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"};
|
||||
}
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_mapping.reset(OpenFileMappingA(access, false, key.c_str()));
|
||||
if (!m_mapping) { throwLastError("SharedMemoryImpl: OpenFileMappingA() failed"); }
|
||||
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly)
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.create(size, mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"};
|
||||
const auto [high, low] = sizeToHighAndLow(size);
|
||||
m_mapping.reset(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, high, low, key.c_str()));
|
||||
// This constructor is supposed to create a new shared memory object,
|
||||
// but passing the name of an existing object causes CreateFileMappingA
|
||||
// to succeed and return a handle to that object. Thus we have to check
|
||||
// GetLastError() too.
|
||||
if (!m_mapping || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwLastError("SharedMemoryImpl: CreateFileMappingA() failed");
|
||||
}
|
||||
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
|
||||
auto operator=(const SharedMemoryImpl&) -> SharedMemoryImpl& = delete;
|
||||
|
||||
void* get() { return m_shm.data(); }
|
||||
auto get() const noexcept -> void* { return m_view.get(); }
|
||||
|
||||
private:
|
||||
QSharedMemory m_shm;
|
||||
UniqueHandle m_mapping;
|
||||
FileView m_view;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -196,7 +212,7 @@ 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}
|
||||
m_ptr{std::exchange(other.m_ptr, nullptr)}
|
||||
{ }
|
||||
|
||||
} // namespace lmms::detail
|
||||
|
||||
181
src/common/SystemSemaphore.cpp
Normal file
181
src/common/SystemSemaphore.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* SystemSemaphore.cpp
|
||||
*
|
||||
* Copyright (c) 2024 Dominic Clark
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "SystemSemaphore.h"
|
||||
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
# include <fcntl.h>
|
||||
# include <semaphore.h>
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# error "No system semaphore implementation available"
|
||||
#endif
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto retryWhileInterrupted(F&& function, std::invoke_result_t<F> error = -1)
|
||||
noexcept(std::is_nothrow_invocable_v<F>) -> auto
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const auto result = function();
|
||||
if (result != error || errno != EINTR) { return result; }
|
||||
}
|
||||
}
|
||||
|
||||
using UniqueSemaphore = UniqueNullableResource<const char*, nullptr, sem_unlink>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
m_ownedSemaphore.reset(m_key.c_str());
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), 0);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
}
|
||||
|
||||
~SystemSemaphoreImpl()
|
||||
{
|
||||
// We can't use `UniqueNullableResource` for `m_sem`, as the null value
|
||||
// (`SEM_FAILED`) is not a constant expression on macOS (it's defined as
|
||||
// `(sem_t*) -1`), so can't be used as a template parameter.
|
||||
sem_close(m_sem);
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool
|
||||
{
|
||||
return retryWhileInterrupted([&]() noexcept {
|
||||
return sem_wait(m_sem);
|
||||
}) == 0;
|
||||
}
|
||||
|
||||
auto release() noexcept -> bool { return sem_post(m_sem) == 0; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
sem_t* m_sem;
|
||||
UniqueSemaphore m_ownedSemaphore;
|
||||
};
|
||||
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits<LONG>::max(), key.c_str())}
|
||||
{
|
||||
if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed");
|
||||
}
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())}
|
||||
{
|
||||
if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); }
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; }
|
||||
auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); }
|
||||
|
||||
private:
|
||||
UniqueHandle m_sem;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
SystemSemaphore::SystemSemaphore() noexcept = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key, value)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::~SystemSemaphore() = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default;
|
||||
auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default;
|
||||
|
||||
auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); }
|
||||
auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); }
|
||||
|
||||
} // namespace lmms
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "AudioEngine.h"
|
||||
|
||||
#include "MixHelpers.h"
|
||||
#include "denormals.h"
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
@@ -67,6 +68,7 @@ namespace lmms
|
||||
using LocklessListElement = LocklessList<PlayHandle*>::Element;
|
||||
|
||||
static thread_local bool s_renderingThread;
|
||||
static thread_local bool s_runningChange;
|
||||
|
||||
|
||||
|
||||
@@ -81,21 +83,14 @@ AudioEngine::AudioEngine( bool renderOnly ) :
|
||||
m_workers(),
|
||||
m_numWorkers( QThread::idealThreadCount()-1 ),
|
||||
m_newPlayHandles( PlayHandle::MaxNumber ),
|
||||
m_qualitySettings( qualitySettings::Mode::Draft ),
|
||||
m_qualitySettings(qualitySettings::Interpolation::Linear),
|
||||
m_masterGain( 1.0f ),
|
||||
m_isProcessing( false ),
|
||||
m_audioDev( nullptr ),
|
||||
m_oldAudioDev( nullptr ),
|
||||
m_audioDevStartFailed( false ),
|
||||
m_profiler(),
|
||||
m_metronomeActive(false),
|
||||
m_clearSignal( false ),
|
||||
m_changesSignal( false ),
|
||||
m_changes( 0 ),
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5,14,0))
|
||||
m_doChangesMutex( QMutex::Recursive ),
|
||||
#endif
|
||||
m_waitingForWrite( false )
|
||||
m_clearSignal(false)
|
||||
{
|
||||
for( int i = 0; i < 2; ++i )
|
||||
{
|
||||
@@ -165,8 +160,6 @@ AudioEngine::AudioEngine( bool renderOnly ) :
|
||||
|
||||
AudioEngine::~AudioEngine()
|
||||
{
|
||||
runChangesInModel();
|
||||
|
||||
for( int w = 0; w < m_numWorkers; ++w )
|
||||
{
|
||||
m_workers[w]->quit();
|
||||
@@ -232,8 +225,6 @@ void AudioEngine::startProcessing(bool needsFifo)
|
||||
}
|
||||
|
||||
m_audioDev->startProcessing();
|
||||
|
||||
m_isProcessing = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -241,8 +232,6 @@ void AudioEngine::startProcessing(bool needsFifo)
|
||||
|
||||
void AudioEngine::stopProcessing()
|
||||
{
|
||||
m_isProcessing = false;
|
||||
|
||||
if( m_fifoWriter != nullptr )
|
||||
{
|
||||
m_fifoWriter->finish();
|
||||
@@ -288,17 +277,6 @@ sample_rate_t AudioEngine::inputSampleRate() const
|
||||
baseSampleRate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sample_rate_t AudioEngine::processingSampleRate() const
|
||||
{
|
||||
return outputSampleRate() * m_qualitySettings.sampleRateMultiplier();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool AudioEngine::criticalXRuns() const
|
||||
{
|
||||
return cpuLoad() >= 99 && Engine::getSong()->isExporting() == false;
|
||||
@@ -397,6 +375,17 @@ void AudioEngine::renderStageInstruments()
|
||||
|
||||
AudioEngineWorkerThread::fillJobQueue(m_playHandles);
|
||||
AudioEngineWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AudioEngine::renderStageEffects()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects);
|
||||
|
||||
// STAGE 2: process effects of all instrument- and sampletracks
|
||||
AudioEngineWorkerThread::fillJobQueue(m_audioPorts);
|
||||
AudioEngineWorkerThread::startAndWaitForJobs();
|
||||
|
||||
// removed all play handles which are done
|
||||
for( PlayHandleList::Iterator it = m_playHandles.begin();
|
||||
@@ -427,17 +416,6 @@ void AudioEngine::renderStageInstruments()
|
||||
|
||||
|
||||
|
||||
void AudioEngine::renderStageEffects()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects);
|
||||
|
||||
// STAGE 2: process effects of all instrument- and sampletracks
|
||||
AudioEngineWorkerThread::fillJobQueue(m_audioPorts);
|
||||
AudioEngineWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AudioEngine::renderStageMix()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing);
|
||||
@@ -445,9 +423,9 @@ void AudioEngine::renderStageMix()
|
||||
Mixer *mixer = Engine::mixer();
|
||||
mixer->masterMix(m_outputBufferWrite);
|
||||
|
||||
emit nextAudioBuffer(m_outputBufferRead);
|
||||
MixHelpers::multiply(m_outputBufferWrite, m_masterGain, m_framesPerPeriod);
|
||||
|
||||
runChangesInModel();
|
||||
emit nextAudioBuffer(m_outputBufferRead);
|
||||
|
||||
// and trigger LFOs
|
||||
EnvelopeAndLfoParameters::instances()->trigger();
|
||||
@@ -459,6 +437,8 @@ void AudioEngine::renderStageMix()
|
||||
|
||||
const surroundSampleFrame *AudioEngine::renderNextBuffer()
|
||||
{
|
||||
const auto lock = std::lock_guard{m_changeMutex};
|
||||
|
||||
m_profiler.startPeriod();
|
||||
s_renderingThread = true;
|
||||
|
||||
@@ -468,7 +448,7 @@ const surroundSampleFrame *AudioEngine::renderNextBuffer()
|
||||
renderStageMix(); // STAGE 3: do master mix in mixer
|
||||
|
||||
s_renderingThread = false;
|
||||
m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod);
|
||||
m_profiler.finishPeriod(outputSampleRate(), m_framesPerPeriod);
|
||||
|
||||
return m_outputBufferRead;
|
||||
}
|
||||
@@ -606,7 +586,6 @@ void AudioEngine::changeQuality(const struct qualitySettings & qs)
|
||||
stopProcessing();
|
||||
|
||||
m_qualitySettings = qs;
|
||||
m_audioDev->applyQualitySettings();
|
||||
|
||||
emit sampleRateChanged();
|
||||
emit qualitySettingsChanged();
|
||||
@@ -811,57 +790,16 @@ void AudioEngine::removePlayHandlesOfTypes(Track * track, PlayHandle::Types type
|
||||
|
||||
void AudioEngine::requestChangeInModel()
|
||||
{
|
||||
if( s_renderingThread )
|
||||
return;
|
||||
|
||||
m_changesMutex.lock();
|
||||
m_changes++;
|
||||
m_changesMutex.unlock();
|
||||
|
||||
m_doChangesMutex.lock();
|
||||
m_waitChangesMutex.lock();
|
||||
if (m_isProcessing && !m_waitingForWrite && !m_changesSignal)
|
||||
{
|
||||
m_changesSignal = true;
|
||||
m_changesRequestCondition.wait( &m_waitChangesMutex );
|
||||
}
|
||||
m_waitChangesMutex.unlock();
|
||||
if (s_renderingThread || s_runningChange) { return; }
|
||||
m_changeMutex.lock();
|
||||
s_runningChange = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::doneChangeInModel()
|
||||
{
|
||||
if( s_renderingThread )
|
||||
return;
|
||||
|
||||
m_changesMutex.lock();
|
||||
bool moreChanges = --m_changes;
|
||||
m_changesMutex.unlock();
|
||||
|
||||
if( !moreChanges )
|
||||
{
|
||||
m_changesSignal = false;
|
||||
m_changesAudioEngineCondition.wakeOne();
|
||||
}
|
||||
m_doChangesMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::runChangesInModel()
|
||||
{
|
||||
if( m_changesSignal )
|
||||
{
|
||||
m_waitChangesMutex.lock();
|
||||
// allow changes in the model from other threads ...
|
||||
m_changesRequestCondition.wakeOne();
|
||||
// ... and wait until they are done
|
||||
m_changesAudioEngineCondition.wait( &m_waitChangesMutex );
|
||||
m_waitChangesMutex.unlock();
|
||||
}
|
||||
if (s_renderingThread || !s_runningChange) { return; }
|
||||
m_changeMutex.unlock();
|
||||
s_runningChange = false;
|
||||
}
|
||||
|
||||
bool AudioEngine::isAudioDevNameValid(QString name)
|
||||
@@ -1297,29 +1235,12 @@ void AudioEngine::fifoWriter::run()
|
||||
auto buffer = new surroundSampleFrame[frames];
|
||||
const surroundSampleFrame * b = m_audioEngine->renderNextBuffer();
|
||||
memcpy( buffer, b, frames * sizeof( surroundSampleFrame ) );
|
||||
write( buffer );
|
||||
m_fifo->write(buffer);
|
||||
}
|
||||
|
||||
// Let audio backend stop processing
|
||||
write( nullptr );
|
||||
m_fifo->write(nullptr);
|
||||
m_fifo->waitUntilRead();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::fifoWriter::write( surroundSampleFrame * buffer )
|
||||
{
|
||||
m_audioEngine->m_waitChangesMutex.lock();
|
||||
m_audioEngine->m_waitingForWrite = true;
|
||||
m_audioEngine->m_waitChangesMutex.unlock();
|
||||
m_audioEngine->runChangesInModel();
|
||||
|
||||
m_fifo->write( buffer );
|
||||
|
||||
m_audioEngine->m_doChangesMutex.lock();
|
||||
m_audioEngine->m_waitingForWrite = false;
|
||||
m_audioEngine->m_doChangesMutex.unlock();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "denormals.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "ThreadableJob.h"
|
||||
|
||||
#if __SSE__
|
||||
@@ -167,7 +166,6 @@ void AudioEngineWorkerThread::startAndWaitForJobs()
|
||||
|
||||
void AudioEngineWorkerThread::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
disable_denormals();
|
||||
|
||||
QMutex m;
|
||||
|
||||
64
src/core/AudioResampler.cpp
Normal file
64
src/core/AudioResampler.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* AudioResampler.cpp - wrapper for libsamplerate
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AudioResampler.h"
|
||||
|
||||
#include <samplerate.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
AudioResampler::AudioResampler(int interpolationMode, int channels)
|
||||
: m_interpolationMode(interpolationMode)
|
||||
, m_channels(channels)
|
||||
, m_state(src_new(interpolationMode, channels, &m_error))
|
||||
{
|
||||
if (!m_state)
|
||||
{
|
||||
const auto errorMessage = std::string{src_strerror(m_error)};
|
||||
const auto fullMessage = std::string{"Failed to create an AudioResampler: "} + errorMessage;
|
||||
throw std::runtime_error{fullMessage};
|
||||
}
|
||||
}
|
||||
|
||||
AudioResampler::~AudioResampler()
|
||||
{
|
||||
src_delete(m_state);
|
||||
}
|
||||
|
||||
auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio)
|
||||
-> ProcessResult
|
||||
{
|
||||
auto data = SRC_DATA{};
|
||||
data.data_in = in;
|
||||
data.input_frames = inputFrames;
|
||||
data.data_out = out;
|
||||
data.output_frames = outputFrames;
|
||||
data.src_ratio = ratio;
|
||||
data.end_of_input = 0;
|
||||
return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen};
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -97,8 +97,8 @@ bool AutomatableModel::isAutomated() const
|
||||
|
||||
bool AutomatableModel::mustQuoteName(const QString& name)
|
||||
{
|
||||
QRegExp reg("^[A-Za-z0-9._-]+$");
|
||||
return !reg.exactMatch(name);
|
||||
QRegularExpression reg("^[A-Za-z0-9._-]+$");
|
||||
return !reg.match(name).hasMatch();
|
||||
}
|
||||
|
||||
void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
|
||||
@@ -613,10 +613,9 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
|
||||
float val = m_value; // make sure our m_value doesn't change midway
|
||||
|
||||
ValueBuffer * vb;
|
||||
if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact())
|
||||
{
|
||||
vb = m_controllerConnection->valueBuffer();
|
||||
auto vb = m_controllerConnection->valueBuffer();
|
||||
if( vb )
|
||||
{
|
||||
float * values = vb->values();
|
||||
@@ -656,7 +655,7 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
if (lm && lm->controllerConnection() && lm->useControllerValue() &&
|
||||
lm->controllerConnection()->getController()->isSampleExact())
|
||||
{
|
||||
vb = lm->valueBuffer();
|
||||
auto vb = lm->valueBuffer();
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
for (int i = 0; i < vb->length(); i++)
|
||||
|
||||
@@ -830,10 +830,10 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
_this.setAttribute( "prog", QString::number( static_cast<int>(progressionType()) ) );
|
||||
_this.setAttribute( "tens", QString::number( getTension() ) );
|
||||
_this.setAttribute( "mute", QString::number( isMuted() ) );
|
||||
|
||||
if( usesCustomClipColor() )
|
||||
|
||||
if (const auto& c = color())
|
||||
{
|
||||
_this.setAttribute( "color", color().name() );
|
||||
_this.setAttribute("color", c->name());
|
||||
}
|
||||
|
||||
for( timeMap::const_iterator it = m_timeMap.begin();
|
||||
@@ -919,10 +919,9 @@ void AutomationClip::loadSettings( const QDomElement & _this )
|
||||
}
|
||||
}
|
||||
|
||||
if( _this.hasAttribute( "color" ) )
|
||||
if (_this.hasAttribute("color"))
|
||||
{
|
||||
useCustomClipColor( true );
|
||||
setColor( _this.attribute( "color" ) );
|
||||
setColor(QColor{_this.attribute("color")});
|
||||
}
|
||||
|
||||
int len = _this.attribute( "len" ).toInt();
|
||||
@@ -1223,11 +1222,9 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate)
|
||||
// TODO: This behavior means that a very small difference between the inValue and outValue can
|
||||
// result in a big change in the curve. In the future, allowing the user to manually adjust
|
||||
// the tangents would be better.
|
||||
float inTangent;
|
||||
float outTangent;
|
||||
if (OFFSET(it) == 0)
|
||||
{
|
||||
inTangent = (INVAL(it + 1) - OUTVAL(it - 1)) / (POS(it + 1) - POS(it - 1));
|
||||
float inTangent = (INVAL(it + 1) - OUTVAL(it - 1)) / (POS(it + 1) - POS(it - 1));
|
||||
it.value().setInTangent(inTangent);
|
||||
// inTangent == outTangent in this case
|
||||
it.value().setOutTangent(inTangent);
|
||||
@@ -1235,9 +1232,9 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate)
|
||||
else
|
||||
{
|
||||
// Calculate the left side of the curve
|
||||
inTangent = (INVAL(it) - OUTVAL(it - 1)) / (POS(it) - POS(it - 1));
|
||||
float inTangent = (INVAL(it) - OUTVAL(it - 1)) / (POS(it) - POS(it - 1));
|
||||
// Calculate the right side of the curve
|
||||
outTangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it));
|
||||
float outTangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it));
|
||||
it.value().setInTangent(inTangent);
|
||||
it.value().setOutTangent(outTangent);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap )
|
||||
|
||||
QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap )
|
||||
{
|
||||
sample_t sample;
|
||||
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
|
||||
{
|
||||
for( int i = 0; i < TLENS[tbl]; i++ )
|
||||
{
|
||||
sample_t sample;
|
||||
in >> sample;
|
||||
waveMipMap.setSampleAt( tbl, i, sample );
|
||||
}
|
||||
@@ -67,9 +67,8 @@ void BandLimitedWave::generateWaves()
|
||||
// don't generate if they already exist
|
||||
if( s_wavesGenerated ) return;
|
||||
|
||||
int i;
|
||||
|
||||
// set wavetable directory
|
||||
// set wavetable directory
|
||||
s_wavetableDir = "data:wavetables/";
|
||||
|
||||
// set wavetable files
|
||||
@@ -89,7 +88,7 @@ void BandLimitedWave::generateWaves()
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -131,7 +130,7 @@ void BandLimitedWave::generateWaves()
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -172,7 +171,7 @@ void BandLimitedWave::generateWaves()
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
//const double om = 1.0 / len;
|
||||
@@ -215,7 +214,7 @@ void BandLimitedWave::generateWaves()
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i <= MAXTBL; i++ )
|
||||
for (int i = 0; i <= MAXTBL; i++)
|
||||
{
|
||||
const int len = TLENS[i];
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "MemoryManager.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -43,7 +42,7 @@ void BufferManager::init( fpp_t fpp )
|
||||
|
||||
sampleFrame * BufferManager::acquire()
|
||||
{
|
||||
return MM_ALLOC<sampleFrame>( s_framesPerPeriod );
|
||||
return new sampleFrame[s_framesPerPeriod];
|
||||
}
|
||||
|
||||
void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset )
|
||||
@@ -62,7 +61,7 @@ void BufferManager::clear( surroundSampleFrame * ab, const f_cnt_t frames,
|
||||
|
||||
void BufferManager::release( sampleFrame * buf )
|
||||
{
|
||||
MM_FREE( buf );
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -4,6 +4,7 @@ set(LMMS_SRCS
|
||||
core/AudioEngine.cpp
|
||||
core/AudioEngineProfiler.cpp
|
||||
core/AudioEngineWorkerThread.cpp
|
||||
core/AudioResampler.cpp
|
||||
core/AutomatableModel.cpp
|
||||
core/AutomationClip.cpp
|
||||
core/AutomationNode.cpp
|
||||
@@ -22,6 +23,7 @@ set(LMMS_SRCS
|
||||
core/Engine.cpp
|
||||
core/EnvelopeAndLfoParameters.cpp
|
||||
core/fft_helpers.cpp
|
||||
core/FileSearch.cpp
|
||||
core/Mixer.cpp
|
||||
core/ImportFilter.cpp
|
||||
core/InlineAutomation.cpp
|
||||
@@ -38,7 +40,6 @@ set(LMMS_SRCS
|
||||
core/LinkedModelGroups.cpp
|
||||
core/LocklessAllocator.cpp
|
||||
core/MemoryHelper.cpp
|
||||
core/MemoryManager.cpp
|
||||
core/MeterModel.cpp
|
||||
core/MicroTimer.cpp
|
||||
core/Microtuner.cpp
|
||||
@@ -65,8 +66,10 @@ set(LMMS_SRCS
|
||||
core/RemotePlugin.cpp
|
||||
core/RenderManager.cpp
|
||||
core/RingBuffer.cpp
|
||||
core/Sample.cpp
|
||||
core/SampleBuffer.cpp
|
||||
core/SampleClip.cpp
|
||||
core/SampleDecoder.cpp
|
||||
core/SamplePlayHandle.cpp
|
||||
core/SampleRecordHandle.cpp
|
||||
core/Scale.cpp
|
||||
@@ -74,6 +77,8 @@ set(LMMS_SRCS
|
||||
core/SerializingObject.cpp
|
||||
core/Song.cpp
|
||||
core/TempoSyncKnobModel.cpp
|
||||
core/ThreadPool.cpp
|
||||
core/Timeline.cpp
|
||||
core/TimePos.cpp
|
||||
core/ToolPlugin.cpp
|
||||
core/Track.cpp
|
||||
|
||||
@@ -48,9 +48,7 @@ Clip::Clip( Track * track ) :
|
||||
m_startPosition(),
|
||||
m_length(),
|
||||
m_mutedModel( false, this, tr( "Mute" ) ),
|
||||
m_selectViewOnCreate( false ),
|
||||
m_color( 128, 128, 128 ),
|
||||
m_useCustomClipColor( false )
|
||||
m_selectViewOnCreate{false}
|
||||
{
|
||||
if( getTrack() )
|
||||
{
|
||||
@@ -185,19 +183,10 @@ void Clip::setStartTimeOffset( const TimePos &startTimeOffset )
|
||||
m_startTimeOffset = startTimeOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Clip::useCustomClipColor( bool b )
|
||||
void Clip::setColor(const std::optional<QColor>& color)
|
||||
{
|
||||
if (b == m_useCustomClipColor) { return; }
|
||||
m_useCustomClipColor = b;
|
||||
m_color = color;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
|
||||
bool Clip::hasColor()
|
||||
{
|
||||
return usesCustomClipColor() || getTrack()->useColor();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -192,9 +192,7 @@ QStringList ConfigManager::availableVstEmbedMethods()
|
||||
{
|
||||
QStringList methods;
|
||||
methods.append("none");
|
||||
#if QT_VERSION >= 0x050100
|
||||
methods.append("qt");
|
||||
#endif
|
||||
#ifdef LMMS_BUILD_WIN32
|
||||
methods.append("win32");
|
||||
#endif
|
||||
|
||||
@@ -149,7 +149,7 @@ unsigned int Controller::runningFrames()
|
||||
// Get position in seconds
|
||||
float Controller::runningTime()
|
||||
{
|
||||
return runningFrames() / Engine::audioEngine()->processingSampleRate();
|
||||
return runningFrames() / Engine::audioEngine()->outputSampleRate();
|
||||
}
|
||||
|
||||
|
||||
@@ -220,24 +220,12 @@ Controller * Controller::create( ControllerType _ct, Model * _parent )
|
||||
|
||||
Controller * Controller::create( const QDomElement & _this, Model * _parent )
|
||||
{
|
||||
Controller * c;
|
||||
if( static_cast<ControllerType>(_this.attribute( "type" ).toInt()) == ControllerType::Peak )
|
||||
{
|
||||
c = PeakController::getControllerBySetting( _this );
|
||||
}
|
||||
else
|
||||
{
|
||||
c = create(
|
||||
static_cast<ControllerType>( _this.attribute( "type" ).toInt() ),
|
||||
_parent );
|
||||
}
|
||||
|
||||
if( c != nullptr )
|
||||
{
|
||||
c->restoreState( _this );
|
||||
}
|
||||
|
||||
return( c );
|
||||
const auto controllerType = static_cast<ControllerType>(_this.attribute("type").toInt());
|
||||
auto controller = controllerType == ControllerType::Peak
|
||||
? PeakController::getControllerBySetting(_this)
|
||||
: create(controllerType, _parent);
|
||||
if (controller) { controller->restoreState(_this); }
|
||||
return controller;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "base64.h"
|
||||
#include "ConfigManager.h"
|
||||
@@ -42,6 +44,7 @@
|
||||
#include "embed.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "LocaleHelper.h"
|
||||
#include "Note.h"
|
||||
#include "PluginFactory.h"
|
||||
#include "ProjectVersion.h"
|
||||
#include "SongEditor.h"
|
||||
@@ -79,7 +82,9 @@ const std::vector<DataFile::UpgradeMethod> DataFile::UPGRADE_METHODS = {
|
||||
&DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange,
|
||||
&DataFile::upgrade_defaultTripleOscillatorHQ,
|
||||
&DataFile::upgrade_mixerRename , &DataFile::upgrade_bbTcoRename,
|
||||
&DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing
|
||||
&DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing,
|
||||
&DataFile::upgrade_loopsRename , &DataFile::upgrade_noteTypes,
|
||||
&DataFile::upgrade_fixCMTDelays
|
||||
};
|
||||
|
||||
// Vector of all versions that have upgrade routines.
|
||||
@@ -231,8 +236,11 @@ bool DataFile::validate( QString extension )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if( extension == "wav" || extension == "ogg" ||
|
||||
extension == "ds" )
|
||||
if( extension == "wav" || extension == "ogg" || extension == "ds"
|
||||
#ifdef LMMS_HAVE_SNDFILE_MP3
|
||||
|| extension == "mp3"
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -376,12 +384,12 @@ bool DataFile::writeFile(const QString& filename, bool withResources)
|
||||
}
|
||||
}
|
||||
|
||||
QFile outfile (fullNameTemp);
|
||||
QSaveFile outfile(fullNameTemp);
|
||||
|
||||
if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
showError(SongEditor::tr("Could not write file"),
|
||||
SongEditor::tr("Could not open %1 for writing. You probably are not permitted to"
|
||||
SongEditor::tr("Could not open %1 for writing. You probably are not permitted to "
|
||||
"write to this file. Please make sure you have write-access to "
|
||||
"the file and try again.").arg(fullName));
|
||||
|
||||
@@ -402,30 +410,29 @@ bool DataFile::writeFile(const QString& filename, bool withResources)
|
||||
write( ts );
|
||||
}
|
||||
|
||||
outfile.close();
|
||||
|
||||
// make sure the file has been written correctly
|
||||
if( QFileInfo( outfile.fileName() ).size() > 0 )
|
||||
if (!outfile.commit())
|
||||
{
|
||||
if( ConfigManager::inst()->value( "app", "disablebackup" ).toInt() )
|
||||
{
|
||||
// remove current file
|
||||
QFile::remove( fullName );
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove old backup file
|
||||
QFile::remove( fullNameBak );
|
||||
// move current file to backup file
|
||||
QFile::rename( fullName, fullNameBak );
|
||||
}
|
||||
// move temporary file to current file
|
||||
QFile::rename( fullNameTemp, fullName );
|
||||
|
||||
return true;
|
||||
showError(SongEditor::tr("Could not write file"),
|
||||
SongEditor::tr("An unknown error has occured and the file could not be saved."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (ConfigManager::inst()->value("app", "disablebackup").toInt())
|
||||
{
|
||||
// remove current file
|
||||
QFile::remove(fullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove old backup file
|
||||
QFile::remove(fullNameBak);
|
||||
// move current file to backup file
|
||||
QFile::rename(fullName, fullNameBak);
|
||||
}
|
||||
// move temporary file to current file
|
||||
QFile::rename(fullNameTemp, fullName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -978,8 +985,7 @@ void DataFile::upgrade_0_4_0_20080622()
|
||||
{
|
||||
QDomElement el = list.item( i ).toElement();
|
||||
QString s = el.attribute( "name" );
|
||||
s.replace( QRegExp( "^Beat/Baseline " ),
|
||||
"Beat/Bassline " );
|
||||
s.replace(QRegularExpression("^Beat/Baseline "), "Beat/Bassline");
|
||||
el.setAttribute( "name", s );
|
||||
}
|
||||
}
|
||||
@@ -1114,7 +1120,7 @@ void DataFile::upgrade_1_1_91()
|
||||
{
|
||||
QDomElement el = list.item( i ).toElement();
|
||||
QString s = el.attribute( "src" );
|
||||
s.replace( QRegExp("/samples/bassloopes/"), "/samples/bassloops/" );
|
||||
s.replace(QRegularExpression("/samples/bassloopes/"), "/samples/bassloops/");
|
||||
el.setAttribute( "src", s );
|
||||
}
|
||||
|
||||
@@ -1199,12 +1205,11 @@ void DataFile::upgrade_1_2_0_rc3()
|
||||
"pattern" );
|
||||
for( int j = 0; !patterns.item( j ).isNull(); ++j )
|
||||
{
|
||||
int patternLength, steps;
|
||||
QDomElement el = patterns.item( j ).toElement();
|
||||
if( el.attribute( "len" ) != "" )
|
||||
{
|
||||
patternLength = el.attribute( "len" ).toInt();
|
||||
steps = patternLength / 12;
|
||||
int patternLength = el.attribute( "len" ).toInt();
|
||||
int steps = patternLength / 12;
|
||||
el.setAttribute( "steps", steps );
|
||||
}
|
||||
}
|
||||
@@ -1461,7 +1466,7 @@ void DataFile::upgrade_1_3_0()
|
||||
if(num == 4)
|
||||
{
|
||||
// don't modify port 4, but some other ones:
|
||||
int zoom_port;
|
||||
int zoom_port = 0;
|
||||
if (plugin == "Equalizer5Band")
|
||||
zoom_port = 36;
|
||||
else if (plugin == "Equalizer8Band")
|
||||
@@ -1672,6 +1677,62 @@ void DataFile::upgrade_automationNodes()
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the negative length notes to StepNotes
|
||||
void DataFile::upgrade_noteTypes()
|
||||
{
|
||||
const auto notes = elementsByTagName("note");
|
||||
|
||||
for (int i = 0; i < notes.size(); ++i)
|
||||
{
|
||||
auto note = notes.item(i).toElement();
|
||||
|
||||
const auto noteSize = note.attribute("len").toInt();
|
||||
if (noteSize < 0)
|
||||
{
|
||||
note.setAttribute("len", DefaultTicksPerBar / 16);
|
||||
note.setAttribute("type", static_cast<int>(Note::Type::Step));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataFile::upgrade_fixCMTDelays()
|
||||
{
|
||||
static const QMap<QString, QString> nameMap {
|
||||
{ "delay_0,01s", "delay_0.01s" },
|
||||
{ "delay_0,1s", "delay_0.1s" },
|
||||
{ "fbdelay_0,01s", "fbdelay_0.01s" },
|
||||
{ "fbdelay_0,1s", "fbdelay_0.1s" }
|
||||
};
|
||||
|
||||
const auto effects = elementsByTagName("effect");
|
||||
|
||||
for (int i = 0; i < effects.size(); ++i)
|
||||
{
|
||||
auto effect = effects.item(i).toElement();
|
||||
|
||||
// We are only interested in LADSPA plugins
|
||||
if (effect.attribute("name") != "ladspaeffect") { continue; }
|
||||
|
||||
// Fetch all attributes (LMMS) beneath the LADSPA effect so that we can check the value of the plugin attribute (XML)
|
||||
auto attributes = effect.elementsByTagName("attribute");
|
||||
for (int j = 0; j < attributes.size(); ++j)
|
||||
{
|
||||
auto attribute = attributes.item(j).toElement();
|
||||
|
||||
if (attribute.attribute("name") == "plugin")
|
||||
{
|
||||
const auto attributeValue = attribute.attribute("value");
|
||||
|
||||
const auto it = nameMap.constFind(attributeValue);
|
||||
if (it != nameMap.constEnd())
|
||||
{
|
||||
attribute.setAttribute("value", *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** \brief Note range has been extended to match MIDI specification
|
||||
*
|
||||
@@ -1814,7 +1875,72 @@ void DataFile::upgrade_sampleAndHold()
|
||||
// Correct old random wave LFO speeds
|
||||
if (e.attribute("wave").toInt() == 6)
|
||||
{
|
||||
e.setAttribute("speed",0.01f);
|
||||
e.setAttribute("speed", 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Change loops' filenames in <sampleclip>s
|
||||
void DataFile::upgrade_loopsRename()
|
||||
{
|
||||
auto createEntry = [](const QString& originalName, const QString& bpm, const QString& extension = "ogg")
|
||||
{
|
||||
const QString replacement = originalName + " - " + bpm + " BPM." + extension;
|
||||
return std::pair{originalName + "." + extension, replacement};
|
||||
};
|
||||
|
||||
static const QMap<QString, QString> namesToNamesWithBPMsMap {
|
||||
{ createEntry("bassloops/briff01", "140") },
|
||||
{ createEntry("bassloops/rave_bass01", "180") },
|
||||
{ createEntry("bassloops/rave_bass02", "180") },
|
||||
{ createEntry("bassloops/tb303_01", "123") },
|
||||
{ createEntry("bassloops/techno_bass01", "140") },
|
||||
{ createEntry("bassloops/techno_bass02", "140") },
|
||||
{ createEntry("bassloops/techno_synth01", "140") },
|
||||
{ createEntry("bassloops/techno_synth02", "140") },
|
||||
{ createEntry("bassloops/techno_synth03", "130") },
|
||||
{ createEntry("bassloops/techno_synth04", "140") },
|
||||
{ createEntry("beats/909beat01", "122") },
|
||||
{ createEntry("beats/break01", "168") },
|
||||
{ createEntry("beats/break02", "141") },
|
||||
{ createEntry("beats/break03", "168") },
|
||||
{ createEntry("beats/electro_beat01", "120") },
|
||||
{ createEntry("beats/electro_beat02", "119") },
|
||||
{ createEntry("beats/house_loop01", "142") },
|
||||
{ createEntry("beats/jungle01", "168") },
|
||||
{ createEntry("beats/rave_hihat01", "180") },
|
||||
{ createEntry("beats/rave_hihat02", "180") },
|
||||
{ createEntry("beats/rave_kick01", "180") },
|
||||
{ createEntry("beats/rave_kick02", "180") },
|
||||
{ createEntry("beats/rave_snare01", "180") },
|
||||
{ createEntry("latin/latin_brass01", "140") },
|
||||
{ createEntry("latin/latin_guitar01", "126") },
|
||||
{ createEntry("latin/latin_guitar02", "140") },
|
||||
{ createEntry("latin/latin_guitar03", "120") }
|
||||
};
|
||||
|
||||
// Replace loop sample names
|
||||
for (const auto& [elem, srcAttrs] : ELEMENTS_WITH_RESOURCES)
|
||||
{
|
||||
auto elements = elementsByTagName(elem);
|
||||
|
||||
for (const auto& srcAttr : srcAttrs)
|
||||
{
|
||||
for (int i = 0; i < elements.length(); ++i)
|
||||
{
|
||||
auto item = elements.item(i).toElement();
|
||||
|
||||
if (item.isNull() || !item.hasAttribute(srcAttr)) { continue; }
|
||||
|
||||
const QString srcVal = item.attribute(srcAttr);
|
||||
|
||||
const auto it = namesToNamesWithBPMsMap.constFind(srcVal);
|
||||
if (it != namesToNamesWithBPMsMap.constEnd())
|
||||
{
|
||||
item.setAttribute(srcAttr, *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1991,5 +2117,4 @@ unsigned int DataFile::legacyFileVersion()
|
||||
return std::distance( UPGRADE_VERSIONS.begin(), firstRequiredUpgrade );
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -69,16 +69,16 @@ float mem_t=1.0f, mem_o=1.0f, mem_n=1.0f, mem_b=1.0f, mem_tune=1.0f, mem_time=1.
|
||||
|
||||
int DrumSynth::LongestEnv()
|
||||
{
|
||||
long e, eon, p;
|
||||
float l=0.f;
|
||||
float l = 0.f;
|
||||
|
||||
for(e=1; e<7; e++) //3
|
||||
{
|
||||
eon = e - 1; if(eon>2) eon=eon-1;
|
||||
p = 0;
|
||||
while (envpts[e][0][p + 1] >= 0.f) p++;
|
||||
envData[e][MAX] = envpts[e][0][p] * timestretch;
|
||||
if(chkOn[eon]==1) if(envData[e][MAX]>l) l=envData[e][MAX];
|
||||
for (long e = 1; e < 7; e++) // 3
|
||||
{
|
||||
long eon = e - 1;
|
||||
if (eon > 2) { eon = eon - 1; }
|
||||
long p = 0;
|
||||
while (envpts[e][0][p + 1] >= 0.f) { p++; }
|
||||
envData[e][MAX] = envpts[e][0][p] * timestretch;
|
||||
if (chkOn[eon] == 1 && envData[e][MAX] > l) { l = envData[e][MAX]; }
|
||||
}
|
||||
//l *= timestretch;
|
||||
|
||||
@@ -102,16 +102,15 @@ float DrumSynth::LoudestEnv()
|
||||
|
||||
void DrumSynth::UpdateEnv(int e, long t)
|
||||
{
|
||||
float endEnv, dT;
|
||||
//0.2's added
|
||||
envData[e][NEXTT] = envpts[e][0][(long)(envData[e][PNT] + 1.f)] * timestretch; //get next point
|
||||
if(envData[e][NEXTT] < 0) envData[e][NEXTT] = 442000 * timestretch; //if end point, hold
|
||||
envData[e][ENV] = envpts[e][1][(long)(envData[e][PNT] + 0.f)] * 0.01f; //this level
|
||||
endEnv = envpts[e][1][(long)(envData[e][PNT] + 1.f)] * 0.01f; //next level
|
||||
dT = envData[e][NEXTT] - (float)t;
|
||||
if(dT < 1.0) dT = 1.0;
|
||||
envData[e][dENV] = (endEnv - envData[e][ENV]) / dT;
|
||||
envData[e][PNT] = envData[e][PNT] + 1.0f;
|
||||
// 0.2's added
|
||||
envData[e][NEXTT] = envpts[e][0][static_cast<long>(envData[e][PNT] + 1.f)] * timestretch; // get next point
|
||||
if (envData[e][NEXTT] < 0) { envData[e][NEXTT] = 442000 * timestretch; } // if end point, hold
|
||||
envData[e][ENV] = envpts[e][1][static_cast<long>(envData[e][PNT] + 0.f)] * 0.01f; // this level
|
||||
float endEnv = envpts[e][1][static_cast<long>(envData[e][PNT] + 1.f)] * 0.01f; // next level
|
||||
float dT = envData[e][NEXTT] - static_cast<float>(t);
|
||||
if (dT < 1.0) { dT = 1.0; }
|
||||
envData[e][dENV] = (endEnv - envData[e][ENV]) / dT;
|
||||
envData[e][PNT] = envData[e][PNT] + 1.0f;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,34 +148,41 @@ void DrumSynth::GetEnv(int env, const char *sec, const char *key, QString ini)
|
||||
|
||||
float DrumSynth::waveform(float ph, int form)
|
||||
{
|
||||
float w;
|
||||
float w;
|
||||
|
||||
switch (form)
|
||||
{
|
||||
case 0: w = (float)sin(fmod(ph,TwoPi)); break; //sine
|
||||
case 1: w = (float)fabs(2.0f*(float)sin(fmod(0.5f*ph,TwoPi)))-1.f; break; //sine^2
|
||||
case 2: while(ph<TwoPi) ph+=TwoPi;
|
||||
w = 0.6366197f * (float)fmod(ph,TwoPi) - 1.f; //tri
|
||||
if(w>1.f) w=2.f-w;
|
||||
break;
|
||||
case 3: w = ph - TwoPi * (float)(int)(ph / TwoPi); //saw
|
||||
w = (0.3183098f * w) - 1.f; break;
|
||||
default: w = (sin(fmod(ph,TwoPi))>0.0)? 1.f: -1.f; break; //square
|
||||
}
|
||||
switch (form)
|
||||
{
|
||||
case 0:
|
||||
w = static_cast<float>(sin(fmod(ph, TwoPi)));
|
||||
break; // sine
|
||||
case 1:
|
||||
w = static_cast<float>(fabs(2.0f * static_cast<float>(sin(fmod(0.5f * ph, TwoPi))) - 1.f));
|
||||
break; // sine^2
|
||||
case 2:
|
||||
while (ph < TwoPi) { ph += TwoPi; }
|
||||
w = 0.6366197f * static_cast<float>(fmod(ph, TwoPi) - 1.f); // tri
|
||||
if (w > 1.f) { w = 2.f - w; }
|
||||
break;
|
||||
case 3:
|
||||
w = ph - TwoPi * static_cast<float>(static_cast<int>(ph / TwoPi)); // saw
|
||||
w = (0.3183098f * w) - 1.f;
|
||||
break;
|
||||
default:
|
||||
w = (sin(fmod(ph, TwoPi)) > 0.0) ? 1.f : -1.f;
|
||||
break; // square
|
||||
}
|
||||
|
||||
return w;
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const char *def, char *buffer, int size, QString file)
|
||||
{
|
||||
stringstream is;
|
||||
bool inSection = false;
|
||||
char *line;
|
||||
char *k, *b;
|
||||
int len = 0;
|
||||
stringstream is;
|
||||
bool inSection = false;
|
||||
int len = 0;
|
||||
|
||||
line = (char*)malloc(200);
|
||||
char* line = static_cast<char*>(malloc(200));
|
||||
|
||||
// Use QFile to handle unicode file name on Windows
|
||||
// Previously we used ifstream directly
|
||||
@@ -201,8 +207,8 @@ int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const c
|
||||
if (line[0] == '[')
|
||||
break;
|
||||
|
||||
k = strtok(line, " \t=");
|
||||
b = strtok(nullptr, "\n\r\0");
|
||||
char* k = strtok(line, " \t=");
|
||||
char* b = strtok(nullptr, "\n\r\0");
|
||||
|
||||
if (k != 0 && strcasecmp(k, key)==0) {
|
||||
if (b==0) {
|
||||
|
||||
@@ -52,13 +52,19 @@ Effect::Effect( const Plugin::Descriptor * _desc,
|
||||
m_autoQuitModel( 1.0f, 1.0f, 8000.0f, 100.0f, 1.0f, this, tr( "Decay" ) ),
|
||||
m_autoQuitDisabled( false )
|
||||
{
|
||||
m_wetDryModel.setCenterValue(0);
|
||||
|
||||
m_srcState[0] = m_srcState[1] = nullptr;
|
||||
reinitSRC();
|
||||
|
||||
|
||||
if( ConfigManager::inst()->value( "ui", "disableautoquit").toInt() )
|
||||
{
|
||||
m_autoQuitDisabled = true;
|
||||
}
|
||||
|
||||
// Call the virtual method onEnabledChanged so that effects can react to changes,
|
||||
// e.g. by resetting state.
|
||||
connect(&m_enabledModel, &BoolModel::dataChanged, [this] { onEnabledChanged(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -208,8 +214,8 @@ void Effect::resample( int _i, const sampleFrame * _src_buf,
|
||||
m_srcData[_i].data_out = _dst_buf[0].data ();
|
||||
m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr;
|
||||
m_srcData[_i].end_of_input = 0;
|
||||
int error;
|
||||
if( ( error = src_process( m_srcState[_i], &m_srcData[_i] ) ) )
|
||||
|
||||
if (int error = src_process(m_srcState[_i], &m_srcData[_i]))
|
||||
{
|
||||
qFatal( "Effect::resample(): error while resampling: %s\n",
|
||||
src_strerror( error ) );
|
||||
|
||||
@@ -146,7 +146,7 @@ float Engine::framesPerTick(sample_rate_t sampleRate)
|
||||
|
||||
void Engine::updateFramesPerTick()
|
||||
{
|
||||
s_framesPerTick = s_audioEngine->processingSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
|
||||
s_framesPerTick = s_audioEngine->outputSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,13 +22,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "Oscillator.h"
|
||||
|
||||
#include "PathUtil.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -118,7 +122,7 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters(
|
||||
m_controlEnvAmountModel( false, this, tr( "Modulate env amount" ) ),
|
||||
m_lfoFrame( 0 ),
|
||||
m_lfoAmountIsZero( false ),
|
||||
m_lfoShapeData( nullptr )
|
||||
m_lfoShapeData(nullptr)
|
||||
{
|
||||
m_amountModel.setCenterValue( 0 );
|
||||
m_lfoAmountModel.setCenterValue( 0 );
|
||||
@@ -221,7 +225,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset )
|
||||
shape_sample = Oscillator::sawSample( phase );
|
||||
break;
|
||||
case LfoShape::UserDefinedWave:
|
||||
shape_sample = m_userWave.userWaveSample( phase );
|
||||
shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase);
|
||||
break;
|
||||
case LfoShape::RandomWave:
|
||||
if( frame == 0 )
|
||||
@@ -354,7 +358,7 @@ void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc,
|
||||
m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" );
|
||||
m_x100Model.saveSettings( _doc, _parent, "x100" );
|
||||
m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" );
|
||||
_parent.setAttribute( "userwavefile", m_userWave.audioFile() );
|
||||
_parent.setAttribute("userwavefile", m_userWave->audioFile());
|
||||
}
|
||||
|
||||
|
||||
@@ -386,7 +390,14 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this )
|
||||
m_sustainModel.setValue( 1.0 - m_sustainModel.value() );
|
||||
}
|
||||
|
||||
m_userWave.setAudioFile( _this.attribute( "userwavefile" ) );
|
||||
if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
|
||||
{
|
||||
m_userWave = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
|
||||
}
|
||||
|
||||
updateSampleVars();
|
||||
}
|
||||
@@ -399,7 +410,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
QMutexLocker m(&m_paramMutex);
|
||||
|
||||
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
|
||||
Engine::audioEngine()->processingSampleRate();
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
// TODO: Remove the expKnobVals, time should be linear
|
||||
const auto predelay_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_predelayModel.value()));
|
||||
@@ -498,7 +509,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
|
||||
|
||||
|
||||
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
|
||||
Engine::audioEngine()->processingSampleRate();
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
m_lfoPredelayFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
|
||||
expKnobVal( m_lfoPredelayModel.value() ) );
|
||||
m_lfoAttackFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
|
||||
|
||||
96
src/core/FileSearch.cpp
Normal file
96
src/core/FileSearch.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* FileSearch.cpp - File system search task
|
||||
*
|
||||
* Copyright (c) 2024 saker
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FileSearch.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <lmmsconfig.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <mingw.thread.h>
|
||||
#else
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
namespace lmms {
|
||||
FileSearch::FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions,
|
||||
const QStringList& excludedPaths, QDir::Filters dirFilters, QDir::SortFlags sortFlags)
|
||||
: m_filter(filter)
|
||||
, m_paths(paths)
|
||||
, m_extensions(extensions)
|
||||
, m_excludedPaths(excludedPaths)
|
||||
, m_dirFilters(dirFilters)
|
||||
, m_sortFlags(sortFlags)
|
||||
{
|
||||
}
|
||||
|
||||
void FileSearch::operator()()
|
||||
{
|
||||
auto stack = QFileInfoList{};
|
||||
for (const auto& path : m_paths)
|
||||
{
|
||||
if (m_excludedPaths.contains(path)) { continue; }
|
||||
|
||||
auto dir = QDir{path};
|
||||
stack.append(dir.entryInfoList(m_dirFilters, m_sortFlags));
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
if (m_cancel.load(std::memory_order_relaxed)) { return; }
|
||||
|
||||
const auto info = stack.takeFirst();
|
||||
const auto entryPath = info.absoluteFilePath();
|
||||
if (m_excludedPaths.contains(entryPath)) { continue; }
|
||||
|
||||
const auto name = info.fileName();
|
||||
const auto validFile = info.isFile() && m_extensions.contains(info.suffix(), Qt::CaseInsensitive);
|
||||
const auto passesFilter = name.contains(m_filter, Qt::CaseInsensitive);
|
||||
|
||||
if ((validFile || info.isDir()) && passesFilter)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{MillisecondsBetweenResults});
|
||||
emit foundMatch(this, entryPath);
|
||||
}
|
||||
|
||||
if (info.isDir())
|
||||
{
|
||||
dir.setPath(entryPath);
|
||||
const auto entries = dir.entryInfoList(m_dirFilters, m_sortFlags);
|
||||
|
||||
// Reverse to maintain the sorting within this directory when popped
|
||||
std::for_each(entries.rbegin(), entries.rend(), [&stack](const auto& entry) { stack.push_front(entry); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit searchCompleted(this);
|
||||
}
|
||||
|
||||
void FileSearch::cancel()
|
||||
{
|
||||
m_cancel.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -37,9 +37,11 @@ namespace lmms
|
||||
|
||||
Instrument::Instrument(InstrumentTrack * _instrument_track,
|
||||
const Descriptor * _descriptor,
|
||||
const Descriptor::SubPluginFeatures::Key *key) :
|
||||
const Descriptor::SubPluginFeatures::Key *key,
|
||||
Flags flags) :
|
||||
Plugin(_descriptor, nullptr/* _instrument_track*/, key),
|
||||
m_instrumentTrack( _instrument_track )
|
||||
m_instrumentTrack( _instrument_track ),
|
||||
m_flags(flags)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -179,27 +181,33 @@ void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n)
|
||||
|
||||
void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n )
|
||||
{
|
||||
const fpp_t frames = _n->framesLeftForCurrentPeriod();
|
||||
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();
|
||||
const f_cnt_t fl = _n->framesLeft();
|
||||
if( fl <= desiredReleaseFrames()+fpp )
|
||||
const auto fpp = Engine::audioEngine()->framesPerPeriod();
|
||||
const auto releaseFrames = desiredReleaseFrames();
|
||||
|
||||
const auto endFrame = _n->framesLeft();
|
||||
const auto startFrame = std::max(0, endFrame - releaseFrames);
|
||||
|
||||
for (auto f = startFrame; f < endFrame && f < fpp; f++)
|
||||
{
|
||||
for( fpp_t f = (fpp_t)( ( fl > desiredReleaseFrames() ) ?
|
||||
(std::max(fpp - desiredReleaseFrames(), 0) +
|
||||
fl % fpp) : 0); f < frames; ++f)
|
||||
const float fac = (float)(endFrame - f) / (float)releaseFrames;
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ch++)
|
||||
{
|
||||
const float fac = (float)( fl-f-1 ) /
|
||||
desiredReleaseFrames();
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
{
|
||||
buf[f][ch] *= fac;
|
||||
}
|
||||
buf[f][ch] *= fac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float Instrument::computeReleaseTimeMsByFrameCount(f_cnt_t frames) const
|
||||
{
|
||||
return frames / getSampleRate() * 1000.;
|
||||
}
|
||||
|
||||
|
||||
sample_rate_t Instrument::getSampleRate() const
|
||||
{
|
||||
return Engine::audioEngine()->outputSampleRate();
|
||||
}
|
||||
|
||||
|
||||
QString Instrument::fullDisplayName() const
|
||||
{
|
||||
|
||||
@@ -369,7 +369,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
const int total_range = range * cnphv.size();
|
||||
|
||||
// number of frames that every note should be played
|
||||
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->processingSampleRate());
|
||||
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->outputSampleRate());
|
||||
const auto gated_frames = (f_cnt_t)(m_arpGateModel.value() * arp_frames / 100.0f);
|
||||
|
||||
// used for calculating remaining frames for arp-note, we have to add
|
||||
@@ -433,42 +433,24 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
|
||||
int cur_arp_idx = 0;
|
||||
// process according to arpeggio-direction...
|
||||
if( dir == ArpDirection::Up )
|
||||
if (dir == ArpDirection::Up || dir == ArpDirection::Down)
|
||||
{
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % range;
|
||||
}
|
||||
else if( dir == ArpDirection::Down )
|
||||
{
|
||||
cur_arp_idx = range - ( cur_frame / arp_frames ) %
|
||||
range - 1;
|
||||
}
|
||||
else if( dir == ArpDirection::UpAndDown && range > 1 )
|
||||
else if ((dir == ArpDirection::UpAndDown || dir == ArpDirection::DownAndUp) && range > 1)
|
||||
{
|
||||
// imagine, we had to play the arp once up and then
|
||||
// once down -> makes 2 * range possible notes...
|
||||
// because we don't play the lower and upper notes
|
||||
// twice, we have to subtract 2
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 );
|
||||
cur_arp_idx = (cur_frame / arp_frames) % (range * 2 - (2 * static_cast<int>(m_arpRepeatsModel.value())));
|
||||
// if greater than range, we have to play down...
|
||||
// looks like the code for arp_dir==DOWN... :)
|
||||
if( cur_arp_idx >= range )
|
||||
if (cur_arp_idx >= range)
|
||||
{
|
||||
cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1;
|
||||
cur_arp_idx = range - cur_arp_idx % (range - 1) - static_cast<int>(m_arpRepeatsModel.value());
|
||||
}
|
||||
}
|
||||
else if( dir == ArpDirection::DownAndUp && range > 1 )
|
||||
{
|
||||
// copied from ArpDirection::UpAndDown above
|
||||
cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 );
|
||||
// if greater than range, we have to play down...
|
||||
// looks like the code for arp_dir==DOWN... :)
|
||||
if( cur_arp_idx >= range )
|
||||
{
|
||||
cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1;
|
||||
}
|
||||
// inverts direction
|
||||
cur_arp_idx = range - cur_arp_idx - 1;
|
||||
}
|
||||
else if( dir == ArpDirection::Random )
|
||||
{
|
||||
// just pick a random chord-index
|
||||
@@ -485,6 +467,12 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
|
||||
cur_arp_idx %= static_cast<int>( range / m_arpRepeatsModel.value() );
|
||||
}
|
||||
|
||||
// If ArpDirection::Down or ArpDirection::DownAndUp, invert the final range.
|
||||
if (dir == ArpDirection::Down || dir == ArpDirection::DownAndUp)
|
||||
{
|
||||
cur_arp_idx = static_cast<int>(range / m_arpRepeatsModel.value()) - cur_arp_idx - 1;
|
||||
}
|
||||
|
||||
// now calculate final key for our arp-note
|
||||
const int sub_note_key = base_note_key + (cur_arp_idx / cur_chord_size ) *
|
||||
KeysPerOctave + chord_table.chords()[selected_arp][cur_arp_idx % cur_chord_size];
|
||||
|
||||
@@ -46,24 +46,22 @@ void InstrumentPlayHandle::play(sampleFrame * working_buffer)
|
||||
|
||||
// ensure that all our nph's have been processed first
|
||||
auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true);
|
||||
|
||||
|
||||
bool nphsLeft;
|
||||
do
|
||||
{
|
||||
nphsLeft = false;
|
||||
for (const NotePlayHandle * constNotePlayHandle : nphv)
|
||||
for (const auto& handle : nphv)
|
||||
{
|
||||
if (constNotePlayHandle->state() != ThreadableJob::ProcessingState::Done &&
|
||||
!constNotePlayHandle->isFinished())
|
||||
if (handle->state() != ThreadableJob::ProcessingState::Done && !handle->isFinished())
|
||||
{
|
||||
nphsLeft = true;
|
||||
NotePlayHandle * notePlayHandle = const_cast<NotePlayHandle *>(constNotePlayHandle);
|
||||
notePlayHandle->process();
|
||||
const_cast<NotePlayHandle*>(handle)->process();
|
||||
}
|
||||
}
|
||||
}
|
||||
while (nphsLeft);
|
||||
|
||||
|
||||
m_instrument->play(working_buffer);
|
||||
|
||||
// Process the audio buffer that the instrument has just worked on...
|
||||
|
||||
@@ -158,7 +158,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
|
||||
|
||||
if( n->m_filter == nullptr )
|
||||
{
|
||||
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->processingSampleRate() );
|
||||
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->outputSampleRate() );
|
||||
}
|
||||
n->m_filter->setFilterType( static_cast<BasicFilters<>::FilterType>(m_filterModel.value()) );
|
||||
|
||||
@@ -303,7 +303,7 @@ f_cnt_t InstrumentSoundShaping::releaseFrames() const
|
||||
|
||||
f_cnt_t ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames();
|
||||
|
||||
if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::Flag::IsSingleStreamed ) )
|
||||
if (m_instrumentTrack->instrument()->isSingleStreamed())
|
||||
{
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
@@ -122,14 +122,8 @@ LadspaManager::~LadspaManager()
|
||||
LadspaManagerDescription * LadspaManager::getDescription(
|
||||
const ladspa_key_t & _plugin )
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin ) )
|
||||
{
|
||||
return( m_ladspaManagerMap[_plugin] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( nullptr );
|
||||
}
|
||||
auto const it = m_ladspaManagerMap.find(_plugin);
|
||||
return it != m_ladspaManagerMap.end() ? *it : nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,11 +133,7 @@ void LadspaManager::addPlugins(
|
||||
LADSPA_Descriptor_Function _descriptor_func,
|
||||
const QString & _file )
|
||||
{
|
||||
const LADSPA_Descriptor * descriptor;
|
||||
|
||||
for( long pluginIndex = 0;
|
||||
( descriptor = _descriptor_func( pluginIndex ) ) != nullptr;
|
||||
++pluginIndex )
|
||||
for (long pluginIndex = 0; const auto descriptor = _descriptor_func(pluginIndex); ++pluginIndex)
|
||||
{
|
||||
ladspa_key_t key( _file, QString( descriptor->Label ) );
|
||||
if( m_ladspaManagerMap.contains( key ) )
|
||||
@@ -523,24 +513,16 @@ bool LadspaManager::isInteger( const ladspa_key_t & _plugin,
|
||||
|
||||
bool LadspaManager::isEnum( const ladspa_key_t & _plugin, uint32_t _port )
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin )
|
||||
&& _port < getPortCount( _plugin ) )
|
||||
auto const * desc = getDescriptor(_plugin);
|
||||
if (desc && _port < desc->PortCount)
|
||||
{
|
||||
LADSPA_Descriptor_Function descriptorFunction =
|
||||
m_ladspaManagerMap[_plugin]->descriptorFunction;
|
||||
const LADSPA_Descriptor * descriptor =
|
||||
descriptorFunction(
|
||||
m_ladspaManagerMap[_plugin]->index );
|
||||
LADSPA_PortRangeHintDescriptor hintDescriptor =
|
||||
descriptor->PortRangeHints[_port].HintDescriptor;
|
||||
desc->PortRangeHints[_port].HintDescriptor;
|
||||
// This is an LMMS extension to ladspa
|
||||
return( LADSPA_IS_HINT_INTEGER( hintDescriptor ) &&
|
||||
LADSPA_IS_HINT_TOGGLED( hintDescriptor ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( false );
|
||||
return LADSPA_IS_HINT_INTEGER(hintDescriptor) && LADSPA_IS_HINT_TOGGLED(hintDescriptor);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -566,22 +548,20 @@ const void * LadspaManager::getImplementationData(
|
||||
|
||||
|
||||
|
||||
const LADSPA_Descriptor * LadspaManager::getDescriptor(
|
||||
const ladspa_key_t & _plugin )
|
||||
const LADSPA_Descriptor * LadspaManager::getDescriptor(const ladspa_key_t & _plugin)
|
||||
{
|
||||
if( m_ladspaManagerMap.contains( _plugin ) )
|
||||
auto const it = m_ladspaManagerMap.find(_plugin);
|
||||
if (it != m_ladspaManagerMap.end())
|
||||
{
|
||||
LADSPA_Descriptor_Function descriptorFunction =
|
||||
m_ladspaManagerMap[_plugin]->descriptorFunction;
|
||||
const LADSPA_Descriptor * descriptor =
|
||||
descriptorFunction(
|
||||
m_ladspaManagerMap[_plugin]->index );
|
||||
return( descriptor );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( nullptr );
|
||||
auto const plugin = *it;
|
||||
|
||||
LADSPA_Descriptor_Function descriptorFunction = plugin->descriptorFunction;
|
||||
const LADSPA_Descriptor* descriptor = descriptorFunction(plugin->index);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,13 +23,15 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
|
||||
#include "LfoController.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Song.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "PathUtil.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "Song.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -48,7 +50,7 @@ LfoController::LfoController( Model * _parent ) :
|
||||
m_phaseOffset( 0 ),
|
||||
m_currentPhase( 0 ),
|
||||
m_sampleFunction( &Oscillator::sinSample ),
|
||||
m_userDefSampleBuffer( new SampleBuffer )
|
||||
m_userDefSampleBuffer(std::make_shared<SampleBuffer>())
|
||||
{
|
||||
setSampleExact( true );
|
||||
connect( &m_waveModel, SIGNAL(dataChanged()),
|
||||
@@ -74,7 +76,6 @@ LfoController::LfoController( Model * _parent ) :
|
||||
|
||||
LfoController::~LfoController()
|
||||
{
|
||||
sharedObject::unref( m_userDefSampleBuffer );
|
||||
m_baseModel.disconnect( this );
|
||||
m_speedModel.disconnect( this );
|
||||
m_amountModel.disconnect( this );
|
||||
@@ -122,7 +123,7 @@ void LfoController::updateValueBuffer()
|
||||
}
|
||||
case Oscillator::WaveShape::UserDefined:
|
||||
{
|
||||
currentSample = m_userDefSampleBuffer->userWaveSample(phase);
|
||||
currentSample = Oscillator::userWaveSample(m_userDefSampleBuffer.get(), phase);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -154,7 +155,7 @@ void LfoController::updatePhase()
|
||||
|
||||
void LfoController::updateDuration()
|
||||
{
|
||||
float newDurationF = Engine::audioEngine()->processingSampleRate() * m_speedModel.value();
|
||||
float newDurationF = Engine::audioEngine()->outputSampleRate() * m_speedModel.value();
|
||||
|
||||
switch(m_multiplierModel.value() )
|
||||
{
|
||||
@@ -222,7 +223,7 @@ void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
m_phaseModel.saveSettings( _doc, _this, "phase" );
|
||||
m_waveModel.saveSettings( _doc, _this, "wave" );
|
||||
m_multiplierModel.saveSettings( _doc, _this, "multiplier" );
|
||||
_this.setAttribute( "userwavefile" , m_userDefSampleBuffer->audioFile() );
|
||||
_this.setAttribute("userwavefile", m_userDefSampleBuffer->audioFile());
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +238,15 @@ void LfoController::loadSettings( const QDomElement & _this )
|
||||
m_phaseModel.loadSettings( _this, "phase" );
|
||||
m_waveModel.loadSettings( _this, "wave" );
|
||||
m_multiplierModel.loadSettings( _this, "multiplier" );
|
||||
m_userDefSampleBuffer->setAudioFile( _this.attribute("userwavefile" ) );
|
||||
|
||||
if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists())
|
||||
{
|
||||
m_userDefSampleBuffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile"));
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); }
|
||||
}
|
||||
|
||||
updateSampleFunction();
|
||||
}
|
||||
|
||||
@@ -143,9 +143,7 @@ bool LinkedModelGroup::containsModel(const QString &name) const
|
||||
void LinkedModelGroups::linkAllModels()
|
||||
{
|
||||
LinkedModelGroup* first = getGroup(0);
|
||||
LinkedModelGroup* cur;
|
||||
|
||||
for (std::size_t i = 1; (cur = getGroup(i)); ++i)
|
||||
for (size_t i = 1; auto cur = getGroup(i); ++i)
|
||||
{
|
||||
first->linkControls(cur);
|
||||
}
|
||||
@@ -172,8 +170,7 @@ void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that)
|
||||
void LinkedModelGroups::loadSettings(const QDomElement& that)
|
||||
{
|
||||
QDomElement models = that.firstChildElement("models");
|
||||
LinkedModelGroup* grp0;
|
||||
if (!models.isNull() && (grp0 = getGroup(0)))
|
||||
if (auto grp0 = getGroup(0); !models.isNull() && grp0)
|
||||
{
|
||||
// only load the first group, the others are linked to the first
|
||||
grp0->loadValues(models);
|
||||
|
||||
@@ -36,15 +36,14 @@ namespace lmms
|
||||
*/
|
||||
void* MemoryHelper::alignedMalloc( size_t byteNum )
|
||||
{
|
||||
char *ptr, *ptr2, *aligned_ptr;
|
||||
int align_mask = LMMS_ALIGN_SIZE - 1;
|
||||
|
||||
ptr = static_cast<char*>( malloc( byteNum + LMMS_ALIGN_SIZE + sizeof( int ) ) );
|
||||
char* ptr = static_cast<char*>(malloc(byteNum + LMMS_ALIGN_SIZE + sizeof(int)));
|
||||
|
||||
if( ptr == nullptr ) return nullptr;
|
||||
|
||||
ptr2 = ptr + sizeof( int );
|
||||
aligned_ptr = ptr2 + ( LMMS_ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) );
|
||||
char* ptr2 = ptr + sizeof(int);
|
||||
char* aligned_ptr = ptr2 + (LMMS_ALIGN_SIZE - ((size_t)ptr2 & align_mask));
|
||||
|
||||
ptr2 = aligned_ptr - sizeof( int );
|
||||
*( ( int* ) ptr2 ) = ( int )( aligned_ptr - ptr );
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* MemoryManager.cpp
|
||||
*
|
||||
* Copyright (c) 2017 Lukas W <lukaswhl/at/gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "MemoryManager.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include "rpmalloc.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
/// Global static object handling rpmalloc intializing and finalizing
|
||||
struct MemoryManagerGlobalGuard {
|
||||
MemoryManagerGlobalGuard() {
|
||||
rpmalloc_initialize();
|
||||
}
|
||||
~MemoryManagerGlobalGuard() {
|
||||
rpmalloc_finalize();
|
||||
}
|
||||
} static mm_global_guard;
|
||||
|
||||
|
||||
namespace {
|
||||
static thread_local size_t thread_guard_depth;
|
||||
}
|
||||
|
||||
MemoryManager::ThreadGuard::ThreadGuard()
|
||||
{
|
||||
if (thread_guard_depth++ == 0) {
|
||||
rpmalloc_thread_initialize();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryManager::ThreadGuard::~ThreadGuard()
|
||||
{
|
||||
if (--thread_guard_depth == 0) {
|
||||
rpmalloc_thread_finalize(true);
|
||||
}
|
||||
}
|
||||
|
||||
static thread_local MemoryManager::ThreadGuard local_mm_thread_guard{};
|
||||
|
||||
void* MemoryManager::alloc(size_t size)
|
||||
{
|
||||
// Reference local thread guard to ensure it is initialized.
|
||||
// Compilers may optimize the instance away otherwise.
|
||||
Q_UNUSED(&local_mm_thread_guard);
|
||||
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::alloc", "Thread not initialized");
|
||||
return rpmalloc(size);
|
||||
}
|
||||
|
||||
|
||||
void MemoryManager::free(void * ptr)
|
||||
{
|
||||
Q_UNUSED(&local_mm_thread_guard);
|
||||
Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::free", "Thread not initialized");
|
||||
return rpfree(ptr);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -178,6 +178,15 @@ struct AddSwappedMultipliedOp
|
||||
const float m_coeff;
|
||||
};
|
||||
|
||||
void multiply(sampleFrame* dst, float coeff, int frames)
|
||||
{
|
||||
for (int i = 0; i < frames; ++i)
|
||||
{
|
||||
dst[i][0] *= coeff;
|
||||
dst[i][1] *= coeff;
|
||||
}
|
||||
}
|
||||
|
||||
void addSwappedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames )
|
||||
{
|
||||
run<>( dst, src, frames, AddSwappedMultipliedOp(coeffSrc) );
|
||||
|
||||
@@ -72,7 +72,6 @@ MixerChannel::MixerChannel( int idx, Model * _parent ) :
|
||||
m_lock(),
|
||||
m_channelIndex( idx ),
|
||||
m_queued( false ),
|
||||
m_hasColor( false ),
|
||||
m_dependenciesMet(0)
|
||||
{
|
||||
BufferManager::clear( m_buffer, Engine::audioEngine()->framesPerPeriod() );
|
||||
@@ -722,6 +721,7 @@ void Mixer::clearChannel(mix_ch_t 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" ) );
|
||||
ch->setColor(std::nullopt);
|
||||
|
||||
// send only to master
|
||||
if( index > 0)
|
||||
@@ -759,7 +759,7 @@ void Mixer::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
ch->m_soloModel.saveSettings( _doc, mixch, "soloed" );
|
||||
mixch.setAttribute( "num", i );
|
||||
mixch.setAttribute( "name", ch->m_name );
|
||||
if( ch->m_hasColor ) mixch.setAttribute( "color", ch->m_color.name() );
|
||||
if (const auto& color = ch->color()) { mixch.setAttribute("color", color->name()); }
|
||||
|
||||
// add the channel sends
|
||||
for (const auto& send : ch->m_sends)
|
||||
@@ -805,10 +805,9 @@ void Mixer::loadSettings( const QDomElement & _this )
|
||||
m_mixerChannels[num]->m_muteModel.loadSettings( mixch, "muted" );
|
||||
m_mixerChannels[num]->m_soloModel.loadSettings( mixch, "soloed" );
|
||||
m_mixerChannels[num]->m_name = mixch.attribute( "name" );
|
||||
if( mixch.hasAttribute( "color" ) )
|
||||
if (mixch.hasAttribute("color"))
|
||||
{
|
||||
m_mixerChannels[num]->m_hasColor = true;
|
||||
m_mixerChannels[num]->m_color.setNamedColor( mixch.attribute( "color" ) );
|
||||
m_mixerChannels[num]->setColor(QColor{mixch.attribute("color")});
|
||||
}
|
||||
|
||||
m_mixerChannels[num]->m_fxChain.restoreState( mixch.firstChildElement(
|
||||
|
||||
@@ -74,7 +74,8 @@ Note::Note( const Note & note ) :
|
||||
m_panning( note.m_panning ),
|
||||
m_length( note.m_length ),
|
||||
m_pos( note.m_pos ),
|
||||
m_detuning( nullptr )
|
||||
m_detuning(nullptr),
|
||||
m_type(note.m_type)
|
||||
{
|
||||
if( note.m_detuning )
|
||||
{
|
||||
@@ -179,6 +180,7 @@ void Note::saveSettings( QDomDocument & doc, QDomElement & parent )
|
||||
parent.setAttribute( "pan", m_panning );
|
||||
parent.setAttribute( "len", m_length );
|
||||
parent.setAttribute( "pos", m_pos );
|
||||
parent.setAttribute("type", static_cast<int>(m_type));
|
||||
|
||||
if( m_detuning && m_length )
|
||||
{
|
||||
@@ -197,6 +199,9 @@ void Note::loadSettings( const QDomElement & _this )
|
||||
m_panning = _this.attribute( "pan" ).toInt();
|
||||
m_length = _this.attribute( "len" ).toInt();
|
||||
m_pos = _this.attribute( "pos" ).toInt();
|
||||
// Default m_type value is 0, which corresponds to RegularNote
|
||||
static_assert(0 == static_cast<int>(Type::Regular));
|
||||
m_type = static_cast<Type>(_this.attribute("type", "0").toInt());
|
||||
|
||||
if( _this.hasChildNodes() )
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
NotePlayHandle *parent,
|
||||
int midiEventChannel,
|
||||
Origin origin ) :
|
||||
PlayHandle( Type::NotePlayHandle, _offset ),
|
||||
PlayHandle( PlayHandle::Type::NotePlayHandle, _offset ),
|
||||
Note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ),
|
||||
m_pluginData( nullptr ),
|
||||
m_instrumentTrack( instrumentTrack ),
|
||||
@@ -109,7 +109,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack,
|
||||
m_instrumentTrack->midiNoteOn( *this );
|
||||
}
|
||||
|
||||
if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::Flag::IsSingleStreamed )
|
||||
if (m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->isSingleStreamed())
|
||||
{
|
||||
setUsesBuffer( false );
|
||||
}
|
||||
@@ -610,9 +610,9 @@ int NotePlayHandleManager::s_size;
|
||||
|
||||
void NotePlayHandleManager::init()
|
||||
{
|
||||
s_available = MM_ALLOC<NotePlayHandle*>( INITIAL_NPH_CACHE );
|
||||
s_available = new NotePlayHandle*[INITIAL_NPH_CACHE];
|
||||
|
||||
auto n = MM_ALLOC<NotePlayHandle>(INITIAL_NPH_CACHE);
|
||||
auto n = static_cast<NotePlayHandle *>(std::malloc(sizeof(NotePlayHandle) * INITIAL_NPH_CACHE));
|
||||
|
||||
for( int i=0; i < INITIAL_NPH_CACHE; ++i )
|
||||
{
|
||||
@@ -655,11 +655,11 @@ void NotePlayHandleManager::release( NotePlayHandle * nph )
|
||||
void NotePlayHandleManager::extend( int c )
|
||||
{
|
||||
s_size += c;
|
||||
auto tmp = MM_ALLOC<NotePlayHandle*>(s_size);
|
||||
MM_FREE( s_available );
|
||||
auto tmp = new NotePlayHandle*[s_size];
|
||||
delete[] s_available;
|
||||
s_available = tmp;
|
||||
|
||||
auto n = MM_ALLOC<NotePlayHandle>(c);
|
||||
auto n = static_cast<NotePlayHandle *>(std::malloc(sizeof(NotePlayHandle) * c));
|
||||
|
||||
for( int i=0; i < c; ++i )
|
||||
{
|
||||
@@ -670,7 +670,7 @@ void NotePlayHandleManager::extend( int c )
|
||||
|
||||
void NotePlayHandleManager::free()
|
||||
{
|
||||
MM_FREE(s_available);
|
||||
delete[] s_available;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ Oscillator::Oscillator(const IntModel *wave_shape_model,
|
||||
|
||||
void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator)
|
||||
{
|
||||
if (m_freq >= Engine::audioEngine()->processingSampleRate() / 2)
|
||||
if (m_freq >= Engine::audioEngine()->outputSampleRate() / 2)
|
||||
{
|
||||
BufferManager::clear(ab, frames);
|
||||
return;
|
||||
@@ -182,19 +182,23 @@ void Oscillator::generateFromFFT(int bands, sample_t* table)
|
||||
normalize(s_sampleBuffer.data(), table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1);
|
||||
}
|
||||
|
||||
void Oscillator::generateAntiAliasUserWaveTable(SampleBuffer *sampleBuffer)
|
||||
std::unique_ptr<OscillatorConstants::waveform_t> Oscillator::generateAntiAliasUserWaveTable(const SampleBuffer* sampleBuffer)
|
||||
{
|
||||
if (sampleBuffer->m_userAntiAliasWaveTable == nullptr) {return;}
|
||||
|
||||
auto userAntiAliasWaveTable = std::make_unique<OscillatorConstants::waveform_t>();
|
||||
for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i)
|
||||
{
|
||||
for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i)
|
||||
// TODO: This loop seems to be doing the same thing for each iteration of the outer loop,
|
||||
// and could probably be moved out of it
|
||||
for (int j = 0; j < OscillatorConstants::WAVETABLE_LENGTH; ++j)
|
||||
{
|
||||
s_sampleBuffer[i] = sampleBuffer->userWaveSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH);
|
||||
s_sampleBuffer[j] = Oscillator::userWaveSample(
|
||||
sampleBuffer, static_cast<float>(j) / OscillatorConstants::WAVETABLE_LENGTH);
|
||||
}
|
||||
fftwf_execute(s_fftPlan);
|
||||
Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*(sampleBuffer->m_userAntiAliasWaveTable))[i].data());
|
||||
Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*userAntiAliasWaveTable)[i].data());
|
||||
}
|
||||
|
||||
return userAntiAliasWaveTable;
|
||||
}
|
||||
|
||||
|
||||
@@ -677,7 +681,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
m_subOsc->update( _ab, _frames, _chnl, true );
|
||||
recalcPhase();
|
||||
const float osc_coeff = m_freq * m_detuning_div_samplerate;
|
||||
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->processingSampleRate();
|
||||
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
@@ -693,7 +697,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
|
||||
template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Sine>(const float sample)
|
||||
{
|
||||
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate();
|
||||
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ)
|
||||
{
|
||||
@@ -807,13 +811,13 @@ template<>
|
||||
inline sample_t Oscillator::getSample<Oscillator::WaveShape::UserDefined>(
|
||||
const float _sample )
|
||||
{
|
||||
if (m_useWaveTable && !m_isModulator)
|
||||
if (m_useWaveTable && m_userAntiAliasWaveTable && !m_isModulator)
|
||||
{
|
||||
return wtSample(m_userWave->m_userAntiAliasWaveTable, _sample);
|
||||
return wtSample(m_userAntiAliasWaveTable.get(), _sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
return userWaveSample(_sample);
|
||||
return userWaveSample(m_userWave.get(), _sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element)
|
||||
element.setAttribute( "len", length() );
|
||||
element.setAttribute("off", startTimeOffset());
|
||||
element.setAttribute( "muted", isMuted() );
|
||||
if( usesCustomClipColor() )
|
||||
if (const auto& c = color())
|
||||
{
|
||||
element.setAttribute( "color", color().name() );
|
||||
element.setAttribute("color", c->name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,20 +90,14 @@ void PatternClip::loadSettings(const QDomElement& element)
|
||||
if (!element.hasAttribute("usestyle"))
|
||||
{
|
||||
// for colors saved in 1.3-onwards
|
||||
setColor(element.attribute("color"));
|
||||
useCustomClipColor(true);
|
||||
setColor(QColor{element.attribute("color")});
|
||||
}
|
||||
else
|
||||
else if (element.attribute("usestyle").toUInt() == 0)
|
||||
{
|
||||
// for colors saved before 1.3
|
||||
setColor(QColor(element.attribute("color").toUInt()));
|
||||
useCustomClipColor(element.attribute("usestyle").toUInt() == 0);
|
||||
setColor(QColor{element.attribute("color").toUInt()});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useCustomClipColor(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ void PeakController::updateValueBuffer()
|
||||
{
|
||||
if( m_coeffNeedsUpdate )
|
||||
{
|
||||
const float ratio = 44100.0f / Engine::audioEngine()->processingSampleRate();
|
||||
const float ratio = 44100.0f / Engine::audioEngine()->outputSampleRate();
|
||||
m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio );
|
||||
m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio );
|
||||
m_coeffNeedsUpdate = false;
|
||||
|
||||
@@ -226,8 +226,8 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
|
||||
}
|
||||
else
|
||||
{
|
||||
InstantiationHook instantiationHook;
|
||||
if ((instantiationHook = ( InstantiationHook ) pi.library->resolve( "lmms_plugin_main" )))
|
||||
auto instantiationHook = reinterpret_cast<InstantiationHook>(pi.library->resolve("lmms_plugin_main"));
|
||||
if (instantiationHook)
|
||||
{
|
||||
inst = instantiationHook(parent, data);
|
||||
if(!inst) {
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <QDomElement>
|
||||
|
||||
#include "ProjectJournal.h"
|
||||
#include "Engine.h"
|
||||
#include "JournallingObject.h"
|
||||
#include "Song.h"
|
||||
#include "AutomationClip.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -67,6 +69,12 @@ void ProjectJournal::undo()
|
||||
jo->restoreState( c.data.content().firstChildElement() );
|
||||
setJournalling( prev );
|
||||
Engine::getSong()->setModified();
|
||||
|
||||
// loading AutomationClip connections correctly
|
||||
if (!c.data.content().elementsByTagName("automationclip").isEmpty())
|
||||
{
|
||||
AutomationClip::resolveAllIDs();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,6 @@ void ProjectRenderer::startProcessing()
|
||||
|
||||
void ProjectRenderer::run()
|
||||
{
|
||||
MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard);
|
||||
#if 0
|
||||
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD)
|
||||
#ifdef LMMS_HAVE_SCHED_H
|
||||
|
||||
@@ -535,7 +535,7 @@ bool RemotePlugin::processMessage( const message & _m )
|
||||
|
||||
case IdSampleRateInformation:
|
||||
reply = true;
|
||||
reply_message.addInt( Engine::audioEngine()->processingSampleRate() );
|
||||
reply_message.addInt( Engine::audioEngine()->outputSampleRate() );
|
||||
break;
|
||||
|
||||
case IdBufferSizeInformation:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "RenderManager.h"
|
||||
|
||||
@@ -182,7 +183,7 @@ QString RenderManager::pathForTrack(const Track *track, int num)
|
||||
{
|
||||
QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
|
||||
QString name = track->name();
|
||||
name = name.remove(QRegExp(FILENAME_FILTER));
|
||||
name = name.remove(QRegularExpression(FILENAME_FILTER));
|
||||
name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
|
||||
return QDir(m_outputPath).filePath(name);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace lmms
|
||||
|
||||
RingBuffer::RingBuffer( f_cnt_t size ) :
|
||||
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::audioEngine()->processingSampleRate() ),
|
||||
m_samplerate( Engine::audioEngine()->outputSampleRate() ),
|
||||
m_size( size + m_fpp )
|
||||
{
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
@@ -45,7 +45,7 @@ RingBuffer::RingBuffer( f_cnt_t size ) :
|
||||
|
||||
RingBuffer::RingBuffer( float size ) :
|
||||
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
|
||||
m_samplerate( Engine::audioEngine()->processingSampleRate() )
|
||||
m_samplerate( Engine::audioEngine()->outputSampleRate() )
|
||||
{
|
||||
m_size = msToFrames( size ) + m_fpp;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
@@ -307,9 +307,9 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset,
|
||||
|
||||
void RingBuffer::updateSamplerate()
|
||||
{
|
||||
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->processingSampleRate() ) / m_samplerate;
|
||||
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->outputSampleRate() ) / m_samplerate;
|
||||
m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
|
||||
m_samplerate = Engine::audioEngine()->processingSampleRate();
|
||||
m_samplerate = Engine::audioEngine()->outputSampleRate();
|
||||
delete[] m_buffer;
|
||||
m_buffer = new sampleFrame[ m_size ];
|
||||
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
|
||||
|
||||
249
src/core/Sample.cpp
Normal file
249
src/core/Sample.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Sample.cpp - State for container-class SampleBuffer
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Sample.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
Sample::Sample(const QString& audioFile)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(audioFile))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const QByteArray& base64, int sampleRate)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(base64, sampleRate))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const sampleFrame* data, size_t numFrames, int sampleRate)
|
||||
: m_buffer(std::make_shared<SampleBuffer>(data, numFrames, sampleRate))
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(std::shared_ptr<const SampleBuffer> buffer)
|
||||
: m_buffer(buffer)
|
||||
, m_startFrame(0)
|
||||
, m_endFrame(m_buffer->size())
|
||||
, m_loopStartFrame(0)
|
||||
, m_loopEndFrame(m_buffer->size())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(const Sample& other)
|
||||
: m_buffer(other.m_buffer)
|
||||
, m_startFrame(other.startFrame())
|
||||
, m_endFrame(other.endFrame())
|
||||
, m_loopStartFrame(other.loopStartFrame())
|
||||
, m_loopEndFrame(other.loopEndFrame())
|
||||
, m_amplification(other.amplification())
|
||||
, m_frequency(other.frequency())
|
||||
, m_reversed(other.reversed())
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(Sample&& other)
|
||||
: m_buffer(std::move(other.m_buffer))
|
||||
, m_startFrame(other.startFrame())
|
||||
, m_endFrame(other.endFrame())
|
||||
, m_loopStartFrame(other.loopStartFrame())
|
||||
, m_loopEndFrame(other.loopEndFrame())
|
||||
, m_amplification(other.amplification())
|
||||
, m_frequency(other.frequency())
|
||||
, m_reversed(other.reversed())
|
||||
{
|
||||
}
|
||||
|
||||
auto Sample::operator=(const Sample& other) -> Sample&
|
||||
{
|
||||
m_buffer = other.m_buffer;
|
||||
m_startFrame = other.startFrame();
|
||||
m_endFrame = other.endFrame();
|
||||
m_loopStartFrame = other.loopStartFrame();
|
||||
m_loopEndFrame = other.loopEndFrame();
|
||||
m_amplification = other.amplification();
|
||||
m_frequency = other.frequency();
|
||||
m_reversed = other.reversed();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Sample::operator=(Sample&& other) -> Sample&
|
||||
{
|
||||
m_buffer = std::move(other.m_buffer);
|
||||
m_startFrame = other.startFrame();
|
||||
m_endFrame = other.endFrame();
|
||||
m_loopStartFrame = other.loopStartFrame();
|
||||
m_loopEndFrame = other.loopEndFrame();
|
||||
m_amplification = other.amplification();
|
||||
m_frequency = other.frequency();
|
||||
m_reversed = other.reversed();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Sample::play(sampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const
|
||||
{
|
||||
assert(numFrames > 0);
|
||||
assert(desiredFrequency > 0);
|
||||
|
||||
const auto pastBounds = state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards);
|
||||
if (loopMode == Loop::Off && pastBounds) { return false; }
|
||||
|
||||
const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / desiredFrequency;
|
||||
const auto inputSampleRate = m_buffer->sampleRate();
|
||||
const auto resampleRatio = outputSampleRate / inputSampleRate;
|
||||
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];
|
||||
|
||||
state->m_frameIndex = std::max<int>(m_startFrame, state->m_frameIndex);
|
||||
|
||||
auto playBuffer = std::vector<sampleFrame>(numFrames / resampleRatio + marginSize);
|
||||
playRaw(playBuffer.data(), playBuffer.size(), state, loopMode);
|
||||
|
||||
const auto resampleResult
|
||||
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
|
||||
advance(state, resampleResult.inputFramesUsed, loopMode);
|
||||
|
||||
const auto outputFrames = resampleResult.outputFramesGenerated;
|
||||
if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, sampleFrame{}); }
|
||||
|
||||
if (!typeInfo<float>::isEqual(m_amplification, 1.0f))
|
||||
{
|
||||
for (int i = 0; i < numFrames; ++i)
|
||||
{
|
||||
dst[i][0] *= m_amplification;
|
||||
dst[i][1] *= m_amplification;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Sample::sampleDuration() const -> std::chrono::milliseconds
|
||||
{
|
||||
const auto numFrames = endFrame() - startFrame();
|
||||
const auto duration = numFrames / static_cast<float>(m_buffer->sampleRate()) * 1000;
|
||||
return std::chrono::milliseconds{static_cast<int>(duration)};
|
||||
}
|
||||
|
||||
void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame)
|
||||
{
|
||||
setStartFrame(startFrame);
|
||||
setEndFrame(endFrame);
|
||||
setLoopStartFrame(loopStartFrame);
|
||||
setLoopEndFrame(loopEndFrame);
|
||||
}
|
||||
|
||||
void Sample::playRaw(sampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const
|
||||
{
|
||||
if (m_buffer->size() < 1) { return; }
|
||||
|
||||
auto index = state->m_frameIndex;
|
||||
auto backwards = state->m_backwards;
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (index < 0 || index >= m_endFrame) { return; }
|
||||
break;
|
||||
case Loop::On:
|
||||
if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; }
|
||||
else if (index >= m_loopEndFrame) { index = m_loopStartFrame; }
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (index < m_loopStartFrame && backwards)
|
||||
{
|
||||
index = m_loopStartFrame;
|
||||
backwards = false;
|
||||
}
|
||||
else if (index >= m_loopEndFrame)
|
||||
{
|
||||
index = m_loopEndFrame - 1;
|
||||
backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index];
|
||||
backwards ? --index : ++index;
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const
|
||||
{
|
||||
state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount;
|
||||
if (loopMode == Loop::Off) { return; }
|
||||
|
||||
const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame);
|
||||
const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame);
|
||||
const auto loopSize = m_loopEndFrame - m_loopStartFrame;
|
||||
if (loopSize == 0) { return; }
|
||||
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::On:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % loopSize;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % loopSize;
|
||||
}
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize;
|
||||
state->m_backwards = false;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize;
|
||||
state->m_backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,21 +25,22 @@
|
||||
#include "SampleClip.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "PathUtil.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "SampleClipView.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "TimeLineWidget.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SampleClip::SampleClip( Track * _track ) :
|
||||
Clip( _track ),
|
||||
m_sampleBuffer( new SampleBuffer ),
|
||||
m_isPlaying( false )
|
||||
SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying)
|
||||
: Clip(_track)
|
||||
, m_sample(std::move(sample))
|
||||
, m_isPlaying(false)
|
||||
{
|
||||
saveJournallingState( false );
|
||||
setSampleFile( "" );
|
||||
@@ -52,16 +53,10 @@ SampleClip::SampleClip( Track * _track ) :
|
||||
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
|
||||
this, SLOT(updateLength()));
|
||||
|
||||
//care about positionmarker
|
||||
gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Song::PlayMode::Song ).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
|
||||
//care about loops and jumps
|
||||
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
|
||||
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
|
||||
//care about mute Clips
|
||||
@@ -87,14 +82,14 @@ SampleClip::SampleClip( Track * _track ) :
|
||||
updateTrackClips();
|
||||
}
|
||||
|
||||
SampleClip::SampleClip(const SampleClip& orig) :
|
||||
SampleClip(orig.getTrack())
|
||||
SampleClip::SampleClip(Track* track)
|
||||
: SampleClip(track, Sample(), false)
|
||||
{
|
||||
}
|
||||
|
||||
SampleClip::SampleClip(const SampleClip& orig) :
|
||||
SampleClip(orig.getTrack(), orig.m_sample, orig.m_isPlaying)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,9 +102,6 @@ SampleClip::~SampleClip()
|
||||
{
|
||||
sampletrack->updateClips();
|
||||
}
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
}
|
||||
|
||||
|
||||
@@ -120,36 +112,45 @@ void SampleClip::changeLength( const TimePos & _length )
|
||||
Clip::changeLength(std::max(static_cast<int>(_length), 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const QString & SampleClip::sampleFile() const
|
||||
void SampleClip::changeLengthToSampleLength()
|
||||
{
|
||||
return m_sampleBuffer->audioFile();
|
||||
int length = m_sample.sampleSize() / Engine::framesPerTick();
|
||||
changeLength(length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleBuffer( SampleBuffer* sb )
|
||||
const QString& SampleClip::sampleFile() const
|
||||
{
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
Engine::audioEngine()->doneChangeInModel();
|
||||
m_sampleBuffer = sb;
|
||||
return m_sample.sampleFile();
|
||||
}
|
||||
|
||||
bool SampleClip::hasSampleFileLoaded(const QString & filename) const
|
||||
{
|
||||
return m_sample.sampleFile() == filename;
|
||||
}
|
||||
|
||||
void SampleClip::setSampleBuffer(std::shared_ptr<const SampleBuffer> sb)
|
||||
{
|
||||
{
|
||||
const auto guard = Engine::audioEngine()->requestChangesGuard();
|
||||
m_sample = Sample(std::move(sb));
|
||||
}
|
||||
updateLength();
|
||||
|
||||
emit sampleChanged();
|
||||
|
||||
Engine::getSong()->setModified();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleFile(const QString & sf)
|
||||
void SampleClip::setSampleFile(const QString& sf)
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
if (!sf.isEmpty())
|
||||
{
|
||||
m_sampleBuffer->setAudioFile(sf);
|
||||
//Otherwise set it to the sample's length
|
||||
m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf));
|
||||
length = sampleLength();
|
||||
}
|
||||
|
||||
@@ -221,6 +222,8 @@ void SampleClip::setIsPlaying(bool isPlaying)
|
||||
void SampleClip::updateLength()
|
||||
{
|
||||
emit sampleChanged();
|
||||
|
||||
Engine::getSong()->setModified();
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +231,7 @@ void SampleClip::updateLength()
|
||||
|
||||
TimePos SampleClip::sampleLength() const
|
||||
{
|
||||
return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() );
|
||||
return static_cast<int>(m_sample.sampleSize() / Engine::framesPerTick(m_sample.sampleRate()));
|
||||
}
|
||||
|
||||
|
||||
@@ -236,7 +239,7 @@ TimePos SampleClip::sampleLength() const
|
||||
|
||||
void SampleClip::setSampleStartFrame(f_cnt_t startFrame)
|
||||
{
|
||||
m_sampleBuffer->setStartFrame( startFrame );
|
||||
m_sample.setStartFrame(startFrame);
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +247,7 @@ void SampleClip::setSampleStartFrame(f_cnt_t startFrame)
|
||||
|
||||
void SampleClip::setSamplePlayLength(f_cnt_t length)
|
||||
{
|
||||
m_sampleBuffer->setEndFrame( length );
|
||||
m_sample.setEndFrame(length);
|
||||
}
|
||||
|
||||
|
||||
@@ -267,15 +270,15 @@ void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
if( sampleFile() == "" )
|
||||
{
|
||||
QString s;
|
||||
_this.setAttribute( "data", m_sampleBuffer->toBase64( s ) );
|
||||
_this.setAttribute("data", m_sample.toBase64());
|
||||
}
|
||||
|
||||
_this.setAttribute( "sample_rate", m_sampleBuffer->sampleRate());
|
||||
if( usesCustomClipColor() )
|
||||
_this.setAttribute( "sample_rate", m_sample.sampleRate());
|
||||
if (const auto& c = color())
|
||||
{
|
||||
_this.setAttribute( "color", color().name() );
|
||||
_this.setAttribute("color", c->name());
|
||||
}
|
||||
if (m_sampleBuffer->reversed())
|
||||
if (m_sample.reversed())
|
||||
{
|
||||
_this.setAttribute("reversed", "true");
|
||||
}
|
||||
@@ -291,32 +294,36 @@ void SampleClip::loadSettings( const QDomElement & _this )
|
||||
{
|
||||
movePosition( _this.attribute( "pos" ).toInt() );
|
||||
}
|
||||
setSampleFile( _this.attribute( "src" ) );
|
||||
|
||||
if (const auto srcFile = _this.attribute("src"); !srcFile.isEmpty())
|
||||
{
|
||||
if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists())
|
||||
{
|
||||
setSampleFile(srcFile);
|
||||
}
|
||||
else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), srcFile)); }
|
||||
}
|
||||
|
||||
if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) )
|
||||
{
|
||||
m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) );
|
||||
if (_this.hasAttribute("sample_rate"))
|
||||
{
|
||||
m_sampleBuffer->setSampleRate(_this.attribute("sample_rate").toInt());
|
||||
}
|
||||
auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() :
|
||||
Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
auto buffer = gui::SampleLoader::createBufferFromBase64(_this.attribute("data"), sampleRate);
|
||||
m_sample = Sample(std::move(buffer));
|
||||
}
|
||||
changeLength( _this.attribute( "len" ).toInt() );
|
||||
setMuted( _this.attribute( "muted" ).toInt() );
|
||||
setStartTimeOffset( _this.attribute( "off" ).toInt() );
|
||||
|
||||
if( _this.hasAttribute( "color" ) )
|
||||
if (_this.hasAttribute("color"))
|
||||
{
|
||||
useCustomClipColor( true );
|
||||
setColor( _this.attribute( "color" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
useCustomClipColor(false);
|
||||
setColor(QColor{_this.attribute("color")});
|
||||
}
|
||||
|
||||
if(_this.hasAttribute("reversed"))
|
||||
{
|
||||
m_sampleBuffer->setReversed(true);
|
||||
m_sample.setReversed(true);
|
||||
emit wasReversed(); // tell SampleClipView to update the view
|
||||
}
|
||||
}
|
||||
|
||||
236
src/core/SampleDecoder.cpp
Normal file
236
src/core/SampleDecoder.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* SampleDecoder.cpp - Decodes audio files in various formats
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "SampleDecoder.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <sndfile.h>
|
||||
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#endif
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "DrumSynth.h"
|
||||
#include "Engine.h"
|
||||
#include "lmms_basics.h"
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace {
|
||||
|
||||
using Decoder = std::optional<SampleDecoder::Result> (*)(const QString&);
|
||||
|
||||
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
|
||||
#endif
|
||||
|
||||
static constexpr std::array<Decoder, 3> decoders = {&decodeSampleSF,
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
&decodeSampleOggVorbis,
|
||||
#endif
|
||||
&decodeSampleDS};
|
||||
|
||||
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
SNDFILE* sndFile = nullptr;
|
||||
auto sfInfo = SF_INFO{};
|
||||
|
||||
// TODO: Remove use of QFile
|
||||
auto file = QFile{audioFile};
|
||||
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
|
||||
|
||||
sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false);
|
||||
if (sf_error(sndFile) != 0) { return std::nullopt; }
|
||||
|
||||
auto buf = std::vector<sample_t>(sfInfo.channels * sfInfo.frames);
|
||||
sf_read_float(sndFile, buf.data(), buf.size());
|
||||
|
||||
sf_close(sndFile);
|
||||
file.close();
|
||||
|
||||
auto result = std::vector<sampleFrame>(sfInfo.frames);
|
||||
for (int i = 0; i < static_cast<int>(result.size()); ++i)
|
||||
{
|
||||
if (sfInfo.channels == 1)
|
||||
{
|
||||
// Upmix from mono to stereo
|
||||
result[i] = {buf[i], buf[i]};
|
||||
}
|
||||
else if (sfInfo.channels > 1)
|
||||
{
|
||||
// TODO: Add support for higher number of channels (i.e., 5.1 channel systems)
|
||||
// The current behavior assumes stereo in all cases excluding mono.
|
||||
// This may not be the expected behavior, given some audio files with a higher number of channels.
|
||||
result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]};
|
||||
}
|
||||
}
|
||||
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(sfInfo.samplerate)};
|
||||
}
|
||||
|
||||
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
// Populated by DrumSynth::GetDSFileSamples
|
||||
int_sample_t* dataPtr = nullptr;
|
||||
|
||||
auto ds = DrumSynth{};
|
||||
const auto engineRate = Engine::audioEngine()->outputSampleRate();
|
||||
const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate);
|
||||
const auto data = std::unique_ptr<int_sample_t[]>{dataPtr}; // NOLINT, we have to use a C-style array here
|
||||
|
||||
if (frames <= 0 || !data) { return std::nullopt; }
|
||||
|
||||
auto result = std::vector<sampleFrame>(frames);
|
||||
src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS);
|
||||
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(engineRate)};
|
||||
}
|
||||
|
||||
#ifdef LMMS_HAVE_OGGVORBIS
|
||||
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>
|
||||
{
|
||||
static auto s_read = [](void* buffer, size_t size, size_t count, void* stream) -> size_t {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
return file->read(static_cast<char*>(buffer), size * count);
|
||||
};
|
||||
|
||||
static auto s_seek = [](void* stream, ogg_int64_t offset, int whence) -> int {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
if (whence == SEEK_SET) { file->seek(offset); }
|
||||
else if (whence == SEEK_CUR) { file->seek(file->pos() + offset); }
|
||||
else if (whence == SEEK_END) { file->seek(file->size() + offset); }
|
||||
else { return -1; }
|
||||
return 0;
|
||||
};
|
||||
|
||||
static auto s_close = [](void* stream) -> int {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
file->close();
|
||||
return 0;
|
||||
};
|
||||
|
||||
static auto s_tell = [](void* stream) -> long {
|
||||
auto file = static_cast<QFile*>(stream);
|
||||
return file->pos();
|
||||
};
|
||||
|
||||
static ov_callbacks s_callbacks = {s_read, s_seek, s_close, s_tell};
|
||||
|
||||
// TODO: Remove use of QFile
|
||||
auto file = QFile{audioFile};
|
||||
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
|
||||
|
||||
auto vorbisFile = OggVorbis_File{};
|
||||
if (ov_open_callbacks(&file, &vorbisFile, nullptr, 0, s_callbacks) < 0) { return std::nullopt; }
|
||||
|
||||
const auto vorbisInfo = ov_info(&vorbisFile, -1);
|
||||
if (vorbisInfo == nullptr) { return std::nullopt; }
|
||||
|
||||
const auto numChannels = vorbisInfo->channels;
|
||||
const auto sampleRate = vorbisInfo->rate;
|
||||
const auto numSamples = ov_pcm_total(&vorbisFile, -1);
|
||||
if (numSamples < 0) { return std::nullopt; }
|
||||
|
||||
auto buffer = std::vector<float>(numSamples);
|
||||
auto output = static_cast<float**>(nullptr);
|
||||
|
||||
auto totalSamplesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
auto samplesRead = ov_read_float(&vorbisFile, &output, numSamples, 0);
|
||||
|
||||
if (samplesRead < 0) { return std::nullopt; }
|
||||
else if (samplesRead == 0) { break; }
|
||||
|
||||
std::copy_n(*output, samplesRead, buffer.begin() + totalSamplesRead);
|
||||
totalSamplesRead += samplesRead;
|
||||
}
|
||||
|
||||
auto result = std::vector<sampleFrame>(totalSamplesRead / numChannels);
|
||||
for (int i = 0; i < result.size(); ++i)
|
||||
{
|
||||
if (numChannels == 1) { result[i] = {buffer[i], buffer[i]}; }
|
||||
else if (numChannels > 1) { result[i] = {buffer[i * numChannels], buffer[i * numChannels + 1]}; }
|
||||
}
|
||||
|
||||
ov_clear(&vorbisFile);
|
||||
return SampleDecoder::Result{std::move(result), static_cast<int>(sampleRate)};
|
||||
}
|
||||
#endif // LMMS_HAVE_OGGVORBIS
|
||||
} // namespace
|
||||
|
||||
auto SampleDecoder::supportedAudioTypes() -> const std::vector<AudioType>&
|
||||
{
|
||||
static const auto s_audioTypes = [] {
|
||||
auto types = std::vector<AudioType>();
|
||||
|
||||
// Add DrumSynth by default since that support comes from us
|
||||
types.push_back(AudioType{"DrumSynth", "ds"});
|
||||
|
||||
auto sfFormatInfo = SF_FORMAT_INFO{};
|
||||
auto simpleTypeCount = 0;
|
||||
sf_command(nullptr, SFC_GET_SIMPLE_FORMAT_COUNT, &simpleTypeCount, sizeof(int));
|
||||
|
||||
// TODO: Ideally, this code should be iterating over the major formats, but some important extensions such as
|
||||
// *.ogg are not included. This is planned for future versions of sndfile.
|
||||
for (int simple = 0; simple < simpleTypeCount; ++simple)
|
||||
{
|
||||
sfFormatInfo.format = simple;
|
||||
sf_command(nullptr, SFC_GET_SIMPLE_FORMAT, &sfFormatInfo, sizeof(sfFormatInfo));
|
||||
|
||||
auto it = std::find_if(types.begin(), types.end(),
|
||||
[&](const AudioType& type) { return sfFormatInfo.extension == type.extension; });
|
||||
if (it != types.end()) { continue; }
|
||||
|
||||
auto name = std::string{sfFormatInfo.extension};
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char ch) { return std::toupper(ch); });
|
||||
|
||||
types.push_back(AudioType{std::move(name), sfFormatInfo.extension});
|
||||
}
|
||||
|
||||
std::sort(types.begin(), types.end(), [&](const AudioType& a, const AudioType& b) { return a.name < b.name; });
|
||||
return types;
|
||||
}();
|
||||
return s_audioTypes;
|
||||
}
|
||||
|
||||
auto SampleDecoder::decode(const QString& audioFile) -> std::optional<Result>
|
||||
{
|
||||
auto result = std::optional<Result>{};
|
||||
for (const auto& decoder : decoders)
|
||||
{
|
||||
result = decoder(audioFile);
|
||||
if (result) { break; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -35,9 +35,9 @@ namespace lmms
|
||||
{
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) :
|
||||
SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioPort) :
|
||||
PlayHandle( Type::SamplePlayHandle ),
|
||||
m_sampleBuffer( sharedObject::ref( sampleBuffer ) ),
|
||||
m_sample(sample),
|
||||
m_doneMayReturnTrue( true ),
|
||||
m_frame( 0 ),
|
||||
m_ownAudioPort( ownAudioPort ),
|
||||
@@ -56,16 +56,15 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) :
|
||||
SamplePlayHandle( new SampleBuffer( sampleFile ) , true)
|
||||
SamplePlayHandle(new Sample(sampleFile), true)
|
||||
{
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) :
|
||||
SamplePlayHandle( clip->sampleBuffer() , false)
|
||||
SamplePlayHandle(&clip->sample(), false)
|
||||
{
|
||||
m_track = clip->getTrack();
|
||||
setAudioPort( ( (SampleTrack *)clip->getTrack() )->audioPort() );
|
||||
@@ -76,10 +75,10 @@ SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) :
|
||||
|
||||
SamplePlayHandle::~SamplePlayHandle()
|
||||
{
|
||||
sharedObject::unref( m_sampleBuffer );
|
||||
if( m_ownAudioPort )
|
||||
{
|
||||
delete audioPort();
|
||||
delete m_sample;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ void SamplePlayHandle::play( sampleFrame * buffer )
|
||||
m_volumeModel->value() / DefaultVolume } };*/
|
||||
// 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))
|
||||
if (!m_sample->play(workingBuffer, &m_state, frames, DefaultBaseFreq))
|
||||
{
|
||||
memset(workingBuffer, 0, frames * sizeof(sampleFrame));
|
||||
}
|
||||
@@ -145,8 +144,8 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const
|
||||
|
||||
f_cnt_t SamplePlayHandle::totalFrames() const
|
||||
{
|
||||
return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) *
|
||||
( Engine::audioEngine()->processingSampleRate() / m_sampleBuffer->sampleRate() );
|
||||
return (m_sample->endFrame() - m_sample->startFrame()) *
|
||||
(static_cast<float>(Engine::audioEngine()->outputSampleRate()) / m_sample->sampleRate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,15 +52,10 @@ SampleRecordHandle::SampleRecordHandle(SampleClip* clip, TimePos startRecordTime
|
||||
|
||||
SampleRecordHandle::~SampleRecordHandle()
|
||||
{
|
||||
if( !m_buffers.empty() )
|
||||
{
|
||||
SampleBuffer* sb;
|
||||
createSampleBuffer( &sb );
|
||||
m_clip->setSampleBuffer( sb );
|
||||
if (!m_buffers.empty()) { m_clip->setSampleBuffer(createSampleBuffer()); }
|
||||
|
||||
m_clip->setStartTimeOffset(m_startRecordTimeOffset);
|
||||
}
|
||||
|
||||
|
||||
while( !m_buffers.empty() )
|
||||
{
|
||||
delete[] m_buffers.front().first;
|
||||
@@ -114,28 +109,22 @@ f_cnt_t SampleRecordHandle::framesRecorded() const
|
||||
|
||||
|
||||
|
||||
void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
|
||||
std::shared_ptr<const SampleBuffer> SampleRecordHandle::createSampleBuffer()
|
||||
{
|
||||
const f_cnt_t frames = framesRecorded();
|
||||
// create buffer to store all recorded buffers in
|
||||
auto data = new sampleFrame[frames];
|
||||
// make sure buffer is cleaned up properly at the end...
|
||||
sampleFrame * data_ptr = data;
|
||||
|
||||
|
||||
assert( data != nullptr );
|
||||
auto bigBuffer = std::vector<sampleFrame>(frames);
|
||||
|
||||
// now copy all buffers into big buffer
|
||||
for( bufferList::const_iterator it = m_buffers.begin(); it != m_buffers.end(); ++it )
|
||||
auto framesCopied = 0;
|
||||
for (const auto& [buf, numFrames] : m_buffers)
|
||||
{
|
||||
memcpy( data_ptr, ( *it ).first, ( *it ).second *
|
||||
sizeof( sampleFrame ) );
|
||||
data_ptr += ( *it ).second;
|
||||
std::copy_n(buf, numFrames, bigBuffer.begin() + framesCopied);
|
||||
framesCopied += numFrames;
|
||||
}
|
||||
|
||||
// create according sample-buffer out of big buffer
|
||||
*sampleBuf = new SampleBuffer( data, frames );
|
||||
( *sampleBuf)->setSampleRate( Engine::audioEngine()->inputSampleRate() );
|
||||
delete[] data;
|
||||
return std::make_shared<const SampleBuffer>(std::move(bigBuffer), Engine::audioEngine()->inputSampleRate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -184,14 +184,9 @@ void Song::setTimeSignature()
|
||||
|
||||
|
||||
|
||||
void Song::savePos()
|
||||
void Song::savePlayStartPosition()
|
||||
{
|
||||
gui::TimeLineWidget* tl = getPlayPos().m_timeLine;
|
||||
|
||||
if( tl != nullptr )
|
||||
{
|
||||
tl->savePos( getPlayPos() );
|
||||
}
|
||||
getTimeline().setPlayStartPosition(getPlayPos());
|
||||
}
|
||||
|
||||
|
||||
@@ -258,16 +253,17 @@ void Song::processNextBuffer()
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto timeline = getPlayPos().m_timeLine;
|
||||
const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled();
|
||||
const auto& timeline = getTimeline();
|
||||
const auto loopEnabled = !m_exporting && timeline.loopEnabled();
|
||||
|
||||
// Ensure playback begins within the loop if it is enabled
|
||||
if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); }
|
||||
if (loopEnabled) { enforceLoop(timeline.loopBegin(), timeline.loopEnd()); }
|
||||
|
||||
// Inform VST plugins if the user moved the play head
|
||||
// Inform VST plugins and sample tracks if the user moved the play head
|
||||
if (getPlayPos().jumped())
|
||||
{
|
||||
m_vstSyncController.setPlaybackJumped(true);
|
||||
emit updateSampleTracks();
|
||||
getPlayPos().setJumped(false);
|
||||
}
|
||||
|
||||
@@ -301,13 +297,13 @@ void Song::processNextBuffer()
|
||||
}
|
||||
|
||||
// Handle loop points, and inform VST plugins of the loop status
|
||||
if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline->loopBegin()))
|
||||
if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline.loopBegin()))
|
||||
{
|
||||
m_vstSyncController.startCycle(
|
||||
timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks());
|
||||
timeline.loopBegin().getTicks(), timeline.loopEnd().getTicks());
|
||||
|
||||
// Loop if necessary, and decrement the remaining loops if we did
|
||||
if (enforceLoop(timeline->loopBegin(), timeline->loopEnd())
|
||||
if (enforceLoop(timeline.loopBegin(), timeline.loopEnd())
|
||||
&& m_loopRenderRemaining > 1)
|
||||
{
|
||||
m_loopRenderRemaining--;
|
||||
@@ -492,7 +488,7 @@ void Song::playSong()
|
||||
|
||||
m_vstSyncController.setPlaybackState( true );
|
||||
|
||||
savePos();
|
||||
savePlayStartPosition();
|
||||
|
||||
emit playbackStateChanged();
|
||||
}
|
||||
@@ -531,7 +527,7 @@ void Song::playPattern()
|
||||
|
||||
m_vstSyncController.setPlaybackState( true );
|
||||
|
||||
savePos();
|
||||
savePlayStartPosition();
|
||||
|
||||
emit playbackStateChanged();
|
||||
}
|
||||
@@ -556,7 +552,7 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop )
|
||||
m_paused = false;
|
||||
}
|
||||
|
||||
savePos();
|
||||
savePlayStartPosition();
|
||||
|
||||
emit playbackStateChanged();
|
||||
}
|
||||
@@ -566,6 +562,8 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop )
|
||||
|
||||
void Song::updateLength()
|
||||
{
|
||||
if (m_loadingProject) { return; }
|
||||
|
||||
m_length = 0;
|
||||
m_tracksMutex.lockForRead();
|
||||
for (auto track : tracks())
|
||||
@@ -644,40 +642,32 @@ void Song::stop()
|
||||
// To avoid race conditions with the processing threads
|
||||
Engine::audioEngine()->requestChangeInModel();
|
||||
|
||||
TimeLineWidget * tl = getPlayPos().m_timeLine;
|
||||
auto& timeline = getTimeline();
|
||||
m_paused = false;
|
||||
m_recording = true;
|
||||
|
||||
if( tl )
|
||||
{
|
||||
switch( tl->behaviourAtStop() )
|
||||
{
|
||||
case TimeLineWidget::BehaviourAtStopState::BackToZero:
|
||||
getPlayPos().setTicks(0);
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
|
||||
break;
|
||||
|
||||
case TimeLineWidget::BehaviourAtStopState::BackToStart:
|
||||
if( tl->savedPos() >= 0 )
|
||||
{
|
||||
getPlayPos().setTicks(tl->savedPos().getTicks());
|
||||
setToTime(tl->savedPos());
|
||||
|
||||
tl->savePos( -1 );
|
||||
}
|
||||
break;
|
||||
|
||||
case TimeLineWidget::BehaviourAtStopState::KeepStopPosition:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getPlayPos().setTicks( 0 );
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
|
||||
}
|
||||
m_playing = false;
|
||||
|
||||
switch (timeline.stopBehaviour())
|
||||
{
|
||||
case Timeline::StopBehaviour::BackToZero:
|
||||
getPlayPos().setTicks(0);
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
|
||||
break;
|
||||
|
||||
case Timeline::StopBehaviour::BackToStart:
|
||||
if (timeline.playStartPosition() >= 0)
|
||||
{
|
||||
getPlayPos().setTicks(timeline.playStartPosition().getTicks());
|
||||
setToTime(timeline.playStartPosition());
|
||||
|
||||
timeline.setPlayStartPosition(-1);
|
||||
}
|
||||
break;
|
||||
|
||||
case Timeline::StopBehaviour::KeepPosition:
|
||||
break;
|
||||
}
|
||||
|
||||
m_elapsedMilliSeconds[static_cast<std::size_t>(PlayMode::None)] = m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)];
|
||||
getPlayPos(PlayMode::None).setTicks(getPlayPos().getTicks());
|
||||
|
||||
@@ -719,37 +709,35 @@ void Song::startExport()
|
||||
m_exporting = true;
|
||||
updateLength();
|
||||
|
||||
const auto& timeline = getTimeline(PlayMode::Song);
|
||||
|
||||
if (m_renderBetweenMarkers)
|
||||
{
|
||||
m_exportSongBegin = m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin();
|
||||
m_exportSongEnd = m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd();
|
||||
m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin();
|
||||
m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd();
|
||||
|
||||
getPlayPos(PlayMode::Song).setTicks( getPlayPos(PlayMode::Song).m_timeLine->loopBegin().getTicks() );
|
||||
getPlayPos(PlayMode::Song).setTicks(timeline.loopBegin().getTicks());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_exportSongEnd = TimePos(m_length, 0);
|
||||
|
||||
// Handle potentially ridiculous loop points gracefully.
|
||||
if (m_loopRenderCount > 1 && getPlayPos(PlayMode::Song).m_timeLine->loopEnd() > m_exportSongEnd)
|
||||
if (m_loopRenderCount > 1 && timeline.loopEnd() > m_exportSongEnd)
|
||||
{
|
||||
m_exportSongEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd();
|
||||
m_exportSongEnd = timeline.loopEnd();
|
||||
}
|
||||
|
||||
if (!m_exportLoop)
|
||||
m_exportSongEnd += TimePos(1,0);
|
||||
|
||||
m_exportSongBegin = TimePos(0,0);
|
||||
// FIXME: remove this check once we load timeline in headless mode
|
||||
if (getPlayPos(PlayMode::Song).m_timeLine)
|
||||
{
|
||||
m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd &&
|
||||
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ?
|
||||
getPlayPos(PlayMode::Song).m_timeLine->loopBegin() : TimePos(0,0);
|
||||
m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd &&
|
||||
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ?
|
||||
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() : TimePos(0,0);
|
||||
}
|
||||
m_exportLoopBegin = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd
|
||||
? timeline.loopBegin()
|
||||
: TimePos{0};
|
||||
m_exportLoopEnd = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd
|
||||
? timeline.loopEnd()
|
||||
: TimePos{0};
|
||||
|
||||
getPlayPos(PlayMode::Song).setTicks( 0 );
|
||||
}
|
||||
@@ -959,13 +947,12 @@ void Song::createNewProject()
|
||||
m_oldFileName = "";
|
||||
setProjectFileName("");
|
||||
|
||||
Track * t;
|
||||
t = Track::create( Track::Type::Instrument, this );
|
||||
dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
|
||||
"tripleoscillator" );
|
||||
t = Track::create(Track::Type::Instrument, Engine::patternStore());
|
||||
dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
|
||||
"kicker" );
|
||||
auto tripleOscTrack = Track::create(Track::Type::Instrument, this);
|
||||
dynamic_cast<InstrumentTrack*>(tripleOscTrack)->loadInstrument("tripleoscillator");
|
||||
|
||||
auto kickerTrack = Track::create(Track::Type::Instrument, Engine::patternStore());
|
||||
dynamic_cast<InstrumentTrack*>(kickerTrack)->loadInstrument("kicker");
|
||||
|
||||
Track::create( Track::Type::Sample, this );
|
||||
Track::create( Track::Type::Pattern, this );
|
||||
Track::create( Track::Type::Automation, this );
|
||||
@@ -978,7 +965,7 @@ void Song::createNewProject()
|
||||
QCoreApplication::instance()->processEvents();
|
||||
|
||||
m_loadingProject = false;
|
||||
|
||||
updateLength();
|
||||
Engine::patternStore()->updateAfterTrackAdd();
|
||||
|
||||
Engine::projectJournal()->setJournalling( true );
|
||||
@@ -1080,11 +1067,7 @@ void Song::loadProject( const QString & fileName )
|
||||
m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" );
|
||||
m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" );
|
||||
|
||||
if( getPlayPos(PlayMode::Song).m_timeLine )
|
||||
{
|
||||
// reset loop-point-state
|
||||
getPlayPos(PlayMode::Song).m_timeLine->toggleLoopPoints( 0 );
|
||||
}
|
||||
getTimeline(PlayMode::Song).setLoopEnabled(false);
|
||||
|
||||
if( !dataFile.content().firstChildElement( "track" ).isNull() )
|
||||
{
|
||||
@@ -1167,9 +1150,9 @@ void Song::loadProject( const QString & fileName )
|
||||
{
|
||||
getGUI()->getProjectNotes()->SerializingObject::restoreState( node.toElement() );
|
||||
}
|
||||
else if( node.nodeName() == getPlayPos(PlayMode::Song).m_timeLine->nodeName() )
|
||||
else if (node.nodeName() == getTimeline(PlayMode::Song).nodeName())
|
||||
{
|
||||
getPlayPos(PlayMode::Song).m_timeLine->restoreState( node.toElement() );
|
||||
getTimeline(PlayMode::Song).restoreState(node.toElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1225,6 +1208,7 @@ void Song::loadProject( const QString & fileName )
|
||||
}
|
||||
|
||||
m_loadingProject = false;
|
||||
updateLength();
|
||||
setModified(false);
|
||||
m_loadOnLaunch = false;
|
||||
}
|
||||
@@ -1253,7 +1237,7 @@ bool Song::saveProjectFile(const QString & filename, bool withResources)
|
||||
getGUI()->pianoRoll()->saveState( dataFile, dataFile.content() );
|
||||
getGUI()->automationEditor()->m_editor->saveState( dataFile, dataFile.content() );
|
||||
getGUI()->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() );
|
||||
getPlayPos(PlayMode::Song).m_timeLine->saveState( dataFile, dataFile.content() );
|
||||
getTimeline(PlayMode::Song).saveState(dataFile, dataFile.content());
|
||||
}
|
||||
|
||||
saveControllerStates( dataFile, dataFile.content() );
|
||||
|
||||
85
src/core/ThreadPool.cpp
Normal file
85
src/core/ThreadPool.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* ThreadPool.cpp
|
||||
*
|
||||
* Copyright (c) 2024 saker
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
#include "ThreadPool.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace lmms {
|
||||
ThreadPool::ThreadPool(size_t numWorkers)
|
||||
{
|
||||
assert(numWorkers > 0);
|
||||
|
||||
m_workers.reserve(numWorkers);
|
||||
for (size_t i = 0; i < numWorkers; ++i)
|
||||
{
|
||||
m_workers.emplace_back([this] { run(); });
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
{
|
||||
const auto lock = std::unique_lock{m_runMutex};
|
||||
m_done = true;
|
||||
}
|
||||
|
||||
m_runCond.notify_all();
|
||||
|
||||
for (auto& worker : m_workers)
|
||||
{
|
||||
if (worker.joinable()) { worker.join(); }
|
||||
}
|
||||
}
|
||||
|
||||
auto ThreadPool::numWorkers() const -> size_t
|
||||
{
|
||||
return m_workers.size();
|
||||
}
|
||||
|
||||
void ThreadPool::run()
|
||||
{
|
||||
while (!m_done)
|
||||
{
|
||||
std::function<void()> task;
|
||||
{
|
||||
auto lock = std::unique_lock{m_runMutex};
|
||||
m_runCond.wait(lock, [this] { return !m_queue.empty() || m_done; });
|
||||
|
||||
if (m_done) { break; }
|
||||
task = m_queue.front();
|
||||
m_queue.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
auto ThreadPool::instance() -> ThreadPool&
|
||||
{
|
||||
static auto s_pool = ThreadPool{s_numWorkers};
|
||||
return s_pool;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "TimePos.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "MeterModel.h"
|
||||
|
||||
namespace lmms
|
||||
@@ -161,11 +162,11 @@ tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const
|
||||
|
||||
f_cnt_t TimePos::frames( const float framesPerTick ) const
|
||||
{
|
||||
if( m_ticks >= 0 )
|
||||
{
|
||||
return static_cast<f_cnt_t>( m_ticks * framesPerTick );
|
||||
}
|
||||
return 0;
|
||||
// Before, step notes used to have negative length. This
|
||||
// assert is a safeguard against negative length being
|
||||
// introduced again (now using Note Types instead #5902)
|
||||
assert(m_ticks >= 0);
|
||||
return static_cast<f_cnt_t>(m_ticks * framesPerTick);
|
||||
}
|
||||
|
||||
double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const
|
||||
@@ -221,4 +222,4 @@ double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute)
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
} // namespace lmms
|
||||
|
||||
83
src/core/Timeline.cpp
Normal file
83
src/core/Timeline.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Timeline.cpp
|
||||
*
|
||||
* Copyright (c) 2023 Dominic Clark
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "Timeline.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
void Timeline::setLoopBegin(TimePos begin)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd});
|
||||
}
|
||||
|
||||
void Timeline::setLoopEnd(TimePos end)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end);
|
||||
}
|
||||
|
||||
void Timeline::setLoopPoints(TimePos begin, TimePos end)
|
||||
{
|
||||
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end);
|
||||
}
|
||||
|
||||
void Timeline::setLoopEnabled(bool enabled)
|
||||
{
|
||||
if (enabled != m_loopEnabled) {
|
||||
m_loopEnabled = enabled;
|
||||
emit loopEnabledChanged(m_loopEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::setStopBehaviour(StopBehaviour behaviour)
|
||||
{
|
||||
if (behaviour != m_stopBehaviour) {
|
||||
m_stopBehaviour = behaviour;
|
||||
emit stopBehaviourChanged(m_stopBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::saveSettings(QDomDocument& doc, QDomElement& element)
|
||||
{
|
||||
element.setAttribute("lp0pos", static_cast<int>(loopBegin()));
|
||||
element.setAttribute("lp1pos", static_cast<int>(loopEnd()));
|
||||
element.setAttribute("lpstate", static_cast<int>(loopEnabled()));
|
||||
element.setAttribute("stopbehaviour", static_cast<int>(stopBehaviour()));
|
||||
}
|
||||
|
||||
void Timeline::loadSettings(const QDomElement& element)
|
||||
{
|
||||
setLoopPoints(
|
||||
static_cast<TimePos>(element.attribute("lp0pos").toInt()),
|
||||
static_cast<TimePos>(element.attribute("lp1pos").toInt())
|
||||
);
|
||||
setLoopEnabled(static_cast<bool>(element.attribute("lpstate").toInt()));
|
||||
setStopBehaviour(static_cast<StopBehaviour>(element.attribute("stopbehaviour", "1").toInt()));
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
@@ -64,10 +64,8 @@ Track::Track( Type type, TrackContainer * tc ) :
|
||||
m_mutedModel( false, this, tr( "Mute" ) ), /*!< For controlling track muting */
|
||||
m_soloModel( false, this, tr( "Solo" ) ), /*!< For controlling track soloing */
|
||||
m_simpleSerializingMode( false ),
|
||||
m_clips(), /*!< The clips (segments) */
|
||||
m_color( 0, 0, 0 ),
|
||||
m_hasColor( false )
|
||||
{
|
||||
m_clips() /*!< The clips (segments) */
|
||||
{
|
||||
m_trackContainer->addTrack( this );
|
||||
m_height = -1;
|
||||
}
|
||||
@@ -209,9 +207,9 @@ void Track::saveSettings( QDomDocument & doc, QDomElement & element )
|
||||
element.setAttribute( "trackheight", m_height );
|
||||
}
|
||||
|
||||
if( m_hasColor )
|
||||
if (m_color.has_value())
|
||||
{
|
||||
element.setAttribute( "color", m_color.name() );
|
||||
element.setAttribute("color", m_color->name());
|
||||
}
|
||||
|
||||
QDomElement tsDe = doc.createElement( nodeName() );
|
||||
@@ -264,14 +262,9 @@ void Track::loadSettings( const QDomElement & element )
|
||||
// Older project files that didn't have this attribute will set the value to false (issue 5562)
|
||||
m_mutedBeforeSolo = QVariant( element.attribute( "mutedBeforeSolo", "0" ) ).toBool();
|
||||
|
||||
if( element.hasAttribute( "color" ) )
|
||||
if (element.hasAttribute("color"))
|
||||
{
|
||||
QColor newColor = QColor(element.attribute("color"));
|
||||
setColor(newColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
resetColor();
|
||||
setColor(QColor{element.attribute("color")});
|
||||
}
|
||||
|
||||
if( m_simpleSerializingMode )
|
||||
@@ -290,10 +283,9 @@ void Track::loadSettings( const QDomElement & element )
|
||||
return;
|
||||
}
|
||||
|
||||
while( !m_clips.empty() )
|
||||
{
|
||||
delete m_clips.front();
|
||||
// m_clips.erase( m_clips.begin() );
|
||||
auto guard = Engine::audioEngine()->requestChangesGuard();
|
||||
deleteClips();
|
||||
}
|
||||
|
||||
QDomNode node = element.firstChild();
|
||||
@@ -634,23 +626,30 @@ void Track::toggleSolo()
|
||||
}
|
||||
}
|
||||
|
||||
void Track::setColor(const QColor& c)
|
||||
void Track::setColor(const std::optional<QColor>& color)
|
||||
{
|
||||
m_hasColor = true;
|
||||
m_color = c;
|
||||
m_color = color;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
void Track::resetColor()
|
||||
{
|
||||
m_hasColor = false;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
|
||||
BoolModel *Track::getMutedModel()
|
||||
{
|
||||
return &m_mutedModel;
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
void Track::setName(const QString& newName)
|
||||
{
|
||||
if (m_name != newName)
|
||||
{
|
||||
m_name = newName;
|
||||
|
||||
if (auto song = Engine::getSong())
|
||||
{
|
||||
song->setModified();
|
||||
}
|
||||
|
||||
emit nameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -155,7 +155,7 @@ void VstSyncController::updateSampleRate()
|
||||
{
|
||||
if (!m_syncData) { return; }
|
||||
|
||||
m_syncData->m_sampleRate = Engine::audioEngine()->processingSampleRate();
|
||||
m_syncData->m_sampleRate = Engine::audioEngine()->outputSampleRate();
|
||||
|
||||
#ifdef VST_SNC_LATENCY
|
||||
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );
|
||||
|
||||
@@ -53,12 +53,7 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
"Could not avoid possible interception by PulseAudio\n" );
|
||||
}
|
||||
|
||||
int err;
|
||||
|
||||
if( ( err = snd_pcm_open( &m_handle,
|
||||
probeDevice().toLatin1().constData(),
|
||||
SND_PCM_STREAM_PLAYBACK,
|
||||
0 ) ) < 0 )
|
||||
if (int err = snd_pcm_open(&m_handle, probeDevice().toLatin1().constData(), SND_PCM_STREAM_PLAYBACK, 0); err < 0)
|
||||
{
|
||||
printf( "Playback open error: %s\n", snd_strerror( err ) );
|
||||
return;
|
||||
@@ -67,14 +62,13 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
snd_pcm_hw_params_malloc( &m_hwParams );
|
||||
snd_pcm_sw_params_malloc( &m_swParams );
|
||||
|
||||
if( ( err = setHWParams( channels(),
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
|
||||
if (int err = setHWParams(channels(), SND_PCM_ACCESS_RW_INTERLEAVED); err < 0)
|
||||
{
|
||||
printf( "Setting of hwparams failed: %s\n",
|
||||
snd_strerror( err ) );
|
||||
return;
|
||||
}
|
||||
if( ( err = setSWParams() ) < 0 )
|
||||
if (int err = setSWParams(); err < 0)
|
||||
{
|
||||
printf( "Setting of swparams failed: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -83,9 +77,8 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
|
||||
// set FD_CLOEXEC flag for all file descriptors so forked processes
|
||||
// do not inherit them
|
||||
struct pollfd * ufds;
|
||||
int count = snd_pcm_poll_descriptors_count( m_handle );
|
||||
ufds = new pollfd[count];
|
||||
auto ufds = new pollfd[count];
|
||||
snd_pcm_poll_descriptors( m_handle, ufds, count );
|
||||
for (int i = 0; i < std::max(3, count); ++i)
|
||||
{
|
||||
@@ -160,7 +153,7 @@ AudioAlsa::DeviceInfoCollection AudioAlsa::getAvailableDevices()
|
||||
{
|
||||
DeviceInfoCollection deviceInfos;
|
||||
|
||||
char **hints;
|
||||
char** hints = nullptr;
|
||||
|
||||
/* Enumerate sound devices */
|
||||
int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
@@ -247,52 +240,6 @@ void AudioAlsa::stopProcessing()
|
||||
stopProcessingThread( this );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioAlsa::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
if( m_handle != nullptr )
|
||||
{
|
||||
snd_pcm_close( m_handle );
|
||||
}
|
||||
|
||||
int err;
|
||||
if( ( err = snd_pcm_open( &m_handle,
|
||||
probeDevice().toLatin1().constData(),
|
||||
SND_PCM_STREAM_PLAYBACK,
|
||||
0 ) ) < 0 )
|
||||
{
|
||||
printf( "Playback open error: %s\n",
|
||||
snd_strerror( err ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if( ( err = setHWParams( channels(),
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
|
||||
{
|
||||
printf( "Setting of hwparams failed: %s\n",
|
||||
snd_strerror( err ) );
|
||||
return;
|
||||
}
|
||||
if( ( err = setSWParams() ) < 0 )
|
||||
{
|
||||
printf( "Setting of swparams failed: %s\n",
|
||||
snd_strerror( err ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioAlsa::run()
|
||||
{
|
||||
auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()];
|
||||
@@ -323,10 +270,7 @@ void AudioAlsa::run()
|
||||
}
|
||||
outbuf_size = frames * channels();
|
||||
|
||||
convertToS16( temp, frames,
|
||||
audioEngine()->masterGain(),
|
||||
outbuf,
|
||||
m_convertEndian );
|
||||
convertToS16(temp, frames, outbuf, m_convertEndian);
|
||||
}
|
||||
int min_len = std::min(len, outbuf_size - outbuf_pos);
|
||||
memcpy( ptr, outbuf + outbuf_pos,
|
||||
@@ -373,10 +317,8 @@ void AudioAlsa::run()
|
||||
|
||||
int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
{
|
||||
int err, dir;
|
||||
|
||||
// choose all parameters
|
||||
if( ( err = snd_pcm_hw_params_any( m_handle, m_hwParams ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_any(m_handle, m_hwParams); err < 0)
|
||||
{
|
||||
printf( "Broken configuration for playback: no configurations "
|
||||
"available: %s\n", snd_strerror( err ) );
|
||||
@@ -384,8 +326,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
}
|
||||
|
||||
// set the interleaved read/write format
|
||||
if( ( err = snd_pcm_hw_params_set_access( m_handle, m_hwParams,
|
||||
_access ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_access(m_handle, m_hwParams, _access); err < 0)
|
||||
{
|
||||
printf( "Access type not available for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -393,11 +334,9 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
}
|
||||
|
||||
// set the sample format
|
||||
if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams,
|
||||
SND_PCM_FORMAT_S16_LE ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_format(m_handle, m_hwParams, SND_PCM_FORMAT_S16_LE); err < 0)
|
||||
{
|
||||
if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams,
|
||||
SND_PCM_FORMAT_S16_BE ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_format(m_handle, m_hwParams, SND_PCM_FORMAT_S16_BE); err < 0)
|
||||
{
|
||||
printf( "Neither little- nor big-endian available for "
|
||||
"playback: %s\n", snd_strerror( err ) );
|
||||
@@ -411,8 +350,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
}
|
||||
|
||||
// set the count of channels
|
||||
if( ( err = snd_pcm_hw_params_set_channels( m_handle, m_hwParams,
|
||||
_channels ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_channels(m_handle, m_hwParams, _channels); err < 0)
|
||||
{
|
||||
printf( "Channel count (%i) not available for playbacks: %s\n"
|
||||
"(Does your soundcard not support surround?)\n",
|
||||
@@ -421,11 +359,9 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
}
|
||||
|
||||
// set the sample rate
|
||||
if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams,
|
||||
sampleRate(), 0 ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_rate(m_handle, m_hwParams, sampleRate(), 0); err < 0)
|
||||
{
|
||||
if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams,
|
||||
audioEngine()->baseSampleRate(), 0 ) ) < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_rate(m_handle, m_hwParams, audioEngine()->baseSampleRate(), 0); err < 0)
|
||||
{
|
||||
printf( "Could not set sample rate: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -435,36 +371,29 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
|
||||
m_periodSize = audioEngine()->framesPerPeriod();
|
||||
m_bufferSize = m_periodSize * 8;
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams,
|
||||
&m_periodSize, &dir );
|
||||
if( err < 0 )
|
||||
int dir;
|
||||
if (int err = snd_pcm_hw_params_set_period_size_near(m_handle, m_hwParams, &m_periodSize, &dir); err < 0)
|
||||
{
|
||||
printf( "Unable to set period size %lu for playback: %s\n",
|
||||
m_periodSize, snd_strerror( err ) );
|
||||
return err;
|
||||
}
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_get_period_size( m_hwParams, &m_periodSize,
|
||||
&dir );
|
||||
if( err < 0 )
|
||||
if (int err = snd_pcm_hw_params_get_period_size(m_hwParams, &m_periodSize, &dir); err < 0)
|
||||
{
|
||||
printf( "Unable to get period size for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
}
|
||||
|
||||
dir = 0;
|
||||
err = snd_pcm_hw_params_set_buffer_size_near( m_handle, m_hwParams,
|
||||
&m_bufferSize );
|
||||
if( err < 0 )
|
||||
if (int err = snd_pcm_hw_params_set_buffer_size_near(m_handle, m_hwParams, &m_bufferSize); err < 0)
|
||||
{
|
||||
printf( "Unable to set buffer size %lu for playback: %s\n",
|
||||
m_bufferSize, snd_strerror( err ) );
|
||||
return ( err );
|
||||
}
|
||||
err = snd_pcm_hw_params_get_buffer_size( m_hwParams, &m_bufferSize );
|
||||
|
||||
if( 2 * m_periodSize > m_bufferSize )
|
||||
if (int err = snd_pcm_hw_params_get_buffer_size(m_hwParams, &m_bufferSize); 2 * m_periodSize > m_bufferSize)
|
||||
{
|
||||
printf( "buffer to small, could not use\n" );
|
||||
return ( err );
|
||||
@@ -472,8 +401,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
|
||||
|
||||
// write the parameters to device
|
||||
err = snd_pcm_hw_params( m_handle, m_hwParams );
|
||||
if( err < 0 )
|
||||
if (int err = snd_pcm_hw_params(m_handle, m_hwParams); err < 0)
|
||||
{
|
||||
printf( "Unable to set hw params for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -488,10 +416,8 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access )
|
||||
|
||||
int AudioAlsa::setSWParams()
|
||||
{
|
||||
int err;
|
||||
|
||||
// get the current swparams
|
||||
if( ( err = snd_pcm_sw_params_current( m_handle, m_swParams ) ) < 0 )
|
||||
if (int err = snd_pcm_sw_params_current(m_handle, m_swParams); err < 0)
|
||||
{
|
||||
printf( "Unable to determine current swparams for playback: %s"
|
||||
"\n", snd_strerror( err ) );
|
||||
@@ -499,8 +425,7 @@ int AudioAlsa::setSWParams()
|
||||
}
|
||||
|
||||
// start the transfer when a period is full
|
||||
if( ( err = snd_pcm_sw_params_set_start_threshold( m_handle,
|
||||
m_swParams, m_periodSize ) ) < 0 )
|
||||
if (int err = snd_pcm_sw_params_set_start_threshold(m_handle, m_swParams, m_periodSize); err < 0)
|
||||
{
|
||||
printf( "Unable to set start threshold mode for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -509,8 +434,7 @@ int AudioAlsa::setSWParams()
|
||||
|
||||
// allow the transfer when at least m_periodSize samples can be
|
||||
// processed
|
||||
if( ( err = snd_pcm_sw_params_set_avail_min( m_handle, m_swParams,
|
||||
m_periodSize ) ) < 0 )
|
||||
if (int err = snd_pcm_sw_params_set_avail_min(m_handle, m_swParams, m_periodSize); err < 0)
|
||||
{
|
||||
printf( "Unable to set avail min for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
@@ -530,7 +454,7 @@ int AudioAlsa::setSWParams()
|
||||
#endif
|
||||
|
||||
// write the parameters to the playback device
|
||||
if( ( err = snd_pcm_sw_params( m_handle, m_swParams ) ) < 0 )
|
||||
if (int err = snd_pcm_sw_params(m_handle, m_swParams); err < 0)
|
||||
{
|
||||
printf( "Unable to set sw params for playback: %s\n",
|
||||
snd_strerror( err ) );
|
||||
|
||||
@@ -34,18 +34,11 @@ namespace lmms
|
||||
|
||||
AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) :
|
||||
m_supportsCapture( false ),
|
||||
m_sampleRate( _audioEngine->processingSampleRate() ),
|
||||
m_sampleRate( _audioEngine->outputSampleRate() ),
|
||||
m_channels( _channels ),
|
||||
m_audioEngine( _audioEngine ),
|
||||
m_buffer( new surroundSampleFrame[audioEngine()->framesPerPeriod()] )
|
||||
{
|
||||
int error;
|
||||
if( ( m_srcState = src_new(
|
||||
audioEngine()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == nullptr )
|
||||
{
|
||||
printf( "Error: src_new() failed in audio_device.cpp!\n" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +46,7 @@ AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine )
|
||||
|
||||
AudioDevice::~AudioDevice()
|
||||
{
|
||||
src_delete( m_srcState );
|
||||
delete[] m_buffer;
|
||||
|
||||
m_devMutex.tryLock();
|
||||
unlock();
|
||||
}
|
||||
@@ -66,49 +57,23 @@ AudioDevice::~AudioDevice()
|
||||
void AudioDevice::processNextBuffer()
|
||||
{
|
||||
const fpp_t frames = getNextBuffer( m_buffer );
|
||||
if( frames )
|
||||
{
|
||||
writeBuffer( m_buffer, frames, audioEngine()->masterGain() );
|
||||
}
|
||||
if (frames) { writeBuffer(m_buffer, frames); }
|
||||
else
|
||||
{
|
||||
m_inProcess = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
|
||||
fpp_t AudioDevice::getNextBuffer(surroundSampleFrame* _ab)
|
||||
{
|
||||
fpp_t frames = audioEngine()->framesPerPeriod();
|
||||
const surroundSampleFrame * b = audioEngine()->nextBuffer();
|
||||
if( !b )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// make sure, no other thread is accessing device
|
||||
lock();
|
||||
const surroundSampleFrame* b = audioEngine()->nextBuffer();
|
||||
if (!b) { return 0; }
|
||||
|
||||
// resample if necessary
|
||||
if( audioEngine()->processingSampleRate() != m_sampleRate )
|
||||
{
|
||||
frames = resample( b, frames, _ab, audioEngine()->processingSampleRate(), m_sampleRate );
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy( _ab, b, frames * sizeof( surroundSampleFrame ) );
|
||||
}
|
||||
|
||||
// release lock
|
||||
unlock();
|
||||
|
||||
if( audioEngine()->hasFifoWriter() )
|
||||
{
|
||||
delete[] b;
|
||||
}
|
||||
memcpy(_ab, b, frames * sizeof(surroundSampleFrame));
|
||||
|
||||
if (audioEngine()->hasFifoWriter()) { delete[] b; }
|
||||
return frames;
|
||||
}
|
||||
|
||||
@@ -144,23 +109,6 @@ void AudioDevice::stopProcessingThread( QThread * thread )
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioDevice::applyQualitySettings()
|
||||
{
|
||||
src_delete( m_srcState );
|
||||
|
||||
int error;
|
||||
if( ( m_srcState = src_new(
|
||||
audioEngine()->currentQualitySettings().libsrcInterpolation(),
|
||||
SURROUND_CHANNELS, &error ) ) == nullptr )
|
||||
{
|
||||
printf( "Error: src_new() failed in audio_device.cpp!\n" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioDevice::registerPort( AudioPort * )
|
||||
{
|
||||
}
|
||||
@@ -179,51 +127,19 @@ void AudioDevice::renamePort( AudioPort * )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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 == nullptr )
|
||||
{
|
||||
return _frames;
|
||||
}
|
||||
m_srcData.input_frames = _frames;
|
||||
m_srcData.output_frames = _frames;
|
||||
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;
|
||||
if( ( error = src_process( m_srcState, &m_srcData ) ) )
|
||||
{
|
||||
printf( "AudioDevice::resample(): error while resampling: %s\n",
|
||||
src_strerror( error ) );
|
||||
}
|
||||
return static_cast<fpp_t>(m_srcData.output_frames_gen);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
|
||||
const fpp_t _frames,
|
||||
const float _master_gain,
|
||||
int_sample_t * _output_buffer,
|
||||
const bool _convert_endian )
|
||||
{
|
||||
if( _convert_endian )
|
||||
{
|
||||
int_sample_t temp;
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
temp = static_cast<int_sample_t>( AudioEngine::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER );
|
||||
|
||||
auto temp = static_cast<int_sample_t>(AudioEngine::clip(_ab[frame][chnl]) * OUTPUT_SAMPLE_MULTIPLIER);
|
||||
|
||||
( _output_buffer + frame * channels() )[chnl] =
|
||||
( temp & 0x00ff ) << 8 |
|
||||
( temp & 0xff00 ) >> 8;
|
||||
@@ -236,11 +152,8 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
( _output_buffer + frame * channels() )[chnl] =
|
||||
static_cast<int_sample_t>(
|
||||
AudioEngine::clip( _ab[frame][chnl] *
|
||||
_master_gain ) *
|
||||
OUTPUT_SAMPLE_MULTIPLIER );
|
||||
(_output_buffer + frame * channels())[chnl]
|
||||
= static_cast<int_sample_t>(AudioEngine::clip(_ab[frame][chnl]) * OUTPUT_SAMPLE_MULTIPLIER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,13 +172,4 @@ void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames )
|
||||
memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool AudioDevice::hqAudio() const
|
||||
{
|
||||
return ConfigManager::inst()->value( "audioengine", "hqaudio" ).toInt();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
@@ -89,7 +89,7 @@ bool AudioFileFlac::startEncoding()
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames, float master_gain)
|
||||
void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames)
|
||||
{
|
||||
OutputSettings::BitDepth depth = getOutputSettings().getBitDepth();
|
||||
float clipvalue = std::nextafterf( -1.0f, 0.0f );
|
||||
@@ -104,7 +104,7 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram
|
||||
// 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] = std::max(clipvalue, _ab[frame][channel] * master_gain);
|
||||
buf[frame*channels() + channel] = std::max(clipvalue, _ab[frame][channel]);
|
||||
}
|
||||
}
|
||||
sf_writef_float(m_sf, static_cast<float*>(buf.data()), frames);
|
||||
@@ -112,7 +112,7 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram
|
||||
else // integer PCM encoding
|
||||
{
|
||||
auto buf = std::vector<int_sample_t>(frames * channels());
|
||||
convertToS16(_ab, frames, master_gain, buf.data(), !isLittleEndian());
|
||||
convertToS16(_ab, frames, buf.data(), !isLittleEndian());
|
||||
sf_writef_short(m_sf, static_cast<short*>(buf.data()), frames);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,21 +53,18 @@ AudioFileMP3::~AudioFileMP3()
|
||||
tearDownEncoder();
|
||||
}
|
||||
|
||||
void AudioFileMP3::writeBuffer( const surroundSampleFrame * _buf,
|
||||
const fpp_t _frames,
|
||||
const float _master_gain )
|
||||
void AudioFileMP3::writeBuffer(const surroundSampleFrame* _buf, const fpp_t _frames)
|
||||
{
|
||||
if (_frames < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Why isn't the gain applied by the driver but inside the device?
|
||||
std::vector<float> interleavedDataBuffer(_frames * 2);
|
||||
for (fpp_t i = 0; i < _frames; ++i)
|
||||
{
|
||||
interleavedDataBuffer[2*i] = _buf[i][0] * _master_gain;
|
||||
interleavedDataBuffer[2*i + 1] = _buf[i][1] * _master_gain;
|
||||
interleavedDataBuffer[2*i] = _buf[i][0];
|
||||
interleavedDataBuffer[2*i + 1] = _buf[i][1];
|
||||
}
|
||||
|
||||
size_t minimumBufferSize = 1.25 * _frames + 7200;
|
||||
|
||||
@@ -156,7 +156,6 @@ bool AudioFileOgg::startEncoding()
|
||||
ogg_packet header_main;
|
||||
ogg_packet header_comments;
|
||||
ogg_packet header_codebooks;
|
||||
int result;
|
||||
|
||||
// Build the packets
|
||||
vorbis_analysis_headerout( &m_vd, m_comments, &header_main,
|
||||
@@ -167,14 +166,9 @@ bool AudioFileOgg::startEncoding()
|
||||
ogg_stream_packetin( &m_os, &header_comments );
|
||||
ogg_stream_packetin( &m_os, &header_codebooks );
|
||||
|
||||
while( ( result = ogg_stream_flush( &m_os, &m_og ) ) )
|
||||
while (ogg_stream_flush(&m_os, &m_og))
|
||||
{
|
||||
if( !result )
|
||||
{
|
||||
break;
|
||||
}
|
||||
int ret = writePage();
|
||||
if( ret != m_og.header_len + m_og.body_len )
|
||||
if (int ret = writePage(); ret != m_og.header_len + m_og.body_len)
|
||||
{
|
||||
// clean up
|
||||
finishEncoding();
|
||||
@@ -185,12 +179,7 @@ bool AudioFileOgg::startEncoding()
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab,
|
||||
const fpp_t _frames,
|
||||
const float _master_gain )
|
||||
void AudioFileOgg::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames)
|
||||
{
|
||||
int eos = 0;
|
||||
|
||||
@@ -201,7 +190,7 @@ void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab,
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
buffer[chnl][frame] = _ab[frame][chnl] * _master_gain;
|
||||
buffer[chnl][frame] = _ab[frame][chnl];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +247,7 @@ void AudioFileOgg::finishEncoding()
|
||||
if( m_ok )
|
||||
{
|
||||
// just for flushing buffers...
|
||||
writeBuffer( nullptr, 0, 0.0f );
|
||||
writeBuffer(nullptr, 0);
|
||||
|
||||
// clean up
|
||||
ogg_stream_clear( &m_os );
|
||||
|
||||
@@ -93,12 +93,7 @@ bool AudioFileWave::startEncoding()
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
|
||||
const fpp_t _frames,
|
||||
const float _master_gain )
|
||||
void AudioFileWave::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames)
|
||||
{
|
||||
OutputSettings::BitDepth bitDepth = getOutputSettings().getBitDepth();
|
||||
|
||||
@@ -109,8 +104,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
buf[frame*channels()+chnl] = _ab[frame][chnl] *
|
||||
_master_gain;
|
||||
buf[frame * channels() + chnl] = _ab[frame][chnl];
|
||||
}
|
||||
}
|
||||
sf_writef_float( m_sf, buf, _frames );
|
||||
@@ -119,8 +113,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab,
|
||||
else
|
||||
{
|
||||
auto buf = new int_sample_t[_frames * channels()];
|
||||
convertToS16( _ab, _frames, _master_gain, buf,
|
||||
!isLittleEndian() );
|
||||
convertToS16(_ab, _frames, buf, !isLittleEndian());
|
||||
|
||||
sf_writef_short( m_sf, buf, _frames );
|
||||
delete[] buf;
|
||||
|
||||
@@ -30,42 +30,42 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "Engine.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "gui_templates.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "LcdSpinBox.h"
|
||||
#include "MainWindow.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "MidiJack.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
AudioDevice(std::clamp<int>(
|
||||
ConfigManager::inst()->value("audiojack", "channels").toInt(),
|
||||
DEFAULT_CHANNELS,
|
||||
SURROUND_CHANNELS), _audioEngine),
|
||||
m_client( nullptr ),
|
||||
m_active( false ),
|
||||
m_midiClient( nullptr ),
|
||||
m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ),
|
||||
m_outBuf( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ),
|
||||
m_framesDoneInCurBuf( 0 ),
|
||||
m_framesToDoInCurBuf( 0 )
|
||||
|
||||
AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam)
|
||||
: AudioDevice(
|
||||
// clang-format off
|
||||
std::clamp<int>(
|
||||
ConfigManager::inst()->value("audiojack", "channels").toInt(),
|
||||
DEFAULT_CHANNELS,
|
||||
SURROUND_CHANNELS
|
||||
),
|
||||
// clang-format on
|
||||
audioEngineParam)
|
||||
, m_client(nullptr)
|
||||
, m_active(false)
|
||||
, m_midiClient(nullptr)
|
||||
, m_tempOutBufs(new jack_default_audio_sample_t*[channels()])
|
||||
, 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()),
|
||||
Qt::QueuedConnection );
|
||||
successful = initJackClient();
|
||||
if (successful) {
|
||||
connect(this, SIGNAL(zombified()), this, SLOT(restartAfterZombified()), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -73,21 +73,18 @@ AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) :
|
||||
|
||||
AudioJack::~AudioJack()
|
||||
{
|
||||
stopProcessing();
|
||||
AudioJack::stopProcessing();
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
while( m_portMap.size() )
|
||||
while (m_portMap.size())
|
||||
{
|
||||
unregisterPort( m_portMap.begin().key() );
|
||||
unregisterPort(m_portMap.begin().key());
|
||||
}
|
||||
#endif
|
||||
|
||||
if( m_client != nullptr )
|
||||
if (m_client != nullptr)
|
||||
{
|
||||
if( m_active )
|
||||
{
|
||||
jack_deactivate( m_client );
|
||||
}
|
||||
jack_client_close( m_client );
|
||||
if (m_active) { jack_deactivate(m_client); }
|
||||
jack_client_close(m_client);
|
||||
}
|
||||
|
||||
delete[] m_tempOutBufs;
|
||||
@@ -100,97 +97,79 @@ AudioJack::~AudioJack()
|
||||
|
||||
void AudioJack::restartAfterZombified()
|
||||
{
|
||||
if( initJackClient() )
|
||||
if (initJackClient())
|
||||
{
|
||||
m_active = false;
|
||||
startProcessing();
|
||||
QMessageBox::information(gui::getGUI()->mainWindow(),
|
||||
tr( "JACK client restarted" ),
|
||||
tr( "LMMS was kicked by JACK for some reason. "
|
||||
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 "
|
||||
"restarted. You will have to make manual "
|
||||
"connections again." ) );
|
||||
"connections again."));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(gui::getGUI()->mainWindow(),
|
||||
tr( "JACK server down" ),
|
||||
tr( "The JACK server seems to have been shutdown "
|
||||
QMessageBox::information(gui::getGUI()->mainWindow(), tr("JACK server down"),
|
||||
tr( "The JACK server seems to have been shutdown "
|
||||
"and starting a new instance failed. "
|
||||
"Therefore LMMS is unable to proceed. "
|
||||
"You should save your project and restart "
|
||||
"JACK and LMMS." ) );
|
||||
"JACK and LMMS."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
AudioJack* AudioJack::addMidiClient(MidiJack *midiClient)
|
||||
|
||||
AudioJack* AudioJack::addMidiClient(MidiJack* midiClient)
|
||||
{
|
||||
if( m_client == nullptr )
|
||||
return nullptr;
|
||||
if (m_client == nullptr) { return nullptr; }
|
||||
|
||||
m_midiClient = midiClient;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool AudioJack::initJackClient()
|
||||
{
|
||||
QString clientName = ConfigManager::inst()->value( "audiojack",
|
||||
"clientname" );
|
||||
if( clientName.isEmpty() )
|
||||
{
|
||||
clientName = "lmms";
|
||||
}
|
||||
QString clientName = ConfigManager::inst()->value("audiojack", "clientname");
|
||||
if (clientName.isEmpty()) { clientName = "lmms"; }
|
||||
|
||||
const char * serverName = nullptr;
|
||||
const char* serverName = nullptr;
|
||||
jack_status_t status;
|
||||
m_client = jack_client_open( clientName.toLatin1().constData(),
|
||||
JackNullOption, &status,
|
||||
serverName );
|
||||
if( m_client == nullptr )
|
||||
m_client = jack_client_open(clientName.toLatin1().constData(), JackNullOption, &status, serverName);
|
||||
if (m_client == nullptr)
|
||||
{
|
||||
printf( "jack_client_open() failed, status 0x%2.0x\n", status );
|
||||
if( status & JackServerFailed )
|
||||
{
|
||||
printf( "Could not connect to JACK server.\n" );
|
||||
}
|
||||
printf("jack_client_open() failed, status 0x%2.0x\n", status);
|
||||
if (status & JackServerFailed) { printf("Could not connect to JACK server.\n"); }
|
||||
return false;
|
||||
}
|
||||
if( status & JackNameNotUnique )
|
||||
if (status & JackNameNotUnique)
|
||||
{
|
||||
printf( "there's already a client with name '%s', so unique "
|
||||
"name '%s' was assigned\n", clientName.
|
||||
toLatin1().constData(),
|
||||
jack_get_client_name( m_client ) );
|
||||
printf( "there's already a client with name '%s', so unique "
|
||||
"name '%s' was assigned\n",
|
||||
clientName.toLatin1().constData(), jack_get_client_name(m_client));
|
||||
}
|
||||
|
||||
// set process-callback
|
||||
jack_set_process_callback( m_client, staticProcessCallback, this );
|
||||
jack_set_process_callback(m_client, staticProcessCallback, this);
|
||||
|
||||
// set shutdown-callback
|
||||
jack_on_shutdown( m_client, shutdownCallback, this );
|
||||
jack_on_shutdown(m_client, shutdownCallback, this);
|
||||
|
||||
if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); }
|
||||
|
||||
|
||||
if( jack_get_sample_rate( m_client ) != sampleRate() )
|
||||
for (ch_cnt_t ch = 0; ch < channels(); ++ch)
|
||||
{
|
||||
setSampleRate( jack_get_sample_rate( m_client ) );
|
||||
}
|
||||
|
||||
for( ch_cnt_t ch = 0; ch < channels(); ++ch )
|
||||
{
|
||||
QString name = QString( "master out " ) +
|
||||
( ( ch % 2 ) ? "R" : "L" ) +
|
||||
QString::number( ch / 2 + 1 );
|
||||
m_outputPorts.push_back( jack_port_register( m_client,
|
||||
name.toLatin1().constData(),
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0 ) );
|
||||
if( m_outputPorts.back() == nullptr )
|
||||
QString name = QString("master out ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1);
|
||||
m_outputPorts.push_back(
|
||||
jack_port_register(m_client, name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0));
|
||||
if (m_outputPorts.back() == nullptr)
|
||||
{
|
||||
printf( "no more JACK-ports available!\n" );
|
||||
printf("no more JACK-ports available!\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -203,51 +182,43 @@ bool AudioJack::initJackClient()
|
||||
|
||||
void AudioJack::startProcessing()
|
||||
{
|
||||
if( m_active || m_client == nullptr )
|
||||
if (m_active || m_client == nullptr)
|
||||
{
|
||||
m_stopped = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if( jack_activate( m_client ) )
|
||||
if (jack_activate(m_client))
|
||||
{
|
||||
printf( "cannot activate client\n" );
|
||||
printf("cannot activate client\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_active = true;
|
||||
|
||||
|
||||
// try to sync JACK's and LMMS's buffer-size
|
||||
// jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() );
|
||||
// jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() );
|
||||
|
||||
|
||||
|
||||
const char * * ports = jack_get_ports( m_client, nullptr, nullptr,
|
||||
JackPortIsPhysical |
|
||||
JackPortIsInput );
|
||||
if( ports == nullptr )
|
||||
const char** ports = jack_get_ports(m_client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
|
||||
if (ports == nullptr)
|
||||
{
|
||||
printf( "no physical playback ports. you'll have to do "
|
||||
"connections at your own!\n" );
|
||||
printf("no physical playback ports. you'll have to do "
|
||||
"connections at your own!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
for( ch_cnt_t ch = 0; ch < channels(); ++ch )
|
||||
for (ch_cnt_t ch = 0; ch < channels(); ++ch)
|
||||
{
|
||||
if( jack_connect( m_client, jack_port_name(
|
||||
m_outputPorts[ch] ),
|
||||
ports[ch] ) )
|
||||
if (jack_connect(m_client, jack_port_name(m_outputPorts[ch]), ports[ch]))
|
||||
{
|
||||
printf( "cannot connect output ports. you'll "
|
||||
"have to do connections at your own!\n"
|
||||
);
|
||||
printf("cannot connect output ports. you'll "
|
||||
"have to do connections at your own!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_stopped = false;
|
||||
free( ports );
|
||||
jack_free(ports);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,127 +229,91 @@ void AudioJack::stopProcessing()
|
||||
m_stopped = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioJack::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
if( jack_get_sample_rate( m_client ) != sampleRate() )
|
||||
{
|
||||
setSampleRate( jack_get_sample_rate( m_client ) );
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioJack::registerPort( AudioPort * _port )
|
||||
void AudioJack::registerPort(AudioPort* port)
|
||||
{
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
// make sure, port is not already registered
|
||||
unregisterPort( _port );
|
||||
const QString name[2] = { _port->name() + " L",
|
||||
_port->name() + " R" } ;
|
||||
unregisterPort(port);
|
||||
const QString name[2] = {port->name() + " L", port->name() + " R"};
|
||||
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
|
||||
{
|
||||
m_portMap[_port].ports[ch] = jack_port_register( m_client,
|
||||
name[ch].toLatin1().constData(),
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0 );
|
||||
m_portMap[port].ports[ch] = jack_port_register(
|
||||
m_client, name[ch].toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||
}
|
||||
#else
|
||||
(void)port;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioJack::unregisterPort( AudioPort * _port )
|
||||
void AudioJack::unregisterPort(AudioPort* port)
|
||||
{
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
if( m_portMap.contains( _port ) )
|
||||
if (m_portMap.contains(port))
|
||||
{
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
|
||||
{
|
||||
if( m_portMap[_port].ports[ch] != nullptr )
|
||||
{
|
||||
jack_port_unregister( m_client,
|
||||
m_portMap[_port].ports[ch] );
|
||||
}
|
||||
if (m_portMap[port].ports[ch] != nullptr) { jack_port_unregister(m_client, m_portMap[port].ports[ch]); }
|
||||
}
|
||||
m_portMap.erase( m_portMap.find( _port ) );
|
||||
m_portMap.erase(m_portMap.find(port));
|
||||
}
|
||||
#else
|
||||
(void)port;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioJack::renamePort( AudioPort * _port )
|
||||
void AudioJack::renamePort(AudioPort* port)
|
||||
{
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
if( m_portMap.contains( _port ) )
|
||||
if (m_portMap.contains(port))
|
||||
{
|
||||
const QString name[2] = { _port->name() + " L",
|
||||
_port->name() + " R" };
|
||||
for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch )
|
||||
const QString name[2] = {port->name() + " L", 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() );
|
||||
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() );
|
||||
jack_port_set_name(m_portMap[port].ports[ch], name[ch].toLatin1().constData());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)port;
|
||||
#endif // AUDIO_PORT_SUPPORT
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
int AudioJack::processCallback(jack_nframes_t nframes)
|
||||
{
|
||||
|
||||
// do midi processing first so that midi input can
|
||||
// add to the following sound processing
|
||||
if( m_midiClient && _nframes > 0 )
|
||||
if (m_midiClient && nframes > 0)
|
||||
{
|
||||
m_midiClient.load()->JackMidiRead(_nframes);
|
||||
m_midiClient.load()->JackMidiWrite(_nframes);
|
||||
m_midiClient.load()->JackMidiRead(nframes);
|
||||
m_midiClient.load()->JackMidiWrite(nframes);
|
||||
}
|
||||
|
||||
for( int c = 0; c < channels(); ++c )
|
||||
for (int c = 0; c < channels(); ++c)
|
||||
{
|
||||
m_tempOutBufs[c] =
|
||||
(jack_default_audio_sample_t *) jack_port_get_buffer(
|
||||
m_outputPorts[c], _nframes );
|
||||
m_tempOutBufs[c] = (jack_default_audio_sample_t*)jack_port_get_buffer(m_outputPorts[c], nframes);
|
||||
}
|
||||
|
||||
#ifdef AUDIO_PORT_SUPPORT
|
||||
const int frames = std::min<int>(_nframes, audioEngine()->framesPerPeriod());
|
||||
for( JackPortMap::iterator it = m_portMap.begin();
|
||||
it != m_portMap.end(); ++it )
|
||||
const int frames = std::min<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 )
|
||||
for (ch_cnt_t ch = 0; ch < channels(); ++ch)
|
||||
{
|
||||
if( it.value().ports[ch] == nullptr )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
jack_default_audio_sample_t * buf =
|
||||
(jack_default_audio_sample_t *) jack_port_get_buffer(
|
||||
it.value().ports[ch],
|
||||
_nframes );
|
||||
for( int frame = 0; frame < frames; ++frame )
|
||||
if (it.value().ports[ch] == nullptr) { continue; }
|
||||
jack_default_audio_sample_t* buf
|
||||
= (jack_default_audio_sample_t*)jack_port_get_buffer(it.value().ports[ch], nframes);
|
||||
for (int frame = 0; frame < frames; ++frame)
|
||||
{
|
||||
buf[frame] = it.key()->buffer()[frame][ch];
|
||||
}
|
||||
@@ -387,28 +322,24 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
#endif
|
||||
|
||||
jack_nframes_t done = 0;
|
||||
while( done < _nframes && m_stopped == false )
|
||||
while (done < nframes && !m_stopped)
|
||||
{
|
||||
jack_nframes_t todo = std::min<jack_nframes_t>(
|
||||
_nframes,
|
||||
m_framesToDoInCurBuf -
|
||||
m_framesDoneInCurBuf);
|
||||
const float gain = audioEngine()->masterGain();
|
||||
for( int c = 0; c < channels(); ++c )
|
||||
jack_nframes_t todo = std::min<jack_nframes_t>(nframes - done, m_framesToDoInCurBuf - m_framesDoneInCurBuf);
|
||||
for (int c = 0; c < channels(); ++c)
|
||||
{
|
||||
jack_default_audio_sample_t * o = m_tempOutBufs[c];
|
||||
for( jack_nframes_t frame = 0; frame < todo; ++frame )
|
||||
jack_default_audio_sample_t* o = m_tempOutBufs[c];
|
||||
for (jack_nframes_t frame = 0; frame < todo; ++frame)
|
||||
{
|
||||
o[done+frame] = m_outBuf[m_framesDoneInCurBuf+frame][c] * gain;
|
||||
o[done + frame] = m_outBuf[m_framesDoneInCurBuf + frame][c];
|
||||
}
|
||||
}
|
||||
done += todo;
|
||||
m_framesDoneInCurBuf += todo;
|
||||
if( m_framesDoneInCurBuf == m_framesToDoInCurBuf )
|
||||
if (m_framesDoneInCurBuf == m_framesToDoInCurBuf)
|
||||
{
|
||||
m_framesToDoInCurBuf = getNextBuffer( m_outBuf );
|
||||
m_framesToDoInCurBuf = getNextBuffer(m_outBuf);
|
||||
m_framesDoneInCurBuf = 0;
|
||||
if( !m_framesToDoInCurBuf )
|
||||
if (!m_framesToDoInCurBuf)
|
||||
{
|
||||
m_stopped = true;
|
||||
break;
|
||||
@@ -416,12 +347,12 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
}
|
||||
}
|
||||
|
||||
if( _nframes != done )
|
||||
if (nframes != done)
|
||||
{
|
||||
for( int c = 0; c < channels(); ++c )
|
||||
for (int c = 0; c < channels(); ++c)
|
||||
{
|
||||
jack_default_audio_sample_t * b = m_tempOutBufs[c] + done;
|
||||
memset( b, 0, sizeof( *b ) * ( _nframes - done ) );
|
||||
jack_default_audio_sample_t* b = m_tempOutBufs[c] + done;
|
||||
memset(b, 0, sizeof(*b) * (nframes - done));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,51 +362,44 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata )
|
||||
|
||||
|
||||
|
||||
int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata )
|
||||
int AudioJack::staticProcessCallback(jack_nframes_t nframes, void* udata)
|
||||
{
|
||||
return static_cast<AudioJack *>( _udata )->
|
||||
processCallback( _nframes, _udata );
|
||||
return static_cast<AudioJack*>(udata)->processCallback(nframes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioJack::shutdownCallback( void * _udata )
|
||||
void AudioJack::shutdownCallback(void* udata)
|
||||
{
|
||||
auto _this = static_cast<AudioJack*>(_udata);
|
||||
_this->m_client = nullptr;
|
||||
_this->zombified();
|
||||
auto thisClass = static_cast<AudioJack*>(udata);
|
||||
thisClass->m_client = nullptr;
|
||||
emit thisClass->zombified();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AudioJack::setupWidget::setupWidget( QWidget * _parent ) :
|
||||
AudioDeviceSetupWidget( AudioJack::name(), _parent )
|
||||
AudioJack::setupWidget::setupWidget(QWidget* parent)
|
||||
: AudioDeviceSetupWidget(AudioJack::name(), parent)
|
||||
{
|
||||
QFormLayout * form = new QFormLayout(this);
|
||||
|
||||
QString cn = ConfigManager::inst()->value( "audiojack", "clientname" );
|
||||
if( cn.isEmpty() )
|
||||
{
|
||||
cn = "lmms";
|
||||
}
|
||||
m_clientName = new QLineEdit( cn, this );
|
||||
QString cn = ConfigManager::inst()->value("audiojack", "clientname");
|
||||
if (cn.isEmpty()) { cn = "lmms"; }
|
||||
m_clientName = new QLineEdit(cn, this);
|
||||
|
||||
form->addRow(tr("Client name"), m_clientName);
|
||||
|
||||
auto m = new gui::LcdSpinBoxModel(/* this */);
|
||||
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
|
||||
m->setStep( 2 );
|
||||
m->setValue( ConfigManager::inst()->value( "audiojack",
|
||||
"channels" ).toInt() );
|
||||
m->setRange(DEFAULT_CHANNELS, SURROUND_CHANNELS);
|
||||
m->setStep(2);
|
||||
m->setValue(ConfigManager::inst()->value("audiojack", "channels").toInt());
|
||||
|
||||
m_channels = new gui::LcdSpinBox( 1, this );
|
||||
m_channels->setModel( m );
|
||||
m_channels = new gui::LcdSpinBox(1, this);
|
||||
m_channels->setModel(m);
|
||||
|
||||
form->addRow(tr("Channels"), m_channels);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -491,14 +415,11 @@ AudioJack::setupWidget::~setupWidget()
|
||||
|
||||
void AudioJack::setupWidget::saveSettings()
|
||||
{
|
||||
ConfigManager::inst()->setValue( "audiojack", "clientname",
|
||||
m_clientName->text() );
|
||||
ConfigManager::inst()->setValue( "audiojack", "channels",
|
||||
QString::number( m_channels->value<int>() ) );
|
||||
ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text());
|
||||
ConfigManager::inst()->setValue("audiojack", "channels", QString::number(m_channels->value<int>()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_JACK
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include "LcdSpinBox.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
@@ -255,41 +254,6 @@ void AudioOss::stopProcessing()
|
||||
stopProcessingThread( this );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioOss::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
unsigned int value = sampleRate();
|
||||
if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 )
|
||||
{
|
||||
perror( "SNDCTL_DSP_SPEED" );
|
||||
printf( "Couldn't set audio frequency\n" );
|
||||
return;
|
||||
}
|
||||
if( value != sampleRate() )
|
||||
{
|
||||
value = audioEngine()->baseSampleRate();
|
||||
if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 )
|
||||
{
|
||||
perror( "SNDCTL_DSP_SPEED" );
|
||||
printf( "Couldn't set audio frequency\n" );
|
||||
return;
|
||||
}
|
||||
setSampleRate( value );
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioOss::run()
|
||||
{
|
||||
auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()];
|
||||
@@ -303,7 +267,7 @@ void AudioOss::run()
|
||||
break;
|
||||
}
|
||||
|
||||
int bytes = convertToS16( temp, frames, audioEngine()->masterGain(), outbuf, m_convertEndian );
|
||||
int bytes = convertToS16(temp, frames, outbuf, m_convertEndian);
|
||||
if( write( m_audioFD, outbuf, bytes ) != bytes )
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -53,7 +53,6 @@ void AudioPortAudioSetupUtil::updateChannels()
|
||||
|
||||
#include "Engine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "gui_templates.h"
|
||||
#include "ComboBox.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
@@ -93,10 +92,9 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, AudioEngine * _audioEngine
|
||||
|
||||
PaDeviceIndex inDevIdx = -1;
|
||||
PaDeviceIndex outDevIdx = -1;
|
||||
const PaDeviceInfo * di;
|
||||
for( int i = 0; i < Pa_GetDeviceCount(); ++i )
|
||||
{
|
||||
di = Pa_GetDeviceInfo( i );
|
||||
const auto di = Pa_GetDeviceInfo(i);
|
||||
if( di->name == device &&
|
||||
Pa_GetHostApiInfo( di->hostApi )->name == backend )
|
||||
{
|
||||
@@ -231,38 +229,6 @@ void AudioPortAudio::stopProcessing()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioPortAudio::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
int samples = audioEngine()->framesPerPeriod();
|
||||
|
||||
PaError err = Pa_OpenStream(
|
||||
&m_paStream,
|
||||
supportsCapture() ? &m_inputParameters : nullptr, // The input parameter
|
||||
&m_outputParameters, // The outputparameter
|
||||
sampleRate(),
|
||||
samples,
|
||||
paNoFlag, // Don't use any flags
|
||||
_process_callback, // our callback function
|
||||
this );
|
||||
|
||||
if( err != paNoError )
|
||||
{
|
||||
printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AudioPortAudio::process_callback(
|
||||
const float *_inputBuffer,
|
||||
float * _outputBuffer,
|
||||
@@ -298,15 +264,11 @@ int AudioPortAudio::process_callback(
|
||||
const int min_len = std::min(static_cast<int>(_framesPerBuffer),
|
||||
m_outBufSize - m_outBufPos);
|
||||
|
||||
float master_gain = audioEngine()->masterGain();
|
||||
|
||||
for( fpp_t frame = 0; frame < min_len; ++frame )
|
||||
{
|
||||
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
|
||||
{
|
||||
( _outputBuffer + frame * channels() )[chnl] =
|
||||
AudioEngine::clip( m_outBuf[frame][chnl] *
|
||||
master_gain );
|
||||
(_outputBuffer + frame * channels())[chnl] = AudioEngine::clip(m_outBuf[frame][chnl]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,10 +310,9 @@ void AudioPortAudioSetupUtil::updateBackends()
|
||||
return;
|
||||
}
|
||||
|
||||
const PaHostApiInfo * hi;
|
||||
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
|
||||
{
|
||||
hi = Pa_GetHostApiInfo( i );
|
||||
const auto hi = Pa_GetHostApiInfo(i);
|
||||
m_backendModel.addItem( hi->name );
|
||||
}
|
||||
|
||||
@@ -372,10 +333,9 @@ void AudioPortAudioSetupUtil::updateDevices()
|
||||
// get active backend
|
||||
const QString& backend = m_backendModel.currentText();
|
||||
int hostApi = 0;
|
||||
const PaHostApiInfo * hi;
|
||||
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
|
||||
{
|
||||
hi = Pa_GetHostApiInfo( i );
|
||||
const auto hi = Pa_GetHostApiInfo(i);
|
||||
if( backend == hi->name )
|
||||
{
|
||||
hostApi = i;
|
||||
@@ -385,10 +345,9 @@ void AudioPortAudioSetupUtil::updateDevices()
|
||||
|
||||
// get devices for selected backend
|
||||
m_deviceModel.clear();
|
||||
const PaDeviceInfo * di;
|
||||
for( int i = 0; i < Pa_GetDeviceCount(); ++i )
|
||||
{
|
||||
di = Pa_GetDeviceInfo( i );
|
||||
const auto di = Pa_GetDeviceInfo(i);
|
||||
if( di->hostApi == hostApi )
|
||||
{
|
||||
m_deviceModel.addItem( di->name );
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "ConfigManager.h"
|
||||
#include "LcdSpinBox.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "gui_templates.h"
|
||||
#include "Engine.h"
|
||||
|
||||
namespace lmms
|
||||
@@ -110,22 +109,6 @@ void AudioPulseAudio::stopProcessing()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioPulseAudio::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
// setSampleRate( engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* This routine is called whenever the stream state changes */
|
||||
static void stream_state_callback( pa_stream *s, void * userdata )
|
||||
{
|
||||
@@ -278,10 +261,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length )
|
||||
m_quit = true;
|
||||
break;
|
||||
}
|
||||
int bytes = convertToS16( temp, frames,
|
||||
audioEngine()->masterGain(),
|
||||
pcmbuf,
|
||||
m_convertEndian );
|
||||
int bytes = convertToS16(temp, frames, pcmbuf, m_convertEndian);
|
||||
if( bytes > 0 )
|
||||
{
|
||||
pa_stream_write( m_s, pcmbuf, bytes, nullptr, 0,
|
||||
|
||||
@@ -67,39 +67,25 @@ f_cnt_t AudioSampleRecorder::framesRecorded() const
|
||||
return frames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf )
|
||||
std::shared_ptr<const SampleBuffer> AudioSampleRecorder::createSampleBuffer()
|
||||
{
|
||||
const f_cnt_t frames = framesRecorded();
|
||||
// create buffer to store all recorded buffers in
|
||||
auto data = new sampleFrame[frames];
|
||||
// make sure buffer is cleaned up properly at the end...
|
||||
sampleFrame * data_ptr = data;
|
||||
|
||||
|
||||
assert( data != nullptr );
|
||||
auto bigBuffer = std::vector<sampleFrame>(frames);
|
||||
|
||||
// now copy all buffers into big buffer
|
||||
for( BufferList::ConstIterator it = m_buffers.begin();
|
||||
it != m_buffers.end(); ++it )
|
||||
auto framesCopied = 0;
|
||||
for (const auto& [buf, numFrames] : m_buffers)
|
||||
{
|
||||
memcpy( data_ptr, ( *it ).first, ( *it ).second *
|
||||
sizeof( sampleFrame ) );
|
||||
data_ptr += ( *it ).second;
|
||||
std::copy_n(buf, numFrames, bigBuffer.begin() + framesCopied);
|
||||
framesCopied += numFrames;
|
||||
}
|
||||
|
||||
// create according sample-buffer out of big buffer
|
||||
*sampleBuf = new SampleBuffer( data, frames );
|
||||
( *sampleBuf )->setSampleRate( sampleRate() );
|
||||
delete[] data;
|
||||
return std::make_shared<const SampleBuffer>(std::move(bigBuffer), sampleRate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab,
|
||||
const fpp_t _frames, const float )
|
||||
void AudioSampleRecorder::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames)
|
||||
{
|
||||
auto buf = new sampleFrame[_frames];
|
||||
for( fpp_t frame = 0; frame < _frames; ++frame )
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -192,37 +191,6 @@ void AudioSdl::stopProcessing()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioSdl::applyQualitySettings()
|
||||
{
|
||||
// Better than if (0)
|
||||
#if 0
|
||||
if( 0 )//hqAudio() )
|
||||
{
|
||||
SDL_CloseAudio();
|
||||
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
m_audioHandle.freq = sampleRate();
|
||||
|
||||
SDL_AudioSpec actual;
|
||||
|
||||
// open the audio device, forcing the desired format
|
||||
if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 )
|
||||
{
|
||||
qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioSdl::sdlAudioCallback( void * _udata, Uint8 * _buf, int _len )
|
||||
{
|
||||
auto _this = static_cast<AudioSdl*>(_udata);
|
||||
@@ -261,13 +229,6 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len )
|
||||
m_currentBufferFramesCount
|
||||
- m_currentBufferFramePos);
|
||||
|
||||
const float gain = audioEngine()->masterGain();
|
||||
for (uint f = 0; f < min_frames_count; f++)
|
||||
{
|
||||
(m_outBuf + m_currentBufferFramePos)[f][0] *= gain;
|
||||
(m_outBuf + m_currentBufferFramePos)[f][1] *= gain;
|
||||
}
|
||||
|
||||
memcpy( _buf, m_outBuf + m_currentBufferFramePos, min_frames_count*sizeof(sampleFrame) );
|
||||
_buf += min_frames_count*sizeof(sampleFrame);
|
||||
_len -= min_frames_count*sizeof(sampleFrame);
|
||||
@@ -291,10 +252,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len )
|
||||
m_convertedBufSize = frames * channels()
|
||||
* sizeof( int_sample_t );
|
||||
|
||||
convertToS16( m_outBuf, frames,
|
||||
audioEngine()->masterGain(),
|
||||
(int_sample_t *)m_convertedBuf,
|
||||
m_outConvertEndian );
|
||||
convertToS16(m_outBuf, frames, reinterpret_cast<int_sample_t*>(m_convertedBuf), m_outConvertEndian);
|
||||
}
|
||||
const int min_len = std::min(_len, m_convertedBufSize
|
||||
- m_convertedBufPos);
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include "LcdSpinBox.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "Engine.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
#include "ConfigManager.h"
|
||||
|
||||
@@ -140,20 +139,6 @@ void AudioSndio::stopProcessing()
|
||||
stopProcessingThread( this );
|
||||
}
|
||||
|
||||
|
||||
void AudioSndio::applyQualitySettings()
|
||||
{
|
||||
if( hqAudio() )
|
||||
{
|
||||
setSampleRate( Engine::audioEngine()->processingSampleRate() );
|
||||
|
||||
/* change sample rate to sampleRate() */
|
||||
}
|
||||
|
||||
AudioDevice::applyQualitySettings();
|
||||
}
|
||||
|
||||
|
||||
void AudioSndio::run()
|
||||
{
|
||||
surroundSampleFrame * temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()];
|
||||
@@ -167,8 +152,7 @@ void AudioSndio::run()
|
||||
break;
|
||||
}
|
||||
|
||||
uint bytes = convertToS16( temp, frames,
|
||||
audioEngine()->masterGain(), outbuf, m_convertEndian );
|
||||
uint bytes = convertToS16(temp, frames, outbuf, m_convertEndian);
|
||||
if( sio_write( m_hdl, outbuf, bytes ) != bytes )
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "Engine.h"
|
||||
#include "debug.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "gui_templates.h"
|
||||
#include "ComboBox.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
@@ -70,7 +69,6 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) :
|
||||
const QString& configDeviceId = ConfigManager::inst()->value( "audiosoundio", "out_device_id" );
|
||||
const QString& configDeviceRaw = ConfigManager::inst()->value( "audiosoundio", "out_device_raw" );
|
||||
|
||||
int err;
|
||||
int outDeviceCount = 0;
|
||||
int backendCount = soundio_backend_count(m_soundio);
|
||||
for (int i = 0; i < backendCount; i += 1)
|
||||
@@ -78,11 +76,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) :
|
||||
SoundIoBackend backend = soundio_get_backend(m_soundio, i);
|
||||
if (configBackend == soundio_backend_name(backend))
|
||||
{
|
||||
if ((err = soundio_connect_backend(m_soundio, backend)))
|
||||
{
|
||||
// error occurred, leave outDeviceCount 0
|
||||
}
|
||||
else
|
||||
if (!soundio_connect_backend(m_soundio, backend))
|
||||
{
|
||||
soundio_flush_events(m_soundio);
|
||||
if (m_disconnectErr)
|
||||
@@ -99,7 +93,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) :
|
||||
if (outDeviceCount <= 0)
|
||||
{
|
||||
// try connecting to the default backend
|
||||
if ((err = soundio_connect(m_soundio)))
|
||||
if (int err = soundio_connect(m_soundio))
|
||||
{
|
||||
fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err));
|
||||
return;
|
||||
@@ -180,7 +174,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) :
|
||||
m_outstream->layout = *soundio_channel_layout_get_default(channels());
|
||||
m_outstream->format = SoundIoFormatFloat32NE;
|
||||
|
||||
if ((err = soundio_outstream_open(m_outstream)))
|
||||
if (int err = soundio_outstream_open(m_outstream))
|
||||
{
|
||||
fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err));
|
||||
return;
|
||||
@@ -215,8 +209,6 @@ AudioSoundIo::~AudioSoundIo()
|
||||
|
||||
void AudioSoundIo::startProcessing()
|
||||
{
|
||||
int err;
|
||||
|
||||
m_outBufFrameIndex = 0;
|
||||
m_outBufFramesTotal = 0;
|
||||
m_outBufSize = audioEngine()->framesPerPeriod();
|
||||
@@ -225,7 +217,7 @@ void AudioSoundIo::startProcessing()
|
||||
|
||||
if (! m_outstreamStarted)
|
||||
{
|
||||
if ((err = soundio_outstream_start(m_outstream)))
|
||||
if (int err = soundio_outstream_start(m_outstream))
|
||||
{
|
||||
fprintf(stderr,
|
||||
"AudioSoundIo::startProcessing() :: soundio unable to start stream: %s\n",
|
||||
@@ -237,7 +229,7 @@ void AudioSoundIo::startProcessing()
|
||||
|
||||
m_stopped = false;
|
||||
|
||||
if ((err = soundio_outstream_pause(m_outstream, false)))
|
||||
if (int err = soundio_outstream_pause(m_outstream, false))
|
||||
{
|
||||
m_stopped = true;
|
||||
fprintf(stderr,
|
||||
@@ -248,12 +240,10 @@ void AudioSoundIo::startProcessing()
|
||||
|
||||
void AudioSoundIo::stopProcessing()
|
||||
{
|
||||
int err;
|
||||
|
||||
m_stopped = true;
|
||||
if (m_outstream)
|
||||
{
|
||||
if ((err = soundio_outstream_pause(m_outstream, true)))
|
||||
if (int err = soundio_outstream_pause(m_outstream, true))
|
||||
{
|
||||
fprintf(stderr,
|
||||
"AudioSoundIo::stopProcessing() :: pausing result error: %s\n",
|
||||
@@ -282,18 +272,14 @@ void AudioSoundIo::writeCallback(int frameCountMin, int frameCountMax)
|
||||
{
|
||||
if (m_stopped) {return;}
|
||||
const struct SoundIoChannelLayout *layout = &m_outstream->layout;
|
||||
SoundIoChannelArea *areas;
|
||||
SoundIoChannelArea* areas;
|
||||
int bytesPerSample = m_outstream->bytes_per_sample;
|
||||
int err;
|
||||
|
||||
const float gain = audioEngine()->masterGain();
|
||||
|
||||
int framesLeft = frameCountMax;
|
||||
|
||||
while (framesLeft > 0)
|
||||
{
|
||||
int frameCount = framesLeft;
|
||||
if ((err = soundio_outstream_begin_write(m_outstream, &areas, &frameCount)))
|
||||
if (int err = soundio_outstream_begin_write(m_outstream, &areas, &frameCount))
|
||||
{
|
||||
errorCallback(err);
|
||||
return;
|
||||
@@ -328,14 +314,14 @@ void AudioSoundIo::writeCallback(int frameCountMin, int frameCountMax)
|
||||
|
||||
for (int channel = 0; channel < layout->channel_count; channel += 1)
|
||||
{
|
||||
float sample = gain * m_outBuf[m_outBufFrameIndex][channel];
|
||||
float sample = m_outBuf[m_outBufFrameIndex][channel];
|
||||
memcpy(areas[channel].ptr, &sample, bytesPerSample);
|
||||
areas[channel].ptr += areas[channel].step;
|
||||
}
|
||||
m_outBufFrameIndex += 1;
|
||||
}
|
||||
|
||||
if ((err = soundio_outstream_end_write(m_outstream)))
|
||||
if (int err = soundio_outstream_end_write(m_outstream))
|
||||
{
|
||||
errorCallback(err);
|
||||
return;
|
||||
@@ -375,11 +361,10 @@ void AudioSoundIo::setupWidget::reconnectSoundIo()
|
||||
|
||||
soundio_disconnect(m_soundio);
|
||||
|
||||
int err;
|
||||
int backend_index = m_backendModel.findText(configBackend);
|
||||
if (backend_index < 0)
|
||||
{
|
||||
if ((err = soundio_connect(m_soundio)))
|
||||
if (int err = soundio_connect(m_soundio))
|
||||
{
|
||||
fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err));
|
||||
return;
|
||||
@@ -390,11 +375,11 @@ void AudioSoundIo::setupWidget::reconnectSoundIo()
|
||||
else
|
||||
{
|
||||
SoundIoBackend backend = soundio_get_backend(m_soundio, backend_index);
|
||||
if ((err = soundio_connect_backend(m_soundio, backend)))
|
||||
if (int err = soundio_connect_backend(m_soundio, backend))
|
||||
{
|
||||
fprintf(stderr, "soundio: unable to connect %s backend: %s\n",
|
||||
soundio_backend_name(backend), soundio_strerror(err));
|
||||
if ((err = soundio_connect(m_soundio)))
|
||||
if (int err = soundio_connect(m_soundio))
|
||||
{
|
||||
fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err));
|
||||
return;
|
||||
|
||||
@@ -59,7 +59,7 @@ Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) :
|
||||
else
|
||||
{
|
||||
qCritical() << "No Lv2 plugin found for URI" << uri;
|
||||
m_valid = false;
|
||||
throw std::runtime_error("No Lv2 plugin found for given URI");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,26 +77,14 @@ void Lv2ControlBase::init(Model* meAsModel)
|
||||
while (channelsLeft > 0)
|
||||
{
|
||||
std::unique_ptr<Lv2Proc> newOne = std::make_unique<Lv2Proc>(m_plugin, meAsModel);
|
||||
if (newOne->isValid())
|
||||
{
|
||||
channelsLeft -= std::max(
|
||||
1 + static_cast<bool>(newOne->inPorts().m_right),
|
||||
1 + static_cast<bool>(newOne->outPorts().m_right));
|
||||
Q_ASSERT(channelsLeft >= 0);
|
||||
m_procs.push_back(std::move(newOne));
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Failed instantiating LV2 processor";
|
||||
m_valid = false;
|
||||
channelsLeft = 0;
|
||||
}
|
||||
}
|
||||
if (m_valid)
|
||||
{
|
||||
m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size();
|
||||
linkAllModels();
|
||||
channelsLeft -= std::max(
|
||||
1 + static_cast<bool>(newOne->inPorts().m_right),
|
||||
1 + static_cast<bool>(newOne->outPorts().m_right));
|
||||
Q_ASSERT(channelsLeft >= 0);
|
||||
m_procs.push_back(std::move(newOne));
|
||||
}
|
||||
m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size();
|
||||
linkAllModels();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/atom/atom.h>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -129,12 +129,11 @@ lv2_evbuf_next(LV2_Evbuf_Iterator iter)
|
||||
|
||||
LV2_Evbuf* evbuf = iter.evbuf;
|
||||
uint32_t offset = iter.offset;
|
||||
uint32_t size;
|
||||
size = ((LV2_Atom_Event*)
|
||||
((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom)
|
||||
+ offset))->body.size;
|
||||
offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);
|
||||
|
||||
const auto contents = static_cast<char*>(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom)) + offset;
|
||||
const uint32_t size = reinterpret_cast<LV2_Atom_Event*>(contents)->body.size;
|
||||
|
||||
offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);
|
||||
LV2_Evbuf_Iterator next = { evbuf, offset };
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,12 @@ void *&Lv2Features::operator[](const char *featName)
|
||||
|
||||
void Lv2Features::clear()
|
||||
{
|
||||
m_featureByUri.clear();
|
||||
m_features.clear();
|
||||
for (auto& [uri, feature] : m_featureByUri)
|
||||
{
|
||||
(void) uri;
|
||||
feature = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <lilv/lilv.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/options/options.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
|
||||
#include <lv2/buf-size/buf-size.h>
|
||||
#include <lv2/options/options.h>
|
||||
#include <lv2/worker/worker.h>
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
|
||||
@@ -94,6 +94,16 @@ void Lv2Options::initOption(LV2_URID key, uint32_t size, LV2_URID type,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Lv2Options::clear()
|
||||
{
|
||||
m_options.clear();
|
||||
m_optionValues.clear();
|
||||
m_optionByUrid.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_LV2
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/port-props/port-props.h>
|
||||
#include <lv2/atom/atom.h>
|
||||
#include <lv2/port-props/port-props.h>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "Lv2Basics.h"
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
|
||||
#include <cmath>
|
||||
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/resize-port/resize-port.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
|
||||
#include <lv2/midi/midi.h>
|
||||
#include <lv2/atom/atom.h>
|
||||
#include <lv2/resize-port/resize-port.h>
|
||||
#include <lv2/worker/worker.h>
|
||||
#include <QDebug>
|
||||
#include <QDomDocument>
|
||||
#include <QtGlobal>
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "Lv2Evbuf.h"
|
||||
#include "MidiEvent.h"
|
||||
#include "MidiEventToByteSeq.h"
|
||||
#include "NoCopyNoMove.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
@@ -168,6 +169,27 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin,
|
||||
|
||||
|
||||
|
||||
class Lv2ProcSuspender : NoCopyNoMove
|
||||
{
|
||||
public:
|
||||
Lv2ProcSuspender(Lv2Proc* proc)
|
||||
: m_proc(proc)
|
||||
, m_wasActive(proc->m_instance)
|
||||
{
|
||||
if (m_wasActive) { m_proc->shutdownPlugin(); }
|
||||
}
|
||||
~Lv2ProcSuspender()
|
||||
{
|
||||
if (m_wasActive) { m_proc->initPlugin(); }
|
||||
}
|
||||
private:
|
||||
Lv2Proc* const m_proc;
|
||||
const bool m_wasActive;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
|
||||
LinkedModelGroup(parent),
|
||||
m_plugin(plugin),
|
||||
@@ -175,6 +197,7 @@ Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
|
||||
m_midiInputBuf(m_maxMidiInputEvents),
|
||||
m_midiInputReader(m_midiInputBuf)
|
||||
{
|
||||
createPorts();
|
||||
initPlugin();
|
||||
}
|
||||
|
||||
@@ -186,22 +209,7 @@ Lv2Proc::~Lv2Proc() { shutdownPlugin(); }
|
||||
|
||||
|
||||
|
||||
void Lv2Proc::reload()
|
||||
{
|
||||
// save controls, which we want to keep
|
||||
QDomDocument doc;
|
||||
QDomElement controls = doc.createElement("controls");
|
||||
saveValues(doc, controls);
|
||||
// backup construction variables
|
||||
const LilvPlugin* plugin = m_plugin;
|
||||
Model* parent = Model::parentModel();
|
||||
// destroy everything using RAII ...
|
||||
this->~Lv2Proc();
|
||||
// ... and reuse it ("placement new")
|
||||
new (this) Lv2Proc(plugin, parent);
|
||||
// reload the controls
|
||||
loadValues(controls);
|
||||
}
|
||||
void Lv2Proc::reload() { Lv2ProcSuspender(this); }
|
||||
|
||||
|
||||
|
||||
@@ -417,12 +425,8 @@ void Lv2Proc::handleMidiInputEvent(const MidiEvent &event, const TimePos &time,
|
||||
|
||||
AutomatableModel *Lv2Proc::modelAtPort(const QString &uri)
|
||||
{
|
||||
// unused currently
|
||||
AutomatableModel *mod;
|
||||
auto itr = m_connectedModels.find(uri.toUtf8().data());
|
||||
if (itr != m_connectedModels.end()) { mod = itr->second; }
|
||||
else { mod = nullptr; }
|
||||
return mod;
|
||||
const auto itr = m_connectedModels.find(uri.toUtf8().data());
|
||||
return itr != m_connectedModels.end() ? itr->second : nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -434,19 +438,23 @@ void Lv2Proc::initPlugin()
|
||||
initPluginSpecificFeatures();
|
||||
m_features.createFeatureVectors();
|
||||
|
||||
createPorts();
|
||||
|
||||
m_instance = lilv_plugin_instantiate(m_plugin,
|
||||
Engine::audioEngine()->processingSampleRate(),
|
||||
Engine::audioEngine()->outputSampleRate(),
|
||||
m_features.featurePointers());
|
||||
|
||||
if (m_instance)
|
||||
{
|
||||
if(m_worker) {
|
||||
const auto iface = static_cast<const LV2_Worker_Interface*>(
|
||||
lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface));
|
||||
if (iface)
|
||||
{
|
||||
m_worker->setHandle(lilv_instance_get_handle(m_instance));
|
||||
m_worker->setInterface(iface);
|
||||
}
|
||||
for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum)
|
||||
{
|
||||
connectPort(portNum);
|
||||
}
|
||||
lilv_instance_activate(m_instance);
|
||||
}
|
||||
else
|
||||
@@ -456,7 +464,7 @@ void Lv2Proc::initPlugin()
|
||||
<< "(URI:"
|
||||
<< lilv_node_as_uri(lilv_plugin_get_uri(m_plugin))
|
||||
<< ")";
|
||||
m_valid = false;
|
||||
throw std::runtime_error("Failed to create Lv2 processor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,15 +473,12 @@ void Lv2Proc::initPlugin()
|
||||
|
||||
void Lv2Proc::shutdownPlugin()
|
||||
{
|
||||
if (m_valid)
|
||||
{
|
||||
lilv_instance_deactivate(m_instance);
|
||||
lilv_instance_free(m_instance);
|
||||
m_instance = nullptr;
|
||||
lilv_instance_deactivate(m_instance);
|
||||
lilv_instance_free(m_instance);
|
||||
m_instance = nullptr;
|
||||
|
||||
m_features.clear();
|
||||
}
|
||||
m_valid = true;
|
||||
m_features.clear();
|
||||
m_options.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -503,7 +508,7 @@ void Lv2Proc::initMOptions()
|
||||
re-initialize, and this code section will be
|
||||
executed again, creating a new option vector.
|
||||
*/
|
||||
float sampleRate = Engine::audioEngine()->processingSampleRate();
|
||||
float sampleRate = Engine::audioEngine()->outputSampleRate();
|
||||
int32_t blockLength = Engine::audioEngine()->framesPerPeriod();
|
||||
int32_t sequenceSize = defaultEvbufSize();
|
||||
|
||||
@@ -527,13 +532,12 @@ void Lv2Proc::initPluginSpecificFeatures()
|
||||
|
||||
// worker (if plugin has worker extension)
|
||||
Lv2Manager* mgr = Engine::getLv2Manager();
|
||||
if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get())) {
|
||||
const auto iface = static_cast<const LV2_Worker_Interface*>(
|
||||
lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface));
|
||||
if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get()))
|
||||
{
|
||||
bool threaded = !Engine::audioEngine()->renderOnly();
|
||||
m_worker.emplace(iface, &m_workLock, threaded);
|
||||
m_worker.emplace(&m_workLock, threaded);
|
||||
m_features[LV2_WORKER__schedule] = m_worker->feature();
|
||||
// Note: m_worker::setHandle will still need to be called later
|
||||
// note: the worker interface can not be instantiated yet - it requires m_instance. see initPlugin()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +570,7 @@ void Lv2Proc::createPort(std::size_t portNum)
|
||||
{
|
||||
AutoLilvNode node(lilv_port_get_name(m_plugin, lilvPort));
|
||||
QString dispName = lilv_node_as_string(node.get());
|
||||
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
|
||||
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
|
||||
if(meta.def() < meta.min(sr) || meta.def() > meta.max(sr))
|
||||
{
|
||||
qWarning() << "Warning: Plugin"
|
||||
@@ -683,7 +687,8 @@ void Lv2Proc::createPort(std::size_t portNum)
|
||||
AutoLilvNode rszMinimumSize = mgr->uri(LV2_RESIZE_PORT__minimumSize);
|
||||
AutoLilvNodes minSizeV(lilv_port_get_value(m_plugin, lilvPort, rszMinimumSize.get()));
|
||||
LilvNode* minSize = minSizeV ? lilv_nodes_get_first(minSizeV.get()) : nullptr;
|
||||
if (minSize && lilv_node_is_int(minSize)) {
|
||||
if (minSize && lilv_node_is_int(minSize))
|
||||
{
|
||||
minimumSize = std::max(minimumSize, lilv_node_as_int(minSize));
|
||||
}
|
||||
}
|
||||
@@ -845,7 +850,8 @@ void Lv2Proc::dumpPort(std::size_t num)
|
||||
{
|
||||
struct DumpPortDetail : public Lv2Ports::ConstVisitor
|
||||
{
|
||||
void visit(const Lv2Ports::Control& ctrl) override {
|
||||
void visit(const Lv2Ports::Control& ctrl) override
|
||||
{
|
||||
qDebug() << " control port";
|
||||
// output ports may be uninitialized yet, only print inputs
|
||||
if (ctrl.m_flow == Lv2Ports::Flow::Input)
|
||||
@@ -853,7 +859,8 @@ void Lv2Proc::dumpPort(std::size_t num)
|
||||
qDebug() << " value:" << ctrl.m_val;
|
||||
}
|
||||
}
|
||||
void visit(const Lv2Ports::Audio& audio) override {
|
||||
void visit(const Lv2Ports::Audio& audio) override
|
||||
{
|
||||
qDebug() << (audio.isSideChain() ? " audio port (sidechain)"
|
||||
: " audio port");
|
||||
qDebug() << " buffer size:" << audio.bufferSize();
|
||||
@@ -869,7 +876,7 @@ void Lv2Proc::dumpPort(std::size_t num)
|
||||
qDebug() << " visualization: " << Lv2Ports::toStr(port.m_vis);
|
||||
if (port.m_type == Lv2Ports::Type::Control || port.m_type == Lv2Ports::Type::Cv)
|
||||
{
|
||||
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
|
||||
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
|
||||
qDebug() << " default:" << port.def();
|
||||
qDebug() << " min:" << port.min(sr);
|
||||
qDebug() << " max:" << port.max(sr);
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
|
||||
#include <lv2/atom/atom.h>
|
||||
#include <lv2/buf-size/buf-size.h>
|
||||
#include <lv2/midi/midi.h>
|
||||
#include <lv2/parameters/parameters.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "Lv2UridMap.h"
|
||||
|
||||
@@ -60,10 +60,7 @@ std::size_t Lv2Worker::bufferSize() const
|
||||
|
||||
|
||||
|
||||
Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface,
|
||||
Semaphore* common_work_lock,
|
||||
bool threaded) :
|
||||
m_iface(iface),
|
||||
Lv2Worker::Lv2Worker(Semaphore* commonWorkLock, bool threaded) :
|
||||
m_threaded(threaded),
|
||||
m_response(bufferSize()),
|
||||
m_requests(bufferSize()),
|
||||
@@ -71,9 +68,8 @@ Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface,
|
||||
m_requestsReader(m_requests),
|
||||
m_responsesReader(m_responses),
|
||||
m_sem(0),
|
||||
m_workLock(common_work_lock)
|
||||
m_workLock(commonWorkLock)
|
||||
{
|
||||
assert(iface);
|
||||
m_scheduleFeature.handle = static_cast<LV2_Worker_Schedule_Handle>(this);
|
||||
m_scheduleFeature.schedule_work = [](LV2_Worker_Schedule_Handle handle,
|
||||
uint32_t size, const void* data) -> LV2_Worker_Status
|
||||
@@ -91,6 +87,24 @@ Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface,
|
||||
|
||||
|
||||
|
||||
void Lv2Worker::setHandle(LV2_Handle handle)
|
||||
{
|
||||
assert(handle);
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Lv2Worker::setInterface(const LV2_Worker_Interface* newInterface)
|
||||
{
|
||||
assert(newInterface);
|
||||
m_interface = newInterface;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Lv2Worker::~Lv2Worker()
|
||||
{
|
||||
m_exit = true;
|
||||
@@ -120,7 +134,9 @@ LV2_Worker_Status Lv2Worker::respond(uint32_t size, const void* data)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iface->work_response(m_handle, size, data);
|
||||
assert(m_handle);
|
||||
assert(m_interface);
|
||||
m_interface->work_response(m_handle, size, data);
|
||||
}
|
||||
return LV2_WORKER_SUCCESS;
|
||||
}
|
||||
@@ -136,6 +152,7 @@ void Lv2Worker::workerFunc()
|
||||
while (true) {
|
||||
m_sem.wait();
|
||||
if (m_exit) { break; }
|
||||
|
||||
const std::size_t readSpace = m_requestsReader.read_space();
|
||||
if (readSpace <= sizeof(size)) { continue; } // (should not happen)
|
||||
|
||||
@@ -144,8 +161,10 @@ void Lv2Worker::workerFunc()
|
||||
if(size > buf.size()) { buf.resize(size); }
|
||||
if(size) { m_requestsReader.read(size).copy(buf.data(), size); }
|
||||
|
||||
assert(m_handle);
|
||||
assert(m_interface);
|
||||
m_workLock->wait();
|
||||
m_iface->work(m_handle, staticWorkerRespond, this, size, buf.data());
|
||||
m_interface->work(m_handle, staticWorkerRespond, this, size, buf.data());
|
||||
m_workLock->post();
|
||||
}
|
||||
}
|
||||
@@ -172,9 +191,11 @@ LV2_Worker_Status Lv2Worker::scheduleWork(uint32_t size, const void *data)
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(m_handle);
|
||||
assert(m_interface);
|
||||
// Execute work immediately in this thread
|
||||
m_workLock->wait();
|
||||
m_iface->work(m_handle, staticWorkerRespond, this, size, data);
|
||||
m_interface->work(m_handle, staticWorkerRespond, this, size, data);
|
||||
m_workLock->post();
|
||||
}
|
||||
|
||||
@@ -189,10 +210,13 @@ void Lv2Worker::emitResponses()
|
||||
{
|
||||
std::size_t read_space = m_responsesReader.read_space();
|
||||
uint32_t size;
|
||||
while (read_space > sizeof(size)) {
|
||||
while (read_space > sizeof(size))
|
||||
{
|
||||
assert(m_handle);
|
||||
assert(m_interface);
|
||||
m_responsesReader.read(sizeof(size)).copy((char*)&size, sizeof(size));
|
||||
if(size) { m_responsesReader.read(size).copy(m_response.data(), size); }
|
||||
m_iface->work_response(m_handle, size, m_response.data());
|
||||
m_interface->work_response(m_handle, size, m_response.data());
|
||||
read_space -= sizeof(size) + size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,6 @@ void printHelp()
|
||||
" -p, --profile <out> Dump profiling information to file <out>\n"
|
||||
" -s, --samplerate <samplerate> Specify output samplerate in Hz\n"
|
||||
" Range: 44100 (default) to 192000\n"
|
||||
" -x, --oversampling <value> Specify oversampling\n"
|
||||
" Possible values: 1, 2, 4, 8\n"
|
||||
" Default: 2\n\n",
|
||||
LMMS_VERSION, LMMS_PROJECT_COPYRIGHT );
|
||||
@@ -356,14 +355,12 @@ int main( int argc, char * * argv )
|
||||
// don't let OS steal the menu bar. FIXME: only effective on Qt4
|
||||
QCoreApplication::setAttribute( Qt::AA_DontUseNativeMenuBar );
|
||||
#endif
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
#endif
|
||||
QCoreApplication * app = coreOnly ?
|
||||
new QCoreApplication( argc, argv ) :
|
||||
new gui::MainApplication(argc, argv);
|
||||
|
||||
AudioEngine::qualitySettings qs( AudioEngine::qualitySettings::Mode::HighQuality );
|
||||
AudioEngine::qualitySettings qs(AudioEngine::qualitySettings::Interpolation::Linear);
|
||||
OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo );
|
||||
ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave;
|
||||
|
||||
@@ -648,36 +645,6 @@ int main( int argc, char * * argv )
|
||||
return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) );
|
||||
}
|
||||
}
|
||||
else if( arg == "--oversampling" || arg == "-x" )
|
||||
{
|
||||
++i;
|
||||
|
||||
if( i == argc )
|
||||
{
|
||||
return usageError( "No oversampling specified" );
|
||||
}
|
||||
|
||||
|
||||
int o = QString( argv[i] ).toUInt();
|
||||
|
||||
switch( o )
|
||||
{
|
||||
case 1:
|
||||
qs.oversampling = AudioEngine::qualitySettings::Oversampling::None;
|
||||
break;
|
||||
case 2:
|
||||
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X2;
|
||||
break;
|
||||
case 4:
|
||||
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X4;
|
||||
break;
|
||||
case 8:
|
||||
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X8;
|
||||
break;
|
||||
default:
|
||||
return usageError( QString( "Invalid oversampling %1" ).arg( argv[i] ) );
|
||||
}
|
||||
}
|
||||
else if( arg == "--import" )
|
||||
{
|
||||
++i;
|
||||
@@ -906,19 +873,13 @@ int main( int argc, char * * argv )
|
||||
mb.setWindowIcon( embed::getIconPixmap( "icon_small" ) );
|
||||
mb.setWindowFlags( Qt::WindowCloseButtonHint );
|
||||
|
||||
QPushButton * recover;
|
||||
QPushButton * discard;
|
||||
QPushButton * exit;
|
||||
|
||||
// setting all buttons to the same roles allows us
|
||||
// to have a custom layout
|
||||
discard = mb.addButton( MainWindow::tr( "Discard" ),
|
||||
QMessageBox::AcceptRole );
|
||||
recover = mb.addButton( MainWindow::tr( "Recover" ),
|
||||
QMessageBox::AcceptRole );
|
||||
auto discard = mb.addButton(MainWindow::tr("Discard"), QMessageBox::AcceptRole);
|
||||
auto recover = mb.addButton(MainWindow::tr("Recover"), QMessageBox::AcceptRole);
|
||||
|
||||
// have a hidden exit button
|
||||
exit = mb.addButton( "", QMessageBox::RejectRole);
|
||||
auto exit = mb.addButton("", QMessageBox::RejectRole);
|
||||
exit->setVisible(false);
|
||||
|
||||
// set icons
|
||||
|
||||
@@ -39,10 +39,7 @@ MidiAlsaRaw::MidiAlsaRaw() :
|
||||
m_outputp( &m_output ),
|
||||
m_quit( false )
|
||||
{
|
||||
int err;
|
||||
if( ( err = snd_rawmidi_open( m_inputp, m_outputp,
|
||||
probeDevice().toLatin1().constData(),
|
||||
0 ) ) < 0 )
|
||||
if (int err = snd_rawmidi_open(m_inputp, m_outputp, probeDevice().toLatin1().constData(), 0); err < 0)
|
||||
{
|
||||
printf( "cannot open MIDI-device: %s\n", snd_strerror( err ) );
|
||||
return;
|
||||
@@ -111,29 +108,27 @@ void MidiAlsaRaw::run()
|
||||
{
|
||||
msleep( 5 ); // must do that, otherwise this thread takes
|
||||
// too much CPU-time, even with LowPriority...
|
||||
int err = poll( m_pfds, m_npfds, 10000 );
|
||||
if( err < 0 && errno == EINTR )
|
||||
if (int err = poll(m_pfds, m_npfds, 10000); err < 0 && errno == EINTR)
|
||||
{
|
||||
printf( "MidiAlsaRaw::run(): Got EINTR while "
|
||||
"polling. Will stop polling MIDI-events from "
|
||||
"MIDI-port.\n" );
|
||||
break;
|
||||
}
|
||||
if( err < 0 )
|
||||
else if (err < 0)
|
||||
{
|
||||
printf( "poll failed: %s\nWill stop polling "
|
||||
"MIDI-events from MIDI-port.\n",
|
||||
strerror( errno ) );
|
||||
break;
|
||||
}
|
||||
if( err == 0 )
|
||||
else if (err == 0)
|
||||
{
|
||||
//printf( "there seems to be no active MIDI-device %d\n", ++cnt );
|
||||
continue;
|
||||
}
|
||||
unsigned short revents;
|
||||
if( ( err = snd_rawmidi_poll_descriptors_revents(
|
||||
m_input, m_pfds, m_npfds, &revents ) ) < 0 )
|
||||
unsigned short revents = 0;
|
||||
if (int err = snd_rawmidi_poll_descriptors_revents(m_input, m_pfds, m_npfds, &revents); err < 0)
|
||||
{
|
||||
printf( "cannot get poll events: %s\nWill stop polling "
|
||||
"MIDI-events from MIDI-port.\n",
|
||||
@@ -149,25 +144,19 @@ void MidiAlsaRaw::run()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
err = snd_rawmidi_read(m_input, buf.data(), buf.size());
|
||||
if( err == -EAGAIN )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if( err < 0 )
|
||||
|
||||
if (int err = snd_rawmidi_read(m_input, buf.data(), buf.size()); err == -EAGAIN) { continue; }
|
||||
else if (err < 0)
|
||||
{
|
||||
printf( "cannot read from port \"%s\": %s\nWill stop "
|
||||
"polling MIDI-events from MIDI-port.\n",
|
||||
/*port_name*/"default", snd_strerror( err ) );
|
||||
break;
|
||||
}
|
||||
if( err == 0 )
|
||||
else if (err == 0) { continue; }
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for( int i = 0; i < err; ++i )
|
||||
{
|
||||
parseData( buf[i] );
|
||||
for (int i = 0; i < err; ++i) { parseData(buf[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,7 @@ MidiAlsaSeq::MidiAlsaSeq() :
|
||||
m_quit( false ),
|
||||
m_portListUpdateTimer( this )
|
||||
{
|
||||
int err;
|
||||
if( ( err = snd_seq_open( &m_seqHandle,
|
||||
probeDevice().toLatin1().constData(),
|
||||
SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 )
|
||||
if (int err = snd_seq_open(&m_seqHandle, probeDevice().toLatin1().constData(), SND_SEQ_OPEN_DUPLEX, 0); err < 0)
|
||||
{
|
||||
fprintf( stderr, "cannot open sequencer: %s\n",
|
||||
snd_strerror( err ) );
|
||||
|
||||
@@ -74,11 +74,11 @@ void MidiController::updateName()
|
||||
|
||||
void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset)
|
||||
{
|
||||
unsigned char controllerNum;
|
||||
switch(event.type())
|
||||
{
|
||||
case MidiControlChange:
|
||||
controllerNum = event.controllerNumber();
|
||||
{
|
||||
unsigned char controllerNum = event.controllerNumber();
|
||||
|
||||
if (m_midiPort.inputController() == controllerNum &&
|
||||
(m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0))
|
||||
@@ -89,7 +89,7 @@ void MidiController::processInEvent(const MidiEvent& event, const TimePos& time,
|
||||
emit valueChanged();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
// Don't care - maybe add special cases for pitch and mod later
|
||||
break;
|
||||
|
||||
@@ -179,7 +179,6 @@ QString MidiJack::probeDevice()
|
||||
// we read data from jack
|
||||
void MidiJack::JackMidiRead(jack_nframes_t nframes)
|
||||
{
|
||||
unsigned int i,b;
|
||||
void* port_buf = jack_port_get_buffer(m_input_port, nframes);
|
||||
jack_midi_event_t in_event;
|
||||
jack_nframes_t event_index = 0;
|
||||
@@ -188,13 +187,13 @@ void MidiJack::JackMidiRead(jack_nframes_t nframes)
|
||||
int rval = jack_midi_event_get(&in_event, port_buf, 0);
|
||||
if (rval == 0 /* 0 = success */)
|
||||
{
|
||||
for(i=0; i<nframes; i++)
|
||||
for (unsigned int i = 0; i < nframes; i++)
|
||||
{
|
||||
while((in_event.time == i) && (event_index < event_count))
|
||||
{
|
||||
// lmms is setup to parse bytes coming from a device
|
||||
// parse it byte by byte as it expects
|
||||
for(b=0;b<in_event.size;b++)
|
||||
for (unsigned int b = 0; b < in_event.size; b++)
|
||||
parseData( *(in_event.buffer + b) );
|
||||
|
||||
event_index++;
|
||||
|
||||
@@ -91,24 +91,21 @@ void MidiSndio::sendByte( const unsigned char c )
|
||||
void MidiSndio::run()
|
||||
{
|
||||
struct pollfd pfd;
|
||||
nfds_t nfds;
|
||||
char buf[0x100], *p;
|
||||
size_t n;
|
||||
int ret;
|
||||
char buf[0x100];
|
||||
while( m_quit == false && m_hdl )
|
||||
{
|
||||
nfds = mio_pollfd( m_hdl, &pfd, POLLIN );
|
||||
ret = poll( &pfd, nfds, 100 );
|
||||
nfds_t nfds = mio_pollfd(m_hdl, &pfd, POLLIN);
|
||||
int ret = poll(&pfd, nfds, 100);
|
||||
if ( ret < 0 )
|
||||
break;
|
||||
if ( !ret || !( mio_revents( m_hdl, &pfd ) & POLLIN ) )
|
||||
continue;
|
||||
n = mio_read( m_hdl, buf, sizeof(buf) );
|
||||
size_t n = mio_read(m_hdl, buf, sizeof(buf));
|
||||
if ( !n )
|
||||
{
|
||||
break;
|
||||
}
|
||||
for (p = buf; n > 0; n--, p++)
|
||||
for (char* p = buf; n > 0; n--, p++)
|
||||
{
|
||||
parseData( *p );
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "LcdSpinBox.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
|
||||
@@ -26,14 +26,16 @@ SET(LMMS_SRCS
|
||||
gui/MicrotunerConfig.cpp
|
||||
gui/MidiCCRackView.cpp
|
||||
gui/MidiSetupWidget.cpp
|
||||
gui/MixerLine.cpp
|
||||
gui/MixerChannelView.cpp
|
||||
gui/MixerView.cpp
|
||||
gui/ModelView.cpp
|
||||
gui/PeakControllerDialog.cpp
|
||||
gui/PluginBrowser.cpp
|
||||
gui/ProjectNotes.cpp
|
||||
gui/RowTableView.cpp
|
||||
gui/SampleLoader.cpp
|
||||
gui/SampleTrackWindow.cpp
|
||||
gui/SampleWaveform.cpp
|
||||
gui/SendButtonIndicator.cpp
|
||||
gui/SideBar.cpp
|
||||
gui/SideBarWidget.cpp
|
||||
@@ -59,12 +61,14 @@ SET(LMMS_SRCS
|
||||
gui/editors/TrackContainerView.cpp
|
||||
|
||||
gui/instrument/EnvelopeAndLfoView.cpp
|
||||
gui/instrument/EnvelopeGraph.cpp
|
||||
gui/instrument/InstrumentFunctionViews.cpp
|
||||
gui/instrument/InstrumentMidiIOView.cpp
|
||||
gui/instrument/InstrumentTuningView.cpp
|
||||
gui/instrument/InstrumentSoundShapingView.cpp
|
||||
gui/instrument/InstrumentTrackWindow.cpp
|
||||
gui/instrument/InstrumentView.cpp
|
||||
gui/instrument/LfoGraph.cpp
|
||||
gui/instrument/PianoView.cpp
|
||||
|
||||
gui/menus/MidiPortMenu.cpp
|
||||
@@ -94,11 +98,13 @@ SET(LMMS_SRCS
|
||||
|
||||
gui/widgets/AutomatableButton.cpp
|
||||
gui/widgets/AutomatableSlider.cpp
|
||||
gui/widgets/BarModelEditor.cpp
|
||||
gui/widgets/CPULoadWidget.cpp
|
||||
gui/widgets/CaptionMenu.cpp
|
||||
gui/widgets/ComboBox.cpp
|
||||
gui/widgets/CustomTextKnob.cpp
|
||||
gui/widgets/Fader.cpp
|
||||
gui/widgets/FloatModelEditorBase.cpp
|
||||
gui/widgets/Graph.cpp
|
||||
gui/widgets/GroupBox.cpp
|
||||
gui/widgets/Knob.cpp
|
||||
@@ -108,13 +114,14 @@ SET(LMMS_SRCS
|
||||
gui/widgets/LedCheckBox.cpp
|
||||
gui/widgets/LeftRightNav.cpp
|
||||
gui/widgets/MeterDialog.cpp
|
||||
gui/widgets/MixerLineLcdSpinBox.cpp
|
||||
gui/widgets/MixerChannelLcdSpinBox.cpp
|
||||
gui/widgets/NStateButton.cpp
|
||||
gui/widgets/Oscilloscope.cpp
|
||||
gui/widgets/PixmapButton.cpp
|
||||
gui/widgets/SimpleTextFloat.cpp
|
||||
gui/widgets/TabBar.cpp
|
||||
gui/widgets/TabWidget.cpp
|
||||
gui/widgets/TempoSyncBarModelEditor.cpp
|
||||
gui/widgets/TempoSyncKnob.cpp
|
||||
gui/widgets/TextFloat.cpp
|
||||
gui/widgets/TimeDisplayWidget.cpp
|
||||
@@ -123,11 +130,3 @@ SET(LMMS_SRCS
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(LMMS_UIS
|
||||
${LMMS_UIS}
|
||||
gui/modals/about_dialog.ui
|
||||
gui/modals/EffectSelectDialog.ui
|
||||
gui/modals/export_project.ui
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -101,8 +101,7 @@ ControlLayout::ControlLayout(QWidget *parent, int margin, int hSpacing, int vSpa
|
||||
|
||||
ControlLayout::~ControlLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = takeAt(0))) { delete item; }
|
||||
while (auto item = takeAt(0)) { delete item; }
|
||||
}
|
||||
|
||||
void ControlLayout::onTextChanged(const QString&)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QAction>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QMessageBox>
|
||||
@@ -68,8 +69,8 @@ ControllerRackView::ControllerRackView() :
|
||||
this, SLOT(addController()));
|
||||
|
||||
Song * song = Engine::getSong();
|
||||
connect( song, SIGNAL(controllerAdded(lmms::Controller*)), SLOT(onControllerAdded(lmms::Controller*)));
|
||||
connect( song, SIGNAL(controllerRemoved(lmms::Controller*)), SLOT(onControllerRemoved(lmms::Controller*)));
|
||||
connect(song, &Song::controllerAdded, this, qOverload<Controller*>(&ControllerRackView::addController));
|
||||
connect(song, &Song::controllerRemoved, this, &ControllerRackView::removeController);
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->addWidget( m_scrollArea );
|
||||
@@ -132,17 +133,51 @@ void ControllerRackView::deleteController( ControllerView * _view )
|
||||
song->removeController( c );
|
||||
}
|
||||
|
||||
void ControllerRackView::moveUp(ControllerView* view)
|
||||
{
|
||||
if (view == m_controllerViews.first()) { return; }
|
||||
|
||||
const auto storedView = std::find(m_controllerViews.begin(), m_controllerViews.end(), view);
|
||||
assert(storedView != m_controllerViews.end());
|
||||
|
||||
const auto index = std::distance(m_controllerViews.begin(), storedView);
|
||||
|
||||
void ControllerRackView::onControllerAdded( Controller * controller )
|
||||
std::swap(m_controllerViews[index - 1], m_controllerViews[index]);
|
||||
m_scrollAreaLayout->removeWidget(view);
|
||||
m_scrollAreaLayout->insertWidget(index - 1, view);
|
||||
}
|
||||
|
||||
void ControllerRackView::moveDown(ControllerView* view)
|
||||
{
|
||||
if (view == m_controllerViews.last()) { return; }
|
||||
|
||||
const auto storedView = std::find(m_controllerViews.begin(), m_controllerViews.end(), view);
|
||||
assert(storedView != m_controllerViews.end());
|
||||
moveUp(*std::next(storedView));
|
||||
}
|
||||
|
||||
void ControllerRackView::addController(Controller* controller)
|
||||
{
|
||||
QWidget * scrollAreaWidget = m_scrollArea->widget();
|
||||
|
||||
auto controllerView = new ControllerView(controller, scrollAreaWidget);
|
||||
|
||||
connect( controllerView, SIGNAL(deleteController(lmms::gui::ControllerView*)),
|
||||
this, SLOT(deleteController(lmms::gui::ControllerView*)), Qt::QueuedConnection );
|
||||
connect(controllerView, &ControllerView::movedUp, this, &ControllerRackView::moveUp);
|
||||
connect(controllerView, &ControllerView::movedDown, this, &ControllerRackView::moveDown);
|
||||
connect(controllerView, &ControllerView::removedController, this, &ControllerRackView::deleteController, Qt::QueuedConnection);
|
||||
|
||||
auto moveUpAction = new QAction(controllerView);
|
||||
moveUpAction->setShortcut(Qt::Key_Up | Qt::AltModifier);
|
||||
moveUpAction->setShortcutContext(Qt::WidgetShortcut);
|
||||
connect(moveUpAction, &QAction::triggered, controllerView, &ControllerView::moveUp);
|
||||
controllerView->addAction(moveUpAction);
|
||||
|
||||
auto moveDownAction = new QAction(controllerView);
|
||||
moveDownAction->setShortcut(Qt::Key_Down | Qt::AltModifier);
|
||||
moveDownAction->setShortcutContext(Qt::WidgetShortcut);
|
||||
connect(moveDownAction, &QAction::triggered, controllerView, &ControllerView::moveDown);
|
||||
controllerView->addAction(moveDownAction);
|
||||
|
||||
|
||||
m_controllerViews.append( controllerView );
|
||||
m_scrollAreaLayout->insertWidget( m_nextIndex, controllerView );
|
||||
@@ -153,7 +188,7 @@ void ControllerRackView::onControllerAdded( Controller * controller )
|
||||
|
||||
|
||||
|
||||
void ControllerRackView::onControllerRemoved( Controller * removedController )
|
||||
void ControllerRackView::removeController(Controller* removedController)
|
||||
{
|
||||
ControllerView * viewOfRemovedController = 0;
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ ControllerView::ControllerView( Controller * _model, QWidget * _parent ) :
|
||||
{
|
||||
this->setFrameStyle( QFrame::StyledPanel );
|
||||
this->setFrameShadow( QFrame::Raised );
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
auto vBoxLayout = new QVBoxLayout(this);
|
||||
|
||||
@@ -132,11 +133,11 @@ void ControllerView::closeControls()
|
||||
m_show = true;
|
||||
}
|
||||
|
||||
void ControllerView::moveUp() { emit movedUp(this); }
|
||||
|
||||
void ControllerView::deleteController()
|
||||
{
|
||||
emit( deleteController( this ) );
|
||||
}
|
||||
void ControllerView::moveDown() { emit movedDown(this); }
|
||||
|
||||
void ControllerView::removeController() { emit removedController(this); }
|
||||
|
||||
void ControllerView::renameController()
|
||||
{
|
||||
@@ -173,10 +174,13 @@ void ControllerView::modelChanged()
|
||||
|
||||
void ControllerView::contextMenuEvent( QContextMenuEvent * )
|
||||
{
|
||||
QPointer<CaptionMenu> contextMenu = new CaptionMenu( model()->displayName(), this );
|
||||
contextMenu->addAction( embed::getIconPixmap( "cancel" ),
|
||||
tr( "&Remove this controller" ),
|
||||
this, SLOT(deleteController()));
|
||||
Controller* c = castModel<Controller>();
|
||||
QPointer<CaptionMenu> contextMenu = new CaptionMenu(c->name(), this);
|
||||
contextMenu->addAction(embed::getIconPixmap("arp_up"), tr("Move &up"), this, &ControllerView::moveUp);
|
||||
contextMenu->addAction(embed::getIconPixmap("arp_down"), tr("Move &down"), this, &ControllerView::moveDown);
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(
|
||||
embed::getIconPixmap("cancel"), tr("&Remove this controller"), this, &ControllerView::removeController);
|
||||
contextMenu->addAction( tr("Re&name this controller"), this, SLOT(renameController()));
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->exec( QCursor::pos() );
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QAction>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
@@ -170,13 +171,22 @@ void EffectRackView::update()
|
||||
if( i >= m_effectViews.size() )
|
||||
{
|
||||
auto view = new EffectView(effect, w);
|
||||
connect( view, SIGNAL(moveUp(lmms::gui::EffectView*)),
|
||||
this, SLOT(moveUp(lmms::gui::EffectView*)));
|
||||
connect( view, SIGNAL(moveDown(lmms::gui::EffectView*)),
|
||||
this, SLOT(moveDown(lmms::gui::EffectView*)));
|
||||
connect( view, SIGNAL(deletePlugin(lmms::gui::EffectView*)),
|
||||
this, SLOT(deletePlugin(lmms::gui::EffectView*)),
|
||||
Qt::QueuedConnection );
|
||||
connect(view, &EffectView::movedUp, this, &EffectRackView::moveUp);
|
||||
connect(view, &EffectView::movedDown, this, &EffectRackView::moveDown);
|
||||
connect(view, &EffectView::deletedPlugin, this, &EffectRackView::deletePlugin, Qt::QueuedConnection);
|
||||
|
||||
QAction* moveUpAction = new QAction(view);
|
||||
moveUpAction->setShortcut(Qt::Key_Up | Qt::AltModifier);
|
||||
moveUpAction->setShortcutContext(Qt::WidgetShortcut);
|
||||
connect(moveUpAction, &QAction::triggered, view, &EffectView::moveUp);
|
||||
view->addAction(moveUpAction);
|
||||
|
||||
QAction* moveDownAction = new QAction(view);
|
||||
moveDownAction->setShortcut(Qt::Key_Down | Qt::AltModifier);
|
||||
moveDownAction->setShortcutContext(Qt::WidgetShortcut);
|
||||
connect(moveDownAction, &QAction::triggered, view, &EffectView::moveDown);
|
||||
view->addAction(moveDownAction);
|
||||
|
||||
view->show();
|
||||
m_effectViews.append( view );
|
||||
if( i < view_map.size() )
|
||||
|
||||
@@ -53,6 +53,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) :
|
||||
m_dragging(false)
|
||||
{
|
||||
setFixedSize(EffectView::DEFAULT_WIDTH, EffectView::DEFAULT_HEIGHT);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
// Disable effects that are of type "DummyEffect"
|
||||
bool isEnabled = !dynamic_cast<DummyEffect *>( effect() );
|
||||
@@ -90,7 +91,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) :
|
||||
{
|
||||
auto ctls_btn = new QPushButton(tr("Controls"), this);
|
||||
QFont f = ctls_btn->font();
|
||||
ctls_btn->setFont( pointSize<8>( f ) );
|
||||
ctls_btn->setFont(adjustedToPixelSize(f, 10));
|
||||
ctls_btn->setGeometry( 150, 14, 50, 20 );
|
||||
connect( ctls_btn, SIGNAL(clicked()),
|
||||
this, SLOT(editControls()));
|
||||
@@ -162,7 +163,7 @@ void EffectView::editControls()
|
||||
|
||||
void EffectView::moveUp()
|
||||
{
|
||||
emit moveUp( this );
|
||||
emit movedUp(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -170,14 +171,14 @@ void EffectView::moveUp()
|
||||
|
||||
void EffectView::moveDown()
|
||||
{
|
||||
emit moveDown( this );
|
||||
emit movedDown(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void EffectView::deletePlugin()
|
||||
{
|
||||
emit deletePlugin( this );
|
||||
emit deletedPlugin(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +258,7 @@ void EffectView::paintEvent( QPaintEvent * )
|
||||
QPainter p( this );
|
||||
p.drawPixmap( 0, 0, m_bg );
|
||||
|
||||
QFont f = pointSizeF( font(), 7.5f );
|
||||
QFont f = adjustedToPixelSize(font(), 10);
|
||||
f.setBold( true );
|
||||
p.setFont( f );
|
||||
|
||||
|
||||
@@ -23,25 +23,30 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FileBrowser.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QDirIterator>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QMdiArea>
|
||||
#include <QMdiSubWindow>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QShortcut>
|
||||
#include <QStringList>
|
||||
#include <cassert>
|
||||
#include <queue>
|
||||
|
||||
#include "FileBrowser.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "DataFile.h"
|
||||
#include "embed.h"
|
||||
#include "Engine.h"
|
||||
#include "FileBrowser.h"
|
||||
#include "FileSearch.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "ImportFilter.h"
|
||||
#include "Instrument.h"
|
||||
@@ -51,12 +56,16 @@
|
||||
#include "PatternStore.h"
|
||||
#include "PluginFactory.h"
|
||||
#include "PresetPreviewPlayHandle.h"
|
||||
#include "Sample.h"
|
||||
#include "SampleClip.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "SamplePlayHandle.h"
|
||||
#include "SampleTrack.h"
|
||||
#include "Song.h"
|
||||
#include "StringPairDrag.h"
|
||||
#include "TextFloat.h"
|
||||
#include "ThreadPool.h"
|
||||
#include "embed.h"
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
@@ -96,14 +105,13 @@ void FileBrowser::addContentCheckBox()
|
||||
|
||||
FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
const QString & title, const QPixmap & pm,
|
||||
QWidget * parent, bool dirs_as_items, bool recurse,
|
||||
QWidget * parent, bool dirs_as_items,
|
||||
const QString& userDir,
|
||||
const QString& factoryDir):
|
||||
SideBarWidget( title, pm, parent ),
|
||||
m_directories( directories ),
|
||||
m_filter( filter ),
|
||||
m_dirsAsItems( dirs_as_items ),
|
||||
m_recurse( recurse ),
|
||||
m_userDir(userDir),
|
||||
m_factoryDir(factoryDir)
|
||||
{
|
||||
@@ -121,11 +129,12 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
searchWidgetLayout->setContentsMargins(0, 0, 0, 0);
|
||||
searchWidgetLayout->setSpacing( 0 );
|
||||
|
||||
m_filterEdit = new QLineEdit( searchWidget );
|
||||
m_filterEdit->setPlaceholderText( tr("Search") );
|
||||
m_filterEdit->setClearButtonEnabled( true );
|
||||
connect( m_filterEdit, SIGNAL( textEdited( const QString& ) ),
|
||||
this, SLOT( filterAndExpandItems( const QString& ) ) );
|
||||
m_filterEdit = new QLineEdit(searchWidget);
|
||||
m_filterEdit->setPlaceholderText(tr("Search"));
|
||||
m_filterEdit->setClearButtonEnabled(true);
|
||||
m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition);
|
||||
|
||||
connect(m_filterEdit, &QLineEdit::textEdited, this, &FileBrowser::onSearch);
|
||||
|
||||
auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget);
|
||||
reload_btn->setToolTip( tr( "Refresh list" ) );
|
||||
@@ -140,6 +149,15 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
m_fileBrowserTreeWidget = new FileBrowserTreeWidget( contentParent() );
|
||||
addContentWidget( m_fileBrowserTreeWidget );
|
||||
|
||||
m_searchTreeWidget = new FileBrowserTreeWidget(contentParent());
|
||||
m_searchTreeWidget->hide();
|
||||
addContentWidget(m_searchTreeWidget);
|
||||
|
||||
m_searchIndicator = new QProgressBar(this);
|
||||
m_searchIndicator->setMinimum(0);
|
||||
m_searchIndicator->setMaximum(100);
|
||||
addContentWidget(m_searchIndicator);
|
||||
|
||||
// Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box.
|
||||
auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter()));
|
||||
filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
@@ -157,86 +175,128 @@ void FileBrowser::saveDirectoriesStates()
|
||||
|
||||
void FileBrowser::restoreDirectoriesStates()
|
||||
{
|
||||
expandItems(nullptr, m_savedExpandedDirs);
|
||||
expandItems(m_savedExpandedDirs);
|
||||
}
|
||||
|
||||
bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item)
|
||||
void FileBrowser::foundSearchMatch(FileSearch* search, const QString& match)
|
||||
{
|
||||
// Call with item = nullptr to filter the entire tree
|
||||
assert(search != nullptr);
|
||||
if (m_currentSearch.get() != search) { return; }
|
||||
|
||||
if (item == nullptr)
|
||||
auto basePath = QString{};
|
||||
for (const auto& path : m_directories.split('*'))
|
||||
{
|
||||
// First search character so need to save current expanded directories
|
||||
if (m_previousFilterValue.isEmpty())
|
||||
if (!match.startsWith(QDir{path}.absolutePath())) { continue; }
|
||||
basePath = path;
|
||||
break;
|
||||
}
|
||||
|
||||
if (basePath.isEmpty()) { return; }
|
||||
|
||||
const auto baseDir = QDir{basePath};
|
||||
const auto matchInfo = QFileInfo{match};
|
||||
const auto matchRelativeToBasePath = baseDir.relativeFilePath(match);
|
||||
|
||||
auto pathParts = QDir::cleanPath(matchRelativeToBasePath).split("/");
|
||||
auto currentItem = static_cast<QTreeWidgetItem*>(nullptr);
|
||||
auto currentDir = baseDir;
|
||||
|
||||
for (const auto& pathPart : pathParts)
|
||||
{
|
||||
auto childCount = currentItem ? currentItem->childCount() : m_searchTreeWidget->topLevelItemCount();
|
||||
auto childItem = static_cast<QTreeWidgetItem*>(nullptr);
|
||||
|
||||
for (int i = 0; i < childCount; ++i)
|
||||
{
|
||||
saveDirectoriesStates();
|
||||
auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i);
|
||||
if (item->text(0) == pathPart)
|
||||
{
|
||||
childItem = item;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m_previousFilterValue = filter;
|
||||
if (!childItem)
|
||||
{
|
||||
auto pathPartInfo = QFileInfo(currentDir, pathPart);
|
||||
if (pathPartInfo.isDir())
|
||||
{
|
||||
// Only update directory (i.e., add entries) when it is the matched directory (so do not update
|
||||
// parents since entries would be added to them that did not match the filter)
|
||||
const auto disablePopulation = pathParts.indexOf(pathPart) < pathParts.size() - 1;
|
||||
|
||||
auto item = new Directory(pathPart, currentDir.path(), m_filter, disablePopulation);
|
||||
currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item);
|
||||
item->update();
|
||||
if (disablePopulation) { m_searchTreeWidget->expandItem(item); }
|
||||
childItem = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto item = new FileItem(pathPart, currentDir.path());
|
||||
currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item);
|
||||
childItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
currentItem = childItem;
|
||||
if (!currentDir.cd(pathPart)) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowser::searchCompleted(FileSearch* search)
|
||||
{
|
||||
assert(search != nullptr);
|
||||
if (m_currentSearch.get() != search) { return; }
|
||||
|
||||
m_currentSearch.reset();
|
||||
m_searchIndicator->setMaximum(100);
|
||||
}
|
||||
|
||||
void FileBrowser::onSearch(const QString& filter)
|
||||
{
|
||||
if (m_currentSearch) { m_currentSearch->cancel(); }
|
||||
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
// Restore previous expanded directories
|
||||
if (item == nullptr)
|
||||
{
|
||||
restoreDirectoriesStates();
|
||||
}
|
||||
displaySearch(false);
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
auto directories = m_directories.split('*');
|
||||
if (m_showUserContent && !m_showUserContent->isChecked()) { directories.removeAll(m_userDir); }
|
||||
if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { directories.removeAll(m_factoryDir); }
|
||||
if (directories.isEmpty()) { return; }
|
||||
|
||||
m_searchTreeWidget->clear();
|
||||
displaySearch(true);
|
||||
|
||||
auto browserExtensions = m_filter;
|
||||
const auto searchExtensions = browserExtensions.remove("*.").split(' ');
|
||||
|
||||
auto search = std::make_shared<FileSearch>(
|
||||
filter, directories, searchExtensions, excludedPaths(), dirFilters(), sortFlags());
|
||||
connect(search.get(), &FileSearch::foundMatch, this, &FileBrowser::foundSearchMatch, Qt::QueuedConnection);
|
||||
connect(search.get(), &FileSearch::searchCompleted, this, &FileBrowser::searchCompleted, Qt::QueuedConnection);
|
||||
|
||||
m_currentSearch = search;
|
||||
ThreadPool::instance().enqueue([search] { (*search)(); });
|
||||
}
|
||||
|
||||
void FileBrowser::displaySearch(bool on)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
m_searchTreeWidget->show();
|
||||
m_fileBrowserTreeWidget->hide();
|
||||
m_searchIndicator->setMaximum(0);
|
||||
return;
|
||||
}
|
||||
|
||||
bool anyMatched = false;
|
||||
|
||||
int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount();
|
||||
|
||||
for (int i = 0; i < numChildren; ++i)
|
||||
{
|
||||
QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i);
|
||||
|
||||
auto d = dynamic_cast<Directory*>(it);
|
||||
if (d)
|
||||
{
|
||||
if (it->text(0).contains(filter, Qt::CaseInsensitive))
|
||||
{
|
||||
it->setHidden(false);
|
||||
it->setExpanded(true);
|
||||
filterAndExpandItems(QString(), it);
|
||||
anyMatched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expanding is required when recursive to load in its contents, even if it's collapsed right afterward
|
||||
it->setExpanded(true);
|
||||
|
||||
bool didMatch = filterAndExpandItems(filter, it);
|
||||
it->setHidden(!didMatch);
|
||||
it->setExpanded(didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
auto f = dynamic_cast<FileItem*>(it);
|
||||
if (f)
|
||||
{
|
||||
// File
|
||||
bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive);
|
||||
it->setHidden(!didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
|
||||
// A standard item (i.e. no file or directory item?)
|
||||
else
|
||||
{
|
||||
// Hide if there's any filter
|
||||
it->setHidden(!filter.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return anyMatched;
|
||||
m_searchTreeWidget->hide();
|
||||
m_fileBrowserTreeWidget->show();
|
||||
m_searchIndicator->setMaximum(100);
|
||||
}
|
||||
|
||||
|
||||
@@ -275,14 +335,16 @@ void FileBrowser::reloadTree()
|
||||
}
|
||||
else
|
||||
{
|
||||
filterAndExpandItems(m_filterEdit->text());
|
||||
onSearch(m_filterEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FileBrowser::expandItems(QTreeWidgetItem* item, QList<QString> expandedDirs)
|
||||
void FileBrowser::expandItems(const QList<QString>& expandedDirs, QTreeWidgetItem* item)
|
||||
{
|
||||
if (expandedDirs.isEmpty()) { return; }
|
||||
|
||||
int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount();
|
||||
for (int i = 0; i < numChildren; ++i)
|
||||
{
|
||||
@@ -290,14 +352,10 @@ void FileBrowser::expandItems(QTreeWidgetItem* item, QList<QString> expandedDirs
|
||||
auto d = dynamic_cast<Directory*>(it);
|
||||
if (d)
|
||||
{
|
||||
// Expanding is required when recursive to load in its contents, even if it's collapsed right afterward
|
||||
if (m_recurse) { d->setExpanded(true); }
|
||||
|
||||
d->setExpanded(expandedDirs.contains(d->fullName()));
|
||||
|
||||
if (m_recurse && it->childCount())
|
||||
if (it->childCount() > 0)
|
||||
{
|
||||
expandItems(it, expandedDirs);
|
||||
expandItems(expandedDirs, it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +379,8 @@ void FileBrowser::giveFocusToFilter()
|
||||
|
||||
void FileBrowser::addItems(const QString & path )
|
||||
{
|
||||
if (FileBrowser::excludedPaths().contains(path)) { return; }
|
||||
|
||||
if( m_dirsAsItems )
|
||||
{
|
||||
m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) );
|
||||
@@ -328,68 +388,63 @@ void FileBrowser::addItems(const QString & path )
|
||||
}
|
||||
|
||||
// try to add all directories from file system alphabetically into the tree
|
||||
QDir cdir( path );
|
||||
QStringList files = cdir.entryList( QDir::Dirs, QDir::Name );
|
||||
files.sort(Qt::CaseInsensitive);
|
||||
for( QStringList::const_iterator it = files.constBegin();
|
||||
it != files.constEnd(); ++it )
|
||||
QDir cdir(path);
|
||||
if (!cdir.isReadable()) { return; }
|
||||
QFileInfoList entries = cdir.entryInfoList(
|
||||
m_filter.split(' '), dirFilters(), QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase);
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
QString cur_file = *it;
|
||||
if( cur_file[0] != '.' )
|
||||
if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; }
|
||||
|
||||
QString fileName = entry.fileName();
|
||||
if (entry.isDir())
|
||||
{
|
||||
// Merge dir's together
|
||||
bool orphan = true;
|
||||
for( int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i )
|
||||
for (int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i)
|
||||
{
|
||||
auto d = dynamic_cast<Directory*>(m_fileBrowserTreeWidget->topLevelItem(i));
|
||||
if( d == nullptr || cur_file < d->text( 0 ) )
|
||||
if (d == nullptr || fileName < d->text(0))
|
||||
{
|
||||
// insert before item, we're done
|
||||
auto dd = new Directory(cur_file, path, m_filter);
|
||||
m_fileBrowserTreeWidget->insertTopLevelItem( i,dd );
|
||||
auto dd = new Directory(fileName, path, m_filter);
|
||||
m_fileBrowserTreeWidget->insertTopLevelItem(i,dd);
|
||||
dd->update(); // add files to the directory
|
||||
orphan = false;
|
||||
break;
|
||||
}
|
||||
else if( cur_file == d->text( 0 ) )
|
||||
else if (fileName == d->text(0))
|
||||
{
|
||||
// imagine we have subdirs named "TripleOscillator/xyz" in
|
||||
// two directories from m_directories
|
||||
// then only add one tree widget for both
|
||||
// so we don't add a new Directory - we just
|
||||
// add the path to the current directory
|
||||
d->addDirectory( path );
|
||||
d->addDirectory(path);
|
||||
d->update();
|
||||
orphan = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( orphan )
|
||||
if (orphan)
|
||||
{
|
||||
// it has not yet been added yet, so it's (lexically)
|
||||
// larger than all other dirs => append it at the bottom
|
||||
auto d = new Directory(cur_file, path, m_filter);
|
||||
auto d = new Directory(fileName, path, m_filter);
|
||||
d->update();
|
||||
m_fileBrowserTreeWidget->addTopLevelItem( d );
|
||||
m_fileBrowserTreeWidget->addTopLevelItem(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files = cdir.entryList( QDir::Files, QDir::Name );
|
||||
for( QStringList::const_iterator it = files.constBegin();
|
||||
it != files.constEnd(); ++it )
|
||||
{
|
||||
QString cur_file = *it;
|
||||
if( cur_file[0] != '.' )
|
||||
else if (entry.isFile())
|
||||
{
|
||||
// TODO: don't insert instead of removing, order changed
|
||||
// remove existing file-items
|
||||
QList<QTreeWidgetItem *> existing = m_fileBrowserTreeWidget->findItems(
|
||||
cur_file, Qt::MatchFixedString );
|
||||
if( !existing.empty() )
|
||||
QList<QTreeWidgetItem *> existing = m_fileBrowserTreeWidget->findItems(fileName, Qt::MatchFixedString);
|
||||
if (!existing.empty())
|
||||
{
|
||||
delete existing.front();
|
||||
}
|
||||
(void) new FileItem( m_fileBrowserTreeWidget, cur_file, path );
|
||||
(void) new FileItem(m_fileBrowserTreeWidget, fileName, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,9 +720,12 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file)
|
||||
embed::getIconPixmap("sample_file", 24, 24), 0);
|
||||
// TODO: this can be removed once we do this outside the event thread
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
auto s = new SamplePlayHandle(fileName);
|
||||
s->setDoneMayReturnTrue(false);
|
||||
newPPH = s;
|
||||
if (auto buffer = SampleLoader::createBufferFromFile(fileName))
|
||||
{
|
||||
auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)});
|
||||
s->setDoneMayReturnTrue(false);
|
||||
newPPH = s;
|
||||
}
|
||||
delete tf;
|
||||
}
|
||||
else if (
|
||||
@@ -958,74 +1016,27 @@ void FileBrowserTreeWidget::updateDirectory(QTreeWidgetItem * item )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QPixmap * Directory::s_folderPixmap = nullptr;
|
||||
QPixmap * Directory::s_folderOpenedPixmap = nullptr;
|
||||
QPixmap * Directory::s_folderLockedPixmap = nullptr;
|
||||
|
||||
|
||||
Directory::Directory(const QString & filename, const QString & path,
|
||||
const QString & filter ) :
|
||||
QTreeWidgetItem( QStringList( filename ), TypeDirectoryItem ),
|
||||
m_directories( path ),
|
||||
m_filter( filter ),
|
||||
m_dirCount( 0 )
|
||||
Directory::Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation)
|
||||
: QTreeWidgetItem(QStringList(filename), TypeDirectoryItem)
|
||||
, m_directories(path)
|
||||
, m_filter(filter)
|
||||
, m_dirCount(0)
|
||||
, m_disableEntryPopulation(disableEntryPopulation)
|
||||
{
|
||||
initPixmaps();
|
||||
|
||||
setIcon(0, !QDir{fullName()}.isReadable() ? m_folderLockedPixmap : m_folderPixmap);
|
||||
setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator );
|
||||
|
||||
if( !QDir( fullName() ).isReadable() )
|
||||
{
|
||||
setIcon( 0, *s_folderLockedPixmap );
|
||||
}
|
||||
else
|
||||
{
|
||||
setIcon( 0, *s_folderPixmap );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Directory::initPixmaps()
|
||||
{
|
||||
if( s_folderPixmap == nullptr )
|
||||
{
|
||||
s_folderPixmap = new QPixmap(
|
||||
embed::getIconPixmap( "folder" ) );
|
||||
}
|
||||
|
||||
if( s_folderOpenedPixmap == nullptr )
|
||||
{
|
||||
s_folderOpenedPixmap = new QPixmap(
|
||||
embed::getIconPixmap( "folder_opened" ) );
|
||||
}
|
||||
|
||||
if( s_folderLockedPixmap == nullptr )
|
||||
{
|
||||
s_folderLockedPixmap = new QPixmap(
|
||||
embed::getIconPixmap( "folder_locked" ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Directory::update()
|
||||
{
|
||||
if( !isExpanded() )
|
||||
{
|
||||
setIcon( 0, *s_folderPixmap );
|
||||
setIcon(0, m_folderPixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
setIcon( 0, *s_folderOpenedPixmap );
|
||||
if( !childCount() )
|
||||
setIcon(0, m_folderOpenedPixmap);
|
||||
if (!m_disableEntryPopulation && !childCount())
|
||||
{
|
||||
m_dirCount = 0;
|
||||
// for all paths leading here, add their items
|
||||
@@ -1058,14 +1069,19 @@ void Directory::update()
|
||||
|
||||
bool Directory::addItems(const QString& path)
|
||||
{
|
||||
if (FileBrowser::excludedPaths().contains(path)) { return false; }
|
||||
|
||||
QDir thisDir(path);
|
||||
if (!thisDir.isReadable()) { return false; }
|
||||
|
||||
treeWidget()->setUpdatesEnabled(false);
|
||||
|
||||
QFileInfoList entries = thisDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware | QDir::DirsFirst | QDir::Name);
|
||||
for (auto& entry : entries)
|
||||
QFileInfoList entries
|
||||
= thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags());
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; }
|
||||
|
||||
QString fileName = entry.fileName();
|
||||
if (entry.isDir())
|
||||
{
|
||||
@@ -1073,7 +1089,7 @@ bool Directory::addItems(const QString& path)
|
||||
addChild(dir);
|
||||
m_dirCount++;
|
||||
}
|
||||
else if (entry.isFile() && thisDir.match(m_filter, fileName.toLower()))
|
||||
else if (entry.isFile())
|
||||
{
|
||||
auto fileItem = new FileItem(fileName, path);
|
||||
addChild(fileItem);
|
||||
@@ -1089,15 +1105,6 @@ bool Directory::addItems(const QString& path)
|
||||
|
||||
|
||||
|
||||
QPixmap * FileItem::s_projectFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_presetFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_sampleFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_soundfontFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_vstPluginFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_midiFilePixmap = nullptr;
|
||||
QPixmap * FileItem::s_unknownFilePixmap = nullptr;
|
||||
|
||||
|
||||
FileItem::FileItem(QTreeWidget * parent, const QString & name,
|
||||
const QString & path ) :
|
||||
QTreeWidgetItem( parent, QStringList( name) , TypeFileItem ),
|
||||
@@ -1123,72 +1130,38 @@ FileItem::FileItem(const QString & name, const QString & path ) :
|
||||
|
||||
void FileItem::initPixmaps()
|
||||
{
|
||||
if( s_projectFilePixmap == nullptr )
|
||||
{
|
||||
s_projectFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"project_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if( s_presetFilePixmap == nullptr )
|
||||
{
|
||||
s_presetFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"preset_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if( s_sampleFilePixmap == nullptr )
|
||||
{
|
||||
s_sampleFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"sample_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if ( s_soundfontFilePixmap == nullptr )
|
||||
{
|
||||
s_soundfontFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"soundfont_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if ( s_vstPluginFilePixmap == nullptr )
|
||||
{
|
||||
s_vstPluginFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"vst_plugin_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if( s_midiFilePixmap == nullptr )
|
||||
{
|
||||
s_midiFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"midi_file", 16, 16 ) );
|
||||
}
|
||||
|
||||
if( s_unknownFilePixmap == nullptr )
|
||||
{
|
||||
s_unknownFilePixmap = new QPixmap( embed::getIconPixmap(
|
||||
"unknown_file" ) );
|
||||
}
|
||||
static auto s_projectFilePixmap = embed::getIconPixmap("project_file", 16, 16);
|
||||
static auto s_presetFilePixmap = embed::getIconPixmap("preset_file", 16, 16);
|
||||
static auto s_sampleFilePixmap = embed::getIconPixmap("sample_file", 16, 16);
|
||||
static auto s_soundfontFilePixmap = embed::getIconPixmap("soundfont_file", 16, 16);
|
||||
static auto s_vstPluginFilePixmap = embed::getIconPixmap("vst_plugin_file", 16, 16);
|
||||
static auto s_midiFilePixmap = embed::getIconPixmap("midi_file", 16, 16);
|
||||
static auto s_unknownFilePixmap = embed::getIconPixmap("unknown_file");
|
||||
|
||||
switch( m_type )
|
||||
{
|
||||
case FileType::Project:
|
||||
setIcon( 0, *s_projectFilePixmap );
|
||||
setIcon(0, s_projectFilePixmap);
|
||||
break;
|
||||
case FileType::Preset:
|
||||
setIcon( 0, *s_presetFilePixmap );
|
||||
setIcon(0, s_presetFilePixmap);
|
||||
break;
|
||||
case FileType::SoundFont:
|
||||
setIcon( 0, *s_soundfontFilePixmap );
|
||||
setIcon(0, s_soundfontFilePixmap);
|
||||
break;
|
||||
case FileType::VstPlugin:
|
||||
setIcon( 0, *s_vstPluginFilePixmap );
|
||||
setIcon(0, s_vstPluginFilePixmap);
|
||||
break;
|
||||
case FileType::Sample:
|
||||
case FileType::Patch: // TODO
|
||||
setIcon( 0, *s_sampleFilePixmap );
|
||||
setIcon(0, s_sampleFilePixmap);
|
||||
break;
|
||||
case FileType::Midi:
|
||||
setIcon( 0, *s_midiFilePixmap );
|
||||
setIcon(0, s_midiFilePixmap);
|
||||
break;
|
||||
case FileType::Unknown:
|
||||
default:
|
||||
setIcon( 0, *s_unknownFilePixmap );
|
||||
setIcon(0, s_unknownFilePixmap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1277,5 +1250,30 @@ QString FileItem::extension(const QString & file )
|
||||
return QFileInfo( file ).suffix().toLower();
|
||||
}
|
||||
|
||||
QString FileItem::defaultFilters()
|
||||
{
|
||||
const auto projectFilters = QStringList{"*.mmp", "*.mpt", "*.mmpz"};
|
||||
const auto presetFilters = QStringList{"*.xpf", "*.xml", "*.xiz", "*.lv2"};
|
||||
const auto soundFontFilters = QStringList{"*.sf2", "*.sf3"};
|
||||
const auto patchFilters = QStringList{"*.pat"};
|
||||
const auto midiFilters = QStringList{"*.mid", "*.midi", "*.rmi"};
|
||||
|
||||
auto vstPluginFilters = QStringList{"*.dll"};
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
vstPluginFilters.append("*.so");
|
||||
#endif
|
||||
|
||||
auto audioFilters
|
||||
= QStringList{"*.wav", "*.ogg", "*.ds", "*.flac", "*.spx", "*.voc", "*.aif", "*.aiff", "*.au", "*.raw"};
|
||||
#ifdef LMMS_HAVE_SNDFILE_MP3
|
||||
audioFilters.append("*.mp3");
|
||||
#endif
|
||||
|
||||
const auto extensions = projectFilters + presetFilters + soundFontFilters + patchFilters + midiFilters
|
||||
+ vstPluginFilters + audioFilters;
|
||||
|
||||
return extensions.join(" ");
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms::gui
|
||||
|
||||
@@ -148,7 +148,7 @@ GuiApplication::GuiApplication()
|
||||
connect(m_songEditor, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*)));
|
||||
|
||||
displayInitProgress(tr("Preparing mixer"));
|
||||
m_mixerView = new MixerView;
|
||||
m_mixerView = new MixerView(Engine::mixer());
|
||||
connect(m_mixerView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*)));
|
||||
|
||||
displayInitProgress(tr("Preparing controller rack"));
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "Knob.h"
|
||||
#include "TempoSyncKnob.h"
|
||||
#include "PixmapButton.h"
|
||||
#include "SampleLoader.h"
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
@@ -210,14 +211,14 @@ LfoControllerDialog::~LfoControllerDialog()
|
||||
|
||||
void LfoControllerDialog::askUserDefWave()
|
||||
{
|
||||
SampleBuffer * sampleBuffer = dynamic_cast<LfoController*>(this->model())->
|
||||
m_userDefSampleBuffer;
|
||||
QString fileName = sampleBuffer->openAndSetWaveformFile();
|
||||
if( fileName.isEmpty() == false )
|
||||
{
|
||||
// TODO:
|
||||
m_userWaveBtn->setToolTip(sampleBuffer->audioFile());
|
||||
}
|
||||
const auto fileName = SampleLoader::openWaveformFile();
|
||||
if (fileName.isEmpty()) { return; }
|
||||
|
||||
auto lfoModel = dynamic_cast<LfoController*>(model());
|
||||
auto& buffer = lfoModel->m_userDefSampleBuffer;
|
||||
buffer = SampleLoader::createBufferFromFile(fileName);
|
||||
|
||||
m_userWaveBtn->setToolTip(buffer->audioFile());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <lilv/lilv.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/port-props/port-props.h>
|
||||
#include <lv2/port-props/port-props.h>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "Controls.h"
|
||||
@@ -74,7 +74,7 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) :
|
||||
break;
|
||||
case PortVis::Integer:
|
||||
{
|
||||
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
|
||||
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
|
||||
auto pMin = port.min(sr);
|
||||
auto pMax = port.max(sr);
|
||||
int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax));
|
||||
@@ -137,7 +137,8 @@ AutoLilvNode Lv2ViewProc::uri(const char *uriStr)
|
||||
|
||||
|
||||
|
||||
Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
|
||||
Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) :
|
||||
m_helpWindowEventFilter(this)
|
||||
{
|
||||
auto grid = new QGridLayout(meAsWidget);
|
||||
|
||||
@@ -156,8 +157,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
|
||||
m_toggleUIButton->setCheckable(true);
|
||||
m_toggleUIButton->setChecked(false);
|
||||
m_toggleUIButton->setIcon(embed::getIconPixmap("zoom"));
|
||||
m_toggleUIButton->setFont(
|
||||
pointSize<8>(m_toggleUIButton->font()));
|
||||
m_toggleUIButton->setFont(adjustedToPixelSize(m_toggleUIButton->font(), 8));
|
||||
btnBox->addWidget(m_toggleUIButton, 0);
|
||||
}
|
||||
btnBox->addStretch(1);
|
||||
@@ -172,7 +172,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
|
||||
LILV_FOREACH(nodes, itr, props.get())
|
||||
{
|
||||
const LilvNode* node = lilv_nodes_get(props.get(), itr);
|
||||
auto infoLabel = new QLabel(lilv_node_as_string(node));
|
||||
auto infoLabel = new QLabel(QString(lilv_node_as_string(node)).trimmed() + "\n");
|
||||
infoLabel->setWordWrap(true);
|
||||
infoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
|
||||
@@ -181,8 +181,9 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
|
||||
btnBox->addWidget(m_helpButton);
|
||||
|
||||
m_helpWindow = getGUI()->mainWindow()->addWindowedWidget(infoLabel);
|
||||
m_helpWindow->setSizePolicy(QSizePolicy::Minimum,
|
||||
m_helpWindow->setSizePolicy(QSizePolicy::Expanding,
|
||||
QSizePolicy::Expanding);
|
||||
m_helpWindow->installEventFilter(&m_helpWindowEventFilter);
|
||||
m_helpWindow->setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
m_helpWindow->hide();
|
||||
|
||||
@@ -203,6 +204,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
|
||||
|
||||
|
||||
Lv2ViewBase::~Lv2ViewBase() {
|
||||
closeHelpWindow();
|
||||
// TODO: hide UI if required
|
||||
}
|
||||
|
||||
@@ -228,6 +230,14 @@ void Lv2ViewBase::toggleHelp(bool visible)
|
||||
|
||||
|
||||
|
||||
void Lv2ViewBase::closeHelpWindow()
|
||||
{
|
||||
if (m_helpWindow) { m_helpWindow->close(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Lv2ViewBase::modelChanged(Lv2ControlBase *ctrlBase)
|
||||
{
|
||||
// reconnect models
|
||||
@@ -248,6 +258,32 @@ AutoLilvNode Lv2ViewBase::uri(const char *uriStr)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Lv2ViewBase::onHelpWindowClosed()
|
||||
{
|
||||
m_helpButton->setChecked(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
HelpWindowEventFilter::HelpWindowEventFilter(Lv2ViewBase* viewBase) :
|
||||
m_viewBase(viewBase) {}
|
||||
|
||||
|
||||
|
||||
|
||||
bool HelpWindowEventFilter::eventFilter(QObject* , QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::Close) {
|
||||
m_viewBase->m_helpButton->setChecked(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms::gui
|
||||
|
||||
#endif // LMMS_HAVE_LV2
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user