Files
qtmultimedia/demos/spectrum/3rdparty/fftreal/FFTReal.hpp
Qt by Nokia 60941c2741 Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you
want to look at revision history older than this, please refer to the
Qt Git wiki for how to use Git history grafting. At the time of
writing, this wiki is located here:

http://qt.gitorious.org/qt/pages/GitIntroductionWithQt

If you have already performed the grafting and you don't see any
history beyond this commit, try running "git log" with the "--follow"
argument.

Branched from the monolithic repo, Qt master branch, at commit
896db169ea224deb96c59ce8af800d019de63f12
2011-04-27 12:05:43 +02:00

917 lines
20 KiB
C++

/*****************************************************************************
FFTReal.hpp
Copyright (c) 2005 Laurent de Soras
--- Legal stuff ---
This library 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 2.1 of the License, or (at your option) any later version.
This library 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 this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*Tab=3***********************************************************************/
#if defined (FFTReal_CURRENT_CODEHEADER)
#error Recursive inclusion of FFTReal code header.
#endif
#define FFTReal_CURRENT_CODEHEADER
#if ! defined (FFTReal_CODEHEADER_INCLUDED)
#define FFTReal_CODEHEADER_INCLUDED
/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
#include <cassert>
#include <cmath>
static inline bool FFTReal_is_pow2 (long x)
{
assert (x > 0);
return ((x & -x) == x);
}
static inline int FFTReal_get_next_pow2 (long x)
{
--x;
int p = 0;
while ((x & ~0xFFFFL) != 0)
{
p += 16;
x >>= 16;
}
while ((x & ~0xFL) != 0)
{
p += 4;
x >>= 4;
}
while (x > 0)
{
++p;
x >>= 1;
}
return (p);
}
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*
==============================================================================
Name: ctor
Input parameters:
- length: length of the array on which we want to do a FFT. Range: power of
2 only, > 0.
Throws: std::bad_alloc
==============================================================================
*/
template <class DT>
FFTReal <DT>::FFTReal (long length)
: _length (length)
, _nbr_bits (FFTReal_get_next_pow2 (length))
, _br_lut ()
, _trigo_lut ()
, _buffer (length)
, _trigo_osc ()
{
assert (FFTReal_is_pow2 (length));
assert (_nbr_bits <= MAX_BIT_DEPTH);
init_br_lut ();
init_trigo_lut ();
init_trigo_osc ();
}
/*
==============================================================================
Name: get_length
Description:
Returns the number of points processed by this FFT object.
Returns: The number of points, power of 2, > 0.
Throws: Nothing
==============================================================================
*/
template <class DT>
long FFTReal <DT>::get_length () const
{
return (_length);
}
/*
==============================================================================
Name: do_fft
Description:
Compute the FFT of the array.
Input parameters:
- x: pointer on the source array (time).
Output parameters:
- f: pointer on the destination array (frequencies).
f [0...length(x)/2] = real values,
f [length(x)/2+1...length(x)-1] = negative imaginary values of
coefficents 1...length(x)/2-1.
Throws: Nothing
==============================================================================
*/
template <class DT>
void FFTReal <DT>::do_fft (DataType f [], const DataType x []) const
{
assert (f != 0);
assert (f != use_buffer ());
assert (x != 0);
assert (x != use_buffer ());
assert (x != f);
// General case
if (_nbr_bits > 2)
{
compute_fft_general (f, x);
}
// 4-point FFT
else if (_nbr_bits == 2)
{
f [1] = x [0] - x [2];
f [3] = x [1] - x [3];
const DataType b_0 = x [0] + x [2];
const DataType b_2 = x [1] + x [3];
f [0] = b_0 + b_2;
f [2] = b_0 - b_2;
}
// 2-point FFT
else if (_nbr_bits == 1)
{
f [0] = x [0] + x [1];
f [1] = x [0] - x [1];
}
// 1-point FFT
else
{
f [0] = x [0];
}
}
/*
==============================================================================
Name: do_ifft
Description:
Compute the inverse FFT of the array. Note that data must be post-scaled:
IFFT (FFT (x)) = x * length (x).
Input parameters:
- f: pointer on the source array (frequencies).
f [0...length(x)/2] = real values
f [length(x)/2+1...length(x)-1] = negative imaginary values of
coefficents 1...length(x)/2-1.
Output parameters:
- x: pointer on the destination array (time).
Throws: Nothing
==============================================================================
*/
template <class DT>
void FFTReal <DT>::do_ifft (const DataType f [], DataType x []) const
{
assert (f != 0);
assert (f != use_buffer ());
assert (x != 0);
assert (x != use_buffer ());
assert (x != f);
// General case
if (_nbr_bits > 2)
{
compute_ifft_general (f, x);
}
// 4-point IFFT
else if (_nbr_bits == 2)
{
const DataType b_0 = f [0] + f [2];
const DataType b_2 = f [0] - f [2];
x [0] = b_0 + f [1] * 2;
x [2] = b_0 - f [1] * 2;
x [1] = b_2 + f [3] * 2;
x [3] = b_2 - f [3] * 2;
}
// 2-point IFFT
else if (_nbr_bits == 1)
{
x [0] = f [0] + f [1];
x [1] = f [0] - f [1];
}
// 1-point IFFT
else
{
x [0] = f [0];
}
}
/*
==============================================================================
Name: rescale
Description:
Scale an array by divide each element by its length. This function should
be called after FFT + IFFT.
Input parameters:
- x: pointer on array to rescale (time or frequency).
Throws: Nothing
==============================================================================
*/
template <class DT>
void FFTReal <DT>::rescale (DataType x []) const
{
const DataType mul = DataType (1.0 / _length);
if (_length < 4)
{
long i = _length - 1;
do
{
x [i] *= mul;
--i;
}
while (i >= 0);
}
else
{
assert ((_length & 3) == 0);
// Could be optimized with SIMD instruction sets (needs alignment check)
long i = _length - 4;
do
{
x [i + 0] *= mul;
x [i + 1] *= mul;
x [i + 2] *= mul;
x [i + 3] *= mul;
i -= 4;
}
while (i >= 0);
}
}
/*
==============================================================================
Name: use_buffer
Description:
Access the internal buffer, whose length is the FFT one.
Buffer content will be erased at each do_fft() / do_ifft() call!
This buffer cannot be used as:
- source for FFT or IFFT done with this object
- destination for FFT or IFFT done with this object
Returns:
Buffer start address
Throws: Nothing
==============================================================================
*/
template <class DT>
typename FFTReal <DT>::DataType * FFTReal <DT>::use_buffer () const
{
return (&_buffer [0]);
}
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
template <class DT>
void FFTReal <DT>::init_br_lut ()
{
const long length = 1L << _nbr_bits;
_br_lut.resize (length);
_br_lut [0] = 0;
long br_index = 0;
for (long cnt = 1; cnt < length; ++cnt)
{
// ++br_index (bit reversed)
long bit = length >> 1;
while (((br_index ^= bit) & bit) == 0)
{
bit >>= 1;
}
_br_lut [cnt] = br_index;
}
}
template <class DT>
void FFTReal <DT>::init_trigo_lut ()
{
using namespace std;
if (_nbr_bits > 3)
{
const long total_len = (1L << (_nbr_bits - 1)) - 4;
_trigo_lut.resize (total_len);
for (int level = 3; level < _nbr_bits; ++level)
{
const long level_len = 1L << (level - 1);
DataType * const level_ptr =
&_trigo_lut [get_trigo_level_index (level)];
const double mul = PI / (level_len << 1);
for (long i = 0; i < level_len; ++ i)
{
level_ptr [i] = static_cast <DataType> (cos (i * mul));
}
}
}
}
template <class DT>
void FFTReal <DT>::init_trigo_osc ()
{
const int nbr_osc = _nbr_bits - TRIGO_BD_LIMIT;
if (nbr_osc > 0)
{
_trigo_osc.resize (nbr_osc);
for (int osc_cnt = 0; osc_cnt < nbr_osc; ++osc_cnt)
{
OscType & osc = _trigo_osc [osc_cnt];
const long len = 1L << (TRIGO_BD_LIMIT + osc_cnt);
const double mul = (0.5 * PI) / len;
osc.set_step (mul);
}
}
}
template <class DT>
const long * FFTReal <DT>::get_br_ptr () const
{
return (&_br_lut [0]);
}
template <class DT>
const typename FFTReal <DT>::DataType * FFTReal <DT>::get_trigo_ptr (int level) const
{
assert (level >= 3);
return (&_trigo_lut [get_trigo_level_index (level)]);
}
template <class DT>
long FFTReal <DT>::get_trigo_level_index (int level) const
{
assert (level >= 3);
return ((1L << (level - 1)) - 4);
}
// Transform in several passes
template <class DT>
void FFTReal <DT>::compute_fft_general (DataType f [], const DataType x []) const
{
assert (f != 0);
assert (f != use_buffer ());
assert (x != 0);
assert (x != use_buffer ());
assert (x != f);
DataType * sf;
DataType * df;
if ((_nbr_bits & 1) != 0)
{
df = use_buffer ();
sf = f;
}
else
{
df = f;
sf = use_buffer ();
}
compute_direct_pass_1_2 (df, x);
compute_direct_pass_3 (sf, df);
for (int pass = 3; pass < _nbr_bits; ++ pass)
{
compute_direct_pass_n (df, sf, pass);
DataType * const temp_ptr = df;
df = sf;
sf = temp_ptr;
}
}
template <class DT>
void FFTReal <DT>::compute_direct_pass_1_2 (DataType df [], const DataType x []) const
{
assert (df != 0);
assert (x != 0);
assert (df != x);
const long * const bit_rev_lut_ptr = get_br_ptr ();
long coef_index = 0;
do
{
const long rev_index_0 = bit_rev_lut_ptr [coef_index];
const long rev_index_1 = bit_rev_lut_ptr [coef_index + 1];
const long rev_index_2 = bit_rev_lut_ptr [coef_index + 2];
const long rev_index_3 = bit_rev_lut_ptr [coef_index + 3];
DataType * const df2 = df + coef_index;
df2 [1] = x [rev_index_0] - x [rev_index_1];
df2 [3] = x [rev_index_2] - x [rev_index_3];
const DataType sf_0 = x [rev_index_0] + x [rev_index_1];
const DataType sf_2 = x [rev_index_2] + x [rev_index_3];
df2 [0] = sf_0 + sf_2;
df2 [2] = sf_0 - sf_2;
coef_index += 4;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_direct_pass_3 (DataType df [], const DataType sf []) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
const DataType sqrt2_2 = DataType (SQRT2 * 0.5);
long coef_index = 0;
do
{
DataType v;
df [coef_index] = sf [coef_index] + sf [coef_index + 4];
df [coef_index + 4] = sf [coef_index] - sf [coef_index + 4];
df [coef_index + 2] = sf [coef_index + 2];
df [coef_index + 6] = sf [coef_index + 6];
v = (sf [coef_index + 5] - sf [coef_index + 7]) * sqrt2_2;
df [coef_index + 1] = sf [coef_index + 1] + v;
df [coef_index + 3] = sf [coef_index + 1] - v;
v = (sf [coef_index + 5] + sf [coef_index + 7]) * sqrt2_2;
df [coef_index + 5] = v + sf [coef_index + 3];
df [coef_index + 7] = v - sf [coef_index + 3];
coef_index += 8;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_direct_pass_n (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass >= 3);
assert (pass < _nbr_bits);
if (pass <= TRIGO_BD_LIMIT)
{
compute_direct_pass_n_lut (df, sf, pass);
}
else
{
compute_direct_pass_n_osc (df, sf, pass);
}
}
template <class DT>
void FFTReal <DT>::compute_direct_pass_n_lut (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass >= 3);
assert (pass < _nbr_bits);
const long nbr_coef = 1 << pass;
const long h_nbr_coef = nbr_coef >> 1;
const long d_nbr_coef = nbr_coef << 1;
long coef_index = 0;
const DataType * const cos_ptr = get_trigo_ptr (pass);
do
{
const DataType * const sf1r = sf + coef_index;
const DataType * const sf2r = sf1r + nbr_coef;
DataType * const dfr = df + coef_index;
DataType * const dfi = dfr + nbr_coef;
// Extreme coefficients are always real
dfr [0] = sf1r [0] + sf2r [0];
dfi [0] = sf1r [0] - sf2r [0]; // dfr [nbr_coef] =
dfr [h_nbr_coef] = sf1r [h_nbr_coef];
dfi [h_nbr_coef] = sf2r [h_nbr_coef];
// Others are conjugate complex numbers
const DataType * const sf1i = sf1r + h_nbr_coef;
const DataType * const sf2i = sf1i + nbr_coef;
for (long i = 1; i < h_nbr_coef; ++ i)
{
const DataType c = cos_ptr [i]; // cos (i*PI/nbr_coef);
const DataType s = cos_ptr [h_nbr_coef - i]; // sin (i*PI/nbr_coef);
DataType v;
v = sf2r [i] * c - sf2i [i] * s;
dfr [i] = sf1r [i] + v;
dfi [-i] = sf1r [i] - v; // dfr [nbr_coef - i] =
v = sf2r [i] * s + sf2i [i] * c;
dfi [i] = v + sf1i [i];
dfi [nbr_coef - i] = v - sf1i [i];
}
coef_index += d_nbr_coef;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_direct_pass_n_osc (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass > TRIGO_BD_LIMIT);
assert (pass < _nbr_bits);
const long nbr_coef = 1 << pass;
const long h_nbr_coef = nbr_coef >> 1;
const long d_nbr_coef = nbr_coef << 1;
long coef_index = 0;
OscType & osc = _trigo_osc [pass - (TRIGO_BD_LIMIT + 1)];
do
{
const DataType * const sf1r = sf + coef_index;
const DataType * const sf2r = sf1r + nbr_coef;
DataType * const dfr = df + coef_index;
DataType * const dfi = dfr + nbr_coef;
osc.clear_buffers ();
// Extreme coefficients are always real
dfr [0] = sf1r [0] + sf2r [0];
dfi [0] = sf1r [0] - sf2r [0]; // dfr [nbr_coef] =
dfr [h_nbr_coef] = sf1r [h_nbr_coef];
dfi [h_nbr_coef] = sf2r [h_nbr_coef];
// Others are conjugate complex numbers
const DataType * const sf1i = sf1r + h_nbr_coef;
const DataType * const sf2i = sf1i + nbr_coef;
for (long i = 1; i < h_nbr_coef; ++ i)
{
osc.step ();
const DataType c = osc.get_cos ();
const DataType s = osc.get_sin ();
DataType v;
v = sf2r [i] * c - sf2i [i] * s;
dfr [i] = sf1r [i] + v;
dfi [-i] = sf1r [i] - v; // dfr [nbr_coef - i] =
v = sf2r [i] * s + sf2i [i] * c;
dfi [i] = v + sf1i [i];
dfi [nbr_coef - i] = v - sf1i [i];
}
coef_index += d_nbr_coef;
}
while (coef_index < _length);
}
// Transform in several pass
template <class DT>
void FFTReal <DT>::compute_ifft_general (const DataType f [], DataType x []) const
{
assert (f != 0);
assert (f != use_buffer ());
assert (x != 0);
assert (x != use_buffer ());
assert (x != f);
DataType * sf = const_cast <DataType *> (f);
DataType * df;
DataType * df_temp;
if (_nbr_bits & 1)
{
df = use_buffer ();
df_temp = x;
}
else
{
df = x;
df_temp = use_buffer ();
}
for (int pass = _nbr_bits - 1; pass >= 3; -- pass)
{
compute_inverse_pass_n (df, sf, pass);
if (pass < _nbr_bits - 1)
{
DataType * const temp_ptr = df;
df = sf;
sf = temp_ptr;
}
else
{
sf = df;
df = df_temp;
}
}
compute_inverse_pass_3 (df, sf);
compute_inverse_pass_1_2 (x, df);
}
template <class DT>
void FFTReal <DT>::compute_inverse_pass_n (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass >= 3);
assert (pass < _nbr_bits);
if (pass <= TRIGO_BD_LIMIT)
{
compute_inverse_pass_n_lut (df, sf, pass);
}
else
{
compute_inverse_pass_n_osc (df, sf, pass);
}
}
template <class DT>
void FFTReal <DT>::compute_inverse_pass_n_lut (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass >= 3);
assert (pass < _nbr_bits);
const long nbr_coef = 1 << pass;
const long h_nbr_coef = nbr_coef >> 1;
const long d_nbr_coef = nbr_coef << 1;
long coef_index = 0;
const DataType * const cos_ptr = get_trigo_ptr (pass);
do
{
const DataType * const sfr = sf + coef_index;
const DataType * const sfi = sfr + nbr_coef;
DataType * const df1r = df + coef_index;
DataType * const df2r = df1r + nbr_coef;
// Extreme coefficients are always real
df1r [0] = sfr [0] + sfi [0]; // + sfr [nbr_coef]
df2r [0] = sfr [0] - sfi [0]; // - sfr [nbr_coef]
df1r [h_nbr_coef] = sfr [h_nbr_coef] * 2;
df2r [h_nbr_coef] = sfi [h_nbr_coef] * 2;
// Others are conjugate complex numbers
DataType * const df1i = df1r + h_nbr_coef;
DataType * const df2i = df1i + nbr_coef;
for (long i = 1; i < h_nbr_coef; ++ i)
{
df1r [i] = sfr [i] + sfi [-i]; // + sfr [nbr_coef - i]
df1i [i] = sfi [i] - sfi [nbr_coef - i];
const DataType c = cos_ptr [i]; // cos (i*PI/nbr_coef);
const DataType s = cos_ptr [h_nbr_coef - i]; // sin (i*PI/nbr_coef);
const DataType vr = sfr [i] - sfi [-i]; // - sfr [nbr_coef - i]
const DataType vi = sfi [i] + sfi [nbr_coef - i];
df2r [i] = vr * c + vi * s;
df2i [i] = vi * c - vr * s;
}
coef_index += d_nbr_coef;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_inverse_pass_n_osc (DataType df [], const DataType sf [], int pass) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
assert (pass > TRIGO_BD_LIMIT);
assert (pass < _nbr_bits);
const long nbr_coef = 1 << pass;
const long h_nbr_coef = nbr_coef >> 1;
const long d_nbr_coef = nbr_coef << 1;
long coef_index = 0;
OscType & osc = _trigo_osc [pass - (TRIGO_BD_LIMIT + 1)];
do
{
const DataType * const sfr = sf + coef_index;
const DataType * const sfi = sfr + nbr_coef;
DataType * const df1r = df + coef_index;
DataType * const df2r = df1r + nbr_coef;
osc.clear_buffers ();
// Extreme coefficients are always real
df1r [0] = sfr [0] + sfi [0]; // + sfr [nbr_coef]
df2r [0] = sfr [0] - sfi [0]; // - sfr [nbr_coef]
df1r [h_nbr_coef] = sfr [h_nbr_coef] * 2;
df2r [h_nbr_coef] = sfi [h_nbr_coef] * 2;
// Others are conjugate complex numbers
DataType * const df1i = df1r + h_nbr_coef;
DataType * const df2i = df1i + nbr_coef;
for (long i = 1; i < h_nbr_coef; ++ i)
{
df1r [i] = sfr [i] + sfi [-i]; // + sfr [nbr_coef - i]
df1i [i] = sfi [i] - sfi [nbr_coef - i];
osc.step ();
const DataType c = osc.get_cos ();
const DataType s = osc.get_sin ();
const DataType vr = sfr [i] - sfi [-i]; // - sfr [nbr_coef - i]
const DataType vi = sfi [i] + sfi [nbr_coef - i];
df2r [i] = vr * c + vi * s;
df2i [i] = vi * c - vr * s;
}
coef_index += d_nbr_coef;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_inverse_pass_3 (DataType df [], const DataType sf []) const
{
assert (df != 0);
assert (sf != 0);
assert (df != sf);
const DataType sqrt2_2 = DataType (SQRT2 * 0.5);
long coef_index = 0;
do
{
df [coef_index] = sf [coef_index] + sf [coef_index + 4];
df [coef_index + 4] = sf [coef_index] - sf [coef_index + 4];
df [coef_index + 2] = sf [coef_index + 2] * 2;
df [coef_index + 6] = sf [coef_index + 6] * 2;
df [coef_index + 1] = sf [coef_index + 1] + sf [coef_index + 3];
df [coef_index + 3] = sf [coef_index + 5] - sf [coef_index + 7];
const DataType vr = sf [coef_index + 1] - sf [coef_index + 3];
const DataType vi = sf [coef_index + 5] + sf [coef_index + 7];
df [coef_index + 5] = (vr + vi) * sqrt2_2;
df [coef_index + 7] = (vi - vr) * sqrt2_2;
coef_index += 8;
}
while (coef_index < _length);
}
template <class DT>
void FFTReal <DT>::compute_inverse_pass_1_2 (DataType x [], const DataType sf []) const
{
assert (x != 0);
assert (sf != 0);
assert (x != sf);
const long * bit_rev_lut_ptr = get_br_ptr ();
const DataType * sf2 = sf;
long coef_index = 0;
do
{
{
const DataType b_0 = sf2 [0] + sf2 [2];
const DataType b_2 = sf2 [0] - sf2 [2];
const DataType b_1 = sf2 [1] * 2;
const DataType b_3 = sf2 [3] * 2;
x [bit_rev_lut_ptr [0]] = b_0 + b_1;
x [bit_rev_lut_ptr [1]] = b_0 - b_1;
x [bit_rev_lut_ptr [2]] = b_2 + b_3;
x [bit_rev_lut_ptr [3]] = b_2 - b_3;
}
{
const DataType b_0 = sf2 [4] + sf2 [6];
const DataType b_2 = sf2 [4] - sf2 [6];
const DataType b_1 = sf2 [5] * 2;
const DataType b_3 = sf2 [7] * 2;
x [bit_rev_lut_ptr [4]] = b_0 + b_1;
x [bit_rev_lut_ptr [5]] = b_0 - b_1;
x [bit_rev_lut_ptr [6]] = b_2 + b_3;
x [bit_rev_lut_ptr [7]] = b_2 - b_3;
}
sf2 += 8;
coef_index += 8;
bit_rev_lut_ptr += 8;
}
while (coef_index < _length);
}
#endif // FFTReal_CODEHEADER_INCLUDED
#undef FFTReal_CURRENT_CODEHEADER
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/