Support per-note detuning and panning with Sf2 Player (#6602)
* Add `ArrayVector` class template and tests * Fix counting of failed test suites * Support detuning and panning with Sf2 Player * Restrict panning to supported FluidSynth versions * Fix data array cast type * Fix tests for Qt<5.10 and correct mistaken test * DIsplay warning for FluidSynth < 2 * Remove unnecessary clamp * Update include guard name
This commit is contained in:
@@ -494,7 +494,11 @@ IF(WANT_SF2)
|
||||
find_package(FluidSynth 1.1.0)
|
||||
if(FluidSynth_FOUND)
|
||||
SET(LMMS_HAVE_FLUIDSYNTH TRUE)
|
||||
SET(STATUS_FLUIDSYNTH "OK")
|
||||
if(FluidSynth_VERSION_STRING VERSION_GREATER_EQUAL 2)
|
||||
set(STATUS_FLUIDSYNTH "OK")
|
||||
else()
|
||||
set(STATUS_FLUIDSYNTH "OK (FluidSynth version < 2: per-note panning unsupported)")
|
||||
endif()
|
||||
else()
|
||||
SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similar)"
|
||||
"is highly recommended")
|
||||
|
||||
388
include/ArrayVector.h
Normal file
388
include/ArrayVector.h
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* ArrayVector.h
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LMMS_ARRAY_VECTOR_H
|
||||
#define LMMS_ARRAY_VECTOR_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename T, typename = void>
|
||||
constexpr bool is_input_iterator_v = false;
|
||||
|
||||
template<typename T>
|
||||
constexpr bool is_input_iterator_v<T, std::void_t<typename std::iterator_traits<T>::iterator_category>> =
|
||||
std::is_convertible_v<typename std::iterator_traits<T>::iterator_category, std::input_iterator_tag>;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* A container that stores up to a maximum of `N` elements of type `T` directly
|
||||
* within itself, rather than separately on the heap. Useful when a dynamically
|
||||
* resizeable container is needed for use in real-time code. Can be thought of
|
||||
* as a hybrid between `std::array` and `std::vector`. The interface follows
|
||||
* that of `std::vector` - see standard C++ documentation.
|
||||
*/
|
||||
template<typename T, std::size_t N>
|
||||
class ArrayVector
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
ArrayVector() = default;
|
||||
|
||||
ArrayVector(const ArrayVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{other.m_size}
|
||||
{
|
||||
std::uninitialized_copy(other.begin(), other.end(), begin());
|
||||
}
|
||||
|
||||
ArrayVector(ArrayVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>) :
|
||||
m_size{other.m_size}
|
||||
{
|
||||
std::uninitialized_move(other.begin(), other.end(), begin());
|
||||
other.clear();
|
||||
}
|
||||
|
||||
ArrayVector(size_type count, const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{count}
|
||||
{
|
||||
assert(count <= N);
|
||||
std::uninitialized_fill_n(begin(), count, value);
|
||||
}
|
||||
|
||||
explicit ArrayVector(size_type count) noexcept(std::is_nothrow_default_constructible_v<T>) :
|
||||
m_size{count}
|
||||
{
|
||||
assert(count <= N);
|
||||
std::uninitialized_value_construct_n(begin(), count);
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
ArrayVector(It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
const auto end = std::uninitialized_copy(first, last, begin());
|
||||
m_size = end - begin();
|
||||
assert(m_size <= N);
|
||||
}
|
||||
|
||||
ArrayVector(std::initializer_list<T> il) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{il.size()}
|
||||
{
|
||||
assert(il.size() <= N);
|
||||
std::uninitialized_copy(il.begin(), il.end(), begin());
|
||||
}
|
||||
|
||||
~ArrayVector() { std::destroy(begin(), end()); }
|
||||
|
||||
ArrayVector& operator=(const ArrayVector& other)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
if (this != &other) {
|
||||
const auto toAssign = std::min(other.size(), size());
|
||||
const auto assignedFromEnd = other.begin() + toAssign;
|
||||
const auto assignedToEnd = std::copy(other.begin(), other.begin() + toAssign, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_copy(assignedFromEnd, other.end(), end());
|
||||
m_size = other.size();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ArrayVector& operator=(ArrayVector&& other)
|
||||
noexcept(std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
if (this != &other) {
|
||||
const auto toAssign = std::min(other.size(), size());
|
||||
const auto assignedFromEnd = other.begin() + toAssign;
|
||||
const auto assignedToEnd = std::move(other.begin(), other.begin() + toAssign, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_move(assignedFromEnd, other.end(), end());
|
||||
m_size = other.size();
|
||||
other.clear();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ArrayVector& operator=(std::initializer_list<T> il)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
assert(il.size() <= N);
|
||||
const auto toAssign = std::min(il.size(), size());
|
||||
const auto assignedFromEnd = il.begin() + toAssign;
|
||||
const auto assignedToEnd = std::copy(il.begin(), assignedFromEnd, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_copy(assignedFromEnd, il.end(), end());
|
||||
m_size = il.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void assign(size_type count, const T& value)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
assert(count <= N);
|
||||
const auto temp = value;
|
||||
const auto toAssign = std::min(count, size());
|
||||
const auto toConstruct = count - toAssign;
|
||||
const auto assignedToEnd = std::fill_n(begin(), toAssign, temp);
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_fill_n(assignedToEnd, toConstruct, temp);
|
||||
m_size = count;
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
void assign(It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
auto pos = begin();
|
||||
for (; first != last && pos != end(); ++pos, ++first) {
|
||||
*pos = *first;
|
||||
}
|
||||
std::destroy(pos, end());
|
||||
pos = std::uninitialized_copy(first, last, pos);
|
||||
m_size = pos - begin();
|
||||
assert(m_size <= N);
|
||||
}
|
||||
|
||||
reference at(size_type index)
|
||||
{
|
||||
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
const_reference at(size_type index) const
|
||||
{
|
||||
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
reference operator[](size_type index) noexcept
|
||||
{
|
||||
assert(index < m_size);
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
const_reference operator[](size_type index) const noexcept
|
||||
{
|
||||
assert(index < m_size);
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
reference front() noexcept { return operator[](0); }
|
||||
const_reference front() const noexcept { return operator[](0); }
|
||||
|
||||
reference back() noexcept { return operator[](m_size - 1); }
|
||||
const_reference back() const noexcept { return operator[](m_size - 1); }
|
||||
|
||||
pointer data() noexcept { return *std::launder(reinterpret_cast<T(*)[N]>(m_data)); }
|
||||
const_pointer data() const noexcept { return *std::launder(reinterpret_cast<const T(*)[N]>(m_data)); }
|
||||
|
||||
iterator begin() noexcept { return data(); }
|
||||
const_iterator begin() const noexcept { return data(); }
|
||||
const_iterator cbegin() const noexcept { return data(); }
|
||||
|
||||
iterator end() noexcept { return data() + m_size; }
|
||||
const_iterator end() const noexcept { return data() + m_size; }
|
||||
const_iterator cend() const noexcept { return data() + m_size; }
|
||||
|
||||
reverse_iterator rbegin() noexcept { return std::reverse_iterator{end()}; }
|
||||
const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator{end()}; }
|
||||
const_reverse_iterator crbegin() const noexcept { return std::reverse_iterator{cend()}; }
|
||||
|
||||
reverse_iterator rend() noexcept { return std::reverse_iterator{begin()}; }
|
||||
const_reverse_iterator rend() const noexcept { return std::reverse_iterator{begin()}; }
|
||||
const_reverse_iterator crend() const noexcept { return std::reverse_iterator{cbegin()}; }
|
||||
|
||||
bool empty() const noexcept { return m_size == 0; }
|
||||
bool full() const noexcept { return m_size == N; }
|
||||
size_type size() const noexcept { return m_size; }
|
||||
size_type max_size() const noexcept { return N; }
|
||||
size_type capacity() const noexcept { return N; }
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
std::destroy(begin(), end());
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); }
|
||||
iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); }
|
||||
|
||||
iterator insert(const_iterator pos, size_type count, const T& value)
|
||||
{
|
||||
assert(m_size + count <= N);
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
const auto newEnd = std::uninitialized_fill_n(end(), count, value);
|
||||
std::rotate(mutPos, end(), newEnd);
|
||||
m_size += count;
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
iterator insert(const_iterator pos, It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
const auto newEnd = std::uninitialized_copy(first, last, end());
|
||||
std::rotate(mutPos, end(), newEnd);
|
||||
m_size = newEnd - begin();
|
||||
assert(m_size <= N);
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
iterator insert(const_iterator pos, std::initializer_list<T> il) { return insert(pos, il.begin(), il.end()); }
|
||||
|
||||
template<typename... Args>
|
||||
iterator emplace(const_iterator pos, Args&&... args)
|
||||
{
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
emplace_back(std::forward<Args>(args)...);
|
||||
std::rotate(mutPos, end() - 1, end());
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
|
||||
iterator erase(const_iterator first, const_iterator last)
|
||||
{
|
||||
assert(cbegin() <= first && first <= last && last <= cend());
|
||||
const auto mutFirst = begin() + (first - cbegin());
|
||||
const auto mutLast = begin() + (last - cbegin());
|
||||
const auto newEnd = std::move(mutLast, end(), mutFirst);
|
||||
std::destroy(newEnd, end());
|
||||
m_size = newEnd - begin();
|
||||
return mutFirst;
|
||||
}
|
||||
|
||||
void push_back(const T& value) { emplace_back(value); }
|
||||
void push_back(T&& value) { emplace_back(std::move(value)); }
|
||||
|
||||
template<typename... Args>
|
||||
reference emplace_back(Args&&... args)
|
||||
{
|
||||
assert(!full());
|
||||
// TODO C++20: Use std::construct_at
|
||||
const auto result = new(static_cast<void*>(end())) T(std::forward<Args>(args)...);
|
||||
++m_size;
|
||||
return *result;
|
||||
}
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
assert(!empty());
|
||||
--m_size;
|
||||
std::destroy_at(end());
|
||||
}
|
||||
|
||||
void resize(size_type size)
|
||||
{
|
||||
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
||||
if (size < m_size) {
|
||||
std::destroy(begin() + size, end());
|
||||
} else {
|
||||
std::uninitialized_value_construct(end(), begin() + size);
|
||||
}
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void resize(size_type size, const value_type& value)
|
||||
{
|
||||
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
||||
if (size < m_size) {
|
||||
std::destroy(begin() + size, end());
|
||||
} else {
|
||||
std::uninitialized_fill(end(), begin() + size, value);
|
||||
}
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void swap(ArrayVector& other)
|
||||
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
using std::swap;
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
friend void swap(ArrayVector& a, ArrayVector& b)
|
||||
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
const auto toSwap = std::min(a.size(), b.size());
|
||||
const auto aSwapEnd = a.begin() + toSwap;
|
||||
const auto bSwapEnd = b.begin() + toSwap;
|
||||
std::swap_ranges(a.begin(), aSwapEnd, b.begin());
|
||||
std::uninitialized_move(aSwapEnd, a.end(), bSwapEnd);
|
||||
std::uninitialized_move(bSwapEnd, b.end(), aSwapEnd);
|
||||
std::destroy(aSwapEnd, a.end());
|
||||
std::destroy(bSwapEnd, b.end());
|
||||
std::swap(a.m_size, b.m_size);
|
||||
}
|
||||
|
||||
// TODO C++20: Replace with operator<=>
|
||||
friend bool operator<(const ArrayVector& l, const ArrayVector& r)
|
||||
{
|
||||
return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());
|
||||
}
|
||||
friend bool operator<=(const ArrayVector& l, const ArrayVector& r) { return !(r < l); }
|
||||
friend bool operator>(const ArrayVector& l, const ArrayVector& r) { return r < l; }
|
||||
friend bool operator>=(const ArrayVector& l, const ArrayVector& r) { return !(l < r); }
|
||||
|
||||
friend bool operator==(const ArrayVector& l, const ArrayVector& r)
|
||||
{
|
||||
return std::equal(l.begin(), l.end(), r.begin(), r.end());
|
||||
}
|
||||
// TODO C++20: Remove
|
||||
friend bool operator!=(const ArrayVector& l, const ArrayVector& r) { return !(l == r); }
|
||||
|
||||
private:
|
||||
alignas(T) std::byte m_data[std::max(N * sizeof(T), std::size_t{1})]; // Intentionally a raw array
|
||||
size_type m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_ARRAY_VECTOR_H
|
||||
@@ -108,6 +108,9 @@ public:
|
||||
return m_unpitchedFrequency;
|
||||
}
|
||||
|
||||
//! Get the current per-note detuning for this note
|
||||
float currentDetuning() const { return m_baseDetuning->value(); }
|
||||
|
||||
/*! Renders one chunk using the attached instrument into the buffer */
|
||||
void play( sampleFrame* buffer ) override;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <QDomElement>
|
||||
#include <QLabel>
|
||||
|
||||
#include "ArrayVector.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "FileDialog.h"
|
||||
@@ -71,17 +72,47 @@ Plugin::Descriptor PLUGIN_EXPORT sf2player_plugin_descriptor =
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-owning reference to a single FluidSynth voice, for tracking whether the
|
||||
* referenced voice is still the same voice that was passed to the constructor.
|
||||
*/
|
||||
class FluidVoice
|
||||
{
|
||||
public:
|
||||
//! Create a reference to the voice currently pointed at by `voice`.
|
||||
explicit FluidVoice(fluid_voice_t* voice) :
|
||||
m_voice{voice},
|
||||
m_id{fluid_voice_get_id(voice)}
|
||||
{ }
|
||||
|
||||
//! Get a pointer to the referenced voice.
|
||||
fluid_voice_t* get() const noexcept { return m_voice; }
|
||||
|
||||
//! Test whether this object still refers to the original voice.
|
||||
bool isValid() const
|
||||
{
|
||||
return fluid_voice_get_id(m_voice) == m_id && fluid_voice_is_playing(m_voice);
|
||||
}
|
||||
|
||||
private:
|
||||
fluid_voice_t* m_voice;
|
||||
unsigned int m_id;
|
||||
};
|
||||
|
||||
struct Sf2PluginData
|
||||
{
|
||||
int midiNote;
|
||||
int lastPanning;
|
||||
float lastVelocity;
|
||||
fluid_voice_t * fluidVoice;
|
||||
// The soundfonts I checked used at most two voices per note, so space for
|
||||
// four should be safe. This may need to be increased if a soundfont with
|
||||
// more voices per note is found.
|
||||
ArrayVector<FluidVoice, 4> fluidVoices;
|
||||
bool isNew;
|
||||
f_cnt_t offset;
|
||||
bool noteOffSent;
|
||||
} ;
|
||||
panning_t panning;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -681,10 +712,10 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * )
|
||||
pluginData->midiNote = midiNote;
|
||||
pluginData->lastPanning = 0;
|
||||
pluginData->lastVelocity = _n->midiVelocity( baseVelocity );
|
||||
pluginData->fluidVoice = nullptr;
|
||||
pluginData->isNew = true;
|
||||
pluginData->offset = _n->offset();
|
||||
pluginData->noteOffSent = false;
|
||||
pluginData->panning = _n->getPanning();
|
||||
|
||||
_n->m_pluginData = pluginData;
|
||||
|
||||
@@ -703,6 +734,17 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * )
|
||||
m_playingNotes.append( _n );
|
||||
m_playingNotesMutex.unlock();
|
||||
}
|
||||
|
||||
// Update the pitch of all the voices
|
||||
if (const auto data = static_cast<Sf2PluginData*>(_n->m_pluginData)) {
|
||||
const auto detuning = _n->currentDetuning();
|
||||
for (const auto& voice : data->fluidVoices) {
|
||||
if (voice.isValid()) {
|
||||
fluid_voice_gen_set(voice.get(), GEN_COARSETUNE, detuning);
|
||||
fluid_voice_update_param(voice.get(), GEN_COARSETUNE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -715,35 +757,47 @@ void Sf2Instrument::noteOn( Sf2PluginData * n )
|
||||
const int poly = fluid_synth_get_polyphony( m_synth );
|
||||
#ifndef _MSC_VER
|
||||
fluid_voice_t* voices[poly];
|
||||
unsigned int id[poly];
|
||||
#else
|
||||
const auto voices = static_cast<fluid_voice_t**>(_alloca(poly * sizeof(fluid_voice_t*)));
|
||||
const auto id = static_cast<unsigned int*>(_alloca(poly * sizeof(unsigned int)));
|
||||
#endif
|
||||
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
|
||||
for( int i = 0; i < poly; ++i )
|
||||
{
|
||||
id[i] = 0;
|
||||
}
|
||||
for( int i = 0; i < poly && voices[i]; ++i )
|
||||
{
|
||||
id[i] = fluid_voice_get_id( voices[i] );
|
||||
}
|
||||
|
||||
fluid_synth_noteon( m_synth, m_channel, n->midiNote, n->lastVelocity );
|
||||
|
||||
// get new voice and save it
|
||||
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
|
||||
for( int i = 0; i < poly && voices[i]; ++i )
|
||||
// Get any new voices and store them in the plugin data
|
||||
fluid_synth_get_voicelist(m_synth, voices, poly, -1);
|
||||
for (int i = 0; i < poly && voices[i] && !n->fluidVoices.full(); ++i)
|
||||
{
|
||||
const unsigned int newID = fluid_voice_get_id( voices[i] );
|
||||
if( id[i] != newID || newID == 0 )
|
||||
{
|
||||
n->fluidVoice = voices[i];
|
||||
break;
|
||||
const auto voice = voices[i];
|
||||
// FluidSynth stops voices with the same channel and pitch upon note-on,
|
||||
// so voices with the current channel and pitch are playing this note.
|
||||
if (fluid_voice_get_channel(voice) == m_channel
|
||||
&& fluid_voice_get_key(voice) == n->midiNote
|
||||
&& fluid_voice_is_on(voice)
|
||||
) {
|
||||
n->fluidVoices.emplace_back(voices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#if FLUIDSYNTH_VERSION_MAJOR >= 2
|
||||
// Smallest balance value that results in full attenuation of one channel.
|
||||
// Corresponds to internal FluidSynth macro `FLUID_CB_AMP_SIZE`.
|
||||
constexpr static auto maxBalance = 1441.f;
|
||||
// Convert panning from linear to exponential for FluidSynth
|
||||
const auto panning = n->panning;
|
||||
const auto factor = 1.f - std::abs(panning) / static_cast<float>(PanningRight);
|
||||
const auto balance = std::copysign(
|
||||
factor <= 0 ? maxBalance : std::min(-200.f * std::log10(factor), maxBalance),
|
||||
panning
|
||||
);
|
||||
// Set note panning on all the voices
|
||||
for (const auto& voice : n->fluidVoices) {
|
||||
if (voice.isValid()) {
|
||||
fluid_voice_gen_set(voice.get(), GEN_CUSTOM_BALANCE, balance);
|
||||
fluid_voice_update_param(voice.get(), GEN_CUSTOM_BALANCE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_synthMutex.unlock();
|
||||
|
||||
m_notesRunningMutex.lock();
|
||||
@@ -859,6 +913,7 @@ void Sf2Instrument::play( sampleFrame * _working_buffer )
|
||||
void Sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf )
|
||||
{
|
||||
m_synthMutex.lock();
|
||||
fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect
|
||||
if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() &&
|
||||
m_srcState != nullptr )
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ ADD_EXECUTABLE(tests
|
||||
QTestSuite.cpp
|
||||
$<TARGET_OBJECTS:lmmsobjs>
|
||||
|
||||
src/core/ArrayVectorTest.cpp
|
||||
src/core/AutomatableModelTest.cpp
|
||||
src/core/ProjectVersionTest.cpp
|
||||
src/core/RelativePathsTest.cpp
|
||||
|
||||
@@ -16,7 +16,7 @@ int main(int argc, char* argv[])
|
||||
int failed = 0;
|
||||
for (QTestSuite*& suite : QTestSuite::suites())
|
||||
{
|
||||
failed += QTest::qExec(suite, argc, argv);
|
||||
if (QTest::qExec(suite, argc, argv) != 0) { ++failed; }
|
||||
}
|
||||
qDebug() << "<<" << failed << "out of"<<numsuites<<"test suites failed.";
|
||||
return failed;
|
||||
|
||||
831
tests/src/core/ArrayVectorTest.cpp
Normal file
831
tests/src/core/ArrayVectorTest.cpp
Normal file
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
* ArrayVectorTest.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 "ArrayVector.h"
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
|
||||
#include "QTestSuite.h"
|
||||
|
||||
using lmms::ArrayVector;
|
||||
|
||||
struct ShouldNotConstruct
|
||||
{
|
||||
ShouldNotConstruct() { QFAIL("should not construct"); }
|
||||
};
|
||||
|
||||
struct ShouldNotDestruct
|
||||
{
|
||||
~ShouldNotDestruct() { QFAIL("should not destruct"); }
|
||||
};
|
||||
|
||||
enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign };
|
||||
|
||||
struct Constructible
|
||||
{
|
||||
Constructible() : construction{Construction::Default} {}
|
||||
Constructible(const Constructible&) : construction{Construction::Copy} {}
|
||||
Constructible(Constructible&&) : construction{Construction::Move} {}
|
||||
Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; }
|
||||
Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; }
|
||||
Construction construction;
|
||||
};
|
||||
|
||||
struct DestructorCheck
|
||||
{
|
||||
~DestructorCheck() { *destructed = true; }
|
||||
bool* destructed;
|
||||
};
|
||||
|
||||
class ArrayVectorTest : QTestSuite
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void defaultConstructorTest()
|
||||
{
|
||||
// Ensure no elements are constructed
|
||||
const auto v = ArrayVector<ShouldNotConstruct, 1>();
|
||||
// Ensure the container is empty
|
||||
QVERIFY(v.empty());
|
||||
}
|
||||
|
||||
void copyConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 1>{{}};
|
||||
const auto copy = v;
|
||||
for (const auto& element : copy) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto copy = v;
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void moveConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure all elements are move constructed
|
||||
auto v = ArrayVector<Constructible, 1>{{}};
|
||||
const auto moved = std::move(v);
|
||||
for (const auto& element : moved) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto moved = std::move(v);
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end()));
|
||||
// Move construction should leave the source empty
|
||||
QVERIFY(v.empty());
|
||||
}
|
||||
}
|
||||
|
||||
void fillValueConstructorTest()
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 2>(1, {});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
// Ensure the container has the correct size
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
}
|
||||
|
||||
void fillDefaultConstructorTest()
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 2>(1);
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Default);
|
||||
}
|
||||
// Ensure the container has the correct size
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
}
|
||||
|
||||
void rangeConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure the elements are copy constructed from normal iterators
|
||||
const auto data = std::array{Constructible{}};
|
||||
const auto v = ArrayVector<Constructible, 1>(data.begin(), data.end());
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure the elements are move constructed from move iterators
|
||||
auto data = std::array{Constructible{}};
|
||||
const auto v = ArrayVector<Constructible, 1>(
|
||||
std::move_iterator{data.begin()}, std::move_iterator{data.end()});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto data = std::array{1, 2, 3};
|
||||
const auto v = ArrayVector<int, 5>(data.begin(), data.end());
|
||||
QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void initializerListConstructorTest()
|
||||
{
|
||||
// Ensure the container is constructed with the correct data
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
void destructorTest()
|
||||
{
|
||||
{
|
||||
// Should not call destructors for space without elements
|
||||
const auto v = ArrayVector<ShouldNotDestruct, 1>{};
|
||||
}
|
||||
{
|
||||
// Should call destructors for all elements
|
||||
auto destructed = false;
|
||||
{
|
||||
const auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
}
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
}
|
||||
|
||||
void copyAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Self-assignment should not change the contents
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto oldValue = v;
|
||||
v = v;
|
||||
QCOMPARE(v, oldValue);
|
||||
}
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
const auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>(5);
|
||||
dst = src;
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
const auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>{};
|
||||
dst = src;
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto src = ArrayVector<int, 5>{1, 2, 3};
|
||||
auto dst = ArrayVector<int, 5>{};
|
||||
dst = src;
|
||||
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void moveAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Self-assignment should not change the contents
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto oldValue = v;
|
||||
v = std::move(v);
|
||||
QCOMPARE(v, oldValue);
|
||||
}
|
||||
{
|
||||
// Assignment to a larger container should move assign
|
||||
auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>(5);
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::MoveAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should move construct
|
||||
auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>{};
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto src = ArrayVector<int, 5>{1, 2, 3};
|
||||
auto dst = ArrayVector<int, 5>{};
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void initializerListAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
auto v = ArrayVector<Constructible, 2>(2);
|
||||
v = {Constructible{}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v = {Constructible{}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v = {1, 2, 3};
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void fillValueAssignTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
auto v = ArrayVector<Constructible, 5>(5);
|
||||
v.assign(3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
auto v = ArrayVector<Constructible, 5>{};
|
||||
v.assign(3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v.assign(3, 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 1, 1}));
|
||||
}
|
||||
}
|
||||
|
||||
void rangeAssignTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>(2);
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
const auto data = std::array{1, 2, 3};
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void atTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.at(1), 2);
|
||||
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.at(1), 2);
|
||||
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
|
||||
}
|
||||
}
|
||||
|
||||
void subscriptTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v[1], 2);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v[1], 2);
|
||||
}
|
||||
}
|
||||
|
||||
void frontTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.front(), 1);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.front(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void backTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.back(), 3);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.back(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
void dataTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.data(), &v.front());
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.data(), &v.front());
|
||||
}
|
||||
}
|
||||
|
||||
void beginEndTest()
|
||||
{
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end()));
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void rbeginRendTest()
|
||||
{
|
||||
const auto expected = std::array{3, 2, 1};
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
|
||||
QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end()));
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void emptyFullSizeMaxCapacityTest()
|
||||
{
|
||||
auto v = ArrayVector<int, 2>{};
|
||||
QVERIFY(v.empty());
|
||||
QVERIFY(!v.full());
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
v.push_back(1);
|
||||
QVERIFY(!v.empty());
|
||||
QVERIFY(!v.full());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
v.push_back(2);
|
||||
QVERIFY(!v.empty());
|
||||
QVERIFY(v.full());
|
||||
QCOMPARE(v.size(), std::size_t{2});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
auto empty = ArrayVector<int, 0>{};
|
||||
QVERIFY(empty.empty());
|
||||
QVERIFY(empty.full());
|
||||
QCOMPARE(empty.size(), std::size_t{0});
|
||||
QCOMPARE(empty.max_size(), std::size_t{0});
|
||||
QCOMPARE(empty.capacity(), std::size_t{0});
|
||||
}
|
||||
|
||||
void insertValueTest()
|
||||
{
|
||||
{
|
||||
// Copy
|
||||
const auto data = Constructible{};
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.insert(v.cbegin(), data);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Move
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.insert(v.cbegin(), Constructible{});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Move);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (copy)
|
||||
const auto data = 1;
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.insert(v.cbegin(), data);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.insert(v.cbegin(), 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertFillValueTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
auto v = ArrayVector<Constructible, 5>{};
|
||||
v.insert(v.cbegin(), 3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
auto v = ArrayVector<int, 5>{1, 3};
|
||||
v.insert(v.cbegin() + 1, 3, 2);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 2, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertRangeTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.insert(v.cbegin(), data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
const auto data = std::array{2, 3};
|
||||
auto v = ArrayVector<int, 5>{1, 4};
|
||||
v.insert(v.cbegin() + 1, data.begin(), data.end());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertInitializerListTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.insert(v.cbegin(), {Constructible{}});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{1, 4};
|
||||
v.insert(v.cbegin() + 1, {2, 3});
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
|
||||
}
|
||||
}
|
||||
|
||||
void emplaceTest()
|
||||
{
|
||||
{
|
||||
// Ensure the value is constructed in-place
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.emplace(v.cbegin());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.emplace(v.cbegin(), 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void eraseTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.erase(v.cbegin());
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{10, 1, 2, 3};
|
||||
v.erase(v.cbegin());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void eraseRangeTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.erase(v.cbegin(), v.cend());
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 20, 21, 2, 3};
|
||||
v.erase(v.cbegin() + 1, v.cbegin() + 3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void pushBackTest()
|
||||
{
|
||||
{
|
||||
// Copy
|
||||
const auto data = Constructible{};
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.push_back(data);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Move
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.push_back({});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Move);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (copy)
|
||||
const auto data = 3;
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.push_back(data);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.push_back(3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void emplaceBackTest()
|
||||
{
|
||||
{
|
||||
// Ensure the value is constructed in-place
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.emplace_back();
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.emplace_back(3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void popBackTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.pop_back();
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
v.pop_back();
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2}));
|
||||
}
|
||||
}
|
||||
|
||||
void resizeDefaultTest()
|
||||
{
|
||||
{
|
||||
// Smaller
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
v.resize(0);
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Bigger
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
v.resize(1);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Too big
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
|
||||
}
|
||||
}
|
||||
|
||||
void resizeValueTest()
|
||||
{
|
||||
{
|
||||
// Smaller
|
||||
auto dummy = false;
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
v.resize(0, {&dummy});
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Bigger
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
v.resize(1, {});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Too big
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
v.resize(1, 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 1>{1}));
|
||||
}
|
||||
}
|
||||
|
||||
void clearTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.clear();
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
v.clear();
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{}));
|
||||
}
|
||||
}
|
||||
|
||||
void memberSwapTest()
|
||||
{
|
||||
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
|
||||
auto b = ArrayVector<int, 5>{2, 4, 6};
|
||||
|
||||
const auto aOriginal = a;
|
||||
const auto bOriginal = b;
|
||||
|
||||
a.swap(b);
|
||||
|
||||
QCOMPARE(a, bOriginal);
|
||||
QCOMPARE(b, aOriginal);
|
||||
}
|
||||
|
||||
void freeSwapTest()
|
||||
{
|
||||
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
|
||||
auto b = ArrayVector<int, 5>{2, 4, 6};
|
||||
|
||||
const auto aOriginal = a;
|
||||
const auto bOriginal = b;
|
||||
|
||||
swap(a, b);
|
||||
|
||||
QCOMPARE(a, bOriginal);
|
||||
QCOMPARE(b, aOriginal);
|
||||
}
|
||||
|
||||
void comparisonTest()
|
||||
{
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto l = ArrayVector<int, 5>{1, 2, 2};
|
||||
const auto e = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto g = ArrayVector<int, 5>{1, 3, 3};
|
||||
|
||||
QVERIFY(l < v);
|
||||
QVERIFY(!(e < v));
|
||||
QVERIFY(!(g < v));
|
||||
|
||||
QVERIFY(l <= v);
|
||||
QVERIFY(e <= v);
|
||||
QVERIFY(!(g <= v));
|
||||
|
||||
QVERIFY(!(l > v));
|
||||
QVERIFY(!(e > v));
|
||||
QVERIFY(g > v);
|
||||
|
||||
QVERIFY(!(l >= v));
|
||||
QVERIFY(e >= v);
|
||||
QVERIFY(g >= v);
|
||||
|
||||
QVERIFY(!(l == v));
|
||||
QVERIFY(e == v);
|
||||
QVERIFY(!(g == v));
|
||||
|
||||
QVERIFY(l != v);
|
||||
QVERIFY(!(e != v));
|
||||
QVERIFY(g != v);
|
||||
}
|
||||
} ArrayVectorTests;
|
||||
|
||||
#include "ArrayVectorTest.moc"
|
||||
Reference in New Issue
Block a user