Merge remote-tracking branch 'upstream/master' into refactor-samplebuffer

This commit is contained in:
sakertooth
2023-08-31 11:13:36 -04:00
19 changed files with 1419 additions and 61 deletions

View File

@@ -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")
@@ -605,7 +609,9 @@ else()
set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo")
endif()
if(STRIP)
set(STRIP_COMMAND "$<IF:$<TARGET:Debug,RelWithDebInfo>,${NOOP_COMMAND},${STRIP}>")
# TODO CMake 3.19: Now that CONFIG generator expressions support testing for
# multiple configurations, combine the OR into a single CONFIG expression.
set(STRIP_COMMAND "$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,${NOOP_COMMAND},${STRIP}>")
else()
set(STRIP_COMMAND "${NOOP_COMMAND}")
endif()

388
include/ArrayVector.h Normal file
View 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

View File

@@ -25,6 +25,7 @@
#ifndef LMMS_GUI_INSTRUMENT_TRACK_VIEW_H
#define LMMS_GUI_INSTRUMENT_TRACK_VIEW_H
#include "MixerLineLcdSpinBox.h"
#include "TrackView.h"
#include "InstrumentTrack.h"
@@ -72,6 +73,7 @@ public:
protected:
void modelChanged() override;
void dragEnterEvent( QDragEnterEvent * _dee ) override;
void dropEvent( QDropEvent * _de ) override;
@@ -97,6 +99,7 @@ private:
// widgets in track-settings-widget
TrackLabelButton * m_tlb;
MixerLineLcdSpinBox* m_mixerChannelNumber;
Knob * m_volumeKnob;
Knob * m_panningKnob;
FadeButton * m_activityIndicator;

View File

@@ -144,6 +144,9 @@ protected:
void wheelEvent( QWheelEvent * _me ) override;
void changeEvent(QEvent * ev) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
virtual float getValue( const QPoint & _p );
private slots:
@@ -160,6 +163,7 @@ private:
float _innerRadius = 1) const;
void drawKnob( QPainter * _p );
void showTextFloat(int msecBeforeDisplay, int msecDisplayTime);
void setPosition( const QPoint & _p );
bool updateAngle();

View File

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

View File

@@ -26,6 +26,7 @@
#define LMMS_GUI_SAMPLE_TRACK_VIEW_H
#include "MixerLineLcdSpinBox.h"
#include "TrackView.h"
namespace lmms
@@ -90,6 +91,7 @@ private slots:
private:
SampleTrackWindow * m_window;
MixerLineLcdSpinBox* m_mixerChannelNumber;
Knob * m_volumeKnob;
Knob * m_panningKnob;
FadeButton * m_activityIndicator;

View File

@@ -31,6 +31,7 @@
#include "lmms_export.h"
class QLabel;
class QTimer;
namespace lmms::gui
{
@@ -44,6 +45,8 @@ public:
void setText(const QString & text);
void showWithDelay(int msecBeforeDisplay, int msecDisplayTime);
void setVisibilityTimeOut(int msecs);
void moveGlobal(QWidget * w, const QPoint & offset)
@@ -51,11 +54,14 @@ public:
move(w->mapToGlobal(QPoint(0, 0)) + offset);
}
void hide();
private:
QLabel * m_textLabel;
QTimer * m_showTimer;
QTimer * m_hideTimer;
};
} // namespace lmms::gui
#endif

View File

@@ -48,11 +48,11 @@ class FadeButton;
class TrackContainerView;
const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224;
const int DEFAULT_SETTINGS_WIDGET_WIDTH = 256;
const int TRACK_OP_WIDTH = 78;
// This shaves 150-ish pixels off track buttons,
// ruled from config: ui.compacttrackbuttons
const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96;
const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 128;
const int TRACK_OP_WIDTH_COMPACT = 62;

View File

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

View File

@@ -1671,6 +1671,8 @@ void DataFile::upgrade_extendedNoteRange()
{
auto root = documentElement();
UpgradeExtendedNoteRange upgradeExtendedNoteRange(root);
upgradeExtendedNoteRange.upgrade();
}

View File

@@ -193,6 +193,7 @@ static void fixTrack(QDomElement & track, std::set<unsigned int> & automatedBase
for (int i = 0; i < subTracks.size(); ++i)
{
QDomElement subTrack = subTracks.item(i).toElement();
assert (static_cast<Track::Type>(subTrack.attribute("type").toInt()) != Track::Type::Pattern);
fixTrack(subTrack, automatedBaseNoteIds);
}
}

View File

@@ -62,16 +62,16 @@ void SideBarWidget::paintEvent( QPaintEvent * )
QFont f = p.font();
f.setBold( true );
f.setUnderline( true );
f.setUnderline(false);
f.setPointSize( f.pointSize() + 2 );
p.setFont( f );
p.setPen( palette().highlightedText().color() );
const int tx = m_icon.width()+4;
const int tx = m_icon.width() + 8;
QFontMetrics metrics( f );
const int ty = metrics.ascent();
const int ty = (metrics.ascent() + m_icon.height()) / 2;
p.drawText( tx, ty, m_title );
p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) );

View File

@@ -63,7 +63,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
m_tlb = new TrackLabelButton( this, getTrackSettingsWidget() );
m_tlb->setCheckable( true );
m_tlb->setIcon( embed::getIconPixmap( "instrument_track" ) );
m_tlb->move( 3, 1 );
m_tlb->show();
connect( m_tlb, SIGNAL(toggled(bool)),
@@ -75,24 +74,14 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
connect(ConfigManager::inst(), SIGNAL(valueChanged(QString,QString,QString)),
this, SLOT(handleConfigChange(QString,QString,QString)));
// creation of widgets for track-settings-widget
int widgetWidth;
if( ConfigManager::inst()->value( "ui",
"compacttrackbuttons" ).toInt() )
{
widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT;
}
else
{
widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH;
}
m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this);
m_mixerChannelNumber->show();
m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(),
tr( "Volume" ) );
m_volumeKnob->setVolumeKnob( true );
m_volumeKnob->setModel( &_it->m_volumeModel );
m_volumeKnob->setHintText( tr( "Volume:" ), "%" );
m_volumeKnob->move( widgetWidth-2*24, 2 );
m_volumeKnob->setLabel( tr( "VOL" ) );
m_volumeKnob->show();
@@ -100,7 +89,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
tr( "Panning" ) );
m_panningKnob->setModel( &_it->m_panningModel );
m_panningKnob->setHintText(tr("Panning:"), "%");
m_panningKnob->move( widgetWidth-24, 2 );
m_panningKnob->setLabel( tr( "PAN" ) );
m_panningKnob->show();
@@ -151,9 +139,18 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
QApplication::palette().color( QPalette::Active,
QPalette::BrightText).darker(),
getTrackSettingsWidget() );
m_activityIndicator->setGeometry(
widgetWidth-2*24-11, 2, 8, 28 );
m_activityIndicator->setFixedSize(8, 28);
m_activityIndicator->show();
auto layout = new QHBoxLayout(getTrackSettingsWidget());
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_tlb);
layout->addWidget(m_mixerChannelNumber);
layout->addWidget(m_activityIndicator);
layout->addWidget(m_volumeKnob);
layout->addWidget(m_panningKnob);
connect( m_activityIndicator, SIGNAL(pressed()),
this, SLOT(activityIndicatorPressed()));
connect( m_activityIndicator, SIGNAL(released()),
@@ -268,6 +265,13 @@ void InstrumentTrackView::handleConfigChange(QString cls, QString attr, QString
}
}
void InstrumentTrackView::modelChanged()
{
TrackView::modelChanged();
auto st = castModel<InstrumentTrack>();
m_mixerChannelNumber->setModel(&st->m_mixerChannelModel);
}
void InstrumentTrackView::dragEnterEvent( QDragEnterEvent * _dee )
{
InstrumentTrackWindow::dragEnterEventGeneric( _dee );

View File

@@ -56,20 +56,17 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
connect(m_tlb, SIGNAL(clicked(bool)),
this, SLOT(showEffects()));
m_tlb->setIcon(embed::getIconPixmap("sample_track"));
m_tlb->move(3, 1);
m_tlb->show();
m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this);
m_mixerChannelNumber->show();
m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(),
tr( "Track volume" ) );
m_volumeKnob->setVolumeKnob( true );
m_volumeKnob->setModel( &_t->m_volumeModel );
m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" );
int settingsWidgetWidth = ConfigManager::inst()->
value( "ui", "compacttrackbuttons" ).toInt()
? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT
: DEFAULT_SETTINGS_WIDGET_WIDTH;
m_volumeKnob->move( settingsWidgetWidth - 2 * 24, 2 );
m_volumeKnob->setLabel( tr( "VOL" ) );
m_volumeKnob->show();
@@ -77,7 +74,6 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
tr( "Panning" ) );
m_panningKnob->setModel( &_t->m_panningModel );
m_panningKnob->setHintText( tr( "Panning:" ), "%" );
m_panningKnob->move( settingsWidgetWidth - 24, 2 );
m_panningKnob->setLabel( tr( "PAN" ) );
m_panningKnob->show();
@@ -87,8 +83,18 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
QApplication::palette().color(QPalette::Active, QPalette::BrightText).darker(),
getTrackSettingsWidget()
);
m_activityIndicator->setGeometry(settingsWidgetWidth - 2 * 24 - 11, 2, 8, 28);
m_activityIndicator->setFixedSize(8, 28);
m_activityIndicator->show();
auto layout = new QHBoxLayout(getTrackSettingsWidget());
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_tlb);
layout->addWidget(m_mixerChannelNumber);
layout->addWidget(m_activityIndicator);
layout->addWidget(m_volumeKnob);
layout->addWidget(m_panningKnob);
connect(_t, SIGNAL(playingChanged()), this, SLOT(updateIndicator()));
setModel( _t );
@@ -170,6 +176,7 @@ void SampleTrackView::modelChanged()
{
auto st = castModel<SampleTrack>();
m_volumeKnob->setModel(&st->m_volumeModel);
m_mixerChannelNumber->setModel(&st->m_mixerChannelModel);
TrackView::modelChanged();
}

View File

@@ -22,6 +22,8 @@
*
*/
#include "Knob.h"
#include <memory>
#include <QApplication>
#include <QFontMetrics>
@@ -34,7 +36,6 @@
#endif
#include "lmms_math.h"
#include "Knob.h"
#include "CaptionMenu.h"
#include "ConfigManager.h"
#include "ControllerConnection.h"
@@ -48,7 +49,6 @@
#include "SimpleTextFloat.h"
#include "StringPairDrag.h"
namespace lmms::gui
{
@@ -484,6 +484,13 @@ void Knob::drawKnob( QPainter * _p )
_p->drawImage( 0, 0, m_cache );
}
void Knob::showTextFloat(int msecBeforeDisplay, int msecDisplayTime)
{
s_textFloat->setText(displayValue());
s_textFloat->moveGlobal(this, QPoint(width() + 2, 0));
s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime);
}
float Knob::getValue( const QPoint & _p )
{
float value;
@@ -580,10 +587,8 @@ void Knob::mousePressEvent( QMouseEvent * _me )
emit sliderPressed();
s_textFloat->setText( displayValue() );
s_textFloat->moveGlobal( this,
QPoint( width() + 2, 0 ) );
s_textFloat->show();
showTextFloat(0, 0);
m_buttonPressed = true;
}
else if( _me->button() == Qt::LeftButton &&
@@ -613,6 +618,7 @@ void Knob::mouseMoveEvent( QMouseEvent * _me )
m_lastMousePos = _me->pos();
}
s_textFloat->setText( displayValue() );
s_textFloat->show();
}
@@ -638,7 +644,15 @@ void Knob::mouseReleaseEvent( QMouseEvent* event )
s_textFloat->hide();
}
void Knob::enterEvent(QEvent *event)
{
showTextFloat(700, 2000);
}
void Knob::leaveEvent(QEvent *event)
{
s_textFloat->hide();
}
void Knob::focusOutEvent( QFocusEvent * _fe )

View File

@@ -45,6 +45,14 @@ SimpleTextFloat::SimpleTextFloat() :
m_textLabel = new QLabel(this);
layout->addWidget(m_textLabel);
m_showTimer = new QTimer();
m_showTimer->setSingleShot(true);
QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show);
m_hideTimer = new QTimer();
m_hideTimer->setSingleShot(true);
QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide);
}
void SimpleTextFloat::setText(const QString & text)
@@ -52,6 +60,29 @@ void SimpleTextFloat::setText(const QString & text)
m_textLabel->setText(text);
}
void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime)
{
if (msecBeforeDisplay != 0)
{
m_showTimer->start(msecBeforeDisplay);
}
else
{
show();
}
if (msecDisplayTime != 0)
{
m_hideTimer->start(msecBeforeDisplay + msecDisplayTime);
}
}
void SimpleTextFloat::hide()
{
m_showTimer->stop();
m_hideTimer->stop();
QWidget::hide();
}
void SimpleTextFloat::setVisibilityTimeOut(int msecs)
{

View File

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

View File

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

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