├── ci ├── llvm-run.sh ├── ci.yaml ├── Brewfile ├── linux.sh ├── osx.sh ├── nfbuildlinux.py ├── ios.py ├── nfbuildosx.py ├── linux.py ├── osx.py ├── build_options.py └── nfbuild.py ├── NFParam.png ├── .gitmodules ├── resources ├── paramAutomationExpected.png ├── params.gp ├── plot.sh └── paramAutomationExpected.txt ├── libraries └── CMakeLists.txt ├── source ├── test │ ├── CMakeLists.txt │ └── NFParamTests.cpp ├── CMakeLists.txt ├── ParamEvent.cpp ├── WAAParamEvents.h ├── ParamImplementation.h ├── WAAParamEvents.cpp └── ParamImplementation.cpp ├── CONTRIBUTING.md ├── .gitignore ├── CMakeLists.txt ├── .circleci └── config.yml ├── include └── NFParam │ ├── ParamEvent.h │ └── Param.h ├── README.md ├── .clang-format └── LICENSE /ci/llvm-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec xcrun llvm-cov gcov "$@" 3 | -------------------------------------------------------------------------------- /NFParam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFParam/HEAD/NFParam.png -------------------------------------------------------------------------------- /ci/ci.yaml: -------------------------------------------------------------------------------- 1 | # NFParam CI config 2 | 'unit_tests': 3 | - 'NFParamTests' 4 | -------------------------------------------------------------------------------- /ci/Brewfile: -------------------------------------------------------------------------------- 1 | brew "clang-format" 2 | brew "cmake" 3 | brew "lcov" 4 | brew "ninja" 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libraries/Catch2"] 2 | path = libraries/Catch2 3 | url = https://github.com/catchorg/Catch2.git 4 | -------------------------------------------------------------------------------- /resources/paramAutomationExpected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFParam/HEAD/resources/paramAutomationExpected.png -------------------------------------------------------------------------------- /libraries/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Setup unit test framework 2 | if (NOT TARGET Catch2) 3 | set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Catch2/single_include) 4 | add_library(Catch2 INTERFACE) 5 | target_include_directories(Catch2 INTERFACE ${CATCH_INCLUDE_DIR}) 6 | endif() 7 | -------------------------------------------------------------------------------- /source/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable( 2 | NFParamTests 3 | NFParamTests.cpp) 4 | target_include_directories( 5 | NFParamTests 6 | PUBLIC 7 | ${NFPARAM_INCLUDE_DIRECTORY}) 8 | target_link_libraries( 9 | NFParamTests 10 | PUBLIC 11 | NFParam 12 | Catch2) 13 | -------------------------------------------------------------------------------- /resources/params.gp: -------------------------------------------------------------------------------- 1 | set term png 2 | set title "Param values" 3 | set output output 4 | set xlabel "Time (s)" 5 | set ylabel "Value" 6 | set xtics 0, 0.1, 1 7 | set ytics 0, 0.1, 1 8 | set grid ytics lt 0 lw 1 lc rgb "#bbbbbb" 9 | set grid xtics lt 0 lw 1 lc rgb "#bbbbbb" 10 | plot filename title "" with lines lt 1 lw 2 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are welcomed. Open a pull-request or an issue. 3 | 4 | ## Code of conduct 5 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 6 | 7 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 8 | -------------------------------------------------------------------------------- /resources/plot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 2 ] 4 | then 5 | echo "Usage: sh plot.sh " 6 | exit 0 7 | fi 8 | 9 | input=$1 10 | output=$2 11 | gpfile=$(find . | grep "params.gp") 12 | 13 | echo Using gnuplot script $gpfile with input data = $input and output image = $output 14 | gnuplot -e "filename='$input';output='$output'" $gpfile 15 | 16 | 17 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(NFParam 2 | STATIC 3 | ${NFPARAM_INCLUDE_DIRECTORY}/NFParam/Param.h 4 | ${NFPARAM_INCLUDE_DIRECTORY}/NFParam/ParamEvent.h 5 | ParamEvent.cpp 6 | WAAParamEvents.h 7 | WAAParamEvents.cpp 8 | ParamImplementation.h 9 | ParamImplementation.cpp) 10 | target_include_directories( 11 | NFParam 12 | PUBLIC 13 | ${NFPARAM_INCLUDE_DIRECTORY}) 14 | 15 | add_subdirectory(test) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Environment normalization: 2 | /.bundle 3 | /vendor/bundle 4 | 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | project.xcworkspace 24 | 25 | ## Other 26 | *.xccheckout 27 | *.moved-aside 28 | *.xcuserstate 29 | *.xcscmblueprint 30 | .DS_Store 31 | *.swp 32 | 33 | ## Obj-C/Swift specific 34 | *.hmap 35 | *.ipa 36 | 37 | # Carthage 38 | Carthage/Build 39 | 40 | # Ignore changes to our project.xcconfig that gets overridden by the build system 41 | project.xcconfig 42 | 43 | # Python 44 | *.egg* 45 | *.pyc 46 | nfparam_env 47 | 48 | # Test output 49 | paramAutomation* 50 | 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(NFParam) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") 7 | 8 | if(LLVM_STDLIB) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ \ 10 | -Wno-tautological-undefined-compare") 11 | endif() 12 | 13 | if(USE_ADDRESS_SANITIZER) 14 | message("Using Address & Leak Sanitizer") 15 | set( 16 | CMAKE_CXX_FLAGS 17 | "${CMAKE_CXX_FLAGS} -fsanitize=address -g -fno-omit-frame-pointer") 18 | set( 19 | CMAKE_EXE_LINKER_FLAGS 20 | "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -g -fno-omit-frame-pointer") 21 | endif() 22 | 23 | if(CODE_COVERAGE) 24 | message("Using Code Coverage") 25 | set(CMAKE_CXX_FLAGS 26 | "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -fno-inline") 27 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 28 | endif() 29 | 30 | set(NFPARAM_INCLUDE_DIRECTORY 31 | "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE STRING "NFParam Includes" FORCE) 32 | 33 | add_subdirectory(libraries) 34 | add_subdirectory(source) 35 | -------------------------------------------------------------------------------- /ci/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on any non-zero status 4 | set -e 5 | 6 | # Install system dependencies 7 | sudo apt-get -q update 8 | sudo apt-get install -y --no-install-recommends apt-utils \ 9 | clang-3.9 \ 10 | git \ 11 | lcov \ 12 | libc++-dev \ 13 | ninja-build \ 14 | python-pip \ 15 | python-software-properties \ 16 | python-virtualenv \ 17 | software-properties-common \ 18 | unzip \ 19 | wget \ 20 | clang-format-3.9 21 | 22 | # Extra repo for clang-format-4.0 23 | sudo add-apt-repository -y ppa:ubuntu-mozilla-security/ppa 24 | 25 | # Extra repo for gcc-4.9 so we don't have to use 4.8 26 | sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 27 | 28 | sudo apt-get -q update 29 | sudo apt-get install -y --no-install-recommends \ 30 | clang-format-4.0 \ 31 | gcc-4.9 \ 32 | g++-4.9 33 | sudo apt-get install -y --reinstall binutils 34 | 35 | # Update submodules 36 | git submodule update --init --recursive 37 | 38 | # install cmake 3.6.x 39 | wget --no-check-certificate https://cmake.org/files/v3.6/cmake-3.6.3-Linux-x86_64.sh 40 | chmod +x cmake-3.6.3-Linux-x86_64.sh 41 | sudo sh cmake-3.6.3-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir 42 | 43 | # Set up virtualenv 44 | virtualenv nfparam_env 45 | . nfparam_env/bin/activate 46 | 47 | # Install Python Packages 48 | pip install pyyaml \ 49 | flake8 \ 50 | cmakelint 51 | 52 | # Execute our python build tools 53 | python ci/linux.py "$@" 54 | -------------------------------------------------------------------------------- /ci/osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2018 Spotify AB. 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | 21 | # Exit on any non-zero status 22 | set -e 23 | 24 | # Install system dependencies 25 | HOMEBREW_BREWFILE=${PWD}/ci/Brewfile 26 | echo $HOMEBREW_BREWFILE 27 | brew bundle --file=$HOMEBREW_BREWFILE 28 | 29 | # Set up virtualenv 30 | virtualenv nfparam_env 31 | source nfparam_env/bin/activate 32 | 33 | # Install Python Packages 34 | pip install pyyaml \ 35 | flake8 \ 36 | cmakelint \ 37 | requests 38 | 39 | # Execute our python build tools 40 | if [ -n "$BUILD_IOS" ]; then 41 | python ci/ios.py "$@" 42 | else 43 | python ci/osx.py "$@" 44 | fi 45 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | buildlinuxclang: 4 | docker: 5 | - image: ubuntu:trusty 6 | steps: 7 | - checkout 8 | - run: 9 | name: Build with Clang on Linux 10 | command: sh ci/linux.sh clang_build 11 | - store_artifacts: 12 | path: build/output/libNFParam.zip 13 | destination: libNFParam.zip 14 | buildlinuxgcc: 15 | docker: 16 | - image: ubuntu:trusty 17 | steps: 18 | - checkout 19 | - run: 20 | name: Build Linux with gcc 21 | command: sh ci/linux.sh gcc_build 22 | - store_artifacts: 23 | path: build/output/libNFParam.zip 24 | destination: libNFParam.zip 25 | buildmac: 26 | macos: 27 | xcode: "9.0" 28 | steps: 29 | - checkout 30 | - run: git submodule sync 31 | - run: git submodule update --init --recursive 32 | - run: 33 | name: Build OSX 34 | command: sh ci/osx.sh build 35 | - store_artifacts: 36 | path: build/output/libNFParam.zip 37 | destination: libNFParam.zip 38 | buildmacios: 39 | macos: 40 | xcode: "9.0" 41 | steps: 42 | - checkout 43 | - run: 44 | name: Build iOS 45 | command: BUILD_IOS=1 sh ci/osx.sh build 46 | - store_artifacts: 47 | path: build/output/libNFParam.zip 48 | destination: libNFParam.zip 49 | workflows: 50 | version: 2 51 | build: 52 | jobs: 53 | - buildlinuxclang 54 | - buildlinuxgcc 55 | - buildmac 56 | - buildmacios 57 | -------------------------------------------------------------------------------- /source/ParamEvent.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nativeformat { 4 | namespace param { 5 | 6 | ParamEvent::ParamEvent(double start_time, double end_time, Anchor anchor) 7 | : start_time(start_time), end_time(end_time), start_value(0), anchor(anchor) {} 8 | 9 | ParamEvent::~ParamEvent() {} 10 | 11 | float ParamEvent::endValue() { 12 | return valueAtTime(end_time); 13 | } 14 | 15 | float ParamEvent::cumulativeValue(double start_time, double end_time, double precision) { 16 | double cumulative_value = 0.0; 17 | 18 | // define a function to define the trapezoid rule 19 | auto trapezoid = [this](const double &time_at, const double &time_diff) { 20 | return (this->valueAtTime(time_at + time_diff) + this->valueAtTime(time_at)) * time_diff / 2.; 21 | }; 22 | // now calculate remainder 23 | double time = start_time; 24 | for (; time + precision < end_time; time += precision) { 25 | cumulative_value += trapezoid(time, precision); 26 | } 27 | // get the remainder of the integral 28 | cumulative_value += trapezoid(time, end_time - time); 29 | return cumulative_value; 30 | } 31 | 32 | CustomParamEvent::CustomParamEvent(double start_time, 33 | double end_time, 34 | Anchor anchor, 35 | NF_AUDIO_PARAM_FUNCTION function) 36 | : ParamEvent(start_time, end_time, anchor), function(function) {} 37 | 38 | CustomParamEvent::~CustomParamEvent() {} 39 | 40 | float CustomParamEvent::valueAtTime(double time) { 41 | return function(time); 42 | } 43 | 44 | } // namespace param 45 | } // namespace nativeformat 46 | -------------------------------------------------------------------------------- /include/NFParam/ParamEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | namespace nativeformat { 27 | namespace param { 28 | 29 | typedef std::function NF_AUDIO_PARAM_FUNCTION; 30 | 31 | /* The ParamEvent Anchor determines whether a ParamEvent is 32 | * tied to the start time and value, end time and value, or 33 | * the full Range. This determines how the start and end 34 | * values and times will change (or remain fixed) when other 35 | * ParaEvents are added. 36 | */ 37 | enum class Anchor { NONE = 0x0, START = 0x1, END = 0x2, ALL = 0x3 }; 38 | inline Anchor operator&(Anchor a, Anchor b) { 39 | using T = std::underlying_type::type; 40 | return static_cast(static_cast(a) & static_cast(b)); 41 | } 42 | 43 | struct ParamEvent { 44 | double start_time; 45 | double end_time; 46 | double start_value; 47 | 48 | const Anchor anchor; 49 | 50 | ParamEvent(double start_time, double end_time, Anchor anchor); 51 | virtual ~ParamEvent(); 52 | 53 | virtual float valueAtTime(double time) = 0; 54 | virtual float endValue(); 55 | virtual float cumulativeValue(double start_time, double end_time, double precision = .1); 56 | 57 | static constexpr double INVALID_TIME = -1.0; 58 | }; 59 | 60 | struct CustomParamEvent : public ParamEvent { 61 | NF_AUDIO_PARAM_FUNCTION function; 62 | 63 | CustomParamEvent(double start_time, 64 | double end_time, 65 | Anchor anchor, 66 | NF_AUDIO_PARAM_FUNCTION function); 67 | 68 | virtual ~CustomParamEvent(); 69 | float valueAtTime(double time) override; 70 | }; 71 | 72 | } // namespace param 73 | } // namespace nativeformat 74 | -------------------------------------------------------------------------------- /include/NFParam/Param.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | namespace nativeformat { 29 | namespace param { 30 | 31 | class Param { 32 | public: 33 | // from WAA spec 34 | virtual float defaultValue() const = 0; 35 | virtual float maxValue() const = 0; 36 | virtual float minValue() const = 0; 37 | virtual void setValue(float value) = 0; 38 | virtual void setValueAtTime(float value, double time) = 0; 39 | virtual void linearRampToValueAtTime(float end_value, double end_time) = 0; 40 | virtual void setTargetAtTime(float target, double start_time, float time_constant) = 0; 41 | virtual void exponentialRampToValueAtTime(float value, double end_time) = 0; 42 | virtual void setValueCurveAtTime(std::vector values, 43 | double start_time, 44 | double duration) = 0; 45 | 46 | // other methods 47 | virtual void addCustomEvent(double start_time, 48 | double end_time, 49 | Anchor anchor, 50 | NF_AUDIO_PARAM_FUNCTION function) = 0; 51 | virtual float valueForTime(double time) = 0; 52 | virtual void valuesForTimeRange(float *values, 53 | size_t values_count, 54 | double start_time, 55 | double end_time) = 0; 56 | virtual std::string name() = 0; 57 | virtual float smoothedValueForTimeRange(double start_time, 58 | double end_time, 59 | size_t samples = 5) = 0; 60 | virtual float cumulativeValueForTimeRange(double start_time, 61 | double end_time, 62 | double precision = 0.1) = 0; 63 | }; 64 | 65 | std::shared_ptr createParam(float default_value, 66 | float max_value, 67 | float min_value, 68 | const std::string &name); 69 | 70 | } // namespace param 71 | } // namespace nativeformat 72 | -------------------------------------------------------------------------------- /source/WAAParamEvents.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | namespace nativeformat { 29 | namespace param { 30 | 31 | struct ValueAtTimeEvent : ParamEvent { 32 | ValueAtTimeEvent(float value, double time); 33 | virtual ~ValueAtTimeEvent(); 34 | 35 | float valueAtTime(double time) override; 36 | 37 | virtual float cumulativeValue(double start_time, double end_time, double precision = .1) override; 38 | }; 39 | 40 | struct TargetAtTimeEvent : ParamEvent { 41 | const float time_constant; 42 | const float target; 43 | 44 | TargetAtTimeEvent(float value, double time, float time_constant); 45 | virtual ~TargetAtTimeEvent(); 46 | 47 | float valueAtTime(double time) override; 48 | virtual float cumulativeValue(double start_time, double end_time, double precision = .1) override; 49 | }; 50 | 51 | struct LinearRampEvent : ParamEvent { 52 | const float target; 53 | 54 | LinearRampEvent(float value, double time); 55 | virtual ~LinearRampEvent(); 56 | 57 | float valueAtTime(double time) override; 58 | virtual float cumulativeValue(double start_time, double end_time, double precision = .1) override; 59 | }; 60 | 61 | struct ExponentialRampEvent : ParamEvent { 62 | const float target; 63 | 64 | ExponentialRampEvent(float value, double time); 65 | virtual ~ExponentialRampEvent(); 66 | 67 | float valueAtTime(double time) override; 68 | virtual float cumulativeValue(double start_time, double end_time, double precision = .1) override; 69 | 70 | private: 71 | double base() { return target / start_value; } 72 | }; 73 | 74 | struct ValueCurveEvent : ParamEvent { 75 | const std::vector values; 76 | const double duration; 77 | 78 | ValueCurveEvent(const std::vector &values, double start_time, double duration); 79 | virtual ~ValueCurveEvent(); 80 | 81 | float valueAtTime(double time) override; 82 | }; 83 | 84 | struct DummyEvent : ParamEvent { 85 | DummyEvent(float value); 86 | virtual ~DummyEvent(); 87 | 88 | float valueAtTime(double time) override; 89 | }; 90 | 91 | template 92 | std::unique_ptr createEvent(Args... args) { 93 | return std::unique_ptr(new EventClass(args...)); 94 | } 95 | 96 | } // namespace param 97 | } // namespace nativeformat 98 | -------------------------------------------------------------------------------- /ci/nfbuildlinux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildLinux(NFBuild): 36 | clang_format_binary = 'clang-format-4.0' 37 | def __init__(self): 38 | super(self.__class__, self).__init__() 39 | self.project_file = 'build.ninja' 40 | 41 | def generateProject(self, 42 | code_coverage=False, 43 | address_sanitizer=False, 44 | thread_sanitizer=False, 45 | undefined_behaviour_sanitizer=False, 46 | ios=False, 47 | gcc=False): 48 | cmake_call = [ 49 | 'cmake', 50 | '..', 51 | '-GNinja'] 52 | if gcc: 53 | cmake_call.extend(['-DLLVM_STDLIB=0']) 54 | else: 55 | cmake_call.extend(['-DLLVM_STDLIB=1']) 56 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 57 | if cmake_result != 0: 58 | sys.exit(cmake_result) 59 | 60 | def targetBinary(self, target): 61 | for root, dirnames, filenames in os.walk(self.build_directory): 62 | for filename in fnmatch.filter(filenames, target): 63 | full_target_file = os.path.join(root, filename) 64 | return full_target_file 65 | return '' 66 | 67 | def buildTarget(self, target, sdk='linux', arch='x86_64'): 68 | result = subprocess.call([ 69 | 'ninja', 70 | '-C', 71 | self.build_directory, 72 | '-f', 73 | self.project_file, 74 | target]) 75 | if result != 0: 76 | sys.exit(result) 77 | 78 | def packageArtifacts(self): 79 | lib_name = 'libNFParam.a' 80 | output_folder = os.path.join(self.build_directory, 'output') 81 | artifacts_folder = os.path.join(output_folder, 'NFParam') 82 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 83 | source_folder = os.path.join(self.build_directory, 'source') 84 | lib_matches = self.find_file(source_folder, lib_name) 85 | shutil.copyfile(lib_matches[0], os.path.join(artifacts_folder, lib_name)) 86 | output_zip = os.path.join(output_folder, 'libNFParam.zip') 87 | self.make_archive(artifacts_folder, output_zip) 88 | -------------------------------------------------------------------------------- /ci/ios.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | buildOptions.addOption("lintCmake", "Lint cmake files") 33 | buildOptions.addOption("lintCpp", "Lint CPP Files") 34 | buildOptions.addOption("lintCppWithInlineChange", 35 | "Lint CPP Files and fix them") 36 | buildOptions.addOption("makeBuildDirectory", 37 | "Wipe existing build directory") 38 | buildOptions.addOption("generateProject", "Regenerate xcode project") 39 | buildOptions.addOption("buildTargetIphoneSimulator", 40 | "Build Target: iPhone Simulator") 41 | buildOptions.addOption("buildTargetIphoneOS", "Build Target: iPhone OS") 42 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 43 | 44 | buildOptions.setDefaultWorkflow("Empty workflow", []) 45 | 46 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 47 | 'installDependencies', 48 | 'lintCmake', 49 | 'lintCppWithInlineChange' 50 | ]) 51 | 52 | buildOptions.addWorkflow("build", "Production Build", [ 53 | 'installDependencies', 54 | 'lintCmake', 55 | 'lintCpp', 56 | 'makeBuildDirectory', 57 | 'generateProject', 58 | 'buildTargetIphoneSimulator', 59 | 'buildTargetIphoneOS', 60 | 'packageArtifacts' 61 | ]) 62 | 63 | options = buildOptions.parseArgs() 64 | buildOptions.verbosePrintBuildOptions(options) 65 | 66 | library_target = 'NFParam' 67 | nfbuild = NFBuildOSX() 68 | 69 | if buildOptions.checkOption(options, 'installDependencies'): 70 | nfbuild.installDependencies() 71 | 72 | if buildOptions.checkOption(options, 'lintCmake'): 73 | nfbuild.lintCmake() 74 | 75 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 76 | nfbuild.lintCPP(make_inline_changes=True) 77 | elif buildOptions.checkOption(options, 'lintCpp'): 78 | nfbuild.lintCPP(make_inline_changes=False) 79 | 80 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 81 | nfbuild.makeBuildDirectory() 82 | 83 | if buildOptions.checkOption(options, 'generateProject'): 84 | nfbuild.generateProject(ios=True) 85 | 86 | if buildOptions.checkOption(options, 'buildTargetIphoneSimulator'): 87 | nfbuild.buildTarget(library_target, 88 | sdk='iphonesimulator', 89 | arch='x86_64') 90 | 91 | if buildOptions.checkOption(options, 'buildTargetIphoneOS'): 92 | nfbuild.buildTarget(library_target, sdk='iphoneos', arch='arm64') 93 | if buildOptions.checkOption(options, "packageArtifacts"): 94 | nfbuild.packageArtifacts() 95 | 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /ci/nfbuildosx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildOSX(NFBuild): 36 | clang_format_binary = 'clang-format' 37 | def __init__(self): 38 | super(self.__class__, self).__init__() 39 | self.project_file = os.path.join( 40 | self.build_directory, 41 | 'NFParam.xcodeproj') 42 | 43 | def generateProject(self, 44 | code_coverage=False, 45 | electron_build=False, 46 | address_sanitizer=False, 47 | thread_sanitizer=False, 48 | undefined_behaviour_sanitizer=False, 49 | ios=False): 50 | cmake_call = [ 51 | 'cmake', 52 | '..', 53 | '-GXcode'] 54 | if code_coverage: 55 | cmake_call.append('-DCODE_COVERAGE=1') 56 | else: 57 | cmake_call.append('-DCODE_COVERAGE=0') 58 | if address_sanitizer: 59 | cmake_call.append('-DUSE_ADDRESS_SANITIZER=1') 60 | else: 61 | cmake_call.append('-DUSE_ADDRESS_SANITIZER=0') 62 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 63 | if cmake_result != 0: 64 | sys.exit(cmake_result) 65 | 66 | def buildTarget(self, target, sdk='macosx', arch='x86_64'): 67 | xcodebuild_result = subprocess.call([ 68 | 'xcodebuild', 69 | '-project', 70 | self.project_file, 71 | '-target', 72 | target, 73 | '-sdk', 74 | sdk, 75 | '-arch', 76 | arch, 77 | '-configuration', 78 | self.build_type, 79 | 'build']) 80 | if xcodebuild_result != 0: 81 | sys.exit(xcodebuild_result) 82 | 83 | def targetBinary(self, target): 84 | for root, dirnames, filenames in os.walk(self.build_directory): 85 | for filename in fnmatch.filter(filenames, target): 86 | full_target_file = os.path.join(root, filename) 87 | return full_target_file 88 | return '' 89 | 90 | def packageArtifacts(self): 91 | lib_name = 'libNFParam.a' 92 | output_folder = os.path.join(self.build_directory, 'output') 93 | artifacts_folder = os.path.join(output_folder, 'NFParam') 94 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 95 | source_folder = os.path.join(self.build_directory, 'source') 96 | lib_matches = self.find_file(source_folder, lib_name) 97 | lipo_command = ['lipo', '-create'] 98 | for lib_match in lib_matches: 99 | lipo_command.append(lib_match) 100 | lipo_command.extend(['-output', os.path.join(artifacts_folder, lib_name)]) 101 | lipo_result = subprocess.call(lipo_command) 102 | if lipo_result != 0: 103 | sys.exit(lipo_result) 104 | output_zip = os.path.join(output_folder, 'libNFParam.zip') 105 | self.make_archive(artifacts_folder, output_zip) 106 | -------------------------------------------------------------------------------- /source/ParamImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include "WAAParamEvents.h" 30 | 31 | namespace nativeformat { 32 | namespace param { 33 | 34 | class ParamImplementation : public Param { 35 | typedef std::unique_ptr EVENT_PTR; 36 | 37 | public: 38 | ParamImplementation(float default_value, 39 | float max_value, 40 | float min_value, 41 | const std::string &name); 42 | virtual ~ParamImplementation(); 43 | 44 | float valueForTime(double time) override; 45 | void valuesForTimeRange(float *values, 46 | size_t values_count, 47 | double start_time, 48 | double end_time) override; 49 | 50 | std::string name() override; 51 | float smoothedValueForTimeRange(double start_time, double end_time, size_t samples = 5) override; 52 | float cumulativeValueForTimeRange(double start_time, 53 | double end_time, 54 | double precision = 0.1) override; 55 | 56 | // WAAParam 57 | float defaultValue() const override; 58 | float maxValue() const override; 59 | float minValue() const override; 60 | void setValue(float value) override; 61 | void setValueAtTime(float value, double time) override; 62 | void linearRampToValueAtTime(float end_value, double end_time) override; 63 | void setTargetAtTime(float target, double start_time, float time_constant) override; 64 | void exponentialRampToValueAtTime(float value, double end_time) override; 65 | void setValueCurveAtTime(std::vector values, double start_time, double duration) override; 66 | 67 | // Custom 68 | virtual void addCustomEvent(double start_time, 69 | double end_time, 70 | Anchor anchor, 71 | NF_AUDIO_PARAM_FUNCTION function) override; 72 | 73 | private: 74 | const float _default_value; 75 | const float _max_value; 76 | const float _min_value; 77 | const std::string _name; 78 | std::list _events; 79 | std::mutex _events_mutex; 80 | std::vector _smoothed_samples_buffer; 81 | std::map> _cumulative_values_cache; 82 | 83 | // Find the event (if any) that governs the param curve at the given time 84 | std::list>::iterator iteratorForTime(double time); 85 | 86 | // Find the last event (if any) whose anchor time is <= time 87 | std::list>::iterator prevEvent(double time); 88 | 89 | // Populate start and end with the required start and end of an event. 90 | // If the event's anchor is NONE, getRequiredTimeRange will return false. 91 | // If the event's anchor is START or END, start = end. 92 | // If the event's anchor is ALL, end >= start. 93 | bool getRequiredTimeRange(const EVENT_PTR &event, double &start, double &end); 94 | 95 | // Throw an exception if an event overlaps with any existing events 96 | void checkOverlap(const EVENT_PTR &event); 97 | 98 | // Update events' start and end times according to their anchor. 99 | // Assumes prev's required time range ends before event's. 100 | void updateTimes(EVENT_PTR &prev, EVENT_PTR &event); 101 | 102 | // Update adjacent events on insertion of a new event 103 | void addEvent(EVENT_PTR new_event, std::list::iterator prev_event); 104 | 105 | void invalidateCachedCumulativeValuesAfterTime(double time); 106 | }; 107 | 108 | } // namespace param 109 | } // namespace nativeformat 110 | -------------------------------------------------------------------------------- /ci/linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import os 24 | import sys 25 | 26 | from nfbuildlinux import NFBuildLinux 27 | from build_options import BuildOptions 28 | 29 | 30 | def main(): 31 | buildOptions = BuildOptions() 32 | buildOptions.addOption("debug", "Enable Debug Mode") 33 | buildOptions.addOption("installDependencies", "Install dependencies") 34 | buildOptions.addOption("lintCmake", "Lint cmake files") 35 | buildOptions.addOption("lintCpp", "Lint CPP Files") 36 | buildOptions.addOption("lintCppWithInlineChange", 37 | "Lint CPP Files and fix them") 38 | buildOptions.addOption("unitTests", "Run Unit Tests") 39 | buildOptions.addOption("makeBuildDirectory", 40 | "Wipe existing build directory") 41 | buildOptions.addOption("generateProject", "Regenerate xcode project") 42 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 43 | buildOptions.addOption("gnuToolchain", "Build with gcc and libstdc++") 44 | buildOptions.addOption("llvmToolchain", "Build with clang and libc++") 45 | buildOptions.addOption("packageArtifacts", "Package the binary artifacts") 46 | 47 | buildOptions.setDefaultWorkflow("Empty workflow", []) 48 | 49 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 50 | 'installDependencies', 51 | 'lintCmake', 52 | 'lintCppWithInlineChange' 53 | ]) 54 | 55 | buildOptions.addWorkflow("clang_build", "Production Build", [ 56 | 'llvmToolchain', 57 | 'installDependencies', 58 | 'lintCmake', 59 | 'lintCpp', 60 | 'makeBuildDirectory', 61 | 'generateProject', 62 | 'buildTargetLibrary', 63 | 'unitTests', 64 | 'packageArtifacts' 65 | ]) 66 | 67 | buildOptions.addWorkflow("gcc_build", "Production Build", [ 68 | 'gnuToolchain', 69 | 'installDependencies', 70 | 'lintCmake', 71 | 'lintCpp', 72 | 'makeBuildDirectory', 73 | 'generateProject', 74 | 'buildTargetLibrary', 75 | 'unitTests', 76 | 'packageArtifacts' 77 | ]) 78 | 79 | options = buildOptions.parseArgs() 80 | buildOptions.verbosePrintBuildOptions(options) 81 | 82 | library_target = 'NFParamTests' 83 | nfbuild = NFBuildLinux() 84 | 85 | if buildOptions.checkOption(options, 'debug'): 86 | nfbuild.build_type = 'Debug' 87 | 88 | if buildOptions.checkOption(options, 'installDependencies'): 89 | nfbuild.installDependencies() 90 | 91 | if buildOptions.checkOption(options, 'lintCmake'): 92 | nfbuild.lintCmake() 93 | 94 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 95 | nfbuild.lintCPP(make_inline_changes=True) 96 | elif buildOptions.checkOption(options, 'lintCpp'): 97 | nfbuild.lintCPP(make_inline_changes=False) 98 | 99 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 100 | nfbuild.makeBuildDirectory() 101 | 102 | if buildOptions.checkOption(options, 'generateProject'): 103 | if buildOptions.checkOption(options, 'gnuToolchain'): 104 | os.environ['CC'] = 'gcc-4.9' 105 | os.environ['CXX'] = 'g++-4.9' 106 | nfbuild.generateProject(gcc=True) 107 | elif buildOptions.checkOption(options, 'llvmToolchain'): 108 | os.environ['CC'] = 'clang-3.9' 109 | os.environ['CXX'] = 'clang++-3.9' 110 | nfbuild.generateProject(gcc=False) 111 | else: 112 | nfbuild.generateProject() 113 | 114 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 115 | nfbuild.buildTarget(library_target) 116 | 117 | if buildOptions.checkOption(options, 'unitTests'): 118 | nfbuild.runUnitTests() 119 | if buildOptions.checkOption(options, 'packageArtifacts'): 120 | nfbuild.packageArtifacts() 121 | 122 | if __name__ == "__main__": 123 | main() 124 | -------------------------------------------------------------------------------- /ci/osx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("debug", "Enable Debug Mode") 32 | buildOptions.addOption("installDependencies", "Install dependencies") 33 | buildOptions.addOption("packageArtifacts", "Package the binary artifacts") 34 | buildOptions.addOption("lintCmake", "Lint cmake files") 35 | buildOptions.addOption("lintCpp", "Lint CPP Files") 36 | buildOptions.addOption("lintCppWithInlineChange", 37 | "Lint CPP Files and fix them") 38 | 39 | buildOptions.addOption("unitTests", "Run Unit Tests") 40 | 41 | buildOptions.addOption("makeBuildDirectory", 42 | "Wipe existing build directory") 43 | 44 | buildOptions.addOption("generateProject", "Regenerate xcode project") 45 | buildOptions.addOption("addressSanitizer", 46 | "Enable Address Sanitizer in generate project") 47 | buildOptions.addOption("codeCoverage", 48 | "Enable code coverage in generate project") 49 | 50 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 51 | 52 | buildOptions.setDefaultWorkflow("Empty workflow", []) 53 | 54 | buildOptions.addWorkflow("local_unit", "Run local unit tests", [ 55 | 'debug', 56 | 'installDependencies', 57 | 'lintCmake', 58 | 'unitTests' 59 | ]) 60 | 61 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 62 | 'debug', 63 | 'installDependencies', 64 | 'lintCmake', 65 | 'lintCppWithInlineChange' 66 | ]) 67 | 68 | buildOptions.addWorkflow("build", "Production Build", [ 69 | 'debug', 70 | 'installDependencies', 71 | 'lintCmake', 72 | 'lintCpp', 73 | 'makeBuildDirectory', 74 | 'generateProject', 75 | 'buildTargetLibrary', 76 | 'unitTests', 77 | 'packageArtifacts' 78 | ]) 79 | 80 | buildOptions.addWorkflow("code_coverage", "Collect code coverage", [ 81 | 'debug', 82 | 'installDependencies', 83 | 'lintCmake', 84 | 'lintCpp', 85 | 'makeBuildDirectory', 86 | 'generateProject', 87 | 'codeCoverage', 88 | 'unitTests' 89 | ]) 90 | 91 | options = buildOptions.parseArgs() 92 | buildOptions.verbosePrintBuildOptions(options) 93 | 94 | library_target = 'NFParamTests' 95 | nfbuild = NFBuildOSX() 96 | 97 | if buildOptions.checkOption(options, 'debug'): 98 | nfbuild.build_type = 'Debug' 99 | 100 | if buildOptions.checkOption(options, 'installDependencies'): 101 | nfbuild.installDependencies() 102 | 103 | if buildOptions.checkOption(options, 'lintCmake'): 104 | nfbuild.lintCmake() 105 | 106 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 107 | nfbuild.lintCPP(make_inline_changes=True) 108 | elif buildOptions.checkOption(options, 'lintCpp'): 109 | nfbuild.lintCPP(make_inline_changes=False) 110 | 111 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 112 | nfbuild.makeBuildDirectory() 113 | 114 | if buildOptions.checkOption(options, 'generateProject'): 115 | nfbuild.generateProject( 116 | code_coverage='codeCoverage' in options, 117 | address_sanitizer='addressSanitizer' in options 118 | ) 119 | 120 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 121 | nfbuild.buildTarget(library_target) 122 | 123 | if buildOptions.checkOption(options, 'unitTests'): 124 | nfbuild.runUnitTests() 125 | 126 | if buildOptions.checkOption(options, 'codeCoverage'): 127 | nfbuild.collectCodeCoverage() 128 | if buildOptions.checkOption(options, 'packageArtifacts'): 129 | nfbuild.packageArtifacts() 130 | 131 | if __name__ == "__main__": 132 | main() 133 | -------------------------------------------------------------------------------- /source/WAAParamEvents.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "WAAParamEvents.h" 23 | 24 | #include 25 | 26 | namespace nativeformat { 27 | namespace param { 28 | 29 | ValueAtTimeEvent::ValueAtTimeEvent(float value, double time) 30 | : ParamEvent(time, ParamEvent::INVALID_TIME, Anchor::START) { 31 | ParamEvent::start_value = value; 32 | } 33 | 34 | ValueAtTimeEvent::~ValueAtTimeEvent() {} 35 | 36 | float ValueAtTimeEvent::valueAtTime(double time) { 37 | return start_value; 38 | } 39 | 40 | float ValueAtTimeEvent::cumulativeValue(double start_time, double end_time, double precision) { 41 | // The integral of a delta function is equivalent to the value of the delta 42 | return start_value * (end_time - start_time); 43 | } 44 | 45 | TargetAtTimeEvent::TargetAtTimeEvent(float value, double time, float time_constant) 46 | : ParamEvent(time, ParamEvent::INVALID_TIME, Anchor::START), 47 | time_constant(time_constant), 48 | target(value) {} 49 | 50 | TargetAtTimeEvent::~TargetAtTimeEvent() {} 51 | 52 | float TargetAtTimeEvent::valueAtTime(double time) { 53 | if (time < start_time) { 54 | return start_value; 55 | } 56 | return target + (start_value - target) * std::exp(-1.0 * ((time - start_time) / time_constant)); 57 | } 58 | 59 | float TargetAtTimeEvent::cumulativeValue(double start_time, double end_time, double precision) { 60 | // \int_{t1}^{t2} T + (s - T)*exp(-\frac{x-s}{c})dt = 61 | // Tx - \frac{s-T}{c}exp(-\frac{x-s}{c}) |_{t1}^{t2} 62 | auto integral = [&](const double &time) { 63 | auto exp_arg = -1.0 * (time - start_time) / time_constant; 64 | auto exp_fac = (start_value - target) / time_constant; 65 | return target * time - exp_fac * std::exp(exp_arg); 66 | }; 67 | 68 | return integral(end_time) - integral(start_time) + start_value * (end_time - start_time); 69 | } 70 | 71 | LinearRampEvent::LinearRampEvent(float value, double time) 72 | : ParamEvent(0.0, time, Anchor::END), target(value) {} 73 | LinearRampEvent::~LinearRampEvent() {} 74 | 75 | float LinearRampEvent::valueAtTime(double time) { 76 | if (time < start_time || start_time == end_time) { 77 | return start_value; 78 | } 79 | if (time > end_time) { 80 | return target; 81 | } 82 | double c = (time - start_time) / (end_time - start_time); 83 | return start_value + (target - start_value) * c; 84 | } 85 | 86 | float LinearRampEvent::cumulativeValue(double start_time, double end_time, double precision) { 87 | // int_{t1}^{t2}mx + bdx = 1/2 * mx^2 + bx|_{t1}^{t2} 88 | // neglecting coefficients and initial value on purpose 89 | auto integral = [](const double &time) { return time * time; }; 90 | auto slope = (target - start_value) / (end_time - start_time); 91 | return .5 * slope * (integral(end_time) - integral(start_time)) + 92 | start_value * (end_time - start_time); 93 | } 94 | 95 | ExponentialRampEvent::ExponentialRampEvent(float value, double time) 96 | : ParamEvent(0.0, time, Anchor::END), target(value) {} 97 | 98 | ExponentialRampEvent::~ExponentialRampEvent() {} 99 | 100 | float ExponentialRampEvent::valueAtTime(double time) { 101 | if (time < start_time || start_time == end_time) { 102 | return start_value; 103 | } 104 | if (time > end_time) { 105 | return target; 106 | } 107 | double p = (time - start_time) / (end_time - start_time); 108 | return start_value * std::pow(base(), p); 109 | } 110 | 111 | float ExponentialRampEvent::cumulativeValue(double start_time, double end_time, double precision) { 112 | // int_{t1}^{t2} a^b db = \frac{a^b}{logx} |_{t1}^{t2} 113 | auto integral = [this](const double &time) { return std::pow(this->base(), time); }; 114 | return (integral(end_time) - integral(start_time)) / std::log(base()) + 115 | start_value * (end_time - start_time); 116 | } 117 | 118 | ValueCurveEvent::ValueCurveEvent(const std::vector &values, 119 | double start_time, 120 | double duration) 121 | : ParamEvent(start_time, start_time + duration, Anchor::ALL), 122 | values(values), 123 | duration(duration) { 124 | ParamEvent::start_value = values.front(); 125 | } 126 | 127 | ValueCurveEvent::~ValueCurveEvent() {} 128 | 129 | float ValueCurveEvent::valueAtTime(double time) { 130 | if (time > end_time) { 131 | return values.back(); 132 | } 133 | size_t n = values.size(); 134 | size_t k = std::floor((time - start_time) * (n - 1) / duration); 135 | float v0 = values[k]; 136 | float v1 = values[k + 1]; 137 | return v0 + (v1 - v0) * (time - start_time) / (end_time - start_time); 138 | } 139 | 140 | DummyEvent::DummyEvent(float value) : ParamEvent(0.0, ParamEvent::INVALID_TIME, Anchor::NONE) { 141 | ParamEvent::start_value = value; 142 | } 143 | 144 | DummyEvent::~DummyEvent() {} 145 | 146 | float DummyEvent::valueAtTime(double time) { 147 | return start_value; 148 | } 149 | 150 | } // namespace param 151 | } // namespace nativeformat 152 | -------------------------------------------------------------------------------- /ci/build_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import argparse 5 | 6 | # Argument parsing abstraction designed for the build context. 7 | # Generates an array of buildOptions from an input arg list. 8 | # Allows options to be grouped into workflows, and workflows 9 | # to be overridden with minor changes. 10 | 11 | # Example commands: 12 | 13 | # run the lint workflow with an override to disable dependencies install: 14 | # python build_options.py lint -installDependencies=0 -v 15 | 16 | # run the default workflow with an override to make it fly. 17 | # python build_options.py -doFlyAway=1 -v 18 | 19 | 20 | class BuildOptions: 21 | 22 | def __init__(self): 23 | self.options = {} 24 | self.workflows = {} 25 | self.verbose = True 26 | 27 | # Define a build option, with documentation. 28 | def addOption(self, option, doc): 29 | self.options[option] = doc 30 | 31 | # Define a workflow, which consists of a group of build options. 32 | # This will be an optional single positional argument. 33 | def addWorkflow(self, workflow, doc, options): 34 | for option in options: 35 | if option not in self.options: 36 | self.flushed_print( 37 | "Error: Workflow %s contains invalid option %s" 38 | % (workflow, option)) 39 | exit(1) 40 | self.workflows[workflow] = { 41 | 'doc': doc, 42 | 'options': {x: '1' for x in options} 43 | } 44 | 45 | # Define the default workflow, if the cmdline input doesn't include 46 | # a position workflow arg. 47 | def setDefaultWorkflow(self, doc, options): 48 | self.addWorkflow("default", doc, options) 49 | 50 | def getOptionDoc(self, option): 51 | return self.options[option] 52 | 53 | def getWorkflowHelp(self): 54 | str = "" 55 | for workflow, data in self.workflows.iteritems(): 56 | str += "%s:\n\t%s\n" % (workflow, data['doc']) 57 | return str 58 | 59 | # Parse input arguments 60 | def parseArgs(self): 61 | parser = argparse.ArgumentParser( 62 | formatter_class=argparse.RawTextHelpFormatter) 63 | 64 | # Verbose is automatically defined 65 | parser.add_argument("-quiet", "-q", 66 | help="Mute build steps", 67 | action='store_true') 68 | 69 | # Can have any number of workflows. If 0, it runs the default workflow. 70 | # If more than 1, the built options get intersected 71 | parser.add_argument("workflows", nargs='*', default=['default'], 72 | help=self.getWorkflowHelp()) 73 | 74 | # Define build options with leading - 75 | for k, v in self.options.iteritems(): 76 | parser.add_argument("-" + k, help=v) 77 | args = parser.parse_args() 78 | argHash = vars(args) 79 | result = {} 80 | 81 | if 'quiet' in argHash and argHash['quiet']: 82 | self.verbose = False 83 | 84 | for workflow in argHash['workflows']: 85 | if workflow not in self.workflows: 86 | self.flushed_print("Error: Specified invalid workflow %s" % 87 | workflow) 88 | exit(1) 89 | # Load options from selected workflows 90 | result.update(self.workflows[workflow]['options']) 91 | 92 | # Apply option overrides to workflow 93 | for option, doc in self.options.iteritems(): 94 | if option in argHash and argHash[option]: 95 | result[option] = argHash[option] 96 | 97 | return [k for k, v in result.iteritems() if v == '1'] 98 | 99 | # Print which build options are enabled. 100 | def verbosePrintBuildOptions(self, args): 101 | if self.verbose: 102 | self.flushed_print("Build Options: " + str(args)) 103 | 104 | # Caller should call this when a build option is being executed 105 | # for verbose build logging. 106 | def verbosePrint(self, option): 107 | if self.verbose: 108 | self.flushed_print("===== %s =====" % self.getOptionDoc(option)) 109 | 110 | # Check if this given build option is defined. If so, print the step has 111 | # begun and return true so caller can run the step. 112 | # Sanity check the input option to catch typos. 113 | def checkOption(self, args, arg, quiet=False): 114 | if arg not in self.options: 115 | self.flushed_print("Error: Checked undefined option %s" % arg) 116 | exit(1) 117 | if arg in args: 118 | if not quiet: 119 | self.verbosePrint(arg) 120 | return True 121 | return False 122 | 123 | def flushed_print(self, str): 124 | print str 125 | sys.stdout.flush() 126 | 127 | 128 | # Create a toy version for testing if user executes this file directly 129 | def test_version(): 130 | 131 | buildOptions = BuildOptions() 132 | buildOptions.addOption("option1", "option 1 description") 133 | buildOptions.addOption("option2", "option 2 description") 134 | buildOptions.addOption("option3", "option 3 description") 135 | buildOptions.setDefaultWorkflow("Default workflow description", [ 136 | 'option1' 137 | ]) 138 | buildOptions.addWorkflow("workflow1", "workflow1 description", [ 139 | 'option1', 140 | 'option2' 141 | ]) 142 | buildOptions.addWorkflow("workflow2", "workflow2 description", [ 143 | 'option2', 144 | 'option3' 145 | ]) 146 | 147 | options = buildOptions.parseArgs() 148 | buildOptions.verbosePrintBuildOptions(options) 149 | 150 | if buildOptions.checkOption(options, "option1"): 151 | buildOptions.flushed_print("running option1") 152 | 153 | if buildOptions.checkOption(options, "option2"): 154 | buildOptions.flushed_print("running option2") 155 | 156 | if buildOptions.checkOption(options, "option3"): 157 | buildOptions.flushed_print("running option3") 158 | 159 | 160 | if __name__ == "__main__": 161 | test_version() 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NFParam 2 | 3 | [![CircleCI](https://circleci.com/gh/spotify/NFParam/tree/master.svg?style=svg)](https://circleci.com/gh/spotify/NFParam/tree/master) 4 | [![License](https://img.shields.io/github/license/spotify/NFParam.svg)](LICENSE) 5 | [![Spotify FOSS Slack](https://slackin.spotify.com/badge.svg)](https://slackin.spotify.com) 6 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/spotify/nfparam)](http://clayallsopp.github.io/readme-score?url=https://github.com/spotify/nfparam) 7 | 8 | A C++ library for defining and evaluating piecewise functions, inspired by the Web Audio API [AudioParam](https://webaudio.github.io/web-audio-api/#AudioParam) interface. 9 | 10 | ## Platform support 11 | `NFParam` should compile and run on any platform with a C++11 compiler, but the following are tested in CI. 12 | 13 | - [x] 📱 iOS 9.0+ 14 | - [x] 💻 OS X 10.11+ 15 | - [x] 🐧 Ubuntu Trusty 14.04+ (clang 3.9 or gcc 4.9) 16 | 17 | Developed at Spotify 2019-2022, Discontinued and handed over to new maintainers January 2023 18 | 19 | ## Raison D'être :thought_balloon: 20 | When designing a cross platform player that could be used for complex mixing and effects, we required a library that worked in the same way that the Web Audio API [AudioParam](https://webaudio.github.io/web-audio-api/#AudioParam) worked but on none web based platforms. This led to the creation of this library, which is not only able to emulate the [AudioParam](https://webaudio.github.io/web-audio-api/#AudioParam) library but can also handle seeks into the centre of a function being evaluated due to its architecture not being a state machine. In addition to supporting everything [AudioParam](https://webaudio.github.io/web-audio-api/#AudioParam) supports, we have also added in some extra goodies such as `smoothedValueForTimeRange` and `cumulativeValueForTimeRange`. 21 | 22 | ## Architecture :triangular_ruler: 23 | `NFParam` is designed as a C++11 interface to define a control curve and interact with it in real time. The API allows you to create a parameter and then begin to add control curves to execute at specific times. The library is thread safe and can be written or read from any thread. The system works by having a list of events, doing a binary search on that list to find the correct function to execute, then executing that function on the current time being requested. 24 | 25 | ## Installation 26 | CMake 3.5 or later is required to generate the build. 27 | ```shell 28 | brew install cmake 29 | ``` 30 | or 31 | ```shell 32 | sudo apt-get install cmake 33 | ``` 34 | 35 | `NFParam` is a [CMake](https://cmake.org/) project, so to use it within a larger CMake project, simply add the following line to your `CMakeLists.txt` file: 36 | ``` 37 | add_subdirectory(NFParam) 38 | ``` 39 | Alternatively, you can compile NFParam as a standalone library. 40 | The [ci build scripts](./ci) can be used to install all necessary dependencies, build the library, and run unit tests. 41 | 42 | ### For Linux 43 | ```shell 44 | sh ci/linux.sh build 45 | ``` 46 | 47 | ### For iOS/OSX 48 | ```shell 49 | sh ci/osx.sh build 50 | ``` 51 | 52 | ## Examples 53 | 54 | ### Create the "Audio Param Example" curve pictured below 55 | 56 | #### Create the `Param` 57 | Specify the default value, min, max, and a name. 58 | Until events are added, its value will be the default value for all times. 59 | ``` 60 | auto p = nativeformat::param::createParam(0, 1, -1, "testParam"); 61 | ``` 62 | 63 | #### Add some events 64 | The `setValueAtTime` command behaves as a step function. 65 | The value specified will be maintained until the next event anchor. 66 | ``` 67 | p->setValueAtTime(0.2f, 0.0); 68 | p->setValueAtTime(0.3f, 0.1); 69 | p->setValueAtTime(0.4f, 0.2); 70 | ``` 71 | 72 | The linearRampToValueAtTime event computes the slope necessary to 73 | reach the target value from the previous anchor point by the specified time. 74 | ``` 75 | p->linearRampToValueAtTime(1.0f, 0.3); 76 | p->linearRampToValueAtTime(0.8f, 0.325); 77 | ``` 78 | 79 | The setTargetAtTime command will expoenentially approach the target value 80 | from the previous anchor with a rate specified by the time constant. 81 | It does not have a fixed end time. 82 | ``` 83 | p->setTargetAtTime(0.5f, 0.325, time_constant); 84 | p->setValueAtTime(0.552f, 0.5); 85 | ``` 86 | 87 | The `exponentialRampToValueAtTime` event computes the constant necessary to 88 | reach the target value from the previous anchor point by the specified time. 89 | ``` 90 | p->exponentialRampToValueAtTime(0.75f, 0.6); 91 | p->exponentialRampToValueAtTime(0.05f, 0.7); 92 | ``` 93 | 94 | The `setValueCurveAtTime` event linearly interpolates between the points provided on the user-defined curve. 95 | It cannot overlap with any other command anchors. 96 | ``` 97 | size_t curve_len = 44100; 98 | std::vector curve(curve_len); 99 | for (int i = 0; i < curve_len; ++i) { 100 | curve[i] = std::sin((3.14159265 * i) / curve_len); 101 | } 102 | p->setValueCurveAtTime(curve, 0.7, 0.3); 103 | ``` 104 | #### Retrieve some values from the `Param` 105 | Finally, let's sample some values from the param we have defined! 106 | ``` 107 | size_t points = 1001; 108 | std::vector x(points), y(points); 109 | p->valuesForTimeRange(y.data(), points, 0.0, 1.0); 110 | 111 | double ts = 1.0 / (points - 1); 112 | double t = 0; 113 | for (int i = 0; i < x.size(); ++i) { 114 | x[i] = t; 115 | t += ts; 116 | } 117 | ``` 118 | 119 | ## Tests 120 | [`NFParamTests.cpp`](source/test/NFParamTests.cpp) contains a number of test cases, 121 | two of which generate TSV output files that should match the 122 | [AudioParam automation example](https://webaudio.github.io/web-audio-api/#example1-AudioParam) 123 | from the WAA spec. In the `resources` directory, there is a [script](resources/plot.sh) that will plot the unit test output data 124 | if you have [gnuplot](http://gnuplot.info/) installed. The path where `paramAutomation.txt` is generated when the tests are run by the 125 | ci scripts varies by platform. 126 | 127 | ### Generating a plot 128 | ``` 129 | sh plot.sh ../build/source/test/Debug/paramAutomation.txt out.png 130 | ``` 131 | ![](resources/paramAutomationExpected.png?raw=true) 132 | 133 | ## Contributing :mailbox_with_mail: 134 | Contributions are welcomed, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. 135 | 136 | ## License :memo: 137 | The project is available under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) license. 138 | 139 | ### Acknowledgements 140 | - Icon in readme banner is “[Settings](https://thenounproject.com/search/?q=parameter&i=1477820)” by Bharat from the Noun Project. 141 | 142 | #### Contributors 143 | * [Julia Cox](https://github.com/astrocox) 144 | * [Justin Sarma](https://github.com/jsarma) 145 | * [David Rubinstein](https://github.com/drubinstein) 146 | * [Will Sackfield](https://github.com/8W9aG) 147 | -------------------------------------------------------------------------------- /ci/nfbuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import pprint 26 | import shutil 27 | import subprocess 28 | import sys 29 | import yaml 30 | 31 | 32 | class NFBuild(object): 33 | def __init__(self): 34 | ci_yaml_file = os.path.join('ci', 'ci.yaml') 35 | self.build_configuration = yaml.load(open(ci_yaml_file, 'r')) 36 | self.build_type = 'Release' 37 | self.pretty_printer = pprint.PrettyPrinter(indent=4) 38 | self.current_working_directory = os.getcwd() 39 | self.build_directory = 'build' 40 | self.output_directory = os.path.join(self.build_directory, 'output') 41 | self.statically_analyzed_files = [] 42 | 43 | def build_print(self, print_string): 44 | print print_string 45 | sys.stdout.flush() 46 | 47 | def makeBuildDirectory(self): 48 | if os.path.exists(self.build_directory): 49 | shutil.rmtree(self.build_directory) 50 | os.makedirs(self.build_directory) 51 | os.makedirs(self.output_directory) 52 | 53 | def installDependencies(self): 54 | pass 55 | 56 | def generateProject(self, 57 | code_coverage=False, 58 | electron_build=False, 59 | address_sanitizer=False, 60 | thread_sanitizer=False, 61 | undefined_behaviour_sanitizer=False, 62 | ios=False, 63 | gcc=False): 64 | assert True, "generateProject should be overridden by subclass" 65 | 66 | def buildTarget(self, target, sdk='macosx'): 67 | assert True, "buildTarget should be overridden by subclass" 68 | 69 | def lintCPPFile(self, filepath, make_inline_changes=False): 70 | current_source = open(filepath, 'r').read() 71 | clang_format_call = [self.clang_format_binary, '-style=file'] 72 | if make_inline_changes: 73 | clang_format_call.append('-i') 74 | clang_format_call.append(filepath) 75 | new_source = subprocess.check_output(clang_format_call) 76 | if current_source != new_source and not make_inline_changes: 77 | self.build_print( 78 | filepath + " failed C++ lint, file should look like:") 79 | self.build_print(new_source) 80 | return False 81 | return True 82 | 83 | def lintCPPDirectory(self, directory, make_inline_changes=False): 84 | passed = True 85 | for root, dirnames, filenames in os.walk(directory): 86 | for filename in filenames: 87 | if not filename.endswith(('.cpp', '.h', '.m', '.mm')): 88 | continue 89 | full_filepath = os.path.join(root, filename) 90 | if not self.lintCPPFile(full_filepath, make_inline_changes): 91 | passed = False 92 | return passed 93 | 94 | def lintCPP(self, make_inline_changes=False): 95 | lint_result = self.lintCPPDirectory('source', make_inline_changes) 96 | lint_result &= self.lintCPPDirectory('include', make_inline_changes) 97 | if not lint_result: 98 | sys.exit(1) 99 | 100 | def lintCmakeFile(self, filepath): 101 | self.build_print("Linting: " + filepath) 102 | return subprocess.call(['cmakelint', filepath]) == 0 103 | 104 | def lintCmakeDirectory(self, directory): 105 | passed = True 106 | for root, dirnames, filenames in os.walk(directory): 107 | for filename in filenames: 108 | if not filename.endswith('CMakeLists.txt'): 109 | continue 110 | full_filepath = os.path.join(root, filename) 111 | if not self.lintCmakeFile(full_filepath): 112 | passed = False 113 | return passed 114 | 115 | def targetBinary(self, target): 116 | assert True, "targetBinary should be overridden by subclass" 117 | 118 | def lintCmake(self): 119 | lint_result = self.lintCmakeFile('CMakeLists.txt') 120 | lint_result &= self.lintCmakeDirectory('source') 121 | if not lint_result: 122 | sys.exit(1) 123 | 124 | def runTarget(self, target): 125 | target_file = self.targetBinary(target) 126 | target_result = subprocess.call([target_file]) 127 | if target_result: 128 | sys.exit(target_result) 129 | 130 | def runUnitTests(self): 131 | for unit_test_target in self.build_configuration['unit_tests']: 132 | self.buildTarget(unit_test_target) 133 | self.runTarget(unit_test_target) 134 | 135 | def collectCodeCoverage(self): 136 | for root, dirnames, filenames in os.walk('build'): 137 | for filename in fnmatch.filter(filenames, '*.gcda'): 138 | full_filepath = os.path.join(root, filename) 139 | if full_filepath.startswith('build/source/') and \ 140 | '/test/' not in full_filepath and \ 141 | '/usr/include/c++/' not in full_filepath: 142 | continue 143 | os.remove(full_filepath) 144 | llvm_run_script = os.path.join( 145 | os.path.join( 146 | self.current_working_directory, 147 | 'ci'), 148 | 'llvm-run.sh') 149 | cov_info = os.path.join('build', 'cov.info') 150 | filtered_cov_info = os.path.join('build', 'filtered_cov.info') 151 | 152 | # Run lcov on filtered info 153 | lcov_result = subprocess.call([ 154 | 'lcov', 155 | '--directory', 156 | '.', 157 | '--base-directory', 158 | '.', 159 | '--gcov-tool', 160 | llvm_run_script, 161 | '--capture', 162 | '-o', 163 | cov_info]) 164 | if lcov_result: 165 | sys.exit(lcov_result) 166 | 167 | # Remove stdlib headers from files to check 168 | lcov_result = subprocess.call([ 169 | 'lcov', 170 | '--remove', 171 | cov_info, 172 | '*/usr/include/c++/*', 173 | '-o', 174 | filtered_cov_info]) 175 | if lcov_result: 176 | sys.exit(lcov_result) 177 | 178 | coverage_output = os.path.join(self.output_directory, 'code_coverage') 179 | genhtml_result = subprocess.call([ 180 | 'genhtml', 181 | filtered_cov_info, 182 | '-o', 183 | coverage_output]) 184 | if genhtml_result: 185 | sys.exit(genhtml_result) 186 | 187 | def packageArtifacts(self): 188 | assert True, "packageArtifacts should be overridden by subclass" 189 | 190 | def make_archive(self, source, destination): 191 | base = os.path.basename(destination) 192 | name = base.split('.')[0] 193 | format = base.split('.')[1] 194 | archive_from = os.path.dirname(source) 195 | archive_to = os.path.basename(source.strip(os.sep)) 196 | print(source, destination, archive_from, archive_to) 197 | shutil.make_archive(name, format, archive_from, archive_to) 198 | shutil.move('%s.%s'%(name,format), destination) 199 | 200 | def find_file(self, directory, file_name, multiple_files=False): 201 | matches = [] 202 | for root, dirnames, filenames in os.walk(directory): 203 | for filename in fnmatch.filter(filenames, file_name): 204 | matches.append(os.path.join(root, filename)) 205 | if not multiple_files: 206 | break 207 | if not multiple_files and len(matches) > 0: 208 | break 209 | return matches 210 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # This file was generated with clang-format 4.0.0 2 | # $ clang-format -style Google -dump-config > .clang-format 3 | # 4 | # Read all about the available options here: 5 | # http://releases.llvm.org/4.0.0/tools/clang/docs/ClangFormatStyleOptions.html 6 | # 7 | # Spotify specific tweaks: 8 | # 9 | # --- 10 | # Language: Cpp 11 | # 12 | # - Standard Auto -> Cpp11 13 | # A> instead of A > 14 | # 15 | # - ColumnLimit 80 -> 100 16 | # We allow up to 100 characters per line 17 | # 18 | # - PointerAlignment Left -> Right 19 | # Always put '*' and '&' close to variable/function 20 | # Guidelines allows both alignments, but we want right (for legacy reasons) 21 | # 22 | # - DerivePointerAlignment: true -> false 23 | # Always put '*' and '&' close to variable/function 24 | # Guidelines allows both alignments, but we want right (for legacy reasons) 25 | # 26 | # - AllowShortFunctionsOnASingleLine: All -> Inline 27 | # We don't want to put out of class function definitions on a single line. 28 | # Standard allows it, but we prefer to keep the oneliners for methods inside classes. 29 | # 30 | # - BinPackArguments: true -> false 31 | # A function declaration’s or function definition’s parameters will either all be on the same 32 | # line or will have one line each. 33 | # Guidelines allows both true and false, but we like false better so we prefer that. 34 | # 35 | # - BinPackParameters: true -> false 36 | # A function call’s arguments will either be all on the same line or will have one line each. 37 | # Guidelines allows both true and false, but we like false better so we prefer that. 38 | # 39 | # - ForEachMacros: Remove all listed macros 40 | # We don't use foreach macros so clang-format shouldn't special treat any keywords. 41 | # 42 | # - IncludeCategories: 43 | # Tweaked priorities to match our preferred order. 44 | # 45 | # - ObjCSpaceAfterProperty: false -> true 46 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 47 | # 48 | # - ObjCSpaceBeforeProtocolList: false -> true 49 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 50 | # 51 | # --- 52 | # Language: ObjC 53 | # 54 | # Hand tweaked config that aims to match Spotifys Objective-C coding style, 55 | # see https://github.com/spotify/ios-style. 56 | # 57 | --- 58 | Language: Cpp 59 | # BasedOnStyle: Google 60 | AccessModifierOffset: -1 61 | AlignAfterOpenBracket: Align 62 | AlignConsecutiveAssignments: false 63 | AlignConsecutiveDeclarations: false 64 | AlignEscapedNewlinesLeft: true 65 | AlignOperands: true 66 | AlignTrailingComments: true 67 | AllowAllParametersOfDeclarationOnNextLine: true 68 | AllowShortBlocksOnASingleLine: false 69 | AllowShortCaseLabelsOnASingleLine: false 70 | AllowShortFunctionsOnASingleLine: Inline 71 | AllowShortIfStatementsOnASingleLine: true 72 | AllowShortLoopsOnASingleLine: true 73 | AlwaysBreakAfterDefinitionReturnType: None 74 | AlwaysBreakAfterReturnType: None 75 | AlwaysBreakBeforeMultilineStrings: true 76 | AlwaysBreakTemplateDeclarations: true 77 | BinPackArguments: false 78 | BinPackParameters: false 79 | # Note: BraceWrapping is ignored since BreakBeforeBraces isn't set to Custom 80 | BraceWrapping: 81 | AfterClass: false 82 | AfterControlStatement: false 83 | AfterEnum: false 84 | AfterFunction: false 85 | AfterNamespace: false 86 | AfterObjCDeclaration: false 87 | AfterStruct: false 88 | AfterUnion: false 89 | BeforeCatch: false 90 | BeforeElse: false 91 | IndentBraces: false 92 | BreakBeforeBinaryOperators: None 93 | BreakBeforeBraces: Attach 94 | BreakBeforeTernaryOperators: true 95 | BreakConstructorInitializersBeforeComma: false 96 | BreakAfterJavaFieldAnnotations: false 97 | BreakStringLiterals: true 98 | ColumnLimit: 100 99 | CommentPragmas: '^ IWYU pragma:' 100 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 101 | ConstructorInitializerIndentWidth: 4 102 | ContinuationIndentWidth: 4 103 | Cpp11BracedListStyle: true 104 | DerivePointerAlignment: false 105 | DisableFormat: false 106 | ExperimentalAutoDetectBinPacking: false 107 | ForEachMacros: [] 108 | IncludeCategories: 109 | - Regex: '^' 110 | Priority: 3 111 | - Regex: '^' 112 | Priority: 4 113 | - Regex: '^["<]base/.*.h[">]' 114 | Priority: 5 115 | - Regex: '^".*"' 116 | Priority: 6 117 | - Regex: '^<[a-z_]*>' 118 | Priority: 1 119 | - Regex: '^<.*>' 120 | Priority: 2 121 | IncludeIsMainRegex: '([-_](test|unittest))?$' 122 | IndentCaseLabels: true 123 | IndentWidth: 2 124 | IndentWrappedFunctionNames: false 125 | JavaScriptQuotes: Leave 126 | JavaScriptWrapImports: true 127 | KeepEmptyLinesAtTheStartOfBlocks: false 128 | MacroBlockBegin: '' 129 | MacroBlockEnd: '' 130 | MaxEmptyLinesToKeep: 1 131 | NamespaceIndentation: None 132 | ObjCBlockIndentWidth: 2 133 | ObjCSpaceAfterProperty: true 134 | ObjCSpaceBeforeProtocolList: true 135 | PenaltyBreakBeforeFirstCallParameter: 1 136 | PenaltyBreakComment: 300 137 | PenaltyBreakFirstLessLess: 120 138 | PenaltyBreakString: 1000 139 | PenaltyExcessCharacter: 1000000 140 | PenaltyReturnTypeOnItsOwnLine: 200 141 | PointerAlignment: Right 142 | ReflowComments: true 143 | SortIncludes: true 144 | SpaceAfterCStyleCast: false 145 | SpaceAfterTemplateKeyword: true 146 | SpaceBeforeAssignmentOperators: true 147 | SpaceBeforeParens: ControlStatements 148 | SpaceInEmptyParentheses: false 149 | SpacesBeforeTrailingComments: 2 150 | SpacesInAngles: false 151 | SpacesInCStyleCastParentheses: false 152 | SpacesInContainerLiterals: true 153 | SpacesInParentheses: false 154 | SpacesInSquareBrackets: false 155 | Standard: Cpp11 156 | TabWidth: 8 157 | UseTab: Never 158 | 159 | # 160 | # Objective-C 161 | # 162 | --- 163 | Language: ObjC 164 | # BasedOnStyle: Google 165 | AccessModifierOffset: -1 166 | AlignAfterOpenBracket: Align 167 | AlignConsecutiveAssignments: false 168 | AlignConsecutiveDeclarations: false 169 | AlignEscapedNewlinesLeft: true 170 | AlignOperands: true 171 | AlignTrailingComments: true 172 | AllowAllParametersOfDeclarationOnNextLine: true 173 | AllowShortBlocksOnASingleLine: false 174 | AllowShortCaseLabelsOnASingleLine: false 175 | AllowShortFunctionsOnASingleLine: false 176 | AllowShortIfStatementsOnASingleLine: false 177 | AllowShortLoopsOnASingleLine: false 178 | AlwaysBreakAfterDefinitionReturnType: None 179 | AlwaysBreakAfterReturnType: None 180 | AlwaysBreakBeforeMultilineStrings: true 181 | AlwaysBreakTemplateDeclarations: true 182 | BinPackArguments: false 183 | BinPackParameters: false 184 | BraceWrapping: 185 | AfterClass: false 186 | AfterControlStatement: false 187 | AfterEnum: false 188 | AfterFunction: true 189 | AfterNamespace: false 190 | AfterObjCDeclaration: false 191 | AfterStruct: false 192 | AfterUnion: false 193 | BeforeCatch: false 194 | BeforeElse: false 195 | IndentBraces: false 196 | BreakBeforeBinaryOperators: None 197 | BreakBeforeBraces: Custom 198 | BreakBeforeTernaryOperators: true 199 | BreakConstructorInitializersBeforeComma: false 200 | BreakAfterJavaFieldAnnotations: false 201 | BreakStringLiterals: true 202 | ColumnLimit: 120 203 | CommentPragmas: '^ IWYU pragma:' 204 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 205 | ConstructorInitializerIndentWidth: 4 206 | ContinuationIndentWidth: 4 207 | Cpp11BracedListStyle: true 208 | DerivePointerAlignment: false 209 | DisableFormat: false 210 | ExperimentalAutoDetectBinPacking: false 211 | ForEachMacros: [] 212 | IncludeCategories: 213 | - Regex: '^' 214 | Priority: 3 215 | - Regex: '^' 216 | Priority: 4 217 | - Regex: '^["<]base/.*.h[">]' 218 | Priority: 5 219 | - Regex: '^".*"' 220 | Priority: 6 221 | - Regex: '^<[a-z_]*>' 222 | Priority: 1 223 | - Regex: '^<.*>' 224 | Priority: 2 225 | IncludeIsMainRegex: '([-_](test|unittest))?$' 226 | IndentCaseLabels: true 227 | IndentWidth: 4 228 | IndentWrappedFunctionNames: false 229 | JavaScriptQuotes: Leave 230 | JavaScriptWrapImports: true 231 | KeepEmptyLinesAtTheStartOfBlocks: false 232 | MacroBlockBegin: '' 233 | MacroBlockEnd: '' 234 | MaxEmptyLinesToKeep: 1 235 | NamespaceIndentation: None 236 | ObjCBlockIndentWidth: 4 237 | ObjCSpaceAfterProperty: true 238 | ObjCSpaceBeforeProtocolList: true 239 | PenaltyBreakBeforeFirstCallParameter: 1 240 | PenaltyBreakComment: 300 241 | PenaltyBreakFirstLessLess: 120 242 | PenaltyBreakString: 1000 243 | PenaltyExcessCharacter: 1000000 244 | PenaltyReturnTypeOnItsOwnLine: 200 245 | PointerAlignment: Right 246 | ReflowComments: true 247 | SortIncludes: true 248 | SpaceAfterCStyleCast: false 249 | SpaceAfterTemplateKeyword: true 250 | SpaceBeforeAssignmentOperators: true 251 | SpaceBeforeParens: ControlStatements 252 | SpaceInEmptyParentheses: false 253 | SpacesBeforeTrailingComments: 2 254 | SpacesInAngles: false 255 | SpacesInCStyleCastParentheses: false 256 | SpacesInContainerLiterals: true 257 | SpacesInParentheses: false 258 | SpacesInSquareBrackets: false 259 | Standard: Cpp11 260 | TabWidth: 4 261 | UseTab: Never 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019-2022, Spotify AB 191 | Copyright 2023, New Maintainers 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /source/ParamImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "ParamImplementation.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace param { 29 | 30 | ParamImplementation::ParamImplementation(float default_value, 31 | float max_value, 32 | float min_value, 33 | const std::string &name) 34 | : _default_value(default_value), _max_value(max_value), _min_value(min_value), _name(name) { 35 | _events.push_back(createEvent(default_value)); 36 | } 37 | 38 | ParamImplementation::~ParamImplementation() {} 39 | 40 | float ParamImplementation::valueForTime(double time) { 41 | std::lock_guard events_mutex(_events_mutex); 42 | auto current_iterator = iteratorForTime(time); 43 | 44 | if (current_iterator == _events.end()) { 45 | return defaultValue(); 46 | } 47 | return std::min(std::max((*current_iterator)->valueAtTime(time), minValue()), maxValue()); 48 | } 49 | 50 | void ParamImplementation::valuesForTimeRange(float *values, 51 | size_t values_count, 52 | double start_time, 53 | double end_time) { 54 | if (values_count == 0) { 55 | return; 56 | } 57 | if (start_time == end_time) { 58 | float value = valueForTime(start_time); 59 | for (int i = 0; i < values_count; ++i) { 60 | values[i] = value; 61 | } 62 | return; 63 | } 64 | std::lock_guard events_mutex(_events_mutex); 65 | memset(values, 0, values_count * sizeof(float)); 66 | auto event_it = iteratorForTime(start_time); 67 | double step = (end_time - start_time) / (values_count - 1); 68 | double current_time = start_time; 69 | for (int i = 0; i < values_count; ++i, current_time += step) { 70 | if (event_it != _events.end() && current_time >= (*event_it)->end_time && 71 | (*event_it)->end_time != ParamEvent::INVALID_TIME && event_it != _events.end()) { 72 | event_it++; 73 | } 74 | values[i] = 75 | (event_it == _events.end()) ? defaultValue() : (*event_it)->valueAtTime(current_time); 76 | } 77 | } 78 | 79 | std::string ParamImplementation::name() { 80 | return _name; 81 | } 82 | 83 | float ParamImplementation::smoothedValueForTimeRange(double start_time, 84 | double end_time, 85 | size_t samples) { 86 | if (_smoothed_samples_buffer.size() < samples) { 87 | _smoothed_samples_buffer.resize(samples); 88 | } 89 | valuesForTimeRange(&_smoothed_samples_buffer[0], samples, start_time, end_time); 90 | float average_value = 0.0f; 91 | for (size_t i = 0; i < samples; ++i) { 92 | average_value += _smoothed_samples_buffer[i]; 93 | } 94 | float output_value = average_value / static_cast(samples); 95 | return output_value; 96 | } 97 | 98 | float ParamImplementation::cumulativeValueForTimeRange(double start_time, 99 | double end_time, 100 | double precision) { 101 | auto param_iter = iteratorForTime(start_time); 102 | auto end_iter = iteratorForTime(end_time); 103 | 104 | if (param_iter == _events.end()) { 105 | return 0; 106 | } 107 | 108 | // special case where the whole time range is covered by one event 109 | if (param_iter == end_iter) 110 | return param_iter->get()->cumulativeValue(start_time, end_time, precision); 111 | 112 | // start with cumulative value in the start region 113 | float cumulative_value = 0.0f; 114 | auto current_end_time = param_iter->get()->end_time; 115 | 116 | if (current_end_time != ParamEvent::INVALID_TIME) { 117 | cumulative_value += param_iter->get()->cumulativeValue(start_time, current_end_time, precision); 118 | } 119 | 120 | // now get everything between the start event and the second to last event 121 | for (++param_iter; param_iter != end_iter; ++param_iter) { 122 | cumulative_value += param_iter->get()->cumulativeValue( 123 | param_iter->get()->start_time, param_iter->get()->end_time, precision); 124 | } 125 | 126 | // now get remainder 127 | if (param_iter != _events.end()) { 128 | cumulative_value += 129 | param_iter->get()->cumulativeValue(param_iter->get()->start_time, end_time, precision); 130 | } 131 | 132 | // if we went past the last event, use default 133 | if (current_end_time < end_time) { 134 | cumulative_value += (end_time - current_end_time) * defaultValue(); 135 | } 136 | 137 | return cumulative_value; 138 | } 139 | 140 | float ParamImplementation::defaultValue() const { 141 | return _default_value; 142 | } 143 | 144 | float ParamImplementation::maxValue() const { 145 | return _max_value; 146 | } 147 | 148 | float ParamImplementation::minValue() const { 149 | return _min_value; 150 | } 151 | 152 | void ParamImplementation::setValue(float value) { 153 | setValueAtTime(value, 0.0); 154 | } 155 | 156 | void ParamImplementation::setValueAtTime(float value, double time) { 157 | std::lock_guard events_mutex(_events_mutex); 158 | auto prev_it = prevEvent(time); 159 | auto event = createEvent(value, time); 160 | addEvent(std::move(event), prev_it); 161 | } 162 | 163 | void ParamImplementation::linearRampToValueAtTime(float end_value, double end_time) { 164 | { 165 | std::lock_guard events_mutex(_events_mutex); 166 | auto prev_it = prevEvent(end_time); 167 | auto event = createEvent(end_value, end_time); 168 | addEvent(std::move(event), prev_it); 169 | } 170 | 171 | // implicit setValueAtTime to maintain the end_value 172 | setValueAtTime(end_value, end_time); 173 | } 174 | 175 | void ParamImplementation::exponentialRampToValueAtTime(float end_value, double end_time) { 176 | { 177 | std::lock_guard events_mutex(_events_mutex); 178 | auto prev_it = prevEvent(end_time); 179 | auto event = createEvent(end_value, end_time); 180 | addEvent(std::move(event), prev_it); 181 | } 182 | 183 | // implicit setValueAtTime to maintain the end_value 184 | setValueAtTime(end_value, end_time); 185 | } 186 | 187 | void ParamImplementation::setTargetAtTime(float target, double start_time, float time_constant) { 188 | std::lock_guard events_mutex(_events_mutex); 189 | auto prev_it = prevEvent(start_time); 190 | auto event = createEvent(target, start_time, time_constant); 191 | if (prev_it != _events.end()) { 192 | event->start_value = (*prev_it)->endValue(); 193 | } 194 | addEvent(std::move(event), prev_it); 195 | } 196 | 197 | void ParamImplementation::setValueCurveAtTime(std::vector values, 198 | double start_time, 199 | double duration) { 200 | std::lock_guard events_mutex(_events_mutex); 201 | auto prev_it = prevEvent(start_time); 202 | auto event = createEvent(values, start_time, duration); 203 | addEvent(std::move(event), prev_it); 204 | } 205 | 206 | void ParamImplementation::addCustomEvent(double start_time, 207 | double end_time, 208 | Anchor anchor, 209 | NF_AUDIO_PARAM_FUNCTION function) { 210 | std::lock_guard events_mutex(_events_mutex); 211 | auto prev_it = prevEvent(start_time); 212 | auto event = createEvent(start_time, end_time, anchor, function); 213 | addEvent(std::move(event), prev_it); 214 | } 215 | 216 | std::list>::iterator ParamImplementation::iteratorForTime(double time) { 217 | if (time < 0.0) { 218 | return _events.end(); 219 | } 220 | for (auto it = _events.begin(); it != _events.end(); it++) { 221 | EVENT_PTR &event = *it; 222 | if ((event->end_time > time || event->end_time == ParamEvent::INVALID_TIME) && 223 | event->start_time <= time) { 224 | return it; 225 | } 226 | } 227 | return _events.end(); 228 | } 229 | 230 | std::list>::iterator ParamImplementation::prevEvent(double time) { 231 | auto prev_it = _events.end(); 232 | double prev_time = 0.0; 233 | for (auto it = _events.begin(); it != _events.end(); it++) { 234 | double t = ((*it)->anchor & Anchor::END) == Anchor::END ? (*it)->end_time : (*it)->start_time; 235 | if (t >= prev_time && t <= time) { 236 | prev_time = t; 237 | prev_it = it; 238 | } 239 | } 240 | return prev_it; 241 | } 242 | 243 | bool ParamImplementation::getRequiredTimeRange(const EVENT_PTR &event, double &start, double &end) { 244 | if (event->anchor == Anchor::NONE) { 245 | return false; 246 | } 247 | start = ((event->anchor & Anchor::START) == Anchor::START) ? event->start_time : event->end_time; 248 | end = ((event->anchor & Anchor::END) == Anchor::END) ? event->end_time : event->start_time; 249 | return true; 250 | } 251 | 252 | void ParamImplementation::checkOverlap(const EVENT_PTR &event) { 253 | double s1, e1, s2, e2; 254 | if (!getRequiredTimeRange(event, s1, e1)) { 255 | return; 256 | } 257 | for (const auto &e : _events) { 258 | if (getRequiredTimeRange(e, s2, e2)) { 259 | if (s1 < e2 && s2 < e1) { 260 | std::stringstream msg; 261 | msg << "New event with required time range " << s1 << " - " << e1 262 | << " conflicts with existing event with required time range " << s2 << " - " << e2; 263 | throw std::invalid_argument(msg.str()); 264 | } 265 | } 266 | } 267 | } 268 | 269 | void ParamImplementation::updateTimes(EVENT_PTR &prev, EVENT_PTR &event) { 270 | Anchor prev_anchor = prev->anchor; 271 | if ((event->anchor & Anchor::START) == Anchor::NONE) { 272 | // If event does not have a fixed start, get it from previous event 273 | double prev_anchor_time = 0.0; 274 | if ((prev_anchor & Anchor::END) == Anchor::END) { 275 | prev_anchor_time = prev->end_time; 276 | } else { 277 | // New event has no fixed start and previous event has no fixed end 278 | // set duration of previous event to 0 and steal its end value 279 | prev_anchor_time = prev->start_time; 280 | prev->end_time = prev_anchor_time; 281 | event->start_value = prev->endValue(); 282 | } 283 | event->start_time = prev_anchor_time; 284 | } else { 285 | // If event does have a fixed start, give it to previous event 286 | if ((prev_anchor & Anchor::END) == Anchor::NONE) { 287 | prev->end_time = event->start_time; 288 | } 289 | } 290 | } 291 | 292 | void ParamImplementation::addEvent(EVENT_PTR new_event, std::list::iterator prev_event) { 293 | checkOverlap(new_event); 294 | 295 | auto next_event = prev_event; 296 | if (prev_event != _events.end()) { 297 | updateTimes(*prev_event, new_event); 298 | ++next_event; 299 | if (next_event != _events.end()) { 300 | updateTimes(new_event, *next_event); 301 | } 302 | } 303 | invalidateCachedCumulativeValuesAfterTime(new_event->start_time); 304 | _events.insert(next_event, std::move(new_event)); 305 | } 306 | 307 | void ParamImplementation::invalidateCachedCumulativeValuesAfterTime(double time) { 308 | for (auto &precision_map_pair : _cumulative_values_cache) { 309 | auto &precision_map = precision_map_pair.second; 310 | for (auto &map_pair : precision_map) { 311 | if (map_pair.first >= time) { 312 | precision_map.erase(map_pair.first); 313 | } 314 | } 315 | } 316 | } 317 | 318 | std::shared_ptr createParam(float default_value, 319 | float max_value, 320 | float min_value, 321 | const std::string &name) { 322 | return std::make_shared(default_value, max_value, min_value, name); 323 | } 324 | 325 | } // namespace param 326 | } // namespace nativeformat 327 | -------------------------------------------------------------------------------- /source/test/NFParamTests.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include 4 | #include 5 | #include "../source/WAAParamEvents.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | TEST_CASE("order of setValueAtTime commands should not matter") { 15 | auto p1 = nativeformat::param::createParam(0, 1, -1, "testParam1"); 16 | p1->setValueAtTime(0.25f, 1.0); 17 | p1->setValueAtTime(0.5f, 2.0); 18 | p1->setValueAtTime(0.75f, 3.0); 19 | 20 | auto p2 = nativeformat::param::createParam(0, 1, -1, "testParam2"); 21 | p2->setValueAtTime(0.75f, 3.0); 22 | p2->setValueAtTime(0.25f, 1.0); 23 | p2->setValueAtTime(0.5f, 2.0); 24 | 25 | const size_t value_count = 8; 26 | std::vector v1(value_count), v2(value_count); 27 | p1->valuesForTimeRange(v1.data(), value_count, 0.0, 4.0); 28 | p2->valuesForTimeRange(v2.data(), value_count, 0.0, 4.0); 29 | 30 | // check that v1 values are correct 31 | std::vector expected_vals{0.0f, 0.0f, 0.25f, 0.25f, 0.5f, 0.5f, .75f, .75f}; 32 | for (int i = 0; i < value_count; ++i) { 33 | CHECK(v1[i] == expected_vals[i]); 34 | } 35 | 36 | // check that v1 and v2 values match 37 | for (int i = 0; i < value_count; ++i) { 38 | CHECK(v2[i] == v1[i]); 39 | } 40 | } 41 | 42 | TEST_CASE("exception should be thrown for event conflicts") { 43 | auto p = nativeformat::param::createParam(0, 1, -1, "testParam"); 44 | std::vector curve{1.0f, 2.0f, 4.0f, 2.0f, 1.0f}; 45 | 46 | std::string actual; 47 | std::string expected = 48 | "New event with required time range 0 - 2 conflicts " 49 | "with existing event with required time range 1 - 1"; 50 | try { 51 | p->setValueAtTime(0.25f, 1.0); 52 | p->setValueCurveAtTime(curve, 0.0, 2.0); 53 | } catch (const std::exception &e) { 54 | actual = e.what(); 55 | } 56 | CHECK(actual == expected); 57 | 58 | p = nativeformat::param::createParam(0, 1, -1, "testParam"); 59 | actual = ""; 60 | expected = 61 | "New event with required time range 1 - 1 conflicts with existing " 62 | "event with required time range 0 - 2"; 63 | try { 64 | p->setValueCurveAtTime(curve, 0.0, 2.0); 65 | p->setValueAtTime(0.25f, 1.0); 66 | } catch (const std::exception &e) { 67 | actual = e.what(); 68 | } 69 | CHECK(actual == expected); 70 | } 71 | 72 | TEST_CASE("param values should be clamped to min and max") { 73 | auto p = nativeformat::param::createParam(0, 1.0f, -1.0f, "testParam"); 74 | p->setValueAtTime(-0.5f, 0.0); 75 | p->linearRampToValueAtTime(2.0f, 1.0); 76 | 77 | CHECK(p->valueForTime(0.0) == -0.5f); 78 | CHECK(p->valueForTime(1.0) == 1.0f); 79 | } 80 | 81 | TEST_CASE("full automation example from WAA spec should work") { 82 | // create curve for setCurveAtTime 83 | size_t curve_len = 44100; 84 | std::vector curve(curve_len); 85 | for (int i = 0; i < curve_len; ++i) { 86 | curve[i] = std::sin((3.14159265 * i) / curve_len); 87 | } 88 | 89 | // calculate expected end point for setTargetAtTime 90 | float time_constant = 0.1f; 91 | float set_target_end_val = 0.5 + 0.3 * std::exp(-0.175 / time_constant); 92 | 93 | auto p = nativeformat::param::createParam(0, 1, -1, "testParam"); 94 | p->setValueAtTime(0.2f, 0.0); 95 | p->setValueAtTime(0.3f, 0.1); 96 | p->setValueAtTime(0.4f, 0.2); 97 | p->linearRampToValueAtTime(1.0f, 0.3); 98 | p->linearRampToValueAtTime(0.8f, 0.325); 99 | p->setTargetAtTime(0.5f, 0.325, time_constant); 100 | p->setValueAtTime(0.552f, 0.5); 101 | p->exponentialRampToValueAtTime(0.75f, 0.6); 102 | p->exponentialRampToValueAtTime(0.05f, 0.7); 103 | p->setValueCurveAtTime(curve, 0.7, 0.3); 104 | 105 | size_t points = 1001; 106 | std::vector x(points), y(points); 107 | p->valuesForTimeRange(y.data(), points, 0.0, 1.0); 108 | 109 | double ts = 1.0 / (points - 1); 110 | double t = 0; 111 | for (int i = 0; i < x.size(); ++i) { 112 | x[i] = t; 113 | t += ts; 114 | } 115 | 116 | // Write the generated output to a file for plotting 117 | std::ofstream output_file("paramAutomation.txt"); 118 | for (int i = 0; i < x.size(); ++i) { 119 | output_file << x[i] << "\t" << y[i] << std::endl; 120 | } 121 | 122 | // Read test vector from file 123 | std::string path = "resources/paramAutomationExpected.txt"; 124 | FILE *input_file = std::fopen(path.c_str(), "r"); 125 | 126 | // Account for different build folder depths 127 | int i = 0; 128 | while (!input_file && i++ < 10) { 129 | path = "../" + path; 130 | input_file = std::fopen(path.c_str(), "r"); 131 | } 132 | 133 | float tmp1, tmp2; 134 | for (int i = 0; i < x.size(); ++i) { 135 | fscanf(input_file, "%f\n%f\n", &tmp1, &tmp2); 136 | CHECK(y[i] == Approx(tmp2)); 137 | } 138 | } 139 | 140 | TEST_CASE("automation example should work with reordered commands") { 141 | // create curve for setCurveAtTime 142 | size_t curve_len = 44100; 143 | std::vector curve(curve_len); 144 | for (int i = 0; i < curve_len; ++i) { 145 | curve[i] = std::sin((3.14159265 * i) / curve_len); 146 | } 147 | 148 | // calculate expected end point for setTargetAtTime 149 | float time_constant = 0.1f; 150 | float set_target_end_val = 0.5 + 0.3 * std::exp(-0.175 / time_constant); 151 | 152 | auto p = nativeformat::param::createParam(0, 1, -1, "testParam"); 153 | p->setValueAtTime(0.2f, 0.0); 154 | p->exponentialRampToValueAtTime(0.05f, 0.7); 155 | p->setValueAtTime(0.3f, 0.1); 156 | p->linearRampToValueAtTime(1.0f, 0.3); 157 | p->setValueAtTime(0.4f, 0.2); 158 | p->linearRampToValueAtTime(0.8f, 0.325); 159 | p->setValueAtTime(0.552f, 0.5); 160 | p->setTargetAtTime(0.5f, 0.325, time_constant); 161 | p->exponentialRampToValueAtTime(0.75f, 0.6); 162 | p->setValueCurveAtTime(curve, 0.7, 0.3); 163 | 164 | size_t points = 1001; 165 | std::vector x(points), y(points); 166 | p->valuesForTimeRange(y.data(), points, 0.0, 1.0); 167 | 168 | double ts = 1.0 / (points - 1); 169 | double t = 0; 170 | for (int i = 0; i < x.size(); ++i) { 171 | x[i] = t; 172 | t += ts; 173 | } 174 | 175 | // Write the generated output to a file for plotting 176 | std::ofstream output_file("paramAutomation_reordered.txt"); 177 | for (int i = 0; i < x.size(); ++i) { 178 | output_file << x[i] << "\t" << y[i] << std::endl; 179 | } 180 | 181 | // Read test vector from file 182 | std::string path = "resources/paramAutomationExpected.txt"; 183 | FILE *input_file = std::fopen(path.c_str(), "r"); 184 | 185 | // Account for different build folder depths 186 | int i = 0; 187 | while (!input_file && i++ < 10) { 188 | path = "../" + path; 189 | input_file = std::fopen(path.c_str(), "r"); 190 | } 191 | 192 | float tmp1, tmp2; 193 | for (int i = 0; i < x.size(); ++i) { 194 | fscanf(input_file, "%f\n%f\n", &tmp1, &tmp2); 195 | CHECK(y[i] == Approx(tmp2)); 196 | } 197 | } 198 | 199 | TEST_CASE("Events should return start or end value for times out of range") { 200 | nativeformat::param::LinearRampEvent lre(2.0, 2.0); 201 | nativeformat::param::ExponentialRampEvent ere(2.0, 1.0); 202 | nativeformat::param::ValueAtTimeEvent vte(5.0, 1.0); 203 | nativeformat::param::TargetAtTimeEvent tte(1.0, 1.0, 1.0); 204 | 205 | // check at t = -1, should be the start_value for all events 206 | CHECK(lre.valueAtTime(-1.0) == Approx(0.0)); 207 | CHECK(ere.valueAtTime(-1.0) == Approx(0.0)); 208 | CHECK(vte.valueAtTime(-1.0) == Approx(5.0)); 209 | CHECK(tte.valueAtTime(-1.0) == Approx(0.0)); 210 | 211 | // check at t -> infinity (30 is close enough) 212 | CHECK(lre.valueAtTime(30.0) == Approx(2.0)); 213 | CHECK(ere.valueAtTime(30.0) == Approx(2.0)); 214 | CHECK(vte.valueAtTime(30.0) == Approx(5.0)); 215 | CHECK(tte.valueAtTime(30.0) == Approx(1.0)); 216 | } 217 | 218 | TEST_CASE("A linear ramp event should integrate nicely") { 219 | nativeformat::param::LinearRampEvent event(2.0, 2.0); 220 | CHECK(event.cumulativeValue(0.0, 2.0) == Approx(2.0f)); 221 | } 222 | 223 | TEST_CASE("The integral over a linear ramp should include initial value") { 224 | nativeformat::param::LinearRampEvent event(2.0, 2.0); 225 | event.start_value = 1.0; 226 | CHECK(event.cumulativeValue(0.0, 2.0) == Approx(3.0f)); 227 | } 228 | 229 | TEST_CASE("A exponential ramp event should integrate nicely") { 230 | nativeformat::param::ExponentialRampEvent event(2.0, 1.0); 231 | event.start_value = 1.0; 232 | CHECK(event.cumulativeValue(0.0, 1.0) == Approx(2.442695f)); 233 | } 234 | 235 | TEST_CASE("Value at time has the integral of the value at that time") { 236 | nativeformat::param::ValueAtTimeEvent event(5.0, 1.0); 237 | CHECK(event.cumulativeValue(0.0, 2.0) == Approx(10.0f)); 238 | } 239 | 240 | TEST_CASE("Target at time event should integrate nicely") { 241 | nativeformat::param::TargetAtTimeEvent event(1.0, 1.0, 1.0); 242 | event.start_value = 0.0; 243 | CHECK(event.cumulativeValue(0.0, 1.0) == Approx(0.3678794f)); 244 | } 245 | 246 | TEST_CASE("The cumulative value over multiple events should work") { 247 | auto p = nativeformat::param::createParam(0.0f, 4.0f, 0.0f, "testParam"); 248 | p->linearRampToValueAtTime(2.0f, 1.0); 249 | p->linearRampToValueAtTime(4.0f, 2.0); 250 | auto cv = p->cumulativeValueForTimeRange(0., 2.); 251 | INFO("cumulative value: " << cv); 252 | CHECK(p->cumulativeValueForTimeRange(0., 2.0f) == Approx(6.0)); 253 | } 254 | 255 | TEST_CASE("CustomParamEvent should allow an arbitrary value function") { 256 | float default_val = 0.5; 257 | double event_end = 4.0; 258 | auto p = nativeformat::param::createParam(default_val, 16.0f, 0.0f, "testParam"); 259 | p->addCustomEvent( 260 | 0.0, event_end, nativeformat::param::Anchor::ALL, [](float t) { return t * t; }); 261 | size_t count = 11; 262 | std::vector values(count, 0.0); 263 | p->valuesForTimeRange(values.data(), count, 0.0, 5.0); 264 | 265 | // Check expected values within the custom event 266 | // Value should return to default for t > 4.0 267 | std::vector expected_values{0.0, 0.25, 1.0, 2.25, 4.0, 6.25, 9.0, 12.25, 0.5, 0.5, 0.5}; 268 | for (size_t i = 0; i < values.size(); ++i) { 269 | CHECK(values[i] == Approx(expected_values[i])); 270 | } 271 | 272 | // Check that defaults are preserved elsewhere 273 | float v = p->valueForTime(5.0); 274 | CHECK(v == Approx(default_val)); 275 | 276 | // Check trapezoid rule integral 277 | float area = p->cumulativeValueForTimeRange(0.0, event_end); 278 | float expected_area = 64.0 / 3; 279 | CHECK(area == Approx(expected_area).epsilon(0.01)); 280 | 281 | // Check that smoothed value is the average 282 | float avg = p->smoothedValueForTimeRange(0.0, 4.0, 1000); 283 | float expected_avg = expected_area / 4.0; 284 | CHECK(avg == Approx(expected_avg).epsilon(0.001)); 285 | 286 | // Check that integral continues to work after final event 287 | double box_time = 2.0; 288 | area = p->cumulativeValueForTimeRange(0.0, event_end + box_time, 0.0001); 289 | expected_area += default_val * box_time; 290 | CHECK(area == Approx(expected_area).epsilon(0.01)); 291 | } 292 | 293 | TEST_CASE("Param with no events should return default value") { 294 | float default_value = 0.7; 295 | size_t count = 4; 296 | auto p = nativeformat::param::createParam(default_value, 2.0f, -2.0f, "testParam"); 297 | std::vector values(count, 0.0); 298 | 299 | float v = p->valueForTime(1.0); 300 | p->valuesForTimeRange(values.data(), count, 0.0, 3.0); 301 | 302 | CHECK(v == Approx(default_value)); 303 | for (auto val : values) { 304 | CHECK(val == Approx(default_value)); 305 | } 306 | } 307 | 308 | TEST_CASE("valuesForTimeRange should return constant value when start == end") { 309 | auto p = nativeformat::param::createParam(0, 2.0f, -2.0f, "testParam"); 310 | p->setValueAtTime(1.5f, 1.0); 311 | size_t count = 4; 312 | std::vector values(count, 0.0); 313 | p->valuesForTimeRange(values.data(), count, 3.0, 3.0); 314 | for (auto val : values) { 315 | CHECK(val == Approx(values.front())); 316 | } 317 | } 318 | 319 | TEST_CASE("valuesForTimeRange should do nothing when count == 0") { 320 | auto p = nativeformat::param::createParam(0, 2.0f, -2.0f, "testParam"); 321 | p->setValueAtTime(1.5f, 1.0); 322 | std::vector values; 323 | p->valuesForTimeRange(values.data(), 0, 3.0, 3.0); 324 | for (auto val : values) { 325 | CHECK(val == Approx(0.0)); 326 | } 327 | } 328 | 329 | TEST_CASE("integral should be 0 if start = end or start < 0") { 330 | auto p = nativeformat::param::createParam(0, 2.0f, -2.0f, "testParam"); 331 | p->setValueAtTime(1.5f, 1.0); 332 | CHECK(p->cumulativeValueForTimeRange(1.0, 1.0) == Approx(0)); 333 | CHECK(p->cumulativeValueForTimeRange(-2.0, -1.0) == Approx(0)); 334 | CHECK(p->name() == "testParam"); 335 | } 336 | 337 | TEST_CASE("setValue should set value for all times") { 338 | auto p = nativeformat::param::createParam(0, 2.0f, -2.0f, "testParam"); 339 | float v = 1.13f; 340 | p->setValue(v); 341 | CHECK(p->valueForTime(0.0) == Approx(v)); 342 | CHECK(p->valueForTime(10.0) == Approx(v)); 343 | CHECK(p->valueForTime(100.0) == Approx(v)); 344 | } 345 | -------------------------------------------------------------------------------- /resources/paramAutomationExpected.txt: -------------------------------------------------------------------------------- 1 | 0 0.2 2 | 0.001 0.2 3 | 0.002 0.2 4 | 0.003 0.2 5 | 0.004 0.2 6 | 0.005 0.2 7 | 0.006 0.2 8 | 0.007 0.2 9 | 0.008 0.2 10 | 0.009 0.2 11 | 0.01 0.2 12 | 0.011 0.2 13 | 0.012 0.2 14 | 0.013 0.2 15 | 0.014 0.2 16 | 0.015 0.2 17 | 0.016 0.2 18 | 0.017 0.2 19 | 0.018 0.2 20 | 0.019 0.2 21 | 0.02 0.2 22 | 0.021 0.2 23 | 0.022 0.2 24 | 0.023 0.2 25 | 0.024 0.2 26 | 0.025 0.2 27 | 0.026 0.2 28 | 0.027 0.2 29 | 0.028 0.2 30 | 0.029 0.2 31 | 0.03 0.2 32 | 0.031 0.2 33 | 0.032 0.2 34 | 0.033 0.2 35 | 0.034 0.2 36 | 0.035 0.2 37 | 0.036 0.2 38 | 0.037 0.2 39 | 0.038 0.2 40 | 0.039 0.2 41 | 0.04 0.2 42 | 0.041 0.2 43 | 0.042 0.2 44 | 0.043 0.2 45 | 0.044 0.2 46 | 0.045 0.2 47 | 0.046 0.2 48 | 0.047 0.2 49 | 0.048 0.2 50 | 0.049 0.2 51 | 0.05 0.2 52 | 0.051 0.2 53 | 0.052 0.2 54 | 0.053 0.2 55 | 0.054 0.2 56 | 0.055 0.2 57 | 0.056 0.2 58 | 0.057 0.2 59 | 0.058 0.2 60 | 0.059 0.2 61 | 0.06 0.2 62 | 0.061 0.2 63 | 0.062 0.2 64 | 0.063 0.2 65 | 0.064 0.2 66 | 0.065 0.2 67 | 0.066 0.2 68 | 0.067 0.2 69 | 0.068 0.2 70 | 0.069 0.2 71 | 0.07 0.2 72 | 0.071 0.2 73 | 0.072 0.2 74 | 0.073 0.2 75 | 0.074 0.2 76 | 0.075 0.2 77 | 0.076 0.2 78 | 0.077 0.2 79 | 0.078 0.2 80 | 0.079 0.2 81 | 0.08 0.2 82 | 0.081 0.2 83 | 0.082 0.2 84 | 0.083 0.2 85 | 0.084 0.2 86 | 0.085 0.2 87 | 0.086 0.2 88 | 0.087 0.2 89 | 0.088 0.2 90 | 0.089 0.2 91 | 0.09 0.2 92 | 0.091 0.2 93 | 0.092 0.2 94 | 0.093 0.2 95 | 0.094 0.2 96 | 0.095 0.2 97 | 0.096 0.2 98 | 0.097 0.2 99 | 0.098 0.2 100 | 0.099 0.2 101 | 0.1 0.3 102 | 0.101 0.3 103 | 0.102 0.3 104 | 0.103 0.3 105 | 0.104 0.3 106 | 0.105 0.3 107 | 0.106 0.3 108 | 0.107 0.3 109 | 0.108 0.3 110 | 0.109 0.3 111 | 0.11 0.3 112 | 0.111 0.3 113 | 0.112 0.3 114 | 0.113 0.3 115 | 0.114 0.3 116 | 0.115 0.3 117 | 0.116 0.3 118 | 0.117 0.3 119 | 0.118 0.3 120 | 0.119 0.3 121 | 0.12 0.3 122 | 0.121 0.3 123 | 0.122 0.3 124 | 0.123 0.3 125 | 0.124 0.3 126 | 0.125 0.3 127 | 0.126 0.3 128 | 0.127 0.3 129 | 0.128 0.3 130 | 0.129 0.3 131 | 0.13 0.3 132 | 0.131 0.3 133 | 0.132 0.3 134 | 0.133 0.3 135 | 0.134 0.3 136 | 0.135 0.3 137 | 0.136 0.3 138 | 0.137 0.3 139 | 0.138 0.3 140 | 0.139 0.3 141 | 0.14 0.3 142 | 0.141 0.3 143 | 0.142 0.3 144 | 0.143 0.3 145 | 0.144 0.3 146 | 0.145 0.3 147 | 0.146 0.3 148 | 0.147 0.3 149 | 0.148 0.3 150 | 0.149 0.3 151 | 0.15 0.3 152 | 0.151 0.3 153 | 0.152 0.3 154 | 0.153 0.3 155 | 0.154 0.3 156 | 0.155 0.3 157 | 0.156 0.3 158 | 0.157 0.3 159 | 0.158 0.3 160 | 0.159 0.3 161 | 0.16 0.3 162 | 0.161 0.3 163 | 0.162 0.3 164 | 0.163 0.3 165 | 0.164 0.3 166 | 0.165 0.3 167 | 0.166 0.3 168 | 0.167 0.3 169 | 0.168 0.3 170 | 0.169 0.3 171 | 0.17 0.3 172 | 0.171 0.3 173 | 0.172 0.3 174 | 0.173 0.3 175 | 0.174 0.3 176 | 0.175 0.3 177 | 0.176 0.3 178 | 0.177 0.3 179 | 0.178 0.3 180 | 0.179 0.3 181 | 0.18 0.3 182 | 0.181 0.3 183 | 0.182 0.3 184 | 0.183 0.3 185 | 0.184 0.3 186 | 0.185 0.3 187 | 0.186 0.3 188 | 0.187 0.3 189 | 0.188 0.3 190 | 0.189 0.3 191 | 0.19 0.3 192 | 0.191 0.3 193 | 0.192 0.3 194 | 0.193 0.3 195 | 0.194 0.3 196 | 0.195 0.3 197 | 0.196 0.3 198 | 0.197 0.3 199 | 0.198 0.3 200 | 0.199 0.3 201 | 0.2 0.4 202 | 0.201 0.406 203 | 0.202 0.412 204 | 0.203 0.418 205 | 0.204 0.424 206 | 0.205 0.43 207 | 0.206 0.436 208 | 0.207 0.442 209 | 0.208 0.448 210 | 0.209 0.454 211 | 0.21 0.46 212 | 0.211 0.466 213 | 0.212 0.472 214 | 0.213 0.478 215 | 0.214 0.484 216 | 0.215 0.49 217 | 0.216 0.496 218 | 0.217 0.502 219 | 0.218 0.508 220 | 0.219 0.514 221 | 0.22 0.52 222 | 0.221 0.526 223 | 0.222 0.532 224 | 0.223 0.538 225 | 0.224 0.544 226 | 0.225 0.55 227 | 0.226 0.556 228 | 0.227 0.562 229 | 0.228 0.568 230 | 0.229 0.574 231 | 0.23 0.58 232 | 0.231 0.586 233 | 0.232 0.592 234 | 0.233 0.598 235 | 0.234 0.604 236 | 0.235 0.61 237 | 0.236 0.616 238 | 0.237 0.622 239 | 0.238 0.628 240 | 0.239 0.634 241 | 0.24 0.64 242 | 0.241 0.646 243 | 0.242 0.652 244 | 0.243 0.658 245 | 0.244 0.664 246 | 0.245 0.67 247 | 0.246 0.676 248 | 0.247 0.682 249 | 0.248 0.688 250 | 0.249 0.694 251 | 0.25 0.7 252 | 0.251 0.706 253 | 0.252 0.712 254 | 0.253 0.718 255 | 0.254 0.724 256 | 0.255 0.73 257 | 0.256 0.736 258 | 0.257 0.742 259 | 0.258 0.748 260 | 0.259 0.754 261 | 0.26 0.76 262 | 0.261 0.766 263 | 0.262 0.772 264 | 0.263 0.778 265 | 0.264 0.784 266 | 0.265 0.79 267 | 0.266 0.796 268 | 0.267 0.802 269 | 0.268 0.808 270 | 0.269 0.814 271 | 0.27 0.82 272 | 0.271 0.826 273 | 0.272 0.832 274 | 0.273 0.838 275 | 0.274 0.844 276 | 0.275 0.85 277 | 0.276 0.856 278 | 0.277 0.862 279 | 0.278 0.868 280 | 0.279 0.874 281 | 0.28 0.88 282 | 0.281 0.886 283 | 0.282 0.892 284 | 0.283 0.898 285 | 0.284 0.904 286 | 0.285 0.91 287 | 0.286 0.916 288 | 0.287 0.922 289 | 0.288 0.928 290 | 0.289 0.934 291 | 0.29 0.94 292 | 0.291 0.946 293 | 0.292 0.952 294 | 0.293 0.958 295 | 0.294 0.964 296 | 0.295 0.97 297 | 0.296 0.976 298 | 0.297 0.982 299 | 0.298 0.988 300 | 0.299 0.994 301 | 0.3 1 302 | 0.301 0.992 303 | 0.302 0.984 304 | 0.303 0.976 305 | 0.304 0.968 306 | 0.305 0.96 307 | 0.306 0.952 308 | 0.307 0.944 309 | 0.308 0.936 310 | 0.309 0.928 311 | 0.31 0.92 312 | 0.311 0.912 313 | 0.312 0.904 314 | 0.313 0.896 315 | 0.314 0.888 316 | 0.315 0.88 317 | 0.316 0.872 318 | 0.317 0.864 319 | 0.318 0.856 320 | 0.319 0.848 321 | 0.32 0.84 322 | 0.321 0.832 323 | 0.322 0.824 324 | 0.323 0.816 325 | 0.324 0.808 326 | 0.325 0.8 327 | 0.326 0.797015 328 | 0.327 0.79406 329 | 0.328 0.791134 330 | 0.329 0.788237 331 | 0.33 0.785369 332 | 0.331 0.782529 333 | 0.332 0.779718 334 | 0.333 0.776935 335 | 0.334 0.774179 336 | 0.335 0.771451 337 | 0.336 0.76875 338 | 0.337 0.766076 339 | 0.338 0.763429 340 | 0.339 0.760807 341 | 0.34 0.758212 342 | 0.341 0.755643 343 | 0.342 0.753099 344 | 0.343 0.750581 345 | 0.344 0.748088 346 | 0.345 0.745619 347 | 0.346 0.743175 348 | 0.347 0.740756 349 | 0.348 0.73836 350 | 0.349 0.735988 351 | 0.35 0.73364 352 | 0.351 0.731315 353 | 0.352 0.729014 354 | 0.353 0.726735 355 | 0.354 0.724479 356 | 0.355 0.722245 357 | 0.356 0.720034 358 | 0.357 0.717845 359 | 0.358 0.715677 360 | 0.359 0.713531 361 | 0.36 0.711406 362 | 0.361 0.709303 363 | 0.362 0.70722 364 | 0.363 0.705158 365 | 0.364 0.703117 366 | 0.365 0.701096 367 | 0.366 0.699095 368 | 0.367 0.697114 369 | 0.368 0.695153 370 | 0.369 0.693211 371 | 0.37 0.691288 372 | 0.371 0.689385 373 | 0.372 0.687501 374 | 0.373 0.685635 375 | 0.374 0.683788 376 | 0.375 0.681959 377 | 0.376 0.680149 378 | 0.377 0.678356 379 | 0.378 0.676582 380 | 0.379 0.674824 381 | 0.38 0.673085 382 | 0.381 0.671363 383 | 0.382 0.669658 384 | 0.383 0.66797 385 | 0.384 0.666298 386 | 0.385 0.664644 387 | 0.386 0.663005 388 | 0.387 0.661383 389 | 0.388 0.659778 390 | 0.389 0.658188 391 | 0.39 0.656614 392 | 0.391 0.655055 393 | 0.392 0.653513 394 | 0.393 0.651985 395 | 0.394 0.650473 396 | 0.395 0.648976 397 | 0.396 0.647493 398 | 0.397 0.646026 399 | 0.398 0.644573 400 | 0.399 0.643134 401 | 0.4 0.64171 402 | 0.401 0.6403 403 | 0.402 0.638904 404 | 0.403 0.637522 405 | 0.404 0.636153 406 | 0.405 0.634799 407 | 0.406 0.633457 408 | 0.407 0.632129 409 | 0.408 0.630815 410 | 0.409 0.629513 411 | 0.41 0.628224 412 | 0.411 0.626949 413 | 0.412 0.625685 414 | 0.413 0.624435 415 | 0.414 0.623197 416 | 0.415 0.621971 417 | 0.416 0.620757 418 | 0.417 0.619556 419 | 0.418 0.618366 420 | 0.419 0.617188 421 | 0.42 0.616022 422 | 0.421 0.614868 423 | 0.422 0.613725 424 | 0.423 0.612593 425 | 0.424 0.611473 426 | 0.425 0.610364 427 | 0.426 0.609266 428 | 0.427 0.608178 429 | 0.428 0.607102 430 | 0.429 0.606036 431 | 0.43 0.604981 432 | 0.431 0.603937 433 | 0.432 0.602903 434 | 0.433 0.601879 435 | 0.434 0.600865 436 | 0.435 0.599861 437 | 0.436 0.598868 438 | 0.437 0.597884 439 | 0.438 0.59691 440 | 0.439 0.595946 441 | 0.44 0.594991 442 | 0.441 0.594046 443 | 0.442 0.59311 444 | 0.443 0.592184 445 | 0.444 0.591266 446 | 0.445 0.590358 447 | 0.446 0.589459 448 | 0.447 0.588569 449 | 0.448 0.587688 450 | 0.449 0.586815 451 | 0.45 0.585951 452 | 0.451 0.585096 453 | 0.452 0.584249 454 | 0.453 0.583411 455 | 0.454 0.582581 456 | 0.455 0.58176 457 | 0.456 0.580946 458 | 0.457 0.580141 459 | 0.458 0.579343 460 | 0.459 0.578554 461 | 0.46 0.577772 462 | 0.461 0.576998 463 | 0.462 0.576232 464 | 0.463 0.575474 465 | 0.464 0.574723 466 | 0.465 0.573979 467 | 0.466 0.573243 468 | 0.467 0.572514 469 | 0.468 0.571793 470 | 0.469 0.571078 471 | 0.47 0.570371 472 | 0.471 0.569671 473 | 0.472 0.568978 474 | 0.473 0.568291 475 | 0.474 0.567612 476 | 0.475 0.566939 477 | 0.476 0.566273 478 | 0.477 0.565614 479 | 0.478 0.564961 480 | 0.479 0.564314 481 | 0.48 0.563674 482 | 0.481 0.563041 483 | 0.482 0.562414 484 | 0.483 0.561793 485 | 0.484 0.561178 486 | 0.485 0.560569 487 | 0.486 0.559966 488 | 0.487 0.55937 489 | 0.488 0.558779 490 | 0.489 0.558194 491 | 0.49 0.557615 492 | 0.491 0.557042 493 | 0.492 0.556474 494 | 0.493 0.555912 495 | 0.494 0.555356 496 | 0.495 0.554805 497 | 0.496 0.55426 498 | 0.497 0.55372 499 | 0.498 0.553185 500 | 0.499 0.552656 501 | 0.5 0.552 502 | 0.501 0.553695 503 | 0.502 0.555394 504 | 0.503 0.557099 505 | 0.504 0.55881 506 | 0.505 0.560525 507 | 0.506 0.562246 508 | 0.507 0.563972 509 | 0.508 0.565703 510 | 0.509 0.56744 511 | 0.51 0.569182 512 | 0.511 0.57093 513 | 0.512 0.572682 514 | 0.513 0.57444 515 | 0.514 0.576204 516 | 0.515 0.577973 517 | 0.516 0.579747 518 | 0.517 0.581527 519 | 0.518 0.583312 520 | 0.519 0.585103 521 | 0.52 0.586899 522 | 0.521 0.588701 523 | 0.522 0.590508 524 | 0.523 0.592321 525 | 0.524 0.594139 526 | 0.525 0.595963 527 | 0.526 0.597793 528 | 0.527 0.599628 529 | 0.528 0.601469 530 | 0.529 0.603316 531 | 0.53 0.605168 532 | 0.531 0.607026 533 | 0.532 0.608889 534 | 0.533 0.610758 535 | 0.534 0.612633 536 | 0.535 0.614514 537 | 0.536 0.616401 538 | 0.537 0.618293 539 | 0.538 0.620191 540 | 0.539 0.622095 541 | 0.54 0.624005 542 | 0.541 0.62592 543 | 0.542 0.627842 544 | 0.543 0.62977 545 | 0.544 0.631703 546 | 0.545 0.633642 547 | 0.546 0.635587 548 | 0.547 0.637539 549 | 0.548 0.639496 550 | 0.549 0.641459 551 | 0.55 0.643428 552 | 0.551 0.645404 553 | 0.552 0.647385 554 | 0.553 0.649372 555 | 0.554 0.651366 556 | 0.555 0.653366 557 | 0.556 0.655371 558 | 0.557 0.657383 559 | 0.558 0.659402 560 | 0.559 0.661426 561 | 0.56 0.663456 562 | 0.561 0.665493 563 | 0.562 0.667536 564 | 0.563 0.669586 565 | 0.564 0.671641 566 | 0.565 0.673703 567 | 0.566 0.675771 568 | 0.567 0.677846 569 | 0.568 0.679927 570 | 0.569 0.682014 571 | 0.57 0.684108 572 | 0.571 0.686208 573 | 0.572 0.688315 574 | 0.573 0.690428 575 | 0.574 0.692547 576 | 0.575 0.694673 577 | 0.576 0.696806 578 | 0.577 0.698945 579 | 0.578 0.701091 580 | 0.579 0.703243 581 | 0.58 0.705402 582 | 0.581 0.707568 583 | 0.582 0.70974 584 | 0.583 0.711919 585 | 0.584 0.714104 586 | 0.585 0.716297 587 | 0.586 0.718496 588 | 0.587 0.720701 589 | 0.588 0.722914 590 | 0.589 0.725133 591 | 0.59 0.727359 592 | 0.591 0.729592 593 | 0.592 0.731832 594 | 0.593 0.734079 595 | 0.594 0.736332 596 | 0.595 0.738593 597 | 0.596 0.74086 598 | 0.597 0.743135 599 | 0.598 0.745416 600 | 0.599 0.747705 601 | 0.6 0.75 602 | 0.601 0.729962 603 | 0.602 0.71046 604 | 0.603 0.691478 605 | 0.604 0.673004 606 | 0.605 0.655023 607 | 0.606 0.637523 608 | 0.607 0.62049 609 | 0.608 0.603912 610 | 0.609 0.587778 611 | 0.61 0.572074 612 | 0.611 0.55679 613 | 0.612 0.541914 614 | 0.613 0.527436 615 | 0.614 0.513344 616 | 0.615 0.499629 617 | 0.616 0.48628 618 | 0.617 0.473288 619 | 0.618 0.460643 620 | 0.619 0.448336 621 | 0.62 0.436358 622 | 0.621 0.4247 623 | 0.622 0.413353 624 | 0.623 0.402309 625 | 0.624 0.391561 626 | 0.625 0.3811 627 | 0.626 0.370918 628 | 0.627 0.361008 629 | 0.628 0.351363 630 | 0.629 0.341975 631 | 0.63 0.332839 632 | 0.631 0.323946 633 | 0.632 0.315291 634 | 0.633 0.306868 635 | 0.634 0.298669 636 | 0.635 0.290689 637 | 0.636 0.282923 638 | 0.637 0.275364 639 | 0.638 0.268007 640 | 0.639 0.260847 641 | 0.64 0.253878 642 | 0.641 0.247095 643 | 0.642 0.240493 644 | 0.643 0.234068 645 | 0.644 0.227814 646 | 0.645 0.221728 647 | 0.646 0.215804 648 | 0.647 0.210038 649 | 0.648 0.204427 650 | 0.649 0.198965 651 | 0.65 0.193649 652 | 0.651 0.188475 653 | 0.652 0.18344 654 | 0.653 0.178539 655 | 0.654 0.173769 656 | 0.655 0.169126 657 | 0.656 0.164608 658 | 0.657 0.16021 659 | 0.658 0.15593 660 | 0.659 0.151764 661 | 0.66 0.147709 662 | 0.661 0.143762 663 | 0.662 0.139922 664 | 0.663 0.136183 665 | 0.664 0.132545 666 | 0.665 0.129004 667 | 0.666 0.125557 668 | 0.667 0.122203 669 | 0.668 0.118938 670 | 0.669 0.11576 671 | 0.67 0.112667 672 | 0.671 0.109657 673 | 0.672 0.106727 674 | 0.673 0.103876 675 | 0.674 0.101101 676 | 0.675 0.0983995 677 | 0.676 0.0957705 678 | 0.677 0.0932118 679 | 0.678 0.0907215 680 | 0.679 0.0882977 681 | 0.68 0.0859386 682 | 0.681 0.0836426 683 | 0.682 0.0814079 684 | 0.683 0.0792329 685 | 0.684 0.077116 686 | 0.685 0.0750557 687 | 0.686 0.0730504 688 | 0.687 0.0710987 689 | 0.688 0.0691992 690 | 0.689 0.0673504 691 | 0.69 0.065551 692 | 0.691 0.0637996 693 | 0.692 0.0620951 694 | 0.693 0.0604361 695 | 0.694 0.0588214 696 | 0.695 0.0572499 697 | 0.696 0.0557203 698 | 0.697 0.0542316 699 | 0.698 0.0527827 700 | 0.699 0.0513725 701 | 0.7 0.05 702 | 0.701 0.0104008 703 | 0.702 0.0208717 704 | 0.703 0.0313403 705 | 0.704 0.0418054 706 | 0.705 0.052266 707 | 0.706 0.0627208 708 | 0.707 0.0731688 709 | 0.708 0.0836087 710 | 0.709 0.0940395 711 | 0.71 0.10446 712 | 0.711 0.114869 713 | 0.712 0.125265 714 | 0.713 0.135648 715 | 0.714 0.146016 716 | 0.715 0.156368 717 | 0.716 0.166702 718 | 0.717 0.177019 719 | 0.718 0.187316 720 | 0.719 0.197592 721 | 0.72 0.207847 722 | 0.721 0.218079 723 | 0.722 0.228287 724 | 0.723 0.23847 725 | 0.724 0.248626 726 | 0.725 0.258756 727 | 0.726 0.268857 728 | 0.727 0.278929 729 | 0.728 0.28897 730 | 0.729 0.298979 731 | 0.73 0.308956 732 | 0.731 0.318899 733 | 0.732 0.328807 734 | 0.733 0.338678 735 | 0.734 0.348513 736 | 0.735 0.358309 737 | 0.736 0.368066 738 | 0.737 0.377783 739 | 0.738 0.387458 740 | 0.739 0.397091 741 | 0.74 0.40668 742 | 0.741 0.416225 743 | 0.742 0.425724 744 | 0.743 0.435176 745 | 0.744 0.444581 746 | 0.745 0.453937 747 | 0.746 0.463243 748 | 0.747 0.472498 749 | 0.748 0.481701 750 | 0.749 0.490852 751 | 0.75 0.499949 752 | 0.751 0.508991 753 | 0.752 0.517977 754 | 0.753 0.526906 755 | 0.754 0.535778 756 | 0.755 0.54459 757 | 0.756 0.553343 758 | 0.757 0.562036 759 | 0.758 0.570666 760 | 0.759 0.579235 761 | 0.76 0.587739 762 | 0.761 0.596179 763 | 0.762 0.604554 764 | 0.763 0.612863 765 | 0.764 0.621104 766 | 0.765 0.629277 767 | 0.766 0.637381 768 | 0.767 0.645415 769 | 0.768 0.653379 770 | 0.769 0.661271 771 | 0.77 0.66909 772 | 0.771 0.676836 773 | 0.772 0.684508 774 | 0.773 0.692104 775 | 0.774 0.699625 776 | 0.775 0.707069 777 | 0.776 0.714435 778 | 0.777 0.721724 779 | 0.778 0.728933 780 | 0.779 0.736062 781 | 0.78 0.74311 782 | 0.781 0.750077 783 | 0.782 0.756961 784 | 0.783 0.763763 785 | 0.784 0.770481 786 | 0.785 0.777114 787 | 0.786 0.783662 788 | 0.787 0.790124 789 | 0.788 0.796499 790 | 0.789 0.802788 791 | 0.79 0.808988 792 | 0.791 0.815099 793 | 0.792 0.821121 794 | 0.793 0.827053 795 | 0.794 0.832894 796 | 0.795 0.838644 797 | 0.796 0.844302 798 | 0.797 0.849867 799 | 0.798 0.855339 800 | 0.799 0.860718 801 | 0.8 0.866002 802 | 0.801 0.871191 803 | 0.802 0.876284 804 | 0.803 0.881281 805 | 0.804 0.886182 806 | 0.805 0.890985 807 | 0.806 0.895691 808 | 0.807 0.900299 809 | 0.808 0.904808 810 | 0.809 0.909217 811 | 0.81 0.913527 812 | 0.811 0.917737 813 | 0.812 0.921846 814 | 0.813 0.925854 815 | 0.814 0.92976 816 | 0.815 0.933565 817 | 0.816 0.937267 818 | 0.817 0.940866 819 | 0.818 0.944362 820 | 0.819 0.947755 821 | 0.82 0.951043 822 | 0.821 0.954228 823 | 0.822 0.957307 824 | 0.823 0.960282 825 | 0.824 0.963151 826 | 0.825 0.965915 827 | 0.826 0.968573 828 | 0.827 0.971124 829 | 0.828 0.97357 830 | 0.829 0.975908 831 | 0.83 0.978139 832 | 0.831 0.980263 833 | 0.832 0.98228 834 | 0.833 0.984189 835 | 0.834 0.985989 836 | 0.835 0.987682 837 | 0.836 0.989267 838 | 0.837 0.990743 839 | 0.838 0.99211 840 | 0.839 0.993368 841 | 0.84 0.994518 842 | 0.841 0.995558 843 | 0.842 0.99649 844 | 0.843 0.997312 845 | 0.844 0.998024 846 | 0.845 0.998628 847 | 0.846 0.999121 848 | 0.847 0.999505 849 | 0.848 0.99978 850 | 0.849 0.999945 851 | 0.85 1 852 | 0.851 0.999946 853 | 0.852 0.999781 854 | 0.853 0.999508 855 | 0.854 0.999124 856 | 0.855 0.998631 857 | 0.856 0.998029 858 | 0.857 0.997317 859 | 0.858 0.996496 860 | 0.859 0.995565 861 | 0.86 0.994525 862 | 0.861 0.993377 863 | 0.862 0.992119 864 | 0.863 0.990752 865 | 0.864 0.989277 866 | 0.865 0.987693 867 | 0.866 0.986001 868 | 0.867 0.984201 869 | 0.868 0.982293 870 | 0.869 0.980277 871 | 0.87 0.978154 872 | 0.871 0.975923 873 | 0.872 0.973586 874 | 0.873 0.971142 875 | 0.874 0.968591 876 | 0.875 0.965934 877 | 0.876 0.96317 878 | 0.877 0.960302 879 | 0.878 0.957328 880 | 0.879 0.954249 881 | 0.88 0.951065 882 | 0.881 0.947777 883 | 0.882 0.944386 884 | 0.883 0.94089 885 | 0.884 0.937292 886 | 0.885 0.93359 887 | 0.886 0.929786 888 | 0.887 0.925881 889 | 0.888 0.921873 890 | 0.889 0.917765 891 | 0.89 0.913556 892 | 0.891 0.909247 893 | 0.892 0.904838 894 | 0.893 0.90033 895 | 0.894 0.895723 896 | 0.895 0.891018 897 | 0.896 0.886215 898 | 0.897 0.881315 899 | 0.898 0.876318 900 | 0.899 0.871226 901 | 0.9 0.866037 902 | 0.901 0.860754 903 | 0.902 0.855376 904 | 0.903 0.849905 905 | 0.904 0.84434 906 | 0.905 0.838683 907 | 0.906 0.832934 908 | 0.907 0.827093 909 | 0.908 0.821162 910 | 0.909 0.81514 911 | 0.91 0.80903 912 | 0.911 0.80283 913 | 0.912 0.796543 914 | 0.913 0.790168 915 | 0.914 0.783706 916 | 0.915 0.777159 917 | 0.916 0.770526 918 | 0.917 0.763809 919 | 0.918 0.757008 920 | 0.919 0.750124 921 | 0.92 0.743158 922 | 0.921 0.73611 923 | 0.922 0.728981 924 | 0.923 0.721773 925 | 0.924 0.714485 926 | 0.925 0.707119 927 | 0.926 0.699676 928 | 0.927 0.692156 929 | 0.928 0.68456 930 | 0.929 0.676888 931 | 0.93 0.669143 932 | 0.931 0.661324 933 | 0.932 0.653433 934 | 0.933 0.64547 935 | 0.934 0.637436 936 | 0.935 0.629332 937 | 0.936 0.62116 938 | 0.937 0.612919 939 | 0.938 0.604611 940 | 0.939 0.596237 941 | 0.94 0.587797 942 | 0.941 0.579293 943 | 0.942 0.570725 944 | 0.943 0.562095 945 | 0.944 0.553403 946 | 0.945 0.54465 947 | 0.946 0.535838 948 | 0.947 0.526966 949 | 0.948 0.518038 950 | 0.949 0.509052 951 | 0.95 0.50001 952 | 0.951 0.490914 953 | 0.952 0.481764 954 | 0.953 0.472561 955 | 0.954 0.463306 956 | 0.955 0.454 957 | 0.956 0.444645 958 | 0.957 0.43524 959 | 0.958 0.425788 960 | 0.959 0.41629 961 | 0.96 0.406745 962 | 0.961 0.397156 963 | 0.962 0.387524 964 | 0.963 0.377849 965 | 0.964 0.368133 966 | 0.965 0.358376 967 | 0.966 0.34858 968 | 0.967 0.338745 969 | 0.968 0.328874 970 | 0.969 0.318966 971 | 0.97 0.309024 972 | 0.971 0.299047 973 | 0.972 0.289038 974 | 0.973 0.278997 975 | 0.974 0.268926 976 | 0.975 0.258825 977 | 0.976 0.248695 978 | 0.977 0.238539 979 | 0.978 0.228356 980 | 0.979 0.218148 981 | 0.98 0.207916 982 | 0.981 0.197662 983 | 0.982 0.187386 984 | 0.983 0.177089 985 | 0.984 0.166772 986 | 0.985 0.156438 987 | 0.986 0.146086 988 | 0.987 0.135719 989 | 0.988 0.125336 990 | 0.989 0.11494 991 | 0.99 0.104531 992 | 0.991 0.0941104 993 | 0.992 0.0836797 994 | 0.993 0.0732399 995 | 0.994 0.0627919 996 | 0.995 0.0523371 997 | 0.996 0.0418766 998 | 0.997 0.0314115 999 | 0.998 0.0209429 1000 | 0.999 0.010472 1001 | 1 0 1002 | --------------------------------------------------------------------------------