diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | build/depends.py | 4 | ||||
-rw-r--r-- | src/audio/signalinfo.cpp | 27 | ||||
-rw-r--r-- | src/audio/signalinfo.h | 78 | ||||
-rw-r--r-- | src/audio/streaminfo.cpp | 27 | ||||
-rw-r--r-- | src/audio/streaminfo.h | 64 | ||||
-rw-r--r-- | src/audio/types.cpp | 45 | ||||
-rw-r--r-- | src/audio/types.h | 188 | ||||
-rw-r--r-- | src/mixxxapplication.cpp | 12 | ||||
-rw-r--r-- | src/util/duration.h | 39 | ||||
-rw-r--r-- | src/util/macros.h | 4 | ||||
-rw-r--r-- | src/util/optional.h | 11 |
12 files changed, 478 insertions, 24 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5053489dca..ed9f31a04b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,9 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/analyzer/plugins/analyzersoundtouchbeats.cpp src/analyzer/plugins/buffering_utils.cpp src/analyzer/trackanalysisscheduler.cpp + src/audio/types.cpp + src/audio/signalinfo.cpp + src/audio/streaminfo.cpp src/control/control.cpp src/control/controlaudiotaperpot.cpp src/control/controlbehavior.cpp diff --git a/build/depends.py b/build/depends.py index cc6572f845..0f11432197 100644 --- a/build/depends.py +++ b/build/depends.py @@ -902,6 +902,10 @@ class MixxxCore(Feature): "src/analyzer/plugins/analyzerqueenmarykey.cpp", "src/analyzer/plugins/buffering_utils.cpp", + "src/audio/types.cpp", + "src/audio/signalinfo.cpp", + "src/audio/streaminfo.cpp", + "src/controllers/controller.cpp", "src/controllers/controllerdebug.cpp", "src/controllers/controllerengine.cpp", diff --git a/src/audio/signalinfo.cpp b/src/audio/signalinfo.cpp new file mode 100644 index 0000000000..a86f449033 --- /dev/null +++ b/src/audio/signalinfo.cpp @@ -0,0 +1,27 @@ +#include "audio/signalinfo.h" + +namespace mixxx { + +namespace audio { + +bool operator==( + const SignalInfo& lhs, + const SignalInfo& rhs) { + return lhs.getChannelCount() == rhs.getChannelCount() && + lhs.getSampleLayout() == rhs.getSampleLayout() && + lhs.getSampleRate() == rhs.getSampleRate(); +} + +QDebug +operator<<(QDebug dbg, const SignalInfo& arg) { + dbg << "SignalInfo{"; + arg.dbgChannelCount(dbg); + arg.dbgSampleLayout(dbg); + arg.dbgSampleRate(dbg); + dbg << '}'; + return dbg; +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/signalinfo.h b/src/audio/signalinfo.h new file mode 100644 index 0000000000..d2e8ffeddf --- /dev/null +++ b/src/audio/signalinfo.h @@ -0,0 +1,78 @@ +#pragma once + +#include "audio/types.h" +#include "util/assert.h" +#include "util/macros.h" +#include "util/optional.h" + +namespace mixxx { + +namespace audio { + +// Properties that characterize an uncompressed PCM audio signal. +class SignalInfo final { + // Properties + PROPERTY_SET_BYVAL_GET_BYREF(ChannelCount, channelCount, ChannelCount) + PROPERTY_SET_BYVAL_GET_BYREF(SampleRate, sampleRate, SampleRate) + PROPERTY_SET_BYVAL_GET_BYREF(OptionalSampleLayout, sampleLayout, SampleLayout) + + public: + constexpr SignalInfo() = default; + constexpr explicit SignalInfo( + OptionalSampleLayout sampleLayout) + : m_sampleLayout(sampleLayout) { + } + SignalInfo( + ChannelCount channelCount, + SampleRate sampleRate, + OptionalSampleLayout sampleLayout = std::nullopt) + : m_channelCount(channelCount), + m_sampleRate(sampleRate), + m_sampleLayout(sampleLayout) { + } + SignalInfo(SignalInfo&&) = default; + SignalInfo(const SignalInfo&) = default; + /*non-virtual*/ ~SignalInfo() = default; + + constexpr bool isValid() const { + return getChannelCount().isValid() && + getSampleLayout() && + getSampleRate().isValid(); + } + + SignalInfo& operator=(SignalInfo&&) = default; + SignalInfo& operator=(const SignalInfo&) = default; + + // Conversion: #samples / sample offset -> #frames / frame offset + template<typename T> + inline T samples2frames(T samples) const { + DEBUG_ASSERT(getChannelCount().isValid()); + DEBUG_ASSERT(0 == (samples % getChannelCount())); + return samples / getChannelCount(); + } + + // Conversion: #frames / frame offset -> #samples / sample offset + template<typename T> + inline T frames2samples(T frames) const { + DEBUG_ASSERT(getChannelCount().isValid()); + return frames * getChannelCount(); + } +}; + +bool operator==( + const SignalInfo& lhs, + const SignalInfo& rhs); + +inline bool operator!=( + const SignalInfo& lhs, + const SignalInfo& rhs) { + return !(lhs == rhs); +} + +QDebug operator<<(QDebug dbg, const SignalInfo& arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_METATYPE(mixxx::audio::SignalInfo) diff --git a/src/audio/streaminfo.cpp b/src/audio/streaminfo.cpp new file mode 100644 index 0000000000..d97c2c113c --- /dev/null +++ b/src/audio/streaminfo.cpp @@ -0,0 +1,27 @@ +#include "audio/streaminfo.h" + +namespace mixxx { + +namespace audio { + +bool operator==( + const StreamInfo& lhs, + const StreamInfo& rhs) { + return lhs.getSignalInfo() == rhs.getSignalInfo() && + lhs.getBitrate() == rhs.getBitrate() && + lhs.getDuration() == rhs.getDuration(); +} + +QDebug +operator<<(QDebug dbg, const StreamInfo& arg) { + dbg << "StreamInfo{"; + arg.dbgSignalInfo(dbg); + arg.dbgBitrate(dbg); + arg.dbgDuration(dbg); + dbg << '}'; + return dbg; +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/streaminfo.h b/src/audio/streaminfo.h new file mode 100644 index 0000000000..8a70a6bd39 --- /dev/null +++ b/src/audio/streaminfo.h @@ -0,0 +1,64 @@ +#pragma once + +#include "audio/signalinfo.h" +#include "util/duration.h" + +namespace mixxx { + +namespace audio { + +// Properties that characterize a (compressed) PCM audio stream. +// +// Currently we assume that every stream has a finite duration +// that is known upfront! +class StreamInfo final { + // Properties + PROPERTY_SET_BYVAL_GET_BYREF(SignalInfo, signalInfo, SignalInfo) + PROPERTY_SET_BYVAL_GET_BYREF(Bitrate, bitrate, Bitrate) + PROPERTY_SET_BYVAL_GET_BYREF(Duration, duration, Duration) + + public: + constexpr StreamInfo() = default; + constexpr explicit StreamInfo( + const SignalInfo& signalInfo) + : m_signalInfo(signalInfo) { + } + constexpr StreamInfo( + const SignalInfo& signalInfo, + Bitrate bitrate, + Duration duration) + : m_signalInfo(signalInfo), + m_bitrate(bitrate), + m_duration(duration) { + } + StreamInfo(StreamInfo&&) = default; + StreamInfo(const StreamInfo&) = default; + /*non-virtual*/ ~StreamInfo() = default; + + constexpr bool isValid() const { + return getSignalInfo().isValid() && + getBitrate().isValid() && + (getDuration() > Duration::empty()); + } + + StreamInfo& operator=(StreamInfo&&) = default; + StreamInfo& operator=(const StreamInfo&) = default; +}; + +bool operator==( + const StreamInfo& lhs, + const StreamInfo& rhs); + +inline bool operator!=( + const StreamInfo& lhs, + const StreamInfo& rhs) { + return !(lhs == rhs); +} + +QDebug operator<<(QDebug dbg, const StreamInfo& arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_METATYPE(mixxx::audio::StreamInfo) diff --git a/src/audio/types.cpp b/src/audio/types.cpp new file mode 100644 index 0000000000..f7addcc954 --- /dev/null +++ b/src/audio/types.cpp @@ -0,0 +1,45 @@ +#include "audio/types.h" + +namespace mixxx { + +namespace audio { + +QDebug operator<<(QDebug dbg, ChannelLayout arg) { + switch (arg) { + case ChannelLayout::Mono: + return dbg << "Mono"; + case ChannelLayout::DualMono: + return dbg << "DualMono"; + case ChannelLayout::Stereo: + return dbg << "Stereo"; + } + DEBUG_ASSERT(!"unreachable code"); + return dbg; +} + +QDebug operator<<(QDebug dbg, SampleLayout arg) { + switch (arg) { + case SampleLayout::Planar: + return dbg << "Planar"; + case SampleLayout::Interleaved: + return dbg << "Interleaved"; + } + DEBUG_ASSERT(!"unreachable code"); + return dbg; +} + +QDebug operator<<(QDebug dbg, SampleRate arg) { + return dbg + << QString::number(arg).toLocal8Bit().constData() + << SampleRate::unit(); +} + +QDebug operator<<(QDebug dbg, Bitrate arg) { + return dbg + << QString::number(arg).toLocal8Bit().constData() + << Bitrate::unit(); +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/types.h b/src/audio/types.h new file mode 100644 index 0000000000..497fa2102e --- /dev/null +++ b/src/audio/types.h @@ -0,0 +1,188 @@ +#pragma once + +#include <QtDebug> + +#include "util/assert.h" +#include "util/optional.h" +#include "util/types.h" + +// Various properties of digital PCM audio signals and streams. +// +// An audio signal or stream contains samples for multiple +// channels sampled at discrete times. +// +// The channel layout (optional) assigns meaning to the +// different channels of a signal. +// +// The sample layout defines how subsequent samples from +// different channels are represented and stored in memory. + +namespace mixxx { + +namespace audio { + +enum class ChannelLayout { + Mono, // 1 channel + DualMono, // 2 channels with identical signals + Stereo, // 2 independent channels left/right + // ...to be continued... +}; + +typedef std::optional<ChannelLayout> OptionalChannelLayout; + +QDebug operator<<(QDebug dbg, ChannelLayout arg); + +class ChannelCount { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr SINT kValueMin = 1; // lower bound (inclusive) + static constexpr SINT kValueMax = 255; // upper bound (inclusive, 8-bit unsigned integer) + + static constexpr ChannelCount min() { + return ChannelCount(kValueMin); + } + static constexpr ChannelCount max() { + return ChannelCount(kValueMax); + } + + static ChannelCount fromLayout(ChannelLayout layout) { + switch (layout) { + case ChannelLayout::Mono: + return ChannelCount(1); + case ChannelLayout::DualMono: + return ChannelCount(1); + case ChannelLayout::Stereo: + return ChannelCount(2); + } + DEBUG_ASSERT(!"unreachable code"); + } + + explicit constexpr ChannelCount(SINT value = kValueDefault) + : m_value(value) { + } + explicit ChannelCount(ChannelLayout layout) + : m_value(fromLayout(layout).m_value) { + } + + constexpr bool isValid() const { + return (kValueMin <= m_value) && + (m_value <= kValueMax); + } + + /*implicit*/ constexpr operator SINT() const { + return m_value; + } + + private: + SINT m_value; +}; + +// Defines the ordering of how samples from multiple channels are +// stored in contiguous buffers: +// - Planar: Channel by channel +// - Interleaved: Frame by frame +// The samples from all channels that are coincident in time are +// called a "frame" (or more specific "sample frame"). +// +// Example: 10 stereo samples from left (L) and right (R) channel +// Planar layout: LLLLLLLLLLRRRRRRRRRR +// Interleaved layout: LRLRLRLRLRLRLRLRLRLR +enum class SampleLayout { + Planar, + Interleaved +}; + +typedef std::optional<SampleLayout> OptionalSampleLayout; + +QDebug operator<<(QDebug dbg, SampleLayout arg); + +class SampleRate { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr SINT kValueMin = 8000; // lower bound (inclusive, = minimum MP3 sample rate) + static constexpr SINT kValueMax = 192000; // upper bound (inclusive) + + static constexpr SampleRate min() { + return SampleRate(kValueMin); + } + static constexpr SampleRate max() { + return SampleRate(kValueMax); + } + + static constexpr const char* unit() { + return "Hz"; + } + + explicit constexpr SampleRate(SINT value = kValueDefault) + : m_value(value) { + } + + constexpr bool isValid() const { + return (kValueMin <= m_value) && + (m_value <= kValueMax); + } + + /*implicit*/ constexpr operator SINT() const { + return m_value; + } + + private: + SINT m_value; +}; + +QDebug operator<<(QDebug dbg, SampleRate arg); + +// The bitrate is measured in kbit/s (kbps) and provides information +// about the level of compression for lossily encoded audio streams. +// It depends on the metadata and decoder if a value for the bitrate +// is available, i.e. it might be invalid if it cannot be determined. +class Bitrate { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr const char* unit() { + return "kbps"; + } + + explicit constexpr Bitrate(SINT value = kValueDefault) + : m_value(value) { + } + + constexpr bool isValid() const { + return m_value > kValueDefault; + } + + /*implicit*/ operator SINT() const { + DEBUG_ASSERT(m_value >= kValueDefault); // unsigned value + return m_value; + } + + private: + SINT m_value; +}; + +QDebug operator<<(QDebug dbg, Bitrate arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_TYPEINFO(mixxx::audio::ChannelCount, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::ChannelCount) + +Q_DECLARE_TYPEINFO(mixxx::audio::OptionalChannelLayout, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::OptionalChannelLayout) + +Q_DECLARE_TYPEINFO(mixxx::audio::OptionalSampleLayout, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::OptionalSampleLayout) + +Q_DECLARE_TYPEINFO(mixxx::audio::SampleRate, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::SampleRate) + +Q_DECLARE_TYPEINFO(mixxx::audio::Bitrate, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::Bitrate) diff --git a/src/mixxxapplication.cpp b/src/mixxxapplication.cpp index 412c342cc1..51349d4ca5 100644 --- a/src/mixxxapplication.cpp +++ b/src/mixxxapplication.cpp @@ -4,6 +4,7 @@ #include "mixxxapplication.h" +#include "audio/types.h" #include "control/controlproxy.h" #include "library/crate/crateid.h" #include "soundio/soundmanagerutil.h" @@ -55,7 +56,16 @@ MixxxApplication::~MixxxApplication() { } void MixxxApplication::registerMetaTypes() { - // Register custom data types for signal processing + // Register custom data types + + // PCM audio types + qRegisterMetaType<mixxx::audio::ChannelCount>("mixxx::audio::ChannelCount"); + qRegisterMetaType<mixxx::audio::OptionalChannelLayout>("mixxx::audio::OptionalChannelLayout"); + qRegisterMetaType<mixxx::audio::OptionalSampleLayout>("mixxx::audio::OptionalSampleLayout"); + qRegisterMetaType<mixxx::audio::SampleRate>("mixxx::audio::SampleRate"); + qRegisterMetaType<mixxx::audio::Bitrate>("mixxx::audio::Bitrate"); + + // TrackId qRegisterMetaType<TrackId>(); qRegisterMetaType<QSet<TrackId>>(); qRegisterMetaType<QList<TrackId>>(); diff --git a/src/util/duration.h b/src/util/duration.h index 0d933f9032..fae49b50dc 100644 --- a/src/util/duration.h +++ b/src/util/duration.h @@ -1,5 +1,4 @@ -#ifndef MIXXX_UTIL_DURATION_H -#define MIXXX_UTIL_DURATION_H +#pragma once #include <QMetaType> #include <QString> @@ -23,43 +22,43 @@ class DurationBase { }; // Returns the duration as an integer number of seconds (rounded-down). - qint64 toIntegerSeconds() const { + constexpr qint64 toIntegerSeconds() const { return m_durationNanos / kNanosPerSecond; } // Returns the duration as a floating point number of seconds. - double toDoubleSeconds() const { + constexpr double toDoubleSeconds() const { return static_cast<double>(m_durationNanos) / kNanosPerSecond; } // Returns the duration as an integer number of milliseconds (rounded-down). - qint64 toIntegerMillis() const { + constexpr qint64 toIntegerMillis() const { return m_durationNanos / kNanosPerMilli; } // Returns the duration as a floating point number of milliseconds. - double toDoubleMillis() const { + constexpr double toDoubleMillis() const { return static_cast<double>(m_durationNanos) / kNanosPerMilli; } // Returns the duration as an integer number of microseconds (rounded-down). - qint64 toIntegerMicros() const { + constexpr qint64 toIntegerMicros() const { return m_durationNanos / kNanosPerMicro; } // Returns the duration as a floating point number of microseconds. - double toDoubleMicros() const { + constexpr double toDoubleMicros() const { return static_cast<double>(m_durationNanos) / kNanosPerMicro; } // Returns the duration as an integer number of nanoseconds. The duration is // represented internally as nanoseconds so no rounding occurs. - qint64 toIntegerNanos() const { + constexpr qint64 toIntegerNanos() const { return m_durationNanos; } // Returns the duration as an integer number of nanoseconds. - double toDoubleNanos() const { + constexpr double toDoubleNanos() const { return static_cast<double>(m_durationNanos); } @@ -96,7 +95,7 @@ class DurationBase { static QChar kDecimalSeparator; protected: - explicit DurationBase(qint64 durationNanos) + explicit constexpr DurationBase(qint64 durationNanos) : m_durationNanos(durationNanos) { } @@ -105,7 +104,7 @@ class DurationBase { class DurationDebug : public DurationBase { public: - DurationDebug(const DurationBase& duration, Units unit) + constexpr DurationDebug(const DurationBase& duration, Units unit) : DurationBase(duration), m_unit(unit) { } @@ -136,30 +135,30 @@ class Duration : public DurationBase { public: // Returns a Duration object representing a duration of 'seconds'. template<typename T> - static Duration fromSeconds(T seconds) { + static constexpr Duration fromSeconds(T seconds) { return Duration(seconds * kNanosPerSecond); } // Returns a Duration object representing a duration of 'millis'. - static Duration fromMillis(qint64 millis) { + static constexpr Duration fromMillis(qint64 millis) { return Duration(millis * kNanosPerMilli); } // Returns a Duration object representing a duration of 'micros'. - static Duration fromMicros(qint64 micros) { + static constexpr Duration fromMicros(qint64 micros) { return Duration(micros * kNanosPerMicro); } // Returns a Duration object representing a duration of 'nanos'. - static Duration fromNanos(qint64 nanos) { + static constexpr Duration fromNanos(qint64 nanos) { return Duration(nanos); } - static Duration empty() { + static constexpr Duration empty() { return Duration(); } - Duration() + constexpr Duration() : DurationBase(0) { } @@ -265,7 +264,7 @@ class Duration : public DurationBase { } private: - explicit Duration(qint64 durationNanos) + explicit constexpr Duration(qint64 durationNanos) : DurationBase(durationNanos) { } }; @@ -274,5 +273,3 @@ class Duration : public DurationBase { Q_DECLARE_TYPEINFO(mixxx::Duration, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(mixxx::Duration) - -#endif /* MIXXX_UTIL_DURATION_H */ diff --git a/src/util/macros.h b/src/util/macros.h index accda62304..080c139184 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -14,7 +14,7 @@ // classes. #define PROPERTY_SET_BYVAL_GET_BYREF(TYPE, NAME, CAP_NAME) \ public: void set##CAP_NAME(TYPE NAME) { m_##NAME = std::move(NAME); } \ -public: TYPE const& get##CAP_NAME() const { return m_##NAME; } \ -public: TYPE& ref##CAP_NAME() { return m_##NAME; } \ +public: constexpr TYPE const& get##CAP_NAME() const { return m_##NAME; } \ +public: constexpr TYPE& ref##CAP_NAME() { return m_##NAME; } \ public: QDebug dbg##CAP_NAME(QDebug dbg) const { return dbg << #NAME ":" << m_##NAME; } \ private: TYPE m_##NAME; diff --git a/src/util/optional.h b/src/util/optional.h index 5767794f36..6fe9139b0c 100644 --- a/src/util/optional.h +++ b/src/util/optional.h @@ -22,3 +22,14 @@ using std::experimental::optional; } // namespace std #endif + +#include <QtDebug> + +template<typename T> +QDebug operator<<(QDebug dbg, std::optional<T> arg) { + if (arg) { + return dbg << *arg; + } else { + return dbg << "nullopt"; + } +} |