cannam@64: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
cannam@64: 
cannam@64: /*
cannam@64:     Vamp
cannam@64: 
cannam@64:     An API for audio analysis and feature extraction plugins.
cannam@64: 
cannam@64:     Centre for Digital Music, Queen Mary, University of London.
cannam@71:     Copyright 2006-2007 Chris Cannam and QMUL.
cannam@64:   
cannam@103:     This file is based in part on Don Cross's public domain FFT
cannam@103:     implementation.
cannam@103: 
cannam@64:     Permission is hereby granted, free of charge, to any person
cannam@64:     obtaining a copy of this software and associated documentation
cannam@64:     files (the "Software"), to deal in the Software without
cannam@64:     restriction, including without limitation the rights to use, copy,
cannam@64:     modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@64:     of the Software, and to permit persons to whom the Software is
cannam@64:     furnished to do so, subject to the following conditions:
cannam@64: 
cannam@64:     The above copyright notice and this permission notice shall be
cannam@64:     included in all copies or substantial portions of the Software.
cannam@64: 
cannam@64:     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@64:     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@64:     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@64:     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@64:     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@64:     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@64:     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@64: 
cannam@64:     Except as contained in this notice, the names of the Centre for
cannam@64:     Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@64:     shall not be used in advertising or otherwise to promote the sale,
cannam@64:     use or other dealings in this Software without prior written
cannam@64:     authorization.
cannam@64: */
cannam@64: 
cannam@64: #include "PluginInputDomainAdapter.h"
cannam@64: 
cannam@64: #include <cmath>
cannam@64: 
cannam@101: 
cannam@101: /**
cannam@101:  * If you want to compile using FFTW instead of the built-in FFT
cannam@101:  * implementation for the PluginInputDomainAdapter, define HAVE_FFTW3
cannam@101:  * in the Makefile.
cannam@101:  *
cannam@103:  * Be aware that FFTW is licensed under the GPL -- unlike this SDK,
cannam@103:  * which is provided under a more liberal BSD license in order to
cannam@103:  * permit use in closed source applications.  The use of FFTW would
cannam@103:  * mean that your code would need to be licensed under the GPL as
cannam@103:  * well.  Do not define this symbol unless you understand and accept
cannam@103:  * the implications of this.
cannam@103:  *
cannam@103:  * Parties such as Linux distribution packagers who redistribute this
cannam@103:  * SDK for use in other programs should _not_ define this symbol, as
cannam@103:  * it would change the effective licensing terms under which the SDK
cannam@103:  * was available to third party developers.
cannam@103:  *
cannam@103:  * The default is not to use FFTW, and to use the built-in FFT instead.
cannam@101:  * 
cannam@103:  * Note: The FFTW code uses FFTW_MEASURE, and so will perform badly on
cannam@103:  * its first invocation unless the host has saved and restored FFTW
cannam@103:  * wisdom (see the FFTW documentation).
cannam@101:  */
cannam@101: #ifdef HAVE_FFTW3
cannam@101: #include <fftw3.h>
cannam@101: #endif
cannam@101: 
cannam@101: 
cannam@64: namespace Vamp {
cannam@64: 
cannam@64: namespace HostExt {
cannam@64: 
cannam@70: class PluginInputDomainAdapter::Impl
cannam@70: {
cannam@70: public:
cannam@70:     Impl(Plugin *plugin, float inputSampleRate);
cannam@70:     ~Impl();
cannam@70:     
cannam@70:     bool initialise(size_t channels, size_t stepSize, size_t blockSize);
cannam@70: 
cannam@70:     size_t getPreferredStepSize() const;
cannam@70:     size_t getPreferredBlockSize() const;
cannam@70: 
cannam@70:     FeatureSet process(const float *const *inputBuffers, RealTime timestamp);
cannam@70: 
cannam@70: protected:
cannam@70:     Plugin *m_plugin;
cannam@70:     float m_inputSampleRate;
cannam@101:     int m_channels;
cannam@101:     int m_blockSize;
cannam@70:     float **m_freqbuf;
cannam@101: 
cannam@70:     double *m_ri;
cannam@101:     double *m_window;
cannam@101: 
cannam@101: #ifdef HAVE_FFTW3
cannam@101:     fftw_plan m_plan;
cannam@101:     fftw_complex *m_cbuf;
cannam@101: #else
cannam@70:     double *m_ro;
cannam@70:     double *m_io;
cannam@70:     void fft(unsigned int n, bool inverse,
cannam@70:              double *ri, double *ii, double *ro, double *io);
cannam@101: #endif
cannam@70: 
cannam@70:     size_t makeBlockSizeAcceptable(size_t) const;
cannam@70: };
cannam@70: 
cannam@64: PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) :
cannam@70:     PluginWrapper(plugin)
cannam@70: {
cannam@70:     m_impl = new Impl(plugin, m_inputSampleRate);
cannam@70: }
cannam@70: 
cannam@70: PluginInputDomainAdapter::~PluginInputDomainAdapter()
cannam@70: {
cannam@70:     delete m_impl;
cannam@70: }
cannam@70:   
cannam@70: bool
cannam@70: PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@70: {
cannam@70:     return m_impl->initialise(channels, stepSize, blockSize);
cannam@70: }
cannam@70: 
cannam@70: Plugin::InputDomain
cannam@70: PluginInputDomainAdapter::getInputDomain() const
cannam@70: {
cannam@70:     return TimeDomain;
cannam@70: }
cannam@70: 
cannam@70: size_t
cannam@70: PluginInputDomainAdapter::getPreferredStepSize() const
cannam@70: {
cannam@70:     return m_impl->getPreferredStepSize();
cannam@70: }
cannam@70: 
cannam@70: size_t
cannam@70: PluginInputDomainAdapter::getPreferredBlockSize() const
cannam@70: {
cannam@70:     return m_impl->getPreferredBlockSize();
cannam@70: }
cannam@70: 
cannam@70: Plugin::FeatureSet
cannam@70: PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime timestamp)
cannam@70: {
cannam@70:     return m_impl->process(inputBuffers, timestamp);
cannam@70: }
cannam@70: 
cannam@92: PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) :
cannam@70:     m_plugin(plugin),
cannam@70:     m_inputSampleRate(inputSampleRate),
cannam@64:     m_channels(0),
cannam@64:     m_blockSize(0),
cannam@101:     m_freqbuf(0),
cannam@101:     m_ri(0),
cannam@101:     m_window(0),
cannam@101: #ifdef HAVE_FFTW3
cannam@101:     m_plan(0),
cannam@101:     m_cbuf(0)
cannam@101: #else
cannam@101:     m_ro(0),
cannam@101:     m_io(0)
cannam@101: #endif
cannam@64: {
cannam@64: }
cannam@64: 
cannam@70: PluginInputDomainAdapter::Impl::~Impl()
cannam@64: {
cannam@70:     // the adapter will delete the plugin
cannam@70: 
cannam@70:     if (m_channels > 0) {
cannam@101:         for (int c = 0; c < m_channels; ++c) {
cannam@70:             delete[] m_freqbuf[c];
cannam@70:         }
cannam@70:         delete[] m_freqbuf;
cannam@101: #ifdef HAVE_FFTW3
cannam@101:         if (m_plan) {
cannam@101:             fftw_destroy_plan(m_plan);
cannam@101:             fftw_free(m_ri);
cannam@101:             fftw_free(m_cbuf);
cannam@101:             m_plan = 0;
cannam@101:         }
cannam@101: #else
cannam@70:         delete[] m_ri;
cannam@70:         delete[] m_ro;
cannam@70:         delete[] m_io;
cannam@101: #endif
cannam@101:         delete[] m_window;
cannam@70:     }
cannam@64: }
cannam@101: 
cannam@101: // for some visual studii apparently
cannam@101: #ifndef M_PI
cannam@101: #define M_PI 3.14159265358979232846
cannam@101: #endif
cannam@64:     
cannam@64: bool
cannam@70: PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@64: {
cannam@64:     if (m_plugin->getInputDomain() == TimeDomain) {
cannam@64: 
cannam@101:         m_blockSize = int(blockSize);
cannam@101:         m_channels = int(channels);
cannam@64: 
cannam@64:         return m_plugin->initialise(channels, stepSize, blockSize);
cannam@64:     }
cannam@64: 
cannam@64:     if (blockSize < 2) {
cannam@70:         std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: blocksize < 2 not supported" << std::endl;
cannam@64:         return false;
cannam@64:     }                
cannam@64:         
cannam@64:     if (blockSize & (blockSize-1)) {
cannam@70:         std::cerr << "ERROR: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: non-power-of-two\nblocksize " << blockSize << " not supported" << std::endl;
cannam@64:         return false;
cannam@64:     }
cannam@64: 
cannam@64:     if (m_channels > 0) {
cannam@101:         for (int c = 0; c < m_channels; ++c) {
cannam@64:             delete[] m_freqbuf[c];
cannam@64:         }
cannam@64:         delete[] m_freqbuf;
cannam@101: #ifdef HAVE_FFTW3
cannam@101:         if (m_plan) {
cannam@101:             fftw_destroy_plan(m_plan);
cannam@101:             fftw_free(m_ri);
cannam@101:             fftw_free(m_cbuf);
cannam@101:             m_plan = 0;
cannam@101:         }
cannam@101: #else
cannam@64:         delete[] m_ri;
cannam@64:         delete[] m_ro;
cannam@64:         delete[] m_io;
cannam@101: #endif
cannam@101:         delete[] m_window;
cannam@64:     }
cannam@64: 
cannam@101:     m_blockSize = int(blockSize);
cannam@101:     m_channels = int(channels);
cannam@64: 
cannam@64:     m_freqbuf = new float *[m_channels];
cannam@101:     for (int c = 0; c < m_channels; ++c) {
cannam@64:         m_freqbuf[c] = new float[m_blockSize + 2];
cannam@64:     }
cannam@101:     m_window = new double[m_blockSize];
cannam@101: 
cannam@101:     for (int i = 0; i < m_blockSize; ++i) {
cannam@101:         // Hanning window
cannam@101:         m_window[i] = (0.50 - 0.50 * cos((2.0 * M_PI * i) / m_blockSize));
cannam@101:     }
cannam@101: 
cannam@101: #ifdef HAVE_FFTW3
cannam@101:     m_ri = (double *)fftw_malloc(blockSize * sizeof(double));
cannam@101:     m_cbuf = (fftw_complex *)fftw_malloc((blockSize/2 + 1) * sizeof(fftw_complex));
cannam@101:     m_plan = fftw_plan_dft_r2c_1d(blockSize, m_ri, m_cbuf, FFTW_MEASURE);
cannam@101: #else
cannam@64:     m_ri = new double[m_blockSize];
cannam@64:     m_ro = new double[m_blockSize];
cannam@64:     m_io = new double[m_blockSize];
cannam@101: #endif
cannam@64: 
cannam@64:     return m_plugin->initialise(channels, stepSize, blockSize);
cannam@64: }
cannam@64: 
cannam@64: size_t
cannam@70: PluginInputDomainAdapter::Impl::getPreferredStepSize() const
cannam@64: {
cannam@64:     size_t step = m_plugin->getPreferredStepSize();
cannam@64: 
cannam@64:     if (step == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) {
cannam@64:         step = getPreferredBlockSize() / 2;
cannam@64:     }
cannam@64: 
cannam@64:     return step;
cannam@64: }
cannam@64: 
cannam@64: size_t
cannam@70: PluginInputDomainAdapter::Impl::getPreferredBlockSize() const
cannam@64: {
cannam@64:     size_t block = m_plugin->getPreferredBlockSize();
cannam@64: 
cannam@64:     if (m_plugin->getInputDomain() == FrequencyDomain) {
cannam@64:         if (block == 0) {
cannam@64:             block = 1024;
cannam@64:         } else {
cannam@64:             block = makeBlockSizeAcceptable(block);
cannam@64:         }
cannam@64:     }
cannam@64: 
cannam@64:     return block;
cannam@64: }
cannam@64: 
cannam@64: size_t
cannam@70: PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const
cannam@64: {
cannam@64:     if (blockSize < 2) {
cannam@64: 
cannam@70:         std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: blocksize < 2 not" << std::endl
cannam@64:                   << "supported, increasing from " << blockSize << " to 2" << std::endl;
cannam@64:         blockSize = 2;
cannam@64:         
cannam@64:     } else if (blockSize & (blockSize-1)) {
cannam@64:             
cannam@101: #ifdef HAVE_FFTW3
cannam@101:         // not an issue with FFTW
cannam@101: #else
cannam@101: 
cannam@101:         // not a power of two, can't handle that with our built-in FFT
cannam@64:         // implementation
cannam@64: 
cannam@64:         size_t nearest = blockSize;
cannam@64:         size_t power = 0;
cannam@64:         while (nearest > 1) {
cannam@64:             nearest >>= 1;
cannam@64:             ++power;
cannam@64:         }
cannam@64:         nearest = 1;
cannam@64:         while (power) {
cannam@64:             nearest <<= 1;
cannam@64:             --power;
cannam@64:         }
cannam@64:         
cannam@64:         if (blockSize - nearest > (nearest*2) - blockSize) {
cannam@64:             nearest = nearest*2;
cannam@64:         }
cannam@64:         
cannam@70:         std::cerr << "WARNING: Vamp::HostExt::PluginInputDomainAdapter::Impl::initialise: non-power-of-two\nblocksize " << blockSize << " not supported, using blocksize " << nearest << " instead" << std::endl;
cannam@64:         blockSize = nearest;
cannam@101: 
cannam@101: #endif
cannam@64:     }
cannam@64: 
cannam@64:     return blockSize;
cannam@64: }
cannam@64: 
cannam@64: Plugin::FeatureSet
cannam@70: PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers,
cannam@70:                                         RealTime timestamp)
cannam@64: {
cannam@64:     if (m_plugin->getInputDomain() == TimeDomain) {
cannam@64:         return m_plugin->process(inputBuffers, timestamp);
cannam@64:     }
cannam@64: 
cannam@64:     // The timestamp supplied should be (according to the Vamp::Plugin
cannam@64:     // spec) the time of the start of the time-domain input block.
cannam@64:     // However, we want to pass to the plugin an FFT output calculated
cannam@64:     // from the block of samples _centred_ on that timestamp.
cannam@64:     // 
cannam@64:     // We have two options:
cannam@64:     // 
cannam@64:     // 1. Buffer the input, calculating the fft of the values at the
cannam@64:     // passed-in block minus blockSize/2 rather than starting at the
cannam@64:     // passed-in block.  So each time we call process on the plugin,
cannam@64:     // we are passing in the same timestamp as was passed to our own
cannam@64:     // process plugin, but not (the frequency domain representation
cannam@64:     // of) the same set of samples.  Advantages: avoids confusion in
cannam@64:     // the host by ensuring the returned values have timestamps
cannam@64:     // comparable with that passed in to this function (in fact this
cannam@64:     // is pretty much essential for one-value-per-block outputs);
cannam@64:     // consistent with hosts such as SV that deal with the
cannam@64:     // frequency-domain transform themselves.  Disadvantages: means
cannam@64:     // making the not necessarily correct assumption that the samples
cannam@64:     // preceding the first official block are all zero (or some other
cannam@64:     // known value).
cannam@64:     //
cannam@64:     // 2. Increase the passed-in timestamps by half the blocksize.  So
cannam@64:     // when we call process, we are passing in the frequency domain
cannam@64:     // representation of the same set of samples as passed to us, but
cannam@64:     // with a different timestamp.  Advantages: simplicity; avoids
cannam@64:     // iffy assumption mentioned above.  Disadvantages: inconsistency
cannam@64:     // with SV in cases where stepSize != blockSize/2; potential
cannam@64:     // confusion arising from returned timestamps being calculated
cannam@64:     // from the adjusted input timestamps rather than the original
cannam@64:     // ones (and inaccuracy where the returned timestamp is implied,
cannam@64:     // as in one-value-per-block).
cannam@64:     //
cannam@64:     // Neither way is ideal, but I don't think either is strictly
cannam@64:     // incorrect either.  I think this is just a case where the same
cannam@64:     // plugin can legitimately produce differing results from the same
cannam@64:     // input data, depending on how that data is packaged.
cannam@64:     // 
cannam@64:     // We'll go for option 2, adjusting the timestamps.  Note in
cannam@64:     // particular that this means some results can differ from those
cannam@64:     // produced by SV.
cannam@64: 
cannam@65: //    std::cerr << "PluginInputDomainAdapter: sampleRate " << m_inputSampleRate << ", blocksize " << m_blockSize << ", adjusting time from " << timestamp;
cannam@64: 
cannam@68:     timestamp = timestamp + RealTime::frame2RealTime
cannam@68:         (m_blockSize/2, int(m_inputSampleRate + 0.5));
cannam@64: 
cannam@65: //    std::cerr << " to " << timestamp << std::endl;
cannam@64: 
cannam@101:     for (int c = 0; c < m_channels; ++c) {
cannam@64: 
cannam@101:         for (int i = 0; i < m_blockSize; ++i) {
cannam@101:             m_ri[i] = double(inputBuffers[c][i]) * m_window[i];
cannam@64:         }
cannam@64: 
cannam@101:         for (int i = 0; i < m_blockSize/2; ++i) {
cannam@64:             // FFT shift
cannam@64:             double value = m_ri[i];
cannam@64:             m_ri[i] = m_ri[i + m_blockSize/2];
cannam@64:             m_ri[i + m_blockSize/2] = value;
cannam@64:         }
cannam@64: 
cannam@101: #ifdef HAVE_FFTW3
cannam@101: 
cannam@101:         fftw_execute(m_plan);
cannam@101: 
cannam@101:         for (int i = 0; i <= m_blockSize/2; ++i) {
cannam@101:             m_freqbuf[c][i * 2] = float(m_cbuf[i][0]);
cannam@101:             m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i][1]);
cannam@101:         }
cannam@101: 
cannam@101: #else
cannam@101: 
cannam@64:         fft(m_blockSize, false, m_ri, 0, m_ro, m_io);
cannam@64: 
cannam@101:         for (int i = 0; i <= m_blockSize/2; ++i) {
cannam@101:             m_freqbuf[c][i * 2] = float(m_ro[i]);
cannam@101:             m_freqbuf[c][i * 2 + 1] = float(m_io[i]);
cannam@64:         }
cannam@101: 
cannam@101: #endif
cannam@64:     }
cannam@64: 
cannam@64:     return m_plugin->process(m_freqbuf, timestamp);
cannam@64: }
cannam@64: 
cannam@101: #ifndef HAVE_FFTW3
cannam@101: 
cannam@64: void
cannam@70: PluginInputDomainAdapter::Impl::fft(unsigned int n, bool inverse,
cannam@70:                                     double *ri, double *ii, double *ro, double *io)
cannam@64: {
cannam@64:     if (!ri || !ro || !io) return;
cannam@64: 
cannam@64:     unsigned int bits;
cannam@64:     unsigned int i, j, k, m;
cannam@64:     unsigned int blockSize, blockEnd;
cannam@64: 
cannam@64:     double tr, ti;
cannam@64: 
cannam@64:     if (n < 2) return;
cannam@64:     if (n & (n-1)) return;
cannam@64: 
cannam@64:     double angle = 2.0 * M_PI;
cannam@64:     if (inverse) angle = -angle;
cannam@64: 
cannam@64:     for (i = 0; ; ++i) {
cannam@64: 	if (n & (1 << i)) {
cannam@64: 	    bits = i;
cannam@64: 	    break;
cannam@64: 	}
cannam@64:     }
cannam@64: 
cannam@64:     static unsigned int tableSize = 0;
cannam@64:     static int *table = 0;
cannam@64: 
cannam@64:     if (tableSize != n) {
cannam@64: 
cannam@64: 	delete[] table;
cannam@64: 
cannam@64: 	table = new int[n];
cannam@64: 
cannam@64: 	for (i = 0; i < n; ++i) {
cannam@64: 	
cannam@64: 	    m = i;
cannam@64: 
cannam@64: 	    for (j = k = 0; j < bits; ++j) {
cannam@64: 		k = (k << 1) | (m & 1);
cannam@64: 		m >>= 1;
cannam@64: 	    }
cannam@64: 
cannam@64: 	    table[i] = k;
cannam@64: 	}
cannam@64: 
cannam@64: 	tableSize = n;
cannam@64:     }
cannam@64: 
cannam@64:     if (ii) {
cannam@64: 	for (i = 0; i < n; ++i) {
cannam@64: 	    ro[table[i]] = ri[i];
cannam@64: 	    io[table[i]] = ii[i];
cannam@64: 	}
cannam@64:     } else {
cannam@64: 	for (i = 0; i < n; ++i) {
cannam@64: 	    ro[table[i]] = ri[i];
cannam@64: 	    io[table[i]] = 0.0;
cannam@64: 	}
cannam@64:     }
cannam@64: 
cannam@64:     blockEnd = 1;
cannam@64: 
cannam@64:     for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
cannam@64: 
cannam@64: 	double delta = angle / (double)blockSize;
cannam@64: 	double sm2 = -sin(-2 * delta);
cannam@64: 	double sm1 = -sin(-delta);
cannam@64: 	double cm2 = cos(-2 * delta);
cannam@64: 	double cm1 = cos(-delta);
cannam@64: 	double w = 2 * cm1;
cannam@64: 	double ar[3], ai[3];
cannam@64: 
cannam@64: 	for (i = 0; i < n; i += blockSize) {
cannam@64: 
cannam@64: 	    ar[2] = cm2;
cannam@64: 	    ar[1] = cm1;
cannam@64: 
cannam@64: 	    ai[2] = sm2;
cannam@64: 	    ai[1] = sm1;
cannam@64: 
cannam@64: 	    for (j = i, m = 0; m < blockEnd; j++, m++) {
cannam@64: 
cannam@64: 		ar[0] = w * ar[1] - ar[2];
cannam@64: 		ar[2] = ar[1];
cannam@64: 		ar[1] = ar[0];
cannam@64: 
cannam@64: 		ai[0] = w * ai[1] - ai[2];
cannam@64: 		ai[2] = ai[1];
cannam@64: 		ai[1] = ai[0];
cannam@64: 
cannam@64: 		k = j + blockEnd;
cannam@64: 		tr = ar[0] * ro[k] - ai[0] * io[k];
cannam@64: 		ti = ar[0] * io[k] + ai[0] * ro[k];
cannam@64: 
cannam@64: 		ro[k] = ro[j] - tr;
cannam@64: 		io[k] = io[j] - ti;
cannam@64: 
cannam@64: 		ro[j] += tr;
cannam@64: 		io[j] += ti;
cannam@64: 	    }
cannam@64: 	}
cannam@64: 
cannam@64: 	blockEnd = blockSize;
cannam@64:     }
cannam@64: 
cannam@64:     if (inverse) {
cannam@64: 
cannam@64: 	double denom = (double)n;
cannam@64: 
cannam@64: 	for (i = 0; i < n; i++) {
cannam@64: 	    ro[i] /= denom;
cannam@64: 	    io[i] /= denom;
cannam@64: 	}
cannam@64:     }
cannam@64: }
cannam@64: 
cannam@101: #endif
cannam@101: 
cannam@64: }
cannam@64:         
cannam@64: }
cannam@64: