diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fb84f5..ea11eeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ option(NEO_BUILD_TESTS "Build unit tests" OFF) project(neuron VERSION 0.1.0 LANGUAGES CXX) -file(GLOB_RECURSE LIST_DIRECTORIES INCLUDE_DIRS "src/*") file(GLOB_RECURSE SRC_FILES "src/*.cpp") add_library(neuron STATIC ${SRC_FILES}) @@ -25,33 +24,36 @@ else() CXX_STANDARD_REQUIRED) endif () +target_include_directories(neuron + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src) + if (NEO_BUILD_TESTS) function(add_neuron_test name src) add_executable("${name}_test_exe" ${src}) - target_include_directories("${name}_test_exe" PRIVATE ${INCLUDE_DIRS}) - target_link_libraries("${name}_test_exe" neuron) - target_link_libraries("${name}_test_exe" gtest gtest_main) + target_link_libraries("${name}_test_exe" PRIVATE neuron gtest gtest_main) add_test(NAME "${name}_test" COMMAND "${name}_test_exe") endfunction() add_subdirectory(vendor/googletest) if (NEO_PLUGIN_SUPPORT) - add_neuron_test(parameter_atomic tests/abstractions/parameter_test_atomic.cpp) + add_neuron_test(parameter_atomic tests/core/parameter_test_atomic.cpp) else () - add_neuron_test(parameter tests/abstractions/parameter_test.cpp) - add_neuron_test(waveform tests/audio/waveform_test.cpp) - add_neuron_test(oscillator tests/generators/oscillator_test.cpp) - add_neuron_test(adsr tests/modulators/adsr_test.cpp) - add_neuron_test(saturator tests/processors/effects/saturator_test.cpp) - add_neuron_test(wavefolder tests/processors/effects/wavefolder_test.cpp) - add_neuron_test(filter tests/processors/filters/filter_test.cpp) - add_neuron_test(arithmetic tests/utilities/arithmetic_test.cpp) - add_neuron_test(midi tests/utilities/midi_test.cpp) - add_neuron_test(smoothed_value tests/utilities/smoothed_value_test.cpp) + add_neuron_test(parameter tests/core/parameter_test.cpp) + add_neuron_test(waveform tests/utils/waveform_test.cpp) + add_neuron_test(oscillator tests/dsp/generators/oscillator_test.cpp) + add_neuron_test(adsr tests/dsp/modulators/adsr_test.cpp) + add_neuron_test(saturator tests/dsp/processors/saturator_test.cpp) + add_neuron_test(wavefolder tests/dsp/processors/wavefolder_test.cpp) + add_neuron_test(filter tests/dsp/processors/filter_test.cpp) + add_neuron_test(arithmetic tests/utils/arithmetic_test.cpp) + add_neuron_test(midi tests/utils/midi_test.cpp) + add_neuron_test(smoothed_value tests/utils/smoothed_value_test.cpp) endif () enable_testing() endif () - -target_include_directories(neuron PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src PRIVATE ${INCLUDE_DIRS}) diff --git a/Makefile b/Makefile index 19613f5..94630dd 100644 --- a/Makefile +++ b/Makefile @@ -1,47 +1,33 @@ TARGET = libneuron -MODULE_DIR = src +INCLUDE_DIR = include +SRC_DIR = src # Each Module Directory is listed below with it's modules. # Header only modules are listed commented out # below the others. -ABSTRACTIONS_MOD_DIR = abstractions -ABSTRACTIONS_MODULES = \ - -AUDIO_MOD_DIR = audio -AUDIO_MODULES = \ - -GENERATOR_MOD_DIR = generators +GENERATOR_MOD_DIR = dsp/generators GENERATOR_MODULES = \ oscillator \ -MODULATOR_MOD_DIR = modulators +MODULATOR_MOD_DIR = dsp/modulators MODULATOR_MODULES = \ adsr \ -PROCESSOR_EFFECTS_MOD_DIR = processors/effects -PROCESSOR_EFFECTS_MODULES = \ +PROCESSOR_MOD_DIR = dsp/processors +PROCESSOR_MODULES = \ +filter \ saturator \ wavefolder \ -PROCESSOR_FILTERS_MOD_DIR = processors/filters -PROCESS_FILTERS_MODULES = \ -filter \ - -UTILITY_MOD_DIR = utilities -UTILITY_MODULES = \ -logger - ###################################### # source ###################################### -CPP_SOURCES += $(addsuffix .cpp, $(MODULE_DIR)/$(GENERATOR_MOD_DIR)/$(GENERATOR_MODULES)) -CPP_SOURCES += $(addsuffix .cpp, $(MODULE_DIR)/$(MODULATOR_MOD_DIR)/$(MODULATOR_MODULES)) -CPP_SOURCES += $(addsuffix .cpp, $(MODULE_DIR)/$(PROCESSOR_EFFECTS_MOD_DIR)/$(PROCESSOR_EFFECTS_MODULES)) -CPP_SOURCES += $(addsuffix .cpp, $(MODULE_DIR)/$(PROCESSOR_FILTERS_MOD_DIR)/$(PROCESS_FILTERS_MODULES)) -CPP_SOURCES += $(addsuffix .cpp, $(MODULE_DIR)/$(UTILITY_MOD_DIR)/$(UTILITY_MODULES)) +CPP_SOURCES += $(addsuffix .cpp, $(SRC_DIR)/$(GENERATOR_MOD_DIR)/$(GENERATOR_MODULES)) +CPP_SOURCES += $(addsuffix .cpp, $(SRC_DIR)/$(MODULATOR_MOD_DIR)/$(MODULATOR_MODULES)) +CPP_SOURCES += $(addsuffix .cpp, $(SRC_DIR)/$(PROCESSOR_MOD_DIR)/$(PROCESSOR_MODULES)) ###################################### # building variables @@ -114,14 +100,8 @@ C_DEFS = \ -DSTM32H750xx C_INCLUDES = \ --I$(MODULE_DIR) \ --I$(MODULE_DIR)/$(ABSTRACTIONS_MOD_DIR) \ --I$(MODULE_DIR)/$(AUDIO_MOD_DIR) \ --I$(MODULE_DIR)/$(GENERATOR_MOD_DIR) \ --I$(MODULE_DIR)/$(MODULATOR_MOD_DIR) \ --I$(MODULE_DIR)/$(PROCESSOR_EFFECTS_MOD_DIR) \ --I$(MODULE_DIR)/$(PROCESSOR_FILTERS_MOD_DIR) \ --I$(MODULE_DIR)/$(UTILITY_MOD_DIR) \ +-I$(INCLUDE_DIR) \ +-I$(SRC_DIR) \ # compile gcc flags ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections diff --git a/src/abstractions/neuron.h b/include/neuron/core/base.h similarity index 100% rename from src/abstractions/neuron.h rename to include/neuron/core/base.h diff --git a/src/audio/context.h b/include/neuron/core/context.h similarity index 100% rename from src/audio/context.h rename to include/neuron/core/context.h diff --git a/src/abstractions/parameter.h b/include/neuron/core/parameter.h similarity index 100% rename from src/abstractions/parameter.h rename to include/neuron/core/parameter.h diff --git a/src/audio/sample.h b/include/neuron/core/sample.h similarity index 100% rename from src/audio/sample.h rename to include/neuron/core/sample.h diff --git a/src/abstractions/generator.h b/include/neuron/dsp/generators/generator.h similarity index 94% rename from src/abstractions/generator.h rename to include/neuron/dsp/generators/generator.h index e0629a3..aa6b9c7 100644 --- a/src/abstractions/generator.h +++ b/include/neuron/dsp/generators/generator.h @@ -1,6 +1,6 @@ #pragma once -#include "audio/sample.h" +#include "neuron/core/sample.h" namespace neuron { diff --git a/src/generators/oscillator.h b/include/neuron/dsp/generators/oscillator.h similarity index 91% rename from src/generators/oscillator.h rename to include/neuron/dsp/generators/oscillator.h index 02d13c6..0307e26 100644 --- a/src/generators/oscillator.h +++ b/include/neuron/dsp/generators/oscillator.h @@ -1,11 +1,11 @@ #pragma once -#include "abstractions/generator.h" -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "audio/context.h" -#include "audio/waveform.h" -#include "utilities/arithmetic.h" +#include "neuron/core/base.h" +#include "neuron/core/context.h" +#include "neuron/core/parameter.h" +#include "neuron/dsp/generators/generator.h" +#include "neuron/utils/arithmetic.h" +#include "neuron/utils/waveform.h" namespace neuron { diff --git a/src/modulators/adsr.h b/include/neuron/dsp/modulators/adsr.h similarity index 95% rename from src/modulators/adsr.h rename to include/neuron/dsp/modulators/adsr.h index 9a52455..1cc7493 100644 --- a/src/modulators/adsr.h +++ b/include/neuron/dsp/modulators/adsr.h @@ -1,9 +1,9 @@ #pragma once -#include "abstractions/modulator.h" -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "audio/context.h" +#include "neuron/core/base.h" +#include "neuron/core/context.h" +#include "neuron/core/parameter.h" +#include "neuron/dsp/modulators/modulator.h" namespace neuron { diff --git a/src/abstractions/modulator.h b/include/neuron/dsp/modulators/modulator.h similarity index 100% rename from src/abstractions/modulator.h rename to include/neuron/dsp/modulators/modulator.h diff --git a/src/processors/filters/filter.h b/include/neuron/dsp/processors/filter.h similarity index 89% rename from src/processors/filters/filter.h rename to include/neuron/dsp/processors/filter.h index 6e8d00c..ac61a77 100644 --- a/src/processors/filters/filter.h +++ b/include/neuron/dsp/processors/filter.h @@ -1,10 +1,10 @@ #pragma once -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "abstractions/processor.h" -#include "audio/context.h" -#include "audio/sample.h" +#include "neuron/core/base.h" +#include "neuron/core/context.h" +#include "neuron/core/parameter.h" +#include "neuron/core/sample.h" +#include "neuron/dsp/processors/processor.h" namespace neuron { diff --git a/src/abstractions/processor.h b/include/neuron/dsp/processors/processor.h similarity index 94% rename from src/abstractions/processor.h rename to include/neuron/dsp/processors/processor.h index 45dab46..6596c24 100644 --- a/src/abstractions/processor.h +++ b/include/neuron/dsp/processors/processor.h @@ -1,6 +1,6 @@ #pragma once -#include "audio/sample.h" +#include "neuron/core/sample.h" namespace neuron { diff --git a/src/processors/effects/saturator.h b/include/neuron/dsp/processors/saturator.h similarity index 92% rename from src/processors/effects/saturator.h rename to include/neuron/dsp/processors/saturator.h index 2adfb07..cd5f5fc 100644 --- a/src/processors/effects/saturator.h +++ b/include/neuron/dsp/processors/saturator.h @@ -1,9 +1,9 @@ #pragma once -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "abstractions/processor.h" -#include "audio/sample.h" +#include "neuron/core/base.h" +#include "neuron/core/parameter.h" +#include "neuron/core/sample.h" +#include "neuron/dsp/processors/processor.h" namespace neuron { diff --git a/src/processors/effects/wavefolder.h b/include/neuron/dsp/processors/wavefolder.h similarity index 94% rename from src/processors/effects/wavefolder.h rename to include/neuron/dsp/processors/wavefolder.h index 5069728..d9cb0bd 100644 --- a/src/processors/effects/wavefolder.h +++ b/include/neuron/dsp/processors/wavefolder.h @@ -1,8 +1,8 @@ #pragma once -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "abstractions/processor.h" +#include "neuron/core/base.h" +#include "neuron/core/parameter.h" +#include "neuron/dsp/processors/processor.h" namespace neuron { diff --git a/include/neuron/neuron.h b/include/neuron/neuron.h new file mode 100644 index 0000000..5ba5bc7 --- /dev/null +++ b/include/neuron/neuron.h @@ -0,0 +1,34 @@ +/** +* Neuron is a lightweight audio DSP library intended for use + * in any relevant application e.g. Electrosmith Daisy patches, + * JUCE plugins, VCV Rack modules. + * + * Author: Matthew Maxwell, 2024 + */ +#pragma once + +#ifndef NEURON_LIB_H +#define NEURON_LIB_H + +// CORE +#include "neuron/core/base.h" +#include "neuron/core/context.h" +#include "neuron/core/parameter.h" +#include "neuron/core/sample.h" + +// DSP (Generators) +#include "neuron/dsp/generators/generator.h" + +// DSP (Modulators) +#include "neuron/dsp/modulators/modulator.h" + +// DSP (Processors) +#include "neuron/dsp/processors/processor.h" + +// UTILS +#include "neuron/utils/arithmetic.h" +#include "neuron/utils/midi.h" +#include "neuron/utils/smoothed_value.h" +#include "neuron/utils/waveform.h" + +#endif diff --git a/src/utilities/arithmetic.h b/include/neuron/utils/arithmetic.h similarity index 98% rename from src/utilities/arithmetic.h rename to include/neuron/utils/arithmetic.h index 7415fc3..bb0cbb9 100644 --- a/src/utilities/arithmetic.h +++ b/include/neuron/utils/arithmetic.h @@ -39,7 +39,7 @@ namespace neuron { template struct Epsilon { - static constexpr T value = T{1e-5}; + static constexpr T value = T { 1e-5 }; }; template<> diff --git a/src/utilities/midi.h b/include/neuron/utils/midi.h similarity index 91% rename from src/utilities/midi.h rename to include/neuron/utils/midi.h index c798590..82004c1 100644 --- a/src/utilities/midi.h +++ b/include/neuron/utils/midi.h @@ -1,6 +1,6 @@ #pragma once -#include "utilities/arithmetic.h" +#include "neuron/utils/arithmetic.h" namespace neuron { diff --git a/include/neuron/utils/smoothed_value.h b/include/neuron/utils/smoothed_value.h new file mode 100644 index 0000000..2e9d55e --- /dev/null +++ b/include/neuron/utils/smoothed_value.h @@ -0,0 +1,189 @@ +#pragma once + +#include "neuron/utils/arithmetic.h" + +#include + +namespace neuron { + enum SmoothingType { + Linear, + Multiplicative, + }; + + template + class SmoothedValue; + + template<> + class SmoothedValue { + public: + SmoothedValue() + : m_currentValue(0.0f) + , m_targetValue(0.0f) + { + UpdateIncrement(); + } + + SmoothedValue(float initialValue) + : m_currentValue(initialValue) + , m_targetValue(initialValue) + { + UpdateIncrement(); + } + + void Reset(double sampleRate, double rampLengthMillis) + { + if (sampleRate > 0.0 && rampLengthMillis >= 0.0) { + m_sampleRate = sampleRate; + m_rampLengthMillis = rampLengthMillis; + UpdateIncrement(); + } + } + + void SetTargetValue(float value) noexcept + { + m_targetValue = value; + UpdateIncrement(); + } + + float GetNextValue() noexcept + { + if (isApproximatelyEqual(m_currentValue, m_targetValue)) { + m_currentValue = m_targetValue; + return m_currentValue; + } + + m_currentValue += m_increment; + if ((m_increment > 0.0f && m_currentValue > m_targetValue) || (m_increment < 0.0f && m_currentValue < m_targetValue)) { + m_currentValue = m_targetValue; + } + + return m_currentValue; + } + + void Skip(int numSamples) + { + if (isApproximatelyEqual(m_currentValue, m_targetValue)) { + m_currentValue = m_targetValue; + return; + } + + float newValue = m_currentValue + m_increment * numSamples; + if ((m_increment > 0.0f && newValue > m_targetValue) || (m_increment < 0.0f && newValue < m_targetValue)) { + m_currentValue = m_targetValue; + } else { + m_currentValue = newValue; + } + } + + private: + static constexpr float INV_1000 = 1.0f / 1000.0f; + + void UpdateIncrement() noexcept + { + if (m_rampLengthMillis <= 0.0) { + m_currentValue = m_targetValue; + m_increment = 0.0f; + return; + } + + float numSamplesBetweenValues = static_cast(m_rampLengthMillis * m_sampleRate) * INV_1000; + m_increment = (m_targetValue - m_currentValue) / numSamplesBetweenValues; + } + + double m_sampleRate = 44100.0; + double m_rampLengthMillis = 50; + + float m_currentValue; + float m_targetValue; + + float m_increment = 0.0f; + }; + + template<> + class SmoothedValue { + public: + SmoothedValue() + : m_currentValue(1.0f) + , m_targetValue(1.0f) + { + UpdateIncrement(); + } + + SmoothedValue(float initialValue) + : m_currentValue(initialValue) + , m_targetValue(initialValue) + { + UpdateIncrement(); + } + + void Reset(double sampleRate, double rampLengthMillis) + { + if (sampleRate > 0.0 && rampLengthMillis >= 0.0) { + m_sampleRate = sampleRate; + m_rampLengthMillis = rampLengthMillis; + UpdateIncrement(); + } + } + + void SetTargetValue(float value) noexcept + { + m_targetValue = value; + UpdateIncrement(); + } + + float GetNextValue() noexcept + { + if (isApproximatelyEqual(m_currentValue, m_targetValue)) { + m_currentValue = m_targetValue; + return m_currentValue; + } + + m_currentValue *= std::exp(m_increment); + if ((m_increment > 0.0f && m_currentValue > m_targetValue) || (m_increment < 0.0f && m_currentValue < m_targetValue)) { + m_currentValue = m_targetValue; + } + + return m_currentValue; + } + + void Skip(int numSamples) + { + if (isApproximatelyEqual(m_currentValue, m_targetValue)) { + m_currentValue = m_targetValue; + return; + } + + m_currentValue *= std::exp(m_increment * numSamples); + } + + private: + static constexpr float INV_1000 = 1.0f / 1000.0f; + + void UpdateIncrement() noexcept + { + if (m_rampLengthMillis <= 0.0) { + m_currentValue = m_targetValue; + m_increment = 0.0f; + return; + } + + float numSamplesBetweenValues = static_cast(m_rampLengthMillis * m_sampleRate) * INV_1000; + if (m_currentValue > 0.0f && m_targetValue > 0.0f) { + m_increment = std::log(m_targetValue / m_currentValue) / numSamplesBetweenValues; + } else { + m_increment = 0.0f; + } + } + + double m_sampleRate = 44100.0; + double m_rampLengthMillis = 50; + + float m_currentValue; + float m_targetValue; + + float m_increment = 0.0f; + }; + + using LinearSmoothedValue = SmoothedValue; + using MultiplicativeSmoothedValue = SmoothedValue; +} diff --git a/src/audio/waveform.h b/include/neuron/utils/waveform.h similarity index 93% rename from src/audio/waveform.h rename to include/neuron/utils/waveform.h index f0cf428..5d76e3d 100644 --- a/src/audio/waveform.h +++ b/include/neuron/utils/waveform.h @@ -1,7 +1,7 @@ #pragma once -#include "audio/sample.h" -#include "utilities/arithmetic.h" +#include "neuron/core/sample.h" +#include "neuron/utils/arithmetic.h" namespace neuron { diff --git a/scripts/format.sh b/scripts/format.sh index d5b4338..244c151 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,6 +1,13 @@ #!/bin/bash printf "Formatting code...\n" + +find include/ -iname '*.h' | xargs clang-format -i -style=file +if [ $? -ne 0 ]; then + printf "Failed to format source code\n" + exit 1 +fi + find src/ -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i -style=file if [ $? -ne 0 ]; then printf "Failed to format source code\n" diff --git a/src/generators/oscillator.cpp b/src/dsp/generators/oscillator.cpp similarity index 98% rename from src/generators/oscillator.cpp rename to src/dsp/generators/oscillator.cpp index d5769e2..25f1d04 100644 --- a/src/generators/oscillator.cpp +++ b/src/dsp/generators/oscillator.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/dsp/generators/oscillator.h" -#include "generators/oscillator.h" +#include using namespace neuron; diff --git a/src/modulators/adsr.cpp b/src/dsp/modulators/adsr.cpp similarity index 98% rename from src/modulators/adsr.cpp rename to src/dsp/modulators/adsr.cpp index 6adb3b5..9921aab 100644 --- a/src/modulators/adsr.cpp +++ b/src/dsp/modulators/adsr.cpp @@ -1,4 +1,4 @@ -#include "modulators/adsr.h" +#include "neuron/dsp/modulators/adsr.h" using namespace neuron; diff --git a/src/processors/filters/filter.cpp b/src/dsp/processors/filter.cpp similarity index 93% rename from src/processors/filters/filter.cpp rename to src/dsp/processors/filter.cpp index 4859c09..5d7a3db 100644 --- a/src/processors/filters/filter.cpp +++ b/src/dsp/processors/filter.cpp @@ -1,5 +1,5 @@ -#include "processors/filters/filter.h" -#include "utilities/arithmetic.h" +#include "neuron/dsp/processors/filter.h" +#include "neuron/utils/arithmetic.h" using namespace neuron; diff --git a/src/processors/effects/saturator.cpp b/src/dsp/processors/saturator.cpp similarity index 92% rename from src/processors/effects/saturator.cpp rename to src/dsp/processors/saturator.cpp index a3b8ed3..57ee3c1 100644 --- a/src/processors/effects/saturator.cpp +++ b/src/dsp/processors/saturator.cpp @@ -1,5 +1,5 @@ -#include "processors/effects/saturator.h" -#include "utilities/arithmetic.h" +#include "neuron/dsp/processors/saturator.h" +#include "neuron/utils/arithmetic.h" using namespace neuron; diff --git a/src/processors/effects/wavefolder.cpp b/src/dsp/processors/wavefolder.cpp similarity index 94% rename from src/processors/effects/wavefolder.cpp rename to src/dsp/processors/wavefolder.cpp index 1046417..88ae1d7 100644 --- a/src/processors/effects/wavefolder.cpp +++ b/src/dsp/processors/wavefolder.cpp @@ -1,5 +1,5 @@ -#include "processors/effects/wavefolder.h" -#include "utilities/arithmetic.h" +#include "neuron/dsp/processors/wavefolder.h" +#include "neuron/utils/arithmetic.h" using namespace neuron; diff --git a/src/neuron.h b/src/neuron.h deleted file mode 100644 index b53261c..0000000 --- a/src/neuron.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Neuron is a lightweight audio DSP library intended for use - * in any relevant application e.g. Electrosmith Daisy patches, - * JUCE plugins, VCV Rack modules. - * - * Author: Matthew Maxwell, 2024 - */ -#pragma once - -#ifndef NEURON_LIB_H -#define NEURON_LIB_H - -// ABSTRACTIONS -#include "abstractions/generator.h" -#include "abstractions/modulator.h" -#include "abstractions/neuron.h" -#include "abstractions/parameter.h" -#include "abstractions/processor.h" - -// AUDIO -#include "audio/context.h" -#include "audio/sample.h" -#include "audio/waveform.h" - -// GENERATORS -#include "generators/oscillator.h" - -// MODULATORS -#include "modulators/adsr.h" - -// PROCESSORS -#include "processors/effects/saturator.h" -#include "processors/effects/wavefolder.h" -#include "processors/filters/filter.h" - -// UTILITIES -#include "utilities/arithmetic.h" -#include "utilities/midi.h" -#include "utilities/smoothed_value.h" - -#endif diff --git a/src/utilities/smoothed_value.h b/src/utilities/smoothed_value.h deleted file mode 100644 index bbc2b1b..0000000 --- a/src/utilities/smoothed_value.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include "arithmetic.h" - -namespace neuron { - class SmoothedValue { - public: - SmoothedValue() : m_currentValue(0.0f), m_targetValue(0.0f) - { - UpdateIncrement(); - } - - SmoothedValue(float initialValue) : m_currentValue(initialValue), m_targetValue(initialValue) - { - UpdateIncrement(); - } - - ~SmoothedValue() = default; - - void Reset(double sampleRate, double rampLengthMillis) - { - if (sampleRate > 0.0 && rampLengthMillis >= 0.0) { - m_sampleRate = sampleRate; - m_rampLengthMillis = rampLengthMillis; - UpdateIncrement(); - } - } - - void SetTargetValue(float value) noexcept - { - m_targetValue = value; - UpdateIncrement(); - } - - float GetNextValue() noexcept - { - if (isApproximatelyEqual(m_currentValue, m_targetValue)) { - m_currentValue = m_targetValue; - return m_currentValue; - } else { - m_currentValue += m_increment; - if ((m_increment > 0.0f && m_currentValue > m_targetValue) || (m_increment < 0.0f && m_currentValue < m_targetValue)) { - m_currentValue = m_targetValue; - } - return m_currentValue; - } - } - - void Skip(int numSamples) - { - if (isApproximatelyEqual(m_currentValue, m_targetValue)) { - m_currentValue = m_targetValue; - return; - } - - float newValue = m_currentValue + m_increment * numSamples; - if ((m_increment > 0.0f && newValue > m_targetValue) || (m_increment < 0.0f && newValue < m_targetValue)) { - m_currentValue = m_targetValue; - } else { - m_currentValue = newValue; - } - } - - private: - static constexpr float INV_1000 = 1.0f / 1000.0f; - - void UpdateIncrement() noexcept - { - if (m_rampLengthMillis <= 0.0) { - m_currentValue = m_targetValue; - m_increment = 0.0f; - return; - } - - float numSamplesBetweenValues = static_cast(m_rampLengthMillis * m_sampleRate) * INV_1000; - m_increment = (m_targetValue - m_currentValue) / numSamplesBetweenValues; - } - - double m_sampleRate = 44100.0; - double m_rampLengthMillis = 50; - - float m_currentValue; - float m_targetValue; - float m_increment = 0.0f; - }; - -} diff --git a/tests/abstractions/parameter_test.cpp b/tests/core/parameter_test.cpp similarity index 95% rename from tests/abstractions/parameter_test.cpp rename to tests/core/parameter_test.cpp index 74084ee..834f3c9 100644 --- a/tests/abstractions/parameter_test.cpp +++ b/tests/core/parameter_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/core/parameter.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/abstractions/parameter_test_atomic.cpp b/tests/core/parameter_test_atomic.cpp similarity index 97% rename from tests/abstractions/parameter_test_atomic.cpp rename to tests/core/parameter_test_atomic.cpp index b42bb97..4efe3c6 100644 --- a/tests/abstractions/parameter_test_atomic.cpp +++ b/tests/core/parameter_test_atomic.cpp @@ -1,8 +1,8 @@ +#include "neuron/core/parameter.h" + #include #include -#include "neuron.h" - using namespace neuron; TEST(parameter_suite, attach_source_test) diff --git a/tests/generators/oscillator_test.cpp b/tests/dsp/generators/oscillator_test.cpp similarity index 98% rename from tests/generators/oscillator_test.cpp rename to tests/dsp/generators/oscillator_test.cpp index 18f4762..56abfcc 100644 --- a/tests/generators/oscillator_test.cpp +++ b/tests/dsp/generators/oscillator_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/dsp/generators/oscillator.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/modulators/adsr_test.cpp b/tests/dsp/modulators/adsr_test.cpp similarity index 98% rename from tests/modulators/adsr_test.cpp rename to tests/dsp/modulators/adsr_test.cpp index 9c29c99..89d86b0 100644 --- a/tests/modulators/adsr_test.cpp +++ b/tests/dsp/modulators/adsr_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/dsp/modulators/adsr.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/processors/filters/filter_test.cpp b/tests/dsp/processors/filter_test.cpp similarity index 80% rename from tests/processors/filters/filter_test.cpp rename to tests/dsp/processors/filter_test.cpp index 90289e9..cec58d1 100644 --- a/tests/processors/filters/filter_test.cpp +++ b/tests/dsp/processors/filter_test.cpp @@ -1,6 +1,7 @@ -#include +#include "neuron/dsp/generators/oscillator.h" +#include "neuron/dsp/processors/filter.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/processors/effects/saturator_test.cpp b/tests/dsp/processors/saturator_test.cpp similarity index 96% rename from tests/processors/effects/saturator_test.cpp rename to tests/dsp/processors/saturator_test.cpp index e25e2e1..68315bb 100644 --- a/tests/processors/effects/saturator_test.cpp +++ b/tests/dsp/processors/saturator_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/dsp/processors/saturator.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/processors/effects/wavefolder_test.cpp b/tests/dsp/processors/wavefolder_test.cpp similarity index 96% rename from tests/processors/effects/wavefolder_test.cpp rename to tests/dsp/processors/wavefolder_test.cpp index e0682a7..4497598 100644 --- a/tests/processors/effects/wavefolder_test.cpp +++ b/tests/dsp/processors/wavefolder_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/dsp/processors/wavefolder.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/utilities/smoothed_value_test.cpp b/tests/utilities/smoothed_value_test.cpp deleted file mode 100644 index e9624c8..0000000 --- a/tests/utilities/smoothed_value_test.cpp +++ /dev/null @@ -1,206 +0,0 @@ -#include -#include "neuron.h" -#include - -using namespace neuron; - -class SmoothedValueTest : public ::testing::Test { -protected: - void SetUp() override { - // Default test parameters - sampleRate = 44100.0; - rampLengthMs = 100.0; // 100ms ramp - } - - // Helper function for floating point comparisons - bool IsApproximatelyEqual(float a, float b, float tolerance = 1e-6f) { - return std::abs(a - b) < tolerance; - } - - double sampleRate; - double rampLengthMs; -}; - -// Test default constructor -TEST_F(SmoothedValueTest, DefaultConstructor) { - SmoothedValue sv; - EXPECT_FLOAT_EQ(sv.GetNextValue(), 0.0f); -} - -// Test constructor with initial value -TEST_F(SmoothedValueTest, ConstructorWithInitialValue) { - const float initialValue = 5.0f; - SmoothedValue sv(initialValue); - EXPECT_FLOAT_EQ(sv.GetNextValue(), initialValue); -} - -// Test Reset with valid parameters -TEST_F(SmoothedValueTest, ResetWithValidParameters) { - SmoothedValue sv(1.0f); - sv.Reset(sampleRate, rampLengthMs); - - EXPECT_FLOAT_EQ(sv.GetNextValue(), 1.0f); - - sv.SetTargetValue(2.0f); - float nextValue = sv.GetNextValue(); - EXPECT_GT(nextValue, 1.0f); - EXPECT_LT(nextValue, 2.0f); -} - -// Test Reset with invalid parameters -TEST_F(SmoothedValueTest, ResetWithInvalidParameters) { - SmoothedValue sv(1.0f); - sv.SetTargetValue(2.0f); - - // Store the state before any reset attempts - float valueBefore = sv.GetNextValue(); - - // Invalid resets should be ignored, so behavior should remain consistent - sv.Reset(-1.0, rampLengthMs); - float valueAfter1 = sv.GetNextValue(); - - sv.Reset(sampleRate, -1.0); - float valueAfter2 = sv.GetNextValue(); - - // Values should continue progressing toward target - EXPECT_GT(valueAfter1, valueBefore); // Still progressing - EXPECT_GT(valueAfter2, valueAfter1); // Still progressing - EXPECT_LT(valueAfter2, 2.0f); // Haven't reached target yet -} - -// Test SetTargetValue -TEST_F(SmoothedValueTest, SetTargetValue) { - SmoothedValue sv(0.0f); - sv.Reset(sampleRate, rampLengthMs); - - sv.SetTargetValue(10.0f); - - float value1 = sv.GetNextValue(); - float value2 = sv.GetNextValue(); - - EXPECT_GT(value1, 0.0f); - EXPECT_GT(value2, value1); - EXPECT_LT(value2, 10.0f); -} - -// Test GetNextValue reaches target eventually -TEST_F(SmoothedValueTest, GetNextValueReachesTarget) { - SmoothedValue sv(0.0f); - sv.Reset(sampleRate, 50.0); - sv.SetTargetValue(1.0f); - - int expectedSamples = static_cast((50.0 / 1000.0) * sampleRate); - - float lastValue = 0.0f; - for (int i = 0; i < expectedSamples + 10; ++i) { - lastValue = sv.GetNextValue(); - } - - EXPECT_FLOAT_EQ(lastValue, 1.0f); -} - -// Test smoothing with negative values -TEST_F(SmoothedValueTest, SmoothingWithNegativeValues) { - SmoothedValue sv(5.0f); - sv.Reset(sampleRate, rampLengthMs); - sv.SetTargetValue(-5.0f); - - float value1 = sv.GetNextValue(); - float value2 = sv.GetNextValue(); - - EXPECT_LT(value1, 5.0f); - EXPECT_LT(value2, value1); - EXPECT_GT(value1, -5.0f); -} - -// Test Skip functionality -TEST_F(SmoothedValueTest, SkipSamples) { - SmoothedValue sv1(0.0f); - SmoothedValue sv2(0.0f); - - sv1.Reset(sampleRate, rampLengthMs); - sv2.Reset(sampleRate, rampLengthMs); - - sv1.SetTargetValue(10.0f); - sv2.SetTargetValue(10.0f); - - for (int i = 0; i < 100; ++i) { - sv1.GetNextValue(); - } - - sv2.Skip(100); - - float value1 = sv1.GetNextValue(); - float value2 = sv2.GetNextValue(); - - EXPECT_TRUE(IsApproximatelyEqual(value1, value2, 1e-5f)); -} - -// Test when ramp length is zero -TEST_F(SmoothedValueTest, ZeroRampLength) { - SmoothedValue sv(1.0f); - sv.Reset(sampleRate, 0.0); - sv.SetTargetValue(5.0f); - - EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); -} - -// Test very short ramp length -TEST_F(SmoothedValueTest, VeryShortRampLength) { - SmoothedValue sv(0.0f); - sv.Reset(sampleRate, 0.1); - sv.SetTargetValue(1.0f); - - float value = 0.0f; - for (int i = 0; i < 10; ++i) { - value = sv.GetNextValue(); - } - - EXPECT_FLOAT_EQ(value, 1.0f); -} - -// Test multiple target changes -TEST_F(SmoothedValueTest, MultipleTargetChanges) { - SmoothedValue sv(0.0f); - sv.Reset(sampleRate, 50.0); - - sv.SetTargetValue(10.0f); - for (int i = 0; i < 100; ++i) { - sv.GetNextValue(); - } - - float valueBeforeChange = sv.GetNextValue(); - sv.SetTargetValue(-5.0f); - float valueAfterChange = sv.GetNextValue(); - - EXPECT_NE(valueBeforeChange, valueAfterChange); - - for (int i = 0; i < 3000; ++i) { - sv.GetNextValue(); - } - EXPECT_FLOAT_EQ(sv.GetNextValue(), -5.0f); -} - -// Test same target value -TEST_F(SmoothedValueTest, SameTargetValue) { - SmoothedValue sv(5.0f); - sv.Reset(sampleRate, rampLengthMs); - sv.SetTargetValue(5.0f); - - EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); - EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); -} - -// Test large sample rate -TEST_F(SmoothedValueTest, LargeSampleRate) { - SmoothedValue sv(0.0f); - sv.Reset(192000.0, 100.0); - sv.SetTargetValue(1.0f); - - float value1 = sv.GetNextValue(); - float value2 = sv.GetNextValue(); - - EXPECT_GT(value1, 0.0f); - EXPECT_GT(value2, value1); - EXPECT_LT(value1, 0.1f); -} \ No newline at end of file diff --git a/tests/utilities/arithmetic_test.cpp b/tests/utils/arithmetic_test.cpp similarity index 79% rename from tests/utilities/arithmetic_test.cpp rename to tests/utils/arithmetic_test.cpp index 24154db..0b2f887 100644 --- a/tests/utilities/arithmetic_test.cpp +++ b/tests/utils/arithmetic_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/utils/arithmetic.h" -#include "neuron.h" +#include using namespace neuron; @@ -77,23 +77,23 @@ TEST(arithmetic_suite, isApproximatelyEqual_test) EXPECT_TRUE(isApproximatelyEqual(-3, -3)); // Test values well within default relative epsilon for float (1e-5f) - EXPECT_TRUE(isApproximatelyEqual(1.0f, 1.0f + 1e-6f)); // Well within 1e-5f * 1.0f + EXPECT_TRUE(isApproximatelyEqual(1.0f, 1.0f + 1e-6f)); // Well within 1e-5f * 1.0f EXPECT_TRUE(isApproximatelyEqual(1.0f, 1.0f - 1e-6f)); - EXPECT_TRUE(isApproximatelyEqual(10.0f, 10.0f + 5e-5f)); // Well within 1e-5f * 10.0f = 1e-4f + EXPECT_TRUE(isApproximatelyEqual(10.0f, 10.0f + 5e-5f)); // Well within 1e-5f * 10.0f = 1e-4f EXPECT_TRUE(isApproximatelyEqual(-1.0f, -1.0f + 1e-6f)); // Test values well within default relative epsilon for double (1e-9) - EXPECT_TRUE(isApproximatelyEqual(1.0, 1.0 + 1e-10)); // Well within 1e-9 * 1.0 + EXPECT_TRUE(isApproximatelyEqual(1.0, 1.0 + 1e-10)); // Well within 1e-9 * 1.0 EXPECT_TRUE(isApproximatelyEqual(1.0, 1.0 - 1e-10)); - EXPECT_TRUE(isApproximatelyEqual(100.0, 100.0 + 5e-8)); // Well within 1e-9 * 100.0 = 1e-7 + EXPECT_TRUE(isApproximatelyEqual(100.0, 100.0 + 5e-8)); // Well within 1e-9 * 100.0 = 1e-7 // Test values outside default relative epsilon for float - EXPECT_FALSE(isApproximatelyEqual(1.0f, 1.0f + 2e-5f)); // Exceeds 1e-5f * 1.0f + EXPECT_FALSE(isApproximatelyEqual(1.0f, 1.0f + 2e-5f)); // Exceeds 1e-5f * 1.0f EXPECT_FALSE(isApproximatelyEqual(1.0f, 1.0f - 2e-5f)); EXPECT_FALSE(isApproximatelyEqual(10.0f, 10.0f + 2e-4f)); // Exceeds 1e-5f * 10.0f // Test values outside default relative epsilon for double - EXPECT_FALSE(isApproximatelyEqual(1.0, 1.0 + 2e-9)); // Exceeds 1e-9 * 1.0 + EXPECT_FALSE(isApproximatelyEqual(1.0, 1.0 + 2e-9)); // Exceeds 1e-9 * 1.0 EXPECT_FALSE(isApproximatelyEqual(1.0, 1.0 - 2e-9)); EXPECT_FALSE(isApproximatelyEqual(100.0, 100.0 + 2e-7)); // Exceeds 1e-9 * 100.0 @@ -113,8 +113,8 @@ TEST(arithmetic_suite, isApproximatelyEqual_test) EXPECT_FALSE(isApproximatelyEqual(0, 1)); // Test with custom epsilon values - EXPECT_TRUE(isApproximatelyEqual(1.0f, 1.0f + 1e-7f, 1e-6f)); // Custom epsilon - EXPECT_FALSE(isApproximatelyEqual(1.0f, 1.0f + 1e-5f, 1e-6f)); // Exceeds custom epsilon + EXPECT_TRUE(isApproximatelyEqual(1.0f, 1.0f + 1e-7f, 1e-6f)); // Custom epsilon + EXPECT_FALSE(isApproximatelyEqual(1.0f, 1.0f + 1e-5f, 1e-6f)); // Exceeds custom epsilon // Test edge cases near zero - demonstrate relative epsilon limitations EXPECT_TRUE(isApproximatelyEqual(0.0f, 0.0f)); @@ -129,25 +129,25 @@ TEST(arithmetic_suite, isApproximatelyEqual_test) EXPECT_TRUE(std::abs(0.0f - tiny_val) <= 1e-8f); // Direct absolute comparison works // Test with small values where relative epsilon works well - float small_val = 1e-3f; // 0.001f + float small_val = 1e-3f; // 0.001f EXPECT_TRUE(isApproximatelyEqual(small_val, small_val)); - EXPECT_TRUE(isApproximatelyEqual(small_val, small_val + 5e-9f)); // Well within 1e-5f * 1e-3f = 1e-8f + EXPECT_TRUE(isApproximatelyEqual(small_val, small_val + 5e-9f)); // Well within 1e-5f * 1e-3f = 1e-8f EXPECT_FALSE(isApproximatelyEqual(small_val, small_val + 2e-8f)); // Exceeds 1e-8f // Test with large values where relative epsilon scales appropriately - float large_val = 1e6f; // 1,000,000 + float large_val = 1e6f; // 1,000,000 EXPECT_TRUE(isApproximatelyEqual(large_val, large_val)); - EXPECT_TRUE(isApproximatelyEqual(large_val, large_val + 5.0f)); // Well within 1e-5f * 1e6f = 10.0f + EXPECT_TRUE(isApproximatelyEqual(large_val, large_val + 5.0f)); // Well within 1e-5f * 1e6f = 10.0f EXPECT_FALSE(isApproximatelyEqual(large_val, large_val + 20.0f)); // Exceeds 10.0f threshold // Test audio-relevant ranges // Frequency range: 20Hz - 20kHz - EXPECT_TRUE(isApproximatelyEqual(440.0f, 440.0f + 0.002f)); // Well within 1e-5f * 440 ≈ 0.0044f - EXPECT_FALSE(isApproximatelyEqual(440.0f, 440.0f + 0.01f)); // Exceeds threshold + EXPECT_TRUE(isApproximatelyEqual(440.0f, 440.0f + 0.002f)); // Well within 1e-5f * 440 ≈ 0.0044f + EXPECT_FALSE(isApproximatelyEqual(440.0f, 440.0f + 0.01f)); // Exceeds threshold // Gain range: 0.0 - 1.0 - EXPECT_TRUE(isApproximatelyEqual(0.5f, 0.5f + 1e-6f)); // Well within 1e-5f * 0.5 = 5e-6f - EXPECT_FALSE(isApproximatelyEqual(0.5f, 0.5f + 1e-5f)); // Exceeds 5e-6f + EXPECT_TRUE(isApproximatelyEqual(0.5f, 0.5f + 1e-6f)); // Well within 1e-5f * 0.5 = 5e-6f + EXPECT_FALSE(isApproximatelyEqual(0.5f, 0.5f + 1e-5f)); // Exceeds 5e-6f // Test sign differences EXPECT_FALSE(isApproximatelyEqual(1.0f, -1.0f)); @@ -155,10 +155,10 @@ TEST(arithmetic_suite, isApproximatelyEqual_test) // Test boundary behavior around typical audio values // MIDI note velocity (0-127) - EXPECT_TRUE(isApproximatelyEqual(64.0f, 64.0f + 0.0005f)); // Well within tolerance - EXPECT_FALSE(isApproximatelyEqual(64.0f, 64.0f + 0.001f)); // Outside tolerance + EXPECT_TRUE(isApproximatelyEqual(64.0f, 64.0f + 0.0005f)); // Well within tolerance + EXPECT_FALSE(isApproximatelyEqual(64.0f, 64.0f + 0.001f)); // Outside tolerance // Sample values (-1.0 to 1.0 range) - EXPECT_TRUE(isApproximatelyEqual(0.8f, 0.8f + 5e-6f)); // Well within tolerance - EXPECT_FALSE(isApproximatelyEqual(0.8f, 0.8f + 1e-5f)); // At/outside tolerance + EXPECT_TRUE(isApproximatelyEqual(0.8f, 0.8f + 5e-6f)); // Well within tolerance + EXPECT_FALSE(isApproximatelyEqual(0.8f, 0.8f + 1e-5f)); // At/outside tolerance } diff --git a/tests/utilities/midi_test.cpp b/tests/utils/midi_test.cpp similarity index 93% rename from tests/utilities/midi_test.cpp rename to tests/utils/midi_test.cpp index 916e92f..dccfeba 100644 --- a/tests/utilities/midi_test.cpp +++ b/tests/utils/midi_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/utils/midi.h" -#include "neuron.h" +#include using namespace neuron; diff --git a/tests/utils/smoothed_value_test.cpp b/tests/utils/smoothed_value_test.cpp new file mode 100644 index 0000000..6f88734 --- /dev/null +++ b/tests/utils/smoothed_value_test.cpp @@ -0,0 +1,508 @@ +#include "neuron/utils/smoothed_value.h" + +#include +#include + +using namespace neuron; + +class LinearSmoothedValueTest : public ::testing::Test { +protected: + void SetUp() override + { + // Default test parameters + sampleRate = 44100.0; + rampLengthMs = 100.0; // 100ms ramp + } + + // Helper function for floating point comparisons + bool IsApproximatelyEqual(float a, float b, float tolerance = 1e-6f) + { + return std::abs(a - b) < tolerance; + } + + double sampleRate; + double rampLengthMs; +}; + +class MultiplicativeSmoothedValueTest : public ::testing::Test { +protected: + void SetUp() override + { + // Default test parameters + sampleRate = 44100.0; + rampLengthMs = 100.0; // 100ms ramp + } + + // Helper function for floating point comparisons + bool IsApproximatelyEqual(float a, float b, float tolerance = 1e-6f) + { + return std::abs(a - b) < tolerance; + } + + double sampleRate; + double rampLengthMs; +}; + +// LINEAR ============================================================================================================= + +// Test default constructor +TEST_F(LinearSmoothedValueTest, DefaultConstructor) +{ + LinearSmoothedValue sv; + EXPECT_FLOAT_EQ(sv.GetNextValue(), 0.0f); +} + +// Test constructor with initial value +TEST_F(LinearSmoothedValueTest, ConstructorWithInitialValue) +{ + const float initialValue = 5.0f; + LinearSmoothedValue sv(initialValue); + EXPECT_FLOAT_EQ(sv.GetNextValue(), initialValue); +} + +// Test Reset with valid parameters +TEST_F(LinearSmoothedValueTest, ResetWithValidParameters) +{ + LinearSmoothedValue sv(1.0f); + sv.Reset(sampleRate, rampLengthMs); + + EXPECT_FLOAT_EQ(sv.GetNextValue(), 1.0f); + + sv.SetTargetValue(2.0f); + float nextValue = sv.GetNextValue(); + EXPECT_GT(nextValue, 1.0f); + EXPECT_LT(nextValue, 2.0f); +} + +// Test Reset with invalid parameters +TEST_F(LinearSmoothedValueTest, ResetWithInvalidParameters) +{ + LinearSmoothedValue sv(1.0f); + sv.SetTargetValue(2.0f); + + // Store the state before any reset attempts + float valueBefore = sv.GetNextValue(); + + // Invalid resets should be ignored, so behavior should remain consistent + sv.Reset(-1.0, rampLengthMs); + float valueAfter1 = sv.GetNextValue(); + + sv.Reset(sampleRate, -1.0); + float valueAfter2 = sv.GetNextValue(); + + // Values should continue progressing toward target + EXPECT_GT(valueAfter1, valueBefore); // Still progressing + EXPECT_GT(valueAfter2, valueAfter1); // Still progressing + EXPECT_LT(valueAfter2, 2.0f); // Haven't reached target yet +} + +// Test SetTargetValue +TEST_F(LinearSmoothedValueTest, SetTargetValue) +{ + LinearSmoothedValue sv(0.0f); + sv.Reset(sampleRate, rampLengthMs); + + sv.SetTargetValue(10.0f); + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_GT(value1, 0.0f); + EXPECT_GT(value2, value1); + EXPECT_LT(value2, 10.0f); +} + +// Test GetNextValue reaches target eventually +TEST_F(LinearSmoothedValueTest, GetNextValueReachesTarget) +{ + LinearSmoothedValue sv(0.0f); + sv.Reset(sampleRate, 50.0); + sv.SetTargetValue(1.0f); + + int expectedSamples = static_cast((50.0 / 1000.0) * sampleRate); + + float lastValue = 0.0f; + for (int i = 0; i < expectedSamples + 10; ++i) { + lastValue = sv.GetNextValue(); + } + + EXPECT_FLOAT_EQ(lastValue, 1.0f); +} + +// Test smoothing with negative values +TEST_F(LinearSmoothedValueTest, SmoothingWithNegativeValues) +{ + LinearSmoothedValue sv(5.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(-5.0f); + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_LT(value1, 5.0f); + EXPECT_LT(value2, value1); + EXPECT_GT(value1, -5.0f); +} + +// Test Skip functionality +TEST_F(LinearSmoothedValueTest, SkipSamples) +{ + LinearSmoothedValue sv1(0.0f); + LinearSmoothedValue sv2(0.0f); + + sv1.Reset(sampleRate, rampLengthMs); + sv2.Reset(sampleRate, rampLengthMs); + + sv1.SetTargetValue(10.0f); + sv2.SetTargetValue(10.0f); + + for (int i = 0; i < 100; ++i) { + sv1.GetNextValue(); + } + + sv2.Skip(100); + + float value1 = sv1.GetNextValue(); + float value2 = sv2.GetNextValue(); + + EXPECT_TRUE(IsApproximatelyEqual(value1, value2, 1e-5f)); +} + +// Test when ramp length is zero +TEST_F(LinearSmoothedValueTest, ZeroRampLength) +{ + LinearSmoothedValue sv(1.0f); + sv.Reset(sampleRate, 0.0); + sv.SetTargetValue(5.0f); + + EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); +} + +// Test very short ramp length +TEST_F(LinearSmoothedValueTest, VeryShortRampLength) +{ + LinearSmoothedValue sv(0.0f); + sv.Reset(sampleRate, 0.1); + sv.SetTargetValue(1.0f); + + float value = 0.0f; + for (int i = 0; i < 10; ++i) { + value = sv.GetNextValue(); + } + + EXPECT_FLOAT_EQ(value, 1.0f); +} + +// Test multiple target changes +TEST_F(LinearSmoothedValueTest, MultipleTargetChanges) +{ + LinearSmoothedValue sv(0.0f); + sv.Reset(sampleRate, 50.0); + + sv.SetTargetValue(10.0f); + for (int i = 0; i < 100; ++i) { + sv.GetNextValue(); + } + + float valueBeforeChange = sv.GetNextValue(); + sv.SetTargetValue(-5.0f); + float valueAfterChange = sv.GetNextValue(); + + EXPECT_NE(valueBeforeChange, valueAfterChange); + + for (int i = 0; i < 3000; ++i) { + sv.GetNextValue(); + } + EXPECT_FLOAT_EQ(sv.GetNextValue(), -5.0f); +} + +// Test same target value +TEST_F(LinearSmoothedValueTest, SameTargetValue) +{ + LinearSmoothedValue sv(5.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(5.0f); + + EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); + EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); +} + +// Test large sample rate +TEST_F(LinearSmoothedValueTest, LargeSampleRate) +{ + LinearSmoothedValue sv(0.0f); + sv.Reset(192000.0, 100.0); + sv.SetTargetValue(1.0f); + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_GT(value1, 0.0f); + EXPECT_GT(value2, value1); + EXPECT_LT(value1, 0.1f); +} + +// MULTIPLICATIVE ===================================================================================================== + +// Test constructor with initial value (multiplicative can't start from zero) +TEST_F(MultiplicativeSmoothedValueTest, ConstructorWithInitialValue) +{ + const float initialValue = 5.0f; + MultiplicativeSmoothedValue sv(initialValue); + EXPECT_FLOAT_EQ(sv.GetNextValue(), initialValue); +} + +// Test basic multiplicative behavior - exponential curve +TEST_F(MultiplicativeSmoothedValueTest, ExponentialCurve) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(sampleRate, 100.0); // 100ms ramp + sv.SetTargetValue(8.0f); // 3 octaves (2^3 = 8) + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + float value3 = sv.GetNextValue(); + + // For multiplicative smoothing, the ratio between consecutive values should be approximately constant + float ratio1 = value2 / value1; + float ratio2 = value3 / value2; + + EXPECT_GT(value1, 1.0f); + EXPECT_GT(value2, value1); + EXPECT_GT(value3, value2); + EXPECT_TRUE(IsApproximatelyEqual(ratio1, ratio2, 1e-4f)); +} + +// Test frequency doubling (octave) - with more realistic tolerance +TEST_F(MultiplicativeSmoothedValueTest, FrequencyOctave) +{ + MultiplicativeSmoothedValue sv(440.0f); // A4 + sv.Reset(sampleRate, 50.0); + sv.SetTargetValue(880.0f); // A5 (one octave up) + + // Check that we get exponential progression + float prevValue = 440.0f; + for (int i = 0; i < 10; ++i) { + float currentValue = sv.GetNextValue(); + EXPECT_GT(currentValue, prevValue); + EXPECT_LT(currentValue, 880.0f); + prevValue = currentValue; + } + + // Eventually gets close to target - use more relaxed tolerance + for (int i = 0; i < 5000; ++i) { // More samples to ensure convergence + sv.GetNextValue(); + } + float finalValue = sv.GetNextValue(); + EXPECT_TRUE(IsApproximatelyEqual(finalValue, 880.0f, 0.01f)); // 1% tolerance +} + +// Test dB conversion - another common audio use case +TEST_F(MultiplicativeSmoothedValueTest, DecibelConversion) +{ + MultiplicativeSmoothedValue sv(1.0f); // 0 dB + sv.Reset(sampleRate, 100.0); + sv.SetTargetValue(3.16228f); // Approximately 10 dB (10^(10/20)) + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_GT(value1, 1.0f); + EXPECT_GT(value2, value1); + EXPECT_LT(value2, 3.16228f); +} + +// Test zero handling - multiplicative can't reach zero +TEST_F(MultiplicativeSmoothedValueTest, ZeroHandling) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(0.0f); // Invalid target for multiplicative + + // Should not progress since target is zero + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_FLOAT_EQ(value1, 1.0f); // Should remain at current value + EXPECT_FLOAT_EQ(value2, 1.0f); +} + +// Test starting from zero - should handle gracefully +TEST_F(MultiplicativeSmoothedValueTest, StartingFromZero) +{ + MultiplicativeSmoothedValue sv(0.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(10.0f); + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + // Should not progress since current value is zero + EXPECT_FLOAT_EQ(value1, 0.0f); + EXPECT_FLOAT_EQ(value2, 0.0f); +} + +// Test SetTargetValue +TEST_F(MultiplicativeSmoothedValueTest, SetTargetValue) +{ + MultiplicativeSmoothedValue sv(2.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(32.0f); // 4 octaves (2^4 * 2 = 32) + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_GT(value1, 2.0f); + EXPECT_GT(value2, value1); + EXPECT_LT(value2, 32.0f); +} + +// Test GetNextValue gets close to target +TEST_F(MultiplicativeSmoothedValueTest, GetNextValueReachesTarget) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(sampleRate, 50.0); + sv.SetTargetValue(4.0f); + + float lastValue = 1.0f; + for (int i = 0; i < 5000; ++i) { // More samples + lastValue = sv.GetNextValue(); + } + + // Use percentage-based tolerance for multiplicative + EXPECT_TRUE(IsApproximatelyEqual(lastValue, 4.0f, 0.01f)); // 1% tolerance +} + +// Test Skip functionality +TEST_F(MultiplicativeSmoothedValueTest, SkipSamples) +{ + MultiplicativeSmoothedValue sv1(1.0f); + MultiplicativeSmoothedValue sv2(1.0f); + + sv1.Reset(sampleRate, rampLengthMs); + sv2.Reset(sampleRate, rampLengthMs); + + sv1.SetTargetValue(16.0f); + sv2.SetTargetValue(16.0f); + + for (int i = 0; i < 100; ++i) { + sv1.GetNextValue(); + } + + sv2.Skip(100); + + float value1 = sv1.GetNextValue(); + float value2 = sv2.GetNextValue(); + + EXPECT_TRUE(IsApproximatelyEqual(value1, value2, 1e-4f)); +} + +// Test when ramp length is zero +TEST_F(MultiplicativeSmoothedValueTest, ZeroRampLength) +{ + MultiplicativeSmoothedValue sv(2.0f); + sv.Reset(sampleRate, 0.0); + sv.SetTargetValue(8.0f); + + EXPECT_FLOAT_EQ(sv.GetNextValue(), 8.0f); +} + +// Test very short ramp length - adjust expectations +TEST_F(MultiplicativeSmoothedValueTest, VeryShortRampLength) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(sampleRate, 0.1); // Very short ramp + sv.SetTargetValue(2.0f); + + float value = 1.0f; + for (int i = 0; i < 20; ++i) { // More iterations for very short ramp + value = sv.GetNextValue(); + } + + // For very short ramps, expect to get reasonably close + EXPECT_TRUE(IsApproximatelyEqual(value, 2.0f, 0.05f)); // 5% tolerance +} + +// Test multiple target changes - with realistic tolerances +TEST_F(MultiplicativeSmoothedValueTest, MultipleTargetChanges) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(sampleRate, 50.0); + + sv.SetTargetValue(4.0f); + for (int i = 0; i < 100; ++i) { + sv.GetNextValue(); + } + + float valueBeforeChange = sv.GetNextValue(); + sv.SetTargetValue(0.5f); // Going down + float valueAfterChange = sv.GetNextValue(); + + EXPECT_LT(valueAfterChange, valueBeforeChange); // Should start decreasing + + for (int i = 0; i < 5000; ++i) { + sv.GetNextValue(); + } + float finalValue = sv.GetNextValue(); + EXPECT_TRUE(IsApproximatelyEqual(finalValue, 0.5f, 0.01f)); // 1% tolerance +} + +// Test same target value +TEST_F(MultiplicativeSmoothedValueTest, SameTargetValue) +{ + MultiplicativeSmoothedValue sv(5.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(5.0f); + + EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); + EXPECT_FLOAT_EQ(sv.GetNextValue(), 5.0f); +} + +// Test decreasing values (important for multiplicative) +TEST_F(MultiplicativeSmoothedValueTest, DecreasingValues) +{ + MultiplicativeSmoothedValue sv(8.0f); + sv.Reset(sampleRate, rampLengthMs); + sv.SetTargetValue(2.0f); // Decreasing + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_LT(value1, 8.0f); + EXPECT_LT(value2, value1); + EXPECT_GT(value2, 2.0f); +} + +// Test large sample rate +TEST_F(MultiplicativeSmoothedValueTest, LargeSampleRate) +{ + MultiplicativeSmoothedValue sv(1.0f); + sv.Reset(192000.0, 100.0); + sv.SetTargetValue(2.0f); + + float value1 = sv.GetNextValue(); + float value2 = sv.GetNextValue(); + + EXPECT_GT(value1, 1.0f); + EXPECT_GT(value2, value1); + EXPECT_LT(value1, 1.01f); // Should be a very small increment +} + +// Test musical interval - with more realistic expectations +TEST_F(MultiplicativeSmoothedValueTest, MusicalInterval) +{ + MultiplicativeSmoothedValue sv(440.0f); // A4 + sv.Reset(sampleRate, 120.0); // 120ms for 12 steps + sv.SetTargetValue(880.0f); // A5 + + // Just run for the expected duration plus some buffer + int totalSamples = static_cast((150.0 / 1000.0) * sampleRate); // 150ms buffer + for (int i = 0; i < totalSamples; ++i) { + sv.GetNextValue(); + } + + float finalValue = sv.GetNextValue(); + + // Check that we get reasonably close to 880Hz (within 1%) + EXPECT_TRUE(IsApproximatelyEqual(finalValue, 880.0f, 8.8f)); // ~1% tolerance +} diff --git a/tests/audio/waveform_test.cpp b/tests/utils/waveform_test.cpp similarity index 97% rename from tests/audio/waveform_test.cpp rename to tests/utils/waveform_test.cpp index 5d66133..9d4548f 100644 --- a/tests/audio/waveform_test.cpp +++ b/tests/utils/waveform_test.cpp @@ -1,6 +1,6 @@ -#include +#include "neuron/utils/waveform.h" -#include "neuron.h" +#include using namespace neuron;