From 4966a7ea429c3e197c1626974c79abe9273df56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Sat, 27 Jul 2019 14:31:27 +0200 Subject: [PATCH] Incorporate Filters code into NuEVI repo for smoother setup. --- NuEVI/FilterOnePole.cpp | 240 +++++++++++++++++++++++++++++++++++++ NuEVI/FilterOnePole.h | 89 ++++++++++++++ NuEVI/NuEVI.ino | 3 +- simulation/Makefile | 3 +- simulation/src/filters.cpp | 2 +- 5 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 NuEVI/FilterOnePole.cpp create mode 100644 NuEVI/FilterOnePole.h diff --git a/NuEVI/FilterOnePole.cpp b/NuEVI/FilterOnePole.cpp new file mode 100644 index 0000000..478cd03 --- /dev/null +++ b/NuEVI/FilterOnePole.cpp @@ -0,0 +1,240 @@ +// Copyright 2014 Jonathan Driscoll +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "FilterOnePole.h" + +#include + +FilterOnePole::FilterOnePole( FILTER_TYPE ft, float fc, float initialValue ) { + setFilter( ft, fc, initialValue ); +} + +void FilterOnePole::setFilter( FILTER_TYPE ft, float fc, float initialValue ) { + FT = ft; + setFrequency( fc ); + + Y = initialValue; + Ylast = initialValue; + X = initialValue; + + LastUS = micros(); +} + +float FilterOnePole::input( float inVal ) { + long time = micros(); + ElapsedUS = float(time - LastUS); // cast to float here, for math + LastUS = time; // update this now + + // shift the data values + Ylast = Y; + X = inVal; // this is now the most recent input value + + // filter value is controlled by a parameter called X + // tau is set by the user in microseconds, but must be converted to samples here + TauSamps = TauUS / ElapsedUS; + + float ampFactor; +#ifdef ARM_FLOAT + ampFactor = expf( -1.0 / TauSamps ); // this is 1 if called quickly +#else + ampFactor = exp( -1.0 / TauSamps ); // this is 1 if called quickly +#endif + + Y = (1.0-ampFactor)*X + ampFactor*Ylast; // set the new value + + return output(); +} + +void FilterOnePole::setFrequency( float newFrequency ) { + setTau( 1.0/(TWO_PI*newFrequency ) ); // τ=1/ω +} + +void FilterOnePole::setTau( float newTau ) { + TauUS = newTau * 1e6; +} + +float FilterOnePole::output() { + // figure out which button to read + switch (FT) { + case LOWPASS: + // return the last value + return Y; + break; + case INTEGRATOR: + // using a lowpass, but normaize + return Y * (TauUS/1.0e6); + break; + case HIGHPASS: + // highpass is the _difference_ + return X-Y; + break; + case DIFFERENTIATOR: + // like a highpass, but normalize + return (X-Y)/(TauUS/1.0e6); + break; + default: + // should never get to here, return 0 just in case + return 0; + } +} + +void FilterOnePole::print() { + Serial.println(""); + Serial.print(" Y: "); Serial.print( Y ); + Serial.print(" Ylast: "); Serial.print( Ylast ); + Serial.print(" X "); Serial.print( X ); + Serial.print(" ElapsedUS "); Serial.print( ElapsedUS ); + Serial.print(" TauSamps: "); Serial.print( TauSamps ); + //Serial.print(" ampFactor " ); Serial.print( ampFactor ); + Serial.print(" TauUS: "); Serial.print( TauUS ); + Serial.println(""); +} + +void FilterOnePole::test() { + float tau = 10; + float updateInterval = 1; + float nextupdateTime = millis()*1e-3; + + float inputValue = 0; + FilterOnePole hp( HIGHPASS, tau, inputValue ); + FilterOnePole lp( LOWPASS, tau, inputValue ); + + while( true ) { + float now = millis()*1e-3; + + // switch input values on a 20 second cycle + if( round(now/20.0)-(now/20.0) < 0 ) + inputValue = 0; + else + inputValue = 100; + + hp.input(inputValue); + lp.input(inputValue); + + if( now > nextupdateTime ) { + nextupdateTime += updateInterval; + + Serial.print("inputValue: "); Serial.print( inputValue ); + Serial.print("\t high-passed: "); Serial.print( hp.output() ); + Serial.print("\t low-passed: "); Serial.print( lp.output() ); + Serial.println(); + } + } +} + +void FilterOnePole::setToNewValue( float newVal ) { + Y = Ylast = X = newVal; +} + + +// stuff for filter2 (lowpass only) +// should be able to set a separate fall time as well +FilterOnePoleCascade::FilterOnePoleCascade( float riseTime, float initialValue ) { + setRiseTime( riseTime ); + setToNewValue( initialValue ); +} + +void FilterOnePoleCascade::setRiseTime( float riseTime ) { + float tauScale = 3.36; // found emperically, by running test(); + + Pole1.setTau( riseTime / tauScale ); + Pole2.setTau( riseTime / tauScale ); +} + +float FilterOnePoleCascade::input( float inVal ) { + Pole2.input( Pole1.input( inVal )); + return output(); +} + +// clears out the values in the filter +void FilterOnePoleCascade::setToNewValue( float newVal ) { + Pole1.setToNewValue( newVal ); + Pole2.setToNewValue( newVal ); +} + +float FilterOnePoleCascade::output() { + return Pole2.output(); +} + +void FilterOnePoleCascade::test() { + // make a filter, how fast does it run: + + float rise = 1.0; + FilterOnePoleCascade myFilter( rise ); + + // first, test the filter speed ... + long nLoops = 1000; + + Serial.print( "testing filter with a rise time of "); + Serial.print( rise ); Serial.print( "s" ); + + Serial.print( "\n running filter speed loop ... "); + + float startTime, stopTime; + + startTime = millis()*1e-3; + for( long i=0; i 0.1 && !crossedTenPercent ) { + // filter first crossed the 10% point + startTime = millis()*1e-3; + crossedTenPercent = true; + } + } + stopTime = millis()*1e-3; + + Serial.print( "done, rise time: " ); Serial.print( stopTime-startTime ); + + Serial.print( "testing attenuation at f = 1/risetime" ); + + myFilter.setToNewValue( 0.0 ); + + float maxVal = 0; + float valWasOutputThisCycle = true; + + __unused float lastFilterVal = 0; + + while( true ) { + float now = 1e-3*millis(); + + float currentFilterVal = myFilter.input( sin( TWO_PI*now) ); + + if( currentFilterVal < 0.0 ) { + if( !valWasOutputThisCycle ) { + // just crossed below zero, output the max + Serial.print( maxVal*100 ); Serial.print( " %\n" ); + valWasOutputThisCycle = true; + } + + } + + } + +} diff --git a/NuEVI/FilterOnePole.h b/NuEVI/FilterOnePole.h new file mode 100644 index 0000000..cf55b86 --- /dev/null +++ b/NuEVI/FilterOnePole.h @@ -0,0 +1,89 @@ +// Copyright 2014 Jonathan Driscoll +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// FilterOnePole has been copied from https://github.com/JonHub/Filters + + +#ifndef FilterOnePole_h +#define FilterOnePole_h + +enum FILTER_TYPE { + HIGHPASS, + LOWPASS, + INTEGRATOR, + DIFFERENTIATOR +}; + +// the recursive filter class implements a recursive filter (low / pass / highpass +// note that this must be updated in a loop, using the most recent acquired values and the time acquired +// Y = a0*X + a1*Xm1 +// + b1*Ylast +struct FilterOnePole { + FILTER_TYPE FT; + float TauUS; // decay constant of the filter, in US + float TauSamps; // tau, measued in samples (this changes, depending on how long between input()s + + // filter values - these are public, but should not be set externally + float Y; // most recent output value (gets computed on update) + float Ylast; // prevous output value + + float X; // most recent input value + + // elapsed times are kept in long, and will wrap every + // 35 mins, 47 seconds ... however, the wrap does not matter, + // because the delta will still be correct (always positive and small) + float ElapsedUS; // time since last update + long LastUS; // last time measured + + FilterOnePole( FILTER_TYPE ft=LOWPASS, float fc=1.0, float initialValue=0 ); + + // sets or resets the parameters and state of the filter + void setFilter( FILTER_TYPE ft, float tauS, float initialValue ); + + void setFrequency( float newFrequency ); + + void setTau( float newTau ); + + float input( float inVal ); + + float output(); + + void print(); + + void test(); + + void setToNewValue( float newVal ); // resets the filter to a new value +}; + +// two pole filter, these are very useful +struct FilterOnePoleCascade { + + FilterOnePole Pole1; + FilterOnePole Pole2; + + FilterOnePoleCascade( float riseTime=1.0, float initialValue=0 ); // rise time to step function, 10% to 90% + + // rise time is 10% to 90%, for a step input + void setRiseTime( float riseTime ); + + void setToNewValue( float newVal ); + + float input( float inVal ); + + float output(); + + void test(); +}; + +#endif diff --git a/NuEVI/NuEVI.ino b/NuEVI/NuEVI.ino index 72742b5..2f22029 100644 --- a/NuEVI/NuEVI.ino +++ b/NuEVI/NuEVI.ino @@ -1,10 +1,9 @@ #include - #include #include #include -#include // for the breath signal LP filtering, https://github.com/edgar-bonet/Filters +#include "FilterOnePole.h" // for the breath signal low-pass filtering, from https://github.com/JonHub/Filters #include "globals.h" #include "hardware.h" #include "midi.h" diff --git a/simulation/Makefile b/simulation/Makefile index 71ec82e..1a603dd 100644 --- a/simulation/Makefile +++ b/simulation/Makefile @@ -17,7 +17,7 @@ CXXFLAGS= $(CFLAGS) -std=c++14 LIBS=-framework SDL2 -lc++ -lc -framework OpenGL LDFLAGS=-macosx_version_min 10.9 -rpath @executable_path/../Frameworks -SYSINC = ~/Documents/Arduino/libraries/Filters ./include +SYSINC = ./include INCS = ../NuEVI ./include ./imgui ./gl3w INCDIRS = $(addprefix -isystem ,$(SYSINC)) @@ -47,6 +47,7 @@ CXXFILES= ../NuEVI/menu.cpp \ imgui/examples/imgui_impl_sdl.cpp \ imgui/examples/imgui_impl_opengl3.cpp + CFILES= gl3w/gl3w.c OBJS=$(CXXFILES:.cpp=.o) $(CFILES:.c=.o) diff --git a/simulation/src/filters.cpp b/simulation/src/filters.cpp index 6f7eed9..2860caf 100644 --- a/simulation/src/filters.cpp +++ b/simulation/src/filters.cpp @@ -1,3 +1,3 @@ #include -#include "FilterOnepole.cpp" +#include "FilterOnePole.cpp"