Skip to content

Commit

Permalink
Add buffer end mask feature, + refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanbb committed Jan 28, 2019
1 parent 535b2a7 commit 544fdfb
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 133 deletions.
2 changes: 1 addition & 1 deletion Source/CrossingDetector/CircularArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Extends (by ownership) the JUCE array type to use circular (modular) indices.
@see Array
*/

#include "../../../JuceLibraryCode/JuceHeader.h"
#include <BasicJuceHeader.h>

template <typename ElementType>
class CircularArray
Expand Down
127 changes: 61 additions & 66 deletions Source/CrossingDetector/CrossingDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "CrossingDetector.h"
#include "CrossingDetectorEditor.h"

#include <cmath> // for ceil, floor

CrossingDetector::CrossingDetector()
: GenericProcessor ("Crossing Detector")
, thresholdType (CONSTANT)
Expand All @@ -47,15 +49,17 @@ CrossingDetector::CrossingDetector()
, negOn (false)
, eventDuration (5)
, timeout (1000)
, useBufferEndMask (false)
, bufferEndMaskMs (3)
, pastStrict (1.0f)
, pastSpan (0)
, futureStrict (1.0f)
, futureSpan (0)
, useJumpLimit (false)
, jumpLimit (5.0f)
, sampToReenable (pastSpan + futureSpan + 1)
, pastCounter (0)
, futureCounter (0)
, pastSamplesAbove (0)
, futureSamplesAbove (0)
, inputHistory (pastSpan + futureSpan + 2)
, thresholdHistory (pastSpan + futureSpan + 2)
, eventChannelPtr (nullptr)
Expand Down Expand Up @@ -203,59 +207,60 @@ void CrossingDetector::process(AudioSampleBuffer& continuousBuffer)
break;
}

int indCross = i - futureSpan;

// update pastCounter and futureCounter
if (pastSpan > 0)
{
int indLeaving = i - (pastSpan + futureSpan + 2);
int indLeaving = indCross - 2 - pastSpan;
if (inputAt(indLeaving) > thresholdAt(indLeaving))
{
pastCounter--;
pastSamplesAbove--;
}

int indEntering = i - (futureSpan + 2);
int indEntering = indCross - 2;
if (inputAt(indEntering) > thresholdAt(indEntering))
{
pastCounter++;
pastSamplesAbove++;
}
}

if (futureSpan > 0)
{
int indLeaving = i - futureSpan;
int indLeaving = indCross;
if (inputAt(indLeaving) > thresholdAt(indLeaving))
{
futureCounter--;
futureSamplesAbove--;
}

int indEntering = i;
int indEntering = indCross + futureSpan; // (== i)
if (inputAt(indEntering) > thresholdAt(indEntering))
{
futureCounter++;
futureSamplesAbove++;
}
}

if (i < sampToReenable)
if (indCross < sampToReenable ||
(useBufferEndMask && nSamples - indCross > bufferEndMaskSamp))
{
// can't trigger an event now
continue;
}

int crossingOffset = i - futureSpan;

float preVal = inputAt(crossingOffset - 1);
float preThresh = thresholdAt(crossingOffset - 1);
float postVal = inputAt(crossingOffset);
float postThresh = thresholdAt(crossingOffset);
float preVal = inputAt(indCross - 1);
float preThresh = thresholdAt(indCross - 1);
float postVal = inputAt(indCross);
float postThresh = thresholdAt(indCross);

// check whether to trigger an event
if (currPosOn && shouldTrigger(true, preVal, postVal, preThresh, postThresh) ||
currNegOn && shouldTrigger(false, preVal, postVal, preThresh, postThresh))
{
// add event
triggerEvent(startTs, crossingOffset, nSamples, postThresh, postVal);
triggerEvent(startTs, indCross, nSamples, postThresh, postVal);

// update sampToReenable
sampToReenable = i + 1 + timeoutSamp;
sampToReenable = indCross + 1 + timeoutSamp;

// if using random thresholds, set a new threshold
if (currThreshType == RANDOM)
Expand Down Expand Up @@ -427,20 +432,12 @@ void CrossingDetector::setParameter(int parameterIndex, float newValue)

case EVENT_DUR:
eventDuration = static_cast<int>(newValue);
if (CoreServices::getAcquisitionStatus())
{
float sampleRate = getDataChannel(inputChannel)->getSampleRate();
eventDurationSamp = static_cast<int>(ceil(eventDuration / 1000.0f * sampleRate));
}
updateSampleRateDependentValues();
break;

case TIMEOUT:
timeout = static_cast<int>(newValue);
if (CoreServices::getAcquisitionStatus())
{
float sampleRate = getDataChannel(inputChannel)->getSampleRate();
timeoutSamp = static_cast<int>(floor(timeout / 1000.0f * sampleRate));
}
updateSampleRateDependentValues();
break;

case PAST_SPAN:
Expand All @@ -453,8 +450,8 @@ void CrossingDetector::setParameter(int parameterIndex, float newValue)
thresholdHistory.resize(pastSpan + futureSpan + 2);

// counters must reflect current contents of inputHistory and thresholdHistory
pastCounter = 0;
futureCounter = 0;
pastSamplesAbove = 0;
futureSamplesAbove = 0;
break;

case PAST_STRICT:
Expand All @@ -471,8 +468,8 @@ void CrossingDetector::setParameter(int parameterIndex, float newValue)
thresholdHistory.resize(pastSpan + futureSpan + 2);

// counters must reflect current contents of inputHistory and thresholdHistory
pastCounter = 0;
futureCounter = 0;
pastSamplesAbove = 0;
futureSamplesAbove = 0;
break;

case FUTURE_STRICT:
Expand All @@ -486,15 +483,21 @@ void CrossingDetector::setParameter(int parameterIndex, float newValue)
case JUMP_LIMIT:
jumpLimit = newValue;
break;

case USE_BUF_END_MASK:
useBufferEndMask = static_cast<bool>(newValue);
break;

case BUF_END_MASK:
bufferEndMaskMs = static_cast<int>(newValue);
updateSampleRateDependentValues();
break;
}
}

bool CrossingDetector::enable()
{
// input channel is fixed once acquisition starts, so convert timeout and eventDuration
float sampleRate = getDataChannel(inputChannel)->getSampleRate();
eventDurationSamp = static_cast<int>(ceil(eventDuration * sampleRate / 1000.0f));
timeoutSamp = static_cast<int>(floor(timeout * sampleRate / 1000.0f));
updateSampleRateDependentValues();
restartAdaptiveThreshold();
return isEnabled;
}
Expand Down Expand Up @@ -679,42 +682,23 @@ bool CrossingDetector::shouldTrigger(bool direction, float preVal, float postVal
{
jassert(pastCounter >= 0 && futureCounter >= 0);

//check jumpLimit
// check jumpLimit
if (useJumpLimit && abs(postVal - preVal) >= jumpLimit)
{
return false;
}

//number of samples required before and after crossing threshold
// number of samples required before and after crossing threshold
int pastSamplesNeeded = pastSpan ? static_cast<int>(ceil(pastSpan * pastStrict)) : 0;
int futureSamplesNeeded = futureSpan ? static_cast<int>(ceil(futureSpan * futureStrict)) : 0;
// if enough values cross threshold
if (direction)
{
int pastZero = pastSpan - pastCounter;
if (pastZero >= pastSamplesNeeded && futureCounter >= futureSamplesNeeded &&
preVal <= preThresh && postVal > postThresh)
{
return true;
}
else
{
return false;
}
}
else
{
int futureZero = futureSpan - futureCounter;
if (pastCounter >= pastSamplesNeeded && futureZero >= futureSamplesNeeded &&
preVal > preThresh && postVal <= postThresh)
{
return true;
}
else
{
return false;
}
}

// four conditions for the event
bool preSat = direction != (preVal > preThresh);
bool postSat = direction == (postVal > postThresh);
bool pastSat = (direction ? pastSpan - pastSamplesAbove : pastSamplesAbove) >= pastSamplesNeeded;
bool futureSat = (direction ? futureSamplesAbove : futureSpan - futureSamplesAbove) >= futureSamplesNeeded;

return preSat && postSat && pastSat && futureSat;
}

void CrossingDetector::triggerEvent(juce::int64 bufferTs, int crossingOffset,
Expand Down Expand Up @@ -777,3 +761,14 @@ void CrossingDetector::triggerEvent(juce::int64 bufferTs, int crossingOffset,
turnoffEvent = eventOff;
}
}

void CrossingDetector::updateSampleRateDependentValues()
{
const DataChannel* inChan = getDataChannel(inputChannel);
if (inChan == nullptr) { return; }
float sampleRate = inChan->getSampleRate();

eventDurationSamp = int(std::ceil(eventDuration * sampleRate / 1000.0f));
timeoutSamp = int(std::floor(timeout * sampleRate / 1000.0f));
bufferEndMaskSamp = int(std::ceil(bufferEndMaskMs * sampleRate / 1000.0f));
}
20 changes: 14 additions & 6 deletions Source/CrossingDetector/CrossingDetector.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef CROSSING_DETECTOR_H_INCLUDED
#define CROSSING_DETECTOR_H_INCLUDED

#ifdef _WIN32
#include <Windows.h>
#endif

#include <ProcessorHeaders.h>
#include "CircularArray.h"

Expand Down Expand Up @@ -100,6 +96,8 @@ class CrossingDetector : public GenericProcessor
FUTURE_STRICT,
USE_JUMP_LIMIT,
JUMP_LIMIT,
USE_BUF_END_MASK,
BUF_END_MASK
};

// ---------------------------- PRIVATE FUNCTIONS ----------------------
Expand Down Expand Up @@ -178,6 +176,12 @@ class CrossingDetector : public GenericProcessor
void triggerEvent(juce::int64 bufferTs, int crossingOffset, int bufferLength,
float threshold, float crossingLevel);


/********* misc **********/

// Converts parameters specified in ms to samples, and updates the corresponding member variables.
void updateSampleRateDependentValues();

// ------ PARAMETERS ------------

ThresholdType thresholdType;
Expand Down Expand Up @@ -214,6 +218,10 @@ class CrossingDetector : public GenericProcessor
int timeout; // milliseconds after an event onset when no more events are allowed.
int timeoutSamp;

bool useBufferEndMask;
int bufferEndMaskMs;
int bufferEndMaskSamp;

/* Number of *additional* past and future samples to look at at each timepoint (attention span)
* If futureSpan samples are not available to look ahead from a timepoint, the test is delayed until enough samples are available.
* If it succeeds, the event occurs on the first sample in the buffer when the test occurs, but the "crossing point"
Expand All @@ -237,8 +245,8 @@ class CrossingDetector : public GenericProcessor
int sampToReenable;

// counters for delay keeping track of voting samples
int pastCounter;
int futureCounter;
int pastSamplesAbove;
int futureSamplesAbove;

// arrays to implement past/future voting
CircularArray<float> inputHistory;
Expand Down
Loading

0 comments on commit 544fdfb

Please sign in to comment.