diff --git a/CHANGELOG b/CHANGELOG
index 5bc884a..178c7f1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,16 @@
* RECENT CHANGES
*******************************************************************************
+=== 1.0.27 ===
+* Implemented TruePeak meter.
+* Implemented Panorama meter (Panometer).
+* Implemented ScaledMeterGraph with subsampling option.
+* Implemented QuantizedCounter statistics tool.
+* Implemented ILUFSMeter - integrated LUFS meter.
+* Several fixes and improvements in RawRingBuffer.
+* Updated build scripts.
+* Updated module versions in dependencies.
+
=== 1.0.26 ===
* Updated build scripts.
* Updated module versions in dependencies.
diff --git a/README.md b/README.md
index 613ddaf..07ead33 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,10 @@ Set of modules provided:
* Spectral tilt
* Metering
* Correlation meter
- * Loudness meter
+ * Integrated Loudness meter (as defined by BS.1770-5 specification)
+ * Loudness meter (as defined by BS.1770-5 specification)
+ * Panorama meter
+ * True Peak meter (as defined by the BS.1770-5 specification)
* Miscellaneous functions
* Broadcasting related functions and constants
* Signal envelope functions
@@ -45,6 +48,8 @@ Set of modules provided:
* Sample
* Sample player
* Sample playbakc control
+ * Statistics computation
+ * Quantized statistics counter
* Utilities
* Chirp Processor
* Convolver
@@ -57,6 +62,7 @@ Set of modules provided:
* Impulse response taker
* Latency detector
* Meter history
+ * Meter history with scaling option
* Oscillator
* Oversampler
* Randomizer
diff --git a/include/lsp-plug.in/dsp-units/const.h b/include/lsp-plug.in/dsp-units/const.h
index 2f0c62b..6f0bf1c 100644
--- a/include/lsp-plug.in/dsp-units/const.h
+++ b/include/lsp-plug.in/dsp-units/const.h
@@ -42,6 +42,7 @@
#define GAIN_AMP_P_36_DB 63.09575 /* +36 dB */
#define GAIN_AMP_P_24_DB 15.84893 /* +24 dB */
#define GAIN_AMP_P_18_DB 7.943282 /* +18 dB */
+#define GAIN_AMP_P_16_DB 6.30957 /* +16 dB */
#define GAIN_AMP_P_12_DB 3.98107 /* +12 dB */
#define GAIN_AMP_P_11_DB 3.54813 /* +11 dB */
#define GAIN_AMP_P_9_DB 2.81838 /* +9 dB */
@@ -49,6 +50,8 @@
#define GAIN_AMP_P_6_DB 1.99526 /* +6 dB */
#define GAIN_AMP_P_5_DB 1.77828 /* +5 dB */
#define GAIN_AMP_P_3_DB 1.41254 /* +3 dB */
+#define GAIN_AMP_P_2_DB 1.25896 /* +2 dB */
+#define GAIN_AMP_P_1_DB 1.12202 /* +1 dB */
#define GAIN_AMP_0_DB 1.0 /* 0 dB */
#define GAIN_AMP_M_3_DB 0.707946 /* -3 dB */
#define GAIN_AMP_M_6_DB 0.50118 /* -6 dB */
diff --git a/include/lsp-plug.in/dsp-units/meters/Correlometer.h b/include/lsp-plug.in/dsp-units/meters/Correlometer.h
index 85c0680..e3a2d02 100644
--- a/include/lsp-plug.in/dsp-units/meters/Correlometer.h
+++ b/include/lsp-plug.in/dsp-units/meters/Correlometer.h
@@ -35,11 +35,7 @@ namespace lsp
{
/**
- * Loudness meter. Allows to specify multiple channels and their roles to measure the
- * loudness according to the BS.1770-4 standard specification.
- * The meter does not output LKFS (LUFS) values nor LU (Loudness Unit) values.
- * Instead, it provides the regular RMS value which then can be converted into
- * DBFS, LKFS/LUFS or LU values by applying logarithmic function.
+ * Corellometer. Computes normalized correlation between two signals.
*/
class LSP_DSP_UNITS_PUBLIC Correlometer
{
diff --git a/include/lsp-plug.in/dsp-units/meters/ILUFSMeter.h b/include/lsp-plug.in/dsp-units/meters/ILUFSMeter.h
new file mode 100644
index 0000000..b1932f6
--- /dev/null
+++ b/include/lsp-plug.in/dsp-units/meters/ILUFSMeter.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 16 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#ifndef LSP_PLUG_IN_DSP_UNITS_METERS_ILUFSMETER_H_
+#define LSP_PLUG_IN_DSP_UNITS_METERS_ILUFSMETER_H_
+
+#include
+
+#include
+#include
+#include
+#include
+
+
+namespace lsp
+{
+ namespace dspu
+ {
+ /**
+ * Integrated Loudness meter. Allows to specify multiple channels and their roles
+ * to measure the loudness according to the BS.1770-5 standard specification.
+ * The meter does not output LKFS (LUFS) values nor LU (Loudness Unit) values.
+ * Instead, it provides the regular mean square value which then can be converted into
+ * DBFS, LKFS/LUFS or LU values by applying corresponding logarithmic function.
+ *
+ * This meter is useful for measuring Integrated LUFS, for Momentary LUFS and
+ * Short-Term LUFS consider using LoudnessMeter.
+ *
+ * By default, it uses the standardized K-weighted filter over 400 ms measurement
+ * window as described by the BS.1770-5 specification.
+ * If number of channels in the configuration is 1 or 2, then the meter automatically
+ * sets designation value for inputs to CENTER for mono configuration or LEFT/RIGHT
+ * for stereo configuration.
+ */
+ class LSP_DSP_UNITS_PUBLIC ILUFSMeter
+ {
+ protected:
+ enum c_flags_t {
+ C_ENABLED = 1 << 0
+ };
+
+ enum flags_t
+ {
+ F_UPD_FILTERS = 1 << 0,
+ F_UPD_TIME = 1 << 1,
+
+ F_UPD_ALL = F_UPD_FILTERS | F_UPD_TIME
+ };
+
+ typedef struct channel_t
+ {
+ dspu::FilterBank sBank; // Filter bank
+ dspu::Filter sFilter; // Band filter
+
+ const float *vIn; // The input buffer
+ float vBlock[4]; // Buffer for computing gating block with overlapping of 75%
+
+ float fWeight; // Weighting coefficient
+ bs::channel_t enDesignation; // Channel designation
+
+ uint32_t nFlags; // Flags
+ } split_t;
+
+ protected:
+ channel_t *vChannels; // List of channels
+
+ float *vBuffer; // Temporary buffer for processing
+ float *vLoudness; // Loudness of the gating block
+
+ float fBlockPeriod; // Block measuring period in milliseconds
+ float fIntTime; // Integration time
+ float fMaxIntTime; // Maximum integration time
+ float fAvgCoeff; // Averaging coefficient
+ float fLoudness; // Currently measured loudness
+
+ uint32_t nBlockSize; // Block measuring samples
+ uint32_t nBlockOffset; // Block measuring period offset
+ uint32_t nBlockPart; // The index of the current value to update in the gating block
+ uint32_t nMSSize; // Overall number of blocks available in buffer
+ uint32_t nMSHead; // Current position to write new block to buffer
+ int32_t nMSInt; // Number of blocks to integrate
+ int32_t nMSCount; // Count of processed blocks
+
+ uint32_t nSampleRate; // Sample rate
+ uint32_t nChannels; // Number of channels
+ uint32_t nFlags; // Update flags
+ bs::weighting_t enWeight; // Weighting function
+
+ uint8_t *pData; // Unaligned data
+ uint8_t *pVarData; // Unaligned variable data
+
+ public:
+ explicit ILUFSMeter();
+ ILUFSMeter(const ILUFSMeter &) = delete;
+ ILUFSMeter(ILUFSMeter &&) = delete;
+ ~ILUFSMeter();
+
+ ILUFSMeter & operator = (const ILUFSMeter &) = delete;
+ ILUFSMeter & operator = (ILUFSMeter &&) = delete;
+
+ /** Construct object
+ *
+ */
+ void construct();
+
+ /** Destroy object
+ *
+ */
+ void destroy();
+
+ /** Initialize object
+ *
+ * @param channels number of input channels
+ * @param max_int_time maximum integration time in seconds
+ * @param block_period the block measurement period in milliseconds
+ * @return status of operation
+ */
+ status_t init(size_t channels, float max_int_time = 60, float block_period = dspu::bs::LUFS_MEASURE_PERIOD_MS);
+
+ private:
+ float compute_gated_loudness(float threshold);
+
+ public:
+ /**
+ * Bind the buffer to corresponding channel.
+ * @param id channel index to bind
+ * @param in input buffer buffer to bind
+ * @param out output buffer to store measured loudness of channel (optional)
+ * @return status of operation
+ */
+ status_t bind(size_t id, const float *in);
+
+ /**
+ * Unbind buffer from channel
+ * @param id channel identifier
+ * @return status of operation
+ */
+ inline status_t unbind(size_t id) { return bind(id, NULL); }
+
+ /**
+ * Set channel designation
+ * @param id channel identifier
+ * @param designation channel designation
+ */
+ status_t set_designation(size_t id, bs::channel_t designation);
+
+ /**
+ * Get channel designation
+ * @param id identifier of the channel
+ * @return channel designation
+ */
+ bs::channel_t designation(size_t id) const;
+
+ /**
+ * Set channel active
+ * @param id channel identifier
+ * @param active channel activity
+ * @return status of operation
+ */
+ status_t set_active(size_t id, bool active=true);
+
+ /**
+ * Check that channel is active
+ * @param id channel identifier
+ * @return true if channel is active
+ */
+ bool active(size_t id) const;
+
+ /**
+ * Set weighting function
+ * @param weighting weighting function
+ */
+ void set_weighting(bs::weighting_t weighting);
+
+ /**
+ * Get the weighting function
+ * @return weighting function
+ */
+ inline bs::weighting_t weighting() const { return enWeight; }
+
+ /**
+ * Set the integration period
+ * @param period measurement period
+ */
+ void set_integration_period(float period);
+
+ /**
+ * Get the measurement period
+ * @return measurement period
+ */
+ inline float integration_period() const { return fIntTime; }
+
+ /**
+ * Set sample rate
+ * @param sample_rate sample rate to set
+ * @return status of operation
+ */
+ status_t set_sample_rate(size_t sample_rate);
+
+ /**
+ * Get the sample rate
+ * @return sample rate
+ */
+ inline size_t sample_rate() const { return nSampleRate; }
+
+ /**
+ * Process signal from channels and form the gain control signal
+ * @param out buffer to store the overall loudness (optional)
+ * @param count number of samples to process
+ * @param gain additional gain to apply
+ */
+ void process(float *out, size_t count, float gain = bs::DBFS_TO_LUFS_SHIFT_GAIN);
+
+ /**
+ * Get currently measured loudness
+ * @return currently measured loudness
+ */
+ inline float loudness() const { return fLoudness; }
+
+ /**
+ * Check that crossover needs to call reconfigure() before processing
+ * @return true if crossover needs to call reconfigure() before processing
+ */
+ inline bool needs_update() const { return nFlags != 0; }
+
+ /** Reconfigure crossover after parameter update
+ *
+ */
+ void update_settings();
+
+ /**
+ * Clear internal state
+ */
+ void clear();
+
+ /**
+ * Dump the state
+ * @param dumper dumper
+ */
+ void dump(IStateDumper *v) const;
+ };
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
+
+
+#endif /* LSP_PLUG_IN_DSP_UNITS_METERS_ILUFSMETER_H_ */
diff --git a/include/lsp-plug.in/dsp-units/meters/LoudnessMeter.h b/include/lsp-plug.in/dsp-units/meters/LoudnessMeter.h
index 68b8e39..30616c0 100644
--- a/include/lsp-plug.in/dsp-units/meters/LoudnessMeter.h
+++ b/include/lsp-plug.in/dsp-units/meters/LoudnessMeter.h
@@ -35,14 +35,17 @@ namespace lsp
namespace dspu
{
/**
- * Loudness meter. Allows to specify multiple channels and their roles to measure the
- * loudness according to the BS.1770-4 standard specification.
+ * Momenatry/Short Term Loudness meter. Allows to specify multiple channels and
+ * their roles to measure the loudness according to the BS.1770-5 standard specification.
* The meter does not output LKFS (LUFS) values nor LU (Loudness Unit) values.
- * Instead, it provides the regular RMS value which then can be converted into
+ * Instead, it provides the regular mean square value which then can be converted into
* DBFS, LKFS/LUFS or LU values by applying corresponding logarithmic function.
*
+ * This meter is useful for measuring Momentary LUFS and Short-Term LUFS,
+ * for Integrated LUFS consider using ILUFSMeter.
+ *
* By default, it uses the standardized K-weighted filter over 400 ms measurement
- * window as described by the BS.1770-4 specification.
+ * window as described by the BS.1770-5 specification.
* If number of channels in the configuration is 1 or 2, then the meter automatically
* sets designation value for inputs to CENTER for mono configuration or LEFT/RIGHT
* for stereo configuration.
@@ -92,7 +95,7 @@ namespace lsp
size_t nSampleRate; // Sample rate
size_t nPeriod; // Measuring period
- size_t nMSRefresh; // RMS refresh counter
+ size_t nMSRefresh; // RMS refresh counter
size_t nChannels; // Number of channels
size_t nFlags; // Update flags
size_t nDataHead; // Position in the data buffer
@@ -172,6 +175,13 @@ namespace lsp
* @return linking of the channel
*/
float link(size_t id) const;
+
+ /**
+ * Get channel designation
+ * @param id identifier of the channel
+ * @return channel designation
+ */
+ bs::channel_t designation(size_t id) const;
/**
* Set channel active
@@ -188,13 +198,6 @@ namespace lsp
*/
bool active(size_t id) const;
- /**
- * Get channel designation
- * @param id identifier of the channel
- * @return channel designation
- */
- bs::channel_t designation(size_t id) const;
-
/**
* Set weighting function
* @param weighting weighting function
diff --git a/include/lsp-plug.in/dsp-units/meters/Panometer.h b/include/lsp-plug.in/dsp-units/meters/Panometer.h
new file mode 100644
index 0000000..b560785
--- /dev/null
+++ b/include/lsp-plug.in/dsp-units/meters/Panometer.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 12 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#ifndef LSP_PLUG_IN_DSP_UNITS_METERS_PANOMETER_H_
+#define LSP_PLUG_IN_DSP_UNITS_METERS_PANOMETER_H_
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ /**
+ * Pan law
+ */
+ enum pan_law_t
+ {
+ /**
+ * Linear pan law between left and right channels
+ */
+ PAN_LAW_LINEAR,
+
+ /**
+ * Equal power pan law between left and right channels
+ */
+ PAN_LAW_EQUAL_POWER
+ };
+
+ /**
+ * Panorama meter. Computes panorama between two signals.
+ */
+ class LSP_DSP_UNITS_PUBLIC Panometer
+ {
+ private:
+ float *vInA; // History of input buffer 1
+ float *vInB; // History of input buffer 2
+ pan_law_t enPanLaw; // Pan law
+ float fValueA; // Current mean square value for input 1
+ float fValueB; // Current mean square value for input 2
+ float fNorm; // Norming factor
+ float fDefault; // Default value
+ uint32_t nCapacity; // The overall capacity
+ uint32_t nHead; // Write position of the buffer
+ uint32_t nMaxPeriod; // Maximum measurement period
+ uint32_t nPeriod; // Measurement period
+ uint32_t nWindow; // Number of samples processed before reset
+
+ uint8_t *pData; // Pointer to the allocated data
+
+ public:
+ Panometer();
+ Panometer(const Panometer &) = delete;
+ Panometer(Panometer &&) = delete;
+ ~Panometer();
+
+ Panometer & operator = (const Panometer &) = delete;
+ Panometer && operator = (Panometer &&) = delete;
+
+ /**
+ * Construct object
+ */
+ void construct();
+
+ /**
+ * Destroy object
+ */
+ void destroy();
+
+ /**
+ * Initialize object
+ * @param max_chunk_size the maximum period size in samples
+ * @return
+ */
+ status_t init(size_t max_period);
+
+ public:
+ /**
+ * Set pan law
+ * @param law pan law
+ */
+ void set_pan_law(pan_law_t law);
+
+ /**
+ * Get pan law
+ * @return pan law
+ */
+ inline pan_law_t pan_law() const { return enPanLaw; }
+
+ /**
+ * Set default value for panorama if it is not possible to compute it
+ * @param dfl default value for panorama
+ */
+ void set_default_pan(float dfl);
+
+ /**
+ * Get default value for panorama if it is not possible to compute it
+ * @return default value for panorama
+ */
+ inline float default_pan() const { return fDefault; }
+
+ /**
+ * Set the correlation computation period
+ * @param period correlation computation period
+ */
+ void set_period(size_t period);
+
+ /**
+ * Get the correlation computation period
+ * @return correlation computation period
+ */
+ inline size_t period() const { return nPeriod; }
+
+ /**
+ * Clear internal state
+ */
+ void clear();
+
+ /**
+ * Process the input data and compute the correlation function
+ * @param dst destination buffer to store the correlation function
+ * @param a pointer to the first buffer
+ * @param b pointer to the second buffer
+ * @param count number of samples to process
+ */
+ void process(float *dst, const float *a, const float *b, size_t count);
+
+ /**
+ * Dump the state
+ * @param dumper dumper
+ */
+ void dump(IStateDumper *v) const;
+ };
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
+
+#endif /* LSP_PLUG_IN_DSP_UNITS_METERS_PANOMETER_H_ */
diff --git a/include/lsp-plug.in/dsp-units/meters/TruePeakMeter.h b/include/lsp-plug.in/dsp-units/meters/TruePeakMeter.h
new file mode 100644
index 0000000..85539bb
--- /dev/null
+++ b/include/lsp-plug.in/dsp-units/meters/TruePeakMeter.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 16 окт. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#ifndef LSP_PLUG_IN_DSP_UNITS_METERS_TRUEPEAKMETER_H_
+#define LSP_PLUG_IN_DSP_UNITS_METERS_TRUEPEAKMETER_H_
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+
+ /**
+ * True Peak Mmeter. Computes True Peak value according to the BS-1770 recommendations.
+ */
+ class LSP_DSP_UNITS_PUBLIC TruePeakMeter
+ {
+ private:
+ typedef void (*reduce_t)(float *dst, const float *src, size_t count);
+
+ private:
+ uint32_t nSampleRate; // Sample rate
+ uint32_t nHead; // Head of the buffer
+ uint8_t nTimes; // Oversampling times
+ bool bUpdate; // Requires settings update
+
+ dsp::resampling_function_t pFunc; // Resampling function
+ reduce_t pReduce; // Reducing function
+ float *vBuffer; // Buffer for oversampled data
+ uint8_t *pData; // Pointer to the allocated data
+
+ protected:
+ static uint8_t calc_oversampling_multiplier(size_t sample_rate);
+ static void reduce_2x(float *dst, const float *src, size_t count);
+ static void reduce_3x(float *dst, const float *src, size_t count);
+ static void reduce_4x(float *dst, const float *src, size_t count);
+ static void reduce_6x(float *dst, const float *src, size_t count);
+ static void reduce_8x(float *dst, const float *src, size_t count);
+
+ public:
+ TruePeakMeter();
+ TruePeakMeter(const TruePeakMeter &) = delete;
+ TruePeakMeter(TruePeakMeter &&) = delete;
+ ~TruePeakMeter();
+
+ TruePeakMeter & operator = (const TruePeakMeter &) = delete;
+ TruePeakMeter && operator = (TruePeakMeter &&) = delete;
+
+ /**
+ * Construct object
+ */
+ void construct();
+
+ /**
+ * Destroy object
+ */
+ void destroy();
+
+ /**
+ * Initialize
+ */
+ bool init();
+
+ public:
+ void update_settings();
+
+ /**
+ * Set sample rate of the true peak meter
+ */
+ void set_sample_rate(uint32_t sr);
+
+ /**
+ * Get sample rate
+ * @return sample rate
+ */
+ size_t sample_rate() const;
+
+ /**
+ * Clear internal state
+ */
+ void clear();
+
+ /**
+ * Process the input data and compute the true peak value for each sample
+ * @param dst destination buffer to store the true peak values
+ * @param src source buffer to process
+ * @param count number of samples to process
+ */
+ void process(float *dst, const float *src, size_t count);
+
+ /**
+ * Process the buffer and compute the true peak value for each sample
+ * @param buf buffer to process and store true peak values
+ * @param count number of samples to process
+ */
+ void process(float *buf, size_t count);
+
+ /**
+ * Process the input data and compute the maximum true peak value
+ * @param channel the channel number to process
+ * @param src source buffer to process
+ * @param count number of samples to process
+ * @return maximum value of the true peak
+ */
+ float process_max(const float *src, size_t count);
+
+ /**
+ * Return latency for the true peak meter
+ * @return latency of the true peak meter
+ */
+ size_t latency() const;
+
+ /**
+ * Dump the state
+ * @param dumper dumper
+ */
+ void dump(IStateDumper *v) const;
+ };
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
+
+
+#endif /* LSP_PLUG_IN_DSP_UNITS_METERS_TRUEPEAKMETER_H_ */
diff --git a/include/lsp-plug.in/dsp-units/misc/broadcast.h b/include/lsp-plug.in/dsp-units/misc/broadcast.h
index 8d00aa9..59e4ab0 100644
--- a/include/lsp-plug.in/dsp-units/misc/broadcast.h
+++ b/include/lsp-plug.in/dsp-units/misc/broadcast.h
@@ -112,6 +112,26 @@ namespace lsp
*/
constexpr float LUFS_MEASURE_PERIOD_MS = 400.0f;
+ /**
+ * The period of the momentary loudness in milliseconds
+ */
+ constexpr float LUFS_MOMENTARY_PERIOD = 400.0f;
+
+ /**
+ * The period of the short-term loudness in milliseconds
+ */
+ constexpr float LUFS_SHORT_TERM_PERIOD = 3000.0f;
+
+ /**
+ * Absolute gating threshold for Integrated Loudness, LKFS
+ */
+ constexpr float LUFS_GATING_ABS_THRESH_LKFS = -70.0f;
+
+ /**
+ * Relative gating threshold for Integrated Loudness, LKFS
+ */
+ constexpr float LUFS_GATING_REL_THRESH_LKFS = -10.0f;
+
/**
* Return the channel weighting coefficient accoding to BS.1770-4 recommendation
* @param designation channel designation
diff --git a/include/lsp-plug.in/dsp-units/stat/QuantizedCounter.h b/include/lsp-plug.in/dsp-units/stat/QuantizedCounter.h
new file mode 100644
index 0000000..5b45f22
--- /dev/null
+++ b/include/lsp-plug.in/dsp-units/stat/QuantizedCounter.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 13 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#ifndef LSP_PLUG_IN_DSP_UNITS_STAT_QUANTIZEDCOUNTER_H_
+#define LSP_PLUG_IN_DSP_UNITS_STAT_QUANTIZEDCOUNTER_H_
+
+#include
+
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ /**
+ * Counter that allows to compute distibution of some signal among it's values
+ */
+ class QuantizedCounter
+ {
+ private:
+ uint32_t nPeriod; // Duration for statistics
+ uint32_t nLevels; // Number of quantization levels
+ uint32_t nHead; // Position of the head in the history buffer
+ uint32_t nCapacity; // Overall capacity of the history buffer
+ uint32_t nCount; // Number of records in buffer
+ uint32_t nMaxPeriod; // Maximum possible period
+ uint32_t nMaxLevels; // Maximum possible levels
+ float fMinValue; // Minimum value
+ float fMaxValue; // Maximum value
+ float fRStep; // The reciprocal of quantization step
+ bool bUpdate; // Need to update computations
+
+ float *vHistory; // History buffer
+ uint32_t *vCounters; // History counters
+
+ uint8_t *pData;
+
+ private:
+ void inc_counters(const float *src, size_t count);
+ void dec_counters(const float *src, size_t count);
+ void evict_values();
+
+ public:
+ QuantizedCounter();
+ QuantizedCounter(const QuantizedCounter &) = delete;
+ QuantizedCounter(QuantizedCounter &&) = delete;
+ ~QuantizedCounter();
+
+ QuantizedCounter & operator = (const QuantizedCounter &) = delete;
+ QuantizedCounter & operator = (QuantizedCounter &&) = delete;
+
+ void construct();
+ void destroy();
+
+ public:
+ /**
+ * Initialize counter
+ * @param max_period maximum period (in samples)
+ * @param max_levels maximum number of quantization levels
+ * @return status of operation
+ */
+ status_t init(size_t max_period, size_t max_levels);
+
+ public:
+ /**
+ * Set the statistics estimation period
+ * @param period statistics estimation period
+ */
+ void set_period(size_t period);
+
+ /**
+ * Get the statistics estimation period
+ * @return statistics estimation period
+ */
+ inline size_t period() const { return nPeriod; }
+
+ /**
+ * Get maximum possible statistics estimation period
+ * @return maximum possible statistics estimation period
+ */
+ inline size_t max_period() const { return nMaxPeriod; }
+
+ /**
+ * Set number of quantization levels
+ * @param levels number of quantization levels
+ */
+ void set_levels(size_t levels);
+
+ inline size_t levels() const { return nLevels; }
+
+ /**
+ * Set minimum quantization range value
+ * @param min minimum range value
+ */
+ void set_min_value(float min);
+
+ /**
+ * Set maximum quantization range value
+ * @param max maximum range value
+ */
+ void set_max_value(float max);
+
+ /**
+ * Set minimum and maximum range values
+ * @param min minimum range value
+ * @param max maximum range value
+ */
+ void set_value_range(float min, float max);
+
+ /**
+ * Set quantization range parameters
+ * @param min minimum range value
+ * @param max maximum range value
+ * @param levels number of quantization levels
+ */
+ void set_range(float min, float max, size_t levels);
+
+ /**
+ * Check if distribution counter needs to be updated
+ * @return true if distribution counter needs to be updated
+ */
+ inline bool needs_update() const { return bUpdate; }
+
+ /**
+ * Update distribution counter settings
+ */
+ void update_settings();
+
+ /**
+ * Process signal and update distribution counters
+ * @param data data to process
+ * @param count number of samples to process
+ */
+ void process(const float *data, size_t count);
+
+ /**
+ * Clear the statistics
+ */
+ void clear();
+
+ /**
+ * Get actual values of counters
+ * @return actual values of counters
+ */
+ inline const uint32_t *counters() const { return vCounters; }
+
+ /**
+ * Get count of values that are below the quantization range
+ * (the number of values that give the negative counter index)
+ * @return count of values that are below the quantization range
+ */
+ inline uint32_t below() const { return vCounters[nMaxLevels]; }
+
+ /**
+ * Get count of values that are above the quantization range
+ * (the number of values that give the counter index not less then number of levels set)
+ * @return count of values that are below the quantization range
+ */
+ inline uint32_t above() const { return vCounters[nMaxLevels + 1]; }
+
+ /**
+ * Return number of samples used to compute counters
+ * @return number of samples used to compute counters
+ */
+ inline size_t count() const { return nCount; }
+
+ /**
+ * Dump the state
+ * @param dumper dumper
+ */
+ void dump(IStateDumper *v) const;
+ };
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+#endif /* LSP_PLUG_IN_DSP_UNITS_STAT_QUANTIZEDCOUNTER_H_ */
diff --git a/include/lsp-plug.in/dsp-units/util/MeterGraph.h b/include/lsp-plug.in/dsp-units/util/MeterGraph.h
index e85b3be..ddcac67 100644
--- a/include/lsp-plug.in/dsp-units/util/MeterGraph.h
+++ b/include/lsp-plug.in/dsp-units/util/MeterGraph.h
@@ -32,8 +32,10 @@ namespace lsp
{
enum meter_method_t
{
- MM_MAXIMUM,
- MM_MINIMUM
+ MM_ABS_MAXIMUM,
+ MM_ABS_MINIMUM,
+ MM_SIGN_MAXIMUM,
+ MM_SIGN_MINIMUM,
};
class LSP_DSP_UNITS_PUBLIC MeterGraph
@@ -41,9 +43,9 @@ namespace lsp
protected:
ShiftBuffer sBuffer;
float fCurrent;
- size_t nCount;
- size_t nPeriod;
- bool bMinimize;
+ uint32_t nCount;
+ uint32_t nPeriod;
+ meter_method_t enMethod;
public:
explicit MeterGraph();
@@ -83,13 +85,13 @@ namespace lsp
*
* @param m metering method
*/
- inline void set_method(meter_method_t m) { bMinimize = (m == MM_MINIMUM); }
+ inline void set_method(meter_method_t m) { enMethod = m; }
/** Get data stored in buffer
*
* @return pointer to the first element of the buffer
*/
- inline float *data() { return sBuffer.head(); }
+ inline float *data() { return sBuffer.head(); }
/** Set strobe period
*
@@ -100,6 +102,8 @@ namespace lsp
nPeriod = period;
}
+ inline float period() const { return nPeriod; }
+
/** Process single sample
*
* @param sample sample to process
diff --git a/include/lsp-plug.in/dsp-units/util/RawRingBuffer.h b/include/lsp-plug.in/dsp-units/util/RawRingBuffer.h
index d71e3fb..be27a00 100644
--- a/include/lsp-plug.in/dsp-units/util/RawRingBuffer.h
+++ b/include/lsp-plug.in/dsp-units/util/RawRingBuffer.h
@@ -84,6 +84,23 @@ namespace lsp
*/
void write(float data);
+ /**
+ * Write data to the buffer at current head position and advance position
+ *
+ * @param src buffer to write
+ * @param count number of samples
+ * @return actual number of samples written
+ */
+ size_t push(const float *data, size_t count);
+
+ /**
+ * Write the single sample to the ring buffer at current head position and advance position
+ *
+ * @param data sample to append
+ * @return number of samples appended
+ */
+ void push(float data);
+
/**
* Read the data from the ring buffer at the specified offset relative to the current head pointer
* @param dst destination buffer to store the data
@@ -97,7 +114,7 @@ namespace lsp
* @param offset offset behind the head
* @return data pointer at the head of buffer
*/
- float read(size_t offset);
+ float read(size_t offset) const;
/**
* Advance head by the specified amount of samples
@@ -143,6 +160,12 @@ namespace lsp
inline float *head() { return &pData[nHead]; }
inline const float *head() const { return &pData[nHead]; }
+ /**
+ * Get current write position index
+ * @return current write position
+ */
+ inline size_t position() const { return nHead; }
+
/**
* Get pointer to the tail
* @param offset tail offset relative to the head
@@ -177,6 +200,12 @@ namespace lsp
*/
size_t remaining(size_t offset) const;
+ /**
+ * Fill the whole buffer with specified value
+ * @param value value to fill
+ */
+ void fill(float value);
+
/**
* Dump data to the shift buffer
* @param v dumper
@@ -186,7 +215,4 @@ namespace lsp
} /* namespace dspu */
} /* namespace lsp */
-
-
-
#endif /* LSP_PLUG_IN_DSP_UNITS_UTIL_RAWRINGBUFFER_H_ */
diff --git a/include/lsp-plug.in/dsp-units/util/ScaledMeterGraph.h b/include/lsp-plug.in/dsp-units/util/ScaledMeterGraph.h
new file mode 100644
index 0000000..936337e
--- /dev/null
+++ b/include/lsp-plug.in/dsp-units/util/ScaledMeterGraph.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 05 окт 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#ifndef LSP_PLUG_IN_DSP_UNITS_UTIL_SCALEDMETERGRAPH_H_
+#define LSP_PLUG_IN_DSP_UNITS_UTIL_SCALEDMETERGRAPH_H_
+
+#include
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ class LSP_DSP_UNITS_PUBLIC ScaledMeterGraph
+ {
+ protected:
+ typedef struct sampler_t
+ {
+ RawRingBuffer sBuffer; // History buffer
+ float fCurrent; // Current sample
+ uint32_t nCount; // Number of samples processed
+ uint32_t nPeriod; // Sampling period
+ uint32_t nFrames; // Number of frames
+ } sampler_t;
+
+ protected:
+ sampler_t sHistory;
+ sampler_t sFrames;
+
+ uint32_t nPeriod;
+ uint32_t nMaxPeriod;
+ meter_method_t enMethod;
+
+ private:
+ bool update_period();
+
+ static void dump_sampler(IStateDumper *v, const char *name, const sampler_t *s);
+
+ void process_sampler(sampler_t *sampler, float sample);
+ void process_sampler(sampler_t *sampler, const float *src, size_t count);
+ void process_sampler(sampler_t *sampler, const float *src, float gain, size_t count);
+
+ public:
+ explicit ScaledMeterGraph();
+ ScaledMeterGraph(const ScaledMeterGraph &) = delete;
+ ScaledMeterGraph(ScaledMeterGraph &&) = delete;
+ ~ScaledMeterGraph();
+
+ ScaledMeterGraph & operator = (const ScaledMeterGraph &) = delete;
+ ScaledMeterGraph & operator = (ScaledMeterGraph &&) = delete;
+
+ /**
+ * Construct object
+ */
+ void construct();
+
+ /** Initialize meter graph
+ *
+ * @param frames number of frames used for graph and needed to be stored in internal buffer
+ * @param subsampling the sub-sampling period (in samples): the number of samples being aggregated into one single sub-sample
+ * @param max_period the maximum period per
+ * @return true on success
+ */
+ bool init(size_t frames, size_t subsampling, size_t max_period);
+
+ /** Destroy meter graph
+ *
+ */
+ void destroy();
+
+ public:
+ /**
+ * Get sub-sampling period
+ * @return sub-sampling period
+ */
+ inline size_t subsampling() const { return sHistory.nPeriod; }
+
+ /**
+ * Get number of frames
+ * @return number of frames
+ */
+ inline size_t frames() const { return sFrames.nFrames; }
+
+ /**
+ * Set metering method
+ *
+ * @param m metering method
+ */
+ void set_method(meter_method_t m);
+
+ /**
+ * Get metering method
+ * @return metering method
+ */
+ inline meter_method_t method() const { return enMethod; }
+
+ /**
+ * Set strobe period
+ *
+ * @param period strobe period
+ */
+ void set_period(size_t period);
+
+ /**
+ * Get metering period
+ * @return metering period
+ */
+ inline float period() const { return nPeriod; }
+
+ /**
+ * Read last actual frames data from the frame buffer. The maximum allowed frames
+ * to read is passed in the init() method
+ *
+ * @param dst pointer to store data
+ * @param offset negative offset from the current head
+ * @param count number of frames to read
+ */
+ void read(float *dst, size_t count);
+
+ /** Process single sample
+ *
+ * @param sample sample to process
+ */
+ void process(float sample);
+
+ /** Process multiple samples
+ *
+ * @param s array of samples
+ * @param n number of samples to process
+ */
+ void process(const float *s, size_t count);
+
+ /** Process multiple samples multiplied by specified value
+ *
+ * @param s array of samples
+ * @param n number of samples to process
+ */
+ void process(const float *s, float gain, size_t count);
+
+ /** Get current level
+ *
+ * @return current level
+ */
+ float level() const;
+
+ /** Fill graph with specific level
+ *
+ * @param level level
+ */
+ void fill(float level);
+
+ /**
+ * Dump internal state
+ * @param v state dumper
+ */
+ void dump(IStateDumper *v) const;
+ };
+ } /* namespace dspu */
+} /* namespace lsp */
+
+#endif /* LSP_PLUG_IN_DSP_UNITS_UTIL_SCALEDMETERGRAPH_H_ */
diff --git a/include/lsp-plug.in/dsp-units/version.h b/include/lsp-plug.in/dsp-units/version.h
index c123470..84c252f 100644
--- a/include/lsp-plug.in/dsp-units/version.h
+++ b/include/lsp-plug.in/dsp-units/version.h
@@ -25,7 +25,7 @@
// Define version of headers
#define LSP_DSP_UNITS_MAJOR 1
#define LSP_DSP_UNITS_MINOR 0
-#define LSP_DSP_UNITS_MICRO 26
+#define LSP_DSP_UNITS_MICRO 27
#if defined(LSP_DSP_UNITS_PUBLISHER)
#define LSP_DSP_UNITS_PUBLIC LSP_EXPORT_MODIFIER
diff --git a/make/tools.mk b/make/tools.mk
index 4d9cbc5..930bc0f 100644
--- a/make/tools.mk
+++ b/make/tools.mk
@@ -88,6 +88,7 @@ INSTALL ?= $(X_INSTALL_TOOL)
# Patch flags and tools for (cross) build
FLAG_RELRO := -Wl,-z,relro,-z,now
FLAG_STDLIB :=
+FLAG_GC_SECTIONS := -Wl,--gc-sections
NOARCH_CFLAGS :=
NOARCH_CXXFLAGS :=
NOARCH_EXE_FLAGS :=
@@ -105,6 +106,11 @@ else ifeq ($(PLATFORM),Windows)
NOARCH_EXE_FLAGS += -static-libgcc -static-libstdc++
NOARCH_SO_FLAGS += -static-libgcc -static-libstdc++
NOARCH_LDFLAGS += -T $(CURDIR)/make/ld-windows.script
+else ifeq ($(PLATFORM),MacOS)
+ FLAG_RELRO =
+ FLAG_GC_SECTIONS =
+ NOARCH_CXXFLAGS += -std=c++0x
+ NOARCH_LDFLAGS += -keep_private_externs
else ifeq ($(PLATFORM),BSD)
NOARCH_EXE_FLAGS += -L/usr/local/lib
NOARCH_SO_FLAGS += -L/usr/local/lib
@@ -194,11 +200,11 @@ NOARCH_LDFLAGS += -r
LDFLAGS := $(ARCHITECTURE_LDFLAGS) $(NOARCH_LDFLAGS)
HOST_LDFLAGS := $(HOST_ARCHITECTURE_LDFLAGS) $(NOARCH_LDFLAGS)
-NOARCH_EXE_FLAGS += $(FLAG_RELRO) -Wl,--gc-sections
+NOARCH_EXE_FLAGS += $(FLAG_RELRO) $(FLAG_GC_SECTIONS)
EXE_FLAGS := $(ARCHITECTURE_CFLAGS) $(NOARCH_EXE_FLAGS)
HOST_EXE_FLAGS := $(HOST_ARCHITECTURE_CFLAGS) $(NOARCH_EXE_FLAGS)
-NOARCH_SO_FLAGS += $(FLAG_RELRO) -Wl,--gc-sections -shared $(FLAG_STDLIB) -fPIC
+NOARCH_SO_FLAGS += $(FLAG_RELRO) $(FLAG_GC_SECTIONS) -shared $(FLAG_STDLIB) -fPIC
SO_FLAGS := $(ARCHITECTURE_CFLAGS) $(NOARCH_SO_FLAGS)
HOST_SO_FLAGS := $(HOST_ARCHITECTURE_CFLAGS) $(NOARCH_SO_FLAGS)
diff --git a/modules.mk b/modules.mk
index 1bdb755..a2987cc 100644
--- a/modules.mk
+++ b/modules.mk
@@ -20,31 +20,31 @@
#------------------------------------------------------------------------------
# Variables that describe source code dependencies
-LSP_COMMON_LIB_VERSION := 1.0.39
+LSP_COMMON_LIB_VERSION := 1.0.40
LSP_COMMON_LIB_NAME := lsp-common-lib
LSP_COMMON_LIB_TYPE := src
LSP_COMMON_LIB_URL_RO := https://github.com/lsp-plugins/$(LSP_COMMON_LIB_NAME).git
LSP_COMMON_LIB_URL_RW := git@github.com:lsp-plugins/$(LSP_COMMON_LIB_NAME).git
-LSP_DSP_LIB_VERSION := 1.0.27
+LSP_DSP_LIB_VERSION := 1.0.28
LSP_DSP_LIB_NAME := lsp-dsp-lib
LSP_DSP_LIB_TYPE := src
LSP_DSP_LIB_URL_RO := https://github.com/lsp-plugins/$(LSP_DSP_LIB_NAME).git
LSP_DSP_LIB_URL_RW := git@github.com:lsp-plugins/$(LSP_DSP_LIB_NAME).git
-LSP_LLTL_LIB_VERSION := 1.0.22
+LSP_LLTL_LIB_VERSION := 1.0.23
LSP_LLTL_LIB_NAME := lsp-lltl-lib
LSP_LLTL_LIB_TYPE := src
LSP_LLTL_LIB_URL_RO := https://github.com/lsp-plugins/$(LSP_LLTL_LIB_NAME).git
LSP_LLTL_LIB_URL_RW := git@github.com:lsp-plugins/$(LSP_LLTL_LIB_NAME).git
-LSP_RUNTIME_LIB_VERSION := 1.0.25
+LSP_RUNTIME_LIB_VERSION := 1.0.26
LSP_RUNTIME_LIB_NAME := lsp-runtime-lib
LSP_RUNTIME_LIB_TYPE := src
LSP_RUNTIME_LIB_URL_RO := https://github.com/lsp-plugins/$(LSP_RUNTIME_LIB_NAME).git
LSP_RUNTIME_LIB_URL_RW := git@github.com:lsp-plugins/$(LSP_RUNTIME_LIB_NAME).git
-LSP_TEST_FW_VERSION := 1.0.28
+LSP_TEST_FW_VERSION := 1.0.29
LSP_TEST_FW_NAME := lsp-test-fw
LSP_TEST_FW_TYPE := src
LSP_TEST_FW_URL_RO := https://github.com/lsp-plugins/$(LSP_TEST_FW_NAME).git
diff --git a/project.mk b/project.mk
index 823e6ea..dd18dd1 100644
--- a/project.mk
+++ b/project.mk
@@ -23,5 +23,5 @@ ARTIFACT_ID = LSP_DSP_UNITS
ARTIFACT_NAME = lsp-dsp-units
ARTIFACT_DESC = High-level classes for performing DSP
ARTIFACT_HEADERS = lsp-plug.in
-ARTIFACT_VERSION = 1.0.26
+ARTIFACT_VERSION = 1.0.27
diff --git a/res/test/meters/loop.wav b/res/test/meters/loop.wav
new file mode 100644
index 0000000..5bf4142
Binary files /dev/null and b/res/test/meters/loop.wav differ
diff --git a/src/main/meters/Correlometer.cpp b/src/main/meters/Correlometer.cpp
index 401a6c7..ba75de2 100644
--- a/src/main/meters/Correlometer.cpp
+++ b/src/main/meters/Correlometer.cpp
@@ -58,13 +58,11 @@ namespace lsp
void Correlometer::destroy()
{
- if (pData != NULL)
- {
- free_aligned(pData);
- vInA = NULL;
- vInB = NULL;
- pData = NULL;
- }
+ free_aligned(pData);
+
+ vInA = NULL;
+ vInB = NULL;
+ pData = NULL;
}
status_t Correlometer::init(size_t max_period)
@@ -201,6 +199,7 @@ namespace lsp
v->write("nHead", nHead);
v->write("nMaxPeriod", nMaxPeriod);
v->write("nPeriod", nPeriod);
+ v->write("nWindow", nWindow);
v->write("nFlags", nFlags);
v->write("pData", pData);
diff --git a/src/main/meters/ILUFSMeter.cpp b/src/main/meters/ILUFSMeter.cpp
new file mode 100644
index 0000000..39b099b
--- /dev/null
+++ b/src/main/meters/ILUFSMeter.cpp
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 16 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ static constexpr size_t BUFFER_SIZE = 0x400;
+
+ /**
+ * For Ga = -70 LKFS, D = -0.691 LKFS:
+ * because Ga = D + 10*log10(LT)
+ * Then: LT = 10 ^ ((Ga - D) / 10)
+ */
+ static constexpr float GATING_ABS_THRESH = 1.17246530458e-07;
+
+ /**
+ * We computed weigthed sum S = sum(Gi * sum(zij) / |Jg|);
+ * We know that for D = -0.691 LKFS Gated Loudness Lkg = D + 10*log10(S)
+ * We know that for R = -10 LKFS relative threshold Gr = D + 10*log10(S) + R
+ *
+ * Then: Gr = Lkg + R.
+ * Then: 10*log10(Kr * S) = 10*log10(S) + R
+ * 10*log10(Kr * S) - 10*log10(S) = R
+ * 10*log10(Kr) = R
+ * Kr = 10^(R/10) = 10^(-10/10) = 0.1
+ *
+ */
+ static constexpr float GATING_REL_THRESH = 0.1f;
+
+ ILUFSMeter::ILUFSMeter()
+ {
+ construct();
+ }
+
+ ILUFSMeter::~ILUFSMeter()
+ {
+ destroy();
+ }
+
+ void ILUFSMeter::construct()
+ {
+ vChannels = NULL;
+ vBuffer = NULL;
+ vLoudness = NULL;
+
+ fBlockPeriod = 0.0f;
+ fIntTime = 0.0f;
+ fMaxIntTime = 0.0f;
+ fAvgCoeff = 0.0f;
+ fLoudness = 0.0f;
+
+ nBlockSize = 0;
+ nBlockOffset = 0;
+ nBlockPart = 0;
+ nMSSize = 0;
+ nMSHead = 0;
+ nMSInt = 0;
+ nMSCount = -3;
+
+ nSampleRate = 0;
+ nChannels = 0;
+ nFlags = 0;
+ enWeight = bs::WEIGHT_K;
+
+ pData = NULL;
+ pVarData = NULL;
+ }
+
+ void ILUFSMeter::destroy()
+ {
+ if (pData != NULL)
+ {
+ for (size_t i=0; isFilter.destroy();
+ c->sBank.destroy();
+ }
+
+ free_aligned(pData);
+ pData = NULL;
+ vChannels = NULL;
+ vBuffer = NULL;
+ }
+
+ if (pVarData != NULL)
+ {
+ free_aligned(pVarData);
+ vLoudness = NULL;
+ pVarData = NULL;
+ }
+ }
+
+ status_t ILUFSMeter::init(size_t channels, float max_int_time, float block_period)
+ {
+ destroy();
+
+ // Allocate data
+ const size_t szof_channels = align_size(channels * sizeof(channel_t), DEFAULT_ALIGN);
+ const size_t szof_buffer = align_size(sizeof(float) * BUFFER_SIZE, DEFAULT_ALIGN);
+ const size_t to_alloc =
+ szof_channels +
+ szof_buffer;
+
+ uint8_t *ptr = alloc_aligned(pData, to_alloc, DEFAULT_ALIGN);
+ if (ptr == NULL)
+ return STATUS_NO_MEM;
+
+ // Allocate buffers
+ vChannels = advance_ptr_bytes(ptr, szof_channels);
+ vBuffer = advance_ptr_bytes(ptr, szof_buffer);
+
+ // Cleanup and init state for each channel
+ dsp::fill_zero(vBuffer, BUFFER_SIZE);
+ for (size_t i=0; isBank.construct();
+ c->sFilter.construct();
+
+ if (!c->sBank.init(4))
+ return STATUS_NO_MEM;
+ if (!c->sFilter.init(&c->sBank))
+ return STATUS_NO_MEM;
+
+
+ c->vIn = NULL;
+ for (size_t i=0; i<4; ++i)
+ c->vBlock[i] = 0.0f;
+
+ c->fWeight = 0.0f;
+ c->enDesignation = bs::CHANNEL_NONE;
+
+ c->nFlags = C_ENABLED;
+ }
+
+ // Set-up default designations
+ if (channels == 1)
+ {
+ channel_t *c = &vChannels[0];
+ c->enDesignation = bs::CHANNEL_CENTER;
+ c->fWeight = bs::channel_weighting(c->enDesignation);
+ }
+ else if (channels == 2)
+ {
+ channel_t *l = &vChannels[0];
+ channel_t *r = &vChannels[1];
+
+ l->enDesignation = bs::CHANNEL_LEFT;
+ l->fWeight = bs::channel_weighting(l->enDesignation);
+ r->enDesignation = bs::CHANNEL_RIGHT;
+ r->fWeight = bs::channel_weighting(r->enDesignation);
+ }
+
+ // Initialize channels
+ for (size_t i=0; isFilter.init(&c->sBank))
+ return STATUS_NO_MEM;
+ }
+
+ // Initialize settings
+ fBlockPeriod = block_period;
+ fIntTime = max_int_time;
+ fMaxIntTime = max_int_time;
+ fAvgCoeff = 1.0f;
+ fLoudness = 0.0f;
+
+ nBlockSize = 0;
+ nBlockOffset = 0;
+ nBlockPart = 0;
+
+ nMSSize = 0;
+ nMSHead = 0;
+ nMSInt = 0;
+ nMSCount = -3;
+
+ nSampleRate = 0;
+ nChannels = channels;
+ nFlags = F_UPD_ALL;
+ enWeight = bs::WEIGHT_K;
+
+ return STATUS_OK;
+ }
+
+ status_t ILUFSMeter::bind(size_t id, const float *in)
+ {
+ if (id >= nChannels)
+ return STATUS_OVERFLOW;
+
+ channel_t *c = &vChannels[id];
+ c->vIn = in;
+
+ return STATUS_OK;
+ }
+
+ status_t ILUFSMeter::set_designation(size_t id, bs::channel_t designation)
+ {
+ if (id >= nChannels)
+ return STATUS_OVERFLOW;
+
+ channel_t *c = &vChannels[id];
+ c->enDesignation = designation;
+ c->fWeight = bs::channel_weighting(designation);
+
+ return STATUS_OK;
+ }
+
+ bs::channel_t ILUFSMeter::designation(size_t id) const
+ {
+ return (id < nChannels) ? vChannels[id].enDesignation : bs::CHANNEL_NONE;
+ }
+
+ status_t ILUFSMeter::set_active(size_t id, bool active)
+ {
+ if (id >= nChannels)
+ return STATUS_OVERFLOW;
+
+ channel_t *c = &vChannels[id];
+ if (bool(c->nFlags & C_ENABLED) == active)
+ return STATUS_OK;
+
+ c->nFlags = lsp_setflag(c->nFlags, C_ENABLED, active);
+
+ return STATUS_OK;
+ }
+
+ bool ILUFSMeter::active(size_t id) const
+ {
+ return (id < nChannels) ? bool(vChannels[id].nFlags & C_ENABLED) : false;
+ }
+
+ void ILUFSMeter::set_weighting(bs::weighting_t weighting)
+ {
+ if (weighting == enWeight)
+ return;
+
+ enWeight = weighting;
+ nFlags |= F_UPD_FILTERS;
+ }
+
+ void ILUFSMeter::set_integration_period(float period)
+ {
+ period = lsp_limit(period, fBlockPeriod * 0.001f, fMaxIntTime);
+ if (fIntTime == period)
+ return;
+
+ fIntTime = period;
+ nFlags |= F_UPD_TIME;
+ }
+
+ status_t ILUFSMeter::set_sample_rate(size_t sample_rate)
+ {
+ if (nSampleRate == sample_rate)
+ return STATUS_OK;
+
+ // Reallocate ring buffers for RMS estimation and lookahead
+ const size_t blk_count = dspu::millis_to_samples(sample_rate, fBlockPeriod * 0.25f); // 75% overlapping
+ size_t int_count = (dspu::seconds_to_samples(sample_rate, fMaxIntTime) + blk_count - 1) / blk_count;
+
+ const size_t int_szof = align_size(int_count * sizeof(float), DEFAULT_ALIGN);
+ int_count = int_szof / sizeof(float);
+
+ size_t to_alloc = int_szof;
+
+ uint8_t *buf = realloc_aligned(pVarData, to_alloc, DEFAULT_ALIGN);
+ if (buf == NULL)
+ return STATUS_NO_MEM;
+
+ // Store new pointers to buffers
+ vLoudness = advance_ptr_bytes(buf, int_count);
+
+ // Update parameters
+ fAvgCoeff = 0.25f / float(blk_count);
+ nSampleRate = sample_rate;
+ nBlockSize = blk_count;
+ nMSSize = int_count;
+ nFlags = F_UPD_ALL;
+
+ // Clear all buffers
+ clear();
+
+ return STATUS_OK;
+ }
+
+ float ILUFSMeter::compute_gated_loudness(float threshold)
+ {
+ float loudness = 0.0f;
+ if (nMSCount <= 0)
+ return loudness;
+
+ size_t tail = (nMSHead + nMSSize - nMSCount) % nMSSize;
+ size_t count = 0;
+
+ for (ssize_t j=0; j 0) ? loudness / float(count) : 0.0f;
+ }
+
+ void ILUFSMeter::process(float *out, size_t count, float gain)
+ {
+ update_settings();
+
+ for (size_t offset = 0; offset < count; )
+ {
+ // Estimate how many samples we are ready to process
+ const size_t to_do = lsp_min(count - offset, nBlockSize - nBlockOffset, BUFFER_SIZE);
+ if (to_do > 0)
+ {
+ // Compute square sum
+ for (size_t i=0; ivIn != NULL) && (c->nFlags & C_ENABLED))
+ {
+ // Apply the weighting filter
+ c->sFilter.process(vBuffer, &c->vIn[offset], to_do);
+ // Compute the sum of squares
+ c->vBlock[nBlockPart] += dsp::h_sqr_sum(vBuffer, to_do);
+ }
+ }
+
+ nBlockOffset += to_do;
+ }
+
+ // Output the loudness
+ if (out != NULL)
+ dsp::fill(&out[offset], fLoudness * gain, to_do);
+
+ // Perform metering if we exceed a quarter of a gating block size
+ if (nBlockOffset >= nBlockSize)
+ {
+ // For each channel push new block to the history buffer
+ // Compute the loudness of the gating block and store to the buffer
+ float loudness = 0.0f;
+ for (size_t i=0; ivBlock[0];
+ const float s = (blk[0] + blk[1] + blk[2] + blk[3]) * fAvgCoeff;
+
+ loudness += c->fWeight * s;
+ }
+ vLoudness[nMSHead] = loudness;
+ nMSHead = (nMSHead + 1) % nMSSize;
+ nMSCount = lsp_min(nMSCount + 1, nMSInt); // Increment number of blocks for processing
+
+ // Compute integrated loudness, two-stage
+ // There is no necessity to apply the second stage if the loudness threshold
+ // is less than absolute threshold
+ loudness = compute_gated_loudness(GATING_ABS_THRESH);
+ const float thresh = loudness * GATING_REL_THRESH;
+ if (thresh > GATING_ABS_THRESH)
+ loudness = compute_gated_loudness(thresh);
+
+ // Convert the loudness for output. Because we use amplitude decibels,
+ // we need to extract square root
+ fLoudness = sqrtf(loudness);
+
+ // Reset block size and advance positions
+ nBlockOffset = 0;
+ nBlockPart = (nBlockPart + 1) & 0x3;
+ for (size_t i=0; isBank.begin();
+ switch (enWeight)
+ {
+ case bs::WEIGHT_A: fp.nType = dspu::FLT_A_WEIGHTED; break;
+ case bs::WEIGHT_B: fp.nType = dspu::FLT_B_WEIGHTED; break;
+ case bs::WEIGHT_C: fp.nType = dspu::FLT_C_WEIGHTED; break;
+ case bs::WEIGHT_D: fp.nType = dspu::FLT_D_WEIGHTED; break;
+ case bs::WEIGHT_K: fp.nType = dspu::FLT_K_WEIGHTED; break;
+ case bs::WEIGHT_NONE:
+ default:
+ break;
+ }
+
+ c->sFilter.update(nSampleRate, &fp);
+ c->sFilter.rebuild();
+ c->sBank.end(true);
+ }
+ }
+
+ // Reset flags
+ nFlags = 0;
+ }
+
+ void ILUFSMeter::clear()
+ {
+ for (size_t i=0; isFilter.clear();
+ for (size_t i=0; i<4; ++i)
+ c->vBlock[i] = 0.0f;
+ }
+ dsp::fill_zero(vLoudness, nMSSize);
+
+ fLoudness = 0.0f;
+
+ nBlockOffset = 0;
+ nBlockPart = 0;
+
+ nMSHead = 0;
+ nMSInt = 0;
+ nMSCount = -3;
+ }
+
+ void ILUFSMeter::dump(IStateDumper *v) const
+ {
+ v->begin_array("vChannels", vChannels, nChannels);
+ {
+ for (size_t i=0; ibegin_object(c, sizeof(channel_t));
+ {
+ v->write_object("sBank", &c->sBank);
+ v->write_object("sFilter", &c->sFilter);
+
+ v->write("vIn", c->vIn);
+ v->writev("vBlock", c->vBlock, 4);
+
+ v->write("fWeight", c->fWeight);
+ v->write("enDesignation", c->enDesignation);
+
+ v->write("nFlags", c->nFlags);
+ }
+ v->end_object();
+ }
+ }
+ v->end_array();
+
+ v->write("vBuffer", vBuffer);
+ v->write("vLoudness", vLoudness);
+
+ v->write("fBlockPeriod", fBlockPeriod);
+ v->write("fIntTime", fIntTime);
+ v->write("fMaxIntTime", fMaxIntTime);
+ v->write("fAvgCoeff", fAvgCoeff);
+ v->write("fLoudness", fLoudness);
+
+ v->write("nBlockSize", nBlockSize);
+ v->write("nBlockOffset", nBlockOffset);
+ v->write("nBlockPart", nBlockPart);
+ v->write("nMSSize", nMSSize);
+ v->write("nMSHead", nMSHead);
+ v->write("nMSInt", nMSInt);
+ v->write("nMSCount", nMSCount);
+
+ v->write("nSampleRate", nSampleRate);
+ v->write("nChannels", nChannels);
+ v->write("nFlags", nFlags);
+ v->write("enWeight", enWeight);
+
+ v->write("pData", pData);
+ v->write("pVarData", pVarData);
+ }
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
diff --git a/src/main/meters/LoudnessMeter.cpp b/src/main/meters/LoudnessMeter.cpp
index 2279922..0eac020 100644
--- a/src/main/meters/LoudnessMeter.cpp
+++ b/src/main/meters/LoudnessMeter.cpp
@@ -162,7 +162,7 @@ namespace lsp
}
// Initialize settings
- fPeriod = lsp_min(max_period, dspu::bs::LUFS_MEASURE_PERIOD_MS); // BS.1770-4: RMS measurement period
+ fPeriod = lsp_min(max_period, dspu::bs::LUFS_MEASURE_PERIOD_MS); // BS.1770-5: RMS measurement period
fMaxPeriod = max_period;
fAvgCoeff = 1.0f;
@@ -304,9 +304,7 @@ namespace lsp
for (size_t i=0; ivData = reinterpret_cast(buf);
- buf += szof_period;
+ c->vData = advance_ptr_bytes(buf, szof_period);
}
// Update settings
diff --git a/src/main/meters/Panometer.cpp b/src/main/meters/Panometer.cpp
new file mode 100644
index 0000000..98bda55
--- /dev/null
+++ b/src/main/meters/Panometer.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 12 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ constexpr size_t BUFFER_SIZE = 0x400;
+
+ Panometer::Panometer()
+ {
+ construct();
+ }
+
+ Panometer::~Panometer()
+ {
+ destroy();
+ }
+
+ void Panometer::construct()
+ {
+ vInA = NULL;
+ vInB = NULL;
+ enPanLaw = PAN_LAW_EQUAL_POWER;
+ fValueA = 0.0f;
+ fValueB = 0.0f;
+ fNorm = 0.0f;
+ fDefault = 0.5f;
+ nCapacity = 0;
+ nHead = 0;
+ nMaxPeriod = 0;
+ nPeriod = 0;
+ nWindow = 0;
+
+ pData = NULL;
+ }
+
+ void Panometer::destroy()
+ {
+ if (pData != NULL)
+ {
+ free_aligned(pData);
+ vInA = NULL;
+ vInB = NULL;
+ pData = NULL;
+ }
+ }
+
+ status_t Panometer::init(size_t max_period)
+ {
+ destroy();
+
+ // Allocate data
+ const size_t capacity = align_size(max_period + BUFFER_SIZE, DEFAULT_ALIGN);
+ const size_t szof_buf = capacity * sizeof(float);
+ const size_t to_alloc = 2 * szof_buf;
+ uint8_t *data = NULL;
+
+ uint8_t *ptr = alloc_aligned(data, to_alloc);
+ if (ptr == NULL)
+ return STATUS_NO_MEM;
+
+ // Commit state
+ vInA = advance_ptr_bytes(ptr, szof_buf);
+ vInB = advance_ptr_bytes(ptr, szof_buf);
+ nCapacity = capacity;
+ nHead = 0;
+ nMaxPeriod = max_period;
+ nPeriod = 0;
+
+ free_aligned(pData);
+ pData = data;
+
+ // Cleanup buffers (both vInA and vInB -> nCapacity * 2)
+ dsp::fill_zero(vInA, nCapacity * 2);
+
+ return STATUS_OK;
+ }
+
+ void Panometer::set_pan_law(pan_law_t law)
+ {
+ enPanLaw = law;
+ }
+
+ void Panometer::set_default_pan(float dfl)
+ {
+ fDefault = dfl;
+ }
+
+ void Panometer::set_period(size_t period)
+ {
+ period = lsp_min(period, nMaxPeriod);
+ if (period == nPeriod)
+ return;
+
+ nPeriod = period;
+ nWindow = period;
+ fValueA = 0.0f;
+ fValueB = 0.0f;
+ fNorm = (period > 0) ? 1.0f / period : 1.0f;
+ }
+
+ void Panometer::clear()
+ {
+ dsp::fill_zero(vInA, nCapacity);
+ dsp::fill_zero(vInB, nCapacity);
+
+ nHead = nPeriod;
+ }
+
+ void Panometer::process(float *dst, const float *a, const float *b, size_t count)
+ {
+ for (size_t offset=0; offset= nPeriod)
+ {
+ fValueA = 0.0f;
+ fValueB = 0.0f;
+
+ if (nHead < tail)
+ {
+ fValueA = dsp::h_sum(&vInA[tail], nCapacity - tail);
+ fValueB = dsp::h_sum(&vInB[tail], nCapacity - tail);
+ fValueA += dsp::h_sum(&vInA[0], nHead);
+ fValueB += dsp::h_sum(&vInB[0], nHead);
+ }
+ else
+ {
+ fValueA = dsp::h_sum(&vInA[tail], nPeriod);
+ fValueB = dsp::h_sum(&vInB[tail], nPeriod);
+ }
+ nWindow = 0;
+ }
+
+ const size_t can_do = nPeriod - nWindow;
+ size_t to_do = lsp_min(
+ count - offset, // Number of samples left in input buffers
+ nCapacity - nMaxPeriod, // Number of free samples in the ring buffer
+ nCapacity - nHead, // The number of samples before head goes out of ring buffer
+ nCapacity - tail); // The number of samples before tail goes out of ring buffer
+ to_do = lsp_min(to_do, can_do);
+
+ // Fill buffers with data
+ float *ah = &vInA[nHead];
+ float *bh = &vInB[nHead];
+ float *at = &vInA[tail];
+ float *bt = &vInB[tail];
+
+ dsp::sqr2(ah, &a[offset], to_do);
+ dsp::sqr2(bh, &b[offset], to_do);
+
+ // Estimate the actual pan value
+ float va = fValueA;
+ float vb = fValueB;
+ if (enPanLaw == PAN_LAW_LINEAR)
+ {
+ for (size_t i=0; i 1e-18f) ? sr / den : fDefault;
+ }
+ }
+ else
+ {
+ for (size_t i=0; i 1e-36f) ? sr / den : fDefault;
+ }
+ }
+ fValueA = va;
+ fValueB = vb;
+
+ // Update pointers and counters
+ nHead = (nHead + to_do) % nCapacity;
+ nWindow += to_do;
+ offset += to_do;
+ dst += to_do;
+ }
+ }
+
+ void Panometer::dump(IStateDumper *v) const
+ {
+ v->write("vInA", vInA);
+ v->write("vInB", vInB);
+ v->write("enPanLaw", enPanLaw);
+ v->write("fValueA", fValueA);
+ v->write("fValueB", fValueB);
+ v->write("fNorm", fNorm);
+ v->write("fDefault", fDefault);
+ v->write("nCapacity", nCapacity);
+ v->write("nHead", nHead);
+ v->write("nMaxPeriod", nMaxPeriod);
+ v->write("nPeriod", nPeriod);
+ v->write("nWindow", nWindow);
+
+ v->write("pData", pData);
+ }
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
diff --git a/src/main/meters/TruePeakMeter.cpp b/src/main/meters/TruePeakMeter.cpp
new file mode 100644
index 0000000..b4db17a
--- /dev/null
+++ b/src/main/meters/TruePeakMeter.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 16 окт. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ static constexpr size_t BASE_FREQUENCY = 44100;
+ static constexpr size_t TRUE_PEAK_FREQUENCY = BASE_FREQUENCY * 4;
+ static constexpr size_t TRUE_PEAK_LATENCY = 10;
+ static constexpr size_t MAX_BUFFER_TAIL = TRUE_PEAK_LATENCY * 2 * 8;
+ static constexpr size_t BUFFER_SIZE = 0x1000;
+
+ TruePeakMeter::TruePeakMeter()
+ {
+ construct();
+ }
+
+ TruePeakMeter::~TruePeakMeter()
+ {
+ destroy();
+ }
+
+ void TruePeakMeter::construct()
+ {
+ nSampleRate = 0;
+ nHead = 0;
+ nTimes = 0;
+ bUpdate = true;
+
+ pFunc = NULL;
+ pReduce = NULL;
+ vBuffer = NULL;
+ pData = NULL;
+ }
+
+ void TruePeakMeter::destroy()
+ {
+ if (pData != NULL)
+ free_aligned(pData);
+
+ pFunc = NULL;
+ vBuffer = NULL;
+ pData = NULL;
+ }
+
+ bool TruePeakMeter::init()
+ {
+ uint8_t *data = NULL;
+ vBuffer = alloc_aligned(data, BUFFER_SIZE + MAX_BUFFER_TAIL, 0x40);
+ if (vBuffer == NULL)
+ return false;
+
+ free_aligned(pData);
+ pData = data;
+
+ clear();
+ return true;
+ }
+
+ uint8_t TruePeakMeter::calc_oversampling_multiplier(size_t sample_rate)
+ {
+ if (sample_rate >= TRUE_PEAK_FREQUENCY)
+ return 0;
+ if (sample_rate * 2 >= TRUE_PEAK_FREQUENCY)
+ return 2;
+ if (sample_rate * 3 >= TRUE_PEAK_FREQUENCY)
+ return 3;
+ if (sample_rate * 4 >= TRUE_PEAK_FREQUENCY)
+ return 4;
+ if (sample_rate * 6 >= TRUE_PEAK_FREQUENCY)
+ return 6;
+
+ return 8;
+ }
+
+ void TruePeakMeter::set_sample_rate(uint32_t sr)
+ {
+ if (nSampleRate == sr)
+ return;
+
+ nSampleRate = sr;
+ bUpdate = true;
+ }
+
+ size_t TruePeakMeter::sample_rate() const
+ {
+ return nSampleRate;
+ }
+
+ void TruePeakMeter::reduce_2x(float *dst, const float *src, size_t count)
+ {
+ for (size_t i=0; iwrite("nSampleRate", nSampleRate);
+ v->write("nHead", nHead);
+ v->write("nTimes", nTimes);
+ v->write("bUpdate", bUpdate);
+
+ v->write("pFunc", pFunc);
+ v->write("pReduce", pReduce);
+ v->write("vBuffer", vBuffer);
+ v->write("pData", pData);
+ }
+
+ } /* namespce dspu */
+} /* namespace lsp */
+
diff --git a/src/main/sampling/Sample.cpp b/src/main/sampling/Sample.cpp
index f5129c3..fd43b1b 100644
--- a/src/main/sampling/Sample.cpp
+++ b/src/main/sampling/Sample.cpp
@@ -1338,6 +1338,7 @@ namespace lsp
status_t res = try_open_regular_file(is, path);
if (res == STATUS_OK)
return res;
+ const status_t unsuccess_result = res;
// Now we need to walk the whole path from the end to the begin and look for archive
LSPString item;
@@ -1351,7 +1352,7 @@ namespace lsp
{
// Check that there is nothing to do with
if ((parent.is_root()) || (parent.is_empty()))
- return STATUS_NOT_FOUND;
+ return unsuccess_result;
// Remove child entry from parent and prepend for the child
if ((res = parent.get_last(&item)) != STATUS_OK)
diff --git a/src/main/stat/QuantizedCounter.cpp b/src/main/stat/QuantizedCounter.cpp
new file mode 100644
index 0000000..e242947
--- /dev/null
+++ b/src/main/stat/QuantizedCounter.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 13 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ constexpr size_t BUFFER_GAP = 0x400;
+
+ QuantizedCounter::QuantizedCounter()
+ {
+ construct();
+ }
+
+ QuantizedCounter::~QuantizedCounter()
+ {
+ destroy();
+ }
+
+ void QuantizedCounter::construct()
+ {
+ nPeriod = 0;
+ nLevels = 0;
+ nHead = 0;
+ nCount = 0;
+ nMaxPeriod = 0;
+ nMaxLevels = 0;
+
+ fMinValue = 0.0f;
+ fMaxValue = 0.0f;
+ fRStep = 0.0f;
+
+ bUpdate = true;
+
+ vHistory = NULL;
+ vCounters = NULL;
+
+ pData = NULL;
+ }
+
+ void QuantizedCounter::destroy()
+ {
+ free_aligned(pData);
+
+ vHistory = NULL;
+ vCounters = NULL;
+ }
+
+ status_t QuantizedCounter::init(size_t max_period, size_t max_levels)
+ {
+ size_t szof_history = align_size((max_period + BUFFER_GAP) * sizeof(float), DEFAULT_ALIGN);
+ size_t szof_levels = align_size((max_levels + 2) * sizeof(uint32_t), DEFAULT_ALIGN);
+
+ uint8_t *data = NULL;
+ uint8_t *ptr = alloc_aligned(data, szof_history + szof_levels);
+ if (ptr == NULL)
+ return STATUS_NO_MEM;
+
+ vHistory = advance_ptr_bytes(ptr, szof_history);
+ vCounters = advance_ptr_bytes(ptr, szof_levels);
+ nHead = 0;
+ nCapacity = szof_history / sizeof(float);
+ nCount = 0;
+ nMaxPeriod = max_period;
+ nMaxLevels = max_levels;
+
+ dsp::fill_zero(vHistory, nCapacity);
+ for (size_t i=0; i= max_level)
+ index = nMaxLevels + 1;
+
+ ++vCounters[index];
+ }
+ }
+
+ void QuantizedCounter::dec_counters(const float *src, size_t count)
+ {
+ const int32_t max_level = nLevels;
+ for (size_t i=0; i= max_level)
+ index = nMaxLevels + 1;
+
+ --vCounters[index];
+ }
+ }
+
+ void QuantizedCounter::evict_values()
+ {
+ size_t tail = (nHead + nCapacity - nCount) % nCapacity;
+ while (nCount > nPeriod)
+ {
+ const size_t to_do = lsp_min(nCount - nPeriod, nCapacity - tail);
+
+ dec_counters(&vHistory[tail], to_do);
+
+ nCount -= to_do;
+ tail = (tail + to_do) % nCapacity;
+ }
+ }
+
+ void QuantizedCounter::process(const float *data, size_t count)
+ {
+ // Update settings if necessary
+ update_settings();
+
+ // First, we need to evict values that exceed the maximum allowed period
+ evict_values();
+
+ for (size_t offset=0; offset < count; )
+ {
+ const size_t to_do = lsp_min(count - offset, nCapacity - nHead);
+
+ // Put new values to the buffer and update statistics
+ dsp::copy(&vHistory[nHead], &data[offset], to_do);
+ inc_counters(&data[offset], to_do);
+ nCount += to_do;
+ nHead = (nHead + to_do) % nCapacity;
+
+ // Evict old values from the buffer
+ evict_values();
+
+ // Advance in cycle
+ offset += to_do;
+ }
+ }
+
+ void QuantizedCounter::dump(IStateDumper *v) const
+ {
+ v->write("nPeriod", nPeriod);
+ v->write("nLevels", nLevels);
+ v->write("nHead", nHead);
+ v->write("nCapacity", nCapacity);
+ v->write("nCount", nCount);
+ v->write("nMaxPeriod", nMaxPeriod);
+ v->write("nMaxLevels", nMaxLevels);
+
+ v->write("fMinValue", fMinValue);
+ v->write("fMaxValue", fMaxValue);
+ v->write("fRStep", fRStep);
+ v->write("bUpdate", bUpdate);
+
+ v->write("vHistory", vHistory);
+ v->write("vCounters", vCounters);
+
+ v->write("pData", pData);
+ }
+
+ } /* namespace dspu */
+} /* namespace lsp */
+
diff --git a/src/main/util/Analyzer.cpp b/src/main/util/Analyzer.cpp
index c4f4acb..b2a5a9b 100644
--- a/src/main/util/Analyzer.cpp
+++ b/src/main/util/Analyzer.cpp
@@ -289,7 +289,7 @@ namespace lsp
windows::window(vWindow, fft_size, windows::window_t(nWindow));
// Update reactivity
if (nReconfigure & R_TAU)
- fTau = 1.0f - expf(logf(1.0f - M_SQRT1_2) / seconds_to_samples(float(nSampleRate) / float(nPeriod), fReactivity));
+ fTau = 1.0f - expf(logf(1.0f - M_SQRT1_2) / seconds_to_samples(fRate, fReactivity));
// Update counters
if (nReconfigure & R_COUNTERS)
{
diff --git a/src/main/util/MeterGraph.cpp b/src/main/util/MeterGraph.cpp
index db5f320..96e87b8 100644
--- a/src/main/util/MeterGraph.cpp
+++ b/src/main/util/MeterGraph.cpp
@@ -2,25 +2,26 @@
* Copyright (C) 2023 Linux Studio Plugins Project
* (C) 2023 Vladimir Sadovnikov
*
- * This file is part of lsp-plugins
+ * This file is part of lsp-dsp-units
* Created on: 20 мая 2016 г.
*
- * lsp-plugins is free software: you can redistribute it and/or modify
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
- * lsp-plugins is distributed in the hope that it will be useful,
+ * lsp-dsp-units 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
- * along with lsp-plugins. If not, see .
+ * along with lsp-dsp-units. If not, see .
*/
#include
#include
+#include
namespace lsp
{
@@ -43,7 +44,7 @@ namespace lsp
fCurrent = 0.0f;
nCount = 0;
nPeriod = 1;
- bMinimize = false;
+ enMethod = MM_ABS_MAXIMUM;
}
void MeterGraph::destroy()
@@ -67,21 +68,31 @@ namespace lsp
void MeterGraph::process(float sample)
{
- // Make sample positive
- if (sample < 0.0f)
- sample = - sample;
-
- if (bMinimize)
- {
- // Update current sample
- if ((nCount == 0) || (fCurrent < sample))
- fCurrent = sample;
- }
- else
+ // Update current sample
+ switch (enMethod)
{
- // Update current sample
- if ((nCount == 0) || (fCurrent > sample))
- fCurrent = sample;
+ case MM_SIGN_MINIMUM:
+ if ((nCount == 0) || (fabsf(fCurrent) > fabsf(sample)))
+ fCurrent = sample;
+ break;
+
+ case MM_SIGN_MAXIMUM:
+ if ((nCount == 0) || (fabsf(fCurrent) < fabsf(sample)))
+ fCurrent = sample;
+ break;
+
+ case MM_ABS_MINIMUM:
+ sample = fabsf(sample);
+ if ((nCount == 0) || (fCurrent > sample))
+ fCurrent = sample;
+ break;
+
+ default:
+ case MM_ABS_MAXIMUM:
+ sample = fabsf(sample);
+ if ((nCount == 0) || (fCurrent < sample))
+ fCurrent = sample;
+ break;
}
// Increment number of samples processed
@@ -95,128 +106,120 @@ namespace lsp
void MeterGraph::process(const float *s, size_t n)
{
- if (bMinimize)
+ while (n > 0)
{
- while (n > 0)
- {
- // Determine amount of samples to process
- ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
+ // Determine amount of samples to process
+ ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
- // Process the samples
- if (can_do > 0)
+ // Process the samples
+ if (can_do > 0)
+ {
+ // Process samples
+ switch (enMethod)
{
- // Get maximum sample
- float sample = dsp::abs_min(s, can_do);
- if ((nCount == 0) || (fCurrent > sample))
- fCurrent = sample;
-
- // Update counters and pointers
- nCount += can_do;
- n -= can_do;
- s += can_do;
+ case MM_SIGN_MINIMUM:
+ {
+ const float sample = dsp::sign_min(s, can_do);
+ if ((nCount == 0) || (fabsf(fCurrent) > fabsf(sample)))
+ fCurrent = sample;
+ break;
+ }
+ case MM_SIGN_MAXIMUM:
+ {
+ const float sample = dsp::sign_max(s, can_do);
+ if ((nCount == 0) || (fabsf(fCurrent) < fabsf(sample)))
+ fCurrent = sample;
+ break;
+ }
+ case MM_ABS_MINIMUM:
+ {
+ const float sample = dsp::abs_min(s, can_do);
+ if ((nCount == 0) || (fCurrent > sample))
+ fCurrent = sample;
+ break;
+ }
+ default:
+ case MM_ABS_MAXIMUM:
+ {
+ const float sample = dsp::abs_max(s, can_do);
+ if ((nCount == 0) || (fCurrent > sample))
+ fCurrent = sample;
+ break;
+ }
}
- // Check that need to switch to next sample
- if (nCount >= nPeriod)
- {
- // Append current sample to buffer
- sBuffer.process(fCurrent);
- nCount = 0;
- }
+ // Update counters and pointers
+ nCount += can_do;
+ n -= can_do;
+ s += can_do;
}
- }
- else
- {
- while (n > 0)
- {
- // Determine amount of samples to process
- ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
- // Process the samples
- if (can_do > 0)
- {
- // Get maximum sample
- float sample = dsp::abs_max(s, can_do);
- if ((nCount == 0) || (fCurrent < sample))
- fCurrent = sample;
-
- // Update counters and pointers
- nCount += can_do;
- n -= can_do;
- s += can_do;
- }
-
- // Check that need to switch to next sample
- if (nCount >= nPeriod)
- {
- // Append current sample to buffer and update counter
- sBuffer.process(fCurrent);
- nCount = 0;
- }
+ // Check that need to switch to next sample
+ if (nCount >= nPeriod)
+ {
+ // Append current sample to buffer
+ sBuffer.process(fCurrent);
+ nCount = 0;
}
}
}
void MeterGraph::process(const float *s, float gain, size_t n)
{
- if (bMinimize)
+ while (n > 0)
{
- while (n > 0)
- {
- // Determine amount of samples to process
- ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
+ // Determine amount of samples to process
+ ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
- // Process the samples
- if (can_do > 0)
+ // Process the samples
+ if (can_do > 0)
+ {
+ // Process samples
+ switch (enMethod)
{
- // Get maximum sample
- float sample = dsp::abs_min(s, can_do) * gain;
- if ((nCount == 0) || (fCurrent > sample))
- fCurrent = sample;
-
- // Update counters and pointers
- nCount += can_do;
- n -= can_do;
- s += can_do;
+ case MM_SIGN_MINIMUM:
+ {
+ const float sample = dsp::sign_min(s, can_do) * gain;
+ if ((nCount == 0) || (fabsf(fCurrent) > fabsf(sample)))
+ fCurrent = sample;
+ break;
+ }
+ case MM_SIGN_MAXIMUM:
+ {
+ const float sample = dsp::sign_max(s, can_do) * gain;
+ if ((nCount == 0) || (fabsf(fCurrent) < fabsf(sample)))
+ fCurrent = sample;
+ break;
+ }
+ case MM_ABS_MINIMUM:
+ {
+ const float sample = dsp::abs_min(s, can_do) * gain;
+ if ((nCount == 0) || (fCurrent > sample))
+ fCurrent = sample;
+ break;
+ }
+ default:
+ case MM_ABS_MAXIMUM:
+ {
+ const float sample = dsp::abs_max(s, can_do) * gain;
+ if ((nCount == 0) || (fCurrent > sample))
+ fCurrent = sample;
+ break;
+ }
}
- // Check that need to switch to next sample
- if (nCount >= nPeriod)
- {
- // Append current sample to buffer
- sBuffer.process(fCurrent);
- nCount = 0;
- }
+ // Update counters and pointers
+ nCount += can_do;
+ n -= can_do;
+ s += can_do;
}
- }
- else
- {
- while (n > 0)
- {
- // Determine amount of samples to process
- ssize_t can_do = lsp_min(ssize_t(n), ssize_t(nPeriod - nCount));
-
- // Process the samples
- if (can_do > 0)
- {
- // Get maximum sample
- float sample = dsp::abs_max(s, can_do) * gain;
- if ((nCount == 0) || (fCurrent < sample))
- fCurrent = sample;
-
- // Update counters and pointers
- nCount += can_do;
- n -= can_do;
- s += can_do;
- }
- // Check that need to switch to next sample
- if (nCount >= nPeriod)
- {
- // Append current sample to buffer and update counter
- sBuffer.process(fCurrent);
- nCount = 0;
- }
+ // Check that need to switch to next sample
+ if (nCount >= nPeriod)
+ {
+ // Append current sample to buffer
+ sBuffer.process(fCurrent);
+ nCount = 0;
}
}
}
@@ -227,7 +230,7 @@ namespace lsp
v->write("fCurrent", fCurrent);
v->write("nCount", nCount);
v->write("nPeriod", nPeriod);
- v->write("bMinimize", bMinimize);
+ v->write("enMethod", enMethod);
}
} /* namespace dspu */
} /* namespace lsp */
diff --git a/src/main/util/RawRingBuffer.cpp b/src/main/util/RawRingBuffer.cpp
index b27db28..e45e67b 100644
--- a/src/main/util/RawRingBuffer.cpp
+++ b/src/main/util/RawRingBuffer.cpp
@@ -85,6 +85,28 @@ namespace lsp
{
count = lsp_min(count, nCapacity);
+ if ((nHead + count) > nCapacity)
+ {
+ const size_t part1 = nCapacity - nHead;
+ const size_t part2 = count - part1;
+ dsp::copy(&pData[nHead], data, part1);
+ dsp::copy(pData, &data[part1], part2);
+ }
+ else
+ dsp::copy(&pData[nHead], data, count);
+
+ return count;
+ }
+
+ void RawRingBuffer::write(float data)
+ {
+ pData[nHead] = data;
+ }
+
+ size_t RawRingBuffer::push(const float *data, size_t count)
+ {
+ count = lsp_min(count, nCapacity);
+
if ((nHead + count) > nCapacity)
{
const size_t part1 = nCapacity - nHead;
@@ -102,9 +124,10 @@ namespace lsp
return count;
}
- void RawRingBuffer::write(float data)
+ void RawRingBuffer::push(float data)
{
pData[nHead] = data;
+ nHead = (nHead + 1) % nCapacity;
}
size_t RawRingBuffer::read(float *dst, size_t offset, size_t count)
@@ -125,7 +148,7 @@ namespace lsp
return count;
}
- float RawRingBuffer::read(size_t offset)
+ float RawRingBuffer::read(size_t offset) const
{
const size_t tail = (nHead + nCapacity - offset) % nCapacity;
return pData[tail];
@@ -161,6 +184,11 @@ namespace lsp
return lsp_min(nCapacity - nHead, nCapacity - tail);
}
+ void RawRingBuffer::fill(float value)
+ {
+ dsp::fill(pData, value, nCapacity);
+ }
+
void RawRingBuffer::dump(IStateDumper *v) const
{
v->write("pData", pData);
diff --git a/src/main/util/ScaledMeterGraph.cpp b/src/main/util/ScaledMeterGraph.cpp
new file mode 100644
index 0000000..2eb036d
--- /dev/null
+++ b/src/main/util/ScaledMeterGraph.cpp
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 05 окт 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+
+namespace lsp
+{
+ namespace dspu
+ {
+ static constexpr size_t FRAMES_GAP = 0x10;
+
+ ScaledMeterGraph::ScaledMeterGraph()
+ {
+ construct();
+ }
+
+ ScaledMeterGraph::~ScaledMeterGraph()
+ {
+ destroy();
+ }
+
+ void ScaledMeterGraph::construct()
+ {
+ sHistory.sBuffer.construct();
+ sHistory.fCurrent = 0.0f;
+ sHistory.nCount = 0;
+ sHistory.nPeriod = 0;
+ sHistory.nFrames = 0;
+
+ sFrames.sBuffer.construct();
+ sFrames.fCurrent = 0.0f;
+ sFrames.nCount = 0;
+ sFrames.nPeriod = 0;
+ sFrames.nFrames = 0;
+
+ nPeriod = 0;
+ nMaxPeriod = 0;
+ enMethod = MM_ABS_MAXIMUM;
+ }
+
+ void ScaledMeterGraph::destroy()
+ {
+ sHistory.sBuffer.destroy();
+ sFrames.sBuffer.destroy();
+ }
+
+ bool ScaledMeterGraph::init(size_t frames, size_t subsampling, size_t max_period)
+ {
+ const size_t samples = frames * max_period;
+ const size_t subframes = (samples + subsampling - 1) / subsampling;
+
+ if (!sHistory.sBuffer.init(subframes + FRAMES_GAP))
+ return false;
+ if (!sFrames.sBuffer.init(frames + FRAMES_GAP))
+ return false;
+
+ sHistory.nFrames = subframes;
+ sHistory.nPeriod = subsampling;
+ sHistory.fCurrent = 0;
+ sHistory.nCount = 0;
+
+ sFrames.nFrames = frames;
+ sFrames.nPeriod = 0;
+ sFrames.fCurrent = 0;
+ sFrames.nCount = 0;
+
+ nPeriod = 0;
+ nMaxPeriod = max_period;
+
+ return true;
+ }
+
+ void ScaledMeterGraph::set_period(size_t period)
+ {
+ nPeriod = lsp_limit(period, sHistory.nPeriod, nMaxPeriod);
+ }
+
+ void ScaledMeterGraph::set_method(meter_method_t m)
+ {
+ enMethod = m;
+ }
+
+ void ScaledMeterGraph::process_sampler(sampler_t *sampler, float sample)
+ {
+ // Update current sample
+ switch (enMethod)
+ {
+ case MM_SIGN_MINIMUM:
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) > fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+
+ case MM_SIGN_MAXIMUM:
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) < fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+
+ case MM_ABS_MINIMUM:
+ sample = fabsf(sample);
+ if ((sampler->nCount == 0) || (sampler->fCurrent > sample))
+ sampler->fCurrent = sample;
+ break;
+
+ default:
+ case MM_ABS_MAXIMUM:
+ sample = fabsf(sample);
+ if ((sampler->nCount == 0) || (sampler->fCurrent < sample))
+ sampler->fCurrent = sample;
+ break;
+ }
+
+ ++sampler->nCount;
+
+ // Append current sample to buffer if we processed enough samples
+ if (sampler->nCount >= sampler->nPeriod)
+ {
+ sampler->sBuffer.push(sample);
+ sampler->nCount = 0;
+ }
+ }
+
+ void ScaledMeterGraph::process_sampler(sampler_t *sampler, const float *src, size_t count)
+ {
+ // Sink full buffer to the sampler
+ for (size_t offset = 0; offset < count; )
+ {
+ size_t to_process = lsp_min(count - offset, sampler->nPeriod - sampler->nCount);
+
+ if (to_process > 0)
+ {
+ switch (enMethod)
+ {
+ case MM_SIGN_MINIMUM:
+ {
+ const float sample = dsp::sign_min(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) > fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+ }
+ case MM_SIGN_MAXIMUM:
+ {
+ const float sample = dsp::sign_max(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) < fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+ }
+ case MM_ABS_MINIMUM:
+ {
+ const float sample = dsp::abs_min(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (sampler->fCurrent > sample))
+ sampler->fCurrent = sample;
+ break;
+ }
+ default:
+ case MM_ABS_MAXIMUM:
+ {
+ const float sample = dsp::abs_max(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (sampler->fCurrent < sample))
+ sampler->fCurrent = sample;
+ break;
+ }
+ }
+
+ sampler->nCount += to_process;
+ offset += to_process;
+ }
+
+ // Append current sample to buffer if we processed enough samples
+ if (sampler->nCount >= sampler->nPeriod)
+ {
+ sampler->sBuffer.push(sampler->fCurrent);
+ sampler->nCount = 0;
+ }
+ }
+ }
+
+ void ScaledMeterGraph::process_sampler(sampler_t *sampler, const float *src, float gain, size_t count)
+ {
+ // Sink full buffer to the sampler
+ for (size_t offset = 0; offset < count; )
+ {
+ // Process samples if we can
+ size_t to_process = lsp_min(count - offset, sampler->nPeriod - sampler->nCount);
+ if (to_process > 0)
+ {
+ switch (enMethod)
+ {
+ case MM_SIGN_MINIMUM:
+ {
+ const float sample = dsp::sign_min(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) > fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+ }
+ case MM_SIGN_MAXIMUM:
+ {
+ const float sample = dsp::sign_max(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (fabsf(sampler->fCurrent) < fabsf(sample)))
+ sampler->fCurrent = sample;
+ break;
+ }
+ case MM_ABS_MINIMUM:
+ {
+ const float sample = dsp::abs_min(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (sampler->fCurrent > sample))
+ sampler->fCurrent = sample;
+ break;
+ }
+ default:
+ case MM_ABS_MAXIMUM:
+ {
+ const float sample = dsp::abs_max(&src[offset], to_process);
+ if ((sampler->nCount == 0) || (sampler->fCurrent < sample))
+ sampler->fCurrent = sample;
+ break;
+ }
+ }
+
+ sampler->nCount += to_process;
+ offset += to_process;
+ }
+
+ // Append current sample to buffer if we processed enough samples
+ if (sampler->nCount >= sampler->nPeriod)
+ {
+ sampler->sBuffer.push(sampler->fCurrent);
+ sampler->nCount = 0;
+ }
+ }
+ }
+
+ bool ScaledMeterGraph::update_period()
+ {
+ if (nPeriod == sFrames.nPeriod)
+ return false;
+
+ // Submit last measured samples to buffer if they are
+ if (sHistory.nCount > 0)
+ {
+ sHistory.sBuffer.push(sHistory.fCurrent);
+ sHistory.nCount = 0;
+ }
+ else if (sFrames.nCount > 0)
+ sHistory.sBuffer.push(sFrames.fCurrent);
+
+ sFrames.nCount = 0;
+ sFrames.nPeriod = nPeriod;
+ sFrames.fCurrent = -1.0f;
+
+ // Perform the decimation
+ const size_t samples = sFrames.nPeriod * sFrames.nFrames;
+ const size_t subsamples = (samples + sHistory.nPeriod - 1) / sHistory.nPeriod;
+
+ sFrames.sBuffer.reset();
+
+ // Sink full buffer to the sampler
+ for (size_t i = 0; i < subsamples; ++i)
+ {
+ // Process sub-sample
+ const float subsample = sHistory.sBuffer.read(subsamples - i);
+ switch (enMethod)
+ {
+ case MM_SIGN_MINIMUM:
+ {
+ if ((sFrames.fCurrent < 0.0f) || (fabsf(sFrames.fCurrent) > fabsf(subsample)))
+ sFrames.fCurrent = subsample;
+ break;
+ }
+ case MM_SIGN_MAXIMUM:
+ {
+ if ((sFrames.fCurrent < 0.0f) || (fabsf(sFrames.fCurrent) < fabsf(subsample)))
+ sFrames.fCurrent = subsample;
+ break;
+ }
+ case MM_ABS_MINIMUM:
+ {
+ const float sample = fabsf(subsample);
+ if ((sFrames.fCurrent < 0.0f) || (sFrames.fCurrent > sample))
+ sFrames.fCurrent = sample;
+ break;
+ }
+
+ default:
+ case MM_ABS_MAXIMUM:
+ {
+ const float sample = fabsf(subsample);
+ if ((sFrames.fCurrent < 0.0f) || (sFrames.fCurrent < sample))
+ sFrames.fCurrent = sample;
+ break;
+ }
+ }
+
+ sFrames.nCount += sHistory.nPeriod;
+
+ // Append current sample to buffer if we processed enough samples
+ if (sFrames.nCount >= sFrames.nPeriod)
+ {
+ sFrames.sBuffer.push(sFrames.fCurrent);
+ sFrames.nCount -= sFrames.nPeriod;
+ sFrames.fCurrent = -1.0f;
+ }
+ }
+
+ return true;
+ }
+
+ void ScaledMeterGraph::process(float sample)
+ {
+ process_sampler(&sHistory, sample);
+ if (!update_period())
+ process_sampler(&sFrames, sample);
+ }
+
+ void ScaledMeterGraph::process(const float *s, size_t count)
+ {
+ process_sampler(&sHistory, s, count);
+ if (!update_period())
+ process_sampler(&sFrames, s, count);
+ }
+
+ void ScaledMeterGraph::process(const float *s, float gain, size_t count)
+ {
+ process_sampler(&sHistory, s, gain, count);
+ if (!update_period())
+ process_sampler(&sFrames, s, gain, count);
+ }
+
+ void ScaledMeterGraph::read(float *dst, size_t count)
+ {
+ count = lsp_min(count, sFrames.nFrames);
+ sFrames.sBuffer.read(dst, count, count);
+ }
+
+ float ScaledMeterGraph::level() const
+ {
+ return sFrames.sBuffer.read(1);
+ }
+
+ void ScaledMeterGraph::fill(float level)
+ {
+ sFrames.sBuffer.fill(level);
+ sFrames.nCount = 0;
+
+ sHistory.sBuffer.fill(level);
+ sHistory.nCount = 0;
+ }
+
+ void ScaledMeterGraph::dump_sampler(IStateDumper *v, const char *name, const sampler_t *s)
+ {
+ v->begin_object(name, s, sizeof(sampler_t));
+ {
+ v->write_object("sBuffer", &s->sBuffer);
+ v->write("fCurrent", s->fCurrent);
+ v->write("nCount", s->nCount);
+ v->write("nPeriod", s->nPeriod);
+ v->write("nFrames", s->nFrames);
+ }
+ v->end_object();
+ }
+
+ void ScaledMeterGraph::dump(IStateDumper *v) const
+ {
+ dump_sampler(v, "sHistory", &sHistory);
+ dump_sampler(v, "sFrames", &sFrames);
+
+ v->write("nPeriod", nPeriod);
+ v->write("nMaxPeriod", nMaxPeriod);
+ v->write("enMethod", enMethod);
+ }
+ } /* namespace dspu */
+} /* namespace lsp */
+
+
+
diff --git a/src/test/mtest/meters/ilufs.cpp b/src/test/mtest/meters/ilufs.cpp
new file mode 100644
index 0000000..d82087d
--- /dev/null
+++ b/src/test/mtest/meters/ilufs.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 16 нояб. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+MTEST_BEGIN("dspu.meters", ilufs)
+
+ static constexpr size_t BUFFER_SIZE = 0x400;
+
+ MTEST_MAIN
+ {
+ dspu::Sample src, lufs, tpeak, psr;
+ io::Path src_path, dst_path;
+
+ // Load source file
+ MTEST_ASSERT(src_path.fmt("%s/meters/loop.wav", resources()) > 0);
+ printf("Loading file %s...\n", src_path.as_native());
+
+ MTEST_ASSERT(src.load(&src_path) == STATUS_OK);
+ MTEST_ASSERT(src.channels() == 2);
+
+ // Initialize meter
+ dspu::ILUFSMeter lm;
+ const float integration_period = dspu::samples_to_seconds(src.sample_rate(), src.length());
+
+ lm.init(2, integration_period, dspu::bs::LUFS_MEASURE_PERIOD_MS);
+ lm.set_sample_rate(src.sample_rate());
+ lm.set_integration_period(integration_period);
+ lm.set_weighting(dspu::bs::WEIGHT_K);
+ lm.set_active(0, true);
+ lm.set_active(1, true);
+ lm.set_designation(0, dspu::bs::CHANNEL_LEFT);
+ lm.set_designation(1, dspu::bs::CHANNEL_RIGHT);
+
+ dspu::Sample out;
+ MTEST_ASSERT(out.init(1, src.length(), src.length()));
+ out.set_sample_rate(src.sample_rate());
+
+ for (size_t offset=0; offset < src.length(); )
+ {
+ size_t to_process = lsp_min(src.length() - offset, BUFFER_SIZE);
+
+ lm.bind(0, src.channel(0, offset));
+ lm.bind(1, src.channel(1, offset));
+ lm.process(out.channel(0, offset), to_process);
+
+ offset += to_process;
+
+ printf("Integrated LUFS: %.2f LUFS\n", dspu::gain_to_db(lm.loudness() * dspu::bs::DBFS_TO_LUFS_SHIFT_GAIN));
+ }
+
+ // Save the results
+ MTEST_ASSERT(dst_path.fmt("%s/%s-lufs.wav", tempdir(), full_name()) > 0);
+ printf("Saving LUFS curve to %s...\n", dst_path.as_native());
+ MTEST_ASSERT(src.save(&dst_path) > 0);
+ }
+
+MTEST_END
+
+
+
diff --git a/src/test/mtest/meters/psr.cpp b/src/test/mtest/meters/psr.cpp
new file mode 100644
index 0000000..fc2e251
--- /dev/null
+++ b/src/test/mtest/meters/psr.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 06 ноя. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+MTEST_BEGIN("dspu.meters", psr)
+
+ static constexpr size_t BUFFER_SIZE = 0x400;
+
+ MTEST_MAIN
+ {
+ dspu::Sample src, lufs, tpeak, psr;
+ io::Path src_path, dst_path;
+
+ // Load source file
+ MTEST_ASSERT(src_path.fmt("%s/meters/loop.wav", resources()) > 0);
+ MTEST_ASSERT(src.load(&src_path) == STATUS_OK);
+ printf("Loading file %s...\n", src_path.as_native());
+
+ // Initialize meter
+ dspu::TruePeakMeter tpm[2];
+ for (size_t i=0; i<2; ++i)
+ {
+ tpm[i].init();
+ tpm[i].set_sample_rate(src.sample_rate());
+ tpm[i].update_settings();
+ }
+
+ dspu::Dither dth[2];
+ for (size_t i=0; i<2; ++i)
+ {
+ dth[i].init();
+ dth[i].set_bits(24);
+ }
+
+ dspu::LoudnessMeter lm;
+ lm.init(2, dspu::bs::LUFS_MEASURE_PERIOD_MS);
+
+ lm.set_sample_rate(src.sample_rate());
+ lm.set_period(dspu::bs::LUFS_MEASURE_PERIOD_MS);
+ lm.set_weighting(dspu::bs::WEIGHT_K);
+ lm.set_active(0, true);
+ lm.set_active(1, true);
+ lm.set_designation(0, dspu::bs::CHANNEL_LEFT);
+ lm.set_designation(1, dspu::bs::CHANNEL_RIGHT);
+
+ const size_t latency = dspu::millis_to_samples(src.sample_rate(), dspu::bs::LUFS_MEASURE_PERIOD_MS * 0.5f);
+ dspu::Delay tpd;
+ tpd.init(latency);
+ tpd.set_delay(0); //latency - tpm[0].latency());
+
+ const size_t length = src.length();
+ MTEST_ASSERT(src.prepend(length) == STATUS_OK);
+ MTEST_ASSERT(src.append(length) == STATUS_OK);
+
+ MTEST_ASSERT(lufs.init(1, src.length(), src.length()));
+ MTEST_ASSERT(tpeak.init(1, src.length(), src.length()));
+ MTEST_ASSERT(psr.init(1, src.length(), src.length()));
+
+ lufs.set_sample_rate(src.sample_rate());
+ tpeak.set_sample_rate(src.sample_rate());
+ psr.set_sample_rate(src.sample_rate());
+
+ float *buffer = static_cast(malloc(sizeof(float) * BUFFER_SIZE * 2));
+ MTEST_ASSERT(buffer != NULL);
+ lsp_finally { free(buffer); };
+
+ for (size_t offset=0; offset < src.length(); )
+ {
+ size_t to_process = lsp_min(src.length() - offset, BUFFER_SIZE);
+
+ float *b1 = buffer;
+ float *b2 = &buffer[BUFFER_SIZE];
+ float *in[2];
+ in[0] = const_cast(src.channel(0, offset));
+ in[1] = const_cast(src.channel(1, offset));
+
+ // Apply dithering
+ dth[0].process(in[0], in[0], to_process);
+ dth[1].process(in[1], in[1], to_process);
+
+ // Compute TruePeak value
+ tpm[0].process(b1, in[0], to_process);
+ tpm[1].process(b2, in[1], to_process);
+ dsp::pmax2(b1, b2, to_process);
+ tpd.process(b1, b1, to_process);
+
+ // Compute LUFS value
+ lm.bind(0, NULL, in[0], 0);
+ lm.bind(1, NULL, in[1], 0);
+ lm.process(b2, to_process, dspu::bs::DBFS_TO_LUFS_SHIFT_GAIN);
+
+ // Store values
+ dsp::copy(lufs.channel(0, offset), b1, to_process);
+ dsp::copy(tpeak.channel(0, offset), b2, to_process);
+
+ // Compute PSR value
+ for (size_t i=0; i= GAIN_AMP_M_60_DB) ? peak / lufs : 0.0f;
+ }
+
+ dsp::copy(psr.channel(0, offset), b1, to_process);
+
+ offset += to_process;
+ }
+
+ // Save the results
+ MTEST_ASSERT(dst_path.fmt("%s/%s-orig.wav", tempdir(), full_name()) > 0);
+ printf("Saving file %s...\n", dst_path.as_native());
+ MTEST_ASSERT(src.save(&dst_path) > 0);
+
+ MTEST_ASSERT(dst_path.fmt("%s/%s-tpeak.wav", tempdir(), full_name()) > 0);
+ printf("Saving file %s...\n", dst_path.as_native());
+ MTEST_ASSERT(tpeak.save(&dst_path) > 0);
+
+ MTEST_ASSERT(dst_path.fmt("%s/%s-lufs.wav", tempdir(), full_name()) > 0);
+ printf("Saving file %s...\n", dst_path.as_native());
+ MTEST_ASSERT(lufs.save(&dst_path) > 0);
+
+ MTEST_ASSERT(dst_path.fmt("%s/%s-psr.wav", tempdir(), full_name()) > 0);
+ printf("Saving file %s...\n", dst_path.as_native());
+ MTEST_ASSERT(psr.save(&dst_path) > 0);
+ }
+
+MTEST_END
+
+
+
diff --git a/src/test/mtest/meters/true_peak.cpp b/src/test/mtest/meters/true_peak.cpp
new file mode 100644
index 0000000..4784d19
--- /dev/null
+++ b/src/test/mtest/meters/true_peak.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 Linux Studio Plugins Project
+ * (C) 2024 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-dsp-units
+ * Created on: 17 окт. 2024 г.
+ *
+ * lsp-dsp-units is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-dsp-units 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-dsp-units. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+MTEST_BEGIN("dspu.meters", true_peak)
+
+ MTEST_MAIN
+ {
+ dspu::Sample src, dst;
+ io::Path src_path, dst_path;
+
+ // Load source file
+ MTEST_ASSERT(src_path.fmt("%s/meters/loop.wav", resources()) > 0);
+ MTEST_ASSERT(src.load(&src_path) == STATUS_OK);
+ printf("Loading file %s...\n", src_path.as_native());
+
+ // Initialize meter
+ dspu::TruePeakMeter tpm;
+ tpm.init();
+ tpm.set_sample_rate(src.sample_rate());
+ tpm.update_settings();
+ const size_t latency = tpm.latency();
+
+ MTEST_ASSERT(src.append(latency * 2) == STATUS_OK);
+
+ MTEST_ASSERT(dst.init(src.channels(), src.length(), src.length()));
+ dst.set_sample_rate(src.sample_rate());
+
+ for (size_t i=0; i 1.0f)
+ c[i] = -c[i];
+ }
+
+ printf("channel %d true peak level = %.2f\n", int(i), dspu::gain_to_db(tpl));
+ }
+
+ // Save the sample
+ MTEST_ASSERT(dst_path.fmt("%s/%s-tpm.wav", tempdir(), full_name()) > 0);
+ printf("Saving file %s...\n", dst_path.as_native());
+ MTEST_ASSERT(dst.save(&dst_path) > 0);
+ }
+
+MTEST_END
+
+
+
diff --git a/src/test/utest/sampling/sample.cpp b/src/test/utest/sampling/sample.cpp
index 3dafdd9..8a31d3b 100644
--- a/src/test/utest/sampling/sample.cpp
+++ b/src/test/utest/sampling/sample.cpp
@@ -344,23 +344,23 @@ UTEST_BEGIN("dspu.sampling", sample)
// Test file with missing entry
printf(" reading missing file as io::Path '%s'...\n", audio_missing.as_native());
- UTEST_ASSERT(dst.load_ext(&audio_missing) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(&audio_missing) != STATUS_OK);
printf(" reading missing file as LSPString '%s'...\n", str_missing.get_native());
- UTEST_ASSERT(dst.load_ext(&str_missing) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(&str_missing) != STATUS_OK);
printf(" reading missing file as (char *) '%s'...\n", str_missing.get_native());
- UTEST_ASSERT(dst.load_ext(str_missing.get_native()) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(str_missing.get_native()) != STATUS_OK);
// Test file with invalid path
printf(" reading invalid file as io::Path '%s'...\n", audio_invalid.as_native());
- UTEST_ASSERT(dst.load_ext(&audio_invalid) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(&audio_invalid) != STATUS_OK);
printf(" reading invalid file as LSPString '%s'...\n", str_invalid.get_native());
- UTEST_ASSERT(dst.load_ext(&str_invalid) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(&str_invalid) != STATUS_OK);
printf(" reading invalid file as (char *) '%s'...\n", str_invalid.get_native());
- UTEST_ASSERT(dst.load_ext(str_invalid.get_native()) == STATUS_NOT_FOUND);
+ UTEST_ASSERT(dst.load_ext(str_invalid.get_native()) != STATUS_OK);
}
void test_lspc_named_files()