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:
Michael Gregorius
2024-05-31 13:53:49 +02:00
631 changed files with 16343 additions and 12806 deletions

View File

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

View File

@@ -1,49 +0,0 @@
add_library(rpmalloc STATIC
rpmalloc/rpmalloc/rpmalloc.c
rpmalloc/rpmalloc/rpmalloc.h
)
target_include_directories(rpmalloc PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc
)
set_property(TARGET rpmalloc PROPERTY C_STANDARD 11)
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(rpmalloc
PRIVATE -Wno-unused-variable
)
endif()
if (NOT LMMS_BUILD_WIN32)
target_compile_definitions(rpmalloc
PRIVATE -D_GNU_SOURCE
)
endif()
if(MINGW)
target_compile_definitions(rpmalloc
PRIVATE -D_WIN32_WINNT=0x600
)
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
# rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS,
# which is only available starting with GCC 5
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5)
set(ENABLE_VALIDATE_ARGS OFF)
else ()
set(ENABLE_VALIDATE_ARGS ON)
endif()
target_compile_definitions(rpmalloc
PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS}
)
endif()
option(LMMS_ENABLE_MALLOC_STATS "Enables statistics for rpmalloc" OFF)
if (LMMS_ENABLE_MALLOC_STATS)
target_compile_definitions(rpmalloc
PRIVATE -DENABLE_STATISTICS=1
)
endif()

View File

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

View File

@@ -1,6 +1,7 @@
set(COMMON_SRCS
RemotePluginBase.cpp
SharedMemory.cpp
SystemSemaphore.cpp
)
foreach(SRC ${COMMON_SRCS})

View File

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

View File

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

View File

@@ -0,0 +1,181 @@
/*
* SystemSemaphore.cpp
*
* Copyright (c) 2024 Dominic Clark
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#include "SystemSemaphore.h"
#include <limits>
#include <system_error>
#include <type_traits>
#include <utility>
#include "lmmsconfig.h"
#include "RaiiHelpers.h"
#ifdef LMMS_HAVE_UNISTD_H
# include <unistd.h>
#endif
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
# include <fcntl.h>
# include <semaphore.h>
#elif defined(LMMS_BUILD_WIN32)
# include <windows.h>
#else
# error "No system semaphore implementation available"
#endif
namespace lmms {
namespace detail {
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
namespace {
[[noreturn]] void throwSystemError(const char* message)
{
throw std::system_error{errno, std::generic_category(), message};
}
template<typename F>
auto retryWhileInterrupted(F&& function, std::invoke_result_t<F> error = -1)
noexcept(std::is_nothrow_invocable_v<F>) -> auto
{
while (true)
{
const auto result = function();
if (result != error || errno != EINTR) { return result; }
}
}
using UniqueSemaphore = UniqueNullableResource<const char*, nullptr, sem_unlink>;
} // namespace
class SystemSemaphoreImpl
{
public:
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
m_key{"/" + key},
m_sem{retryWhileInterrupted([&]() noexcept {
return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value);
}, SEM_FAILED)}
{
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
m_ownedSemaphore.reset(m_key.c_str());
}
explicit SystemSemaphoreImpl(const std::string& key) :
m_key{"/" + key},
m_sem{retryWhileInterrupted([&]() noexcept {
return sem_open(m_key.c_str(), 0);
}, SEM_FAILED)}
{
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
}
~SystemSemaphoreImpl()
{
// We can't use `UniqueNullableResource` for `m_sem`, as the null value
// (`SEM_FAILED`) is not a constant expression on macOS (it's defined as
// `(sem_t*) -1`), so can't be used as a template parameter.
sem_close(m_sem);
}
auto acquire() noexcept -> bool
{
return retryWhileInterrupted([&]() noexcept {
return sem_wait(m_sem);
}) == 0;
}
auto release() noexcept -> bool { return sem_post(m_sem) == 0; }
private:
std::string m_key;
sem_t* m_sem;
UniqueSemaphore m_ownedSemaphore;
};
#elif defined(LMMS_BUILD_WIN32)
namespace {
[[noreturn]] void throwSystemError(const char* message)
{
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
}
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
} // namespace
class SystemSemaphoreImpl
{
public:
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits<LONG>::max(), key.c_str())}
{
if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) {
throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed");
}
}
explicit SystemSemaphoreImpl(const std::string& key) :
m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())}
{
if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); }
}
auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; }
auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); }
private:
UniqueHandle m_sem;
};
#endif
} // namespace detail
SystemSemaphore::SystemSemaphore() noexcept = default;
SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) :
m_key{std::move(key)},
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key, value)}
{}
SystemSemaphore::SystemSemaphore(std::string key) :
m_key{std::move(key)},
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key)}
{}
SystemSemaphore::~SystemSemaphore() = default;
SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default;
auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default;
auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); }
auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); }
} // namespace lmms

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1,236 @@
/*
* SampleDecoder.cpp - Decodes audio files in various formats
*
* Copyright (c) 2023 saker <sakertooth@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "SampleDecoder.h"
#include <QFile>
#include <QFileInfo>
#include <QString>
#include <memory>
#include <sndfile.h>
#ifdef LMMS_HAVE_OGGVORBIS
#include <vorbis/vorbisfile.h>
#endif
#include "AudioEngine.h"
#include "DrumSynth.h"
#include "Engine.h"
#include "lmms_basics.h"
namespace lmms {
namespace {
using Decoder = std::optional<SampleDecoder::Result> (*)(const QString&);
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
#ifdef LMMS_HAVE_OGGVORBIS
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>;
#endif
static constexpr std::array<Decoder, 3> decoders = {&decodeSampleSF,
#ifdef LMMS_HAVE_OGGVORBIS
&decodeSampleOggVorbis,
#endif
&decodeSampleDS};
auto decodeSampleSF(const QString& audioFile) -> std::optional<SampleDecoder::Result>
{
SNDFILE* sndFile = nullptr;
auto sfInfo = SF_INFO{};
// TODO: Remove use of QFile
auto file = QFile{audioFile};
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false);
if (sf_error(sndFile) != 0) { return std::nullopt; }
auto buf = std::vector<sample_t>(sfInfo.channels * sfInfo.frames);
sf_read_float(sndFile, buf.data(), buf.size());
sf_close(sndFile);
file.close();
auto result = std::vector<sampleFrame>(sfInfo.frames);
for (int i = 0; i < static_cast<int>(result.size()); ++i)
{
if (sfInfo.channels == 1)
{
// Upmix from mono to stereo
result[i] = {buf[i], buf[i]};
}
else if (sfInfo.channels > 1)
{
// TODO: Add support for higher number of channels (i.e., 5.1 channel systems)
// The current behavior assumes stereo in all cases excluding mono.
// This may not be the expected behavior, given some audio files with a higher number of channels.
result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]};
}
}
return SampleDecoder::Result{std::move(result), static_cast<int>(sfInfo.samplerate)};
}
auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Result>
{
// Populated by DrumSynth::GetDSFileSamples
int_sample_t* dataPtr = nullptr;
auto ds = DrumSynth{};
const auto engineRate = Engine::audioEngine()->outputSampleRate();
const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate);
const auto data = std::unique_ptr<int_sample_t[]>{dataPtr}; // NOLINT, we have to use a C-style array here
if (frames <= 0 || !data) { return std::nullopt; }
auto result = std::vector<sampleFrame>(frames);
src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS);
return SampleDecoder::Result{std::move(result), static_cast<int>(engineRate)};
}
#ifdef LMMS_HAVE_OGGVORBIS
auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional<SampleDecoder::Result>
{
static auto s_read = [](void* buffer, size_t size, size_t count, void* stream) -> size_t {
auto file = static_cast<QFile*>(stream);
return file->read(static_cast<char*>(buffer), size * count);
};
static auto s_seek = [](void* stream, ogg_int64_t offset, int whence) -> int {
auto file = static_cast<QFile*>(stream);
if (whence == SEEK_SET) { file->seek(offset); }
else if (whence == SEEK_CUR) { file->seek(file->pos() + offset); }
else if (whence == SEEK_END) { file->seek(file->size() + offset); }
else { return -1; }
return 0;
};
static auto s_close = [](void* stream) -> int {
auto file = static_cast<QFile*>(stream);
file->close();
return 0;
};
static auto s_tell = [](void* stream) -> long {
auto file = static_cast<QFile*>(stream);
return file->pos();
};
static ov_callbacks s_callbacks = {s_read, s_seek, s_close, s_tell};
// TODO: Remove use of QFile
auto file = QFile{audioFile};
if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; }
auto vorbisFile = OggVorbis_File{};
if (ov_open_callbacks(&file, &vorbisFile, nullptr, 0, s_callbacks) < 0) { return std::nullopt; }
const auto vorbisInfo = ov_info(&vorbisFile, -1);
if (vorbisInfo == nullptr) { return std::nullopt; }
const auto numChannels = vorbisInfo->channels;
const auto sampleRate = vorbisInfo->rate;
const auto numSamples = ov_pcm_total(&vorbisFile, -1);
if (numSamples < 0) { return std::nullopt; }
auto buffer = std::vector<float>(numSamples);
auto output = static_cast<float**>(nullptr);
auto totalSamplesRead = 0;
while (true)
{
auto samplesRead = ov_read_float(&vorbisFile, &output, numSamples, 0);
if (samplesRead < 0) { return std::nullopt; }
else if (samplesRead == 0) { break; }
std::copy_n(*output, samplesRead, buffer.begin() + totalSamplesRead);
totalSamplesRead += samplesRead;
}
auto result = std::vector<sampleFrame>(totalSamplesRead / numChannels);
for (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

View File

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

View File

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

View File

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

@@ -0,0 +1,85 @@
/*
* ThreadPool.cpp
*
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "ThreadPool.h"
#include <cassert>
#include <cstddef>
#include <memory>
namespace lmms {
ThreadPool::ThreadPool(size_t numWorkers)
{
assert(numWorkers > 0);
m_workers.reserve(numWorkers);
for (size_t i = 0; i < numWorkers; ++i)
{
m_workers.emplace_back([this] { run(); });
}
}
ThreadPool::~ThreadPool()
{
{
const auto lock = std::unique_lock{m_runMutex};
m_done = true;
}
m_runCond.notify_all();
for (auto& worker : m_workers)
{
if (worker.joinable()) { worker.join(); }
}
}
auto ThreadPool::numWorkers() const -> size_t
{
return m_workers.size();
}
void ThreadPool::run()
{
while (!m_done)
{
std::function<void()> task;
{
auto lock = std::unique_lock{m_runMutex};
m_runCond.wait(lock, [this] { return !m_queue.empty() || m_done; });
if (m_done) { break; }
task = m_queue.front();
m_queue.pop();
}
task();
}
}
auto ThreadPool::instance() -> ThreadPool&
{
static auto s_pool = ThreadPool{s_numWorkers};
return s_pool;
}
} // namespace lmms

View File

@@ -25,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
View File

@@ -0,0 +1,83 @@
/*
* Timeline.cpp
*
* Copyright (c) 2023 Dominic Clark
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#include "Timeline.h"
#include <algorithm>
#include <tuple>
#include <QDomDocument>
#include <QDomElement>
namespace lmms {
void Timeline::setLoopBegin(TimePos begin)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd});
}
void Timeline::setLoopEnd(TimePos end)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end);
}
void Timeline::setLoopPoints(TimePos begin, TimePos end)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end);
}
void Timeline::setLoopEnabled(bool enabled)
{
if (enabled != m_loopEnabled) {
m_loopEnabled = enabled;
emit loopEnabledChanged(m_loopEnabled);
}
}
void Timeline::setStopBehaviour(StopBehaviour behaviour)
{
if (behaviour != m_stopBehaviour) {
m_stopBehaviour = behaviour;
emit stopBehaviourChanged(m_stopBehaviour);
}
}
void Timeline::saveSettings(QDomDocument& doc, QDomElement& element)
{
element.setAttribute("lp0pos", static_cast<int>(loopBegin()));
element.setAttribute("lp1pos", static_cast<int>(loopEnd()));
element.setAttribute("lpstate", static_cast<int>(loopEnabled()));
element.setAttribute("stopbehaviour", static_cast<int>(stopBehaviour()));
}
void Timeline::loadSettings(const QDomElement& element)
{
setLoopPoints(
static_cast<TimePos>(element.attribute("lp0pos").toInt()),
static_cast<TimePos>(element.attribute("lp1pos").toInt())
);
setLoopEnabled(static_cast<bool>(element.attribute("lpstate").toInt()));
setStopBehaviour(static_cast<StopBehaviour>(element.attribute("stopbehaviour", "1").toInt()));
}
} // namespace lmms

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,6 @@
#include "ConfigManager.h"
#include "LcdSpinBox.h"
#include "gui_templates.h"
namespace lmms::gui
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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