diff --git a/CHANGELOG b/CHANGELOG
index 0f0bd54..f441393 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
=== 1.0.27 ===
* Implemented TruePeak meter.
+* Implemented Panorama meter (Panometer).
* Implemented ScaledMeterGraph with subsampling option.
* Several fixes and improvements in RawRingBuffer.
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..715a824
--- /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
+ };
+
+ /**
+ * Corellometer. Computes normalized correlation 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/src/main/meters/Panometer.cpp b/src/main/meters/Panometer.cpp
new file mode 100644
index 0000000..572e0ee
--- /dev/null
+++ b/src/main/meters/Panometer.cpp
@@ -0,0 +1,238 @@
+/*
+ * 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;
+
+ 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-5f) ? sr / den : fDefault;
+ }
+ }
+ else
+ {
+ for (size_t i=0; i 1e-10f) ? 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 */
+
+