From a7660b029ea290b41d2f39970ee558e60372b993 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 25 Aug 2014 03:17:40 +0100 Subject: [PATCH] Re-add dsp-utlity headers --- ports/easySSP/source/dsp-utility/Buffer.h | 171 +++++++ ports/easySSP/source/dsp-utility/Coord.h | 66 +++ .../source/dsp-utility/EnvelopeWalker.h | 88 ++++ .../source/dsp-utility/FftCalculator.h | 110 +++++ .../source/dsp-utility/FrequencyDomainGrid.h | 424 ++++++++++++++++++ .../source/dsp-utility/GonioCalculator.h | 108 +++++ ports/easySSP/source/dsp-utility/LICENSE | 23 + ports/easySSP/source/dsp-utility/README.md | 4 + ports/easySSP/source/dsp-utility/Scaling.h | 183 ++++++++ .../source/dsp-utility/SpectroCalculator.h | 221 +++++++++ .../source/dsp-utility/WindowFunction.h | 149 ++++++ .../easySSP/source/dsp-utility/dsp-utility.h | 71 +++ ports/easySSP/source/dsp-utility/spsc_queue.h | 144 ++++++ 13 files changed, 1762 insertions(+) create mode 100755 ports/easySSP/source/dsp-utility/Buffer.h create mode 100755 ports/easySSP/source/dsp-utility/Coord.h create mode 100755 ports/easySSP/source/dsp-utility/EnvelopeWalker.h create mode 100755 ports/easySSP/source/dsp-utility/FftCalculator.h create mode 100755 ports/easySSP/source/dsp-utility/FrequencyDomainGrid.h create mode 100755 ports/easySSP/source/dsp-utility/GonioCalculator.h create mode 100644 ports/easySSP/source/dsp-utility/LICENSE create mode 100644 ports/easySSP/source/dsp-utility/README.md create mode 100755 ports/easySSP/source/dsp-utility/Scaling.h create mode 100755 ports/easySSP/source/dsp-utility/SpectroCalculator.h create mode 100755 ports/easySSP/source/dsp-utility/WindowFunction.h create mode 100755 ports/easySSP/source/dsp-utility/dsp-utility.h create mode 100755 ports/easySSP/source/dsp-utility/spsc_queue.h diff --git a/ports/easySSP/source/dsp-utility/Buffer.h b/ports/easySSP/source/dsp-utility/Buffer.h new file mode 100755 index 00000000..bd5c4828 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/Buffer.h @@ -0,0 +1,171 @@ +#ifndef TOMATL_BUFFER +#define TOMATL_BUFFER + +#include + +namespace tomatl { namespace dsp { + +template class SimpleBuffer +{ +private: + T* mData = NULL; + size_t mLength = 0; + size_t mPointer = 0; + + TOMATL_DECLARE_NON_MOVABLE_COPYABLE(SimpleBuffer) +public: + SimpleBuffer(size_t length, bool clear = true) : mLength(length) + { + mData = new T[mLength]; + + if (clear) memset(mData, 0x0, sizeof(T) * mLength); + } + + forcedinline bool isFull() + { + return mPointer == mLength; + } + + forcedinline bool putOne(const T& subj) + { + if (!isFull()) + { + mData[mPointer] = subj; + ++mPointer; + + return true; + } + + return false; + } + + T* getContents() { return mData; } + + bool read(T& writeTo, const size_t& pos) + { + if (pos >= 0 && pos < mPointer) + { + writeTo = mData[pos]; + + return true; + } + + return false; + } + + const size_t& getLength() { return mLength; } + const size_t& getPointer() { return mPointer; } + + void clear(size_t position = 0, bool eraseData = false) + { + mPointer = position; + + if (eraseData) memset(mData, 0x0, sizeof(T)* mLength); + } + + virtual ~SimpleBuffer() + { + TOMATL_BRACE_DELETE(mData); + } +}; + +template class DelayBuffer +{ +private: + TOMATL_DECLARE_NON_MOVABLE_COPYABLE(DelayBuffer); + SimpleBuffer* mReadBuffer = NULL; + SimpleBuffer* mWriteBuffer = NULL; + + T mTemp; +public: + DelayBuffer(size_t length) : mReadBuffer(new SimpleBuffer(length)), mWriteBuffer(new SimpleBuffer(length)) + { + for (int i = 0; i < mReadBuffer->getLength(); ++i) + { + mReadBuffer->putOne(0x0); + } + } + + const T& put(const T& subj) + { + if (!mWriteBuffer->putOne(subj)) + { + std::swap(mWriteBuffer, mReadBuffer); + + mWriteBuffer->clear(); + + mWriteBuffer->putOne(subj); + } + + mReadBuffer->read(mTemp, mWriteBuffer->getPointer() - 1); + + return mTemp; + } + + virtual ~DelayBuffer() + { + TOMATL_DELETE(mReadBuffer); + TOMATL_DELETE(mWriteBuffer); + } +}; + +template class OverlappingBufferSequence +{ +private: + TOMATL_DECLARE_NON_MOVABLE_COPYABLE(OverlappingBufferSequence); + std::vector*> mBuffers; + size_t mHopSize; + double mOverlappingFactor; +public: + OverlappingBufferSequence(size_t segmentLength, size_t hopSize) : mHopSize(hopSize), mOverlappingFactor((double)mHopSize / (double)segmentLength) + { + size_t hopCount = segmentLength / hopSize; + + // TODO: sanity checks? hopSize < segmentLength and maybe segmentLength % hopSize == 0 + for (int i = 0; i < hopCount; ++i) + { + mBuffers.push_back(new SimpleBuffer(segmentLength)); + mBuffers[i]->clear(hopSize * i); + } + } + + virtual ~OverlappingBufferSequence() + { + for (int i = 0; i < mBuffers.size(); ++i) + { + TOMATL_DELETE(mBuffers[i]); + } + + mBuffers.clear(); + } + + std::tuple putOne(const T& subject) + { + SimpleBuffer* result = NULL; + size_t index = 0; + + for (int i = 0; i < mBuffers.size(); ++i) + { + mBuffers[i]->putOne(subject); + + if (mBuffers[i]->isFull()) + { + result = mBuffers[i]; + index = i; + mBuffers[i]->clear(); + } + } + + return std::tuple(result == NULL ? NULL : result->getContents(), index); + } + + const size_t& getSegmentLength() { return mBuffers[0]->getLength(); } + const size_t& getHopSize() { return mHopSize; } + const double& getOverlappingFactor() { return mOverlappingFactor; } + const size_t& getSegmentCount() { return mBuffers.size(); } + size_t getSegmentOffset(size_t i) { return i * mHopSize; } +}; + + +}} +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/Coord.h b/ports/easySSP/source/dsp-utility/Coord.h new file mode 100755 index 00000000..b869af00 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/Coord.h @@ -0,0 +1,66 @@ +#ifndef TOMATL_COORD_UTILITY +#define TOMATL_COORD_UTILITY + +#include +#include + +namespace tomatl { namespace dsp { + +template class Coord +{ +public: + static void toPolar(T& x, T& y) + { + T r = std::sqrt(x * x + y * y); + T a = std::atan2(x, y); + + std::swap(x, r); + std::swap(y, a); + } + + static void toPolar(std::pair& subject) + { + toPolar(subject.first, subject.second); + } + + static void toCartesian(T& r, T& a) + { + T x = std::sin(a) * r; + T y = std::cos(a) * r; + + std::swap(x, r); + std::swap(y, a); + } + + static void toCartesian(std::pair& subject) + { + toCartesian(subject.first, subject.second); + } + + static void rotatePolarDegrees(std::pair& subject, T angle) + { + rotatePolarRadians(subject, angle * (2. * TOMATL_PI * (1. / 360.))); + } + + static void rotatePolarRadians(std::pair& subject, T angle) + { + subject.second += angle; + } + +private: + Coord() + { + } + + Coord(Coord& c) + { + } + + ~Coord() + { + } +}; + +}} + +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/EnvelopeWalker.h b/ports/easySSP/source/dsp-utility/EnvelopeWalker.h new file mode 100755 index 00000000..04c74470 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/EnvelopeWalker.h @@ -0,0 +1,88 @@ +#ifndef TOMATL_ENVELOPE_WALKER +#define TOMATL_ENVELOPE_WALKER + +#include + +namespace tomatl { namespace dsp { + +class EnvelopeWalker +{ +public: + EnvelopeWalker() + { + setAttackSpeed(1.).setReleaseSpeed(10.).setSampleRate(44100.); + } + + ~EnvelopeWalker(void) + { + } + + EnvelopeWalker& setAttackSpeed(double attackMs) + { + m_attackMs = attackMs; + m_attackCoef = calculateCoeff(attackMs, m_sampleRate); + + return *this; + } + + EnvelopeWalker& setReleaseSpeed(double releaseMs) + { + m_releaseMs = releaseMs; + m_releaseCoef = calculateCoeff(releaseMs, m_sampleRate); + + return *this; + } + + EnvelopeWalker& setSampleRate(double sampleRate) + { + if (sampleRate != m_sampleRate) + { + m_sampleRate = sampleRate; + setAttackSpeed(m_attackMs); + setReleaseSpeed(m_releaseMs); + } + + return *this; + } + + static double calculateCoeff(double coeffInMs, double sampleRate) + { + return exp(log(0.01) / (coeffInMs * sampleRate * 0.001)); + } + + static void staticProcess(const double& in, double* current, const double& attackCoef, const double& releaseCoef) + { + double tmp = fabs(in); + + if (tmp > *current) + { + *current = attackCoef * (*current - tmp) + tmp; + } + else + { + *current = releaseCoef * (*current - tmp) + tmp; + } + } + + double process(double in) + { + staticProcess(in, &m_currentValue, m_attackCoef, m_releaseCoef); + + return m_currentValue; + } + + double getCurrentValue() + { + return m_currentValue; + } + +private: + double m_attackMs; + double m_releaseMs; + double m_attackCoef; + double m_releaseCoef; + double m_currentValue; + double m_sampleRate; +}; +}} +#endif diff --git a/ports/easySSP/source/dsp-utility/FftCalculator.h b/ports/easySSP/source/dsp-utility/FftCalculator.h new file mode 100755 index 00000000..6a38a147 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/FftCalculator.h @@ -0,0 +1,110 @@ +#ifndef TOMATL_FFT_CALCULATOR +#define TOMATL_FFT_CALCULATOR + +namespace tomatl { namespace dsp { + +template class FftCalculator +{ +private: + FftCalculator(){} + TOMATL_DECLARE_NON_MOVABLE_COPYABLE(FftCalculator); +public: + + static void scaleAfterFft(T* fftBuffer, long fftFrameSize) + { + size_t size = fftFrameSize * 2; + T factor = 1. / (T)fftFrameSize; + + for (int i = 0; i < size; ++i) + { + fftBuffer[i] *= factor; + } + } + + // For testing purposes only. Should not be used in RT processing, as it allocates memory + static void calculateReal(T* fftBuffer, long length) + { + T* buffer = new T[length * 2]; + + for (int i = 0; i < length; ++i) + { + buffer[i * 2] = fftBuffer[i]; + buffer[i * 2 + 1] = 0.; + } + + calculateFast(buffer, length, false); + memcpy(fftBuffer, buffer, sizeof(T) * length); + + delete[] buffer; + } + + static void calculateFast(T* fftBuffer, long fftFrameSize, bool inverse = false) + /* + FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse) + Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the time domain data in fftBuffer[0...2*fftFrameSize-1]. + The FFT array takes and returns the cosine and sine parts in an interleaved manner, ie. fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. + fftFrameSize must be a power of 2. It expects a complex input signal (see footnote 2), + ie. when working with 'common' audio signals our input signal has to be passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. + In that case, the transform of the frequencies of interest is in fftBuffer[0...fftFrameSize]. + */ + { + long sign = (!inverse) ? -1 : 1; + T wr, wi, arg, *p1, *p2, temp; + T tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i; + long i, bitm, j, le, le2, k, logN; + logN = (long)(log(fftFrameSize) / log(2.) + .5); + + for (i = 2; i < 2 * fftFrameSize - 2; i += 2) + { + for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) + { + if (i & bitm) j++; + j <<= 1; + + } + + if (i < j) + { + p1 = fftBuffer + i; p2 = fftBuffer + j; + temp = *p1; *(p1++) = *p2; + *(p2++) = temp; temp = *p1; + *p1 = *p2; *p2 = temp; + } + } + + for (k = 0, le = 2; k < logN; k++) + { + le <<= 1; + le2 = le >> 1; + ur = 1.0; + ui = 0.0; + arg = TOMATL_PI / (le2 >> 1); + wr = cos(arg); + wi = sign*sin(arg); + + for (j = 0; j < le2; j += 2) + { + p1r = fftBuffer + j; p1i = p1r + 1; + p2r = p1r + le2; p2i = p2r + 1; + + for (i = j; i < 2 * fftFrameSize; i += le) + { + tr = *p2r * ur - *p2i * ui; + ti = *p2r * ui + *p2i * ur; + *p2r = *p1r - tr; *p2i = *p1i - ti; + *p1r += tr; *p1i += ti; + p1r += le; p1i += le; + p2r += le; p2i += le; + } + + tr = ur*wr - ui*wi; + ui = ur*wi + ui*wr; + ur = tr; + } + } + } +}; + +}} + +#endif diff --git a/ports/easySSP/source/dsp-utility/FrequencyDomainGrid.h b/ports/easySSP/source/dsp-utility/FrequencyDomainGrid.h new file mode 100755 index 00000000..0bfabae8 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/FrequencyDomainGrid.h @@ -0,0 +1,424 @@ +#ifndef TOMATL_FREQUENCY_DOMAIN_GRID +#define TOMATL_FREQUENCY_DOMAIN_GRID + +#include + +namespace tomatl{ namespace dsp{ + + // TODO: left and top offsets for coordinates. + class FrequencyDomainGrid + { + public: + enum NoteName + { + noteA = 0, + noteASharp, + noteB, + noteC, + noteCSharp, + noteD, + noteDSharp, + noteE, + noteF, + noteFSharp, + noteG, + noteGSharp + }; + + struct NoteNotation + { + NoteNotation() : + mOctaveNumber(4), mNoteName(noteA), mCentsCount(0) + { + } + + NoteNotation(short octaveNumber, NoteName noteName, unsigned short centsCount) : + mOctaveNumber(octaveNumber), mNoteName(noteName), mCentsCount(centsCount) + { + } + + // TODO: this and next() methods are rather hacky. Maybe make NoteName start from C + // and rewrite + static NoteNotation fromFrequency(double freq) + { + NoteNotation result; + + // 440.0 - base tuning frequency. Which is A4. + double linearFreq = std::log2(freq / 440.0) + 5; + + // Octave number is equal to integer part of linear frequency obviously + short octave = floor(linearFreq); + + // Cents from the beginning of octave. Each octave has 1200 cents, because 'cent' is one percent between two adjacent notes (centinote?) + double cents = 1200 * (linearFreq - octave); + + int temp = floor(cents / 100.); + + unsigned short noteNumber = temp % 12; + + // Octave is starting from C, but our measurements were using A as octave start, so adjustment is needed + if (noteNumber < noteC) + { + --octave; + } + + result.mOctaveNumber = octave; + result.mNoteName = (NoteName)noteNumber; + result.mCentsCount = cents - noteNumber * 100; + + // We're trying to 'snap' note to closest one using negative cents. + if (result.mCentsCount > 50) + { + result = result.next(); + result.mCentsCount = -(100 - result.mCentsCount); + } + + return result; + } + + NoteNotation next() + { + NoteNotation result; + + if (mNoteName == noteB) + { + result = NoteNotation(mOctaveNumber + 1, noteC, mCentsCount); + } + else if (mNoteName == noteGSharp) + { + result = NoteNotation(mOctaveNumber, noteA, mCentsCount); + } + else + { + result = NoteNotation(mOctaveNumber, (NoteName)(mNoteName + 1), mCentsCount); + } + + return result; + } + + std::wstring toWstring() + { + std::wstring result; + + const wchar_t* noteNames[12] = { L"A ", L"A#", L"B ", L"C ", L"C#", L"D ", L"D#", L"E ", L"F ", L"F#", L"G ", L"G#" }; + + result += noteNames[mNoteName]; + + result += std::to_wstring(mOctaveNumber); + + result += L" "; + + result += std::to_wstring(mCentsCount); + + result += L" cents"; + + return result; + } + + short mOctaveNumber; + NoteName mNoteName; + short mCentsCount; + }; + + private: + tomatl::dsp::OctaveScale mFreqScale; + tomatl::dsp::LinearScale mMagnitudeScale; // As we use dB values the scale is linear + tomatl::dsp::Bound2D mBounds; + tomatl::dsp::Bound2D mFullBounds; + double* mFreqCache; + size_t mSampleRate; + size_t mBinCount; + size_t mWidth; + size_t mHeight; + + FrequencyDomainGrid(){} + FrequencyDomainGrid(const FrequencyDomainGrid&){} + + void prepareFreqCache() + { + TOMATL_BRACE_DELETE(mFreqCache); + + if (mSampleRate > 0) + { + mFreqCache = new double[mSampleRate]; + memset(mFreqCache, 0x0, sizeof(double)* mSampleRate); + } + } + + forcedinline int calculateBinNumberToX(double value) + { + return mFreqScale.scale(mWidth, mBounds.X, binNumberToFrequency(value), true); + } + + void recalcGrid() + { + mAmplGrid.clear(); + mFreqGrid.clear(); + + const int size = 10; + double freqs[size] = { 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 }; + + for (int i = 0; i < size; ++i) + { + if (TOMATL_IS_IN_BOUNDS_INCLUSIVE(freqs[i], mBounds.X.mLow, mBounds.X.mHigh)) + { + mFreqGrid.push_back(GridLine(freqToX(freqs[i]), freqs[i], freqToString(freqs[i]))); + } + } + + int dbStep = std::abs(std::abs(mBounds.Y.mHigh) - std::abs(mBounds.Y.mLow)) * 3 < 72 ? 2 : 6; + + for (int i = mBounds.Y.mLow; i <= mBounds.Y.mHigh; i += dbStep) + { + mAmplGrid.push_back(GridLine(dbToY(i), i, dbToString(i))); + } + } + public: + struct GridLine + { + GridLine(int location, double value, std::wstring caption) + { + mLocation = location; + mValue = value; + mCaption = caption; + } + + GridLine() : mLocation(0), mValue(0.), mCaption(L"") {} + + + int mLocation; + double mValue; + std::wstring mCaption; + }; + + FrequencyDomainGrid(Bound2D fullBounds, size_t sampleRate = 0, size_t binCount = 0, size_t width = 0, size_t height = 0) + { + mFullBounds = fullBounds; + mBounds = fullBounds; + mSampleRate = sampleRate; + mBinCount = binCount; + mWidth = width; + mHeight = height; + mFreqCache = NULL; + + prepareFreqCache(); + } + + const tomatl::dsp::Bound2D& getCurrentBounds() + { + return mBounds; + } + + bool updateSize(size_t w, size_t h) + { + if (w != mWidth || h != mHeight) + { + mHeight = h; + mWidth = w; + prepareFreqCache(); + recalcGrid(); + + return true; + } + + return false; + } + + bool updateSampleRate(size_t sampleRate) + { + if (sampleRate != mSampleRate) + { + mSampleRate = sampleRate; + prepareFreqCache(); + + return true; + } + + return false; + } + + bool updateBounds(Bound2D bounds) + { + if (!mBounds.areEqual(bounds)) + { + mBounds = bounds; + recalcGrid(); + prepareFreqCache(); + + return true; + } + + return false; + } + + void updateBinCount(size_t binCount) + { + if (binCount != mBinCount) + { + mBinCount = binCount; + } + } + + bool containsPoint(int x, int y) { return x <= getWidth() && y <= getHeight(); } + + bool isFrequencyVisible(const double& freq) { return TOMATL_IS_IN_BOUNDS_INCLUSIVE(freq, mBounds.X.mLow, mBounds.X.mHigh); } + + size_t getWidth() { return mWidth; } + size_t getHeight() { return mHeight; } + + size_t getFreqLineCount() { return mFreqGrid.size(); } + size_t getAmplLineCount() { return mAmplGrid.size(); } + + const GridLine& getFreqLine(int i) { return mFreqGrid[i]; } + const GridLine& getAmplLine(int i) { return mAmplGrid[i]; } + + forcedinline double binNumberToFrequency(const double& value) + { + // Bin count * 2 is just a weird way of getting FFT size + return value * mSampleRate / (mBinCount * 2); + } + + forcedinline int freqToX(const double& value) + { + if (mFreqCache == NULL) + { + return mFreqScale.scale(mWidth, mBounds.X, value, true); + } + + if (mFreqCache[(int)value] == 0) + { + mFreqCache[(int)value] = mFreqScale.scale(mWidth, mBounds.X, value, true); + } + + return mFreqCache[(int)value]; + } + + forcedinline double xToFreq(const int& x) + { + return mFreqScale.unscale(mWidth, mBounds.X, x, true); + } + + + forcedinline int dbToY(const double& value) + { + return mHeight - mMagnitudeScale.scale(mHeight, mBounds.Y, value, true) - 1; + } + + forcedinline int minusInfToY() + { + return dbToY(-1000.); + } + + forcedinline int lowestVisibleFreqToX() + { + return freqToX(mBounds.X.mLow); + } + + forcedinline double yToDb(const int& y) + { + return mMagnitudeScale.unscale(mHeight, mBounds.Y, mHeight - y - 1, true); + } + + forcedinline double fullScaleYToDb(const int& y) + { + return mMagnitudeScale.unscale(mHeight, mFullBounds.Y, mHeight - y - 1, true); + } + + forcedinline double fullScaleXToFreq(const double& value) + { + return mFreqScale.unscale(mWidth, mFullBounds.X, value, true); + } + + ~FrequencyDomainGrid() + { + TOMATL_BRACE_DELETE(mFreqCache); + } + + std::wstring getPointNotation(int x, int y) + { + if (containsPoint(x, y)) + { + auto ampl = dbToExtendedString(yToDb(y)); + auto freq = freqToExtendedString(xToFreq(x)); + + auto note = NoteNotation::fromFrequency(xToFreq(x)); + + return L"" + freq + L", " + ampl + L" | " + note.toWstring() + L""; + } + else + { + return L""; + } + } + + static std::wstring freqToExtendedString(const double& freq) + { + wchar_t buffer[50]; + memset(&buffer, 0x0, 50); + + swprintf(buffer, 50, L"%.0fHz", freq); + + return std::wstring(buffer); + } + + static std::wstring dbToExtendedString(const double & ampl) + { + wchar_t buffer[50]; + memset(&buffer, 0x0, 50); + + swprintf(buffer, 50, L"%.2fdB", ampl); + + return std::wstring(buffer); + } + + static std::wstring freqToString(const double& freq) + { + wchar_t buffer[50]; + memset(&buffer, 0x0, 50); + + if (freq >= 1000) + { + swprintf(buffer, 50, L"%dk", (int)freq / 1000); + } + else + { + swprintf(buffer, 50, L"%d", (int)freq); + } + + return std::wstring(buffer); + } + + static std::wstring dbToString(const double& ampl) + { + wchar_t buffer[50]; + memset(&buffer, 0x0, 50); + + swprintf(buffer, 50, L"%+d", (int)ampl); + + return std::wstring(buffer); + } + private: + std::vector mFreqGrid; + std::vector mAmplGrid; + }; + + /*class EqualizerGrid : public FrequencyDomainGrid + { + public: + + EqualizerGrid(Bound2D fullBounds, size_t sampleRate = 0, size_t binCount = 0, size_t width = 0, size_t height = 0) + : FrequencyDomainGrid(fullBounds, sampleRate, binCount, width, height) + { + } + + void addPoint(FrequencyDomainFilter* filter) { mPoints.push_back(filter); } + + size_t getPointCount() { return mPoints.size(); } + + FrequencyDomainFilter* getPoint(int index) { return mPoints[index]; } + private: + std::vector mPoints; + };*/ +}} + +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/GonioCalculator.h b/ports/easySSP/source/dsp-utility/GonioCalculator.h new file mode 100755 index 00000000..a6aada43 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/GonioCalculator.h @@ -0,0 +1,108 @@ +#ifndef TOMATL_GONIO_CALCULATOR +#define TOMATL_GONIO_CALCULATOR + +namespace tomatl { namespace dsp { + + +template class GonioCalculator +{ +public: + GonioCalculator(size_t segmentLength = 512, size_t sampleRate = 48000, std::pair autoAttackRelease = std::pair(0.01, 5000)) + : mData(NULL), mProcCounter(0), mCustomScaleEnabled(false), mLastScale(1.), mCustomScale(1.) + { + setSegmentLength(segmentLength); + mSqrt2 = std::pow(2., 0.5); + mEnvelope.setAttackSpeed(autoAttackRelease.first); + mEnvelope.setReleaseSpeed(autoAttackRelease.second); + mEnvelope.setSampleRate(sampleRate); + + } + + void setReleaseSpeed(double value) + { + mEnvelope.setReleaseSpeed(value); + } + + std::pair* handlePoint(const std::pair& subject, size_t sampleRate) + { + std::pair point(subject); + mEnvelope.setSampleRate(sampleRate); + + Coord::toPolar(point); + Coord::rotatePolarDegrees(point, -45.); + + // Scale auto-adjusting. We tend to use max space available even if our signal is not normalized to 0dB + double m = 1. / std::max(0.01, mEnvelope.process(point.first)); // 0.01 is limit not to expand beneath -40dB + + mLastScale = mCustomScaleEnabled ? (1. / mCustomScale) : m; + + point.first *= mLastScale; + + Coord::toCartesian(point); + + point.first = std::min(1., std::max(-1., point.first)); + point.second = std::min(1., std::max(-1., point.second)); + + mData[mProcCounter] = point; + + ++mProcCounter; + + if (mProcCounter >= mSegmentLength) + { + mProcCounter = 0; + + return mData; + } + else + { + return NULL; + } + } + + std::pair* handlePoint(const T& x, const T& y, size_t sampleRate) + { + std::pair point(x, y); + + return handlePoint(point, sampleRate); + } + + GonioCalculator& setSegmentLength(size_t segmentLength) + { + TOMATL_DELETE(mData); + mSegmentLength = segmentLength; + + mData = new std::pair[mSegmentLength]; + + memset(mData, 0, sizeof(std::pair) * mSegmentLength); + mProcCounter = 0; + + return *this; + } + + size_t getSegmentLength() + { + return mSegmentLength; + } + + double getCurrentScaleValue() + { + return mLastScale; + } + + void setCustomScaleEnabled(bool value) { mCustomScaleEnabled = value; } + void setCustomScale(double value) { mCustomScale = TOMATL_BOUND_VALUE(value, 0., 1.); } + +private: + size_t mSegmentLength; + std::pair* mData; + unsigned int mProcCounter; + double mSqrt2; + bool mCustomScaleEnabled; + double mCustomScale; + double mLastScale; + tomatl::dsp::EnvelopeWalker mEnvelope; +}; + +}} + +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/LICENSE b/ports/easySSP/source/dsp-utility/LICENSE new file mode 100644 index 00000000..9164fb3d --- /dev/null +++ b/ports/easySSP/source/dsp-utility/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014, Aleksandr Prokopchuk +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ports/easySSP/source/dsp-utility/README.md b/ports/easySSP/source/dsp-utility/README.md new file mode 100644 index 00000000..5788ca5a --- /dev/null +++ b/ports/easySSP/source/dsp-utility/README.md @@ -0,0 +1,4 @@ +dsp-utility +=========== + +Utility code for some common DSP tasks diff --git a/ports/easySSP/source/dsp-utility/Scaling.h b/ports/easySSP/source/dsp-utility/Scaling.h new file mode 100755 index 00000000..bf7d0420 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/Scaling.h @@ -0,0 +1,183 @@ +#ifndef TOMATL_SCALING +#define TOMATL_SCALING + +namespace tomatl { namespace dsp { + +template struct SingleBound +{ + SingleBound() + { + + } + + SingleBound(T low, T high) + { + mLow = low; + mHigh = high; + } + + T mLow; + T mHigh; +}; + +template struct Bound2D +{ + Bound2D() : X(0, 0), Y(0, 0) + { + + } + + Bound2D(T xl, T xh, T yl, T yh) : X(xl, xh), Y(yl, yh) + { + + } + + bool areEqual(const Bound2D& other) + { + return (other.X.mLow == X.mLow && other.X.mHigh == X.mHigh && other.Y.mLow == Y.mLow && other.Y.mHigh == Y.mHigh); + } + + SingleBound X; + SingleBound Y; +}; + +template class IScale +{ +public: + virtual int scale(int length, SingleBound bound, T val, bool limit = false) = 0; + virtual double unscale(int length, SingleBound bound, int val, bool limit = false) = 0; + + static T doLimitValue(T value, T min, T max) + { + if (value <= min) + { + return min; + } + else if (value >= max) + { + return max; + } + else + { + return value; + } + } + + static double log(double x, double base) + { + return std::log(x) / std::log(base); + } +}; + +class LogScale : public IScale +{ +public: + LogScale() { } + + int scale(int length, SingleBound bound, double val, bool limit = false) + { + if (limit) + { + val = doLimitValue(val, bound.mLow, bound.mHigh); + } + + double range = std::abs(bound.mHigh - bound.mLow); + + double expBase = std::pow(range, (1.0 / (double)length)); + + return (int)round(log(val - bound.mLow + 1, expBase)); + } + + double unscale(int length, SingleBound bound, int val, bool limit = false) + { + double range = std::abs(bound.mHigh - bound.mLow); + + double expBase = std::pow(range, (1.0 / (double)length)); + + double value = std::pow(expBase, ((double)val)) + bound.mLow - 1; + + if (limit) + { + value = doLimitValue(value, bound.mLow, bound.mHigh); + } + + return value; + } +}; + +class OctaveScale : public IScale +{ +public: + OctaveScale() { } + + virtual int scale(int length, SingleBound bound, double val, bool limit = false) + { + if (limit) + { + val = doLimitValue(val, bound.mLow, bound.mHigh); + } + + double octaveCount = std::log2(bound.mHigh) - std::log2(bound.mLow); + double pixelPerOctave = length / octaveCount; + double offset = std::round(std::log2(bound.mLow / pixelPerOctave) * pixelPerOctave); + + return (int)std::round(std::log2(val / pixelPerOctave) * pixelPerOctave - offset); + } + + virtual double unscale(int length, SingleBound bound, int val, bool limit = false) + { + double octaveCount = std::log2(bound.mHigh) - std::log2(bound.mLow); + double pixelPerOctave = length / octaveCount; + double offset = std::round(std::log2(bound.mLow / pixelPerOctave) * pixelPerOctave); + + double value = pixelPerOctave * std::pow(2., ((val + offset) / pixelPerOctave)); + + if (limit) + { + value = doLimitValue(value, bound.mLow, bound.mHigh); + } + + return value; + } +}; + +class LinearScale : public IScale +{ +public: + LinearScale() { } + + int scale(int length, SingleBound bound, double val, bool limit = false) + { + if (limit) + { + val = doLimitValue(val, bound.mLow, bound.mHigh); + } + + double increment = std::abs(bound.mHigh - bound.mLow) / ((double)length); + + int result = (int)std::round((val - bound.mLow) / increment); + + if (limit) + { + result = doLimitValue(result, 0, length); + } + + return result; + } + + double unscale(int length, SingleBound bound, int val, bool limit = false) + { + double increment = std::abs(bound.mHigh - bound.mLow) / ((double)length); + + double value = increment * ((double)val) + bound.mLow; + + if (limit) + { + value = doLimitValue(value, bound.mLow, bound.mHigh); + } + + return value; + } +}; +}} +#endif diff --git a/ports/easySSP/source/dsp-utility/SpectroCalculator.h b/ports/easySSP/source/dsp-utility/SpectroCalculator.h new file mode 100755 index 00000000..21f76fbb --- /dev/null +++ b/ports/easySSP/source/dsp-utility/SpectroCalculator.h @@ -0,0 +1,221 @@ +#ifndef TOMATL_SPECTRO_CALCULATOR +#define TOMATL_SPECTRO_CALCULATOR + +#include +#include + +namespace tomatl { namespace dsp { + + struct SpectrumBlock + { + SpectrumBlock() + { + mLength = 0; + mData = NULL; + mIndex = 0; + mSampleRate = 0; + mFramesRendered = 0; + } + + SpectrumBlock(size_t size, std::pair* data, size_t index, size_t sampleRate) + { + mLength = size; + mData = data; + mIndex = index; + mSampleRate = sampleRate; + mFramesRendered = 0; + } + + size_t mLength; + size_t mIndex; + size_t mSampleRate; + size_t mFramesRendered; + std::pair* mData; + }; + + template class SpectroCalculator + { + public: + SpectroCalculator(double sampleRate, std::pair attackRelease, size_t index, size_t fftSize = 1024, size_t channelCount = 2) : + mWindowFunction(new WindowFunction(fftSize, WindowFunctionFactory::getWindowCalculator(WindowFunctionFactory::windowHann), true)) + { + mData = new std::pair[fftSize]; + memset(mData, 0x0, sizeof(std::pair) * fftSize); + mChannelCount = channelCount; + mFftSize = fftSize; + mIndex = index; + mSampleRate = sampleRate; + + for (int i = 0; i < channelCount; ++i) + { + mBuffers.push_back(new OverlappingBufferSequence(mFftSize * 2, mFftSize)); + } + + setAttackSpeed(attackRelease.first); + setReleaseSpeed(attackRelease.second); + } + + ~SpectroCalculator() + { + TOMATL_BRACE_DELETE(mData); + + for (int i = 0; i < mChannelCount; ++i) + { + TOMATL_DELETE(mBuffers[i]); + } + + mBuffers.clear(); + } + + bool checkSampleRate(double sampleRate) + { + if (sampleRate != mSampleRate) + { + mSampleRate = sampleRate; + setAttackSpeed(mAttackRelease.first); + setReleaseSpeed(mAttackRelease.second); + + return true; + } + + return false; + } + + void setReleaseSpeed(double speed) + { + mAttackRelease.second = tomatl::dsp::EnvelopeWalker::calculateCoeff(speed, mSampleRate / mFftSize / mBuffers[0]->getOverlappingFactor() * mChannelCount); + } + + void setAttackSpeed(double speed) + { + mAttackRelease.first = tomatl::dsp::EnvelopeWalker::calculateCoeff(speed, mSampleRate / mFftSize / mBuffers[0]->getOverlappingFactor() * mChannelCount); + } + + SpectrumBlock process(T* channels) + { + bool processed = false; + + for (int i = 0; i < mChannelCount; ++i) + { + // As our signal is built entirely from real numbers, imaginary part will always be zero + mBuffers[i]->putOne(channels[i]); + auto chData = mBuffers[i]->putOne(0.); + + processed = processed || calculateSpectrumFromChannelBufferIfReady(std::get<0>(chData)); + } + + if (processed) + { + return SpectrumBlock(mFftSize / 2., mData, mIndex, mSampleRate); + } + else + { + return SpectrumBlock(); + } + } + + private: + + bool calculateSpectrumFromChannelBufferIfReady(T* chData) + { + if (chData != NULL) + { + // Apply window function to buffer + for (int s = 0; s < mFftSize; ++s) + { + mWindowFunction->applyFunction(chData + s * 2, s, 1, true); + } + + // In-place calculate FFT + FftCalculator::calculateFast(chData, mFftSize); + + // Calculate frequency-magnitude pairs (omitting phase information, as we won't need it) for all frequency bins + for (int bin = 0; bin < (mFftSize / 2.); ++bin) + { + T ampl = 0.; + + T* ftResult = chData; + + // FFT bin in rectangle form + T mFftSin = ftResult[bin * 2]; + T mFftCos = ftResult[bin * 2 + 1]; + + // http://www.dsprelated.com/showmessage/69952/1.php or see below + mFftSin *= 2; + mFftCos *= 2; + mFftSin /= mFftSize; + mFftCos /= mFftSize; + + // Partial conversion to polar coordinates: we calculate radius vector length, but don't calculate angle (aka phase) as we won't need it + T nw = std::sqrt(mFftSin * mFftSin + mFftCos * mFftCos); + + ampl = std::max(nw, ampl); + + double prev = mData[bin].second; + + // Special case - hold spectrum (aka infinite release time) + if (mAttackRelease.second == std::numeric_limits::infinity()) + { + prev = std::max(prev, ampl); + } + else // Time smoothing/averaging is being done here + { + EnvelopeWalker::staticProcess(ampl, &prev, mAttackRelease.first, mAttackRelease.second); + } + + mData[bin].first = bin; + mData[bin].second = prev; + } + + return true; + } + + return false; + } + + std::vector*> mBuffers; + std::pair* mData; + std::pair mAttackRelease; + std::unique_ptr> mWindowFunction; + size_t mChannelCount; + size_t mFftSize; + size_t mIndex; + double mSampleRate; + }; + +}} + +/* + +What do you expect? I assume you have an N length sinusoidal of +the form + +x[n] = A sin(wn) + +and find a spectrum coefficient of magnitude A*N/2 (provided +the frquency w is an integer fraction of the sampling frequency +w = m/N, m < N/2). + +First the factor 1/2. The spectrum of a real-valued signal is +conjugate symmetric, meaning one real-valed sinusoidal is +represented as two complex-valued sinusoidals according +to Eulers formula, + +A*sin(x) = A*(exp(jx)-exp(-jx))/j2. + +These two complex-valued sinusoidals have magnitde +A/2, so if you plot only the range [0,fs/2] you need to +scale by a factor 2 to recover A. + +Second, the factor N. The DFT is a set of vector products +between the input signal x and N complex elements of +unit magnitude: + +X[k] = sum_n=0^N-1 A*exp(j*2*pi*k*n/N)*exp(-j*2*pi*k*n/N) += N*A + +To recover A, one needs to divide by N. + +*/ + +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/WindowFunction.h b/ports/easySSP/source/dsp-utility/WindowFunction.h new file mode 100755 index 00000000..e36f97e2 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/WindowFunction.h @@ -0,0 +1,149 @@ +#ifndef TOMATL_WINDOW_FUNCTION +#define TOMATL_WINDOW_FUNCTION + +#include + +namespace tomatl { namespace dsp{ + +template class WindowFunction +{ +private: + size_t mLength = 0; + std::function mFunction; + T* mPrecalculated = NULL; + T mScalingFactor = 0.; + + TOMATL_DECLARE_NON_MOVABLE_COPYABLE(WindowFunction); +public: + WindowFunction(size_t length, std::function func, bool periodicMode = false) : mFunction(func) + { + mPrecalculated = new T[length]; + mLength = length; + memset(mPrecalculated, 0x0, sizeof(T)* length); + + if (periodicMode) ++length; + + for (int i = 0; i < mLength; ++i) + { + mPrecalculated[i] = mFunction(i, length); + mScalingFactor += mPrecalculated[i]; + } + + mScalingFactor /= length; + } + + forcedinline void applyFunction(T* signal, size_t start, size_t length = 1, bool scale = false) + { + int end = start + length; + + int sample = 0; + + for (int i = start; i < end; ++i) + { + if (i >= 0 && i < mLength) + { + signal[sample] *= mPrecalculated[i]; + + if (scale) signal[sample] /= mScalingFactor; + } + else + { + signal[sample] = 0.; + } + + ++sample; + } + } + + // TODO: handle negative indices? + forcedinline T applyPeriodic(T signal, size_t position) + { + T temp = signal; + + applyFunction(&temp, position % getLength()); + + return temp; + } + + forcedinline const size_t& getLength() { return mLength; } + + // After windowing input signal, it obviously becomes "more quiet", so we're need to compensate that sometimes (http://alpha.science.unitn.it/~bassi/Signal/NInotes/an041.pdf) + // If we'll take signal consisting of all samples being equal to on one, its power (sum of all samples divided by sample count) will be equal to one + // Applying window function to this signal and measuring its power will tell us how much power is being lost. This is how this thing can be calculated - + // Sum all precalculated window samples (on large size like 40960 for accuracy) and divide by sample count. + forcedinline const T& getNormalizationFactor() { return mScalingFactor; } + + virtual ~WindowFunction() + { + TOMATL_BRACE_DELETE(mPrecalculated); + } +}; + +class WindowFunctionFactory +{ +private: + WindowFunctionFactory(){} +public: + enum FunctionType + { + windowRectangle = 0, + windowBlackmanHarris, + windowHann, + windowBarlett, + windowMystic + }; + + template static std::function getWindowCalculator(FunctionType type) + { + if (type == windowRectangle) + { + return [](const int& i, const size_t& length) { return 1.; }; + } + else if (type == windowHann) + { + return [](const int& i, const size_t& length) { return 0.5 * (1. - std::cos(2 * TOMATL_PI * i / (length - 1))); }; + } + else if (type == windowBarlett) + { + return [](const int& i, const size_t& length) + { + double a = ((double)length - 1.) / 2.; + + return 1. - std::abs((i - a) / a); + }; + } + else if (type == windowBlackmanHarris) // Four-term Blackman-Harris window + { + return[](const int& i, const size_t& length) + { + T a0 = 0.35875; + T a1 = 0.48829; + T a2 = 0.14128; + T a3 = 0.01168; + + return + a0 - + a1 * std::cos(2 * TOMATL_PI * i / (length - 1)) + + a2 * std::cos(4 * TOMATL_PI * i / (length - 1)) - + a3 * std::cos(6 * TOMATL_PI * i / (length - 1)); + }; + } + else if (type == windowMystic) + { + return [](const int& i, const size_t& length) + { + double a = std::sin(TOMATL_PI * i + TOMATL_PI / 2 * length); + + return std::sin(TOMATL_PI / 2 * a * a); + }; + } + else + { + throw 20; // TODO: exception + } + } +}; + +}} + +#endif \ No newline at end of file diff --git a/ports/easySSP/source/dsp-utility/dsp-utility.h b/ports/easySSP/source/dsp-utility/dsp-utility.h new file mode 100755 index 00000000..cc0e654a --- /dev/null +++ b/ports/easySSP/source/dsp-utility/dsp-utility.h @@ -0,0 +1,71 @@ +#ifndef TOMATL_DSP_UTILITY +#define TOMATL_DSP_UTILITY + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef TOMATL_DELETE + #define TOMATL_DELETE(x) if (x != NULL) { delete x; x = NULL;} +#endif + +#ifndef TOMATL_BRACE_DELETE + #define TOMATL_BRACE_DELETE(x) if (x != NULL) { delete[] x; x = NULL;} +#endif + +#ifndef TOMATL_BOUND_VALUE + #define TOMATL_BOUND_VALUE(x, minval, maxval) std::min((maxval), std::max((x), (minval))) +#endif + +#ifndef TOMATL_IS_IN_BOUNDS_INCLUSIVE + #define TOMATL_IS_IN_BOUNDS_INCLUSIVE(x, minval, maxval) (x >= minval && x <= maxval) +#endif + +#ifndef TOMATL_TO_DB + #define TOMATL_TO_DB(x) (20. * std::log10(std::abs(x))) +#endif + +#ifndef TOMATL_FROM_DB + #define TOMATL_FROM_DB(x) (std::pow(2., x / 6.)) +#endif + +#ifndef TOMATL_FAST_DIVIDE_BY_255 + #define TOMATL_FAST_DIVIDE_BY_255(x) (((x) + 1 + ((x) >> 8)) >> 8); +#endif + +#ifndef TOMATL_PI + #define TOMATL_PI 3.14159265359 +#endif + +#define TOMATL_DELETED_FUNCTION = delete + +#define TOMATL_DECLARE_NON_COPYABLE(className) \ + className (const className&) TOMATL_DELETED_FUNCTION;\ + className& operator= (const className&) TOMATL_DELETED_FUNCTION; + +#define TOMATL_DECLARE_NON_MOVABLE(className) \ + className(className&&) TOMATL_DELETED_FUNCTION;\ + className& operator=(className&&) TOMATL_DELETED_FUNCTION;\ + +#define TOMATL_DECLARE_NON_MOVABLE_COPYABLE(className) \ + TOMATL_DECLARE_NON_COPYABLE(className) \ + TOMATL_DECLARE_NON_MOVABLE(className) + +// TODO: define for all platforms +#ifndef forcedinline + #define forcedinline __forceinline +#endif + +#include "spsc_queue.h" +#include "Buffer.h" +#include "Coord.h" +#include "Scaling.h" +#include "WindowFunction.h" +#include "EnvelopeWalker.h" +#include "GonioCalculator.h" +#include "FftCalculator.h" +#include "SpectroCalculator.h" +//#include "BiQuad.h" +#include "FrequencyDomainGrid.h" + +#endif diff --git a/ports/easySSP/source/dsp-utility/spsc_queue.h b/ports/easySSP/source/dsp-utility/spsc_queue.h new file mode 100755 index 00000000..da091938 --- /dev/null +++ b/ports/easySSP/source/dsp-utility/spsc_queue.h @@ -0,0 +1,144 @@ +#ifndef TOMATL_SPSC_QUEUE +#define TOMATL_SPSC_QUEUE +//#include + +// TODO: memory fences +namespace tomatl { namespace dsp { + + // load with 'consume' (data-dependent) memory ordering +template +T load_consume(T const* addr) +{ + // hardware fence is implicit on x86 + T v = *const_cast(addr); + //__memory_barrier(); // compiler fence + return v; +} + +// store with 'release' memory ordering +template +void store_release(T* addr, T v) +{ + // hardware fence is implicit on x86 + // __memory_barrier(); // compiler fence + *const_cast(addr) = v; +} + +// cache line size on modern x86 processors (in bytes) +size_t const cache_line_size = 64; + +// single-producer/single-consumer queue +template +class spsc_queue +{ +public: + spsc_queue() + { + node* n = new node; + n->next_ = 0; + tail_ = head_ = first_= tail_copy_ = n; + } + + ~spsc_queue() + { + node* n = first_; + do + { + node* next = n->next_; + delete n; + n = next; + } + while (n); + } + + void enqueue(T v) + { + node* n = alloc_node(); + n->next_ = 0; + n->value_ = v; + store_release(&head_->next_, n); + head_ = n; + } + + // returns 'false' if queue is empty + bool dequeue(T& v) + { + if (load_consume(&tail_->next_)) + { + v = tail_->next_->value_; + store_release(&tail_, tail_->next_); + return true; + } + else + { + return false; + } + } + +private: + // internal node structure + struct node + { + node* next_; + T value_; + }; + + // consumer part + // accessed mainly by consumer, infrequently be producer + node* tail_; // tail of the queue + + // delimiter between consumer part and producer part, + // so that they situated on different cache lines + char cache_line_pad_ [cache_line_size]; + + // producer part + // accessed only by producer + node* head_; // head of the queue + node* first_; // last unused node (tail of node cache) + node* tail_copy_; // helper (points somewhere between first_ and tail_) + + node* alloc_node() + { + // first tries to allocate node from internal node cache, + // if attempt fails, allocates node via ::operator new() + + if (first_ != tail_copy_) + { + node* n = first_; + first_ = first_->next_; + return n; + } + tail_copy_ = load_consume(&tail_); + if (first_ != tail_copy_) + { + node* n = first_; + first_ = first_->next_; + return n; + } + node* n = new node; + return n; + } + + spsc_queue(spsc_queue const&); + spsc_queue& operator = (spsc_queue const&); +}; + +// usage example +/*int main() +{ + spsc_queue q; + q.enqueue(1); + q.enqueue(2); + int v; + bool b = q.dequeue(v); + b = q.dequeue(v); + q.enqueue(3); + q.enqueue(4); + b = q.dequeue(v); + b = q.dequeue(v); + b = q.dequeue(v); +}*/ + +}} + +#endif \ No newline at end of file