Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -317,25 +317,32 @@ target_link_libraries(ElectroBow

if(BUILD_TESTING)
add_executable(ElectroBowPitchDetectorTests
tests/PitchDetectorTests.cpp)
tests/PitchDetectorTests.cpp
Source/PluginProcessor.cpp)

target_include_directories(ElectroBowPitchDetectorTests
PRIVATE
"${AUBIO_SRC_DIR}/src")
"${AUBIO_SRC_DIR}/src"
"${STK_SRC_DIR}/include")

target_compile_features(ElectroBowPitchDetectorTests
PRIVATE
cxx_std_17)

target_compile_definitions(ElectroBowPitchDetectorTests
PRIVATE
JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0)
ELECTROBOW_TEST_BUILD=1
M_SQRT1_2=0.70710678118654752440
JUCE_USE_CURL=0
JucePlugin_Name="ElectroBow")

target_link_libraries(ElectroBowPitchDetectorTests
PRIVATE
juce::juce_core
aubio_static)
juce::juce_graphics
juce::juce_audio_processors_headless
aubio_static
stk_bowed)

add_test(
NAME ElectroBowPitchDetectorTests
Expand Down
6 changes: 4 additions & 2 deletions Source/BowEnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ class BowEnvelope {
}

void sustain() {
if (state != State::Idle)
state = State::Sustain;
// An analyzer refresh may arrive before the configured attack has
// completed. Keep that attack running; process() will enter Sustain
// naturally once the target is reached. Forcing Sustain here would
// turn a 50 ms attack into one analysis hop.
}

void setSustainLevel(float newLevel) {
Expand Down
38 changes: 38 additions & 0 deletions Source/BowTrigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ class BowTrigger {

void reset() {
envelope = 0.0f;
onsetBaseline = 0.0f;
wasAboveThreshold = false;
releaseCounter = 0;
retriggerCooldown = 0;
retriggerArmed = true;
justTriggered = false;
justReleased = false;
}
Expand All @@ -34,10 +37,33 @@ class BowTrigger {

const float level = std::clamp(envelope * sensitivity, 0.0f, 1.0f);

// Keep a deliberately slower reference level for detecting a new
// physical pluck while another string is still audible. The normal
// threshold edge below handles the first attack; this differential
// path is only for a later, distinct rise above an existing sustain.
onsetBaseline += (level - onsetBaseline) * timeToCoefficient(onsetBaselineMs);

if (retriggerCooldown > 0)
--retriggerCooldown;

if (!retriggerArmed && level - onsetBaseline < retriggerRearmThreshold)
retriggerArmed = true;

if (!wasAboveThreshold && level >= triggerThreshold) {
wasAboveThreshold = true;
releaseCounter = 0;
justTriggered = true;
retriggerCooldown = retriggerCooldownSamples();
retriggerArmed = false;
} else if (wasAboveThreshold && retriggerArmed && retriggerCooldown == 0 &&
level >= triggerThreshold && level - onsetBaseline >= retriggerRiseThreshold) {
// Do not require every sounding string to decay to silence
// before allowing another note in a chord or repeated pluck.
// The cooldown turns the rise of one strong transient into one
// event rather than a sequence of duplicate attacks.
justTriggered = true;
retriggerCooldown = retriggerCooldownSamples();
retriggerArmed = false;
}

if (wasAboveThreshold && level <= releaseThreshold) {
Expand Down Expand Up @@ -91,15 +117,27 @@ class BowTrigger {

float detectorAttackMs = 2.0f;
float detectorReleaseMs = 30.0f;
float onsetBaselineMs = 80.0f;

float envelope = 0.0f;
float onsetBaseline = 0.0f;

bool wasAboveThreshold = false;

bool justTriggered = false;
bool justReleased = false;

int releaseCounter = 0;
int retriggerCooldown = 0;
bool retriggerArmed = true;

static constexpr int releaseSamplesRequired = 256;
static constexpr float retriggerRiseThreshold = 0.035f;
static constexpr float retriggerRearmThreshold = 0.015f;
static constexpr float retriggerCooldownMs = 30.0f;

int retriggerCooldownSamples() const noexcept {
return std::max(1, static_cast<int>(std::lround(
sampleRate * static_cast<double>(retriggerCooldownMs) * 0.001)));
}
};
6 changes: 6 additions & 0 deletions Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "PluginProcessor.h"
#ifndef ELECTROBOW_TEST_BUILD
#include "PluginEditor.h"
#endif

#include <algorithm>
#include <cmath>
Expand Down Expand Up @@ -280,7 +282,11 @@ void ElectroBowAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
}

juce::AudioProcessorEditor* ElectroBowAudioProcessor::createEditor() {
#ifdef ELECTROBOW_TEST_BUILD
return nullptr;
#else
return new ElectroBowAudioProcessorEditor(*this);
#endif
}

bool ElectroBowAudioProcessor::hasEditor() const {
Expand Down
Loading
Loading