Below was originally sent 12/5 -- but because I flubbed up
my ML subscription... it didn't make it to you...
==========================================================
Hello,
Attached is a patch against 1.8.1 to add version of the
EngineFilterIIR that is SSE3 optimized. Yes, it needs more
comments... but here's what I have for now.
Brief explanation of the implementation:
0. I used assert() a lot... so to get it to compile right
use: -O3 -DNDEBUG -msse -msse2 -msse3 -ffast-math
-funroll-loops
1. Left and Right channels are calculated in parallel.
(This is mostly the point of doing the SSE3 opts.)
2. The buffer of the previous N samples is converted
to a ring buffer in order to avoid the data shuffling.
x[0] = x[1]; x[1] = x[2]; // ... etc ...
3. To replace the shuffling, the X and Y coefficients are
put in an NxN matrix that has them pre-shuffled. The
two matrices are put together side-by-side so that we
can span rows by simple pointer increments. E.g. for
the 4th order:
CXY = [ [ X coeffs ] [ Y coeffs ] ]
CXY = [ [ 1.0, c1, c2, c1, /**/ c3, c4, c5, c6 ],
[ c1, c2, c1, 1.0, /**/ c4, c5, c6, c3 ],
[ c2, c1, 1.0, c1, /**/ c5, c6, c3, c4 ],
[ c1, 1.0, c1, c2, /**/ c6, c3, c4, c5 ] ];
4. Several things are converted to local variables in
the process() method so that the compiler to avoid
cache/memory slow-downs.
5. The implementation is templated... which should reduce
maintenance and still allow for hard-coded N values
to be optimized.
Thanks,
Gabriel
diff -urpN mixxx-1.8.1.orig/src/engine/enginefilteriir.cpp mixxx-1.8.1/src/engine/enginefilteriir.cpp
--- mixxx-1.8.1.orig/src/engine/enginefilteriir.cpp 2010-10-17 22:34:21.000000000 -0500
+++ mixxx-1.8.1/src/engine/enginefilteriir.cpp 2010-12-04 17:54:35.414262615 -0600
@@ -15,109 +15,368 @@
***************************************************************************/
#include "enginefilteriir.h"
+#include <cstring>
+#include <cassert>
+#include <memory>
+
+#ifdef __SSE3__
+#define IIR_ENABLE_SSE3
+#else
+#warning "Using NON-SSE3 optimized version of filter"
+#endif
+
+#ifdef IIR_ENABLE_SSE3
+#include <xmmintrin.h> // SSE
+#include <emmintrin.h> // SSE2
+#include <pmmintrin.h> // SSE3
+#endif
-EngineFilterIIR::EngineFilterIIR(const double * pCoefs, int iOrder)
+namespace DetailsEngineFilterIIR
{
- order = iOrder;
- coefs = pCoefs;
+#ifdef IIR_ENABLE_SSE3
- // Reset the yv's:
- memset(yv1, 0, sizeof(yv1));
- memset(yv2, 0, sizeof(yv2));
- memset(xv1, 0, sizeof(xv1));
- memset(xv2, 0, sizeof(xv2));
+ typedef __m128d __v2df;
+ typedef union {
+ __v2df v;
+ double d[2];
+ } v2df;
+
+ inline bool not_aligned_16(void* ptr)
+ {
+ return (((intptr_t)ptr) % 16) != 0;
+ }
+
+ // This is the SSE-optimized version of the filter
+ template< short Order >
+ class FilterSSE : public EngineObject
+ {
+ public:
+ FilterSSE(const double *coefs);
+ ~FilterSSE();
+ void process(const CSAMPLE *pIn, const CSAMPLE *pOut, const int iBufferSize);
+ protected:
+ enum {
+ ORDER = Order
+ };
+ unsigned short _k;
+ const unsigned short _k_mask;
+ v2df * _gain;
+ v2df * _CXY[Order];
+ v2df *_xv; // Circular buffer
+ v2df *_yv; // Circular buffer, adjacent to _xv
+ std::auto_ptr<v2df> _memory;
+ };
+
+#endif // IIR_ENABLE_SSE3
+
+ class FilterReference : public EngineObject
+ {
+ public:
+ FilterReference(int order, const double* coefs);
+ ~FilterReference();
+ void process(const CSAMPLE *pIn, const CSAMPLE *pOut, const int iBufferSize);
+ protected:
+ int order;
+ const double *coefs;
+ enum {
+ MAXNZEROS=8,
+ MAXNPOLES=8
+ };
+ double xv1[MAXNZEROS+1], yv1[MAXNPOLES+1];
+ double xv2[MAXNZEROS+1], yv2[MAXNPOLES+1];
+ };
+
+} // namespace DetailsEngineFilterIIR
+
+
+EngineFilterIIR::EngineFilterIIR(const double *pCoefs, int iOrder FILTER)
+{
+
+#ifdef IIR_ENABLE_SSE3
+ switch(iOrder) {
+ DetailsEngineFilterIIR::FilterSSE<2> *fo2;
+ DetailsEngineFilterIIR::FilterSSE<4> *fo4;
+ DetailsEngineFilterIIR::FilterSSE<8> *fo8;
+ case 2:
+ fo2 = new DetailsEngineFilterIIR::FilterSSE<2>(pCoefs);
+ _d = dynamic_cast<EngineObject*>(fo2);
+ break;
+ case 4:
+ fo4 = new DetailsEngineFilterIIR::FilterSSE<4>(pCoefs);
+ _d = dynamic_cast<EngineObject*>(fo4);
+ break;
+ case 8:
+ fo8 = new DetailsEngineFilterIIR::FilterSSE<8>(pCoefs);
+ _d = dynamic_cast<EngineObject*>(fo8);
+ break;
+ default:
+ assert(false);
+ }
+#else // IIR_ENABLE_SSE3
+ DetailsEngineFilterIIR::FilterReference *f;
+ f = new DetailsEngineFilterIIR::FilterReference(iOrder, pCoefs);
+ _d = dynamic_cast<EngineObject*>(f);
+#endif
+
+ assert(_d);
}
EngineFilterIIR::~EngineFilterIIR()
{
+ delete _d;
+ _d = 0;
}
void EngineFilterIIR::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize)
{
- CSAMPLE * pOutput = (CSAMPLE *)pOut;
- double GAIN = coefs[0];
- int i;
- for (i=0; i<iBufferSize; i+=2)
- {
- if (order==8)
- {
- //8th order:
- // Channel 1
- xv1[0] = xv1[1]; xv1[1] = xv1[2]; xv1[2] = xv1[3]; xv1[3] = xv1[4];
- xv1[4] = xv1[5]; xv1[5] = xv1[6]; xv1[6] = xv1[7]; xv1[7] = xv1[8];
- xv1[8] = pIn[i]/GAIN;
- yv1[0] = yv1[1]; yv1[1] = yv1[2]; yv1[2] = yv1[3]; yv1[3] = yv1[4];
- yv1[4] = yv1[5]; yv1[5] = yv1[6]; yv1[6] = yv1[7]; yv1[7] = yv1[8];
- yv1[8] = (xv1[0] + xv1[8]) + coefs[1] * (xv1[1] + xv1[7]) +
- coefs[2] * (xv1[2] + xv1[6]) +
- coefs[3] * (xv1[3] + xv1[5]) + coefs[4] * xv1[4] +
- (coefs[5] * yv1[0]) + ( coefs[6] * yv1[1]) +
- (coefs[7] * yv1[2]) + ( coefs[8] * yv1[3]) +
- (coefs[9] * yv1[4]) + ( coefs[10] * yv1[5]) +
- (coefs[11] * yv1[6]) + ( coefs[12] * yv1[7]);
- Q_ASSERT(yv1[8]<100000 || yv1[8]>-100000);
- pOutput[i] = yv1[8];
-
- // Channel 2
- xv2[0] = xv2[1]; xv2[1] = xv2[2]; xv2[2] = xv2[3]; xv2[3] = xv2[4];
- xv2[4] = xv2[5]; xv2[5] = xv2[6]; xv2[6] = xv2[7]; xv2[7] = xv2[8];
- xv2[8] = pIn[i+1]/GAIN;
- yv2[0] = yv2[1]; yv2[1] = yv2[2]; yv2[2] = yv2[3]; yv2[3] = yv2[4];
- yv2[4] = yv2[5]; yv2[5] = yv2[6]; yv2[6] = yv2[7]; yv2[7] = yv2[8];
- yv2[8] = (xv2[0] + xv2[8]) + coefs[1] * (xv2[1] + xv2[7]) +
- coefs[2] * (xv2[2] + xv2[6]) +
- coefs[3] * (xv2[3] + xv2[5]) + coefs[4] * xv2[4] +
- (coefs[5] * yv2[0]) + ( coefs[6] * yv2[1]) +
- (coefs[7] * yv2[2]) + ( coefs[8] * yv2[3]) +
- (coefs[9] * yv2[4]) + ( coefs[10] * yv2[5]) +
- (coefs[11] * yv2[6]) + ( coefs[12] * yv2[7]);
- Q_ASSERT(yv2[8]<100000 || yv2[8]>-100000);
- pOutput[i+1] = yv2[8];
- }
- else if (order==2)
- {
- // Second order
- xv1[0] = xv1[1]; xv1[1] = xv1[2];
- xv1[2] = pIn[i] / GAIN;
- yv1[0] = yv1[1]; yv1[1] = yv1[2];
- yv1[2] = (xv1[0] + xv1[2]) + coefs[1] * xv1[1] + ( coefs[2] * yv1[0]) + (coefs[3] * yv1[1]);
- pOutput[i] = yv1[2];
-
- xv2[0] = xv2[1]; xv2[1] = xv2[2];
- xv2[2] = pIn[i+1] / GAIN;
- yv2[0] = yv2[1]; yv2[1] = yv2[2];
- yv2[2] = (xv2[0] + xv2[2]) + coefs[1] * xv2[1] + ( coefs[2] * yv2[0]) + (coefs[3] * yv2[1]);
- pOutput[i+1] = yv2[2];
- }
- else
- {
- // Fourth order
- xv1[0] = xv1[1]; xv1[1] = xv1[2]; xv1[2] = xv1[3]; xv1[3] = xv1[4];
- xv1[4] = pIn[i] / GAIN;
- yv1[0] = yv1[1]; yv1[1] = yv1[2]; yv1[2] = yv1[3]; yv1[3] = yv1[4];
- yv1[4] = (xv1[0] + xv1[4]) + coefs[1]*(xv1[1]+xv1[3]) + coefs[2] * xv1[2]
- + ( coefs[3] * yv1[0]) + ( coefs[4] * yv1[1])
- + ( coefs[5] * yv1[2]) + ( coefs[6] * yv1[3]);
- pOutput[i] = yv1[4];
-
- xv2[0] = xv2[1]; xv2[1] = xv2[2]; xv2[2] = xv2[3]; xv2[3] = xv2[4];
- xv2[4] = pIn[i+1] / GAIN;
- yv2[0] = yv2[1]; yv2[1] = yv2[2]; yv2[2] = yv2[3]; yv2[3] = yv2[4];
- yv2[4] = (xv2[0] + xv2[4]) + coefs[1]*(xv2[1]+xv2[3]) + coefs[2] * xv2[2]
- + ( coefs[3] * yv2[0]) + ( coefs[4] * yv2[1])
- + ( coefs[5] * yv2[2]) + ( coefs[6] * yv2[3]);
- pOutput[i+1] = yv2[4];
- }
- }
-
-// Check for denormals
- for (i=0; i<=order; ++i)
- {
- xv1[i] = zap_denormal(xv1[i]);
- yv1[i] = zap_denormal(yv1[i]);
- xv2[i] = zap_denormal(xv2[i]);
- yv2[i] = zap_denormal(yv2[i]);
- }
+ _d->process(pIn, pOut, iBufferSize);
}
+namespace DetailsEngineFilterIIR
+{
+#ifdef IIR_ENABLE_SSE3
+ template< short Order >
+ FilterSSE<Order>::FilterSSE(const double *coefs) :
+ _k(0),
+ _k_mask(Order-1)
+ {
+ // ORDER must be a power of 2:
+ assert( (ORDER & (ORDER-1)) == 0 );
+
+ int mem_reqd = 2 * (ORDER*ORDER) + 2 * ORDER + 4; // mem for _CX, _CY, _xv, _yv, padding
+ _memory.reset( new v2df[mem_reqd] );
+ memset(_memory.get(), 0, mem_reqd * sizeof(v2df));
+
+ char* tmp = reinterpret_cast<char*>(_memory.get());
+ v2df* end = (v2df*)tmp + mem_reqd;
+ while( not_aligned_16(tmp) ) ++tmp;
+ v2df* beg = (v2df*)tmp;
+ assert( beg < end );
+
+ int k;
+ _gain = beg;
+ _CXY[0] = _gain + 1;
+ for( k=1 ; k<ORDER ; ++k ) _CXY[k] = _CXY[k-1] + (2*ORDER);
+ _xv = _CXY[ORDER-1] + (2*ORDER);
+ _yv = _xv + ORDER;
+ assert( _yv < end );
+
+ _gain->d[0] = coefs[0];
+ _gain->d[1] = coefs[0];
+
+ // Initialize the _CX and _CY matrices
+ const short turnaround = ORDER/2;
+ short c;
+ _CXY[0][0].d[0] = 1.0;
+ for( c=k=1 ; k <= turnaround ; ++k, ++c ) {
+ _CXY[0][k].d[0] = coefs[c];
+ }
+ for( c-=2 ; k<ORDER ; ++k, --c ) {
+ _CXY[0][k].d[0] = coefs[c];
+ }
+ assert( k == ORDER );
+ assert( c == 0 );
+
+ for( c=turnaround+1 ; k <= (2*ORDER) ; ++k ) {
+ _CXY[0][k].d[0] = coefs[c];
+ }
+
+ for( k=0 ; k<(2*ORDER) ; ++k ) {
+ _CXY[0][k].d[1] = _CXY[0][k].d[0];
+ }
+
+ // Pre-shuffle the coefficients
+ for( k=1 ; k < ORDER ; ++k ) { // Rows
+ for( c=0 ; c < ORDER ; ++c ) { // Cols
+ _CXY[k][c].v = _CXY[k-1][ _k_mask & (c-1) ].v;
+ _CXY[k][ORDER+c].v = _CXY[k-1][ ORDER + (_k_mask & (c-1)) ].v;
+ }
+ }
+
+ }
+
+ template< short Order >
+ FilterSSE<Order>::~FilterSSE()
+ {
+ }
+
+ template< short Order >
+ void FilterSSE<Order>::process(const CSAMPLE * __restrict pIn,
+ const CSAMPLE * __restrict pOut,
+ const int iBufferSize) __restrict
+ {
+ CSAMPLE * pOutput = (CSAMPLE *)pOut;
+ int i;
+ unsigned short k = _k;
+ unsigned short iters;
+ v2df in, tmp;
+ __m128d acc, mreg;
+ __m128d *CXY_start, *CXY_end;
+ __m128d * __restrict CXY;
+ __m128d XY[2*ORDER], *xy;
+ __m128d GAIN = _gain->v;
+
+ memcpy(XY, _xv, 2*ORDER*2*sizeof(double));
+ xy = XY;
+
+ assert(k < ORDER);
+ CXY_start = (__m128d*)_CXY[0];
+ CXY_end = (__m128d*)(_CXY[ORDER-1] + (2*ORDER));
+ CXY = (__m128d*)_CXY[k];
+
+ for (i=0; i<iBufferSize; i+=2)
+ {
+ in.d[0] = pIn[i];
+ in.d[1] = pIn[i+1];
+ in.v /= GAIN;
+
+ acc = _mm_load_pd(&in.d[0]);
+
+ iters = 2*ORDER;
+ while(iters--) {
+ // acc += _xv[i] * _CX[i] (for iters == 0..ORDER-1)
+ // acc += _yv[i] * _CY[i] (for iters == ORDER..2*ORDER-1)
+ mreg = _mm_load_pd((double*)CXY++);
+ mreg = _mm_mul_pd(mreg, *xy++);
+ acc = _mm_add_pd(mreg, acc);
+ }
+
+ tmp.v = acc;
+ pOutput[i] = (float)tmp.d[0];
+ pOutput[i+1] = (float)tmp.d[1];
+ assert(pOutput[i]<=1.0f || pOutput[i] >= -1.0f);
+ assert(pOutput[i+1]<=1.0f || pOutput[i+1] >= -1.0f);
+ assert(pOutput[i]<100000 || pOutput[i] > -100000);
+ assert(pOutput[i+1]<100000 || pOutput[i+1] > -100000);
+
+ if( k == ORDER-1 ) CXY = CXY_start;
+ assert( CXY < CXY_end );
+
+ xy = XY;
+ _mm_store_pd( (double*)(xy+k), in.v ); // _xv[_k]
+ _mm_store_pd( (double*)(xy+ORDER+k), acc ); // _yv[_k]
+
+ ++k;
+ k &= _k_mask;
+ }
+
+ _k = k;
+ memcpy(_xv, XY, 2*ORDER*sizeof(__m128d));
+
+ // Check for denormals
+ for (i=0; i<=ORDER; ++i)
+ {
+ _xv[i].d[0] = zap_denormal(_xv[i].d[0]);
+ _xv[i].d[1] = zap_denormal(_xv[i].d[1]);
+ _yv[i].d[0] = zap_denormal(_yv[i].d[0]);
+ _yv[i].d[1] = zap_denormal(_yv[i].d[1]);
+ }
+ }
+#endif // IIR_ENABLE_SSE3
+
+ FilterReference::FilterReference(int iOrder, const double* pCoefs)
+ {
+ order = iOrder;
+ coefs = pCoefs;
+
+ // Reset the yv's:
+ memset(yv1, 0, sizeof(yv1));
+ memset(yv2, 0, sizeof(yv2));
+ memset(xv1, 0, sizeof(xv1));
+ memset(xv2, 0, sizeof(xv2));
+ }
+
+ FilterReference::~FilterReference()
+ {
+ }
+
+ void FilterReference::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize)
+ {
+ CSAMPLE * pOutput = (CSAMPLE *)pOut;
+ double GAIN = coefs[0];
+ int i;
+ for (i=0; i<iBufferSize; i+=2)
+ {
+ if (order==8)
+ {
+ //8th order:
+ // Channel 1
+ xv1[0] = xv1[1]; xv1[1] = xv1[2]; xv1[2] = xv1[3]; xv1[3] = xv1[4];
+ xv1[4] = xv1[5]; xv1[5] = xv1[6]; xv1[6] = xv1[7]; xv1[7] = xv1[8];
+ xv1[8] = pIn[i]/GAIN;
+ yv1[0] = yv1[1]; yv1[1] = yv1[2]; yv1[2] = yv1[3]; yv1[3] = yv1[4];
+ yv1[4] = yv1[5]; yv1[5] = yv1[6]; yv1[6] = yv1[7]; yv1[7] = yv1[8];
+ yv1[8] = (xv1[0] + xv1[8]) + coefs[1] * (xv1[1] + xv1[7]) +
+ coefs[2] * (xv1[2] + xv1[6]) +
+ coefs[3] * (xv1[3] + xv1[5]) + coefs[4] * xv1[4] +
+ (coefs[5] * yv1[0]) + ( coefs[6] * yv1[1]) +
+ (coefs[7] * yv1[2]) + ( coefs[8] * yv1[3]) +
+ (coefs[9] * yv1[4]) + ( coefs[10] * yv1[5]) +
+ (coefs[11] * yv1[6]) + ( coefs[12] * yv1[7]);
+ assert(yv1[8]<100000 || yv1[8]>-100000);
+ pOutput[i] = yv1[8];
+
+ // Channel 2
+ xv2[0] = xv2[1]; xv2[1] = xv2[2]; xv2[2] = xv2[3]; xv2[3] = xv2[4];
+ xv2[4] = xv2[5]; xv2[5] = xv2[6]; xv2[6] = xv2[7]; xv2[7] = xv2[8];
+ xv2[8] = pIn[i+1]/GAIN;
+ yv2[0] = yv2[1]; yv2[1] = yv2[2]; yv2[2] = yv2[3]; yv2[3] = yv2[4];
+ yv2[4] = yv2[5]; yv2[5] = yv2[6]; yv2[6] = yv2[7]; yv2[7] = yv2[8];
+ yv2[8] = (xv2[0] + xv2[8]) + coefs[1] * (xv2[1] + xv2[7]) +
+ coefs[2] * (xv2[2] + xv2[6]) +
+ coefs[3] * (xv2[3] + xv2[5]) + coefs[4] * xv2[4] +
+ (coefs[5] * yv2[0]) + ( coefs[6] * yv2[1]) +
+ (coefs[7] * yv2[2]) + ( coefs[8] * yv2[3]) +
+ (coefs[9] * yv2[4]) + ( coefs[10] * yv2[5]) +
+ (coefs[11] * yv2[6]) + ( coefs[12] * yv2[7]);
+ assert(yv2[8]<100000 || yv2[8]>-100000);
+ pOutput[i+1] = yv2[8];
+ }
+ else if (order==2)
+ {
+ // Second order
+ xv1[0] = xv1[1]; xv1[1] = xv1[2];
+ xv1[2] = pIn[i] / GAIN;
+ yv1[0] = yv1[1]; yv1[1] = yv1[2];
+ yv1[2] = (xv1[0] + xv1[2]) + coefs[1] * xv1[1] + ( coefs[2] * yv1[0]) + (coefs[3] * yv1[1]);
+ pOutput[i] = yv1[2];
+
+ xv2[0] = xv2[1]; xv2[1] = xv2[2];
+ xv2[2] = pIn[i+1] / GAIN;
+ yv2[0] = yv2[1]; yv2[1] = yv2[2];
+ yv2[2] = (xv2[0] + xv2[2]) + coefs[1] * xv2[1] + ( coefs[2] * yv2[0]) + (coefs[3] * yv2[1]);
+ pOutput[i+1] = yv2[2];
+ }
+ else
+ {
+ // Fourth order
+ xv1[0] = xv1[1]; xv1[1] = xv1[2]; xv1[2] = xv1[3]; xv1[3] = xv1[4];
+ xv1[4] = pIn[i] / GAIN;
+ yv1[0] = yv1[1]; yv1[1] = yv1[2]; yv1[2] = yv1[3]; yv1[3] = yv1[4];
+ yv1[4] = (xv1[0] + xv1[4]) + coefs[1]*(xv1[1]+xv1[3]) + coefs[2] * xv1[2]
+ + ( coefs[3] * yv1[0]) + ( coefs[4] * yv1[1])
+ + ( coefs[5] * yv1[2]) + ( coefs[6] * yv1[3]);
+ pOutput[i] = yv1[4];
+
+ xv2[0] = xv2[1]; xv2[1] = xv2[2]; xv2[2] = xv2[3]; xv2[3] = xv2[4];
+ xv2[4] = pIn[i+1] / GAIN;
+ yv2[0] = yv2[1]; yv2[1] = yv2[2]; yv2[2] = yv2[3]; yv2[3] = yv2[4];
+ yv2[4] = (xv2[0] + xv2[4]) + coefs[1]*(xv2[1]+xv2[3]) + coefs[2] * xv2[2]
+ + ( coefs[3] * yv2[0]) + ( coefs[4] * yv2[1])
+ + ( coefs[5] * yv2[2]) + ( coefs[6] * yv2[3]);
+ pOutput[i+1] = yv2[4];
+ }
+ }
+
+ // Check for denormals
+ for (i=0; i<=order; ++i)
+ {
+ xv1[i] = zap_denormal(xv1[i]);
+ yv1[i] = zap_denormal(yv1[i]);
+ xv2[i] = zap_denormal(xv2[i]);
+ yv2[i] = zap_denormal(yv2[i]);
+ }
+ }
+
+} // namespace DetailsEngineFilterIIR
diff -urpN mixxx-1.8.1.orig/src/engine/enginefilteriir.h mixxx-1.8.1/src/engine/enginefilteriir.h
--- mixxx-1.8.1.orig/src/engine/enginefilteriir.h 2010-10-17 22:34:21.000000000 -0500
+++ mixxx-1.8.1/src/engine/enginefilteriir.h 2010-12-04 17:49:12.494352048 -0600
@@ -24,15 +24,10 @@ class EngineFilterIIR : public EngineObj
{
public:
EngineFilterIIR(const double *pCoefs, int iOrder);
- ~EngineFilterIIR();
+ virtual ~EngineFilterIIR();
void process(const CSAMPLE *pIn, const CSAMPLE *pOut, const int iBufferSize);
protected:
- int order;
- const double *coefs;
- #define MAXNZEROS 8
- #define MAXNPOLES 8
- double xv1[MAXNZEROS+1], yv1[MAXNPOLES+1];
- double xv2[MAXNZEROS+1], yv2[MAXNPOLES+1];
+ EngineObject * _d;
};
//
------------------------------------------------------------------------------
Learn how Oracle Real Application Clusters (RAC) One Node allows customers
to consolidate database storage, standardize their database environment, and,
should the need arise, upgrade to a full multi-node Oracle RAC database
without downtime or disruption
http://p.sf.net/sfu/oracle-sfdevnl
_______________________________________________
Mixxx-devel mailing list
Mixxx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mixxx-devel