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