Adding FIR filter classes for single and multi channel

parent 2ae591d1
......@@ -62,12 +62,14 @@ set( ITABaseHeader
"include/ITAFade.h"
"include/ITAFastMath.h"
"include/ITAFileSystemUtils.h"
"include/ITAFiniteImpulseResponse.h"
"include/ITAHDFTSpectra.h"
"include/ITAHDFTSpectrum.h"
"include/ITAFunctors.h"
"include/ITAInterpolation.h"
"include/ITALog.h"
"include/ITAMagnitudeSpectrum.h"
"include/ITAMultichannelFiniteImpulseResponse.h"
"include/ITANumericUtils.h"
"include/ITASampleBuffer.h"
"include/ITASampleFrame.h"
......@@ -97,11 +99,13 @@ set( ITABaseSources
"src/ITAException.cpp"
"src/ITAFade.cpp"
"src/ITAFileSystemUtils.cpp"
"src/ITAFiniteImpulseResponse.cpp"
"src/ITAHDFTSpectra.cpp"
"src/ITAHDFTSpectrum.cpp"
"src/ITAInterpolation.cpp"
"src/ITALog.cpp"
"src/ITAMagnitudeSpectrum.cpp"
"src/ITAMultichannelFiniteImpulseResponse.cpp"
"src/ITANumericUtils.cpp"
"src/ITASampleBuffer.cpp"
"src/ITASampleFrame.cpp"
......
/*
* ----------------------------------------------------------------
*
* ITA core libs
* (c) Copyright Institute of Technical Acoustics (ITA)
* RWTH Aachen University, Germany, 2015-2017
*
* ----------------------------------------------------------------
* ____ __________ _______
* // / //__ ___/ // _ |
* // / // / // /_| |
* // / // / // ___ |
* //__/ //__/ //__/ |__|
*
* ----------------------------------------------------------------
*
*/
#ifndef INCLUDE_WATCHER_ITA_FINITE_IMPULSE_RESPONSE
#define INCLUDE_WATCHER_ITA_FINITE_IMPULSE_RESPONSE
// ITABase
#include <ITABaseDefinitions.h>
#include <ITASampleBuffer.h>
#include <string>
//! Single channel finite impulse response class / FIR filter
/**
* ITASampleBuffer with a sampling rate = finite impulse response.
* For multi channel FIR filter, @sa ITAMultichannelFiniteImpulseResponse
*/
class ITA_BASE_API CITAFiniteImpulseResponse : public ITASampleBuffer
{
public:
//! Create empty FIR filter
/**
* Requires initialization to be used, otherwise methods will throw ITAException.
*/
CITAFiniteImpulseResponse();
//! Create FIR filter of certain size with given sampling frequency
/**
* \param iLength Length (size) of buffer in samples
*/
explicit CITAFiniteImpulseResponse( const int iLength, const double dSampleRate, const bool bZeroInit = true );
//! Copy constructor as pointer
/**
* \param pSource Pointer to source buffer
*/
CITAFiniteImpulseResponse( const CITAFiniteImpulseResponse* pSource );
//! Copy constructor as reference
/**
* \param pbSource Reference to source buffer
*/
CITAFiniteImpulseResponse( const CITAFiniteImpulseResponse& sbSource );
virtual ~CITAFiniteImpulseResponse();
//! Sampling frequency of FIR filter (double of Nyquist frequency)
double GetSampleRate() const;
//! Nyquist frequency of FIR filter (half of sample rate)
double GetNyquistFrequency() const;
//! Initialize
/**
* (Re-)Initialize a sample buffer
*
* @param[in] iLength Number of new samples
* @param[in] dSampleRate Sampling rate of FIR filter (double of Nyquist frequency)
* @param[in] bZeroInit Init with zeros
*/
void Init( const int iLength, const double dSampleRate, const bool bZeroInit = true );
private:
//! Disable this Init method from sample buffer
void Init( int, bool );
double m_dSampleRate; //!< Sampling rate
};
#endif // INCLUDE_WATCHER_ITA_FINITE_IMPULSE_RESPONSE
/*
* ----------------------------------------------------------------
*
* ITA core libs
* (c) Copyright Institute of Technical Acoustics (ITA)
* RWTH Aachen University, Germany, 2015-2017
*
* ----------------------------------------------------------------
* ____ __________ _______
* // / //__ ___/ // _ |
* // / // / // /_| |
* // / // / // ___ |
* //__/ //__/ //__/ |__|
*
* ----------------------------------------------------------------
*
*/
#ifndef INCLUDE_WATCHER_ITA_MULTICHANNEL_FINITE_IMPULSE_RESPONSE
#define INCLUDE_WATCHER_ITA_MULTICHANNEL_FINITE_IMPULSE_RESPONSE
// ITABase
#include <ITABaseDefinitions.h>
#include <ITASampleFrame.h>
//! Multichannel finite impulse response class / FIR filters
/**
* ITASampleFrame with a sampling rate = multichannel finite impulse response.
*/
class ITA_BASE_API CITAMultichannelFiniteImpulseResponse : public ITASampleFrame
{
public:
//! Create empty FIR filters
/**
* Requires initialization to be used, otherwise methods will throw ITAException.
*/
CITAMultichannelFiniteImpulseResponse();
//! Create FIR filter of certain size with given sampling frequency
/**
* \param iLength Length (size) of buffer in samples
*/
explicit CITAMultichannelFiniteImpulseResponse( const int iNumChannels, const int iLength, const double dSampleRate, const bool bZeroInit = true );
//! Copy constructor as pointer
/**
* \param pSource Pointer to source buffer
*/
CITAMultichannelFiniteImpulseResponse( const CITAMultichannelFiniteImpulseResponse* pSource );
//! Copy constructor as reference
/**
* \param pbSource Reference to source buffer
*/
CITAMultichannelFiniteImpulseResponse( const CITAMultichannelFiniteImpulseResponse& sbSource );
virtual ~CITAMultichannelFiniteImpulseResponse();
//! Load from file constructor (audiofile)
/**
* Loads FIR data from an audio file.
*
* @note Will throw ITAException on error.
*/
CITAMultichannelFiniteImpulseResponse( const std::string& sFilePath );
//! Loads FIR from file (will re-init length, if necessary)
void LoadFromFile( const std::string& sFilePath );
void StoreToFile( const std::string& sFilePath ) const;
//! Sampling frequency of FIR filter (double of Nyquist frequency)
double GetSampleRate() const;
//! Nyquist frequency of FIR filter (half of sample rate)
double GetNyquistFrequency() const;
//! Initialize
/**
* (Re-)Initialize a sample buffer
*
* @param[in] iNumChannels Number of FIR channels
* \param iLength Number of new samples
* @param[in] dSampleRate Sampling rate of FIR filter (double of Nyquist frequency)
* \param bZeroInit Init with zeros
*/
void Init( const int iNumChannels, const int iLength, const double dSampleRate, const bool bZeroInit = true );
private:
//! Disable these methods from ITASampleFrame
void Init( int, int, bool );
void Load( const std::string& );
void Load( const std::string&, double& );
void Store( const std::string&, double);
double m_dSampleRate; //!< Sampling rate
};
#endif // INCLUDE_WATCHER_ITA_MULTICHANNEL_FINITE_IMPULSE_RESPONSE
......@@ -52,27 +52,27 @@ public:
* \param bZeroinit Frame mit Nullen initialisieren?
*/
explicit ITASampleFrame(int iChannels, int iLength, bool bZeroinit);
explicit ITASampleFrame( int iChannels, int iLength, bool bZeroinit );
//! Kopierkonstruktor (Zeiger)
/**
* Erzeugt einen unabhngigen Frame als Kopie des gegebenen Frames.
* Erzeugt einen unabhngigen Frame als Kopie des gegebenen Frames.
* Der neue Frame hat die selbe Anzahl Kanle und Lnge und enthlt
* die gleichen Werte wie der Quellframe.
*
* \param pSource Zeiger auf den Quellframe
*/
ITASampleFrame(const ITASampleFrame* pSource);
ITASampleFrame( const ITASampleFrame* pSource );
//! Kopierkonstruktor (Referenz)
/**
* Erzeugt einen unabhngigen Frame als Kopie des gegebenen Frames.
* Erzeugt einen unabhngigen Frame als Kopie des gegebenen Frames.
* Der neue Frame hat die selbe Anzahl Kanle und Lnge und enthlt
* die gleichen Werte wie der Quellframe.
*
* \param bSource Referenz auf den Quellframe
*/
ITASampleFrame(const ITASampleFrame& fSource);
ITASampleFrame( const ITASampleFrame& fSource );
//! Ladekonstruktor (Audiodatei)
/**
......@@ -80,7 +80,7 @@ public:
*
* \note Im Fehlerfall wird eine ITAException ausgelst.
*/
ITASampleFrame(const std::string& sFilename);
ITASampleFrame( const std::string& sFilename );
//! Destruktor
virtual ~ITASampleFrame();
......@@ -92,7 +92,12 @@ public:
bool empty() const;
//! Anzahl Kanle zurckgeben
int channels() const;
inline int channels() const
{
return GetNumChannels();
};
int GetNumChannels() const;
// Deprecated
int length() const;
......@@ -107,10 +112,10 @@ public:
* Falls ein Frame bereits initialisiert war, gehen alle vorherigen Daten verloren.
* Die Methode ermglich bequemes programmieren, da Frames zunchst per
* Standard-Konstruktor erzeugt werden knnen und erst spter initialisiert werden.
*
*
* \param dSamplerate Abtastrate [Hz] (0 => unbestimmt)
* \param iChannels Anzahl Kanle
* \param iLength Lnge [Anzahl Samples]
* \param iLength Lnge [Anzahl Samples]
* \param bZeroinit Samples mit Nullen initialisieren?
*/
void Init( int iChannels, int iLength, bool bZeroinit );
......@@ -127,13 +132,13 @@ public:
//! Speicher freigeben
/**
* Gibt den fr den Frame allozierten Speicher frei und setzt seine
* Gibt den fr den Frame allozierten Speicher frei und setzt seine
* Anzahl Kanle und Lnge auf 0. Hierbei gehen natrlich alle Daten verloren.
*/
void free();
//! Setzt alle Samples alles Kanle auf den gegebenen Wert
void fill(float fValue);
void fill( float fValue );
//! Setzt die Samples aller Kanle in einem Bereich auf den angegebenen Wert
/**
......@@ -141,13 +146,13 @@ public:
* \param iCount Anzahl Samples
* \param fFloat Wert
*/
void fill(int iOffset, int iCount, float fValue);
void fill( int iOffset, int iCount, float fValue );
//! Setzt alle Samples aller Kanle zu Null
void zero();
//! Setzt einen Bereich von Samples aller Kanle zu Null
void zero(int iOffset, int iCount);
void zero( int iOffset, int iCount );
//! Setzt Einheitsimpulse in jedem Kanal
void identity();
......@@ -157,7 +162,7 @@ public:
* Blendet die Samples im Bereich [iOffset, iOffset+iCount] mit der
* angegebenen Blendfunktion ein.
*/
void fade(int iOffset, int iCount, int iFadeDirection, int iFadeFunction);
void fade( int iOffset, int iCount, int iFadeDirection, int iFadeFunction );
//! Kreuzblenden
/**
......@@ -169,22 +174,22 @@ public:
* Danach werden iCount Samples zwischen den beiden Puffern kreuzgeblendet.
* Dahinter folgen nur noch Samples dieses Puffers.
*/
void crossfade(const ITASampleFrame* psfSrc, int iOffset, int iCount, int iFadeDirection, int iFadeFunction);
void crossfade(const ITASampleFrame& sfSrc, int iOffset, int iCount, int iFadeDirection, int iFadeFunction);
void crossfade( const ITASampleFrame* psfSrc, int iOffset, int iCount, int iFadeDirection, int iFadeFunction );
void crossfade( const ITASampleFrame& sfSrc, int iOffset, int iCount, int iFadeDirection, int iFadeFunction );
//! Einhllende
/**
/* Wendet eine lineare Einhllende (envelope) auf den Frame an.
*
* \param fGain0 Startwert [0..1]
* \param fGain1 Endwert [0..1]
*/
void envelope(float fGain0, float fGain1);
*
* \param fGain0 Startwert [0..1]
* \param fGain1 Endwert [0..1]
*/
void envelope( float fGain0, float fGain1 );
//! Samples aus einem anderen Frame in den Frame kopieren
/**
* Kopiert iCount Samples aus angegebenen Frame beginnend bei Leseposition iSrcOffset
* in disen Frame, dort beginnend ab Schreibposition iDestOffset.
* in disen Frame, dort beginnend ab Schreibposition iDestOffset.
*
* \param psfSrc Quellframe
* \param iCount Anzahl zu kopierender Samples
......@@ -194,8 +199,8 @@ public:
* \note Kein Schreiben ber das Frameende hinaus!
* \note Beide Frames mssen die gleiche Anzahl Kanle haben
*/
void write(const ITASampleFrame* psfSrc, int iCount, int iSrcOffset=0, int iDestOffset=0);
void write(const ITASampleFrame& sfSrc, int iCount, int iSrcOffset=0, int iDestOffset=0);
void write( const ITASampleFrame* psfSrc, int iCount, int iSrcOffset = 0, int iDestOffset = 0 );
void write( const ITASampleFrame& sfSrc, int iCount, int iSrcOffset = 0, int iDestOffset = 0 );
//! Zyklisches Schreiben
/**
......@@ -214,61 +219,61 @@ public:
*
* TODO: Memory alignment fr SSE?
*/
void cyclic_write(const ITASampleFrame* psfSrc, int iCount, int iSrcOffset=0, int iDestOffset=0);
void cyclic_write(const ITASampleFrame& sfSrc, int iCount, int iSrcOffset=0, int iDestOffset=0);
void cyclic_write( const ITASampleFrame* psfSrc, int iCount, int iSrcOffset = 0, int iDestOffset = 0 );
void cyclic_write( const ITASampleFrame& sfSrc, int iCount, int iSrcOffset = 0, int iDestOffset = 0 );
//! Cyclic shifting of samples
/**
* @param [in] iCount Shifts the samples in frames by given count
*/
void CyclicShift(int iCount);
void CyclicShift( int iCount );
//! In-place Addition: Jedem Sample einen konstanten Wert addieren
void add_scalar(float fValue);
void add_scalar( float fValue );
//! In-place Subtraktion: Jedem Sample einen konstanten Wert subtrahieren
void sub_scalar(float fValue);
void sub_scalar( float fValue );
//! In-place Multiplikation: Jedes Sample mit einem konstanten Wert multiplizieren
void mul_scalar(float fValue);
void mul_scalar( float fValue );
//! In-place Division: Jedes Sample durch einen konstanten Wert dividieren
void div_scalar(float fValue);
void div_scalar( float fValue );
// TODO: Bereiche Addieren usw.
// Operatoren: Alle Kanle mit einem SampleBuffer
void add_buf(const ITASampleBuffer* psbSource);
void sub_buf(const ITASampleBuffer* psbSource);
void mul_buf(const ITASampleBuffer* psbSource);
void div_buf(const ITASampleBuffer* psbSource);
void add_buf( const ITASampleBuffer* psbSource );
void sub_buf( const ITASampleBuffer* psbSource );
void mul_buf( const ITASampleBuffer* psbSource );
void div_buf( const ITASampleBuffer* psbSource );
// Varianten mit Referenzen
void add_buf(const ITASampleBuffer& sbSource);
void sub_buf(const ITASampleBuffer& sbSource);
void mul_buf(const ITASampleBuffer& sbSource);
void div_buf(const ITASampleBuffer& sbSource);
void add_buf( const ITASampleBuffer& sbSource );
void sub_buf( const ITASampleBuffer& sbSource );
void mul_buf( const ITASampleBuffer& sbSource );
void div_buf( const ITASampleBuffer& sbSource );
//! Paarweise alle Samples des gegebenen Blockes zu den Samples diesem addieren
/*
* - Mssen gleiche Anzahl Kanle und Lngen haben!
*/
void add_frame(const ITASampleFrame* psfSource);
void sub_frame(const ITASampleFrame* psfSource);
void mul_frame(const ITASampleFrame* psfSource);
void div_frame(const ITASampleFrame* psfSource);
void add_frame( const ITASampleFrame* psfSource );
void sub_frame( const ITASampleFrame* psfSource );
void mul_frame( const ITASampleFrame* psfSource );
void div_frame( const ITASampleFrame* psfSource );
// Varianten mit Referenzen
void add_frame(const ITASampleFrame& sfSource);
void sub_frame(const ITASampleFrame& sfSource);
void mul_frame(const ITASampleFrame& sfSource);
void div_frame(const ITASampleFrame& sfSource);
void add_frame( const ITASampleFrame& sfSource );
void sub_frame( const ITASampleFrame& sfSource );
void mul_frame( const ITASampleFrame& sfSource );
void div_frame( const ITASampleFrame& sfSource );
// mit bergabe der Position - keine identische Lnge bentigt
void add_frame(const ITASampleFrame* psfSource, int iPos);
void add_frame( const ITASampleFrame* psfSource, int iPos );
// Kombinierte Operatoren
// Werte eines anderen Frames mit einer Konstante multiplizieren und dann hierauf addieren
// Semantik:
// for i from 0 to iCount-1 do
......@@ -277,8 +282,8 @@ public:
// iSrcOffset = Leseposition im Quellpuffer
// iDestOffset = Schreibposition in diesem Puffer
// iCount = Anzahl Samples
void muladd_frame(const ITASampleFrame* psfSource, float fScalar, int iSrcOffset, int iDestOffset, int iCount);
void muladd_frame(const ITASampleFrame& sfSource, float fScalar, int iSrcOffset, int iDestOffset, int iCount);
void muladd_frame( const ITASampleFrame* psfSource, float fScalar, int iSrcOffset, int iDestOffset, int iCount );
void muladd_frame( const ITASampleFrame& sfSource, float fScalar, int iSrcOffset, int iDestOffset, int iCount );
//! Spitzenwert suchen
......@@ -287,7 +292,7 @@ public:
* Auf Wunsch wird auch der Kanal und Index der ersten Samples zurckgegeben, das diesen
* Spitzenwert erreichte (erste Fundstelle).
*/
float findPeak(int* piChannel=NULL, int* piPeakIndex=NULL);
float findPeak( int* piChannel = NULL, int* piPeakIndex = NULL );
//! Negieren (Multiplikation mit -1 bzw. Phasendrehungum 180)
void negate();
......@@ -306,10 +311,10 @@ public:
};
//! Read/Write Indizierungsoperator fr Zugriff auf Kanaldaten
ITASampleBuffer& operator[](int iChannel);
ITASampleBuffer& operator[]( int iChannel );
//! Read-only Indizierungsoperator
const ITASampleBuffer& operator[](int iChannel) const;
const ITASampleBuffer& operator[]( int iChannel ) const;
//! Zuweisungsoperator
/**
......@@ -317,21 +322,21 @@ public:
* Hierzu wird zunchst die Lnge des Puffer der des Quellpuffers angepasst.
* Anschlieend werden alle Samples kopiert.
*/
ITASampleFrame& operator=(const ITASampleFrame& rhs);
ITASampleFrame& operator=( const ITASampleFrame& rhs );
//! Arithemtische Operatoren (Aliase fr arithmetische Methoden - siehe oben)
ITASampleFrame& operator+=(const float rhs);
ITASampleFrame& operator-=(const float rhs);
ITASampleFrame& operator*=(const float rhs);
ITASampleFrame& operator/=(const float rhs);
ITASampleFrame& operator+=(const ITASampleBuffer& rhs);
ITASampleFrame& operator-=(const ITASampleBuffer& rhs);
ITASampleFrame& operator*=(const ITASampleBuffer& rhs);
ITASampleFrame& operator/=(const ITASampleBuffer& rhs);
ITASampleFrame& operator+=(const ITASampleFrame& rhs);
ITASampleFrame& operator-=(const ITASampleFrame& rhs);
ITASampleFrame& operator*=(const ITASampleFrame& rhs);
ITASampleFrame& operator/=(const ITASampleFrame& rhs);
ITASampleFrame& operator+=( const float rhs );
ITASampleFrame& operator-=( const float rhs );
ITASampleFrame& operator*=( const float rhs );
ITASampleFrame& operator/=( const float rhs );
ITASampleFrame& operator+=( const ITASampleBuffer& rhs );
ITASampleFrame& operator-=( const ITASampleBuffer& rhs );
ITASampleFrame& operator*=( const ITASampleBuffer& rhs );
ITASampleFrame& operator/=( const ITASampleBuffer& rhs );
ITASampleFrame& operator+=( const ITASampleFrame& rhs );
ITASampleFrame& operator-=( const ITASampleFrame& rhs );
ITASampleFrame& operator*=( const ITASampleFrame& rhs );
ITASampleFrame& operator/=( const ITASampleFrame& rhs );
//! Informationen ber den Puffer als Zeichenkette zurckgeben
std::string toString() const;
......
#include <ITAFiniteImpulseResponse.h>
CITAFiniteImpulseResponse::CITAFiniteImpulseResponse()
: m_dSampleRate( 0 )
{
}
CITAFiniteImpulseResponse::CITAFiniteImpulseResponse( const int iLength, const double dSampleRate, const bool bZeroInit /*= true */ )
: ITASampleBuffer( iLength, bZeroInit )
, m_dSampleRate( dSampleRate )
{
}
void CITAFiniteImpulseResponse::Init( const int iLength, const double dSampleRate, const bool bZeroInit /*= true */ )
{
m_dSampleRate = dSampleRate;
ITASampleBuffer::Init( iLength, bZeroInit );
}
CITAFiniteImpulseResponse::CITAFiniteImpulseResponse( const CITAFiniteImpulseResponse* pSource )
{
Init( pSource->GetLength(), pSource->GetSampleRate() );
ITASampleBuffer::write( pSource, pSource->GetLength() );
}
CITAFiniteImpulseResponse::CITAFiniteImpulseResponse( const CITAFiniteImpulseResponse& sbSource )
{
Init( sbSource.GetLength(), sbSource.GetSampleRate() );
ITASampleBuffer::write( sbSource, sbSource.GetLength() );
}
double CITAFiniteImpulseResponse::GetSampleRate() const
{
return m_dSampleRate;
}
double CITAFiniteImpulseResponse::GetNyquistFrequency() const
{
return GetSampleRate() / 2.0f;
}
#include <ITAMultichannelFiniteImpulseResponse.h>
#include <ITAException.h>
CITAMultichannelFiniteImpulseResponse::CITAMultichannelFiniteImpulseResponse()
: m_dSampleRate( 0 )
{
}
CITAMultichannelFiniteImpulseResponse::CITAMultichannelFiniteImpulseResponse( const int iNumChannels, const int iLength, const double dSampleRate, const bool bZeroInit /*= true */ )
: ITASampleFrame( iNumChannels, iLength, bZeroInit )
, m_dSampleRate( dSampleRate )
{
}
void CITAMultichannelFiniteImpulseResponse::LoadFromFile( const std::string& sFilePath )
{
ITASampleFrame::Load( sFilePath, m_dSampleRate );
}
void CITAMultichannelFiniteImpulseResponse::StoreToFile( const std::string& sFilePath ) const
{
if( GetNumChannels() <= 0 || GetSampleRate() <= 0.0f )
ITA_EXCEPT1( INVALID_PARAMETER, "Trying to store a multichannel FIR filter with no channels or invalid sampling rate" );
ITASampleFrame::Store( sFilePath, GetSampleRate() );
}
void CITAMultichannelFiniteImpulseResponse::Init( const int iNumChannels, const int iLength, const double dSampleRate, const bool bZeroInit /*= true */ )
{
m_dSampleRate = dSampleRate;
ITASampleFrame::Init( iNumChannels, iLength, bZeroInit );
}
CITAMultichannelFiniteImpulseResponse::CITAMultichannelFiniteImpulseResponse( const CITAMultichannelFiniteImpulseResponse* pSource )
{
Init( pSource->channels(), pSource->GetLength(), pSource->GetSampleRate() );
ITASampleFrame::write( pSource, pSource->GetLength() );
}
CITAMultichannelFiniteImpulseResponse::CITAMultichannelFiniteImpulseResponse( const CITAMultichannelFiniteImpulseResponse& sbSource )
{
Init( sbSource.GetNumChannels(), sbSource.GetLength(), sbSource.GetSampleRate() );
ITASampleFrame::write( sbSource, sbSource.GetLength() );
}
double CITAMultichannelFiniteImpulseResponse::GetSampleRate() const
{
return m_dSampleRate;
}
double CITAMultichannelFiniteImpulseResponse::GetNyquistFrequency() const
{
return GetSampleRate() / 2.0f;
}
......@@ -46,7 +46,7 @@ bool ITASampleFrame::empty() const
return ( ( m_iChannels == 0 ) || ( m_iLength == 0 ) );
}
int ITASampleFrame::channels() const
int ITASampleFrame::GetNumChannels() const
{
return m_iChannels;
}
......