diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..dbc8cc7 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,5 @@ +reviews: + path_filters: + - "!7.x/src/stk/src/**" + - "!7.x/src/stk/include/**" + - "!7.x/src/stk/rawwaves/**" diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 00bf9d5..4d2ca45 100644 --- a/.github/workflows/plugins_build_7.yml +++ b/.github/workflows/plugins_build_7.yml @@ -116,7 +116,10 @@ jobs: build/src/websockets/*.so \ build/src/hdf5/*.so \ build/src/py/*.so \ - build/src/OpenCL/*.so package/ + build/src/OpenCL/*.so \ + build/src/stk/*.so package/ + mkdir -p package/stk/rawwaves + cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -219,7 +222,10 @@ jobs: build/src/websockets/*.dylib \ build/src/hdf5/*.dylib \ build/src/py/*.dylib \ - build/src/OpenCL/*.dylib package/ + build/src/OpenCL/*.dylib \ + build/src/stk/*.dylib package/ + mkdir -p package/stk/rawwaves + cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -319,6 +325,9 @@ jobs: if (Test-Path $vcpkgBin) { Copy-Item (Join-Path $vcpkgBin '*.dll') -Destination $package -ErrorAction SilentlyContinue } + $rawwaves = Join-Path $package 'stk/rawwaves' + New-Item -ItemType Directory -Force -Path $rawwaves | Out-Null + Copy-Item '7.x\src\stk\rawwaves\*.raw' -Destination $rawwaves - name: Upload plugins uses: actions/upload-artifact@v4 diff --git a/7.x/CMakeLists.txt b/7.x/CMakeLists.txt index 80b1181..ed75642 100644 --- a/7.x/CMakeLists.txt +++ b/7.x/CMakeLists.txt @@ -313,3 +313,4 @@ add_subdirectory(./src/wiimote) add_subdirectory(./src/p5glove) add_subdirectory(./src/CUDA) add_subdirectory(./src/OpenCL) +add_subdirectory(./src/stk) diff --git a/7.x/README.md b/7.x/README.md index ec3c5d7..f6691d4 100644 --- a/7.x/README.md +++ b/7.x/README.md @@ -36,6 +36,9 @@ Currently, the plugins available in this tree are - OpenCL (requires the OpenCL headers/loader; see note below) **cladsyn** +- STK (port of the Synthesis ToolKit instruments; STK 4.6.2 is vendored under this directory, see note below) +**STKBandedWG STKBeeThree STKBlowBotl STKBlowHole STKBowed STKBrass STKClarinet STKDrummer STKFlute STKFMVoices STKHevyMetl STKMandolin STKModalBar STKMoog STKPercFlut STKPlucked STKResonate STKRhodey STKSaxofony STKShakers STKSimple STKSitar STKStifKarp STKTubeBell STKVoicForm STKWhistle STKWurley** + Fluid settings opcodes --- @@ -123,6 +126,34 @@ compile-only (the hosted runners have no OpenCL device) and it is included in the Linux, macOS and Windows release packages. +STK opcodes +--- + +The `STK` opcodes are a port of the Csound 6.x plugin to the Csound 7 plugin +API. They expose the instruments of Perry Cook and Gary Scavone's Synthesis +ToolKit in C++ (STK), each named `STK` followed by the STK class, e.g. +`STKSitar`. All of them share the same signature: + +``` +aout STKName ifrequency igain {kcontroller0, kvalue0, ..., kcontrollerN, kvalueN} +``` + +The STK sources are **vendored under this directory** (`include/`, `src/` and +`rawwaves/`) and built from source, so no external STK installation is needed +and the result is independent of any system or package-manager STK. The +vendored copy carries a fix for the sample buildup reported in +[csound/plugins#4](https://github.com/csound/plugins/issues/4): the noise +modulation of the sitar delay line in `Sitar::setFrequency`, which could make +`STKSitar` build up clipped output over time, has been removed. + +It is built by default; configure with `-DBUILD_STK_OPCODES=OFF` to exclude +it. The wavetables the instruments need are installed alongside the plugin +(under `stk/rawwaves`). At run time the `RAWWAVE_PATH` environment variable, +if set, overrides the built-in default location; otherwise the installed +`rawwaves` directory is used. The plugin is included in the Linux, macOS and +Windows release packages. + + Release binaries and macOS architecture -------------- @@ -182,7 +213,8 @@ plugin from the build process, one can pass an option to the cmake command. The options are `BUILD_CHUA_OPCODES`, `BUILD_CUDA_OPCODES`, `BUILD_OPENCL_OPCODES`, `BUILD_FLUID_OPCODES`, `BUILD_IMAGE_OPCODES`, `BUILD_HDF5_OPCODES`, `BUILD_LINEAR_ALGEBRA_OPCODES`, -`BUILD_P5GLOVE_OPCODES`, `BUILD_WIIMOTE_OPCODES`, `BUILD_WEBSOCKET_OPCODE` and +`BUILD_P5GLOVE_OPCODES`, `BUILD_STK_OPCODES`, `BUILD_WIIMOTE_OPCODES`, +`BUILD_WEBSOCKET_OPCODE` and `BUILD_PYTHON_OPCODES`. For example, to exclude the chua plugin, the `cmake` command would be: diff --git a/7.x/src/stk/CMakeLists.txt b/7.x/src/stk/CMakeLists.txt new file mode 100644 index 0000000..caa1d4c --- /dev/null +++ b/7.x/src/stk/CMakeLists.txt @@ -0,0 +1,28 @@ +option(BUILD_STK_OPCODES "Build the stk opcodes" ON) + +# The STK sources are bundled under this directory (include/, src/ and +# rawwaves/) so that a patched copy is always built, independent of any system +# or package-manager STK installation. In particular, src/Sitar.cpp carries a +# fix for the sample buildup described in csound/plugins#4. +if(BUILD_STK_OPCODES) + file(GLOB stk_srcs "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + list(APPEND stk_srcs "${CMAKE_CURRENT_SOURCE_DIR}/stkOpcodes.cpp") + + make_plugin(stkops "${stk_srcs}") + target_include_directories(stkops PRIVATE + ${CSOUND_INCLUDE_DIRS} + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/src") + target_compile_definitions(stkops PRIVATE BUILD_PLUGINS) + + # Install the rawwaves used by the STK instruments and make them the + # default raw wave path; the RAWWAVE_PATH environment variable overrides it. + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/rawwaves" + DESTINATION "${PLUGIN_INSTALL_DIR}/stk") + if(PLUGIN_INSTALL_DIR) + target_compile_definitions(stkops PRIVATE + DEFAULT_RAWWAVE_PATH="${PLUGIN_INSTALL_DIR}/stk/rawwaves") + endif() + + message(STATUS "Building STK opcodes") +endif() diff --git a/7.x/src/stk/LICENSE b/7.x/src/stk/LICENSE new file mode 100644 index 0000000..c606999 --- /dev/null +++ b/7.x/src/stk/LICENSE @@ -0,0 +1,27 @@ +The Synthesis ToolKit in C++ (STK) + +Copyright (c) 1995-2021 Perry R. Cook and Gary P. Scavone + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Any person wishing to distribute modifications to the Software is +asked to send the modifications to the original developer so that they +can be incorporated into the canonical version. This is, however, not +a binding provision of this license. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/7.x/src/stk/OpcodeBase.hpp b/7.x/src/stk/OpcodeBase.hpp new file mode 100644 index 0000000..f0e3843 --- /dev/null +++ b/7.x/src/stk/OpcodeBase.hpp @@ -0,0 +1,489 @@ +/* + OpcodeBase.hpp: + + Copyright (C) 2005, 2009, 2017 by Istva Varga, Victor Lazzarini and + Michael Gogins + + This file is part of Csound. + + The Csound Library is free software; you can redistribute it + and/or modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + Csound is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with Csound; if not, write to the Free Software + Foundation, Inc., 31 Milk Street, #960789, Boston, MA, 02196, USA +*/ +#pragma once + +/** + * There is a conflict between the preprocessor definition "_CR" in the + * standard C++ library and in Csound. To work around this, undefine "_CR" and + * include ALL standard library include files BEFORE including ANY Csound + * include files. + */ +#if defined(_CR) +#undef _CR +#endif + +#include +#include +#include +#if !(defined(__wasi__)) +#include +#endif +#include +#include +#include "interlocks.h" +#ifdef BUILD_PLUGINS +#include "csdl.h" +#else +#include "csoundCore.h" +#endif + +/** + * Template base class, or pseudo-virtual base class, + * for writing Csound opcodes in C++. + * Derive opcode implementation classes like this: + * + * DerivedClass : public OpcodeBase + * { + * public: + * // All output fields must be declared first as cs_float *: + * cs_float *aret1; + * // All input fields must be declared next as cs_float *: + * cs_float *iarg1; + * cs_float *karg2; + * cs_float *aarg3; + * // All internal state variables must be declared after that: + * size_t state1; + * double state2; + * cs_float state3; + * // If the opcode shares data protect it by creating one or more void + * // *mutex member pointers: + * void *mutex1; + * void *mutex2; + * // and create them usind csound->Create_Mutex() in csoundModuleCreate + * // and destroy them using csound->DeleteMutex(mutex) + * // csoundModuleDestroy, and lock them using LockGuard guard(mutex). + * int32_t init(); + * int32_t kontrol(); + * int32_t audio; + * int32_t noteoff(); + * void deinit(); + * }; + */ + +namespace csound { + + static bool diagnostics_enabled = false; + + /** + * Use this to guard against data races in opcode functions. The mutex should + * be created by csound->Create_Mutex() in csoundModuleCreate(), and should + * be destroyed by csound->DeleteMutex() in csoundModuleDestroy(). + * + * If data is shared between opcode instances, the mutex should be global to + * the opcode library; if data is shared between threads for a single instance, + * the mutex should belong to the opcode instance. + */ + struct LockGuard { + LockGuard(CSOUND *csound_, void *mutex_) : csound(csound_), mutex(mutex_) + { + csound->LockMutex(mutex); + } + ~LockGuard() + { + csound->UnlockMutex(mutex); + } + CSOUND *csound; + void *mutex; + }; + + /** + * Use this to store a pointer to a global heap-allocated object, e.g. one + * used to manage state between opcode instances. + */ + + template int32_t CreateGlobalPointer(CSOUND *csound, const char *name, T *pointer) + { + T **pointer_to_pointer = 0; + int32_t result = csound->CreateGlobalVariable(csound, name, sizeof(pointer_to_pointer)); + pointer_to_pointer = static_cast(csound->QueryGlobalVariable(csound, name)); + *pointer_to_pointer = pointer; + return result; + } + + /** + * Retrieve a pointer to a global heap-allocated object, e.g. one + * used to manage state between opcode instances. + */ + template T *QueryGlobalPointer(CSOUND *csound, const char *name, T*& pointer) + { + T **pointer_to_pointer = static_cast(csound->QueryGlobalVariableNoCheck(csound, name)); + if (pointer_to_pointer != 0) { + pointer = *pointer_to_pointer; + } else { + pointer = 0; + } + return pointer; + } + + template + class OpcodeBase + { + public: + int32_t init(CSOUND *csound) + { + return NOTOK; + } + static int32_t init_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->init(csound); + } + int32_t kontrol(CSOUND *csound) + { + return NOTOK; + } + static int32_t kontrol_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->kontrol(csound); + } + int32_t audio(CSOUND *csound) + { + return NOTOK; + } + static int32_t audio_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->audio(csound); + } + /** + This is how to compute audio signals for normal opcodes: + (1) Zero all frames from 0 up to but not including Offset. + (2) Compute all frames from ksmps_offset up to but not including End. + (3) Zero all frames from End up to but not including ksmps. + Example from a C opcode: + uint32_t offset = p->h.insdshead->ksmps_offset; + uint32_t early = p->h.insdshead->ksmps_no_end; + uint32_t n, nsmps = CS_KSMPS; + if (UNLIKELY(offset)) memset(p->r, '\0', offset*sizeof(cs_float)); + if (UNLIKELY(early)) { + nsmps -= early; + memset(&p->r[nsmps], '\0', early*sizeof(cs_float)); + } + for (n = offset; n < nsmps; n++) { + input1 = CS_FLOAT2LRND(p->a[n]); + p->r[n] = (cs_float) (input1 >> input2); + } + So in C++ it should look like this (which is much easier to understand): + int32_t frameIndex = 0; + for( ; frameIndex < kperiodOffset(); ++frameIndex) { + asignal[frameIndex] = 0; + } + for( ; frameIndex < kperiodEnd(); ++frameIndex) { + asignal[frameIndex] = compute(); + } + for( ; frameIndex < ksmps(); ++frameIndex) { + asignal[frameIndex] = 0; + } + */ + uint32_t kperiodOffset() const + { + return opds.insdshead->ksmps_offset; + } + uint32_t kperiodEnd() const + { + return ksmps() - opds.insdshead->ksmps_no_end; + } + uint32_t ksmps() const + { + return opds.insdshead->ksmps; + } + uint32_t output_arg_count() + { + return (uint32_t)opds.optext->t.outArgCount; + } + uint32_t input_arg_count() + { + return (uint32_t)opds.optext->t.inArgCount; + } + void log(CSOUND *csound, const char *format,...) + { + va_list args; + va_start(args, format); + if(csound) { + csound->MessageV(csound, 0, format, args); + } else { + vfprintf(stdout, format, args); + } + va_end(args); + } + void warn(CSOUND *csound, const char *format,...) + { + if(csound) { + if(csound->GetMessageLevel(csound) & CS_WARNMSG) { + va_list args; + va_start(args, format); + csound->MessageV(csound, CSOUNDMSG_WARNING, format, args); + va_end(args); + } + } else { + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + } + } + OPDS opds; + }; + + template + class OpcodeNoteoffBase + { + public: + int32_t init(CSOUND *csound) + { + IGN(csound); + return NOTOK; + } + + static int32_t deinit_(CSOUND *csound, void *opcode) + { + if (!csound->GetReinitFlag(csound) && !csound->GetTieFlag(csound)) { + return noteoff_(csound, opcode); + } + return OK; + } + + static int32_t init_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->init(csound); + } + + int32_t kontrol(CSOUND *csound) + { + IGN(csound); + return NOTOK; + } + static int32_t kontrol_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->kontrol(csound); + } + int32_t audio(CSOUND *csound) + { + IGN(csound); + return NOTOK; + } + static int32_t audio_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->audio(csound); + } + /** + This is how to compute audio signals for normal opcodes: + (1) Zero all frames from 0 up to but not including Offset. + (2) Compute all frames from ksmps_offset up to but not including End. + (3) Zero all frames from End up to but not including ksmps. + Example from a C opcode: + uint32_t offset = p->h.insdshead->ksmps_offset; + uint32_t early = p->h.insdshead->ksmps_no_end; + uint32_t n, nsmps = CS_KSMPS; + if (UNLIKELY(offset)) memset(p->r, '\0', offset*sizeof(cs_float)); + if (UNLIKELY(early)) { + nsmps -= early; + memset(&p->r[nsmps], '\0', early*sizeof(cs_float)); + } + for (n = offset; n < nsmps; n++) { + input1 = CS_FLOAT2LRND(p->a[n]); + p->r[n] = (cs_float) (input1 >> input2); + } + So in C++ it should look like this (which is much easier to understand): + int32_t frameIndex = 0; + for( ; frameIndex < kperiodOffset(); ++frameIndex) { + asignal[frameIndex] = 0; + } + for( ; frameIndex < kperiodEnd(); ++frameIndex) { + asignal[frameIndex] = compute(); + } + for( ; frameIndex < ksmps(); ++frameIndex) { + asignal[frameIndex] = 0; + } + */ + uint32_t kperiodOffset() const + { + return opds.insdshead->ksmps_offset; + } + uint32_t kperiodEnd() const + { + return ksmps() - opds.insdshead->ksmps_no_end; + } + uint32_t ksmps() const + { + return opds.insdshead->ksmps; + } + uint32_t output_arg_count() + { + return (uint32_t)opds.optext->t.outArgCount; + } + uint32_t input_arg_count() + { + return (uint32_t)opds.optext->t.inArgCount; + } + void log(CSOUND *csound, const char *format,...) + { + va_list args; + va_start(args, format); + if(csound) { + csound->MessageV(csound, 0, format, args); + } else { + vfprintf(stdout, format, args); + } + va_end(args); + } + void warn(CSOUND *csound, const char *format,...) + { + if(csound) { + if(csound->GetMessageLevel(csound) & CS_WARNMSG) { + va_list args; + va_start(args, format); + csound->MessageV(csound, CSOUNDMSG_WARNING, format, args); + va_end(args); + } + } else { + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + } + } + int32_t noteoff(CSOUND *csound) + { + return OK; + } + static int32_t noteoff_(CSOUND *csound, void *opcode) + { + return reinterpret_cast(opcode)->noteoff(csound); + } + OPDS opds; + }; + + +#if (__cplusplus >= 201103L) && !(defined(__wasi__)) && !defined(BARE_METAL) + // #pragma message("Defining heap_object_manager_t.") + /** + * The memory of non-POD C++ or C objects allocated on the heap by Csound + * plugins is managed not by Csound, but by the plugin module. This class + * performs that function. + * + * As long as all memory is allocated in or after csoundModuleCreate and + * de-allocated in csoundModuleDestroy, all will be well. The danger however + * is that memory leaks will occur if allocations are not matched with + * de-allocations, or worse, that crashes will occur if any objects are + * deleted twice or de-referenced after deletion. + * + * So, the plugin should manage a collection of all heap-allocated objects, + * keyed first off the Csound instance, and second off an object handle; and + * all such objects must be accessed only by handle, from this collection. + * + * In csoundModuleDestroy, all objects for the calling instance of Csound + * should be de-allocated, and the sub-collection for that instance should + * also be de-allocated. + * + * If then there are no more sub-collections for other instances of Csound, + * the entire contents of the collection should be de-allocated. + * + * This should be done as a singleton, and in a thread-safe way. + * + * There might still be a problem if any of the heap-allocated objects, + * themselves, have global static members, perhaps created by some external + * dependency. + * + * Please note, objects O in the declaration below must not be std::shared_ptr + * objects. Deletion of objects herein must be unconditional. Rather, the + * objects passed are raw pointers to instances of class O. + */ + template class heap_object_manager_t { + private: + std::map> objects_; + std::recursive_mutex mutex; + heap_object_manager_t(){}; + ~heap_object_manager_t(){}; + public: + static heap_object_manager_t &instance() { + static heap_object_manager_t singleton; + return singleton; + } + std::map> &objects() { + std::lock_guard lock(mutex); + return objects_; + } + /** + * Returns a list of pointers to all objects allocated for this + * instance of Csound. + */ + std::vector &objects_for_csound(CSOUND *csound) { + std::lock_guard lock(mutex); + auto &objects_for_csound_ = objects()[csound]; + return objects_for_csound_; + } + /** + * Returns the handle for the object; if the object has not yet been + * stored, inserts it into the list of object pointers for this + * instance of Csound; otherwise, returns the handle of the stored + * object pointer. + */ + int32_t handle_for_object(CSOUND *csound, O *object) { + std::lock_guard lock(mutex); + auto &objects_for_csound_ = objects_for_csound(csound); + auto iterator = std::find(objects_for_csound_.begin(), objects_for_csound_.end(), object); + if (iterator == objects_for_csound_.end()) { + int32_t handle = objects_for_csound_.size(); + objects_for_csound_.push_back(object); + if (diagnostics_enabled) std::fprintf(stderr, "heap_object_manager_t::handle_for_object %p: new object handle: %d (of %ld)\n", object, handle, objects_for_csound_.size()); + return handle; + } else { + int32_t handle = static_cast(iterator - objects_for_csound_.begin()); + if (diagnostics_enabled) std::fprintf(stderr, "heap_object_manager_t::handle_for_object: existing object handle: %d\n", handle); + return handle; + } + } + /** + * Returns the object pointer for the handle; + * if the object pointer has not been stored by + * handle, returns a null pointer. + */ + O *object_for_handle(CSOUND *csound, int32_t handle) { + std::lock_guard lock(mutex); + auto &objects_for_csound_ = objects_for_csound(csound); + if (handle >= objects_for_csound_.size()) { + return nullptr; + } + O *object = objects_for_csound_[handle]; + if (diagnostics_enabled) std::fprintf(stderr, "heap_object_manager_t::object_for_handle: %p %d (of %ld)\n", object, handle, objects_for_csound_.size()); + return object; + } + /** + * First destroys all objects created by the calling + * instance of Csound, then destroys the list of + * object pointers for this instance of Csound. + */ + void module_destroy(CSOUND *csound) { + std::lock_guard lock(mutex); + auto &objects_for_csound_ = objects_for_csound(csound); + for (int32_t i = 0, n = objects_for_csound_.size(); i < n; ++i) { + delete objects_for_csound_[i]; + objects_for_csound_[i] = nullptr; + } + objects_for_csound_.clear(); + objects().erase(csound); + } + }; + +#endif + +} diff --git a/7.x/src/stk/include/ADSR.h b/7.x/src/stk/include/ADSR.h new file mode 100644 index 0000000..777778e --- /dev/null +++ b/7.x/src/stk/include/ADSR.h @@ -0,0 +1,179 @@ +#ifndef STK_ADSR_H +#define STK_ADSR_H + +#include "Generator.h" + +namespace stk { + +/***************************************************/ +/*! \class ADSR + \brief STK ADSR envelope class. + + This class implements a traditional ADSR (Attack, Decay, Sustain, + Release) envelope. It responds to simple keyOn and keyOff + messages, keeping track of its state. The \e state = ADSR::IDLE + before being triggered and after the envelope value reaches 0.0 in + the ADSR::RELEASE state. All rate, target and level settings must + be non-negative. All time settings are in seconds and must be + positive. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class ADSR : public Generator +{ + public: + + //! ADSR envelope states. + enum { + ATTACK, /*!< Attack */ + DECAY, /*!< Decay */ + SUSTAIN, /*!< Sustain */ + RELEASE, /*!< Release */ + IDLE /*!< Before attack / after release */ + }; + + //! Default constructor. + ADSR( void ); + + //! Class destructor. + ~ADSR( void ); + + //! Set target = 1, state = \e ADSR::ATTACK. + void keyOn( void ); + + //! Set target = 0, state = \e ADSR::RELEASE. + void keyOff( void ); + + //! Set the attack rate (gain / sample). + void setAttackRate( StkFloat rate ); + + //! Set the target value for the attack (default = 1.0). + void setAttackTarget( StkFloat target ); + + //! Set the decay rate (gain / sample). + void setDecayRate( StkFloat rate ); + + //! Set the sustain level. + void setSustainLevel( StkFloat level ); + + //! Set the release rate (gain / sample). + void setReleaseRate( StkFloat rate ); + + //! Set the attack rate based on a time duration (seconds). + void setAttackTime( StkFloat time ); + + //! Set the decay rate based on a time duration (seconds). + void setDecayTime( StkFloat time ); + + //! Set the release rate based on a time duration (seconds). + void setReleaseTime( StkFloat time ); + + //! Set sustain level and attack, decay, and release time durations (seconds). + void setAllTimes( StkFloat aTime, StkFloat dTime, StkFloat sLevel, StkFloat rTime ); + + //! Set a sustain target value and attack or decay from current value to target. + void setTarget( StkFloat target ); + + //! Return the current envelope \e state (ATTACK, DECAY, SUSTAIN, RELEASE, IDLE). + int getState( void ) const { return state_; }; + + //! Set to state = ADSR::SUSTAIN with current and target values of \e value. + void setValue( StkFloat value ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + int state_; + StkFloat value_; + StkFloat target_; + StkFloat attackRate_; + StkFloat decayRate_; + StkFloat releaseRate_; + StkFloat releaseTime_; + StkFloat sustainLevel_; +}; + +inline StkFloat ADSR :: tick( void ) +{ + switch ( state_ ) { + + case ATTACK: + value_ += attackRate_; + if ( value_ >= target_ ) { + value_ = target_; + target_ = sustainLevel_; + state_ = DECAY; + } + lastFrame_[0] = value_; + break; + + case DECAY: + if ( value_ > sustainLevel_ ) { + value_ -= decayRate_; + if ( value_ <= sustainLevel_ ) { + value_ = sustainLevel_; + state_ = SUSTAIN; + } + } + else { + value_ += decayRate_; // attack target < sustain level + if ( value_ >= sustainLevel_ ) { + value_ = sustainLevel_; + state_ = SUSTAIN; + } + } + lastFrame_[0] = value_; + break; + + case RELEASE: + value_ -= releaseRate_; + if ( value_ <= 0.0 ) { + value_ = 0.0; + state_ = IDLE; + } + lastFrame_[0] = value_; + + } + + return value_; +} + +inline StkFrames& ADSR :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "ADSR::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i 0). + /*! + The rate is computed as described above. The value of \e tau + must be greater than zero. Values of \e tau close to zero produce + fast approach rates, while values greater than 1.0 produce rather + slow rates. + */ + void setTau( StkFloat tau ); + + //! Set the asymptotic rate based on a time duration (must be > 0). + void setTime( StkFloat time ); + + //! Set the asymptotic rate such that the target value is perceptually reached (to within -60dB of the target) in \e t60 seconds. + void setT60( StkFloat t60 ); + + //! Set the target value. + void setTarget( StkFloat target ); + + //! Set current and target values to \e value. + void setValue( StkFloat value ); + + //! Return the current envelope \e state (0 = at target, 1 otherwise). + int getState( void ) const { return state_; }; + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + StkFloat value_; + StkFloat target_; + StkFloat factor_; + StkFloat constant_; + int state_; +}; + +inline StkFloat Asymp :: tick( void ) +{ + if ( state_ ) { + + value_ = factor_ * value_ + constant_; + + // Check threshold. + if ( target_ > value_ ) { + if ( target_ - value_ <= TARGET_THRESHOLD ) { + value_ = target_; + state_ = 0; + } + } + else { + if ( value_ - target_ <= TARGET_THRESHOLD ) { + value_ = target_; + state_ = 0; + } + } + lastFrame_[0] = value_; + } + + return value_; +} + +inline StkFrames& Asymp :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Asymp::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "BandedWG::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i Out + 3 -/| + 4 -- + \endcode + + Control Change Numbers: + - Operator 4 (feedback) Gain = 2 + - Operator 3 Gain = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class BeeThree : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + BeeThree( void ); + + //! Class destructor. + ~BeeThree( void ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat BeeThree :: tick( unsigned int ) +{ + StkFloat temp; + + if ( modDepth_ > 0.0 ) { + temp = 1.0 + ( modDepth_ * vibrato_.tick() * 0.1 ); + waves_[0]->setFrequency( baseFrequency_ * temp * ratios_[0] ); + waves_[1]->setFrequency( baseFrequency_ * temp * ratios_[1] ); + waves_[2]->setFrequency( baseFrequency_ * temp * ratios_[2] ); + waves_[3]->setFrequency( baseFrequency_ * temp * ratios_[3] ); + } + + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = control1_ * 2.0 * gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + twozero_.tick( temp ); + + temp += control2_ * 2.0 * gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + temp += gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp += gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + + lastFrame_[0] = temp * 0.125; + return lastFrame_[0]; +} + +inline StkFrames& BeeThree :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "BeeThree::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= 1.0. The + \e frequency value should be between zero and half the sample rate. + */ + void setResonance( StkFloat frequency, StkFloat radius, bool normalize = false ); + + //! Set the filter coefficients for a notch at \e frequency (in Hz). + /*! + This method determines the filter coefficients corresponding to + two complex-conjugate zeros with the given \e frequency (in Hz) + and \e radius from the z-plane origin. No filter normalization is + attempted. The \e frequency value should be between zero and half + the sample rate. The \e radius value should be positive. + */ + void setNotch( StkFloat frequency, StkFloat radius ); + + //! Sets the filter zeroes for equal resonance gain. + /*! + When using the filter as a resonator, zeroes places at z = 1, z + = -1 will result in a constant gain at resonance of 1 / (1 - R), + where R is the pole radius setting. + + */ + void setEqualGainZeroes( void ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Input one sample to the filter and return a reference to one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + virtual void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); +}; + +inline StkFloat BiQuad :: tick( StkFloat input ) +{ + inputs_[0] = gain_ * input; + lastFrame_[0] = b_[0] * inputs_[0] + b_[1] * inputs_[1] + b_[2] * inputs_[2]; + lastFrame_[0] -= a_[2] * outputs_[2] + a_[1] * outputs_[1]; + inputs_[2] = inputs_[1]; + inputs_[1] = inputs_[0]; + outputs_[2] = outputs_[1]; + outputs_[1] = lastFrame_[0]; + + return lastFrame_[0]; +} + +inline StkFrames& BiQuad :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "BiQuad::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "BiQuad::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i +#include + +namespace stk { + +/***************************************************/ +/*! \class Blit + \brief STK band-limited impulse train class. + + This class generates a band-limited impulse train using a + closed-form algorithm reported by Stilson and Smith in "Alias-Free + Digital Synthesis of Classic Analog Waveforms", 1996. The user + can specify both the fundamental frequency of the impulse train + and the number of harmonics contained in the resulting signal. + + The signal is normalized so that the peak value is +/-1.0. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). + + Original code by Robin Davies, 2005. + Revisions by Gary Scavone for STK, 2005. +*/ +/***************************************************/ + +class Blit: public Generator +{ + public: + //! Default constructor that initializes BLIT frequency to 220 Hz. + Blit( StkFloat frequency = 220.0 ); + + //! Class destructor. + ~Blit(); + + //! Resets the oscillator state and phase to 0. + void reset(); + + //! Set the phase of the signal. + /*! + Set the phase of the signal, in the range 0 to 1. + */ + void setPhase( StkFloat phase ) { phase_ = PI * phase; }; + + //! Get the current phase of the signal. + /*! + Get the phase of the signal, in the range [0 to 1.0). + */ + StkFloat getPhase() const { return phase_ / PI; }; + + //! Set the impulse train rate in terms of a frequency in Hz. + void setFrequency( StkFloat frequency ); + + //! Set the number of harmonics generated in the signal. + /*! + This function sets the number of harmonics contained in the + resulting signal. It is equivalent to (2 * M) + 1 in the BLIT + algorithm. The default value of 0 sets the algorithm for maximum + harmonic content (harmonics up to half the sample rate). This + parameter is not checked against the current sample rate and + fundamental frequency. Thus, aliasing can result if one or more + harmonics for a given fundamental frequency exceeds fs / 2. This + behavior was chosen over the potentially more problematic solution + of automatically modifying the M parameter, which can produce + audible clicks in the signal. + */ + void setHarmonics( unsigned int nHarmonics = 0 ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void updateHarmonics( void ); + + unsigned int nHarmonics_; + unsigned int m_; + StkFloat rate_; + StkFloat phase_; + StkFloat p_; + +}; + +inline StkFloat Blit :: tick( void ) +{ + // The code below implements the SincM algorithm of Stilson and + // Smith with an additional scale factor of P / M applied to + // normalize the output. + + // A fully optimized version of this code would replace the two sin + // calls with a pair of fast sin oscillators, for which stable fast + // two-multiply algorithms are well known. In the spirit of STK, + // which favors clarity over performance, the optimization has not + // been made here. + + // Avoid a divide by zero at the sinc peak, which has a limiting + // value of 1.0. + StkFloat tmp, denominator = sin( phase_ ); + if ( denominator <= std::numeric_limits::epsilon() ) + tmp = 1.0; + else { + tmp = sin( m_ * phase_ ); + tmp /= m_ * denominator; + } + + phase_ += rate_; + if ( phase_ >= PI ) phase_ -= PI; + + lastFrame_[0] = tmp; + return lastFrame_[0]; +} + +inline StkFrames& Blit :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Blit::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i +#include + +namespace stk { + +/***************************************************/ +/*! \class BlitSaw + \brief STK band-limited sawtooth wave class. + + This class generates a band-limited sawtooth waveform using a + closed-form algorithm reported by Stilson and Smith in "Alias-Free + Digital Synthesis of Classic Analog Waveforms", 1996. The user + can specify both the fundamental frequency of the sawtooth and the + number of harmonics contained in the resulting signal. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). + + Based on initial code of Robin Davies, 2005. + Modified algorithm code by Gary Scavone, 2005. +*/ +/***************************************************/ + +class BlitSaw: public Generator +{ + public: + //! Class constructor. + BlitSaw( StkFloat frequency = 220.0 ); + + //! Class destructor. + ~BlitSaw(); + + //! Resets the oscillator state and phase to 0. + void reset(); + + //! Set the sawtooth oscillator rate in terms of a frequency in Hz. + void setFrequency( StkFloat frequency ); + + //! Set the number of harmonics generated in the signal. + /*! + This function sets the number of harmonics contained in the + resulting signal. It is equivalent to (2 * M) + 1 in the BLIT + algorithm. The default value of 0 sets the algorithm for maximum + harmonic content (harmonics up to half the sample rate). This + parameter is not checked against the current sample rate and + fundamental frequency. Thus, aliasing can result if one or more + harmonics for a given fundamental frequency exceeds fs / 2. This + behavior was chosen over the potentially more problematic solution + of automatically modifying the M parameter, which can produce + audible clicks in the signal. + */ + void setHarmonics( unsigned int nHarmonics = 0 ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void updateHarmonics( void ); + + unsigned int nHarmonics_; + unsigned int m_; + StkFloat rate_; + StkFloat phase_; + StkFloat p_; + StkFloat C2_; + StkFloat a_; + StkFloat state_; + +}; + +inline StkFloat BlitSaw :: tick( void ) +{ + // The code below implements the BLIT algorithm of Stilson and + // Smith, followed by a summation and filtering operation to produce + // a sawtooth waveform. After experimenting with various approaches + // to calculate the average value of the BLIT over one period, I + // found that an estimate of C2_ = 1.0 / period (in samples) worked + // most consistently. A "leaky integrator" is then applied to the + // difference of the BLIT output and C2_. (GPS - 1 October 2005) + + // A fully optimized version of this code would replace the two sin + // calls with a pair of fast sin oscillators, for which stable fast + // two-multiply algorithms are well known. In the spirit of STK, + // which favors clarity over performance, the optimization has + // not been made here. + + // Avoid a divide by zero, or use of a denormalized divisor + // at the sinc peak, which has a limiting value of m_ / p_. + StkFloat tmp, denominator = sin( phase_ ); + if ( fabs(denominator) <= std::numeric_limits::epsilon() ) + tmp = a_; + else { + tmp = sin( m_ * phase_ ); + tmp /= p_ * denominator; + } + + tmp += state_ - C2_; + state_ = tmp * 0.995; + + phase_ += rate_; + if ( phase_ >= PI ) phase_ -= PI; + + lastFrame_[0] = tmp; + return lastFrame_[0]; +} + +inline StkFrames& BlitSaw :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "BlitSaw::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i +#include + +namespace stk { + +/***************************************************/ +/*! \class BlitSquare + \brief STK band-limited square wave class. + + This class generates a band-limited square wave signal. It is + derived in part from the approach reported by Stilson and Smith in + "Alias-Free Digital Synthesis of Classic Analog Waveforms", 1996. + The algorithm implemented in this class uses a SincM function with + an even M value to achieve a bipolar bandlimited impulse train. + This signal is then integrated to achieve a square waveform. The + integration process has an associated DC offset so a DC blocking + filter is applied at the output. + + The user can specify both the fundamental frequency of the + waveform and the number of harmonics contained in the resulting + signal. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). Also note that the harmonics of a + square wave fall at odd integer multiples of the fundamental, so + aliasing will happen with a lower fundamental than with the other + Blit waveforms. This class is not guaranteed to be well behaved + in the presence of significant aliasing. + + Based on initial code of Robin Davies, 2005. + Modified algorithm code by Gary Scavone, 2005--2006. +*/ +/***************************************************/ + +class BlitSquare: public Generator +{ + public: + //! Default constructor that initializes BLIT frequency to 220 Hz. + BlitSquare( StkFloat frequency = 220.0 ); + + //! Class destructor. + ~BlitSquare(); + + //! Resets the oscillator state and phase to 0. + void reset(); + + //! Set the phase of the signal. + /*! + Set the phase of the signal, in the range 0 to 1. + */ + void setPhase( StkFloat phase ) { phase_ = PI * phase; }; + + //! Get the current phase of the signal. + /*! + Get the phase of the signal, in the range [0 to 1.0). + */ + StkFloat getPhase() const { return phase_ / PI; }; + + //! Set the impulse train rate in terms of a frequency in Hz. + void setFrequency( StkFloat frequency ); + + //! Set the number of harmonics generated in the signal. + /*! + This function sets the number of harmonics contained in the + resulting signal. It is equivalent to (2 * M) + 1 in the BLIT + algorithm. The default value of 0 sets the algorithm for maximum + harmonic content (harmonics up to half the sample rate). This + parameter is not checked against the current sample rate and + fundamental frequency. Thus, aliasing can result if one or more + harmonics for a given fundamental frequency exceeds fs / 2. This + behavior was chosen over the potentially more problematic solution + of automatically modifying the M parameter, which can produce + audible clicks in the signal. + */ + void setHarmonics( unsigned int nHarmonics = 0 ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void updateHarmonics( void ); + + unsigned int nHarmonics_; + unsigned int m_; + StkFloat rate_; + StkFloat phase_; + StkFloat p_; + StkFloat a_; + StkFloat lastBlitOutput_; + StkFloat dcbState_; +}; + +inline StkFloat BlitSquare :: tick( void ) +{ + StkFloat temp = lastBlitOutput_; + + // A fully optimized version of this would replace the two sin calls + // with a pair of fast sin oscillators, for which stable fast + // two-multiply algorithms are well known. In the spirit of STK, + // which favors clarity over performance, the optimization has + // not been made here. + + // Avoid a divide by zero, or use of a denomralized divisor + // at the sinc peak, which has a limiting value of 1.0. + StkFloat denominator = sin( phase_ ); + if ( fabs( denominator ) < std::numeric_limits::epsilon() ) { + // Inexact comparison safely distinguishes betwen *close to zero*, and *close to PI*. + if ( phase_ < 0.1f || phase_ > TWO_PI - 0.1f ) + lastBlitOutput_ = a_; + else + lastBlitOutput_ = -a_; + } + else { + lastBlitOutput_ = sin( m_ * phase_ ); + lastBlitOutput_ /= p_ * denominator; + } + + lastBlitOutput_ += temp; + + // Now apply DC blocker. + lastFrame_[0] = lastBlitOutput_ - dcbState_ + 0.999 * lastFrame_[0]; + dcbState_ = lastBlitOutput_; + + phase_ += rate_; + if ( phase_ >= TWO_PI ) phase_ -= TWO_PI; + + return lastFrame_[0]; +} + +inline StkFrames& BlitSquare :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "BlitSquare::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "BlowBotl::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "BlowHole::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class BowTable + \brief STK bowed string table class. + + This class implements a simple bowed string + non-linear function, as described by Smith + (1986). The output is an instantaneous + reflection coefficient value. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class BowTable : public Function +{ +public: + //! Default constructor. + BowTable( void ) : offset_(0.0), slope_(0.1), minOutput_(0.01), maxOutput_(0.98) {}; + + //! Set the table offset value. + /*! + The table offset is a bias which controls the + symmetry of the friction. If you want the + friction to vary with direction, use a non-zero + value for the offset. The default value is zero. + */ + void setOffset( StkFloat offset ) { offset_ = offset; }; + + //! Set the table slope value. + /*! + The table slope controls the width of the friction + pulse, which is related to bow force. + */ + void setSlope( StkFloat slope ) { slope_ = slope; }; + + //! Set the minimum table output value (0.0 - 1.0). + void setMinOutput( StkFloat minimum ) { minOutput_ = minimum; }; + + //! Set the maximum table output value (0.0 - 1.0). + void setMaxOutput( StkFloat maximum ) { maxOutput_ = maximum; }; + + //! Take one sample input and map to one sample of output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the table and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the table and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + +protected: + + StkFloat offset_; + StkFloat slope_; + StkFloat minOutput_; + StkFloat maxOutput_; + +}; + +inline StkFloat BowTable :: tick( StkFloat input ) +{ + // The input represents differential string vs. bow velocity. + StkFloat sample = input + offset_; // add bias to input + sample *= slope_; // then scale it + lastFrame_[0] = (StkFloat) fabs( (double) sample ) + (StkFloat) 0.75; + lastFrame_[0] = (StkFloat) pow( lastFrame_[0], (StkFloat) -4.0 ); + + // Set minimum threshold + if ( lastFrame_[0] < minOutput_ ) lastFrame_[0] = minOutput_; + + // Set maximum threshold + if ( lastFrame_[0] > maxOutput_ ) lastFrame_[0] = maxOutput_; + + return lastFrame_[0]; +} + +inline StkFrames& BowTable :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "BowTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i 1.0) *samples = 1.0; + } + + lastFrame_[0] = *(samples-hop); + return frames; +} + +inline StkFrames& BowTable :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel ) +{ +#if defined(_STK_DEBUG_) + if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "BowTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i 1.0) *oSamples = 1.0; + } + + lastFrame_[0] = *(oSamples-oHop); + return iFrames; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Bowed.h b/7.x/src/stk/include/Bowed.h new file mode 100644 index 0000000..4ad5726 --- /dev/null +++ b/7.x/src/stk/include/Bowed.h @@ -0,0 +1,157 @@ +#ifndef STK_BOWED_H +#define STK_BOWED_H + +#include "Instrmnt.h" +#include "DelayL.h" +#include "BowTable.h" +#include "OnePole.h" +#include "BiQuad.h" +#include "SineWave.h" +#include "ADSR.h" + +namespace stk { + +/***************************************************/ +/*! \class Bowed + \brief STK bowed string instrument class. + + This class implements a bowed string model, a + la Smith (1986), after McIntyre, Schumacher, + Woodhouse (1983). + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + + Control Change Numbers: + - Bow Pressure = 2 + - Bow Position = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Bow Velocity = 100 + - Frequency = 101 + - Volume = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. + Contributions by Esteban Maestre, 2011. +*/ +/***************************************************/ + +class Bowed : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + Bowed( StkFloat lowestFrequency = 8.0 ); + + //! Class destructor. + ~Bowed( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set vibrato gain. + void setVibrato( StkFloat gain ) { vibratoGain_ = gain; }; + + //! Apply breath pressure to instrument with given amplitude and rate of increase. + void startBowing( StkFloat amplitude, StkFloat rate ); + + //! Decrease breath pressure with given rate of decrease. + void stopBowing( StkFloat rate ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayL neckDelay_; + DelayL bridgeDelay_; + BowTable bowTable_; + OnePole stringFilter_; + BiQuad bodyFilters_[6]; + SineWave vibrato_; + ADSR adsr_; + + bool bowDown_; + StkFloat maxVelocity_; + StkFloat baseDelay_; + StkFloat vibratoGain_; + StkFloat betaRatio_; + +}; + +inline StkFloat Bowed :: tick( unsigned int ) +{ + StkFloat bowVelocity = maxVelocity_ * adsr_.tick(); + StkFloat bridgeReflection = -stringFilter_.tick( bridgeDelay_.lastOut() ); + StkFloat nutReflection = -neckDelay_.lastOut(); + StkFloat stringVelocity = bridgeReflection + nutReflection; + StkFloat deltaV = bowVelocity - stringVelocity; // Differential velocity + + StkFloat newVelocity = 0.0; + if ( bowDown_ ) + newVelocity = deltaV * bowTable_.tick( deltaV ); // Non-Linear bow function + neckDelay_.tick( bridgeReflection + newVelocity); // Do string propagations + bridgeDelay_.tick(nutReflection + newVelocity); + + if ( vibratoGain_ > 0.0 ) { + neckDelay_.setDelay( (baseDelay_ * (1.0 - betaRatio_) ) + + (baseDelay_ * vibratoGain_ * vibrato_.tick()) ); + } + + lastFrame_[0] = 0.1248 * bodyFilters_[5].tick( bodyFilters_[4].tick( bodyFilters_[3].tick( bodyFilters_[2].tick( bodyFilters_[1].tick( bodyFilters_[0].tick( bridgeDelay_.lastOut() ) ) ) ) ) ); + + return lastFrame_[0]; +} + +inline StkFrames& Bowed :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Bowed::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i position. + deltaPressure *= deltaPressure; // Basic position to area mapping. + if ( deltaPressure > 1.0 ) deltaPressure = 1.0; // Non-linear saturation. + + // The following input scattering assumes the mouthPressure = area. + lastFrame_[0] = deltaPressure * mouthPressure + ( 1.0 - deltaPressure) * borePressure; + lastFrame_[0] = delayLine_.tick( dcBlock_.tick( lastFrame_[0] ) ); + + return lastFrame_[0]; +} + +inline StkFrames& Brass :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Brass::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i 1 ) { + oStream_ << "Chorus::lastOut(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline StkFloat Chorus :: tick( StkFloat input, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "Chorus::tick(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + delayLine_[0].setDelay( baseLength_ * 0.707 * ( 1.0 + modDepth_ * mods_[0].tick() ) ); + delayLine_[1].setDelay( baseLength_ * 0.5 * ( 1.0 - modDepth_ * mods_[1].tick() ) ); + lastFrame_[0] = effectMix_ * ( delayLine_[0].tick( input ) - input ) + input; + lastFrame_[1] = effectMix_ * ( delayLine_[1].tick( input ) - input ) + input; + return lastFrame_[channel]; +} + +inline StkFrames& Chorus :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() - 1 ) { + oStream_ << "Chorus::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels() - 1; + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() - 1 ) { + oStream_ << "Chorus::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "Clarinet::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class Cubic + \brief STK cubic non-linearity class. + + This class implements the cubic non-linearity + that was used in SynthBuilder. + + The formula implemented is: + + \code + output = gain * (a1 * input + a2 * input^2 + a3 * input^3) + \endcode + + followed by a limiter for values outside +-threshold. + + Ported to STK by Nick Porcaro, 2007. Updated for inclusion + in STK distribution by Gary Scavone, 2011. +*/ +/***************************************************/ + +class Cubic : public Function +{ +public: + //! Default constructor. + Cubic( void ) : a1_(0.5), a2_(0.5), a3_(0.5), gain_(1.0), threshold_(1.0) {}; + + //! Set the a1 coefficient value. + void setA1( StkFloat a1 ) { a1_ = a1; }; + + //! Set the a2 coefficient value. + void setA2( StkFloat a2 ) { a2_ = a2; }; + + //! Set the a3 coefficient value. + void setA3( StkFloat a3 ) { a3_ = a3; }; + + //! Set the gain value. + void setGain( StkFloat gain ) { gain_ = gain; }; + + //! Set the threshold value. + void setThreshold( StkFloat threshold ) { threshold_ = threshold; }; + + //! Input one sample to the function and return one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the function and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the function and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + +protected: + + StkFloat a1_; + StkFloat a2_; + StkFloat a3_; + StkFloat gain_; + StkFloat threshold_; +}; + +inline StkFloat Cubic :: tick( StkFloat input ) +{ + StkFloat inSquared = input * input; + StkFloat inCubed = inSquared * input; + + lastFrame_[0] = gain_ * (a1_ * input + a2_ * inSquared + a3_ * inCubed); + + // Apply threshold if we are out of range. + if ( fabs( lastFrame_[0] ) > threshold_ ) { + lastFrame_[0] = ( lastFrame_[0] < 0 ? -threshold_ : threshold_ ); + } + + return lastFrame_[0]; +} + +inline StkFrames& Cubic :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Cubic::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Cubic::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "Delay::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Delay::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "DelayA::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "DelayA::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i inputs_.size() ) { // The value is too big. + oStream_ << "DelayL::setDelay: argument (" << delay << ") greater than maximum!"; + handleError( StkError::WARNING ); return; + } + + if (delay < 0 ) { + oStream_ << "DelayL::setDelay: argument (" << delay << ") less than zero!"; + handleError( StkError::WARNING ); return; + } + + StkFloat outPointer = inPoint_ - delay; // read chases write + delay_ = delay; + + while ( outPointer < 0 ) + outPointer += inputs_.size(); // modulo maximum length + + outPoint_ = (long) outPointer; // integer part + + alpha_ = outPointer - outPoint_; // fractional part + omAlpha_ = (StkFloat) 1.0 - alpha_; + + if ( outPoint_ == inputs_.size() ) outPoint_ = 0; + doNextOut_ = true; +} + +inline StkFloat DelayL :: tick( StkFloat input ) +{ + inputs_[inPoint_++] = input * gain_; + + // Increment input pointer modulo length. + if ( inPoint_ == inputs_.size() ) + inPoint_ = 0; + + lastFrame_[0] = nextOut(); + doNextOut_ = true; + + // Increment output pointer modulo length. + if ( ++outPoint_ == inputs_.size() ) + outPoint_ = 0; + + return lastFrame_[0]; +} + +inline StkFrames& DelayL :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "DelayL::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "DelayL::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i soundOrder_; + std::vector soundNumber_; + int nSounding_; +}; + +inline StkFloat Drummer :: tick( unsigned int ) +{ + lastFrame_[0] = 0.0; + if ( nSounding_ == 0 ) return lastFrame_[0]; + + for ( int i=0; i= 0 ) { + if ( waves_[i].isFinished() ) { + // Re-order the list. + for ( int j=0; j soundOrder_[i] ) + soundOrder_[j] -= 1; + } + soundOrder_[i] = -1; + nSounding_--; + } + else + lastFrame_[0] += filters_[i].tick( waves_[i].tick() ); + } + } + + return lastFrame_[0]; +} + +inline StkFrames& Drummer :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Drummer::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "Echo::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Echo::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class Effect + \brief STK abstract effects parent class. + + This class provides common functionality for STK effects + subclasses. It is general enough to support both monophonic and + polyphonic input/output classes. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Effect : public Stk +{ + public: + //! Class constructor. + Effect( void ) { lastFrame_.resize( 1, 1, 0.0 ); }; + + //! Return the number of output channels for the class. + unsigned int channelsOut( void ) const { return lastFrame_.channels(); }; + + //! Return an StkFrames reference to the last output sample frame. + const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Reset and clear all internal state. + virtual void clear() = 0; + + //! Set the mixture of input and "effected" levels in the output (0.0 = input only, 1.0 = effect only). + virtual void setEffectMix( StkFloat mix ); + + protected: + + // Returns true if argument value is prime. + bool isPrime( unsigned int number ); + + StkFrames lastFrame_; + StkFloat effectMix_; + +}; + +inline void Effect :: setEffectMix( StkFloat mix ) +{ + if ( mix < 0.0 ) { + oStream_ << "Effect::setEffectMix: mix parameter is less than zero ... setting to zero!"; + handleError( StkError::WARNING ); + effectMix_ = 0.0; + } + else if ( mix > 1.0 ) { + oStream_ << "Effect::setEffectMix: mix parameter is greater than 1.0 ... setting to one!"; + handleError( StkError::WARNING ); + effectMix_ = 1.0; + } + else + effectMix_ = mix; +} + +inline bool Effect :: isPrime( unsigned int number ) +{ + if ( number == 2 ) return true; + if ( number & 1 ) { + for ( int i=3; i<(int)sqrt((double)number)+1; i+=2 ) + if ( (number % i) == 0 ) return false; + return true; // prime + } + else return false; // even +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/Envelope.h b/7.x/src/stk/include/Envelope.h new file mode 100644 index 0000000..f104169 --- /dev/null +++ b/7.x/src/stk/include/Envelope.h @@ -0,0 +1,131 @@ +#ifndef STK_ENVELOPE_H +#define STK_ENVELOPE_H + +#include "Generator.h" + +namespace stk { + +/***************************************************/ +/*! \class Envelope + \brief STK linear line envelope class. + + This class implements a simple linear line envelope generator + which is capable of ramping to an arbitrary target value by a + specified \e rate. It also responds to simple \e keyOn and \e + keyOff messages, ramping to a specified target (default = 1.0) on + keyOn and to a specified target (default = 0.0) on keyOff. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Envelope : public Generator +{ + public: + + //! Default constructor. + Envelope( void ); + + //! Class destructor. + ~Envelope( void ); + + //! Assignment operator. + Envelope& operator= ( const Envelope& e ); + + //! Start ramping to specified target (default = 1). + void keyOn( StkFloat target = 1.0 ) { this->setTarget( target ); }; + + //! Start ramping to specified target (default = 0). + void keyOff( StkFloat target = 0.0 ) { this->setTarget( target ); }; + + //! Set the \e rate. + /*! + The \e rate must be positive (though a value of 0.0 is allowed). + */ + void setRate( StkFloat rate ); + + //! Set the \e rate based on a positive time duration (seconds). + /*! + The \e rate is calculated such that the envelope will ramp from + the current value to the current target in the specified time duration. + */ + void setTime( StkFloat time ); + + //! Set the target value. + void setTarget( StkFloat target ); + + //! Set current and target values to \e value. + void setValue( StkFloat value ); + + //! Return the current envelope \e state (0 = at target, 1 otherwise). + int getState( void ) const { return state_; }; + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + StkFloat value_; + StkFloat target_; + StkFloat rate_; + int state_; +}; + +inline StkFloat Envelope :: tick( void ) +{ + if ( state_ ) { + if ( target_ > value_ ) { + value_ += rate_; + if ( value_ >= target_ ) { + value_ = target_; + state_ = 0; + } + } + else { + value_ -= rate_; + if ( value_ <= target_ ) { + value_ = target_; + state_ = 0; + } + } + lastFrame_[0] = value_; + } + + return value_; +} + +inline StkFrames& Envelope :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Envelope::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i adsr_; + std::vector waves_; + SineWave vibrato_; + TwoZero twozero_; + unsigned int nOperators_; + StkFloat baseFrequency_; + std::vector ratios_; + std::vector gains_; + StkFloat modDepth_; + StkFloat control1_; + StkFloat control2_; + StkFloat fmGains_[100]; + StkFloat fmSusLevels_[16]; + StkFloat fmAttTimes_[32]; + +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/FMVoices.h b/7.x/src/stk/include/FMVoices.h new file mode 100644 index 0000000..120d80a --- /dev/null +++ b/7.x/src/stk/include/FMVoices.h @@ -0,0 +1,135 @@ +#ifndef STK_FMVOICES_H +#define STK_FMVOICES_H + +#include "FM.h" + +namespace stk { + +/***************************************************/ +/*! \class FMVoices + \brief STK singing FM synthesis instrument. + + This class implements 3 carriers and a common + modulator, also referred to as algorithm 6 of + the TX81Z. + + \code + Algorithm 6 is : + /->1 -\ + 4-|-->2 - +-> Out + \->3 -/ + \endcode + + Control Change Numbers: + - Vowel = 2 + - Spectral Tilt = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class FMVoices : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + FMVoices( void ); + + //! Class destructor. + ~FMVoices( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + int currentVowel_; + StkFloat tilt_[3]; + StkFloat mods_[3]; +}; + +inline StkFloat FMVoices :: tick( unsigned int ) +{ + StkFloat temp, temp2; + + temp = gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + temp2 = vibrato_.tick() * modDepth_ * 0.1; + + waves_[0]->setFrequency(baseFrequency_ * (1.0 + temp2) * ratios_[0]); + waves_[1]->setFrequency(baseFrequency_ * (1.0 + temp2) * ratios_[1]); + waves_[2]->setFrequency(baseFrequency_ * (1.0 + temp2) * ratios_[2]); + waves_[3]->setFrequency(baseFrequency_ * (1.0 + temp2) * ratios_[3]); + + waves_[0]->addPhaseOffset(temp * mods_[0]); + waves_[1]->addPhaseOffset(temp * mods_[1]); + waves_[2]->addPhaseOffset(temp * mods_[2]); + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + twozero_.tick( temp ); + temp = gains_[0] * tilt_[0] * adsr_[0]->tick() * waves_[0]->tick(); + temp += gains_[1] * tilt_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp += gains_[2] * tilt_[2] * adsr_[2]->tick() * waves_[2]->tick(); + + lastFrame_[0] = temp * 0.33; + return lastFrame_[0]; +} + +inline StkFrames& FMVoices :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "FMVoices::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; isetRate( fileSize_ * frequency / Stk::sampleRate() ); }; + + //! Increment the read pointer by \e time samples, modulo file size. + void addTime( StkFloat time ); + + //! Increment current read pointer by \e angle, relative to a looping frequency. + /*! + This function increments the read pointer based on the file + size and the current Stk::sampleRate. The \e anAngle value + is a multiple of file size. + */ + void addPhase( StkFloat angle ); + + //! Add a phase offset to the current read pointer. + /*! + This function determines a time offset based on the file + size and the current Stk::sampleRate. The \e angle value + is a multiple of file size. + */ + void addPhaseOffset( StkFloat angle ); + + //! Return the specified channel value of the last computed frame. + /*! + For multi-channel files, use the lastFrame() function to get + all values from the last computed frame. If no file data is + loaded, the returned value is 0.0. The \c channel argument must + be less than the number of channels in the file data (the first + channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + StkFloat lastOut( unsigned int channel = 0 ) { return FileWvIn::lastOut( channel ); }; + + //! Compute a sample frame and return the specified \c channel value. + /*! + For multi-channel files, use the lastFrame() function to get + all values from the computed frame. If no file data is loaded, + the returned value is 0.0. The \c channel argument must be less + than the number of channels in the file data (the first channel is + specified by 0). However, range checking is only performed if + _STK_DEBUG_ is defined during compilation, in which case an + out-of-range value will trigger an StkError exception. + */ + StkFloat tick( unsigned int channel = 0 ); + + //! Fill the StkFrames object with computed sample frames, starting at the specified channel and return the same reference. + /*! + The \c channel argument plus the number of output channels must + be less than the number of channels in the StkFrames argument (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames,unsigned int channel = 0 ); + + protected: + + StkFrames firstFrame_; + StkFloat phaseOffset_; + +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/FileRead.h b/7.x/src/stk/include/FileRead.h new file mode 100644 index 0000000..e65b49c --- /dev/null +++ b/7.x/src/stk/include/FileRead.h @@ -0,0 +1,141 @@ +#ifndef STK_FILEREAD_H +#define STK_FILEREAD_H + +#include "Stk.h" + +namespace stk { + +/***************************************************/ +/*! \class FileRead + \brief STK audio file input class. + + This class provides input support for various + audio file formats. Multi-channel (>2) + soundfiles are supported. The file data is + returned via an external StkFrames object + passed to the read() function. This class + does not store its own copy of the file data, + rather the data is read directly from disk. + + FileRead currently supports uncompressed WAV, + AIFF/AIFC, SND (AU), MAT-file (Matlab), and + STK RAW file formats. Signed integer (8-, + 16-, 24-, and 32-bit) and floating-point (32- and + 64-bit) data types are supported. Compressed + data types are not supported. + + STK RAW files have no header and are assumed to + contain a monophonic stream of 16-bit signed + integers in big-endian byte order at a sample + rate of 22050 Hz. MAT-file data should be saved + in an array with each data channel filling a + matrix row. The sample rate for MAT-files should + be specified in a variable named "fs". If no + such variable is found, the sample rate is + assumed to be 44100 Hz. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class FileRead : public Stk +{ +public: + //! Default constructor. + FileRead( void ); + + //! Overloaded constructor that opens a file during instantiation. + /*! + An StkError will be thrown if the file is not found or its + format is unknown or unsupported. The optional arguments allow a + headerless file type to be supported. If \c typeRaw is false (the + default), the subsequent parameters are ignored. + */ + FileRead( std::string fileName, bool typeRaw = false, unsigned int nChannels = 1, + StkFormat format = STK_SINT16, StkFloat rate = 22050.0 ); + + //! Class destructor. + ~FileRead( void ); + + //! Open the specified file and determine its formatting. + /*! + An StkError will be thrown if the file is not found or its + format is unknown or unsupported. The optional arguments allow a + headerless file type to be supported. If \c typeRaw is false (the + default), the subsequent parameters are ignored. + */ + void open( std::string fileName, bool typeRaw = false, unsigned int nChannels = 1, + StkFormat format = STK_SINT16, StkFloat rate = 22050.0 ); + + //! If a file is open, close it. + void close( void ); + + //! Returns \e true if a file is currently open. + bool isOpen( void ); + + //! Return the file size in sample frames. + unsigned long fileSize( void ) const { return fileSize_; }; + + //! Return the number of audio channels in the file. + unsigned int channels( void ) const { return channels_; }; + + //! Return the data format of the file. + StkFormat format( void ) const { return dataType_; }; + + //! Return the file sample rate in Hz. + /*! + WAV, SND, and AIF formatted files specify a sample rate in + their headers. By definition, STK RAW files have a sample rate of + 22050 Hz. MAT-files are assumed to have a rate of 44100 Hz. + */ + StkFloat fileRate( void ) const { return fileRate_; }; + + //! Read sample frames from the file into an StkFrames object. + /*! + The number of sample frames to read will be determined from the + number of frames of the StkFrames argument. If this size is + larger than the available data in the file (given the file size + and starting frame index), the extra frames will be unaffected + (the StkFrames object will not be resized). Optional parameters + are provided to specify the starting sample frame within the file + (default = 0) and whether to normalize the data with respect to + fixed-point limits (default = true). An StkError will be thrown + if a file error occurs or if the number of channels in the + StkFrames argument is not equal to that in the file. + */ + void read( StkFrames& buffer, unsigned long startFrame = 0, bool doNormalize = true ); + +protected: + + // Get STK RAW file information. + bool getRawInfo( const char *fileName, unsigned int nChannels, + StkFormat format, StkFloat rate ); + + // Get WAV file header information. + bool getWavInfo( const char *fileName ); + + // Get SND (AU) file header information. + bool getSndInfo( const char *fileName ); + + // Get AIFF file header information. + bool getAifInfo( const char *fileName ); + + // Get MAT-file header information. + bool getMatInfo( const char *fileName ); + + // Helper function for MAT-file parsing. + bool findNextMatArray( SINT32 *chunkSize, SINT32 *rows, SINT32 *columns, SINT32 *nametype ); + + FILE *fd_; + bool byteswap_; + bool wavFile_; + unsigned long fileSize_; + unsigned long dataOffset_; + unsigned int channels_; + StkFormat dataType_; + StkFloat fileRate_; +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/FileWrite.h b/7.x/src/stk/include/FileWrite.h new file mode 100644 index 0000000..6378339 --- /dev/null +++ b/7.x/src/stk/include/FileWrite.h @@ -0,0 +1,116 @@ +#ifndef STK_FILEWRITE_H +#define STK_FILEWRITE_H + +#include "Stk.h" + +namespace stk { + +/***************************************************/ +/*! \class FileWrite + \brief STK audio file output class. + + This class provides output support for various + audio file formats. + + FileWrite writes samples to an audio file. It supports + multi-channel data. + + FileWrite currently supports uncompressed WAV, AIFF, AIFC, SND + (AU), MAT-file (Matlab), and STK RAW file formats. Signed integer + (8-, 16-, 24-, and 32-bit) and floating- point (32- and 64-bit) + data types are supported. STK RAW files use 16-bit integers by + definition. MAT-files will always be written as 64-bit floats. + If a data type specification does not match the specified file + type, the data type will automatically be modified. Compressed + data types are not supported. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class FileWrite : public Stk +{ + public: + + typedef unsigned long FILE_TYPE; + + static const FILE_TYPE FILE_RAW; /*!< STK RAW file type. */ + static const FILE_TYPE FILE_WAV; /*!< WAV file type. */ + static const FILE_TYPE FILE_SND; /*!< SND (AU) file type. */ + static const FILE_TYPE FILE_AIF; /*!< AIFF file type. */ + static const FILE_TYPE FILE_MAT; /*!< Matlab MAT-file type. */ + + //! Default constructor. + FileWrite( void ); + + //! Overloaded constructor used to specify a file name, type, and data format with this object. + /*! + An StkError is thrown for invalid argument values or if an error occurs when initializing the output file. + */ + FileWrite( std::string fileName, unsigned int nChannels = 1, FILE_TYPE type = FILE_WAV, Stk::StkFormat format = STK_SINT16 ); + + //! Class destructor. + virtual ~FileWrite(); + + //! Create a file of the specified type and name and output samples to it in the given data format. + /*! + An StkError is thrown for invalid argument values or if an error occurs when initializing the output file. + */ + void open( std::string fileName, unsigned int nChannels = 1, + FileWrite::FILE_TYPE type = FILE_WAV, Stk::StkFormat format = STK_SINT16 ); + + //! If a file is open, write out samples in the queue and then close it. + void close( void ); + + //! Returns \e true if a file is currently open. + bool isOpen( void ); + + //! Write sample frames from the StkFrames object to the file. + /*! + An StkError will be thrown if the number of channels in the + StkFrames argument does not agree with the number of channels + specified when opening the file. + */ + void write( StkFrames& buffer ); + + protected: + + // Write STK RAW file header. + bool setRawFile( std::string fileName ); + + // Write WAV file header. + bool setWavFile( std::string fileName ); + + // Close WAV file, updating the header. + void closeWavFile( void ); + + // Write SND (AU) file header. + bool setSndFile( std::string fileName ); + + // Close SND file, updating the header. + void closeSndFile( void ); + + // Write AIFF file header. + bool setAifFile( std::string fileName ); + + // Close AIFF file, updating the header. + void closeAifFile( void ); + + // Write MAT-file header. + bool setMatFile( std::string fileName ); + + // Close MAT-file, updating the header. + void closeMatFile( void ); + + FILE *fd_; + FILE_TYPE fileType_; + StkFormat dataType_; + unsigned int channels_; + unsigned long frameCounter_; + bool byteswap_; + +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/FileWvIn.h b/7.x/src/stk/include/FileWvIn.h new file mode 100644 index 0000000..208bafd --- /dev/null +++ b/7.x/src/stk/include/FileWvIn.h @@ -0,0 +1,208 @@ +#ifndef STK_FILEWVIN_H +#define STK_FILEWVIN_H + +#include "WvIn.h" +#include "FileRead.h" + +namespace stk { + +/***************************************************/ +/*! \class FileWvIn + \brief STK audio file input class. + + This class inherits from WvIn. It provides a "tick-level" + interface to the FileRead class. It also provides variable-rate + playback functionality. Audio file support is provided by the + FileRead class. Linear interpolation is used for fractional read + rates. + + FileWvIn supports multi-channel data. It is important to + distinguish the tick() method that computes a single frame (and + returns only the specified sample of a multi-channel frame) from + the overloaded one that takes an StkFrames object for + multi-channel and/or multi-frame data. + + FileWvIn will either load the entire content of an audio file into + local memory or incrementally read file data from disk in chunks. + This behavior is controlled by the optional constructor arguments + \e chunkThreshold and \e chunkSize. File sizes greater than \e + chunkThreshold (in sample frames) will be read incrementally in + chunks of \e chunkSize each (also in sample frames). + + For file data read completely into local memory, the \e doNormalize + flag can be used to normalize all values with respect to the maximum + absolute value of the data. + + If the file data format is fixed point, the flag \e doInt2FloatScaling + can be used to control whether the values are scaled with respect to + the corresponding fixed-point maximum. For example, if reading 16-bit + signed integers, the input values will be scaled by 1 / 32768.0. This + scaling will not happen for floating-point file data formats. + + When the file end is reached, subsequent calls to the tick() + functions return zeros and isFinished() returns \e true. + + See the FileRead class for a description of the supported audio + file formats. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class FileWvIn : public WvIn +{ +public: + //! Default constructor. + FileWvIn( unsigned long chunkThreshold = 1000000, unsigned long chunkSize = 1024 ); + + //! Overloaded constructor for file input. + /*! + An StkError will be thrown if the file is not found, its format is + unknown, or a read error occurs. + */ + FileWvIn( std::string fileName, bool raw = false, bool doNormalize = true, + unsigned long chunkThreshold = 1000000, unsigned long chunkSize = 1024, + bool doInt2FloatScaling = true ); + + //! Class destructor. + ~FileWvIn( void ); + + //! Open the specified file and load its data. + /*! + Data from a previously opened file will be overwritten by this + function. An StkError will be thrown if the file is not found, + its format is unknown, or a read error occurs. If the file length + is less than the chunkThreshold limit and \e doNormalize is true, + the file data will be normalized with respect to the maximum absolute + value of the data. If the \e doInt2FloatScaling flag is true and the + input data is fixed-point, a scaling will be applied with respect to + the fixed-point limits. + */ + virtual void openFile( std::string fileName, bool raw = false, bool doNormalize = true, bool doInt2FloatScaling = true ); + + //! Close a file if one is open. + virtual void closeFile( void ); + + //! Clear outputs and reset time (file) pointer to zero. + virtual void reset( void ); + + //! Normalize data to a maximum of +-1.0. + /*! + This function has no effect when data is incrementally loaded + from disk. + */ + virtual void normalize( void ); + + //! Normalize data to a maximum of \e +-peak. + /*! + This function has no effect when data is incrementally loaded + from disk. + */ + virtual void normalize( StkFloat peak ); + + //! Return the file size in sample frames. + virtual unsigned long getSize( void ) const { return fileSize_; }; + + //! Return the input file sample rate in Hz (not the data read rate). + /*! + WAV, SND, and AIF formatted files specify a sample rate in + their headers. STK RAW files have a sample rate of 22050 Hz + by definition. MAT-files are assumed to have a rate of 44100 Hz. + */ + virtual StkFloat getFileRate( void ) const { return data_.dataRate(); }; + + //! Query whether a file is open. + bool isOpen( void ) { return file_.isOpen(); }; + + //! Query whether reading is complete. + bool isFinished( void ) const { return finished_; }; + + //! Set the data read rate in samples. The rate can be negative. + /*! + If the rate value is negative, the data is read in reverse order. + */ + virtual void setRate( StkFloat rate ); + + //! Increment the read pointer by \e time samples. + /*! + Note that this function will not modify the interpolation flag status. + */ + virtual void addTime( StkFloat time ); + + //! Turn linear interpolation on/off. + /*! + Interpolation is automatically off when the read rate is + an integer value. If interpolation is turned off for a + fractional rate, the time index is truncated to an integer + value. + */ + void setInterpolate( bool doInterpolate ) { interpolate_ = doInterpolate; }; + + //! Return the specified channel value of the last computed frame. + /*! + If no file is loaded, the returned value is 0.0. The \c + channel argument must be less than the number of output channels, + which can be determined with the channelsOut() function (the first + channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. \sa + lastFrame() + */ + StkFloat lastOut( unsigned int channel = 0 ); + + //! Compute a sample frame and return the specified \c channel value. + /*! + For multi-channel files, use the lastFrame() function to get + all values from the computed frame. If no file data is loaded, + the returned value is 0.0. The \c channel argument must be less + than the number of channels in the file data (the first channel is + specified by 0). However, range checking is only performed if + _STK_DEBUG_ is defined during compilation, in which case an + out-of-range value will trigger an StkError exception. + */ + virtual StkFloat tick( unsigned int channel = 0 ); + + //! Fill the StkFrames object with computed sample frames, starting at the specified channel and return the same reference. + /*! + The \c channel argument plus the number of input channels must + be less than the number of channels in the StkFrames argument (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + +protected: + + void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + FileRead file_; + bool finished_; + bool interpolate_; + bool int2floatscaling_; + bool chunking_; + StkFloat time_; + StkFloat rate_; + unsigned long fileSize_; + unsigned long chunkThreshold_; + unsigned long chunkSize_; + long chunkPointer_; + +}; + +inline StkFloat FileWvIn :: lastOut( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= data_.channels() ) { + oStream_ << "FileWvIn::lastOut(): channel argument and soundfile data are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + if ( finished_ ) return 0.0; + return lastFrame_[channel]; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/FileWvOut.h b/7.x/src/stk/include/FileWvOut.h new file mode 100644 index 0000000..886c85f --- /dev/null +++ b/7.x/src/stk/include/FileWvOut.h @@ -0,0 +1,102 @@ +#ifndef STK_FILEWVOUT_H +#define STK_FILEWVOUT_H + +#include "WvOut.h" +#include "FileWrite.h" + +namespace stk { + +/***************************************************/ +/*! \class FileWvOut + \brief STK audio file output class. + + This class inherits from WvOut. It provides a "tick-level" + interface to the FileWrite class. + + FileWvOut writes samples to an audio file and supports + multi-channel data. It is important to distinguish the tick() + method that outputs a single sample to all channels in a sample + frame from the overloaded one that takes a reference to an + StkFrames object for multi-channel and/or multi-frame data. + + See the FileWrite class for a description of the supported audio + file formats. + + Currently, FileWvOut is non-interpolating and the output rate is + always Stk::sampleRate(). + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class FileWvOut : public WvOut +{ + public: + + //! Default constructor with optional output buffer size argument. + /*! + The output buffer size defines the number of frames that are + accumulated between writes to disk. + */ + FileWvOut( unsigned int bufferFrames = 1024 ); + + //! Overloaded constructor used to specify a file name, type, and data format with this object. + /*! + An StkError is thrown for invalid argument values or if an error occurs when initializing the output file. + */ + FileWvOut( std::string fileName, + unsigned int nChannels = 1, + FileWrite::FILE_TYPE type = FileWrite::FILE_WAV, + Stk::StkFormat format = STK_SINT16, + unsigned int bufferFrames = 1024 ); + + //! Class destructor. + virtual ~FileWvOut(); + + //! Open a new file with the specified parameters. + /*! + If a file was previously open, it will be closed. An StkError + will be thrown if any of the specified arguments are invalid or a + file error occurs during opening. + */ + void openFile( std::string fileName, + unsigned int nChannels, + FileWrite::FILE_TYPE type, + Stk::StkFormat format ); + + //! Close a file if one is open. + /*! + Any data remaining in the internal buffer will be written to + the file before closing. + */ + void closeFile( void ); + + //! Output a single sample to all channels in a sample frame. + /*! + An StkError is thrown if an output error occurs. + */ + void tick( const StkFloat sample ); + + //! Output the StkFrames data. + /*! + An StkError will be thrown if an output error occurs. An + StkError will also be thrown if _STK_DEBUG_ is defined during + compilation and there is an incompatability between the number of + channels in the FileWvOut object and that in the StkFrames object. + */ + void tick( const StkFrames& frames ); + + protected: + + void incrementFrame( void ); + + FileWrite file_; + unsigned int bufferFrames_; + unsigned int bufferIndex_; + unsigned int iData_; + +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Filter.h b/7.x/src/stk/include/Filter.h new file mode 100644 index 0000000..b267e20 --- /dev/null +++ b/7.x/src/stk/include/Filter.h @@ -0,0 +1,124 @@ +#ifndef STK_FILTER_H +#define STK_FILTER_H + +#include "Stk.h" +#include +#include + +namespace stk { + +/***************************************************/ +/*! \class Filter + \brief STK abstract filter class. + + This class provides limited common functionality for STK digital + filter subclasses. It is general enough to support both + monophonic and polyphonic input/output classes. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Filter : public Stk +{ +public: + //! Class constructor. + Filter( void ) { gain_ = 1.0; channelsIn_ = 1; lastFrame_.resize( 1, 1, 0.0 ); }; + + //! Return the number of input channels for the class. + unsigned int channelsIn( void ) const { return channelsIn_; }; + + //! Return the number of output channels for the class. + unsigned int channelsOut( void ) const { return lastFrame_.channels(); }; + + //! Clears all internal states of the filter. + virtual void clear( void ); + + //! Set the filter gain. + /*! + The gain is applied at the filter input and does not affect the + coefficient values. The default gain value is 1.0. + */ + void setGain( StkFloat gain ) { gain_ = gain; }; + + //! Return the current filter gain. + StkFloat getGain( void ) const { return gain_; }; + + //! Return the filter phase delay at the specified frequency. + /*! + Note that the phase delay calculation accounts for the filter + gain. The frequency value should be greater than 0.0 and less + than or equal to one-half the sample rate. + */ + StkFloat phaseDelay( StkFloat frequency ); + + //! Return an StkFrames reference to the last output sample frame. + const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ) = 0; + +protected: + + StkFloat gain_; + unsigned int channelsIn_; + StkFrames lastFrame_; + + std::vector b_; + std::vector a_; + StkFrames outputs_; + StkFrames inputs_; + +}; + +inline void Filter :: clear( void ) +{ + unsigned int i; + for ( i=0; i 0.5 * Stk::sampleRate() ) { + oStream_ << "Filter::phaseDelay: argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return 0.0; + } + + StkFloat omegaT = 2 * PI * frequency / Stk::sampleRate(); + StkFloat real = 0.0, imag = 0.0; + for ( unsigned int i=0; i &coefficients ); + + //! Class destructor. + ~Fir( void ); + + //! Set filter coefficients. + /*! + An StkError can be thrown if the coefficient vector size is + zero. The internal state of the filter is not cleared unless the + \e clearState flag is \c true. + */ + void setCoefficients( std::vector &coefficients, bool clearState = false ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Input one sample to the filter and return one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + +protected: + +}; + +inline StkFloat Fir :: tick( StkFloat input ) +{ + lastFrame_[0] = 0.0; + inputs_[0] = gain_ * input; + + for ( unsigned int i=(unsigned int)(b_.size())-1; i>0; i-- ) { + lastFrame_[0] += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + lastFrame_[0] += b_[0] * inputs_[0]; + + return lastFrame_[0]; +} + +inline StkFrames& Fir :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Fir::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int i, hop = frames.channels(); + for ( unsigned int j=0; j0; i-- ) { + *samples += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + *samples += b_[0] * inputs_[0]; + } + + lastFrame_[0] = *(samples-hop); + return frames; +} + +inline StkFrames& Fir :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel ) +{ +#if defined(_STK_DEBUG_) + if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Fir::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int i, iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int j=0; j0; i-- ) { + *oSamples += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + *oSamples += b_[0] * inputs_[0]; + } + + lastFrame_[0] = *(oSamples-oHop); + return iFrames; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Flute.h b/7.x/src/stk/include/Flute.h new file mode 100644 index 0000000..8a27181 --- /dev/null +++ b/7.x/src/stk/include/Flute.h @@ -0,0 +1,167 @@ +#ifndef STK_FLUTE_H +#define STK_FLUTE_H + +#include "Instrmnt.h" +#include "JetTable.h" +#include "DelayL.h" +#include "OnePole.h" +#include "PoleZero.h" +#include "Noise.h" +#include "ADSR.h" +#include "SineWave.h" + +namespace stk { + +/***************************************************/ +/*! \class Flute + \brief STK flute physical model class. + + This class implements a simple flute + physical model, as discussed by Karjalainen, + Smith, Waryznyk, etc. The jet model uses + a polynomial, a la Cook. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Jet Delay = 2 + - Noise Gain = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Flute : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + Flute( StkFloat lowestFrequency ); + + //! Class destructor. + ~Flute( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set the reflection coefficient for the jet delay (-1.0 - 1.0). + void setJetReflection( StkFloat coefficient ) { jetReflection_ = coefficient; }; + + //! Set the reflection coefficient for the air column delay (-1.0 - 1.0). + void setEndReflection( StkFloat coefficient ) { endReflection_ = coefficient; }; + + //! Set the length of the jet delay in terms of a ratio of jet delay to air column delay lengths. + void setJetDelay( StkFloat aRatio ); + + //! Apply breath velocity to instrument with given amplitude and rate of increase. + void startBlowing( StkFloat amplitude, StkFloat rate ); + + //! Decrease breath velocity with given rate of decrease. + void stopBlowing( StkFloat rate ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayL jetDelay_; + DelayL boreDelay_; + JetTable jetTable_; + OnePole filter_; + PoleZero dcBlock_; + Noise noise_; + ADSR adsr_; + SineWave vibrato_; + + StkFloat lastFrequency_; + StkFloat maxPressure_; + StkFloat jetReflection_; + StkFloat endReflection_; + StkFloat noiseGain_; + StkFloat vibratoGain_; + StkFloat outputGain_; + StkFloat jetRatio_; + +}; + +inline StkFloat Flute :: tick( unsigned int ) +{ + StkFloat pressureDiff; + StkFloat breathPressure; + + // Calculate the breath pressure (envelope + noise + vibrato) + breathPressure = maxPressure_ * adsr_.tick(); + breathPressure += breathPressure * ( noiseGain_ * noise_.tick() + vibratoGain_ * vibrato_.tick() ); + + StkFloat temp = -filter_.tick( boreDelay_.lastOut() ); + //temp = dcBlock_.tick( temp ); // Block DC on reflection. + + pressureDiff = breathPressure - (jetReflection_ * temp); + pressureDiff = jetDelay_.tick( pressureDiff ); + //pressureDiff = jetTable_.tick( pressureDiff ) + (endReflection_ * temp); + pressureDiff = dcBlock_.tick(jetTable_.tick( pressureDiff )) + (endReflection_ * temp); // moved the DC blocker to after the jet non-linearity (GPS, 29 Jan. 2020) + lastFrame_[0] = (StkFloat) 0.3 * boreDelay_.tick( pressureDiff ); + + lastFrame_[0] *= outputGain_; + return lastFrame_[0]; +} + +inline StkFrames& Flute :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Flute::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= 1.0. The \e frequency value should be between zero and + half the sample rate. + */ + void setResonance( StkFloat frequency, StkFloat radius ); + + //! Set both the current and target resonance parameters. + void setStates( StkFloat frequency, StkFloat radius, StkFloat gain = 1.0 ); + + //! Set target resonance parameters. + void setTargets( StkFloat frequency, StkFloat radius, StkFloat gain = 1.0 ); + + //! Set the sweep rate (between 0.0 - 1.0). + /*! + The formant parameters are varied in increments of the + sweep rate between their current and target values. + A sweep rate of 1.0 will produce an immediate change in + resonance parameters from their current values to the + target values. A sweep rate of 0.0 will produce no + change in resonance parameters. + */ + void setSweepRate( StkFloat rate ); + + //! Set the sweep rate in terms of a time value in seconds. + /*! + This method adjusts the sweep rate based on a + given time for the formant parameters to reach + their target values. + */ + void setSweepTime( StkFloat time ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Input one sample to the filter and return a reference to one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + virtual void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + bool dirty_; + StkFloat frequency_; + StkFloat radius_; + StkFloat startFrequency_; + StkFloat startRadius_; + StkFloat startGain_; + StkFloat targetFrequency_; + StkFloat targetRadius_; + StkFloat targetGain_; + StkFloat deltaFrequency_; + StkFloat deltaRadius_; + StkFloat deltaGain_; + StkFloat sweepState_; + StkFloat sweepRate_; + +}; + +inline StkFloat FormSwep :: tick( StkFloat input ) +{ + if ( dirty_ ) { + sweepState_ += sweepRate_; + if ( sweepState_ >= 1.0 ) { + sweepState_ = 1.0; + dirty_ = false; + radius_ = targetRadius_; + frequency_ = targetFrequency_; + gain_ = targetGain_; + } + else { + radius_ = startRadius_ + (deltaRadius_ * sweepState_); + frequency_ = startFrequency_ + (deltaFrequency_ * sweepState_); + gain_ = startGain_ + (deltaGain_ * sweepState_); + } + this->setResonance( frequency_, radius_ ); + } + + inputs_[0] = gain_ * input; + lastFrame_[0] = b_[0] * inputs_[0] + b_[1] * inputs_[1] + b_[2] * inputs_[2]; + lastFrame_[0] -= a_[2] * outputs_[2] + a_[1] * outputs_[1]; + inputs_[2] = inputs_[1]; + inputs_[1] = inputs_[0]; + outputs_[2] = outputs_[1]; + outputs_[1] = lastFrame_[0]; + + return lastFrame_[0]; +} + +inline StkFrames& FormSwep :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "FormSwep::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "FormSwep::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i 1 ) { + oStream_ << "FreeVerb::lastOut(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline StkFloat FreeVerb::tick( StkFloat inputL, StkFloat inputR, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "FreeVerb::tick(): channel argument must be less than 2!"; + handleError(StkError::FUNCTION_ARGUMENT); + } +#endif + + StkFloat fInput = (inputL + inputR) * gain_; + StkFloat outL = 0.0; + StkFloat outR = 0.0; + + // Parallel LBCF filters + for ( int i = 0; i < nCombs; i++ ) { + // Left channel + //StkFloat yn = fInput + (roomSize_ * FreeVerb::undenormalize(combLPL_[i].tick(FreeVerb::undenormalize(combDelayL_[i].nextOut())))); + StkFloat yn = fInput + (roomSize_ * combLPL_[i].tick( combDelayL_[i].nextOut() ) ); + combDelayL_[i].tick(yn); + outL += yn; + + // Right channel + //yn = fInput + (roomSize_ * FreeVerb::undenormalize(combLPR_[i].tick(FreeVerb::undenormalize(combDelayR_[i].nextOut())))); + yn = fInput + (roomSize_ * combLPR_[i].tick( combDelayR_[i].nextOut() ) ); + combDelayR_[i].tick(yn); + outR += yn; + } + + // Series allpass filters + for ( int i = 0; i < nAllpasses; i++ ) { + // Left channel + //StkFloat vn_m = FreeVerb::undenormalize(allPassDelayL_[i].nextOut()); + StkFloat vn_m = allPassDelayL_[i].nextOut(); + StkFloat vn = outL + (g_ * vn_m); + allPassDelayL_[i].tick(vn); + + // calculate output + outL = -vn + (1.0 + g_)*vn_m; + + // Right channel + //vn_m = FreeVerb::undenormalize(allPassDelayR_[i].nextOut()); + vn_m = allPassDelayR_[i].nextOut(); + vn = outR + (g_ * vn_m); + allPassDelayR_[i].tick(vn); + + // calculate output + outR = -vn + (1.0 + g_)*vn_m; + } + + // Mix output + lastFrame_[0] = outL*wet1_ + outR*wet2_ + inputL*dry_; + lastFrame_[1] = outR*wet1_ + outL*wet2_ + inputR*dry_; + + /* + // Hard limiter ... there's not much else we can do at this point + if ( lastFrame_[0] >= 1.0 ) { + lastFrame_[0] = 0.9999; + } + if ( lastFrame_[0] <= -1.0 ) { + lastFrame_[0] = -0.9999; + } + if ( lastFrame_[1] >= 1.0 ) { + lastFrame_[1] = 0.9999; + } + if ( lastFrame_[1] <= -1.0 ) { + lastFrame_[1] = -0.9999; + } + */ + + return lastFrame_[channel]; +} + +} + +#endif diff --git a/7.x/src/stk/include/Function.h b/7.x/src/stk/include/Function.h new file mode 100644 index 0000000..87aa567 --- /dev/null +++ b/7.x/src/stk/include/Function.h @@ -0,0 +1,41 @@ +#ifndef STK_FUNCTION_H +#define STK_FUNCTION_H + +#include "Stk.h" + +namespace stk { + +/***************************************************/ +/*! \class Function + \brief STK abstract function parent class. + + This class provides common functionality for STK classes that + implement tables or other types of input to output function + mappings. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Function : public Stk +{ + public: + //! Class constructor. + Function( void ) { lastFrame_.resize( 1, 1, 0.0 ); }; + + //! Return the last computed output sample. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Take one sample input and compute one sample of output. + virtual StkFloat tick( StkFloat input ) = 0; + + protected: + + StkFrames lastFrame_; + +}; + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/Generator.h b/7.x/src/stk/include/Generator.h new file mode 100644 index 0000000..ef64c9b --- /dev/null +++ b/7.x/src/stk/include/Generator.h @@ -0,0 +1,50 @@ +#ifndef STK_GENERATOR_H +#define STK_GENERATOR_H + +#include "Stk.h" + +namespace stk { + +/***************************************************/ +/*! \class Generator + \brief STK abstract unit generator parent class. + + This class provides limited common functionality for STK unit + generator sample-source subclasses. It is general enough to + support both monophonic and polyphonic output classes. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Generator : public Stk +{ + public: + + //! Class constructor. + Generator( void ) { lastFrame_.resize( 1, 1, 0.0 ); }; + + //! Return the number of output channels for the class. + unsigned int channelsOut( void ) const { return lastFrame_.channels(); }; + + //! Return an StkFrames reference to the last output sample frame. + const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Fill the StkFrames object with computed sample frames, starting at the specified channel. + /*! + The \c channel argument plus the number of output channels must + be less than the number of channels in the StkFrames argument (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ) = 0; + + protected: + + StkFrames lastFrame_; +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Granulate.h b/7.x/src/stk/include/Granulate.h new file mode 100644 index 0000000..cbaf445 --- /dev/null +++ b/7.x/src/stk/include/Granulate.h @@ -0,0 +1,209 @@ +#ifndef STK_GRANULATE_H +#define STK_GRANULATE_H + +#include +#include "Generator.h" +#include "Envelope.h" +#include "Noise.h" + +namespace stk { + +/***************************************************/ +/*! \class Granulate + \brief STK granular synthesis class. + + This class implements a real-time granular synthesis algorithm + that operates on an input soundfile. Multi-channel files are + supported. Various functions are provided to allow control over + voice and grain parameters. + + The functionality of this class is based on the program MacPod by + Chris Rolfe and Damian Keller, though there are likely to be a + number of differences in the actual implementation. + + by Gary Scavone, 2005--2021. +*/ +/***************************************************/ + +class Granulate: public Generator +{ + public: + //! Default constructor. + Granulate( void ); + + //! Constructor taking input audio file and number of voices arguments. + Granulate( unsigned int nVoices, std::string fileName, bool typeRaw = false ); + + //! Class destructor. + ~Granulate( void ); + + //! Load a monophonic soundfile to be "granulated". + /*! + An StkError will be thrown if the file is not found, its format + is unknown or unsupported, or the file has more than one channel. + */ + void openFile( std::string fileName, bool typeRaw = false ); + + //! Reset the file pointer and all existing grains to the file start. + /*! + Multiple grains are offset from one another in time by grain + duration / nVoices. + */ + void reset( void ); + + //! Set the number of simultaneous grain "voices" to use. + /*! + Multiple grains are offset from one another in time by grain + duration / nVoices. For this reason, it is best to set the grain + parameters before calling this function (during initialization). + */ + void setVoices( unsigned int nVoices = 1 ); + + //! Set the stretch factor used for grain playback (1 - 1000). + /*! + Granular synthesis allows for time-stetching without affecting + the original pitch of a sound. A stretch factor of 4 will produce + a resulting sound of length 4 times the orignal sound. The + default parameter of 1 produces no stretching. + */ + void setStretch( unsigned int stretchFactor = 1 ); + + //! Set global grain parameters used to determine individual grain settings. + /*! + Each grain is defined as having a length of \e duration + milliseconds which must be greater than zero. For values of \e + rampPercent (0 - 100) greater than zero, a linear envelope will be + applied to each grain. If \e rampPercent = 100, the resultant + grain "window" is triangular while \e rampPercent = 50 produces a + trapezoidal window. In addition, each grain can have a time delay + of length \e delay and a grain pointer increment of length \e + offset, which can be negative, before the next ramp onset (in + milliseconds). The \e offset parameter controls grain pointer + jumps between enveloped grain segments, while the \e delay + parameter causes grain calculations to pause between grains. The + actual values calculated for each grain will be randomized by a + factor set using the setRandomFactor() function. + */ + void setGrainParameters( unsigned int duration = 30, unsigned int rampPercent = 50, + int offset = 0, unsigned int delay = 0 ); + + //! This factor is used when setting individual grain parameters (0.0 - 1.0). + /*! + This random factor is applied when all grain state durations + are calculated. If set to 0.0, no randomness occurs. When + randomness = 1.0, a grain segment of length \e duration will be + randomly augmented by up to +- \e duration seconds (i.e., a 30 + millisecond length will be augmented by an extra length of up to + +30 or -30 milliseconds). + */ + void setRandomFactor( StkFloat randomness = 0.1 ); + + //! Return the specified channel value of the last computed frame. + /*! + The \c channel argument must be less than the number of output + channels, which can be determined with the channelsOut() function + (the first channel is specified by 0). However, range checking is + only performed if _STK_DEBUG_ is defined during compilation, in + which case an out-of-range value will trigger an StkError + exception. \sa lastFrame() + */ + StkFloat lastOut( unsigned int channel = 0 ); + + //! Compute one sample frame and return the specified \c channel value. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill the StkFrames object with computed sample frames, starting at the specified channel. + /*! + The \c channel argument plus the number of output channels must + be less than the number of channels in the StkFrames argument (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + enum GrainState { + GRAIN_STOPPED, + GRAIN_FADEIN, + GRAIN_SUSTAIN, + GRAIN_FADEOUT + }; + + protected: + + struct Grain { + StkFloat eScaler; + StkFloat eRate; + unsigned long attackCount; + unsigned long sustainCount; + unsigned long decayCount; + unsigned long delayCount; + unsigned long counter; + //unsigned long pointer; + StkFloat pointer; + unsigned long startPointer; + unsigned int repeats; + GrainState state; + + // Default constructor. + Grain() + :eScaler(0.0), eRate(0.0), attackCount(0), sustainCount(0), decayCount(0), + delayCount(0), counter(0), pointer(0), startPointer(0), repeats(0), state(GRAIN_STOPPED) {} + }; + + void calculateGrain( Granulate::Grain& grain ); + + StkFrames data_; + std::vector grains_; + Noise noise; + //long gPointer_; + StkFloat gPointer_; + + // Global grain parameters. + unsigned int gDuration_; + unsigned int gRampPercent_; + unsigned int gDelay_; + unsigned int gStretch_; + unsigned int stretchCounter_; + int gOffset_; + StkFloat gRandomFactor_; + StkFloat gain_; + +}; + +inline StkFloat Granulate :: lastOut( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= lastFrame_.channels() ) { + oStream_ << "Granulate::lastOut(): channel argument is invalid!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline StkFrames& Granulate :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Granulate::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + for ( unsigned int i=0; i strings_; + std::vector< int > stringState_; // 0 = off, 1 = decaying, 2 = on + std::vector< unsigned int > decayCounter_; + std::vector< unsigned int > filePointer_; + std::vector< StkFloat > pluckGains_; + + OnePole pickFilter_; + OnePole couplingFilter_; + StkFloat couplingGain_; + StkFrames excitation_; + StkFrames lastFrame_; +}; + +// NOTE: It is not possible to implement the Smith coupled string model here because the Twang class does +// not currently offer the chance to have access to a traveling-wave component. Thus, the coupling +// implemented here is approximate. +inline StkFloat Guitar :: tick( StkFloat input ) +{ + StkFloat temp, output = 0.0; + lastFrame_[0] = couplingGain_ * couplingFilter_.tick( lastFrame_[0] ) / strings_.size(); + for ( unsigned int i=0; i 0.2 ) + temp += pluckGains_[i] * excitation_[filePointer_[i]++]; + temp += lastFrame_[0]; // bridge coupling + output += strings_[i].tick( temp ); + // Check if string energy has decayed sufficiently to turn it off. + if ( stringState_[i] == 1 ) { + if ( fabs( strings_[i].lastOut() ) < 0.001 ) decayCounter_[i]++; + else decayCounter_[i] = 0; + if ( decayCounter_[i] > (unsigned int) floor( 0.1 * Stk::sampleRate() ) ) { + stringState_[i] = 0; + decayCounter_[i] = 0; + } + } + } + } + + return lastFrame_[0] = output; +} + +inline StkFrames& Guitar :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Guitar::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Guitar::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i2-- + -->1-->Out + \endcode + + Control Change Numbers: + - Total Modulator Index = 2 + - Modulator Crossfade = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class HevyMetl : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + HevyMetl( void ); + + //! Class destructor. + ~HevyMetl( void ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat HevyMetl :: tick( unsigned int ) +{ + StkFloat temp; + + temp = vibrato_.tick() * modDepth_ * 0.2; + waves_[0]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[0]); + waves_[1]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[1]); + waves_[2]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[2]); + waves_[3]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[3]); + + temp = gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + waves_[1]->addPhaseOffset( temp ); + + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = (1.0 - (control2_ * 0.5)) * gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + twozero_.tick(temp); + + temp += control2_ * 0.5 * gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp = temp * control1_; + + waves_[0]->addPhaseOffset( temp ); + temp = gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + + lastFrame_[0] = temp * 0.5; + return lastFrame_[0]; +} + +inline StkFrames& HevyMetl :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "HevyMetl::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i &bCoefficients, std::vector &aCoefficients ); + + //! Class destructor. + ~Iir( void ); + + //! Set filter coefficients. + /*! + An StkError can be thrown if either of the coefficient vector + sizes is zero, or if the a[0] coefficient is equal to zero. If + a[0] is not equal to 1, the filter coeffcients are normalized by + a[0]. The internal state of the filter is not cleared unless the + \e clearState flag is \c true. + */ + void setCoefficients( std::vector &bCoefficients, std::vector &aCoefficients, bool clearState = false ); + + //! Set numerator coefficients. + /*! + An StkError can be thrown if coefficient vector is empty. Any + previously set denominator coefficients are left unaffected. Note + that the default constructor sets the single denominator + coefficient a[0] to 1.0. The internal state of the filter is not + cleared unless the \e clearState flag is \c true. + */ + void setNumerator( std::vector &bCoefficients, bool clearState = false ); + + //! Set denominator coefficients. + /*! + An StkError can be thrown if the coefficient vector is empty or + if the a[0] coefficient is equal to zero. Previously set + numerator coefficients are unaffected unless a[0] is not equal to + 1, in which case all coeffcients are normalized by a[0]. Note + that the default constructor sets the single numerator coefficient + b[0] to 1.0. The internal state of the filter is not cleared + unless the \e clearState flag is \c true. + */ + void setDenominator( std::vector &aCoefficients, bool clearState = false ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Input one sample to the filter and return one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + +protected: + +}; + +inline StkFloat Iir :: tick( StkFloat input ) +{ + size_t i; + + outputs_[0] = 0.0; + inputs_[0] = gain_ * input; + for ( i=b_.size()-1; i>0; i-- ) { + outputs_[0] += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + outputs_[0] += b_[0] * inputs_[0]; + + for ( i=a_.size()-1; i>0; i-- ) { + outputs_[0] += -a_[i] * outputs_[i]; + outputs_[i] = outputs_[i-1]; + } + + lastFrame_[0] = outputs_[0]; + return lastFrame_[0]; +} + +inline StkFrames& Iir :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Iir::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + size_t i; + unsigned int hop = frames.channels(); + for ( unsigned int j=0; j0; i-- ) { + outputs_[0] += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + outputs_[0] += b_[0] * inputs_[0]; + + for ( i=a_.size()-1; i>0; i-- ) { + outputs_[0] += -a_[i] * outputs_[i]; + outputs_[i] = outputs_[i-1]; + } + + *samples = outputs_[0]; + } + + lastFrame_[0] = *(samples-hop); + return frames; +} + +inline StkFrames& Iir :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel ) +{ +#if defined(_STK_DEBUG_) + if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Iir::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + size_t i; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int j=0; j0; i-- ) { + outputs_[0] += b_[i] * inputs_[i]; + inputs_[i] = inputs_[i-1]; + } + outputs_[0] += b_[0] * inputs_[0]; + + for ( i=a_.size()-1; i>0; i-- ) { + outputs_[0] += -a_[i] * outputs_[i]; + outputs_[i] = outputs_[i-1]; + } + + *oSamples = outputs_[0]; + } + + lastFrame_[0] = *(oSamples-oHop); + return iFrames; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Instrmnt.h b/7.x/src/stk/include/Instrmnt.h new file mode 100644 index 0000000..c15410c --- /dev/null +++ b/7.x/src/stk/include/Instrmnt.h @@ -0,0 +1,108 @@ +#ifndef STK_INSTRMNT_H +#define STK_INSTRMNT_H + +#include "Stk.h" + +namespace stk { + +/***************************************************/ +/*! \class Instrmnt + \brief STK instrument abstract base class. + + This class provides a common interface for + all STK instruments. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Instrmnt : public Stk +{ + public: + //! Class constructor. + Instrmnt( void ) { lastFrame_.resize( 1, 1, 0.0 ); }; + + //! Reset and clear all internal state (for subclasses). + /*! + Not all subclasses implement a clear() function. + */ + virtual void clear( void ) {}; + + //! Start a note with the given frequency and amplitude. + virtual void noteOn( StkFloat frequency, StkFloat amplitude ) = 0; + + //! Stop a note with the given amplitude (speed of decay). + virtual void noteOff( StkFloat amplitude ) = 0; + + //! Set instrument parameters for a particular frequency. + virtual void setFrequency( StkFloat frequency ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + virtual void controlChange(int number, StkFloat value); + + //! Return the number of output channels for the class. + unsigned int channelsOut( void ) const { return lastFrame_.channels(); }; + + //! Return an StkFrames reference to the last output sample frame. + const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Return the specified channel value of the last computed frame. + /*! + The \c channel argument must be less than the number of output + channels, which can be determined with the channelsOut() function + (the first channel is specified by 0). However, range checking is + only performed if _STK_DEBUG_ is defined during compilation, in + which case an out-of-range value will trigger an StkError + exception. \sa lastFrame() + */ + StkFloat lastOut( unsigned int channel = 0 ); + + //! Compute one sample frame and return the specified \c channel value. + /*! + For monophonic instruments, the \c channel argument is ignored. + */ + virtual StkFloat tick( unsigned int channel = 0 ) = 0; + + //! Fill the StkFrames object with computed sample frames, starting at the specified channel. + /*! + The \c channel argument plus the number of output channels must + be less than the number of channels in the StkFrames argument (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ) = 0; + + protected: + + StkFrames lastFrame_; + +}; + +inline void Instrmnt :: setFrequency( StkFloat frequency ) +{ + oStream_ << "Instrmnt::setFrequency: virtual setFrequency function call!"; + handleError( StkError::WARNING ); +} + +inline StkFloat Instrmnt :: lastOut( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= lastFrame_.channels() ) { + oStream_ << "Instrmnt::lastOut(): channel argument is invalid!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline void Instrmnt :: controlChange( int number, StkFloat value ) +{ + oStream_ << "Instrmnt::controlChange: virtual function call!"; + handleError( StkError::WARNING ); +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/JCRev.h b/7.x/src/stk/include/JCRev.h new file mode 100644 index 0000000..7acd0eb --- /dev/null +++ b/7.x/src/stk/include/JCRev.h @@ -0,0 +1,166 @@ +#ifndef STK_JCREV_H +#define STK_JCREV_H + +#include "Effect.h" +#include "Delay.h" +#include "OnePole.h" + +namespace stk { + +/***************************************************/ +/*! \class JCRev + \brief John Chowning's reverberator class. + + This class takes a monophonic input signal and + produces a stereo output signal. It is derived + from the CLM JCRev function, which is based on + the use of networks of simple allpass and comb + delay filters. This class implements three + series allpass units, followed by four parallel + comb filters, and two decorrelation delay lines + in parallel at the output. + + Although not in the original JC reverberator, + one-pole lowpass filters have been added inside + the feedback comb filters. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class JCRev : public Effect +{ + public: + //! Class constructor taking a T60 decay time argument (one second default value). + JCRev( StkFloat T60 = 1.0 ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set the reverberation T60 decay time. + void setT60( StkFloat T60 ); + + //! Return the specified channel value of the last computed stereo frame. + /*! + Use the lastFrame() function to get both values of the last + computed stereo frame. The \c channel argument must be 0 or 1 + (the first channel is specified by 0). However, range checking is + only performed if _STK_DEBUG_ is defined during compilation, in + which case an out-of-range value will trigger an StkError + exception. + */ + StkFloat lastOut( unsigned int channel = 0 ); + + //! Input one sample to the effect and return the specified \c channel value of the computed stereo frame. + /*! + Use the lastFrame() function to get both values of the computed + stereo output frame. The \c channel argument must be 0 or 1 (the + first channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + StkFloat tick( StkFloat input, unsigned int channel = 0 ); + + //! Take a channel of the StkFrames object as inputs to the effect and replace with stereo outputs. + /*! + The StkFrames argument reference is returned. The stereo + outputs are written to the StkFrames argument starting at the + specified \c channel. Therefore, the \c channel argument must be + less than ( channels() - 1 ) of the StkFrames argument (the first + channel is specified by 0). However, range checking is only + performed if _STK_DEBUG_ is defined during compilation, in which + case an out-of-range value will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the effect and write stereo outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. The \c iChannel + argument must be less than the number of channels in the \c + iFrames argument (the first channel is specified by 0). The \c + oChannel argument must be less than ( channels() - 1 ) of the \c + oFrames argument. However, range checking is only performed if + _STK_DEBUG_ is defined during compilation, in which case an + out-of-range value will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + Delay allpassDelays_[3]; + Delay combDelays_[4]; + OnePole combFilters_[4]; + Delay outLeftDelay_; + Delay outRightDelay_; + StkFloat allpassCoefficient_; + StkFloat combCoefficient_[4]; + +}; + +inline StkFloat JCRev :: lastOut( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "JCRev::lastOut(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline StkFloat JCRev :: tick( StkFloat input, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "JCRev::tick(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat temp, temp0, temp1, temp2, temp3, temp4, temp5, temp6; + StkFloat filtout; + + temp = allpassDelays_[0].lastOut(); + temp0 = allpassCoefficient_ * temp; + temp0 += input; + allpassDelays_[0].tick(temp0); + temp0 = -(allpassCoefficient_ * temp0) + temp; + + temp = allpassDelays_[1].lastOut(); + temp1 = allpassCoefficient_ * temp; + temp1 += temp0; + allpassDelays_[1].tick(temp1); + temp1 = -(allpassCoefficient_ * temp1) + temp; + + temp = allpassDelays_[2].lastOut(); + temp2 = allpassCoefficient_ * temp; + temp2 += temp1; + allpassDelays_[2].tick(temp2); + temp2 = -(allpassCoefficient_ * temp2) + temp; + + temp3 = temp2 + ( combFilters_[0].tick( combCoefficient_[0] * combDelays_[0].lastOut() ) ); + temp4 = temp2 + ( combFilters_[1].tick( combCoefficient_[1] * combDelays_[1].lastOut() ) ); + temp5 = temp2 + ( combFilters_[2].tick( combCoefficient_[2] * combDelays_[2].lastOut() ) ); + temp6 = temp2 + ( combFilters_[3].tick( combCoefficient_[3] * combDelays_[3].lastOut() ) ); + + combDelays_[0].tick(temp3); + combDelays_[1].tick(temp4); + combDelays_[2].tick(temp5); + combDelays_[3].tick(temp6); + + filtout = temp3 + temp4 + temp5 + temp6; + + lastFrame_[0] = effectMix_ * (outLeftDelay_.tick(filtout)); + lastFrame_[1] = effectMix_ * (outRightDelay_.tick(filtout)); + temp = (1.0 - effectMix_) * input; + lastFrame_[0] += temp; + lastFrame_[1] += temp; + + return 0.7 * lastFrame_[channel]; +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/JetTable.h b/7.x/src/stk/include/JetTable.h new file mode 100644 index 0000000..1d628c8 --- /dev/null +++ b/7.x/src/stk/include/JetTable.h @@ -0,0 +1,112 @@ +#ifndef STK_JETTABL_H +#define STK_JETTABL_H + +#include "Function.h" + +namespace stk { + +/***************************************************/ +/*! \class JetTable + \brief STK jet table class. + + This class implements a flue jet non-linear + function, computed by a polynomial calculation. + Contrary to the name, this is not a "table". + + Consult Fletcher and Rossing, Karjalainen, + Cook, and others for more information. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class JetTable : public Function +{ +public: + + //! Take one sample input and map to one sample of output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the table and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the table and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + +}; + +inline StkFloat JetTable :: tick( StkFloat input ) +{ + // Perform "table lookup" using a polynomial + // calculation (x^3 - x), which approximates + // the jet sigmoid behavior. + lastFrame_[0] = input * (input * input - 1.0); + + // Saturate at +/- 1.0. + if ( lastFrame_[0] > 1.0 ) lastFrame_[0] = 1.0; + if ( lastFrame_[0] < -1.0 ) lastFrame_[0] = -1.0; + return lastFrame_[0]; +} + +inline StkFrames& JetTable :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "JetTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i 1.0) *samples = 1.0; + if ( *samples < -1.0) *samples = -1.0; + } + + lastFrame_[0] = *(samples-hop); + return frames; +} + +inline StkFrames& JetTable :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel ) +{ +#if defined(_STK_DEBUG_) + if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "JetTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i 1.0) *oSamples = 1.0; + if ( *oSamples < -1.0) *oSamples = -1.0; + } + + lastFrame_[0] = *(oSamples-oHop); + return iFrames; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/LentPitShift.h b/7.x/src/stk/include/LentPitShift.h new file mode 100644 index 0000000..dff5648 --- /dev/null +++ b/7.x/src/stk/include/LentPitShift.h @@ -0,0 +1,263 @@ +#ifndef STK_LENTPITSHIFT_H +#define STK_LENTPITSHIFT_H + +#include "Effect.h" +#include "Delay.h" + +namespace stk { + +/***************************************************/ +/*! \class LentPitShift + \brief Pitch shifter effect class based on the Lent algorithm. + + This class implements a pitch shifter using pitch + tracking and sample windowing and shifting. + + by Francois Germain, 2009. +*/ +/***************************************************/ + +class LentPitShift : public Effect +{ + public: + //! Class constructor. + LentPitShift( StkFloat periodRatio = 1.0, int tMax = RT_BUFFER_SIZE ); + + ~LentPitShift( void ) { + delete window; + delete dt; + delete dpt; + delete cumDt; + } + + //! Reset and clear all internal state. + void clear( void ); + + //! Set the pitch shift factor (1.0 produces no shift). + void setShift( StkFloat shift ); + + //! Input one sample to the filter and return one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + //! Apply the effect on the input samples and store it. + /*! + The samples stored in the input frame vector are processed + and the delayed result are stored in the output frame vector. + */ + void process( ); + + // Frame storage vectors for process function + StkFrames inputFrames; + StkFrames outputFrames; + int ptrFrames; // writing pointer + + // Input delay line + Delay inputLine_; + int inputPtr; + + // Output delay line + Delay outputLine_; + double outputPtr; + + // Pitch tracker variables + unsigned long tMax_; // Maximal period measurable by the pitch tracker. + // It is also the size of the window used by the pitch tracker and + // the size of the frames that can be computed by the tick function + + StkFloat threshold_; // Threshold of detection for the pitch tracker + unsigned long lastPeriod_; // Result of the last pitch tracking loop + StkFloat* dt; // Array containing the euclidian distance coefficients + StkFloat* cumDt; // Array containing the cumulative sum of the coefficients in dt + StkFloat* dpt; // Array containing the pitch tracking function coefficients + + // Pitch shifter variables + StkFloat env[2]; // Coefficients for the linear interpolation when modifying the output samples + StkFloat* window; // Hamming window used for the input portion extraction + double periodRatio_; // Ratio of modification of the signal period + StkFrames zeroFrame; // Frame of tMax_ zero samples + + + // Coefficient delay line that could be used for a dynamic calculation of the pitch + //Delay* coeffLine_; + +}; + +inline void LentPitShift::process() +{ + StkFloat x_t; // input coefficient + StkFloat x_t_T; // previous input coefficient at T samples + StkFloat coeff; // new coefficient for the difference function + + unsigned long alternativePitch = tMax_; // Global minimum storage + lastPeriod_ = tMax_+1; // Storage of the lowest local minimum under the threshold + + // Loop variables + unsigned long delay_; + unsigned int n; + + // Initialization of the dt coefficients. Since the + // frames are of tMax_ length, there is no overlapping + // between the successive windows where pitch tracking + // is performed. + for ( delay_=1; delay_<=tMax_; delay_++ ) + dt[delay_] = 0.; + + // Calculation of the dt coefficients and update of the input delay line. + for ( n=0; n 1 && dpt[delay_-1]-dpt[delay_-2] < 0 && dpt[delay_]-dpt[delay_-1] > 0 ) { + // Check if the minimum is under the threshold + if ( dpt[delay_-1] < threshold_ ){ + lastPeriod_ = delay_-1; + // If a minimum is found, we can stop the loop + break; + } + else if ( dpt[alternativePitch] > dpt[delay_-1] ) + // Otherwise we store it if it is the current global minimum + alternativePitch = delay_-1; + } + } + + // Test for the last period length. + if ( dpt[delay_]-dpt[delay_-1] < 0 ) { + if ( dpt[delay_] < threshold_ ) + lastPeriod_ = delay_; + else if ( dpt[alternativePitch] > dpt[delay_] ) + alternativePitch = delay_; + } + + if ( lastPeriod_ == tMax_+1 ) + // No period has been under the threshold so we used the global minimum + lastPeriod_ = alternativePitch; + + // We put the new zero output coefficients in the output delay line and + // we get the previous calculated coefficients + outputLine_.tick( zeroFrame, outputFrames ); + + // Initialization of the Hamming window used in the algorithm + for ( int n=-(int)lastPeriod_; n<(int)lastPeriod_; n++ ) + window[n+lastPeriod_] = (1 + cos(PI*n/lastPeriod_)) / 2 ; + + long M; // Index of reading in the input delay line + long N; // Index of writing in the output delay line + double sample; // Temporary storage for the new coefficient + + // We loop for all the frames of length lastPeriod_ presents between inputPtr and tMax_ + for ( ; inputPtr<(int)(tMax_-lastPeriod_); inputPtr+=lastPeriod_ ) { + // Test for the decision of compression/expansion + while ( outputPtr < inputPtr ) { + // Coefficients for the linear interpolation + env[1] = fmod( outputPtr + tMax_, 1.0 ); + env[0] = 1.0 - env[1]; + M = tMax_ - inputPtr + lastPeriod_ - 1; // New reading pointer + N = 2*tMax_ - (unsigned long)floor(outputPtr + tMax_) + lastPeriod_ - 1; // New writing pointer + for ( unsigned int j=0; j<2*lastPeriod_; j++,M--,N-- ) { + sample = inputLine_.tapOut(M) * window[j] / 2.; + // Linear interpolation + outputLine_.addTo(env[0] * sample, N); + outputLine_.addTo(env[1] * sample, N-1); + } + outputPtr = outputPtr + lastPeriod_ * periodRatio_; // new output pointer + } + } + // Shifting of the pointers waiting for the new frame of length tMax_. + outputPtr -= tMax_; + inputPtr -= tMax_; +} + + +inline StkFloat LentPitShift :: tick( StkFloat input ) +{ + StkFloat sample; + + inputFrames[ptrFrames] = input; + + sample = outputFrames[ptrFrames++]; + + // Check for end condition + if ( ptrFrames == (int) inputFrames.size() ){ + ptrFrames = 0; + process( ); + } + + return sample; +} + +inline StkFrames& LentPitShift :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "LentPitShift::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "LentPitShift::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "Mandolin::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "Mesh2D::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i +#include +#include +#include + +namespace stk { + +/**********************************************************************/ +/*! \class MidiFileIn + \brief A standard MIDI file reading/parsing class. + + This class can be used to read events from a standard MIDI file. + Event bytes are copied to a C++ vector and must be subsequently + interpreted by the user. The function getNextMidiEvent() skips + meta and sysex events, returning only MIDI channel messages. + Event delta-times are returned in the form of "ticks" and a + function is provided to determine the current "seconds per tick". + Tempo changes are internally tracked by the class and reflected in + the values returned by the function getTickSeconds(). + + by Gary P. Scavone, 2003--2021. +*/ +/**********************************************************************/ + +class MidiFileIn : public Stk +{ + public: + //! Default constructor. + /*! + If an error occurs while opening or parsing the file header, an + StkError exception will be thrown. + */ + MidiFileIn( std::string fileName ); + + //! Class destructor. + ~MidiFileIn(); + + //! Return the MIDI file format (0, 1, or 2). + int getFileFormat() const { return format_; }; + + //! Return the number of tracks in the MIDI file. + unsigned int getNumberOfTracks() const { return nTracks_; }; + + //! Return the MIDI file division value from the file header. + /*! + Note that this value must be "parsed" in accordance with the + MIDI File Specification. In particular, if the MSB is set, the + file uses time-code representations for delta-time values. + */ + int getDivision() const { return division_; }; + + //! Move the specified track event reader to the beginning of its track. + /*! + The relevant track tempo value is reset as well. If an invalid + track number is specified, an StkError exception will be thrown. + */ + void rewindTrack( unsigned int track = 0 ); + + //! Get the current value, in seconds, of delta-time ticks for the specified track. + /*! + This value can change as events are read (via "Set Tempo" + Meta-Events). Therefore, one should call this function after + every call to getNextEvent() or getNextMidiEvent(). If an + invalid track number is specified, an StkError exception will be + thrown. + */ + double getTickSeconds( unsigned int track = 0 ); + + //! Fill the user-provided vector with the next event in the specified track and return the event delta-time in ticks. + /*! + MIDI File events consist of a delta time and a sequence of event + bytes. This function returns the delta-time value and writes + the subsequent event bytes directly to the event vector. The + user must parse the event bytes in accordance with the MIDI File + Specification. All returned MIDI channel events are complete + ... a status byte is provided even when running status is used + in the file. If the track has reached its end, no bytes will be + written and the event vector size will be zero. If an invalid + track number is specified or an error occurs while reading the + file, an StkError exception will be thrown. + */ + unsigned long getNextEvent( std::vector *event, unsigned int track = 0 ); + + //! Fill the user-provided vector with the next MIDI channel event in the specified track and return the event delta time in ticks. + /*! + All returned MIDI events are complete ... a status byte is + provided even when running status is used in the file. Meta and + sysex events in the track are skipped though "Set Tempo" events + are properly parsed for use by the getTickSeconds() function. + If the track has reached its end, no bytes will be written and + the event vector size will be zero. If an invalid track number + is specified or an error occurs while reading the file, an + StkError exception will be thrown. + */ + unsigned long getNextMidiEvent( std::vector *midiEvent, unsigned int track = 0 ); + + protected: + + // This protected class function is used for reading variable-length + // MIDI file values. It is assumed that this function is called with + // the file read pointer positioned at the start of a + // variable-length value. The function returns true if the value is + // successfully parsed. Otherwise, it returns false. + bool readVariableLength( unsigned long *value ); + + std::ifstream file_; + unsigned int nTracks_; + int format_; + int division_; + bool usingTimeCode_; + std::vector tickSeconds_; + std::vector trackPointers_; + std::vector trackOffsets_; + std::vector trackLengths_; + std::vector trackStatus_; + + // This structure and the following variables are used to save and + // keep track of a format 1 tempo map (and the initial tickSeconds + // parameter for formats 0 and 2). + struct TempoChange { + unsigned long count; + double tickSeconds; + }; + std::vector tempoEvents_; + std::vector trackCounters_; + std::vector trackTempoIndex_; +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Modal.h b/7.x/src/stk/include/Modal.h new file mode 100644 index 0000000..626ac6b --- /dev/null +++ b/7.x/src/stk/include/Modal.h @@ -0,0 +1,154 @@ +#ifndef STK_MODAL_H +#define STK_MODAL_H + +#include "Instrmnt.h" +#include "Envelope.h" +#include "FileLoop.h" +#include "SineWave.h" +#include "BiQuad.h" +#include "OnePole.h" + +namespace stk { + +/***************************************************/ +/*! \class Modal + \brief STK resonance model abstract base class. + + This class contains an excitation wavetable, + an envelope, an oscillator, and N resonances + (non-sweeping BiQuad filters), where N is set + during instantiation. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Modal : public Instrmnt +{ +public: + //! Class constructor, taking the desired number of modes to create. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + Modal( unsigned int modes = 4 ); + + //! Class destructor. + virtual ~Modal( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + virtual void setFrequency( StkFloat frequency ); + + //! Set the ratio and radius for a specified mode filter. + void setRatioAndRadius( unsigned int modeIndex, StkFloat ratio, StkFloat radius ); + + //! Set the master gain. + void setMasterGain( StkFloat aGain ) { masterGain_ = aGain; }; + + //! Set the direct gain. + void setDirectGain( StkFloat aGain ) { directGain_ = aGain; }; + + //! Set the gain for a specified mode filter. + void setModeGain( unsigned int modeIndex, StkFloat gain ); + + //! Initiate a strike with the given amplitude (0.0 - 1.0). + virtual void strike( StkFloat amplitude ); + + //! Damp modes with a given decay factor (0.0 - 1.0). + void damp( StkFloat amplitude ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + virtual void controlChange( int number, StkFloat value ) = 0; + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + +protected: + + Envelope envelope_; + FileWvIn *wave_; + BiQuad **filters_; + OnePole onepole_; + SineWave vibrato_; + + unsigned int nModes_; + std::vector ratios_; + std::vector radii_; + + StkFloat vibratoGain_; + StkFloat masterGain_; + StkFloat directGain_; + StkFloat stickHardness_; + StkFloat strikePosition_; + StkFloat baseFrequency_; +}; + +inline StkFloat Modal :: tick( unsigned int ) +{ + StkFloat temp = masterGain_ * onepole_.tick( wave_->tick() * envelope_.tick() ); + + StkFloat temp2 = 0.0; + for ( unsigned int i=0; itick(temp); + + temp2 -= temp2 * directGain_; + temp2 += directGain_ * temp; + + if ( vibratoGain_ != 0.0 ) { + // Calculate AM and apply to master out + temp = 1.0 + ( vibrato_.tick() * vibratoGain_ ); + temp2 = temp * temp2; + } + + lastFrame_[0] = temp2; + return lastFrame_[0]; +} + +inline StkFrames& Modal :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Modal::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= noiseRate_ ) { + noise_.tick(); + noiseCounter_ = 0; + } + lastFrame_[0] += filter_.tick( noise_.lastOut() ); + return lastFrame_[0]; +} + +inline StkFrames& Modulate :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Modulate::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; isetFrequency( mSpeed ); }; + + //! Set the modulation (vibrato) depth. + void setModulationDepth( StkFloat mDepth ) { modDepth_ = mDepth * 0.5; }; + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + FormSwep filters_[2]; + StkFloat modDepth_; + StkFloat filterQ_; + StkFloat filterRate_; + +}; + +inline StkFloat Moog :: tick( unsigned int ) +{ + StkFloat temp; + + if ( modDepth_ != 0.0 ) { + temp = loops_[1]->tick() * modDepth_; + loops_[0]->setFrequency( baseFrequency_ * (1.0 + temp) ); + } + + temp = attackGain_ * attacks_[0]->tick(); + temp += loopGain_ * loops_[0]->tick(); + temp = filter_.tick( temp ); + temp *= adsr_.tick(); + temp = filters_[0].tick( temp ); + lastFrame_[0] = filters_[1].tick( temp ); + return lastFrame_[0] * 6.0; +} + +inline StkFrames& Moog :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Moog::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i 1 ) { + oStream_ << "NRev::lastOut(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + +inline StkFloat NRev :: tick( StkFloat input, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "NRev::tick(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat temp, temp0, temp1, temp2, temp3; + int i; + + temp0 = 0.0; + for ( i=0; i<6; i++ ) { + temp = input + (combCoefficient_[i] * combDelays_[i].lastOut()); + temp0 += combDelays_[i].tick(temp); + } + + for ( i=0; i<3; i++ ) { + temp = allpassDelays_[i].lastOut(); + temp1 = allpassCoefficient_ * temp; + temp1 += temp0; + allpassDelays_[i].tick(temp1); + temp0 = -(allpassCoefficient_ * temp1) + temp; + } + + // One-pole lowpass filter. + lowpassState_ = 0.7 * lowpassState_ + 0.3 * temp0; + temp = allpassDelays_[3].lastOut(); + temp1 = allpassCoefficient_ * temp; + temp1 += lowpassState_; + allpassDelays_[3].tick( temp1 ); + temp1 = -( allpassCoefficient_ * temp1 ) + temp; + + temp = allpassDelays_[4].lastOut(); + temp2 = allpassCoefficient_ * temp; + temp2 += temp1; + allpassDelays_[4].tick( temp2 ); + lastFrame_[0] = effectMix_*( -( allpassCoefficient_ * temp2 ) + temp ); + + temp = allpassDelays_[5].lastOut(); + temp3 = allpassCoefficient_ * temp; + temp3 += temp1; + allpassDelays_[5].tick( temp3 ); + lastFrame_[1] = effectMix_*( - ( allpassCoefficient_ * temp3 ) + temp ); + + temp = ( 1.0 - effectMix_ ) * input; + lastFrame_[0] += temp; + lastFrame_[1] += temp; + + return lastFrame_[channel]; +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/Noise.h b/7.x/src/stk/include/Noise.h new file mode 100644 index 0000000..3ea46e5 --- /dev/null +++ b/7.x/src/stk/include/Noise.h @@ -0,0 +1,84 @@ +#ifndef STK_NOISE_H +#define STK_NOISE_H + +#include "Generator.h" +#include + +namespace stk { + +/***************************************************/ +/*! \class Noise + \brief STK noise generator. + + Generic random number generation using the + C rand() function. The quality of the rand() + function varies from one OS to another. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Noise : public Generator +{ +public: + + //! Default constructor that can also take a specific seed value. + /*! + If the seed value is zero (the default value), the random number generator is + seeded with the system time. + */ + Noise( unsigned int seed = 0 ); + + //! Seed the random number generator with a specific seed value. + /*! + If no seed is provided or the seed value is zero, the random + number generator is seeded with the current system time. + */ + void setSeed( unsigned int seed = 0 ); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + +protected: + +}; + +inline StkFloat Noise :: tick( void ) +{ + return lastFrame_[0] = (StkFloat) ( 2.0 * rand() / (RAND_MAX + 1.0) - 1.0 ); +} + +inline StkFrames& Noise :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Noise::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "OnePole::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "OnePole::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "OneZero::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "OneZero::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i 1 ) { + oStream_ << "PRCRev::lastOut(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + + inline StkFloat PRCRev :: tick( StkFloat input, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel > 1 ) { + oStream_ << "PRCRev::tick(): channel argument must be less than 2!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat temp, temp0, temp1, temp2, temp3; + + temp = allpassDelays_[0].lastOut(); + temp0 = allpassCoefficient_ * temp; + temp0 += input; + allpassDelays_[0].tick(temp0); + temp0 = -(allpassCoefficient_ * temp0) + temp; + + temp = allpassDelays_[1].lastOut(); + temp1 = allpassCoefficient_ * temp; + temp1 += temp0; + allpassDelays_[1].tick(temp1); + temp1 = -(allpassCoefficient_ * temp1) + temp; + + temp2 = temp1 + ( combCoefficient_[0] * combDelays_[0].lastOut() ); + temp3 = temp1 + ( combCoefficient_[1] * combDelays_[1].lastOut() ); + + lastFrame_[0] = effectMix_ * (combDelays_[0].tick(temp2)); + lastFrame_[1] = effectMix_ * (combDelays_[1].tick(temp3)); + temp = (1.0 - effectMix_) * input; + lastFrame_[0] += temp; + lastFrame_[1] += temp; + + return lastFrame_[channel]; +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/PercFlut.h b/7.x/src/stk/include/PercFlut.h new file mode 100644 index 0000000..1dbedef --- /dev/null +++ b/7.x/src/stk/include/PercFlut.h @@ -0,0 +1,127 @@ +#ifndef STK_PERCFLUT_H +#define STK_PERCFLUT_H + +#include "FM.h" + +namespace stk { + +/***************************************************/ +/*! \class PercFlut + \brief STK percussive flute FM synthesis instrument. + + This class implements algorithm 4 of the TX81Z. + + \code + Algorithm 4 is : 4->3--\ + 2-- + -->1-->Out + \endcode + + Control Change Numbers: + - Total Modulator Index = 2 + - Modulator Crossfade = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class PercFlut : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + PercFlut( void ); + + //! Class destructor. + ~PercFlut( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat PercFlut :: tick( unsigned int ) +{ + StkFloat temp; + + temp = vibrato_.tick() * modDepth_ * 0.2; + waves_[0]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[0]); + waves_[1]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[1]); + waves_[2]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[2]); + waves_[3]->setFrequency(baseFrequency_ * (1.0 + temp) * ratios_[3]); + + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + + twozero_.tick(temp); + waves_[2]->addPhaseOffset( temp ); + temp = (1.0 - (control2_ * 0.5)) * gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + + temp += control2_ * 0.5 * gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp = temp * control1_; + + waves_[0]->addPhaseOffset(temp); + temp = gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + + lastFrame_[0] = temp * 0.5; + return lastFrame_[0]; +} + +inline StkFrames& PercFlut :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "PercFlut::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i maxDelay-12 ) delay_[0] -= delayLength_; + while ( delay_[0] < 12 ) delay_[0] += delayLength_; + + delay_[1] = delay_[0] + halfLength_; + while ( delay_[1] > maxDelay-12 ) delay_[1] -= delayLength_; + while ( delay_[1] < 12 ) delay_[1] += delayLength_; + + // Set the new delay line lengths. + delayLine_[0].setDelay( delay_[0] ); + delayLine_[1].setDelay( delay_[1] ); + + // Calculate a triangular envelope. + env_[1] = fabs( ( delay_[0] - halfLength_ + 12 ) * ( 1.0 / (halfLength_ + 12 ) ) ); + env_[0] = 1.0 - env_[1]; + + // Delay input and apply envelope. + lastFrame_[0] = env_[0] * delayLine_[0].tick( input ); + lastFrame_[0] += env_[1] * delayLine_[1].tick( input ); + + // Compute effect mix and output. + lastFrame_[0] *= effectMix_; + lastFrame_[0] += ( 1.0 - effectMix_ ) * input; + + return lastFrame_[0]; +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/Plucked.h b/7.x/src/stk/include/Plucked.h new file mode 100644 index 0000000..26a2257 --- /dev/null +++ b/7.x/src/stk/include/Plucked.h @@ -0,0 +1,117 @@ +#ifndef STK_PLUCKED_H +#define STK_PLUCKED_H + +#include "Instrmnt.h" +#include "DelayA.h" +#include "OneZero.h" +#include "OnePole.h" +#include "Noise.h" + +namespace stk { + +/***************************************************/ +/*! \class Plucked + \brief STK basic plucked string class. + + This class implements a simple plucked string + physical model based on the Karplus-Strong + algorithm. + + For a more advanced plucked string implementation, + see the stk::Twang class. + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + There exist at least two patents, assigned to + Stanford, bearing the names of Karplus and/or + Strong. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Plucked : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + Plucked( StkFloat lowestFrequency = 10.0 ); + + //! Class destructor. + ~Plucked( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Pluck the string with the given amplitude using the current frequency. + void pluck( StkFloat amplitude ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayA delayLine_; + OneZero loopFilter_; + OnePole pickFilter_; + Noise noise_; + + StkFloat loopGain_; +}; + +inline StkFloat Plucked :: tick( unsigned int ) +{ + // Here's the whole inner loop of the instrument!! + return lastFrame_[0] = 3.0 * delayLine_.tick( loopFilter_.tick( delayLine_.lastOut() * loopGain_ ) ); +} + +inline StkFrames& Plucked :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Plucked::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "PoleZero::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "Recorder::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i 1, the reed has slammed shut and the + // reflection function value saturates at 1.0. + if ( lastFrame_[0] > 1.0) lastFrame_[0] = (StkFloat) 1.0; + + // This is nearly impossible in a physical system, but + // a reflection function value of -1.0 corresponds to + // an open end (and no discontinuity in bore profile). + if ( lastFrame_[0] < -1.0) lastFrame_[0] = (StkFloat) -1.0; + + return lastFrame_[0]; +} + +inline StkFrames& ReedTable :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "ReedTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i 1.0) *samples = 1.0; + if ( *samples < -1.0) *samples = -1.0; + } + + lastFrame_[0] = *(samples-hop); + return frames; +} + +inline StkFrames& ReedTable :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel ) +{ +#if defined(_STK_DEBUG_) + if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "ReedTable::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i 1.0) *oSamples = 1.0; + if ( *oSamples < -1.0) *oSamples = -1.0; + } + + lastFrame_[0] = *(oSamples-oHop); + return iFrames; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Resonate.h b/7.x/src/stk/include/Resonate.h new file mode 100644 index 0000000..4a3b7d4 --- /dev/null +++ b/7.x/src/stk/include/Resonate.h @@ -0,0 +1,124 @@ +#ifndef STK_RESONATE_H +#define STK_RESONATE_H + +#include "Instrmnt.h" +#include "ADSR.h" +#include "BiQuad.h" +#include "Noise.h" + +namespace stk { + +/***************************************************/ +/*! \class Resonate + \brief STK noise driven formant filter. + + This instrument contains a noise source, which + excites a biquad resonance filter, with volume + controlled by an ADSR. + + Control Change Numbers: + - Resonance Frequency (0-Nyquist) = 2 + - Pole Radii = 4 + - Notch Frequency (0-Nyquist) = 11 + - Zero Radii = 1 + - Envelope Gain = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Resonate : public Instrmnt +{ + public: + //! Class constructor. + Resonate( void ); + + //! Class destructor. + ~Resonate( void ); + + //! Set the filter for a resonance at the given frequency (Hz) and radius. + void setResonance( StkFloat frequency, StkFloat radius ); + + //! Set the filter for a notch at the given frequency (Hz) and radius. + void setNotch( StkFloat frequency, StkFloat radius ); + + //! Set the filter zero coefficients for contant resonance gain. + void setEqualGainZeroes( void ) { filter_.setEqualGainZeroes(); }; + + //! Initiate the envelope with a key-on event. + void keyOn( void ) { adsr_.keyOn(); }; + + //! Signal a key-off event to the envelope. + void keyOff( void ) { adsr_.keyOff(); }; + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + ADSR adsr_; + BiQuad filter_; + Noise noise_; + StkFloat poleFrequency_; + StkFloat poleRadius_; + StkFloat zeroFrequency_; + StkFloat zeroRadius_; + +}; + +inline StkFloat Resonate :: tick( unsigned int ) +{ + lastFrame_[0] = filter_.tick( noise_.tick() ); + lastFrame_[0] *= adsr_.tick(); + return lastFrame_[0]; +} + +inline StkFrames& Resonate :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Resonate::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Rhodey : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + Rhodey( void ); + + //! Class destructor. + ~Rhodey( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat Rhodey :: tick( unsigned int ) +{ + StkFloat temp, temp2; + + temp = gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp = temp * control1_; + + waves_[0]->addPhaseOffset( temp ); + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + twozero_.tick(temp); + + waves_[2]->addPhaseOffset( temp ); + temp = ( 1.0 - (control2_ * 0.5)) * gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + temp += control2_ * 0.5 * gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + + // Calculate amplitude modulation and apply it to output. + temp2 = vibrato_.tick() * modDepth_; + temp = temp * (1.0 + temp2); + + lastFrame_[0] = temp * 0.5; + return lastFrame_[0]; +} + +inline StkFrames& Rhodey :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Rhodey::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i_ + + where is the string used in the SKINI stream. + + by Perry R. Cook, 1995--2021. +*/ +/*********************************************************/ + +namespace stk { + +#define NOPE -32767 +#define YEP 1 +#define SK_DBL -32766 +#define SK_INT -32765 +#define SK_STR -32764 +#define __SK_Exit_ 999 + +/***** MIDI COMPATIBLE MESSAGES *****/ +/*** (Status bytes for channel=0) ***/ + +#define __SK_NoteOff_ 128 +#define __SK_NoteOn_ 144 +#define __SK_PolyPressure_ 160 +#define __SK_ControlChange_ 176 +#define __SK_ProgramChange_ 192 +#define __SK_AfterTouch_ 208 +#define __SK_ChannelPressure_ __SK_AfterTouch_ +#define __SK_PitchWheel_ 224 +#define __SK_PitchBend_ __SK_PitchWheel_ +#define __SK_PitchChange_ 49 + +#define __SK_Clock_ 248 +#define __SK_SongStart_ 250 +#define __SK_Continue_ 251 +#define __SK_SongStop_ 252 +#define __SK_ActiveSensing_ 254 +#define __SK_SystemReset_ 255 + +#define __SK_Volume_ 7 +#define __SK_ModWheel_ 1 +#define __SK_Modulation_ __SK_ModWheel_ +#define __SK_Breath_ 2 +#define __SK_FootControl_ 4 +#define __SK_Portamento_ 65 +#define __SK_Balance_ 8 +#define __SK_Pan_ 10 +#define __SK_Sustain_ 64 +#define __SK_Damper_ __SK_Sustain_ +#define __SK_Expression_ 11 + +#define __SK_AfterTouch_Cont_ 128 +#define __SK_ModFrequency_ __SK_Expression_ + +#define __SK_ProphesyRibbon_ 16 +#define __SK_ProphesyWheelUp_ 2 +#define __SK_ProphesyWheelDown_ 3 +#define __SK_ProphesyPedal_ 18 +#define __SK_ProphesyKnob1_ 21 +#define __SK_ProphesyKnob2_ 22 + +/*** Instrument Family Specific ***/ + +#define __SK_NoiseLevel_ __SK_FootControl_ + +#define __SK_PickPosition_ __SK_FootControl_ +#define __SK_StringDamping_ __SK_Expression_ +#define __SK_StringDetune_ __SK_ModWheel_ +#define __SK_BodySize_ __SK_Breath_ +#define __SK_BowPressure_ __SK_Breath_ +#define __SK_BowPosition_ __SK_PickPosition_ +#define __SK_BowBeta_ __SK_BowPosition_ + +#define __SK_ReedStiffness_ __SK_Breath_ +#define __SK_ReedRestPos_ __SK_FootControl_ + +#define __SK_FluteEmbouchure_ __SK_Breath_ +#define __SK_JetDelay_ __SK_FluteEmbouchure_ + +#define __SK_LipTension_ __SK_Breath_ +#define __SK_SlideLength_ __SK_FootControl_ + +#define __SK_StrikePosition_ __SK_PickPosition_ +#define __SK_StickHardness_ __SK_Breath_ + +#define __SK_TrillDepth_ 1051 +#define __SK_TrillSpeed_ 1052 +#define __SK_StrumSpeed_ __SK_TrillSpeed_ +#define __SK_RollSpeed_ __SK_TrillSpeed_ + +#define __SK_FilterQ_ __SK_Breath_ +#define __SK_FilterFreq_ 1062 +#define __SK_FilterSweepRate_ __SK_FootControl_ + +#define __SK_ShakerInst_ 1071 +#define __SK_ShakerEnergy_ __SK_Breath_ +#define __SK_ShakerDamping_ __SK_ModFrequency_ +#define __SK_ShakerNumObjects_ __SK_FootControl_ + +#define __SK_Strumming_ 1090 +#define __SK_NotStrumming_ 1091 +#define __SK_Trilling_ 1092 +#define __SK_NotTrilling_ 1093 +#define __SK_Rolling_ __SK_Strumming_ +#define __SK_NotRolling_ __SK_NotStrumming_ + +#define __SK_PlayerSkill_ 2001 +#define __SK_Chord_ 2002 +#define __SK_ChordOff_ 2003 + +#define __SK_SINGER_FilePath_ 3000 +#define __SK_SINGER_Frequency_ 3001 +#define __SK_SINGER_NoteName_ 3002 +#define __SK_SINGER_Shape_ 3003 +#define __SK_SINGER_Glot_ 3004 +#define __SK_SINGER_VoicedUnVoiced_ 3005 +#define __SK_SINGER_Synthesize_ 3006 +#define __SK_SINGER_Silence_ 3007 +#define __SK_SINGER_VibratoAmt_ __SK_ModWheel_ +#define __SK_SINGER_RndVibAmt_ 3008 +#define __SK_SINGER_VibFreq_ __SK_Expression_ + +} // stk namespace diff --git a/7.x/src/stk/include/SKINItbl.h b/7.x/src/stk/include/SKINItbl.h new file mode 100644 index 0000000..cfe1fda --- /dev/null +++ b/7.x/src/stk/include/SKINItbl.h @@ -0,0 +1,135 @@ + +#include "SKINImsg.h" + +namespace stk { + +#define __SK_MaxMsgTypes_ 80 + +struct SkiniSpec { char messageString[32]; + long type; + long data2; + long data3; + }; + +/* SEE COMMENT BLOCK AT BOTTOM FOR FIELDS AND USES */ +/* MessageString , type, data2, data3 */ + +struct SkiniSpec skini_msgs[__SK_MaxMsgTypes_] = +{ + {"NoteOff" , __SK_NoteOff_, SK_DBL, SK_DBL}, + {"NoteOn" , __SK_NoteOn_, SK_DBL, SK_DBL}, + {"PolyPressure" , __SK_PolyPressure_, SK_DBL, SK_DBL}, + {"ControlChange" , __SK_ControlChange_, SK_INT, SK_DBL}, + {"ProgramChange" , __SK_ProgramChange_, SK_DBL, NOPE}, + {"AfterTouch" , __SK_AfterTouch_, SK_DBL, NOPE}, + {"ChannelPressure" ,__SK_ChannelPressure_, SK_DBL, NOPE}, + {"PitchWheel" , __SK_PitchWheel_, SK_DBL, NOPE}, + {"PitchBend" , __SK_PitchBend_, SK_DBL, NOPE}, + {"PitchChange" , __SK_PitchChange_, SK_DBL, NOPE}, + + {"Clock" , __SK_Clock_, NOPE, NOPE}, + {"Undefined" , 249, NOPE, NOPE}, + {"SongStart" , __SK_SongStart_, NOPE, NOPE}, + {"Continue" , __SK_Continue_, NOPE, NOPE}, + {"SongStop" , __SK_SongStop_, NOPE, NOPE}, + {"Undefined" , 253, NOPE, NOPE}, + {"ActiveSensing" , __SK_ActiveSensing_, NOPE, NOPE}, + {"SystemReset" , __SK_SystemReset_, NOPE, NOPE}, + + {"Volume" , __SK_ControlChange_, __SK_Volume_ , SK_DBL}, + {"ModWheel" , __SK_ControlChange_, __SK_ModWheel_ , SK_DBL}, + {"Modulation" , __SK_ControlChange_, __SK_Modulation_ , SK_DBL}, + {"Breath" , __SK_ControlChange_, __SK_Breath_ , SK_DBL}, + {"FootControl" , __SK_ControlChange_, __SK_FootControl_ , SK_DBL}, + {"Portamento" , __SK_ControlChange_, __SK_Portamento_ , SK_DBL}, + {"Balance" , __SK_ControlChange_, __SK_Balance_ , SK_DBL}, + {"Pan" , __SK_ControlChange_, __SK_Pan_ , SK_DBL}, + {"Sustain" , __SK_ControlChange_, __SK_Sustain_ , SK_DBL}, + {"Damper" , __SK_ControlChange_, __SK_Damper_ , SK_DBL}, + {"Expression" , __SK_ControlChange_, __SK_Expression_ , SK_DBL}, + + {"NoiseLevel" , __SK_ControlChange_, __SK_NoiseLevel_ , SK_DBL}, + {"PickPosition" , __SK_ControlChange_, __SK_PickPosition_ , SK_DBL}, + {"StringDamping" , __SK_ControlChange_, __SK_StringDamping_ , SK_DBL}, + {"StringDetune" , __SK_ControlChange_, __SK_StringDetune_ , SK_DBL}, + {"BodySize" , __SK_ControlChange_, __SK_BodySize_ , SK_DBL}, + {"BowPressure" , __SK_ControlChange_, __SK_BowPressure_ , SK_DBL}, + {"BowPosition" , __SK_ControlChange_, __SK_BowPosition_ , SK_DBL}, + {"BowBeta" , __SK_ControlChange_, __SK_BowBeta_ , SK_DBL}, + + {"ReedStiffness" , __SK_ControlChange_, __SK_ReedStiffness_ , SK_DBL}, + {"ReedRestPos" , __SK_ControlChange_, __SK_ReedRestPos_ , SK_DBL}, + {"FluteEmbouchure" , __SK_ControlChange_, __SK_FluteEmbouchure_ , SK_DBL}, + {"LipTension" , __SK_ControlChange_, __SK_LipTension_ , SK_DBL}, + {"StrikePosition" , __SK_ControlChange_, __SK_StrikePosition_ , SK_DBL}, + {"StickHardness" , __SK_ControlChange_, __SK_StickHardness_ , SK_DBL}, + + {"TrillDepth" , __SK_ControlChange_, __SK_TrillDepth_ , SK_DBL}, + {"TrillSpeed" , __SK_ControlChange_, __SK_TrillSpeed_ , SK_DBL}, + + {"Strumming" , __SK_ControlChange_, __SK_Strumming_ , 127 }, + {"NotStrumming" , __SK_ControlChange_, __SK_Strumming_ , 0 }, + + {"PlayerSkill" , __SK_ControlChange_, __SK_PlayerSkill_ , SK_DBL}, + + {"Chord" , __SK_Chord_ , SK_DBL, SK_STR}, + {"ChordOff" , __SK_ChordOff_ , SK_DBL, NOPE}, + + {"ShakerInst" , __SK_ControlChange_, __SK_ShakerInst_ , SK_DBL}, + {"Maraca" , __SK_ControlChange_, __SK_ShakerInst_ , 0 }, + {"Sekere" , __SK_ControlChange_, __SK_ShakerInst_ , 1 }, + {"Cabasa" , __SK_ControlChange_, __SK_ShakerInst_ , 2 }, + {"Bamboo" , __SK_ControlChange_, __SK_ShakerInst_ , 3 }, + {"Waterdrp" , __SK_ControlChange_, __SK_ShakerInst_ , 4 }, + {"Tambourn" , __SK_ControlChange_, __SK_ShakerInst_ , 5 }, + {"Sleighbl" , __SK_ControlChange_, __SK_ShakerInst_ , 6 }, + {"Guiro" , __SK_ControlChange_, __SK_ShakerInst_ , 7 }, + + {"OpenFile" , 256, SK_STR, NOPE}, + {"SetPath" , 257, SK_STR, NOPE}, + + {"FilePath" , __SK_SINGER_FilePath_ , SK_STR, NOPE}, + {"Frequency" , __SK_SINGER_Frequency_ , SK_STR, NOPE}, + {"NoteName" , __SK_SINGER_NoteName_ , SK_STR, NOPE}, + {"VocalShape" , __SK_SINGER_Shape_ , SK_STR, NOPE}, + {"Glottis" , __SK_SINGER_Glot_ , SK_STR, NOPE}, + {"VoicedUnVoiced" , __SK_SINGER_VoicedUnVoiced_, SK_DBL, SK_STR}, + {"Synthesize" , __SK_SINGER_Synthesize_ , SK_STR, NOPE}, + {"Silence" , __SK_SINGER_Silence_ , SK_STR, NOPE}, + {"RndVibAmt" , __SK_SINGER_RndVibAmt_ , SK_STR, NOPE}, + {"VibratoAmt" , __SK_ControlChange_ ,__SK_SINGER_VibratoAmt_,SK_DBL}, + {"VibFreq" , __SK_ControlChange_ ,__SK_SINGER_VibFreq_ ,SK_DBL} +}; + + +/** FORMAT: *************************************************************/ +/* */ +/* MessageStr$ , type, data2, data3, */ +/* */ +/* type is the message type sent back from the SKINI line parser. */ +/* data is either */ +/* NOPE : field not used, specifically, there aren't going */ +/* to be any more fields on this line. So if there */ +/* is NOPE in data2, data3 won't even be checked */ +/* SK_INT : byte (actually scanned as 32 bit signed integer) */ +/* If it's a MIDI data field which is required to */ +/* be an integer, like a controller number, it's */ +/* 0-127. Otherwise, get creative with SK_INTs. */ +/* SK_DBL : double precision floating point. SKINI uses these */ +/* in the MIDI context for note numbers with micro */ +/* tuning, velocities, controller values, etc. */ +/* SK_STR : only valid in final field. This allows (nearly) */ +/* arbitrary message types to be supported by simply */ +/* scanning the string to EndOfLine and then passing */ +/* it to a more intelligent handler. For example, */ +/* MIDI SYSEX (system exclusive) messages of up to */ +/* 256 bytes can be read as space-delimited integers */ +/* into the SK_STR buffer. Longer bulk dumps, */ +/* soundfiles, etc. should be handled as a new */ +/* message type pointing to a FileName stored in the */ +/* SK_STR field, or as a new type of multi-line */ +/* message. */ +/* */ +/*************************************************************************/ + +} // stk namespace diff --git a/7.x/src/stk/include/Sampler.h b/7.x/src/stk/include/Sampler.h new file mode 100644 index 0000000..3bc4d7e --- /dev/null +++ b/7.x/src/stk/include/Sampler.h @@ -0,0 +1,75 @@ +#ifndef STK_SAMPLER_H +#define STK_SAMPLER_H + +#include "Instrmnt.h" +#include "ADSR.h" +#include "FileLoop.h" +#include "OnePole.h" + +namespace stk { + +/***************************************************/ +/*! \class Sampler + \brief STK sampling synthesis abstract base class. + + This instrument provides an ADSR envelope, a one-pole filter, and + structures for an arbitrary number of attack and looped files. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Sampler : public Instrmnt +{ + public: + //! Default constructor. + Sampler( void ); + + //! Class destructor. + virtual ~Sampler( void ); + + //! Set instrument parameters for a particular frequency. + virtual void setFrequency( StkFloat frequency ) = 0; + + //! Initiate the envelopes with a key-on event and reset the attack waves. + void keyOn( void ); + + //! Signal a key-off event to the envelopes. + void keyOff( void ); + + //! Stop a note with the given amplitude (speed of decay). + virtual void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + virtual void controlChange( int number, StkFloat value ) = 0; + + //! Compute and return one output sample. + virtual StkFloat tick( unsigned int channel = 0 ) = 0; + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + virtual StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ) = 0; + + protected: + + ADSR adsr_; + std::vector attacks_; + std::vector loops_; + OnePole filter_; + StkFloat baseFrequency_; + std::vector attackRatios_; + std::vector loopRatios_; + StkFloat attackGain_; + StkFloat loopGain_; + +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/Saxofony.h b/7.x/src/stk/include/Saxofony.h new file mode 100644 index 0000000..110ec02 --- /dev/null +++ b/7.x/src/stk/include/Saxofony.h @@ -0,0 +1,166 @@ +#ifndef STK_SAXOFONY_H +#define STK_SAXOFONY_H + +#include "Instrmnt.h" +#include "DelayL.h" +#include "ReedTable.h" +#include "OneZero.h" +#include "Envelope.h" +#include "Noise.h" +#include "SineWave.h" + +namespace stk { + +/***************************************************/ +/*! \class Saxofony + \brief STK faux conical bore reed instrument class. + + This class implements a "hybrid" digital + waveguide instrument that can generate a + variety of wind-like sounds. It has also been + referred to as the "blowed string" model. The + waveguide section is essentially that of a + string, with one rigid and one lossy + termination. The non-linear function is a + reed table. The string can be "blown" at any + point between the terminations, though just as + with strings, it is impossible to excite the + system at either end. If the excitation is + placed at the string mid-point, the sound is + that of a clarinet. At points closer to the + "bridge", the sound is closer to that of a + saxophone. See Scavone (2002) for more details. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Reed Stiffness = 2 + - Reed Aperture = 26 + - Noise Gain = 4 + - Blow Position = 11 + - Vibrato Frequency = 29 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Saxofony : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + Saxofony( StkFloat lowestFrequency ); + + //! Class destructor. + ~Saxofony( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set the "blowing" position between the air column terminations (0.0 - 1.0). + void setBlowPosition( StkFloat aPosition ); + + //! Apply breath pressure to instrument with given amplitude and rate of increase. + void startBlowing( StkFloat amplitude, StkFloat rate ); + + //! Decrease breath pressure with given rate of decrease. + void stopBlowing( StkFloat rate ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayL delays_[2]; + ReedTable reedTable_; + OneZero filter_; + Envelope envelope_; + Noise noise_; + SineWave vibrato_; + + StkFloat outputGain_; + StkFloat noiseGain_; + StkFloat vibratoGain_; + StkFloat position_; + +}; + +inline StkFloat Saxofony :: tick( unsigned int ) +{ + StkFloat pressureDiff; + StkFloat breathPressure; + StkFloat temp; + + // Calculate the breath pressure (envelope + noise + vibrato) + breathPressure = envelope_.tick(); + breathPressure += breathPressure * noiseGain_ * noise_.tick(); + breathPressure += breathPressure * vibratoGain_ * vibrato_.tick(); + + temp = -0.95 * filter_.tick( delays_[0].lastOut() ); + lastFrame_[0] = temp - delays_[1].lastOut(); + pressureDiff = breathPressure - lastFrame_[0]; + delays_[1].tick( temp ); + delays_[0].tick( breathPressure - (pressureDiff * reedTable_.tick(pressureDiff)) - temp ); + + lastFrame_[0] *= outputGain_; + return lastFrame_[0]; +} + +inline StkFrames& Saxofony :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Saxofony::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i +#include + +namespace stk { + +/***************************************************/ +/*! \class Shakers + \brief PhISEM and PhOLIES class. + + PhISEM (Physically Informed Stochastic Event Modeling) is an + algorithmic approach for simulating collisions of multiple + independent sound producing objects. This class is a meta-model + that can simulate a Maraca, Sekere, Cabasa, Bamboo Wind Chimes, + Water Drops, Tambourine, Sleighbells, and a Guiro. + + PhOLIES (Physically-Oriented Library of Imitated Environmental + Sounds) is a similar approach for the synthesis of environmental + sounds. This class implements simulations of breaking sticks, + crunchy snow (or not), a wrench, sandpaper, and more. + + Control Change Numbers: + - Shake Energy = 2 + - System Decay = 4 + - Number Of Objects = 11 + - Resonance Frequency = 1 + - Shake Energy = 128 + - Instrument Selection = 1071 + - Maraca = 0 + - Cabasa = 1 + - Sekere = 2 + - Tambourine = 3 + - Sleigh Bells = 4 + - Bamboo Chimes = 5 + - Sand Paper = 6 + - Coke Can = 7 + - Sticks = 8 + - Crunch = 9 + - Big Rocks = 10 + - Little Rocks = 11 + - Next Mug = 12 + - Penny + Mug = 13 + - Nickle + Mug = 14 + - Dime + Mug = 15 + - Quarter + Mug = 16 + - Franc + Mug = 17 + - Peso + Mug = 18 + - Guiro = 19 + - Wrench = 20 + - Water Drops = 21 + - Tuned Bamboo Chimes = 22 + + by Perry R. Cook with updates by Gary Scavone, 1995--2021. +*/ +/***************************************************/ + +class Shakers : public Instrmnt +{ + public: + //! Class constructor taking instrument type argument. + Shakers( int type = 0 ); + + //! Start a note with the given instrument and amplitude. + /*! + Use the instrument numbers above, converted to frequency values + as if MIDI note numbers, to select a particular instrument. + */ + void noteOn( StkFloat instrument, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + struct BiQuad { + StkFloat gain; + StkFloat b[3]; + StkFloat a[3]; // a0 term assumed equal to 1.0 + StkFloat inputs[3]; + StkFloat outputs[3]; + + // Default constructor. + BiQuad() + { + gain = 0.0; + for ( int i=0; i<3; i++ ) { + b[i] = 0.0; + a[i] = 0.0; + inputs[i] = 0.0; + outputs[i] = 0.0; + } + } + }; + + protected: + + void setType( int type ); + void setResonance( BiQuad &filter, StkFloat frequency, StkFloat radius ); + StkFloat tickResonance( BiQuad &filter, StkFloat input ); + void setEqualization( StkFloat b0, StkFloat b1, StkFloat b2 ); + StkFloat tickEqualize( StkFloat input ); + int randomInt( int max ); + StkFloat randomFloat( StkFloat max = 1.0 ); + StkFloat noise( void ); + void waterDrop( void ); + + int shakerType_; + unsigned int nResonances_; + StkFloat shakeEnergy_; + StkFloat soundDecay_; + StkFloat systemDecay_; + StkFloat nObjects_; + StkFloat sndLevel_; + StkFloat baseGain_; + StkFloat currentGain_; + StkFloat baseDecay_; + StkFloat baseObjects_; + StkFloat decayScale_; + BiQuad equalizer_; + StkFloat ratchetCount_; + StkFloat ratchetDelta_; + StkFloat baseRatchetDelta_; + int lastRatchetValue_; + + std::vector< BiQuad > filters_; + std::vector< StkFloat > baseFrequencies_; + std::vector< StkFloat > baseRadii_; + std::vector< bool > doVaryFrequency_; + std::vector< StkFloat > tempFrequencies_; + StkFloat varyFactor_; +}; + +inline void Shakers :: setResonance( BiQuad &filter, StkFloat frequency, StkFloat radius ) +{ + filter.a[1] = -2.0 * radius * cos( TWO_PI * frequency / Stk::sampleRate()); + filter.a[2] = radius * radius; +} + +inline StkFloat Shakers :: tickResonance( BiQuad &filter, StkFloat input ) +{ + filter.outputs[0] = input * filter.gain * currentGain_; + filter.outputs[0] -= filter.a[1] * filter.outputs[1] + filter.a[2] * filter.outputs[2]; + filter.outputs[2] = filter.outputs[1]; + filter.outputs[1] = filter.outputs[0]; + return filter.outputs[0]; +} + +inline void Shakers :: setEqualization( StkFloat b0, StkFloat b1, StkFloat b2 ) +{ + equalizer_.b[0] = b0; + equalizer_.b[1] = b1; + equalizer_.b[2] = b2; +} + +inline StkFloat Shakers :: tickEqualize( StkFloat input ) +{ + equalizer_.inputs[0] = input; + equalizer_.outputs[0] = equalizer_.b[0] * equalizer_.inputs[0] + equalizer_.b[1] * equalizer_.inputs[1] + equalizer_.b[2] * equalizer_.inputs[2]; + equalizer_.inputs[2] = equalizer_.inputs[1]; + equalizer_.inputs[1] = equalizer_.inputs[0]; + return equalizer_.outputs[0]; +} + +inline int Shakers :: randomInt( int max ) // Return random integer between 0 and max-1 +{ + return (int) ((float)max * rand() / (RAND_MAX + 1.0) ); +} + +inline StkFloat Shakers :: randomFloat( StkFloat max ) // Return random float between 0.0 and max +{ + return (StkFloat) (max * rand() / (RAND_MAX + 1.0) ); +} + +inline StkFloat Shakers :: noise( void ) // Return random StkFloat float between -1.0 and 1.0 +{ + return ( (StkFloat) ( 2.0 * rand() / (RAND_MAX + 1.0) ) - 1.0 ); +} + +const StkFloat MIN_ENERGY = 0.001; +const StkFloat WATER_FREQ_SWEEP = 1.0001; + +inline void Shakers :: waterDrop( void ) +{ + if ( randomInt( 32767 ) < nObjects_) { + sndLevel_ = shakeEnergy_; + unsigned int j = randomInt( 3 ); + if ( j == 0 && filters_[0].gain == 0.0 ) { // don't change unless fully decayed + tempFrequencies_[0] = baseFrequencies_[1] * (0.75 + (0.25 * noise())); + filters_[0].gain = fabs( noise() ); + } + else if (j == 1 && filters_[1].gain == 0.0) { + tempFrequencies_[1] = baseFrequencies_[1] * (1.0 + (0.25 * noise())); + filters_[1].gain = fabs( noise() ); + } + else if ( filters_[2].gain == 0.0 ) { + tempFrequencies_[2] = baseFrequencies_[1] * (1.25 + (0.25 * noise())); + filters_[2].gain = fabs( noise() ); + } + } + + // Sweep center frequencies. + for ( unsigned int i=0; i<3; i++ ) { // WATER_RESONANCES = 3 + filters_[i].gain *= baseRadii_[i]; + if ( filters_[i].gain > 0.001 ) { + tempFrequencies_[i] *= WATER_FREQ_SWEEP; + filters_[i].a[1] = -2.0 * baseRadii_[i] * cos( TWO_PI * tempFrequencies_[i] / Stk::sampleRate() ); + } + else + filters_[i].gain = 0.0; + } +} + +inline StkFloat Shakers :: tick( unsigned int ) +{ + unsigned int iTube = 0; + StkFloat input = 0.0; + if ( shakerType_ == 19 || shakerType_ == 20 ) { + if ( ratchetCount_ <= 0 ) return lastFrame_[0] = 0.0; + + shakeEnergy_ -= ( ratchetDelta_ + ( 0.002 * shakeEnergy_ ) ); + if ( shakeEnergy_ < 0.0 ) { + shakeEnergy_ = 1.0; + ratchetCount_--; + } + + if ( randomFloat( 1024 ) < nObjects_ ) + sndLevel_ += shakeEnergy_ * shakeEnergy_; + + // Sound is enveloped noise + input = sndLevel_ * noise() * shakeEnergy_; + } + else { + if ( shakeEnergy_ < MIN_ENERGY ) return lastFrame_[0] = 0.0; + + // Exponential system decay + shakeEnergy_ *= systemDecay_; + + // Random events + if ( shakerType_ == 21 ) { + waterDrop(); + input = sndLevel_; + } + else { + if ( randomFloat( 1024.0 ) < nObjects_ ) { + sndLevel_ += shakeEnergy_; + input = sndLevel_; + // Vary resonance frequencies if specified. + for ( unsigned int i=0; i 1.0 ) + // std::cout << "lastOutput = " << lastFrame_[0] << std::endl; + + return lastFrame_[0]; +} + +inline StkFrames& Shakers :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Shakers::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; itick(); + biquad_.tick( noise_.tick() ); + lastFrame_[0] += (1.0 - loopGain_) * biquad_.lastOut(); + lastFrame_[0] = filter_.tick( lastFrame_[0] ); + lastFrame_[0] *= adsr_.tick(); + return lastFrame_[0]; +} + +inline StkFrames& Simple :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Simple::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i= TABLE_SIZE ) + time_ -= TABLE_SIZE; + + iIndex_ = (unsigned int) time_; + alpha_ = time_ - iIndex_; + StkFloat tmp = table_[ iIndex_ ]; + tmp += ( alpha_ * ( table_[ iIndex_ + 1 ] - tmp ) ); + + // Increment time, which can be negative. + time_ += rate_; + + lastFrame_[0] = tmp; + return lastFrame_[0]; +} + +inline StkFrames& SineWave :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "SineWave::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + StkFloat tmp = 0.0; + + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= TABLE_SIZE ) + time_ -= TABLE_SIZE; + + iIndex_ = (unsigned int) time_; + alpha_ = time_ - iIndex_; + tmp = table_[ iIndex_ ]; + tmp += ( alpha_ * ( table_[ iIndex_ + 1 ] - tmp ) ); + *samples = tmp; + + // Increment time, which can be negative. + time_ += rate_; + } + + lastFrame_[0] = tmp; + return frames; +} + +} // stk namespace + +#endif + diff --git a/7.x/src/stk/include/SingWave.h b/7.x/src/stk/include/SingWave.h new file mode 100644 index 0000000..34107b6 --- /dev/null +++ b/7.x/src/stk/include/SingWave.h @@ -0,0 +1,136 @@ +#ifndef STK_SINGWAVE_H +#define STK_SINGWAVE_H + +#include "FileLoop.h" +#include "Modulate.h" +#include "Envelope.h" + +namespace stk { + +/***************************************************/ +/*! \class SingWave + \brief STK "singing" looped soundfile class. + + This class loops a specified soundfile and modulates it both + periodically and randomly to produce a pitched musical sound, like + a simple voice or violin. In general, it is not be used alone + because of "munchkinification" effects from pitch shifting. + Within STK, it is used as an excitation source for other + instruments. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class SingWave : public Generator +{ + public: + //! Class constructor taking filename argument. + /*! + An StkError will be thrown if the file is not found, its format + is unknown, or a read error occurs. If the soundfile has no + header, the second argument should be \e true and the file data + will be assumed to consist of 16-bit signed integers in big-endian + byte order at a sample rate of 22050 Hz. + */ + SingWave( std::string fileName, bool raw = false ); + + //! Class destructor. + ~SingWave( void ); + + //! Reset file to beginning. + void reset( void ) { wave_.reset(); lastFrame_[0] = 0.0; }; + + //! Normalize the file to a maximum of +-1.0. + void normalize( void ) { wave_.normalize(); }; + + //! Normalize the file to a maximum of \e +- peak. + void normalize( StkFloat peak ) { wave_.normalize( peak ); }; + + //! Set looping parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set the vibrato frequency in Hz. + void setVibratoRate( StkFloat rate ) { modulator_.setVibratoRate( rate ); }; + + //! Set the vibrato gain. + void setVibratoGain( StkFloat gain ) { modulator_.setVibratoGain( gain ); }; + + //! Set the random-ness amount. + void setRandomGain( StkFloat gain ) { modulator_.setRandomGain( gain ); }; + + //! Set the sweep rate. + void setSweepRate( StkFloat rate ) { sweepRate_ = rate; }; + + //! Set the gain rate. + void setGainRate( StkFloat rate ) { envelope_.setRate( rate ); }; + + //! Set the gain target value. + void setGainTarget( StkFloat target ) { envelope_.setTarget( target ); }; + + //! Start a note. + void noteOn( void ) { envelope_.keyOn(); }; + + //! Stop a note. + void noteOff( void ) { envelope_.keyOff(); }; + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Compute and return one output sample. + StkFloat tick( void ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + FileLoop wave_; + Modulate modulator_; + Envelope envelope_; + Envelope pitchEnvelope_; + StkFloat rate_; + StkFloat sweepRate_; + +}; + +inline StkFloat SingWave :: tick( void ) +{ + // Set the wave rate. + StkFloat newRate = pitchEnvelope_.tick(); + newRate += newRate * modulator_.tick(); + wave_.setRate( newRate ); + + lastFrame_[0] = wave_.tick(); + lastFrame_[0] *= envelope_.tick(); + + return lastFrame_[0]; +} + +inline StkFrames& SingWave :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "SingWave::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class Sitar + \brief STK sitar string model class. + + This class implements a sitar plucked string + physical model based on the Karplus-Strong + algorithm. + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + There exist at least two patents, assigned to + Stanford, bearing the names of Karplus and/or + Strong. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Sitar : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + Sitar( StkFloat lowestFrequency = 8.0 ); + + //! Class destructor. + ~Sitar( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Pluck the string with the given amplitude using the current frequency. + void pluck( StkFloat amplitude ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayA delayLine_; + OneZero loopFilter_; + Noise noise_; + ADSR envelope_; + + StkFloat loopGain_; + StkFloat amGain_; + StkFloat delay_; + StkFloat targetDelay_; + +}; + +inline StkFloat Sitar :: tick( unsigned int ) +{ + if ( fabs(targetDelay_ - delay_) > 0.001 ) { + if ( targetDelay_ < delay_ ) + delay_ *= 0.99999; + else + delay_ *= 1.00001; + delayLine_.setDelay( delay_ ); + } + + lastFrame_[0] = delayLine_.tick( loopFilter_.tick( delayLine_.lastOut() * loopGain_ ) + + (amGain_ * envelope_.tick() * noise_.tick())); + + return lastFrame_[0]; +} + +inline StkFrames& Sitar :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Sitar::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i +#include +#include + +namespace stk { + +/***************************************************/ +/*! \class Skini + \brief STK SKINI parsing class + + This class parses SKINI formatted text + messages. It can be used to parse individual + messages or it can be passed an entire file. + The SKINI specification is Perry's and his + alone, but it's all text so it shouldn't be too + hard to figure out. + + SKINI (Synthesis toolKit Instrument Network + Interface) is like MIDI, but allows for + floating-point control changes, note numbers, + etc. The following example causes a sharp + middle C to be played with a velocity of 111.132: + + \code + noteOn 60.01 111.132 + \endcode + + \sa \ref skini + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Skini : public Stk +{ + public: + + //! A message structure to store and pass parsed SKINI messages. + struct Message { + long type; /*!< The message type, as defined in SKINImsg.h. */ + long channel; /*!< The message channel (not limited to 16!). */ + StkFloat time; /*!< The message time stamp in seconds (delta or absolute). */ + std::vector floatValues; /*!< The message values read as floats (values are type-specific). */ + std::vector intValues; /*!< The message values read as ints (number and values are type-specific). */ + std::string remainder; /*!< Any remaining message data, read as ascii text. */ + + // Default constructor. + Message() + :type(0), channel(0), time(0.0), floatValues(2), intValues(2) {} + }; + + //! Default constructor. + Skini(); + + //! Class destructor + ~Skini(); + + //! Set a SKINI formatted file for reading. + /*! + If the file is successfully opened, this function returns \e + true. Otherwise, \e false is returned. + */ + bool setFile( std::string fileName ); + + //! Parse the next file message (if a file is loaded) and return the message type. + /*! + This function skips over lines in a file which cannot be + parsed. A type value equal to zero in the referenced message + structure (and the returned value) indicates the file end is + reached or no file is open for reading. + */ + long nextMessage( Skini::Message& message ); + + //! Attempt to parse the given string and returning the message type. + /*! + A type value equal to zero in the referenced message structure + indicates an invalid message. + */ + long parseString( std::string& line, Skini::Message& message ); + + //! Return the SKINI type string for the given type value. + static std::string whatsThisType(long type); + + //! Return the SKINI controller string for the given controller number. + static std::string whatsThisController(long number); + + protected: + + void tokenize( const std::string& str, std::vector& tokens, const std::string& delimiters ); + + std::ifstream file_; +}; + +//! A static table of equal-tempered MIDI to frequency (Hz) values. +static const double Midi2Pitch[129] = { + 8.176, 8.662, 9.177, 9.723, 10.301, 10.913, 11.562, 12.25, + 12.978, 13.75, 14.568, 15.434, 16.352, 17.324, 18.354, 19.445, + 20.602, 21.827, 23.125, 24.50, 25.957, 27.50, 29.135, 30.868, + 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 49.0, + 51.913, 55.0, 58.271, 61.735, 65.406, 69.296, 73.416, 77.782, + 82.407, 87.307, 92.499, 97.999, 103.826, 110.0, 116.541, 123.471, + 130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, + 207.652, 220.0, 233.082, 246.942, 261.626, 277.183, 293.665, 311.127, + 329.628, 349.228, 369.994, 391.995, 415.305, 440.0, 466.164, 493.883, + 523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, + 830.609, 880.0, 932.328, 987.767, 1046.502, 1108.731, 1174.659, 1244.508, + 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760.0, 1864.655, 1975.533, + 2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, + 3322.438, 3520.0, 3729.31, 3951.066, 4186.009, 4434.922, 4698.636, 4978.032, + 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040.0, 7458.62, 7902.133, + 8372.018, 8869.844, 9397.273, 9956.063, 10548.082, 11175.303, 11839.822, 12543.854, + 13289.75}; + +} // stk namespace + +#endif + + diff --git a/7.x/src/stk/include/Sphere.h b/7.x/src/stk/include/Sphere.h new file mode 100644 index 0000000..f54ada5 --- /dev/null +++ b/7.x/src/stk/include/Sphere.h @@ -0,0 +1,79 @@ +#ifndef STK_SPHERE_H +#define STK_SPHERE_H + +#include "Stk.h" +#include "Vector3D.h" + +namespace stk { + +/***************************************************/ +/*! \class Sphere + \brief STK sphere class. + + This class implements a spherical ball with + radius, mass, position, and velocity parameters. + + by Perry R. Cook, 1995--2021. +*/ +/***************************************************/ + +class Sphere : public Stk +{ +public: + //! Constructor taking an initial radius value. + Sphere( StkFloat radius = 1.0 ) { radius_ = radius; mass_ = 1.0; }; + + //! Set the 3D center position of the sphere. + void setPosition( StkFloat x, StkFloat y, StkFloat z ) { position_.setXYZ(x, y, z); }; + + //! Set the 3D velocity of the sphere. + void setVelocity( StkFloat x, StkFloat y, StkFloat z ) { velocity_.setXYZ(x, y, z); }; + + //! Set the radius of the sphere. + void setRadius( StkFloat radius ) { radius_ = radius; }; + + //! Set the mass of the sphere. + void setMass( StkFloat mass ) { mass_ = mass; }; + + //! Get the current position of the sphere as a 3D vector. + Vector3D* getPosition( void ) { return &position_; }; + + //! Get the relative position of the given point to the sphere as a 3D vector. + Vector3D* getRelativePosition( Vector3D *position ); + + //! Set the velocity of the sphere as a 3D vector. + StkFloat getVelocity( Vector3D* velocity ); + + //! Returns the distance from the sphere boundary to the given position (< 0 if inside). + StkFloat isInside( Vector3D *position ); + + //! Get the current sphere radius. + StkFloat getRadius( void ) { return radius_; }; + + //! Get the current sphere mass. + StkFloat getMass( void ) { return mass_; }; + + //! Increase the current sphere velocity by the given 3D components. + void addVelocity( StkFloat x, StkFloat y, StkFloat z ); + + //! Move the sphere for the given time increment. + void tick( StkFloat timeIncrement ); + +private: + Vector3D position_; + Vector3D velocity_; + Vector3D workingVector_; + StkFloat radius_; + StkFloat mass_; +}; + +inline void Sphere::tick( StkFloat timeIncrement ) +{ + position_.setX(position_.getX() + (timeIncrement * velocity_.getX())); + position_.setY(position_.getY() + (timeIncrement * velocity_.getY())); + position_.setZ(position_.getZ() + (timeIncrement * velocity_.getZ())); +}; + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/StifKarp.h b/7.x/src/stk/include/StifKarp.h new file mode 100644 index 0000000..51ed96f --- /dev/null +++ b/7.x/src/stk/include/StifKarp.h @@ -0,0 +1,153 @@ +#ifndef STK_STIFKARP_H +#define STK_STIFKARP_H + +#include "Instrmnt.h" +#include "DelayL.h" +#include "DelayA.h" +#include "OneZero.h" +#include "Noise.h" +#include "BiQuad.h" + +namespace stk { + +/***************************************************/ +/*! \class StifKarp + \brief STK plucked stiff string instrument. + + This class implements a simple plucked string + algorithm (Karplus Strong) with enhancements + (Jaffe-Smith, Smith, and others), including + string stiffness and pluck position controls. + The stiffness is modeled with allpass filters. + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + + Control Change Numbers: + - Pickup Position = 4 + - String Sustain = 11 + - String Stretch = 1 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class StifKarp : public Instrmnt +{ + public: + //! Class constructor, taking the lowest desired playing frequency. + StifKarp( StkFloat lowestFrequency = 10.0 ); + + //! Class destructor. + ~StifKarp( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set the stretch "factor" of the string (0.0 - 1.0). + void setStretch( StkFloat stretch ); + + //! Set the pluck or "excitation" position along the string (0.0 - 1.0). + void setPickupPosition( StkFloat position ); + + //! Set the base loop gain. + /*! + The actual loop gain is set according to the frequency. + Because of high-frequency loop filter roll-off, higher + frequency settings have greater loop gains. + */ + void setBaseLoopGain( StkFloat aGain ); + + //! Pluck the string with the given amplitude using the current frequency. + void pluck( StkFloat amplitude ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ); + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + DelayA delayLine_; + DelayL combDelay_; + OneZero filter_; + Noise noise_; + BiQuad biquad_[4]; + + StkFloat loopGain_; + StkFloat baseLoopGain_; + StkFloat lastFrequency_; + StkFloat lastLength_; + StkFloat stretching_; + StkFloat pluckAmplitude_; + StkFloat pickupPosition_; + +}; + +inline StkFloat StifKarp :: tick( unsigned int ) +{ + StkFloat temp = delayLine_.lastOut() * loopGain_; + + // Calculate allpass stretching. + for (int i=0; i<4; i++) + temp = biquad_[i].tick(temp); + + // Moving average filter. + temp = filter_.tick(temp); + + lastFrame_[0] = delayLine_.tick(temp); + lastFrame_[0] = lastFrame_[0] - combDelay_.tick( lastFrame_[0] ); + return lastFrame_[0]; +} + +inline StkFrames& StifKarp :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "StifKarp::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i +#include +#include +#include +#include +#include +//#include + +/*! \namespace stk + \brief The STK namespace. + + Most Stk classes are defined within the STK namespace. Exceptions + to this include the classes RtAudio and RtMidi. +*/ +namespace stk { + +/***************************************************/ +/*! \class Stk + \brief STK base class + + Nearly all STK classes inherit from this class. + The global sample rate and rawwave path variables + can be queried and modified via Stk. In addition, + this class provides error handling and + byte-swapping functions. + + The Synthesis ToolKit in C++ (STK) is a set of open source audio + signal processing and algorithmic synthesis classes written in the + C++ programming language. STK was designed to facilitate rapid + development of music synthesis and audio processing software, with + an emphasis on cross-platform functionality, realtime control, + ease of use, and educational example code. STK currently runs + with realtime support (audio and MIDI) on Linux, Macintosh OS X, + and Windows computer platforms. Generic, non-realtime support has + been tested under NeXTStep, Sun, and other platforms and should + work with any standard C++ compiler. + + STK WWW site: http://ccrma.stanford.edu/software/stk/ + + The Synthesis ToolKit in C++ (STK) + Copyright (c) 1995--2021 Perry R. Cook and Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/***************************************************/ + +//#define _STK_DEBUG_ + +// Most data in STK is passed and calculated with the +// following user-definable floating-point type. You +// can change this to "float" if you prefer or perhaps +// a "long double" in the future. +typedef double StkFloat; + +//! STK error handling class. +/*! + This is a fairly abstract exception handling class. There could + be sub-classes to take care of more specific error conditions ... or + not. +*/ +class StkError : public std::exception +{ +public: + enum Type { + STATUS, + WARNING, + DEBUG_PRINT, + MEMORY_ALLOCATION, + MEMORY_ACCESS, + FUNCTION_ARGUMENT, + FILE_NOT_FOUND, + FILE_UNKNOWN_FORMAT, + FILE_ERROR, + PROCESS_THREAD, + PROCESS_SOCKET, + PROCESS_SOCKET_IPADDR, + AUDIO_SYSTEM, + MIDI_SYSTEM, + UNSPECIFIED + }; + +protected: + std::string message_; + Type type_; + +public: + //! The constructor. + StkError(const std::string& message, Type type = StkError::UNSPECIFIED) + : std::exception(), message_(message), type_(type) {} + + //! The destructor. + virtual ~StkError(void) throw() {}; + + //! Prints thrown error message to stderr. + virtual void printMessage(void) { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage(void) { return message_; } + + //! Returns the thrown error message as a C string. + virtual const char *getMessageCString(void) { return message_.c_str(); } + + virtual const char *what(void) const throw() { return message_.c_str(); } +}; + + +class Stk +{ +public: + + typedef unsigned long StkFormat; + static const StkFormat STK_SINT8; /*!< -128 to +127 */ + static const StkFormat STK_SINT16; /*!< -32768 to +32767 */ + static const StkFormat STK_SINT24; /*!< Lower 3 bytes of 32-bit signed integer. */ + static const StkFormat STK_SINT32; /*!< -2147483648 to +2147483647. */ + static const StkFormat STK_FLOAT32; /*!< Normalized between plus/minus 1.0. */ + static const StkFormat STK_FLOAT64; /*!< Normalized between plus/minus 1.0. */ + + //! Static method that returns the current STK sample rate. + static StkFloat sampleRate( void ) { return srate_; } + + //! Static method that sets the STK sample rate. + /*! + The sample rate set using this method is queried by all STK + classes that depend on its value. It is initialized to the + default SRATE set in Stk.h. Many STK classes use the sample rate + during instantiation. Therefore, if you wish to use a rate that + is different from the default rate, it is imperative that it be + set \e BEFORE STK objects are instantiated. A few classes that + make use of the global STK sample rate are automatically notified + when the rate changes so that internal class data can be + appropriately updated. However, this has not been fully + implemented. Specifically, classes that appropriately update + their own data when either a setFrequency() or noteOn() function + is called do not currently receive the automatic notification of + rate change. If the user wants a specific class instance to + ignore such notifications, perhaps in a multi-rate context, the + function Stk::ignoreSampleRateChange() should be called. + */ + static void setSampleRate( StkFloat rate ); + + //! A function to enable/disable the automatic updating of class data when the STK sample rate changes. + /*! + This function allows the user to enable or disable class data + updates in response to global sample rate changes on a class by + class basis. + */ + void ignoreSampleRateChange( bool ignore = true ) { ignoreSampleRateChange_ = ignore; }; + + //! Static method that frees memory from alertList_. + static void clear_alertList(){std::vector().swap(alertList_);}; + + //! Static method that returns the current rawwave path. + static std::string rawwavePath(void) { return rawwavepath_; } + + //! Static method that sets the STK rawwave path. + static void setRawwavePath( std::string path ); + + //! Static method that byte-swaps a 16-bit data type. + static void swap16( unsigned char *ptr ); + + //! Static method that byte-swaps a 32-bit data type. + static void swap32( unsigned char *ptr ); + + //! Static method that byte-swaps a 64-bit data type. + static void swap64( unsigned char *ptr ); + + //! Static cross-platform method to sleep for a number of milliseconds. + static void sleep( unsigned long milliseconds ); + + //! Static method to check whether a value is within a specified range. + static bool inRange( StkFloat value, StkFloat min, StkFloat max ) { + if ( value < min ) return false; + else if ( value > max ) return false; + else return true; + } + + //! Static function for error reporting and handling using c-strings. + static void handleError( const char *message, StkError::Type type ); + + //! Static function for error reporting and handling using c++ strings. + static void handleError( std::string message, StkError::Type type ); + + //! Toggle display of WARNING and STATUS messages. + static void showWarnings( bool status ) { showWarnings_ = status; } + + //! Toggle display of error messages before throwing exceptions. + static void printErrors( bool status ) { printErrors_ = status; } + +private: + static StkFloat srate_; + static std::string rawwavepath_; + static bool showWarnings_; + static bool printErrors_; + static std::vector alertList_; + +protected: + + static std::ostringstream oStream_; + bool ignoreSampleRateChange_; + + //! Default constructor. + Stk( void ); + + //! Class destructor. + virtual ~Stk( void ); + + //! This function should be implemented in subclasses that depend on the sample rate. + virtual void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); + + //! Add class pointer to list for sample rate change notification. + void addSampleRateAlert( Stk *ptr ); + + //! Remove class pointer from list for sample rate change notification. + void removeSampleRateAlert( Stk *ptr ); + + //! Internal function for error reporting that assumes message in \c oStream_ variable. + void handleError( StkError::Type type ) const; +}; + + +/***************************************************/ +/*! \class StkFrames + \brief An STK class to handle vectorized audio data. + + This class can hold single- or multi-channel audio data. The data + type is always StkFloat and the channel format is always + interleaved. In an effort to maintain efficiency, no + out-of-bounds checks are performed in this class unless + _STK_DEBUG_ is defined. + + Internally, the data is stored in a one-dimensional C array. An + indexing operator is available to set and retrieve data values. + Alternately, one can use pointers to access the data, using the + index operator to get an address for a particular location in the + data: + + StkFloat* ptr = &myStkFrames[0]; + + Note that this class can also be used as a table with interpolating + lookup. + + Possible future improvements in this class could include functions + to convert to and return other data types. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class StkFrames +{ +public: + + //! The default constructor initializes the frame data structure to size zero. + StkFrames( unsigned int nFrames = 0, unsigned int nChannels = 0 ); + + //! Overloaded constructor that initializes the frame data to the specified size with \c value. + StkFrames( const StkFloat& value, unsigned int nFrames, unsigned int nChannels ); + + //! The destructor. + virtual ~StkFrames(); + + // A copy constructor. + StkFrames( const StkFrames& f ); + + // Assignment operator that returns a reference to self. + virtual StkFrames& operator= ( const StkFrames& f ); + + //! Subscript operator that returns a reference to element \c n of self. + /*! + The result can be used as an lvalue. This reference is valid + until the resize function is called or the array is destroyed. The + index \c n must be between 0 and size less one. No range checking + is performed unless _STK_DEBUG_ is defined. + */ + StkFloat& operator[] ( size_t n ); + + //! Subscript operator that returns the value at element \c n of self. + /*! + The index \c n must be between 0 and size less one. No range + checking is performed unless _STK_DEBUG_ is defined. + */ + StkFloat operator[] ( size_t n ) const; + + //! Sum operator + /*! + The dimensions of the argument are expected to be the same as + self. No range checking is performed unless _STK_DEBUG_ is + defined. + */ + StkFrames operator+(const StkFrames &frames) const; + + //! Assignment by sum operator into self. + /*! + The dimensions of the argument are expected to be the same as + self. No range checking is performed unless _STK_DEBUG_ is + defined. + */ + StkFrames& operator+= ( StkFrames& f ); + + //! Assignment by product operator into self. + /*! + The dimensions of the argument are expected to be the same as + self. No range checking is performed unless _STK_DEBUG_ is + defined. + */ + StkFrames& operator*= ( StkFrames& f ); + + //! Scaling operator (StkFrame * StkFloat). + StkFrames operator* ( StkFloat v ) const; + + //! Scaling operator (StkFloat * StkFrame) + friend StkFrames operator*(StkFloat v, const StkFrames& f); + + //! Scaling operator (inline). + StkFrames& operator*= ( StkFloat v ); + + //! Channel / frame subscript operator that returns a reference. + /*! + The result can be used as an lvalue. This reference is valid + until the resize function is called or the array is destroyed. The + \c frame index must be between 0 and frames() - 1. The \c channel + index must be between 0 and channels() - 1. No range checking is + performed unless _STK_DEBUG_ is defined. + */ + StkFloat& operator() ( size_t frame, unsigned int channel ); + + //! Channel / frame subscript operator that returns a value. + /*! + The \c frame index must be between 0 and frames() - 1. The \c + channel index must be between 0 and channels() - 1. No range checking + is performed unless _STK_DEBUG_ is defined. + */ + StkFloat operator() ( size_t frame, unsigned int channel ) const; + + //! Return an interpolated value at the fractional frame index and channel. + /*! + This function performs linear interpolation. The \c frame + index must be between 0.0 and frames() - 1. The \c channel index + must be between 0 and channels() - 1. No range checking is + performed unless _STK_DEBUG_ is defined. + */ + StkFloat interpolate( StkFloat frame, unsigned int channel = 0 ) const; + + //! Returns the total number of audio samples represented by the object. + size_t size() const { return size_; }; + + //! Returns \e true if the object size is zero and \e false otherwise. + bool empty() const; + + //! Resize self to represent the specified number of channels and frames. + /*! + Changes the size of self based on the number of frames and + channels. No element assignment is performed. No memory + deallocation occurs if the new size is smaller than the previous + size. Further, no new memory is allocated when the new size is + smaller or equal to a previously allocated size. + */ + virtual void resize( size_t nFrames, unsigned int nChannels = 1 ); + + //! Resize self to represent the specified number of channels and frames and perform element initialization. + /*! + Changes the size of self based on the number of frames and + channels, and assigns \c value to every element. No memory + deallocation occurs if the new size is smaller than the previous + size. Further, no new memory is allocated when the new size is + smaller or equal to a previously allocated size. + */ + virtual void resize( size_t nFrames, unsigned int nChannels, StkFloat value ); + + //! Retrieves a single channel + /*! + Copies the specified \c channel into \c destinationFrames's \c destinationChannel. \c destinationChannel must be between 0 and destination.channels() - 1 and + \c channel must be between 0 and channels() - 1. destination.frames() must be >= frames(). + No range checking is performed unless _STK_DEBUG_ is defined. + */ + StkFrames& getChannel(unsigned int channel,StkFrames& destinationFrames, unsigned int destinationChannel) const; + + //! Sets a single channel + /*! + Copies the \c sourceChannel of \c sourceFrames into the \c channel of self. + SourceFrames.frames() must be equal to frames(). + No range checking is performed unless _STK_DEBUG_ is defined. + */ + void setChannel(unsigned int channel,const StkFrames &sourceFrames,unsigned int sourceChannel); + + //! Return the number of channels represented by the data. + unsigned int channels( void ) const { return nChannels_; }; + + //! Return the number of sample frames represented by the data. + unsigned int frames( void ) const { return (unsigned int)nFrames_; }; + + //! Set the sample rate associated with the StkFrames data. + /*! + By default, this value is set equal to the current STK sample + rate at the time of instantiation. + */ + void setDataRate( StkFloat rate ) { dataRate_ = rate; }; + + //! Return the sample rate associated with the StkFrames data. + /*! + By default, this value is set equal to the current STK sample + rate at the time of instantiation. + */ + StkFloat dataRate( void ) const { return dataRate_; }; + +protected: + + StkFloat *data_; + StkFloat dataRate_; + size_t nFrames_; + unsigned int nChannels_; + size_t size_; + size_t bufferSize_; + +}; + +inline bool StkFrames :: empty() const +{ + if ( size_ > 0 ) return false; + else return true; +} + +inline StkFloat& StkFrames :: operator[] ( size_t n ) +{ +#if defined(_STK_DEBUG_) + if ( n >= size_ ) { + std::ostringstream error; + error << "StkFrames::operator[]: invalid index (" << n << ") value!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + return data_[n]; +} + +inline StkFloat StkFrames :: operator[] ( size_t n ) const +{ +#if defined(_STK_DEBUG_) + if ( n >= size_ ) { + std::ostringstream error; + error << "StkFrames::operator[]: invalid index (" << n << ") value!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + return data_[n]; +} + +inline StkFloat& StkFrames :: operator() ( size_t frame, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( frame >= nFrames_ || channel >= nChannels_ ) { + std::ostringstream error; + error << "StkFrames::operator(): invalid frame (" << frame << ") or channel (" << channel << ") value!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + return data_[ frame * nChannels_ + channel ]; +} + +inline StkFloat StkFrames :: operator() ( size_t frame, unsigned int channel ) const +{ +#if defined(_STK_DEBUG_) + if ( frame >= nFrames_ || channel >= nChannels_ ) { + std::ostringstream error; + error << "StkFrames::operator(): invalid frame (" << frame << ") or channel (" << channel << ") value!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + return data_[ frame * nChannels_ + channel ]; +} + +inline StkFrames StkFrames::operator+(const StkFrames &f) const +{ +#if defined(_STK_DEBUG_) + if ( f.frames() != nFrames_ || f.channels() != nChannels_ ) { + std::ostringstream error; + error << "StkFrames::operator+: frames argument must be of equal dimensions!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + StkFrames sum((unsigned int)nFrames_,nChannels_); + StkFloat *sumPtr = &sum[0]; + const StkFloat *fptr = f.data_; + const StkFloat *dPtr = data_; + for (unsigned int i = 0; i < size_; i++) { + *sumPtr++ = *fptr++ + *dPtr++; + } + return sum; +} + +inline StkFrames& StkFrames :: operator+= ( StkFrames& f ) +{ +#if defined(_STK_DEBUG_) + if ( f.frames() != nFrames_ || f.channels() != nChannels_ ) { + std::ostringstream error; + error << "StkFrames::operator+=: frames argument must be of equal dimensions!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + StkFloat *fptr = &f[0]; + StkFloat *dptr = data_; + for ( unsigned int i=0; i taps = std::vector( 1, 0 ), unsigned long maxDelay = 4095 ); + + //! Class destructor. + ~TapDelay(); + + //! Set the maximum delay-line length. + /*! + This method should generally only be used during initial setup + of the delay line. If it is used between calls to the tick() + function, without a call to clear(), a signal discontinuity will + likely occur. If the current maximum length is greater than the + new length, no change will be made. + */ + void setMaximumDelay( unsigned long delay ); + + //! Set the delay-line tap lengths. + /*! + The valid range for each tap length is from 0 to the maximum delay-line length. + */ + void setTapDelays( std::vector taps ); + + //! Return the current delay-line length. + std::vector getTapDelays( void ) const { return delays_; }; + + //! Return the specified tap value of the last computed frame. + /*! + Use the lastFrame() function to get all tap values from the + last computed frame. The \c tap argument must be less than the + number of delayline taps (the first tap is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFloat lastOut( unsigned int tap = 0 ) const; + + //! Input one sample to the delayline and return outputs at all tap positions. + /*! + The StkFrames argument reference is returned. The output + values are ordered according to the tap positions set using the + setTapDelays() function (no sorting is performed). The StkFrames + argument must contain at least as many channels as the number of + taps. However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFloat input, StkFrames& outputs ); + + //! Take a channel of the StkFrames object as inputs to the filter and write outputs back to the same object. + /*! + The StkFrames argument reference is returned. The output + values are ordered according to the tap positions set using the + setTapDelays() function (no sorting is performed). The StkFrames + argument must contain at least as many channels as the number of + taps. However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. The output values + are ordered according to the tap positions set using the + setTapDelays() function (no sorting is performed). The \c + iChannel argument must be less than the number of channels in + the \c iFrames argument (the first channel is specified by 0). + The \c oFrames argument must contain at least as many channels as + the number of taps. However, range checking is only performed if + _STK_DEBUG_ is defined during compilation, in which case an + out-of-range value will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0 ); + + protected: + + unsigned long inPoint_; + std::vector outPoint_; + std::vector delays_; + +}; + +inline StkFloat TapDelay :: lastOut( unsigned int tap ) const +{ +#if defined(_STK_DEBUG_) + if ( tap >= lastFrame_.size() ) { + oStream_ << "TapDelay::lastOut(): tap argument and number of taps are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[tap]; +} + +inline StkFrames& TapDelay :: tick( StkFloat input, StkFrames& outputs ) +{ +#if defined(_STK_DEBUG_) + if ( outputs.channels() < outPoint_.size() ) { + oStream_ << "TapDelay::tick(): number of taps > channels in StkFrames argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + inputs_[inPoint_++] = input * gain_; + + // Check for end condition + if ( inPoint_ == inputs_.size() ) + inPoint_ = 0; + + // Read out next values + StkFloat *outs = &outputs[0]; + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "TapDelay::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + if ( frames.channels() < outPoint_.size() ) { + oStream_ << "TapDelay::tick(): number of taps > channels in StkFrames argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &frames[channel]; + StkFloat *oSamples = &frames[0]; + std::size_t j; + unsigned int iHop = frames.channels(); + std::size_t oHop = frames.channels() - outPoint_.size(); + for ( unsigned long i=0; i= iFrames.channels() ) { + oStream_ << "TapDelay::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + if ( oFrames.channels() < outPoint_.size() ) { + oStream_ << "TapDelay::tick(): number of taps > channels in output StkFrames argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[0]; + std::size_t j; + unsigned int iHop = iFrames.channels(); + std::size_t oHop = oFrames.channels() - outPoint_.size(); + for ( unsigned long i=0; i3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class TubeBell : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + TubeBell( void ); + + //! Class destructor. + ~TubeBell( void ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat TubeBell :: tick( unsigned int ) +{ + StkFloat temp, temp2; + + temp = gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp = temp * control1_; + + waves_[0]->addPhaseOffset( temp ); + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + twozero_.tick( temp ); + + waves_[2]->addPhaseOffset( temp ); + temp = ( 1.0 - (control2_ * 0.5)) * gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + temp += control2_ * 0.5 * gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + + // Calculate amplitude modulation and apply it to output. + temp2 = vibrato_.tick() * modDepth_; + temp = temp * (1.0 + temp2); + + lastFrame_[0] = temp * 0.5; + return lastFrame_[0]; +} + +inline StkFrames& TubeBell :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "TubeBell::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i coefficients ); + + //! Return an StkFrames reference to the last output sample frame. + //const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Return the last computed output value. + // StkFloat lastOut( void ) { return lastFrame_[0]; }; + StkFloat lastOut( void ) { return lastOutput_; }; + + //! Compute and return one output sample. + StkFloat tick( StkFloat input ); + + //! Take a channel of the \c iFrames object as inputs to the class and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the effect and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + DelayA delayLine_; + DelayL combDelay_; + Fir loopFilter_; + + StkFloat lastOutput_; + StkFloat frequency_; + StkFloat loopGain_; + StkFloat pluckPosition_; +}; + +inline StkFloat Twang :: tick( StkFloat input ) +{ + lastOutput_ = delayLine_.tick( input + loopFilter_.tick( delayLine_.lastOut() ) ); + lastOutput_ -= combDelay_.tick( lastOutput_ ); // comb filtering on output + lastOutput_ *= 0.5; + + return lastOutput_; +} + +inline StkFrames& Twang :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "Twang::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "Twang::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i= 1.0. The + \e frequency value should be between zero and half the sample rate. + For a better resonance filter, use a BiQuad filter. \sa BiQuad + filter class + */ + void setResonance(StkFloat frequency, StkFloat radius, bool normalize = false); + + //! Return the last computed output value. + StkFloat lastOut( void ) const { return lastFrame_[0]; }; + + //! Input one sample to the filter and return one output. + StkFloat tick( StkFloat input ); + + //! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs. + /*! + The StkFrames argument reference is returned. The \c channel + argument must be less than the number of channels in the + StkFrames argument (the first channel is specified by 0). + However, range checking is only performed if _STK_DEBUG_ is + defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + //! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object. + /*! + The \c iFrames object reference is returned. Each channel + argument must be less than the number of channels in the + corresponding StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 ); + + protected: + + virtual void sampleRateChanged( StkFloat newRate, StkFloat oldRate ); +}; + +inline StkFloat TwoPole :: tick( StkFloat input ) +{ + inputs_[0] = gain_ * input; + lastFrame_[0] = b_[0] * inputs_[0] - a_[1] * outputs_[1] - a_[2] * outputs_[2]; + outputs_[2] = outputs_[1]; + outputs_[1] = lastFrame_[0]; + + return lastFrame_[0]; +} + +inline StkFrames& TwoPole :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "TwoPole::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "TwoPole::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i= frames.channels() ) { + oStream_ << "TwoZero::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "TwoZero::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class Vector3D + \brief STK 3D vector class. + + This class implements a three-dimensional vector. + + by Perry R. Cook, 1995--2021. +*/ +/***************************************************/ + +class Vector3D : public Stk +{ + +public: + //! Default constructor taking optional initial X, Y, and Z values. + Vector3D( StkFloat x = 0.0, StkFloat y = 0.0, StkFloat z = 0.0 ) { setXYZ( x, y, z ); }; + + //! Get the current X value. + StkFloat getX( void ) { return X_; }; + + //! Get the current Y value. + StkFloat getY( void ) { return Y_; }; + + //! Get the current Z value. + StkFloat getZ( void ) { return Z_; }; + + //! Calculate the vector length. + StkFloat getLength( void ); + + //! Set the X, Y, and Z values simultaniously. + void setXYZ( StkFloat x, StkFloat y, StkFloat z ) { X_ = x; Y_ = y; Z_ = z; }; + + //! Set the X value. + void setX( StkFloat x ) { X_ = x; }; + + //! Set the Y value. + void setY( StkFloat y ) { Y_ = y; }; + + //! Set the Z value. + void setZ( StkFloat z ) { Z_ = z; }; + +protected: + StkFloat X_; + StkFloat Y_; + StkFloat Z_; +}; + +inline StkFloat Vector3D :: getLength( void ) +{ + StkFloat temp; + temp = X_ * X_; + temp += Y_ * Y_; + temp += Z_ * Z_; + temp = sqrt( temp ); + return temp; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/include/VoicForm.h b/7.x/src/stk/include/VoicForm.h new file mode 100644 index 0000000..3c24104 --- /dev/null +++ b/7.x/src/stk/include/VoicForm.h @@ -0,0 +1,161 @@ +#ifndef STK_VOICFORM_H +#define STK_VOICFORM_H + +#include "Instrmnt.h" +#include "Envelope.h" +#include "Noise.h" +#include "SingWave.h" +#include "FormSwep.h" +#include "OnePole.h" +#include "OneZero.h" + +namespace stk { + +/***************************************************/ +/*! \class VoicForm + \brief Four formant synthesis instrument. + + This instrument contains an excitation singing + wavetable (looping wave with random and + periodic vibrato, smoothing on frequency, + etc.), excitation noise, and four sweepable + complex resonances. + + Measured formant data is included, and enough + data is there to support either parallel or + cascade synthesis. In the floating point case + cascade synthesis is the most natural so + that's what you'll find here. + + Control Change Numbers: + - Voiced/Unvoiced Mix = 2 + - Vowel/Phoneme Selection = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Loudness (Spectral Tilt) = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class VoicForm : public Instrmnt +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + VoicForm( void ); + + //! Class destructor. + ~VoicForm( void ); + + //! Reset and clear all internal state. + void clear( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Set instrument parameters for the given phoneme. Returns false if phoneme not found. + bool setPhoneme( const char* phoneme ); + + //! Set the voiced component gain. + void setVoiced( StkFloat vGain ) { voiced_->setGainTarget(vGain); }; + + //! Set the unvoiced component gain. + void setUnVoiced( StkFloat nGain ) { noiseEnv_.setTarget(nGain); }; + + //! Set the sweep rate for a particular formant filter (0-3). + void setFilterSweepRate( unsigned int whichOne, StkFloat rate ); + + //! Set voiced component pitch sweep rate. + void setPitchSweepRate( StkFloat rate ) { voiced_->setSweepRate(rate); }; + + //! Start the voice. + void speak( void ) { voiced_->noteOn(); }; + + //! Stop the voice. + void quiet( void ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Stop a note with the given amplitude (speed of decay). + void noteOff( StkFloat amplitude ) { this->quiet(); }; + + //! Perform the control change specified by \e number and \e value (0.0 - 128.0). + void controlChange( int number, StkFloat value ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + +protected: + + SingWave *voiced_; + Noise noise_; + Envelope noiseEnv_; + FormSwep filters_[4]; + OnePole onepole_; + OneZero onezero_; + +}; + +inline StkFloat VoicForm :: tick( unsigned int ) +{ + StkFloat temp; + temp = onepole_.tick( onezero_.tick( voiced_->tick() ) ); + temp += noiseEnv_.tick() * noise_.tick(); + lastFrame_[0] = filters_[0].tick(temp); + lastFrame_[0] += filters_[1].tick(temp); + lastFrame_[0] += filters_[2].tick(temp); + lastFrame_[0] += filters_[3].tick(temp); + /* + temp += noiseEnv_.tick() * noise_.tick(); + lastFrame_[0] = filters_[0].tick(temp); + lastFrame_[0] = filters_[1].tick(lastFrame_[0]); + lastFrame_[0] = filters_[2].tick(lastFrame_[0]); + lastFrame_[0] = filters_[3].tick(lastFrame_[0]); + */ + return lastFrame_[0]; +} + +inline StkFrames& VoicForm :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "VoicForm::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i + +namespace stk { + +/***************************************************/ +/*! \class Voicer + \brief STK voice manager class. + + This class can be used to manage a group of STK instrument + classes. Individual voices can be controlled via unique note + tags. Instrument groups can be controlled by group number. + + A previously constructed STK instrument class is linked with a + voice manager using the addInstrument() function. An optional + group number argument can be specified to the addInstrument() + function as well (default group = 0). The voice manager does not + delete any instrument instances ... it is the responsibility of + the user to allocate and deallocate all instruments. + + The tick() function returns the mix of all sounding voices. Each + noteOn returns a unique tag (credits to the NeXT MusicKit), so you + can send control changes to specific voices within an ensemble. + Alternately, control changes can be sent to all voices in a given + group. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Voicer : public Stk +{ + public: + //! Class constructor taking an optional note decay time (in seconds). + Voicer( StkFloat decayTime = 0.2 ); + + //! Add an instrument with an optional group number to the voice manager. + /*! + A set of instruments can be grouped by group number and + controlled via the functions that take a group number argument. + */ + void addInstrument( Instrmnt *instrument, int group=0 ); + + //! Remove the given instrument pointer from the voice manager's control. + /*! + It is important that any instruments which are to be deleted by + the user while the voice manager is running be first removed from + the manager's control via this function!! + */ + void removeInstrument( Instrmnt *instrument ); + + //! Initiate a noteOn event with the given note number and amplitude and return a unique note tag. + /*! + Send the noteOn message to the first available unused voice. + If all voices are sounding, the oldest voice is interrupted and + sent the noteOn message. If the optional group argument is + non-zero, only voices in that group are used. If no voices are + found for a specified non-zero group value, the function returns + -1. The amplitude value should be in the range 0.0 - 128.0. + */ + long noteOn( StkFloat noteNumber, StkFloat amplitude, int group=0 ); + + //! Send a noteOff to all voices having the given noteNumber and optional group (default group = 0). + /*! + The amplitude value should be in the range 0.0 - 128.0. + */ + void noteOff( StkFloat noteNumber, StkFloat amplitude, int group=0 ); + + //! Send a noteOff to the voice with the given note tag. + /*! + The amplitude value should be in the range 0.0 - 128.0. + */ + void noteOff( long tag, StkFloat amplitude ); + + //! Send a frequency update message to all voices assigned to the optional group argument (default group = 0). + /*! + The \e noteNumber argument corresponds to a MIDI note number, though it is a floating-point value and can range beyond the normal 0-127 range. + */ + void setFrequency( StkFloat noteNumber, int group=0 ); + + //! Send a frequency update message to the voice with the given note tag. + /*! + The \e noteNumber argument corresponds to a MIDI note number, though it is a floating-point value and can range beyond the normal 0-127 range. + */ + void setFrequency( long tag, StkFloat noteNumber ); + + //! Send a pitchBend message to all voices assigned to the optional group argument (default group = 0). + void pitchBend( StkFloat value, int group=0 ); + + //! Send a pitchBend message to the voice with the given note tag. + void pitchBend( long tag, StkFloat value ); + + //! Send a controlChange to all instruments assigned to the optional group argument (default group = 0). + void controlChange( int number, StkFloat value, int group=0 ); + + //! Send a controlChange to the voice with the given note tag. + void controlChange( long tag, int number, StkFloat value ); + + //! Send a noteOff message to all existing voices. + void silence( void ); + + //! Return the current number of output channels. + unsigned int channelsOut( void ) const { return lastFrame_.channels(); }; + + //! Return an StkFrames reference to the last output sample frame. + const StkFrames& lastFrame( void ) const { return lastFrame_; }; + + //! Return the specified channel value of the last computed frame. + /*! + The \c channel argument must be less than the number of output + channels, which can be determined with the channelsOut() function + (the first channel is specified by 0). However, range checking is + only performed if _STK_DEBUG_ is defined during compilation, in + which case an out-of-range value will trigger an StkError + exception. \sa lastFrame() + */ + StkFloat lastOut( unsigned int channel = 0 ); + + //! Mix one sample frame of all sounding voices and return the specified \c channel value. + /*! + The \c channel argument must be less than the number of output + channels, which can be determined with the channelsOut() function + (the first channel is specified by 0). However, range checking is + only performed if _STK_DEBUG_ is defined during compilation, in + which case an out-of-range value will trigger an StkError + exception. + */ + StkFloat tick( unsigned int channel = 0 ); + + //! Fill the StkFrames argument with computed frames and return the same reference. + /*! + The number of channels in the StkFrames argument must equal + the number of channels in the file data. However, this is only + checked if _STK_DEBUG_ is defined during compilation, in which + case an incompatibility will trigger an StkError exception. If no + file data is loaded, the function does nothing (a warning will be + issued if _STK_DEBUG_ is defined during compilation). + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + + struct Voice { + Instrmnt *instrument; + long tag; + StkFloat noteNumber; + StkFloat frequency; + int sounding; + int group; + + // Default constructor. + Voice() + :instrument(0), tag(0), noteNumber(-1.0), frequency(0.0), sounding(0), group(0) {} + }; + + std::vector voices_; + long tags_; + int muteTime_; + StkFrames lastFrame_; +}; + +inline StkFloat Voicer :: lastOut( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= lastFrame_.channels() ) { + oStream_ << "Voicer::lastOut(): channel argument is invalid!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + return lastFrame_[channel]; +} + + +inline StkFloat Voicer :: tick( unsigned int channel ) +{ + unsigned int j; + for ( j=0; jtick(); + for ( j=0; jchannelsOut(); j++ ) lastFrame_[j] += voices_[i].instrument->lastOut( j ); + } + if ( voices_[i].sounding < 0 ) + voices_[i].sounding++; + if ( voices_[i].sounding == 0 ) + voices_[i].noteNumber = -1; + } + + return lastFrame_[channel]; +} + +inline StkFrames& Voicer :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Voicer::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "Whistle::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +class Wurley : public FM +{ + public: + //! Class constructor. + /*! + An StkError will be thrown if the rawwave path is incorrectly set. + */ + Wurley( void ); + + //! Class destructor. + ~Wurley( void ); + + //! Set instrument parameters for a particular frequency. + void setFrequency( StkFloat frequency ); + + //! Start a note with the given frequency and amplitude. + void noteOn( StkFloat frequency, StkFloat amplitude ); + + //! Compute and return one output sample. + StkFloat tick( unsigned int channel = 0 ); + + //! Fill a channel of the StkFrames object with computed outputs. + /*! + The \c channel argument must be less than the number of + channels in the StkFrames argument (the first channel is specified + by 0). However, range checking is only performed if _STK_DEBUG_ + is defined during compilation, in which case an out-of-range value + will trigger an StkError exception. + */ + StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); + + protected: + +}; + +inline StkFloat Wurley :: tick( unsigned int ) +{ + StkFloat temp, temp2; + + temp = gains_[1] * adsr_[1]->tick() * waves_[1]->tick(); + temp = temp * control1_; + + waves_[0]->addPhaseOffset( temp ); + waves_[3]->addPhaseOffset( twozero_.lastOut() ); + temp = gains_[3] * adsr_[3]->tick() * waves_[3]->tick(); + twozero_.tick(temp); + + waves_[2]->addPhaseOffset( temp ); + temp = ( 1.0 - (control2_ * 0.5)) * gains_[0] * adsr_[0]->tick() * waves_[0]->tick(); + temp += control2_ * 0.5 * gains_[2] * adsr_[2]->tick() * waves_[2]->tick(); + + // Calculate amplitude modulation and apply it to output. + temp2 = vibrato_.tick() * modDepth_; + temp = temp * (1.0 + temp2); + + lastFrame_[0] = temp * 0.5; + return lastFrame_[0]; +} + +inline StkFrames& Wurley :: tick( StkFrames& frames, unsigned int channel ) +{ + unsigned int nChannels = lastFrame_.channels(); +#if defined(_STK_DEBUG_) + if ( channel > frames.channels() - nChannels ) { + oStream_ << "Wurley::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i 1.0 ) { + sample = 1.0; + clip = true; + } + else if ( sample < -1.0 ) { + sample = -1.0; + clip = true; + } + + if ( clip == true && clipping_ == false ) { + // First occurrence of clipping since instantiation or reset. + clipping_ = true; + oStream_ << "WvOut: data value(s) outside +-1.0 detected ... clamping at outer bound!"; + handleError( StkError::WARNING ); + } + + return sample; +} + +} // stk namespace + +#endif diff --git a/7.x/src/stk/rawwaves/ahh.raw b/7.x/src/stk/rawwaves/ahh.raw new file mode 100644 index 0000000..dda5f55 Binary files /dev/null and b/7.x/src/stk/rawwaves/ahh.raw differ diff --git a/7.x/src/stk/rawwaves/bassdrum.raw b/7.x/src/stk/rawwaves/bassdrum.raw new file mode 100644 index 0000000..16b1ece Binary files /dev/null and b/7.x/src/stk/rawwaves/bassdrum.raw differ diff --git a/7.x/src/stk/rawwaves/britestk.raw b/7.x/src/stk/rawwaves/britestk.raw new file mode 100644 index 0000000..4bf9ba6 Binary files /dev/null and b/7.x/src/stk/rawwaves/britestk.raw differ diff --git a/7.x/src/stk/rawwaves/cowbell1.raw b/7.x/src/stk/rawwaves/cowbell1.raw new file mode 100644 index 0000000..4cd641f Binary files /dev/null and b/7.x/src/stk/rawwaves/cowbell1.raw differ diff --git a/7.x/src/stk/rawwaves/crashcym.raw b/7.x/src/stk/rawwaves/crashcym.raw new file mode 100644 index 0000000..21318b2 Binary files /dev/null and b/7.x/src/stk/rawwaves/crashcym.raw differ diff --git a/7.x/src/stk/rawwaves/dope.raw b/7.x/src/stk/rawwaves/dope.raw new file mode 100644 index 0000000..6a7a1ff Binary files /dev/null and b/7.x/src/stk/rawwaves/dope.raw differ diff --git a/7.x/src/stk/rawwaves/eee.raw b/7.x/src/stk/rawwaves/eee.raw new file mode 100644 index 0000000..d3888db Binary files /dev/null and b/7.x/src/stk/rawwaves/eee.raw differ diff --git a/7.x/src/stk/rawwaves/fwavblnk.raw b/7.x/src/stk/rawwaves/fwavblnk.raw new file mode 100644 index 0000000..14da074 Binary files /dev/null and b/7.x/src/stk/rawwaves/fwavblnk.raw differ diff --git a/7.x/src/stk/rawwaves/halfwave.raw b/7.x/src/stk/rawwaves/halfwave.raw new file mode 100644 index 0000000..b34639b Binary files /dev/null and b/7.x/src/stk/rawwaves/halfwave.raw differ diff --git a/7.x/src/stk/rawwaves/hihatcym.raw b/7.x/src/stk/rawwaves/hihatcym.raw new file mode 100644 index 0000000..f61389e Binary files /dev/null and b/7.x/src/stk/rawwaves/hihatcym.raw differ diff --git a/7.x/src/stk/rawwaves/impuls10.raw b/7.x/src/stk/rawwaves/impuls10.raw new file mode 100644 index 0000000..e18689d Binary files /dev/null and b/7.x/src/stk/rawwaves/impuls10.raw differ diff --git a/7.x/src/stk/rawwaves/impuls20.raw b/7.x/src/stk/rawwaves/impuls20.raw new file mode 100644 index 0000000..9d89aec Binary files /dev/null and b/7.x/src/stk/rawwaves/impuls20.raw differ diff --git a/7.x/src/stk/rawwaves/impuls40.raw b/7.x/src/stk/rawwaves/impuls40.raw new file mode 100644 index 0000000..9a3619b Binary files /dev/null and b/7.x/src/stk/rawwaves/impuls40.raw differ diff --git a/7.x/src/stk/rawwaves/makefunc.c b/7.x/src/stk/rawwaves/makefunc.c new file mode 100644 index 0000000..b318065 --- /dev/null +++ b/7.x/src/stk/rawwaves/makefunc.c @@ -0,0 +1,55 @@ +/**********************************************/ +/** Utility to make various functions **/ +/** like exponential and log gain curves. **/ +/** **/ +/** Included here: **/ +/** Yamaha TX81Z curves for master gain, **/ +/** Envelope Rates (in normalized units), **/ +/** envelope sustain level, and more.... **/ +/**********************************************/ + +#include +#include +#include + +void main() +{ + int i,j; + double temp; + double data[128]; + + /*************** TX81Z Master Gain *************/ + for (i=0;i<100;i++) { + data[i] = pow(2.0,-(99-i)/10.0); + } + data[0] = 0.0; + printf("double __FM4Op_gains[99] = {"); + for (i=0;i<100;i++) { + if (i%8 == 0) printf("\n"); + printf("%lf,",data[i]); + } + printf("};\n"); + /*************** TX81Z Sustain Level ***********/ + for (i=0;i<16;i++) { + data[i] = pow(2.0,-(15-i)/2.0); + } + data[0] = 0.0; + printf("double __FM4Op_susLevels[16] = {"); + for (i=0;i<16;i++) { + if (i%8 == 0) printf("\n"); + printf("%lf,",data[i]); + } + printf("};\n"); + /****************** Attack Rate ***************/ + for (i=0;i<32;i++) { + data[i] = 6.0 * pow(5.7,-(i-1)/5.0); + } + printf("double __FM4Op_attTimes[16] = {"); + for (i=0;i<32;i++) { + if (i%8 == 0) printf("\n"); + printf("%lf,",data[i]); + } + printf("};\n"); + exit(1); +} + diff --git a/7.x/src/stk/rawwaves/makemidi.c b/7.x/src/stk/rawwaves/makemidi.c new file mode 100644 index 0000000..480f638 --- /dev/null +++ b/7.x/src/stk/rawwaves/makemidi.c @@ -0,0 +1,33 @@ +/**********************************************/ +/** Utility to make various functions **/ +/** like exponential and log gain curves. **/ +/** Specifically for direct MIDI parameter **/ +/** conversions. **/ +/** Included here: **/ +/** A440 Referenced Equal Tempered Pitches **/ +/** as a function of MIDI note number. **/ +/** **/ +/**********************************************/ + +#include +#include +#include + +void main() +{ + int i,j; + double temp; + double data[128]; + + /********* Pitch as fn. of MIDI Note **********/ + + printf("double __MIDI_To_Pitch[128] = {"); + for (i=0;i<128;i++) { + if (i%8 == 0) printf("\n"); + temp = 220.0 * pow(2.0,((double) i - 57) / 12.0); + printf("%.2lf,",temp); + } + printf("};\n"); + exit(1); +} + diff --git a/7.x/src/stk/rawwaves/makewavs.c b/7.x/src/stk/rawwaves/makewavs.c new file mode 100644 index 0000000..20a89ac --- /dev/null +++ b/7.x/src/stk/rawwaves/makewavs.c @@ -0,0 +1,116 @@ +/**********************************************/ +/** Utility to make various flavors of **/ +/** sine wave (rectified, etc), and **/ +/** other commonly needed waveforms, like **/ +/** triangles, ramps, etc. **/ +/** The files generated are all 16 bit **/ +/** linear signed integer, of length **/ +/** as defined by LENGTH below **/ +/**********************************************/ + +#include +#include +#include + +#define LENGTH 256 +#define PI 3.14159265358979323846 + +void main() +{ + int i,j; + double temp; + short data[LENGTH + 2]; + FILE *fd; + + /////////// Yer Basic TX81Z Waves, Including Sine /////////// + fd = fopen("halfwave.raw","wb"); + for (i=0;i +#include +#include + +#define LENGTH 1024 +#define PI 3.14159265358979323846 + +void main() +{ + int i,j; + double temp; + short data[LENGTH + 2]; + FILE *fd; + + fd = fopen("sinewave.raw","wb"); + for (i=0; i 0.0 ) + releaseRate_ = value_ / ( releaseTime_ * Stk::sampleRate() ); +} + +void ADSR :: setAttackRate( StkFloat rate ) +{ + if ( rate < 0.0 ) { + oStream_ << "ADSR::setAttackRate: argument must be >= 0.0!"; + handleError( StkError::WARNING ); return; + } + + attackRate_ = rate; +} + +void ADSR :: setAttackTarget( StkFloat target ) +{ + if ( target < 0.0 ) { + oStream_ << "ADSR::setAttackTarget: negative target not allowed!"; + handleError( StkError::WARNING ); return; + } + + target_ = target; +} + +void ADSR :: setDecayRate( StkFloat rate ) +{ + if ( rate < 0.0 ) { + oStream_ << "ADSR::setDecayRate: negative rates not allowed!"; + handleError( StkError::WARNING ); return; + } + + decayRate_ = rate; +} + +void ADSR :: setSustainLevel( StkFloat level ) +{ + if ( level < 0.0 ) { + oStream_ << "ADSR::setSustainLevel: negative level not allowed!"; + handleError( StkError::WARNING ); return; + } + + sustainLevel_ = level; +} + +void ADSR :: setReleaseRate( StkFloat rate ) +{ + if ( rate < 0.0 ) { + oStream_ << "ADSR::setReleaseRate: negative rates not allowed!"; + handleError( StkError::WARNING ); return; + } + + releaseRate_ = rate; + + // Set to negative value so we don't update the release rate on keyOff() + releaseTime_ = -1.0; +} + +void ADSR :: setAttackTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "ADSR::setAttackTime: negative or zero times not allowed!"; + handleError( StkError::WARNING ); return; + } + + attackRate_ = 1.0 / ( time * Stk::sampleRate() ); +} + +void ADSR :: setDecayTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "ADSR::setDecayTime: negative or zero times not allowed!"; + handleError( StkError::WARNING ); return; + } + + decayRate_ = (1.0 - sustainLevel_) / ( time * Stk::sampleRate() ); +} + +void ADSR :: setReleaseTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "ADSR::setReleaseTime: negative or zero times not allowed!"; + handleError( StkError::WARNING ); return; + } + + releaseRate_ = sustainLevel_ / ( time * Stk::sampleRate() ); + releaseTime_ = time; +} + +void ADSR :: setAllTimes( StkFloat aTime, StkFloat dTime, StkFloat sLevel, StkFloat rTime ) +{ + this->setAttackTime( aTime ); + this->setSustainLevel( sLevel ); + this->setDecayTime( dTime ); + this->setReleaseTime( rTime ); +} + +void ADSR :: setTarget( StkFloat target ) +{ + if ( target < 0.0 ) { + oStream_ << "ADSR::setTarget: negative target not allowed!"; + handleError( StkError::WARNING ); return; + } + + target_ = target; + + this->setSustainLevel( target_ ); + if ( value_ < target_ ) state_ = ATTACK; + if ( value_ > target_ ) state_ = DECAY; +} + +void ADSR :: setValue( StkFloat value ) +{ + state_ = SUSTAIN; + target_ = value; + value_ = value; + this->setSustainLevel( value ); + lastFrame_[0] = value; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Asymp.cpp b/7.x/src/stk/src/Asymp.cpp new file mode 100644 index 0000000..7be0519 --- /dev/null +++ b/7.x/src/stk/src/Asymp.cpp @@ -0,0 +1,112 @@ +/***************************************************/ +/*! \class Asymp + \brief STK asymptotic curve envelope class + + This class implements a simple envelope generator + which asymptotically approaches a target value. + The algorithm used is of the form: + + y[n] = a y[n-1] + (1-a) target, + + where a = exp(-T/tau), T is the sample period, and + tau is a time constant. The user can set the time + constant (default value = 0.3) and target value. + Theoretically, this recursion never reaches its + target, though the calculations in this class are + stopped when the current value gets within a small + threshold value of the target (at which time the + current value is set to the target). It responds + to \e keyOn and \e keyOff messages by ramping to + 1.0 on keyOn and to 0.0 on keyOff. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Asymp.h" +#include + +namespace stk { + +Asymp :: Asymp( void ) +{ + value_ = 0.0; + target_ = 0.0; + state_ = 0; + factor_ = exp( -1.0 / ( 0.3 * Stk::sampleRate() ) ); + constant_ = 0.0; + Stk::addSampleRateAlert( this ); +} + +Asymp :: ~Asymp( void ) +{ + Stk::removeSampleRateAlert( this ); +} + +void Asymp :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + StkFloat tau = -1.0 / ( std::log( factor_ ) * oldRate ); + factor_ = std::exp( -1.0 / ( tau * newRate ) ); + } +} + +void Asymp :: keyOn( void ) +{ + this->setTarget( 1.0 ); +} + +void Asymp :: keyOff( void ) +{ + this->setTarget( 0.0 ); +} + +void Asymp :: setTau( StkFloat tau ) +{ + if ( tau <= 0.0 ) { + oStream_ << "Asymp::setTau: negative or zero tau not allowed!"; + handleError( StkError::WARNING ); return; + } + + factor_ = std::exp( -1.0 / ( tau * Stk::sampleRate() ) ); + constant_ = ( 1.0 - factor_ ) * target_; +} + +void Asymp :: setTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "Asymp::setTime: negative or zero times not allowed!"; + handleError( StkError::WARNING ); return; + } + + StkFloat tau = -time / std::log( TARGET_THRESHOLD ); + factor_ = std::exp( -1.0 / ( tau * Stk::sampleRate() ) ); + constant_ = ( 1.0 - factor_ ) * target_; +} + +void Asymp :: setT60( StkFloat t60 ) +{ + if ( t60 <= 0.0 ) { + oStream_ << "Asymp::setT60: negative or zero t60 not allowed!"; + handleError( StkError::WARNING ); return; + } + + setTau( t60 / 6.91 ); +} + +void Asymp :: setTarget( StkFloat target ) +{ + target_ = target; + if ( value_ != target_ ) state_ = 1; + constant_ = ( 1.0 - factor_ ) * target_; +} + +void Asymp :: setValue( StkFloat value ) +{ + state_ = 0; + target_ = value; + value_ = value; +} + +} // stk namespace + diff --git a/7.x/src/stk/src/BandedWG.cpp b/7.x/src/stk/src/BandedWG.cpp new file mode 100644 index 0000000..5df80ac --- /dev/null +++ b/7.x/src/stk/src/BandedWG.cpp @@ -0,0 +1,364 @@ +/***************************************************/ +/*! \class BandedWG + \brief Banded waveguide modeling class. + + This class uses banded waveguide techniques to + model a variety of sounds, including bowed + bars, glasses, and bowls. For more + information, see Essl, G. and Cook, P. "Banded + Waveguides: Towards Physical Modelling of Bar + Percussion Instruments", Proceedings of the + 1999 International Computer Music Conference. + + Control Change Numbers: + - Bow Pressure = 2 + - Bow Motion = 4 + - Strike Position = 8 (not implemented) + - Vibrato Frequency = 11 + - Gain = 1 + - Bow Velocity = 128 + - Set Striking = 64 + - Instrument Presets = 16 + - Uniform Bar = 0 + - Tuned Bar = 1 + - Glass Harmonica = 2 + - Tibetan Bowl = 3 + + by Georg Essl, 1999--2004. + Modified for STK 4.0 by Gary Scavone. +*/ +/***************************************************/ + +#include "BandedWG.h" +#include "SKINImsg.h" +#include + +namespace stk { + +BandedWG :: BandedWG( void ) +{ + doPluck_ = true; + + bowTable_.setSlope( 3.0 ); + adsr_.setAllTimes( 0.02, 0.005, 0.9, 0.01 ); + + frequency_ = 220.0; + this->setPreset(0); + + bowPosition_ = 0; + baseGain_ = 0.999; + + integrationConstant_ = 0.0; + trackVelocity_ = false; + + bowVelocity_ = 0.0; + bowTarget_ = 0.0; + + strikeAmp_ = 0.0; +} + +BandedWG :: ~BandedWG( void ) +{ +} + +void BandedWG :: clear( void ) +{ + for ( int i=0; i 1568.0) frequency = 1568.0; + + StkFloat radius; + StkFloat base = Stk::sampleRate() / frequency; + StkFloat length; + for (int i=0; i 2.0) { + delay_[i].setDelay( length ); + gains_[i]=basegains_[i]; + // gains_[i]=(StkFloat) pow(basegains_[i], 1/((StkFloat)delay_[i].getDelay())); + // std::cerr << gains_[i]; + } + else { + nModes_ = i; + break; + } + // std::cerr << std::endl; + + // Set the bandpass filter resonances + radius = 1.0 - PI * 32 / Stk::sampleRate(); //frequency_ * modes_[i] / Stk::sampleRate()/32; + if ( radius < 0.0 ) radius = 0.0; + bandpass_[i].setResonance(frequency * modes_[i], radius, true); + + delay_[i].clear(); + bandpass_[i].clear(); + } + + //int olen = (int)(delay_[0].getDelay()); + //strikePosition_ = (int)(strikePosition_*(length/modes_[0])/olen); +} + +void BandedWG :: setStrikePosition( StkFloat position ) +{ + strikePosition_ = (int)(delay_[0].getDelay() * position / 2.0); +} + +void BandedWG :: startBowing( StkFloat amplitude, StkFloat rate ) +{ + adsr_.setAttackRate(rate); + adsr_.keyOn(); + maxVelocity_ = 0.03 + (0.1 * amplitude); +} + +void BandedWG :: stopBowing( StkFloat rate ) +{ + adsr_.setReleaseRate(rate); + adsr_.keyOff(); +} + +void BandedWG :: pluck( StkFloat amplitude ) +{ + int j; + StkFloat min_len = delay_[nModes_-1].getDelay(); + for (int i=0; isetFrequency( frequency ); + + if ( doPluck_ ) + this->pluck( amplitude ); + else + this->startBowing( amplitude, amplitude * 0.001 ); +} + +void BandedWG :: noteOff( StkFloat amplitude ) +{ + if ( !doPluck_ ) + this->stopBowing( (1.0 - amplitude) * 0.005 ); +} + +StkFloat BandedWG :: tick( unsigned int ) +{ + int k; + + StkFloat input = 0.0; + if ( doPluck_ ) { + input = 0.0; + // input = strikeAmp_/nModes_; + // strikeAmp_ = 0.0; + } + else { + if ( integrationConstant_ == 0.0 ) + velocityInput_ = 0.0; + else + velocityInput_ = integrationConstant_ * velocityInput_; + + for ( k=0; k 128.0 ) ) { + oStream_ << "BandedWG::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_BowPressure_) { // 2 + if ( normalizedValue == 0.0 ) + doPluck_ = true; + else { + doPluck_ = false; + bowTable_.setSlope( 10.0 - (9.0 * normalizedValue)); + } + } + else if (number == 4) { // 4 + if ( !trackVelocity_ ) trackVelocity_ = true; + bowTarget_ += 0.005 * (normalizedValue - bowPosition_); + bowPosition_ = normalizedValue; + //adsr_.setTarget(bowPosition_); + } + else if (number == 8) // 8 + this->setStrikePosition( normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) { // 128 + //bowTarget_ += 0.02 * (normalizedValue - bowPosition_); + //bowPosition_ = normalizedValue; + if ( trackVelocity_ ) trackVelocity_ = false; + maxVelocity_ = 0.13 * normalizedValue; + adsr_.setTarget(normalizedValue); + } + else if (number == __SK_ModWheel_) { // 1 + // baseGain_ = 0.9989999999 + (0.001 * normalizedValue ); + baseGain_ = 0.8999999999999999 + (0.1 * normalizedValue); + // std::cerr << "Yuck!" << std::endl; + for (int i=0; isetPreset((int) value); +#if defined(_STK_DEBUG_) + else { + oStream_ << "BandedWG::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/BeeThree.cpp b/7.x/src/stk/src/BeeThree.cpp new file mode 100644 index 0000000..a9e53b8 --- /dev/null +++ b/7.x/src/stk/src/BeeThree.cpp @@ -0,0 +1,79 @@ +/***************************************************/ +/*! \class BeeThree + \brief STK Hammond-oid organ FM synthesis instrument. + + This class implements a simple 4 operator + topology, also referred to as algorithm 8 of + the TX81Z. + + \code + Algorithm 8 is : + 1 --. + 2 -\| + +-> Out + 3 -/| + 4 -- + \endcode + + Control Change Numbers: + - Operator 4 (feedback) Gain = 2 + - Operator 3 Gain = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "BeeThree.h" + +namespace stk { + +BeeThree :: BeeThree( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio( 0, 0.999 ); + this->setRatio( 1, 1.997 ); + this->setRatio( 2, 3.006 ); + this->setRatio( 3, 6.009 ); + + gains_[0] = fmGains_[95]; + gains_[1] = fmGains_[95]; + gains_[2] = fmGains_[99]; + gains_[3] = fmGains_[95]; + + adsr_[0]->setAllTimes( 0.005, 0.003, 1.0, 0.01 ); + adsr_[1]->setAllTimes( 0.005, 0.003, 1.0, 0.01 ); + adsr_[2]->setAllTimes( 0.005, 0.003, 1.0, 0.01 ); + adsr_[3]->setAllTimes( 0.005, 0.001, 0.4, 0.03 ); + + twozero_.setGain( 0.1 ); +} + +BeeThree :: ~BeeThree( void ) +{ +} + +void BeeThree :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[95]; + gains_[1] = amplitude * fmGains_[95]; + gains_[2] = amplitude * fmGains_[99]; + gains_[3] = amplitude * fmGains_[95]; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/BiQuad.cpp b/7.x/src/stk/src/BiQuad.cpp new file mode 100644 index 0000000..024a65f --- /dev/null +++ b/7.x/src/stk/src/BiQuad.cpp @@ -0,0 +1,103 @@ +/***************************************************/ +/*! \class BiQuad + \brief STK biquad (two-pole, two-zero) filter class. + + This class implements a two-pole, two-zero digital filter. + Methods are provided for creating a resonance or notch in the + frequency response while maintaining a constant filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "BiQuad.h" +#include + +namespace stk { + +BiQuad :: BiQuad() : Filter() +{ + b_.resize( 3, 0.0 ); + a_.resize( 3, 0.0 ); + b_[0] = 1.0; + a_[0] = 1.0; + inputs_.resize( 3, 1, 0.0 ); + outputs_.resize( 3, 1, 0.0 ); + + Stk::addSampleRateAlert( this ); +} + +BiQuad :: ~BiQuad() +{ + Stk::removeSampleRateAlert( this ); +} + +void BiQuad :: setCoefficients( StkFloat b0, StkFloat b1, StkFloat b2, StkFloat a1, StkFloat a2, bool clearState ) +{ + b_[0] = b0; + b_[1] = b1; + b_[2] = b2; + a_[1] = a1; + a_[2] = a2; + + if ( clearState ) this->clear(); +} + +void BiQuad :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + oStream_ << "BiQuad::sampleRateChanged: you may need to recompute filter coefficients!"; + handleError( StkError::WARNING ); + } +} + +void BiQuad :: setResonance( StkFloat frequency, StkFloat radius, bool normalize ) +{ +#if defined(_STK_DEBUG_) + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "BiQuad::setResonance: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 || radius >= 1.0 ) { + oStream_ << "BiQuad::setResonance: radius argument (" << radius << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + a_[2] = radius * radius; + a_[1] = -2.0 * radius * cos( TWO_PI * frequency / Stk::sampleRate() ); + + if ( normalize ) { + // Use zeros at +- 1 and normalize the filter peak gain. + b_[0] = 0.5 - 0.5 * a_[2]; + b_[1] = 0.0; + b_[2] = -b_[0]; + } +} + +void BiQuad :: setNotch( StkFloat frequency, StkFloat radius ) +{ +#if defined(_STK_DEBUG_) + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "BiQuad::setNotch: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 ) { + oStream_ << "BiQuad::setNotch: radius argument (" << radius << ") is negative!"; + handleError( StkError::WARNING ); return; + } +#endif + + // This method does not attempt to normalize the filter gain. + b_[2] = radius * radius; + b_[1] = (StkFloat) -2.0 * radius * cos( TWO_PI * (double) frequency / Stk::sampleRate() ); +} + +void BiQuad :: setEqualGainZeroes( void ) +{ + b_[0] = 1.0; + b_[1] = 0.0; + b_[2] = -1.0; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Blit.cpp b/7.x/src/stk/src/Blit.cpp new file mode 100644 index 0000000..cff786e --- /dev/null +++ b/7.x/src/stk/src/Blit.cpp @@ -0,0 +1,78 @@ +/***************************************************/ +/*! \class Blit + \brief STK band-limited impulse train class. + + This class generates a band-limited impulse train using a + closed-form algorithm reported by Stilson and Smith in "Alias-Free + Digital Synthesis of Classic Analog Waveforms", 1996. The user + can specify both the fundamental frequency of the impulse train + and the number of harmonics contained in the resulting signal. + + The signal is normalized so that the peak value is +/-1.0. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). + + Original code by Robin Davies, 2005. + Revisions by Gary Scavone for STK, 2005. +*/ +/***************************************************/ + +#include "Blit.h" + +namespace stk { + +Blit:: Blit( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "Blit::Blit: argument (" << frequency << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + nHarmonics_ = 0; + this->setFrequency( frequency ); + this->reset(); +} + +Blit :: ~Blit() +{ +} + +void Blit :: reset() +{ + phase_ = 0.0; + lastFrame_[0] = 0.0; +} + +void Blit :: setFrequency( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "Blit::setFrequency: argument (" << frequency << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + p_ = Stk::sampleRate() / frequency; + rate_ = PI / p_; + this->updateHarmonics(); +} + +void Blit :: setHarmonics( unsigned int nHarmonics ) +{ + nHarmonics_ = nHarmonics; + this->updateHarmonics(); +} + +void Blit :: updateHarmonics( void ) +{ + if ( nHarmonics_ <= 0 ) { + unsigned int maxHarmonics = (unsigned int) floor( 0.5 * p_ ); + m_ = 2 * maxHarmonics + 1; + } + else + m_ = 2 * nHarmonics_ + 1; +} + +} // stk namespace diff --git a/7.x/src/stk/src/BlitSaw.cpp b/7.x/src/stk/src/BlitSaw.cpp new file mode 100644 index 0000000..f732a8e --- /dev/null +++ b/7.x/src/stk/src/BlitSaw.cpp @@ -0,0 +1,91 @@ +/***************************************************/ +/*! \class BlitSaw + \brief STK band-limited sawtooth wave class. + + This class generates a band-limited sawtooth waveform using a + closed-form algorithm reported by Stilson and Smith in "Alias-Free + Digital Synthesis of Classic Analog Waveforms", 1996. The user + can specify both the fundamental frequency of the sawtooth and the + number of harmonics contained in the resulting signal. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). + + Based on initial code of Robin Davies, 2005. + Modified algorithm code by Gary Scavone, 2005. +*/ +/***************************************************/ + +#include "BlitSaw.h" + +namespace stk { + +BlitSaw:: BlitSaw( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "BlitSaw::BlitSaw: argument (" << frequency << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + nHarmonics_ = 0; + this->reset(); + this->setFrequency( frequency ); +} + +BlitSaw :: ~BlitSaw() +{ +} + +void BlitSaw :: reset() +{ + phase_ = 0.0f; + state_ = 0.0; + lastFrame_[0] = 0.0; +} + +void BlitSaw :: setFrequency( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "BlitSaw::setFrequency: argument (" << frequency << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + p_ = Stk::sampleRate() / frequency; + C2_ = 1 / p_; + rate_ = PI * C2_; + this->updateHarmonics(); +} + +void BlitSaw :: setHarmonics( unsigned int nHarmonics ) +{ + nHarmonics_ = nHarmonics; + this->updateHarmonics(); + + // I found that the initial DC offset could be minimized with an + // initial state setting as given below. This initialization should + // only happen before starting the oscillator for the first time + // (but after setting the frequency and number of harmonics). I + // struggled a bit to decide where best to put this and finally + // settled on here. In general, the user shouldn't be messing with + // the number of harmonics once the oscillator is running because + // this is automatically taken care of in the setFrequency() + // function. (GPS - 1 October 2005) + state_ = -0.5 * a_; +} + +void BlitSaw :: updateHarmonics( void ) +{ + if ( nHarmonics_ <= 0 ) { + unsigned int maxHarmonics = (unsigned int) floor( 0.5 * p_ ); + m_ = 2 * maxHarmonics + 1; + } + else + m_ = 2 * nHarmonics_ + 1; + + a_ = m_ / p_; +} + +} // stk namespace diff --git a/7.x/src/stk/src/BlitSquare.cpp b/7.x/src/stk/src/BlitSquare.cpp new file mode 100644 index 0000000..2a51b07 --- /dev/null +++ b/7.x/src/stk/src/BlitSquare.cpp @@ -0,0 +1,95 @@ +/***************************************************/ +/*! \class BlitSquare + \brief STK band-limited square wave class. + + This class generates a band-limited square wave signal. It is + derived in part from the approach reported by Stilson and Smith in + "Alias-Free Digital Synthesis of Classic Analog Waveforms", 1996. + The algorithm implemented in this class uses a SincM function with + an even M value to achieve a bipolar bandlimited impulse train. + This signal is then integrated to achieve a square waveform. The + integration process has an associated DC offset so a DC blocking + filter is applied at the output. + + The user can specify both the fundamental frequency of the + waveform and the number of harmonics contained in the resulting + signal. + + If nHarmonics is 0, then the signal will contain all harmonics up + to half the sample rate. Note, however, that this setting may + produce aliasing in the signal when the frequency is changing (no + automatic modification of the number of harmonics is performed by + the setFrequency() function). Also note that the harmonics of a + square wave fall at odd integer multiples of the fundamental, so + aliasing will happen with a lower fundamental than with the other + Blit waveforms. This class is not guaranteed to be well behaved + in the presence of significant aliasing. + + Based on initial code of Robin Davies, 2005 + Modified algorithm code by Gary Scavone, 2005--2010. +*/ +/***************************************************/ + +#include "BlitSquare.h" + +namespace stk { + +BlitSquare:: BlitSquare( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "BlitSquare::BlitSquare: argument (" << frequency << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + nHarmonics_ = 0; + this->setFrequency( frequency ); + this->reset(); +} + +BlitSquare :: ~BlitSquare() +{ +} + +void BlitSquare :: reset() +{ + phase_ = 0.0; + lastFrame_[0] = 0.0; + dcbState_ = 0.0; + lastBlitOutput_ = 0; +} + +void BlitSquare :: setFrequency( StkFloat frequency ) +{ + if ( frequency <= 0.0 ) { + oStream_ << "BlitSquare::setFrequency: argument (" << frequency << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + // By using an even value of the parameter M, we get a bipolar blit + // waveform at half the blit frequency. Thus, we need to scale the + // frequency value here by 0.5. (GPS, 2006). + p_ = 0.5 * Stk::sampleRate() / frequency; + rate_ = PI / p_; + this->updateHarmonics(); +} + +void BlitSquare :: setHarmonics( unsigned int nHarmonics ) +{ + nHarmonics_ = nHarmonics; + this->updateHarmonics(); +} + +void BlitSquare :: updateHarmonics( void ) +{ + // Make sure we end up with an even value of the parameter M here. + if ( nHarmonics_ <= 0 ) { + unsigned int maxHarmonics = (unsigned int) floor( 0.5 * p_ ); + m_ = 2 * (maxHarmonics + 1); + } + else + m_ = 2 * (nHarmonics_ + 1); + + a_ = m_ / p_; +} + +} // stk namespace diff --git a/7.x/src/stk/src/BlowBotl.cpp b/7.x/src/stk/src/BlowBotl.cpp new file mode 100644 index 0000000..2882712 --- /dev/null +++ b/7.x/src/stk/src/BlowBotl.cpp @@ -0,0 +1,122 @@ +/***************************************************/ +/*! \class BlowBotl + \brief STK blown bottle instrument class. + + This class implements a helmholtz resonator + (biquad filter) with a polynomial jet + excitation (a la Cook). + + Control Change Numbers: + - Noise Gain = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Volume = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "BlowBotl.h" +#include "SKINImsg.h" + +namespace stk { + +#define __BOTTLE_RADIUS_ 0.999 + +BlowBotl :: BlowBotl( void ) +{ + dcBlock_.setBlockZero(); + + vibrato_.setFrequency( 5.925 ); + vibratoGain_ = 0.0; + + resonator_.setResonance( 500.0, __BOTTLE_RADIUS_, true ); + adsr_.setAllTimes( 0.005, 0.01, 0.8, 0.010 ); + + noiseGain_ = 20.0; + maxPressure_ = 0.0; +} + +BlowBotl :: ~BlowBotl( void ) +{ +} + +void BlowBotl :: clear( void ) +{ + resonator_.clear(); +} + +void BlowBotl :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "BlowBotl::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + resonator_.setResonance( frequency, __BOTTLE_RADIUS_, true ); +} + +void BlowBotl :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "BlowBotl::startBowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setAttackRate( rate ); + maxPressure_ = amplitude; + adsr_.keyOn(); +} + +void BlowBotl :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "BlowBotl::stopBowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setReleaseRate( rate ); + adsr_.keyOff(); +} + +void BlowBotl :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + startBlowing( 1.1 + (amplitude * 0.20), amplitude * 0.02); + outputGain_ = amplitude + 0.001; +} + +void BlowBotl :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.02 ); +} + +void BlowBotl :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( value < 0 || ( number != 101 && value > 128.0 ) ) { + oStream_ << "BlowBotl::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_NoiseLevel_) // 4 + noiseGain_ = normalizedValue * 30.0; + else if (number == __SK_ModFrequency_) // 11 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if (number == __SK_ModWheel_) // 1 + vibratoGain_ = normalizedValue * 0.4; + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "BlowBotl::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/BlowHole.cpp b/7.x/src/stk/src/BlowHole.cpp new file mode 100644 index 0000000..5f34251 --- /dev/null +++ b/7.x/src/stk/src/BlowHole.cpp @@ -0,0 +1,224 @@ +/***************************************************/ +/*! \class BlowHole + \brief STK clarinet physical model with one + register hole and one tonehole. + + This class is based on the clarinet model, + with the addition of a two-port register hole + and a three-port dynamic tonehole + implementation, as discussed by Scavone and + Cook (1998). + + In this implementation, the distances between + the reed/register hole and tonehole/bell are + fixed. As a result, both the tonehole and + register hole will have variable influence on + the playing frequency, which is dependent on + the length of the air column. In addition, + the highest playing freqeuency is limited by + these fixed lengths. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Reed Stiffness = 2 + - Noise Gain = 4 + - Tonehole State = 11 + - Register State = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "BlowHole.h" +#include "SKINImsg.h" +#include + +namespace stk { + +BlowHole :: BlowHole( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "BlowHole::BlowHole: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( 0.5 * Stk::sampleRate() / lowestFrequency ); + + // delays[0] is the delay line between the reed and the register vent. + delays_[0].setDelay( 5.0 * Stk::sampleRate() / 22050.0 ); + // delays[1] is the delay line between the register vent and the tonehole. + delays_[1].setMaximumDelay( nDelays + 1 ); + // delays[2] is the delay line between the tonehole and the end of the bore. + delays_[2].setDelay( 4.0 * Stk::sampleRate() / 22050.0 ); + + reedTable_.setOffset( 0.7 ); + reedTable_.setSlope( -0.3 ); + + // Calculate the initial tonehole three-port scattering coefficient + StkFloat rb = 0.0075; // main bore radius + StkFloat rth = 0.003; // tonehole radius + scatter_ = -pow(rth,2) / ( pow(rth,2) + 2*pow(rb,2) ); + + // Calculate tonehole coefficients and set for initially open. + StkFloat te = 1.4 * rth; // effective length of the open hole + thCoeff_ = (te*2*Stk::sampleRate() - 347.23) / (te*2*Stk::sampleRate() + 347.23); + tonehole_.setA1( -thCoeff_ ); + tonehole_.setB0( thCoeff_ ); + tonehole_.setB1( -1.0 ); + + // Calculate register hole filter coefficients + double r_rh = 0.0015; // register vent radius + te = 1.4 * r_rh; // effective length of the open hole + double xi = 0.0; // series resistance term + double zeta = 347.23 + 2*PI*pow(rb,2)*xi/1.1769; + double psi = 2*PI*pow(rb,2)*te / (PI*pow(r_rh,2)); + StkFloat rhCoeff = (zeta - 2 * Stk::sampleRate() * psi) / (zeta + 2 * Stk::sampleRate() * psi); + rhGain_ = -347.23 / (zeta + 2 * Stk::sampleRate() * psi); + vent_.setA1( rhCoeff ); + vent_.setB0( 1.0 ); + vent_.setB1( 1.0 ); + // Start with register vent closed + vent_.setGain( 0.0 ); + + vibrato_.setFrequency((StkFloat) 5.735); + outputGain_ = 1.0; + noiseGain_ = 0.2; + vibratoGain_ = 0.01; + + this->setFrequency( 220.0 ); + this->clear(); +} + +BlowHole :: ~BlowHole( void ) +{ +} + +void BlowHole :: clear( void ) +{ + delays_[0].clear(); + delays_[1].clear(); + delays_[2].clear(); + filter_.tick( 0.0 ); + tonehole_.tick( 0.0 ); + vent_.tick( 0.0 ); +} + +void BlowHole :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "BlowHole::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Account for approximate filter delays and one sample "lastOut" delay. + StkFloat delay = ( Stk::sampleRate() / frequency ) * 0.5 - 3.5; + delay -= delays_[0].getDelay() + delays_[2].getDelay(); + + delays_[1].setDelay( delay ); +} + +void BlowHole :: setVent( StkFloat newValue ) +{ + // This method allows setting of the register vent "open-ness" at + // any point between "Open" (newValue = 1) and "Closed" + // (newValue = 0). + + StkFloat gain; + + if ( newValue <= 0.0 ) + gain = 0.0; + else if ( newValue >= 1.0 ) + gain = rhGain_; + else + gain = newValue * rhGain_; + + vent_.setGain( gain ); +} + +void BlowHole :: setTonehole( StkFloat newValue ) +{ + // This method allows setting of the tonehole "open-ness" at + // any point between "Open" (newValue = 1) and "Closed" + // (newValue = 0). + StkFloat new_coeff; + + if ( newValue <= 0.0 ) + new_coeff = 0.9995; + else if ( newValue >= 1.0 ) + new_coeff = thCoeff_; + else + new_coeff = ( newValue * (thCoeff_ - 0.9995) ) + 0.9995; + + tonehole_.setA1( -new_coeff ); + tonehole_.setB0( new_coeff ); +} + +void BlowHole :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "BlowHole::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( amplitude ); +} + +void BlowHole :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "BlowHole::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( 0.0 ); +} + +void BlowHole :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( 0.55 + (amplitude * 0.30), amplitude * 0.005 ); + outputGain_ = amplitude + 0.001; +} + +void BlowHole :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.01 ); +} + +void BlowHole :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "BlowHole::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_ReedStiffness_) // 2 + reedTable_.setSlope( -0.44 + (0.26 * normalizedValue) ); + else if (number == __SK_NoiseLevel_) // 4 + noiseGain_ = ( normalizedValue * 0.4); + else if (number == __SK_ModFrequency_) // 11 + this->setTonehole( normalizedValue ); + else if (number == __SK_ModWheel_) // 1 + this->setVent( normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) // 128 + envelope_.setValue( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "BlowHole::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Bowed.cpp b/7.x/src/stk/src/Bowed.cpp new file mode 100644 index 0000000..7e955ba --- /dev/null +++ b/7.x/src/stk/src/Bowed.cpp @@ -0,0 +1,180 @@ +/***************************************************/ +/*! \class Bowed + \brief STK bowed string instrument class. + + This class implements a bowed string model, a + la Smith (1986), after McIntyre, Schumacher, + Woodhouse (1983). + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + + Control Change Numbers: + - Bow Pressure = 2 + - Bow Position = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Bow Velocity = 100 + - Frequency = 101 + - Volume = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. + Contributions by Esteban Maestre, 2011. +*/ +/***************************************************/ + +#include "Bowed.h" +#include "SKINImsg.h" + +namespace stk { + +Bowed :: Bowed( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Bowed::Bowed: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / lowestFrequency ); + + neckDelay_.setMaximumDelay( nDelays + 1 ); + neckDelay_.setDelay( 100.0 ); + + bridgeDelay_.setMaximumDelay( nDelays + 1 ); + bridgeDelay_.setDelay( 29.0 ); + + bowTable_.setSlope( 3.0 ); + bowTable_.setOffset( 0.001); + bowDown_ = false; + maxVelocity_ = 0.25; + + vibrato_.setFrequency( 6.12723 ); + vibratoGain_ = 0.0; + + stringFilter_.setPole( 0.75 - (0.2 * 22050.0 / Stk::sampleRate()) ); + stringFilter_.setGain( 0.95 ); + + // Old single body filter + //bodyFilter_.setResonance( 500.0, 0.85, true ); + //bodyFilter_.setGain( 0.2 ); + + // New body filter provided by Esteban Maestre (cascade of second-order sections) + bodyFilters_[0].setCoefficients( 1.0, 1.5667, 0.3133, -0.5509, -0.3925 ); + bodyFilters_[1].setCoefficients( 1.0, -1.9537, 0.9542, -1.6357, 0.8697 ); + bodyFilters_[2].setCoefficients( 1.0, -1.6683, 0.8852, -1.7674, 0.8735 ); + bodyFilters_[3].setCoefficients( 1.0, -1.8585, 0.9653, -1.8498, 0.9516 ); + bodyFilters_[4].setCoefficients( 1.0, -1.9299, 0.9621, -1.9354, 0.9590 ); + bodyFilters_[5].setCoefficients( 1.0, -1.9800, 0.9888, -1.9867, 0.9923 ); + + adsr_.setAllTimes( 0.02, 0.005, 0.9, 0.01 ); + + betaRatio_ = 0.127236; + + // Necessary to initialize internal variables. + this->setFrequency( 220.0 ); + this->clear(); +} + +Bowed :: ~Bowed( void ) +{ +} + +void Bowed :: clear( void ) +{ + neckDelay_.clear(); + bridgeDelay_.clear(); + stringFilter_.clear(); + for ( int i=0; i<6; i++ ) bodyFilters_[i].clear(); +} + +void Bowed :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Bowed::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Delay = length - approximate filter delay. + baseDelay_ = Stk::sampleRate() / frequency - 4.0; + if ( baseDelay_ <= 0.0 ) baseDelay_ = 0.3; + bridgeDelay_.setDelay( baseDelay_ * betaRatio_ ); // bow to bridge length + neckDelay_.setDelay( baseDelay_ * (1.0 - betaRatio_) ); // bow to nut (finger) length +} + +void Bowed :: startBowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Bowed::startBowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setAttackRate( rate ); + adsr_.keyOn(); + maxVelocity_ = 0.03 + ( 0.2 * amplitude ); + bowDown_ = true; +} + +void Bowed :: stopBowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Bowed::stopBowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setReleaseRate( rate ); + adsr_.keyOff(); +} + +void Bowed :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->startBowing( amplitude, amplitude * 0.001 ); + this->setFrequency( frequency ); +} + +void Bowed :: noteOff( StkFloat amplitude ) +{ + this->stopBowing( (1.0 - amplitude) * 0.005 ); +} + +void Bowed :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( value < 0 || ( number != 101 && value > 128.0 ) ) { + oStream_ << "Bowed::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == __SK_BowPressure_ ) { // 2 + if ( normalizedValue > 0.0 ) bowDown_ = true; + else bowDown_ = false; + bowTable_.setSlope( 5.0 - (4.0 * normalizedValue) ); + } + else if ( number == __SK_BowPosition_ ) { // 4 + betaRatio_ = normalizedValue; + bridgeDelay_.setDelay( baseDelay_ * betaRatio_ ); + neckDelay_.setDelay( baseDelay_ * (1.0 - betaRatio_) ); + } + else if ( number == __SK_ModFrequency_ ) // 11 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if ( number == __SK_ModWheel_ ) // 1 + vibratoGain_ = ( normalizedValue * 0.4 ); + else if ( number == 100 ) // 100: set instantaneous bow velocity + adsr_.setTarget( normalizedValue ); + else if ( number == 101 ) // 101: set instantaneous value of frequency + this->setFrequency( value ); + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Bowed::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Brass.cpp b/7.x/src/stk/src/Brass.cpp new file mode 100644 index 0000000..6a65983 --- /dev/null +++ b/7.x/src/stk/src/Brass.cpp @@ -0,0 +1,158 @@ +/***************************************************/ +/*! \class Brass + \brief STK simple brass instrument class. + + This class implements a simple brass instrument + waveguide model, a la Cook (TBone, HosePlayer). + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + + Control Change Numbers: + - Lip Tension = 2 + - Slide Length = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Volume = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Brass.h" +#include "SKINImsg.h" +#include + +namespace stk { + +Brass :: Brass( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Brass::Brass: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / lowestFrequency ); + delayLine_.setMaximumDelay( nDelays + 1 ); + + lipFilter_.setGain( 0.03 ); + dcBlock_.setBlockZero(); + adsr_.setAllTimes( 0.005, 0.001, 1.0, 0.010 ); + + vibrato_.setFrequency( 6.137 ); + vibratoGain_ = 0.0; + maxPressure_ = 0.0; + lipTarget_ = 0.0; + + this->clear(); + + // This is necessary to initialize variables. + this->setFrequency( 220.0 ); +} + +Brass :: ~Brass( void ) +{ +} + +void Brass :: clear( void ) +{ + delayLine_.clear(); + lipFilter_.clear(); + dcBlock_.clear(); +} + +void Brass :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Brass::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Fudge correction for filter delays. + slideTarget_ = ( Stk::sampleRate() / frequency * 2.0 ) + 3.0; + delayLine_.setDelay( slideTarget_ ); // play a harmonic + + lipTarget_ = frequency; + lipFilter_.setResonance( frequency, 0.997 ); +} + +void Brass :: setLip( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Brass::setLip: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + lipFilter_.setResonance( frequency, 0.997 ); +} + +void Brass :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Brass::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setAttackRate( rate ); + maxPressure_ = amplitude; + adsr_.keyOn(); +} + +void Brass :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Brass::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setReleaseRate( rate ); + adsr_.keyOff(); +} + +void Brass :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( amplitude, amplitude * 0.001 ); +} + +void Brass :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.005 ); +} + +void Brass :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Brass::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_LipTension_) { // 2 + StkFloat temp = lipTarget_ * pow( 4.0, (2.0 * normalizedValue) - 1.0 ); + this->setLip( temp ); + } + else if (number == __SK_SlideLength_) // 4 + delayLine_.setDelay( slideTarget_ * (0.5 + normalizedValue) ); + else if (number == __SK_ModFrequency_) // 11 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if (number == __SK_ModWheel_ ) // 1 + vibratoGain_ = normalizedValue * 0.4; + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Brass::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Chorus.cpp b/7.x/src/stk/src/Chorus.cpp new file mode 100644 index 0000000..1efd1db --- /dev/null +++ b/7.x/src/stk/src/Chorus.cpp @@ -0,0 +1,56 @@ +/***************************************************/ +/*! \class Chorus + \brief STK chorus effect class. + + This class implements a chorus effect. It takes a monophonic + input signal and produces a stereo output signal. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Chorus.h" + +namespace stk { + +Chorus :: Chorus( StkFloat baseDelay ) +{ + lastFrame_.resize( 1, 2, 0.0 ); // resize lastFrame_ for stereo output + delayLine_[0].setMaximumDelay( (unsigned long) (baseDelay * 1.414) + 2); + delayLine_[0].setDelay( baseDelay ); + delayLine_[1].setMaximumDelay( (unsigned long) (baseDelay * 1.414) + 2); + delayLine_[1].setDelay( baseDelay ); + baseLength_ = baseDelay; + + mods_[0].setFrequency( 0.2 ); + mods_[1].setFrequency( 0.222222 ); + modDepth_ = 0.05; + effectMix_ = 0.5; + this->clear(); +} + +void Chorus :: clear( void ) +{ + delayLine_[0].clear(); + delayLine_[1].clear(); + lastFrame_[0] = 0.0; + lastFrame_[1] = 0.0; +} + + void Chorus :: setModDepth( StkFloat depth ) +{ + if ( depth < 0.0 || depth > 1.0 ) { + oStream_ << "Chorus::setModDepth(): depth argument must be between 0.0 - 1.0!"; + handleError( StkError::WARNING ); return; + } + + modDepth_ = depth; +}; + +void Chorus :: setModFrequency( StkFloat frequency ) +{ + mods_[0].setFrequency( frequency ); + mods_[1].setFrequency( frequency * 1.1111 ); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Clarinet.cpp b/7.x/src/stk/src/Clarinet.cpp new file mode 100644 index 0000000..f81e0bf --- /dev/null +++ b/7.x/src/stk/src/Clarinet.cpp @@ -0,0 +1,138 @@ +/***************************************************/ +/*! \class Clarinet + \brief STK clarinet physical model class. + + This class implements a simple clarinet + physical model, as discussed by Smith (1986), + McIntyre, Schumacher, Woodhouse (1983), and + others. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Reed Stiffness = 2 + - Noise Gain = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Clarinet.h" +#include "SKINImsg.h" + +namespace stk { + +Clarinet :: Clarinet( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Clarinet::Clarinet: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( 0.5 * Stk::sampleRate() / lowestFrequency ); + delayLine_.setMaximumDelay( nDelays + 1 ); + + reedTable_.setOffset( 0.7 ); + reedTable_.setSlope( -0.3 ); + + vibrato_.setFrequency( 5.735 ); + outputGain_ = 1.0; + noiseGain_ = 0.2; + vibratoGain_ = 0.1; + + this->setFrequency( 220.0 ); + this->clear(); +} + +Clarinet :: ~Clarinet( void ) +{ +} + +void Clarinet :: clear( void ) +{ + delayLine_.clear(); + filter_.tick( 0.0 ); +} + +void Clarinet :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Clarinet::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Account for filter delay and one sample "lastOut" delay. + StkFloat delay = ( Stk::sampleRate() / frequency ) * 0.5 - filter_.phaseDelay( frequency ) - 1.0; + delayLine_.setDelay( delay ); +} + +void Clarinet :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Clarinet::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( amplitude ); +} + +void Clarinet :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Clarinet::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( 0.0 ); +} + +void Clarinet :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( 0.55 + (amplitude * 0.30), amplitude * 0.005 ); + outputGain_ = amplitude + 0.001; +} + +void Clarinet :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.01 ); +} + +void Clarinet :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Clarinet::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == __SK_ReedStiffness_ ) // 2 + reedTable_.setSlope( -0.44 + ( 0.26 * normalizedValue ) ); + else if ( number == __SK_NoiseLevel_ ) // 4 + noiseGain_ = ( normalizedValue * 0.4 ); + else if ( number == __SK_ModFrequency_ ) // 11 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if ( number == __SK_ModWheel_ ) // 1 + vibratoGain_ = ( normalizedValue * 0.5 ); + else if ( number == __SK_AfterTouch_Cont_ ) // 128 + envelope_.setValue( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Clarinet::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Delay.cpp b/7.x/src/stk/src/Delay.cpp new file mode 100644 index 0000000..27e1cc8 --- /dev/null +++ b/7.x/src/stk/src/Delay.cpp @@ -0,0 +1,110 @@ +/***************************************************/ +/*! \class Delay + \brief STK non-interpolating delay line class. + + This class implements a non-interpolating digital delay-line. If + the delay and maximum length are not specified during + instantiation, a fixed maximum length of 4095 and a delay of zero + is set. + + A non-interpolating delay line is typically used in fixed + delay-length applications, such as for reverberation. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Delay.h" + +namespace stk { + +Delay :: Delay( unsigned long delay, unsigned long maxDelay ) +{ + // Writing before reading allows delays from 0 to length-1. + // If we want to allow a delay of maxDelay, we need a + // delay-line of length = maxDelay+1. + if ( delay > maxDelay ) { + oStream_ << "Delay::Delay: maxDelay must be > than delay argument!\n"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( ( maxDelay + 1 ) > inputs_.size() ) + inputs_.resize( maxDelay + 1, 1, 0.0 ); + + inPoint_ = 0; + this->setDelay( delay ); +} + +Delay :: ~Delay() +{ +} + +void Delay :: setMaximumDelay( unsigned long delay ) +{ + if ( delay < inputs_.size() ) return; + inputs_.resize( delay + 1, 1, 0.0 ); +} + +void Delay :: setDelay( unsigned long delay ) +{ + if ( delay > inputs_.size() - 1 ) { // The value is too big. + oStream_ << "Delay::setDelay: argument (" << delay << ") greater than maximum!\n"; + handleError( StkError::WARNING ); return; + } + + // read chases write + if ( inPoint_ >= delay ) outPoint_ = inPoint_ - delay; + else outPoint_ = inputs_.size() + inPoint_ - delay; + delay_ = delay; +} + +StkFloat Delay :: energy( void ) const +{ + unsigned long i; + StkFloat e = 0; + if ( inPoint_ >= outPoint_ ) { + for ( i=outPoint_; i= 0.5!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( delay > (StkFloat) maxDelay ) { + oStream_ << "DelayA::DelayA: maxDelay must be > than delay argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + // Writing before reading allows delays from 0 to length-1. + if ( maxDelay + 1 > inputs_.size() ) + inputs_.resize( maxDelay + 1, 1, 0.0 ); + + inPoint_ = 0; + this->setDelay( delay ); + apInput_ = 0.0; + doNextOut_ = true; +} + +DelayA :: ~DelayA() +{ +} + +void DelayA :: clear() +{ + for ( unsigned int i=0; i length ) { // The value is too big. + oStream_ << "DelayA::setDelay: argument (" << delay << ") greater than maximum!"; + handleError( StkError::WARNING ); return; + } + + if ( delay < 0.5 ) { + oStream_ << "DelayA::setDelay: argument (" << delay << ") less than 0.5 not possible!"; + handleError( StkError::WARNING ); + } + + StkFloat outPointer = inPoint_ - delay + 1.0; // outPoint chases inpoint + delay_ = delay; + + while ( outPointer < 0 ) + outPointer += length; // modulo maximum length + + outPoint_ = (long) outPointer; // integer part + if ( outPoint_ == length ) outPoint_ = 0; + alpha_ = 1.0 + outPoint_ - outPointer; // fractional part + + if ( alpha_ < 0.5 ) { + // The optimal range for alpha is about 0.5 - 1.5 in order to + // achieve the flattest phase delay response. + outPoint_ += 1; + if ( outPoint_ >= length ) outPoint_ -= length; + alpha_ += (StkFloat) 1.0; + } + + coeff_ = (1.0 - alpha_) / (1.0 + alpha_); // coefficient for allpass +} + +StkFloat DelayA :: tapOut( unsigned long tapDelay ) +{ + long tap = inPoint_ - tapDelay - 1; + while ( tap < 0 ) // Check for wraparound. + tap += inputs_.size(); + + return inputs_[tap]; +} + +void DelayA :: tapIn( StkFloat value, unsigned long tapDelay ) +{ + long tap = inPoint_ - tapDelay - 1; + while ( tap < 0 ) // Check for wraparound. + tap += inputs_.size(); + + inputs_[tap] = value; +} + +} // stk namespace diff --git a/7.x/src/stk/src/DelayL.cpp b/7.x/src/stk/src/DelayL.cpp new file mode 100644 index 0000000..1dfc8d4 --- /dev/null +++ b/7.x/src/stk/src/DelayL.cpp @@ -0,0 +1,73 @@ +/***************************************************/ +/*! \class DelayL + \brief STK linear interpolating delay line class. + + This class implements a fractional-length digital delay-line using + first-order linear interpolation. If the delay and maximum length + are not specified during instantiation, a fixed maximum length of + 4095 and a delay of zero is set. + + Linear interpolation is an efficient technique for achieving + fractional delay lengths, though it does introduce high-frequency + signal attenuation to varying degrees depending on the fractional + delay setting. The use of higher order Lagrange interpolators can + typically improve (minimize) this attenuation characteristic. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "DelayL.h" + +namespace stk { + +DelayL :: DelayL( StkFloat delay, unsigned long maxDelay ) +{ + if ( delay < 0.0 ) { + oStream_ << "DelayL::DelayL: delay must be >= 0.0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( delay > (StkFloat) maxDelay ) { + oStream_ << "DelayL::DelayL: maxDelay must be > than delay argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + // Writing before reading allows delays from 0 to length-1. + if ( maxDelay + 1 > inputs_.size() ) + inputs_.resize( maxDelay + 1, 1, 0.0 ); + + inPoint_ = 0; + this->setDelay( delay ); + doNextOut_ = true; +} + +DelayL :: ~DelayL() +{ +} + +void DelayL :: setMaximumDelay( unsigned long delay ) +{ + if ( delay < inputs_.size() ) return; + inputs_.resize(delay + 1, 1, 0.0); +} + +StkFloat DelayL :: tapOut( unsigned long tapDelay ) +{ + long tap = inPoint_ - tapDelay - 1; + while ( tap < 0 ) // Check for wraparound. + tap += inputs_.size(); + + return inputs_[tap]; +} + +void DelayL :: tapIn( StkFloat value, unsigned long tapDelay ) +{ + long tap = inPoint_ - tapDelay - 1; + while ( tap < 0 ) // Check for wraparound. + tap += inputs_.size(); + + inputs_[tap] = value; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Drummer.cpp b/7.x/src/stk/src/Drummer.cpp new file mode 100644 index 0000000..51f627c --- /dev/null +++ b/7.x/src/stk/src/Drummer.cpp @@ -0,0 +1,141 @@ +/***************************************************/ +/*! \class Drummer + \brief STK drum sample player class. + + This class implements a drum sampling + synthesizer using FileWvIn objects and one-pole + filters. The drum rawwave files are sampled + at 22050 Hz, but will be appropriately + interpolated for other sample rates. You can + specify the maximum polyphony (maximum number + of simultaneous voices) via a #define in the + Drummer.h. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Drummer.h" +#include + +namespace stk { + +// Not really General MIDI yet. +unsigned char genMIDIMap[128] = + { 0,0,0,0,0,0,0,0, // 0-7 + 0,0,0,0,0,0,0,0, // 8-15 + 0,0,0,0,0,0,0,0, // 16-23 + 0,0,0,0,0,0,0,0, // 24-31 + 0,0,0,0,1,0,2,0, // 32-39 + 2,3,6,3,6,4,7,4, // 40-47 + 5,8,5,0,0,0,10,0, // 48-55 + 9,0,0,0,0,0,0,0, // 56-63 + 0,0,0,0,0,0,0,0, // 64-71 + 0,0,0,0,0,0,0,0, // 72-79 + 0,0,0,0,0,0,0,0, // 80-87 + 0,0,0,0,0,0,0,0, // 88-95 + 0,0,0,0,0,0,0,0, // 96-103 + 0,0,0,0,0,0,0,0, // 104-111 + 0,0,0,0,0,0,0,0, // 112-119 + 0,0,0,0,0,0,0,0 // 120-127 + }; + +char waveNames[DRUM_NUMWAVES][16] = + { + "dope.raw", + "bassdrum.raw", + "snardrum.raw", + "tomlowdr.raw", + "tommiddr.raw", + "tomhidrm.raw", + "hihatcym.raw", + "ridecymb.raw", + "crashcym.raw", + "cowbell1.raw", + "tambourn.raw" + }; + +Drummer :: Drummer( void ) : Instrmnt() +{ + // This counts the number of sounding voices. + nSounding_ = 0; + soundOrder_ = std::vector (DRUM_POLYPHONY, -1); + soundNumber_ = std::vector (DRUM_POLYPHONY, -1); +} + +Drummer :: ~Drummer( void ) +{ +} + +void Drummer :: noteOn( StkFloat instrument, StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Drummer::noteOn: amplitude parameter is out of bounds!"; + handleError( StkError::WARNING ); return; + } + + // Yes, this is tres kludgey. + int noteNumber = (int) ( ( 12 * log( instrument / 220.0 ) / log( 2.0 ) ) + 57.01 ); + + // If we already have a wave of this note number loaded, just reset + // it. Otherwise, look first for an unused wave or preempt the + // oldest if already at maximum polyphony. + int iWave; + for ( iWave=0; iWave soundOrder_[iWave] ) + soundOrder_[j] -= 1; + } + } + soundOrder_[iWave] = nSounding_ - 1; + soundNumber_[iWave] = noteNumber; + //std::cout << "iWave = " << iWave << ", nSounding = " << nSounding_ << ", soundOrder[] = " << soundOrder_[iWave] << std::endl; + + // Concatenate the STK rawwave path to the rawwave file + waves_[iWave].openFile( (Stk::rawwavePath() + waveNames[ genMIDIMap[ noteNumber ] ]).c_str(), true ); + if ( Stk::sampleRate() != 22050.0 ) + waves_[iWave].setRate( 22050.0 / Stk::sampleRate() ); + filters_[iWave].setPole( 0.999 - (amplitude * 0.6) ); + filters_[iWave].setGain( amplitude ); + } + + /* +#if defined(_STK_DEBUG_) + oStream_ << "Drummer::noteOn: number sounding = " << nSounding_ << ", notes: "; + for ( int i=0; i + +namespace stk { + +Echo :: Echo( unsigned long maximumDelay ) : Effect() +{ + this->setMaximumDelay( maximumDelay ); + delayLine_.setDelay( length_ >> 1 ); + effectMix_ = 0.5; + this->clear(); +} + +void Echo :: clear( void ) +{ + delayLine_.clear(); + lastFrame_[0] = 0.0; +} + +void Echo :: setMaximumDelay( unsigned long delay ) +{ + if ( delay == 0 ) { + oStream_ << "Echo::setMaximumDelay: parameter cannot be zero!"; + handleError( StkError::WARNING ); return; + } + + length_ = delay; + delayLine_.setMaximumDelay( delay ); +} + +void Echo :: setDelay( unsigned long delay ) +{ + if ( delay > length_ ) { + oStream_ << "Echo::setDelay: parameter is greater than maximum delay length!"; + handleError( StkError::WARNING ); return; + } + + delayLine_.setDelay( delay ); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Envelope.cpp b/7.x/src/stk/src/Envelope.cpp new file mode 100644 index 0000000..0c1afeb --- /dev/null +++ b/7.x/src/stk/src/Envelope.cpp @@ -0,0 +1,87 @@ +/***************************************************/ +/*! \class Envelope + \brief STK linear line envelope class. + + This class implements a simple linear line envelope generator + which is capable of ramping to an arbitrary target value by a + specified \e rate. It also responds to simple \e keyOn and \e + keyOff messages, ramping to a specified target (default = 1.0) on + keyOn and to a specified target (default = 0.0) on keyOff. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Envelope.h" +#include + +namespace stk { + +Envelope :: Envelope( void ) : Generator() +{ + target_ = 0.0; + value_ = 0.0; + rate_ = 0.001; + state_ = 0; + Stk::addSampleRateAlert( this ); +} + +Envelope :: ~Envelope( void ) +{ + Stk::removeSampleRateAlert( this ); +} + +Envelope& Envelope :: operator= ( const Envelope& e ) +{ + if ( this != &e ) { + target_ = e.target_; + value_ = e.value_; + rate_ = e.rate_; + state_ = e.state_; + } + + return *this; +} + +void Envelope :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) + rate_ = oldRate * rate_ / newRate; +} + +void Envelope :: setRate( StkFloat rate ) +{ + if ( rate < 0.0 ) { + oStream_ << "Envelope::setRate: argument must be >= 0.0!"; + handleError( StkError::WARNING ); return; + } + + rate_ = rate; +} + +void Envelope :: setTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "Envelope::setTime: argument must be > 0.0!"; + handleError( StkError::WARNING ); return; + } + + //rate_ = 1.0 / ( time * Stk::sampleRate() ); + rate_ = fabs(target_ - value_) / ( time * Stk::sampleRate() ); +} + +void Envelope :: setTarget( StkFloat target ) +{ + target_ = target; + if ( value_ != target_ ) state_ = 1; +} + +void Envelope :: setValue( StkFloat value ) +{ + state_ = 0; + target_ = value; + value_ = value; + lastFrame_[0] = value_; +} + +} // stk namespace diff --git a/7.x/src/stk/src/FM.cpp b/7.x/src/stk/src/FM.cpp new file mode 100644 index 0000000..d5f3ae7 --- /dev/null +++ b/7.x/src/stk/src/FM.cpp @@ -0,0 +1,179 @@ +/***************************************************/ +/*! \class FM + \brief STK abstract FM synthesis base class. + + This class controls an arbitrary number of + waves and envelopes, determined via a + constructor argument. + + Control Change Numbers: + - Control One = 2 + - Control Two = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "FM.h" +#include "SKINImsg.h" + +namespace stk { + +FM :: FM( unsigned int operators ) + : nOperators_(operators) +{ + if ( nOperators_ == 0 ) { + oStream_ << "FM::FM: Number of operators must be greater than zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + twozero_.setB2( -1.0 ); + twozero_.setGain( 0.0 ); + + vibrato_.setFrequency( 6.0 ); + + unsigned int j; + adsr_.resize( nOperators_ ); + waves_.resize( nOperators_ ); + for (j=0; j=0; i--) { + fmGains_[i] = temp; + temp *= 0.933033; + } + + temp = 1.0; + for (i=15; i>=0; i--) { + fmSusLevels_[i] = temp; + temp *= 0.707101; + } + + temp = 8.498186; + for (i=0; i<32; i++) { + fmAttTimes_[i] = temp; + temp *= 0.707101; + } +} + +FM :: ~FM( void ) +{ + for (unsigned int i=0; isetFrequency( baseFrequency_ * ratios_[i] ); +} + +void FM :: setRatio( unsigned int waveIndex, StkFloat ratio ) +{ + if ( waveIndex >= nOperators_ ) { + oStream_ << "FM:setRatio: waveIndex parameter is greater than the number of operators!"; + handleError( StkError::WARNING ); return; + } + + ratios_[waveIndex] = ratio; + if (ratio > 0.0) + waves_[waveIndex]->setFrequency( baseFrequency_ * ratio ); + else + waves_[waveIndex]->setFrequency( ratio ); +} + +void FM :: setGain( unsigned int waveIndex, StkFloat gain ) +{ + if ( waveIndex >= nOperators_ ) { + oStream_ << "FM::setGain: waveIndex parameter is greater than the number of operators!"; + handleError( StkError::WARNING ); return; + } + + gains_[waveIndex] = gain; +} + +void FM :: keyOn( void ) +{ + for ( unsigned int i=0; ikeyOn(); +} + +void FM :: keyOff( void ) +{ + for ( unsigned int i=0; ikeyOff(); +} + +void FM :: noteOff( StkFloat amplitude ) +{ + this->keyOff(); +} + +void FM :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "FM::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_Breath_) // 2 + this->setControl1( normalizedValue ); + else if (number == __SK_FootControl_) // 4 + this->setControl2( normalizedValue ); + else if (number == __SK_ModFrequency_) // 11 + this->setModulationSpeed( normalizedValue * 12.0); + else if (number == __SK_ModWheel_) // 1 + this->setModulationDepth( normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) { // 128 + //adsr_[0]->setTarget( normalizedValue ); + adsr_[1]->setTarget( normalizedValue ); + //adsr_[2]->setTarget( normalizedValue ); + adsr_[3]->setTarget( normalizedValue ); + } +#if defined(_STK_DEBUG_) + else { + oStream_ << "FM::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/FMVoices.cpp b/7.x/src/stk/src/FMVoices.cpp new file mode 100644 index 0000000..469e1a3 --- /dev/null +++ b/7.x/src/stk/src/FMVoices.cpp @@ -0,0 +1,164 @@ +/***************************************************/ +/*! \class FMVoices + \brief STK singing FM synthesis instrument. + + This class implements 3 carriers and a common + modulator, also referred to as algorithm 6 of + the TX81Z. + + \code + Algorithm 6 is : + /->1 -\ + 4-|-->2 - +-> Out + \->3 -/ + \endcode + + Control Change Numbers: + - Vowel = 2 + - Spectral Tilt = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "FMVoices.h" +#include "SKINImsg.h" +#include "Phonemes.h" + +namespace stk { + +FMVoices :: FMVoices( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 2.00); + this->setRatio(1, 4.00); + this->setRatio(2, 12.0); + this->setRatio(3, 1.00); + + gains_[3] = fmGains_[80]; + + adsr_[0]->setAllTimes( 0.05, 0.05, fmSusLevels_[15], 0.05); + adsr_[1]->setAllTimes( 0.05, 0.05, fmSusLevels_[15], 0.05); + adsr_[2]->setAllTimes( 0.05, 0.05, fmSusLevels_[15], 0.05); + adsr_[3]->setAllTimes( 0.01, 0.01, fmSusLevels_[15], 0.5); + + twozero_.setGain( 0.0 ); + modDepth_ = (StkFloat) 0.005; + currentVowel_ = 0; + tilt_[0] = 1.0; + tilt_[1] = 0.5; + tilt_[2] = 0.2; + mods_[0] = 1.0; + mods_[1] = 1.1; + mods_[2] = 1.1; + baseFrequency_ = 110.0; + this->setFrequency( 110.0 ); +} + +FMVoices :: ~FMVoices( void ) +{ +} + +void FMVoices :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "FMVoices::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat temp, temp2 = 0.0; + int tempi = 0; + unsigned int i = 0; + + if (currentVowel_ < 32) { + i = currentVowel_; + temp2 = 0.9; + } + else if (currentVowel_ < 64) { + i = currentVowel_ - 32; + temp2 = 1.0; + } + else if (currentVowel_ < 96) { + i = currentVowel_ - 64; + temp2 = 1.1; + } + else if (currentVowel_ < 128) { + i = currentVowel_ - 96; + temp2 = 1.2; + } + else return; + + baseFrequency_ = frequency; + temp = (temp2 * Phonemes::formantFrequency(i, 0) / baseFrequency_) + 0.5; + tempi = (int) temp; + this->setRatio( 0, (StkFloat) tempi ); + temp = (temp2 * Phonemes::formantFrequency(i, 1) / baseFrequency_) + 0.5; + tempi = (int) temp; + this->setRatio( 1, (StkFloat) tempi ); + temp = (temp2 * Phonemes::formantFrequency(i, 2) / baseFrequency_) + 0.5; + tempi = (int) temp; + this->setRatio( 2, (StkFloat) tempi ); + gains_[0] = 1.0; + gains_[1] = 1.0; + gains_[2] = 1.0; +} + +void FMVoices :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + tilt_[0] = amplitude; + tilt_[1] = amplitude * amplitude; + tilt_[2] = tilt_[1] * amplitude; + this->keyOn(); +} + +void FMVoices :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "FMVoices::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_Breath_) // 2 + gains_[3] = fmGains_[(int) ( normalizedValue * 99.9 )]; + else if (number == __SK_FootControl_) { // 4 + currentVowel_ = (int) (normalizedValue * 127.0); + this->setFrequency(baseFrequency_); + } + else if (number == __SK_ModFrequency_) // 11 + this->setModulationSpeed( normalizedValue * 12.0); + else if (number == __SK_ModWheel_) // 1 + this->setModulationDepth( normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) { // 128 + tilt_[0] = normalizedValue; + tilt_[1] = normalizedValue * normalizedValue; + tilt_[2] = tilt_[1] * normalizedValue; + } +#if defined(_STK_DEBUG_) + else { + oStream_ << "FMVoices::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/FileLoop.cpp b/7.x/src/stk/src/FileLoop.cpp new file mode 100644 index 0000000..67ff0e9 --- /dev/null +++ b/7.x/src/stk/src/FileLoop.cpp @@ -0,0 +1,237 @@ +/***************************************************/ +/*! \class FileLoop + \brief STK file looping / oscillator class. + + This class provides audio file looping functionality. Any audio + file that can be loaded by FileRead can be looped using this + class. + + FileLoop supports multi-channel data. It is important to + distinguish the tick() method that computes a single frame (and + returns only the specified sample of a multi-channel frame) from + the overloaded one that takes an StkFrames object for + multi-channel and/or multi-frame data. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "FileLoop.h" +#include + +namespace stk { + +FileLoop :: FileLoop( unsigned long chunkThreshold, unsigned long chunkSize ) + : FileWvIn( chunkThreshold, chunkSize ), phaseOffset_(0.0) +{ + Stk::addSampleRateAlert( this ); +} + +FileLoop :: FileLoop( std::string fileName, bool raw, bool doNormalize, + unsigned long chunkThreshold, unsigned long chunkSize, + bool doInt2FloatScaling ) + : FileWvIn( chunkThreshold, chunkSize ), phaseOffset_(0.0) +{ + this->openFile( fileName, raw, doNormalize, doInt2FloatScaling ); + Stk::addSampleRateAlert( this ); +} + +FileLoop :: ~FileLoop( void ) +{ + Stk::removeSampleRateAlert( this ); +} + +void FileLoop :: openFile( std::string fileName, bool raw, bool doNormalize, bool doInt2FloatScaling ) +{ + // Call close() in case another file is already open. + this->closeFile(); + + // Attempt to open the file ... an error might be thrown here. + file_.open( fileName, raw ); + + // Determine whether chunking or not. + if ( file_.fileSize() > chunkThreshold_ ) { + chunking_ = true; + chunkPointer_ = 0; + data_.resize( chunkSize_ + 1, file_.channels() ); + } + else { + chunking_ = false; + data_.resize( file_.fileSize() + 1, file_.channels() ); + } + + if ( doInt2FloatScaling ) + int2floatscaling_ = true; + else + int2floatscaling_ = false; + + // Load all or part of the data. + file_.read( data_, 0, int2floatscaling_ ); + + if ( chunking_ ) { // If chunking, save the first sample frame for later. + firstFrame_.resize( 1, data_.channels() ); + for ( unsigned int i=0; isetRate( data_.dataRate() / Stk::sampleRate() ); + + if ( doNormalize & !chunking_ ) this->normalize(); + + this->reset(); +} + +void FileLoop :: setRate( StkFloat rate ) +{ + rate_ = rate; + + if ( fmod( rate_, 1.0 ) != 0.0 ) interpolate_ = true; + else interpolate_ = false; +} + +void FileLoop :: addTime( StkFloat time ) +{ + // Add an absolute time in samples. + time_ += time; + + while ( time_ < 0.0 ) + time_ += fileSize_; + while ( time_ >= fileSize_ ) + time_ -= fileSize_; +} + +void FileLoop :: addPhase( StkFloat angle ) +{ + // Add a time in cycles (one cycle = fileSize). + time_ += fileSize_ * angle; + + while ( time_ < 0.0 ) + time_ += fileSize_; + while ( time_ >= fileSize_ ) + time_ -= fileSize_; +} + +void FileLoop :: addPhaseOffset( StkFloat angle ) +{ + // Add a phase offset in cycles, where 1.0 = fileSize. + phaseOffset_ = fileSize_ * angle; +} + +StkFloat FileLoop :: tick( unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= data_.channels() ) { + oStream_ << "FileLoop::tick(): channel argument and soundfile data are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + if ( finished_ ) return 0.0; + + // Check limits of time address ... if necessary, recalculate modulo + // fileSize. + while ( time_ < 0.0 ) + time_ += fileSize_; + while ( time_ >= fileSize_ ) + time_ -= fileSize_; + + StkFloat tyme = time_; + if ( phaseOffset_ ) { + tyme += phaseOffset_; + while ( tyme < 0.0 ) + tyme += fileSize_; + while ( tyme >= fileSize_ ) + tyme -= fileSize_; + } + + if ( chunking_ ) { + + // Check the time address vs. our current buffer limits. + if ( ( time_ < (StkFloat) chunkPointer_ ) || + ( time_ > (StkFloat) ( chunkPointer_ + chunkSize_ - 1 ) ) ) { + + while ( time_ < (StkFloat) chunkPointer_ ) { // negative rate + chunkPointer_ -= chunkSize_ - 1; // overlap chunks by one frame + if ( chunkPointer_ < 0 ) chunkPointer_ = 0; + } + while ( time_ > (StkFloat) ( chunkPointer_ + chunkSize_ - 1 ) ) { // positive rate + chunkPointer_ += chunkSize_ - 1; // overlap chunks by one frame + if ( chunkPointer_ + chunkSize_ > fileSize_ ) { // at end of file + chunkPointer_ = fileSize_ - chunkSize_ + 1; // leave extra frame at end of buffer + // Now fill extra frame with first frame data. + for ( unsigned int j=0; j frames.channels() - nChannels ) { + oStream_ << "FileLoop::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; i2) + soundfiles are supported. The file data is + returned via an external StkFrames object + passed to the read() function. This class + does not store its own copy of the file data, + rather the data is read directly from disk. + + FileRead currently supports uncompressed WAV, + AIFF/AIFC, SND (AU), MAT-file (Matlab), and + STK RAW file formats. Signed integer (8-, + 16-, 24- and 32-bit) and floating-point (32- and + 64-bit) data types are supported. Compressed + data types are not supported. + + STK RAW files have no header and are assumed to + contain a monophonic stream of 16-bit signed + integers in big-endian byte order at a sample + rate of 22050 Hz. MAT-file data should be saved + in an array with each data channel filling a + matrix row. The sample rate for MAT-files should + be specified in a variable named "fs". If no + such variable is found, the sample rate is + assumed to be 44100 Hz. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "FileRead.h" +#include +#include +#include +#include +#include + +namespace stk { + +FileRead :: FileRead() + : fd_(0), fileSize_(0), channels_(0), dataType_(0), fileRate_(0.0) +{ +} + +FileRead :: FileRead( std::string fileName, bool typeRaw, unsigned int nChannels, + StkFormat format, StkFloat rate ) + : fd_(0) +{ + open( fileName, typeRaw, nChannels, format, rate ); +} + +FileRead :: ~FileRead() +{ + if ( fd_ ) + fclose( fd_ ); +} + +void FileRead :: close( void ) +{ + if ( fd_ ) fclose( fd_ ); + fd_ = 0; + wavFile_ = false; + fileSize_ = 0; + channels_ = 0; + dataType_ = 0; + fileRate_ = 0.0; +} + +bool FileRead :: isOpen( void ) +{ + if ( fd_ ) return true; + else return false; +} + +void FileRead :: open( std::string fileName, bool typeRaw, unsigned int nChannels, + StkFormat format, StkFloat rate ) +{ + // If another file is open, close it. + close(); + + // Try to open the file. + fd_ = fopen( fileName.c_str(), "rb" ); + if ( !fd_ ) { + oStream_ << "FileRead::open: could not open or find file (" << fileName << ")!"; + handleError( StkError::FILE_NOT_FOUND ); + } + + // Attempt to determine file type from header (unless RAW). + bool result = false; + if ( typeRaw ) + result = getRawInfo( fileName.c_str(), nChannels, format, rate ); + else { + char header[12]; + if ( fread( &header, 4, 3, fd_ ) != 3 ) goto error; + if ( !strncmp( header, "RIFF", 4 ) && + !strncmp( &header[8], "WAVE", 4 ) ) + result = getWavInfo( fileName.c_str() ); + else if ( !strncmp( header, ".snd", 4 ) ) + result = getSndInfo( fileName.c_str() ); + else if ( !strncmp( header, "FORM", 4 ) && + ( !strncmp( &header[8], "AIFF", 4 ) || !strncmp(&header[8], "AIFC", 4) ) ) + result = getAifInfo( fileName.c_str() ); + else { + if ( fseek( fd_, 126, SEEK_SET ) == -1 ) goto error; + if ( fread( &header, 2, 1, fd_ ) != 1 ) goto error; + if ( !strncmp( header, "MI", 2 ) || + !strncmp( header, "IM", 2 ) ) + result = getMatInfo( fileName.c_str() ); + else { + oStream_ << "FileRead::open: file (" << fileName << ") format unknown."; + handleError( StkError::FILE_UNKNOWN_FORMAT ); + } + } + } + + // If here, we had a file type candidate but something else went wrong. + if ( result == false ) + handleError( StkError::FILE_ERROR ); + + // Check for empty files. + if ( fileSize_ == 0 ) { + oStream_ << "FileRead::open: file (" << fileName << ") data size is zero!"; + handleError( StkError::FILE_ERROR ); + } + + return; + + error: + oStream_ << "FileRead::open: error reading file (" << fileName << ")!"; + handleError( StkError::FILE_ERROR ); +} + +bool FileRead :: getRawInfo( const char *fileName, unsigned int nChannels, StkFormat format, StkFloat rate ) +{ + // Use the system call "stat" to determine the file length. + struct stat filestat; + if ( stat(fileName, &filestat) == -1 ) { + oStream_ << "FileRead: Could not stat RAW file (" << fileName << ")."; + return false; + } + if ( nChannels == 0 ) { + oStream_ << "FileRead: number of channels can't be 0 (" << fileName << ")."; + return false; + } + + // Rawwave files have no header and by default, are assumed to + // contain a monophonic stream of 16-bit signed integers in + // big-endian byte order at a sample rate of 22050 Hz. However, + // different parameters can be specified if desired. + dataOffset_ = 0; + channels_ = nChannels; + dataType_ = format; + fileRate_ = rate; + int sampleBytes = 0; + if ( format == STK_SINT8 ) sampleBytes = 1; + else if ( format == STK_SINT16 ) sampleBytes = 2; + else if ( format == STK_SINT32 || format == STK_FLOAT32 ) sampleBytes = 4; + else if ( format == STK_FLOAT64 ) sampleBytes = 8; + else { + oStream_ << "FileRead: StkFormat " << format << " is invalid (" << fileName << ")."; + return false; + } + + fileSize_ = (long) filestat.st_size / sampleBytes / channels_; // length in frames + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; +#endif + + return true; +} + +bool FileRead :: getWavInfo( const char *fileName ) +{ + // Find "format" chunk ... it must come before the "data" chunk. + char id[4]; + SINT32 chunkSize; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + while ( strncmp(id, "fmt ", 4) ) { + if ( fread(&chunkSize, 4, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&chunkSize); +#endif + if ( fseek(fd_, chunkSize, SEEK_CUR) == -1 ) goto error; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + } + + // Check that the data is not compressed. + unsigned short format_tag; + if ( fread(&chunkSize, 4, 1, fd_) != 1 ) goto error; // Read fmt chunk size. + if ( fread(&format_tag, 2, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap16((unsigned char *)&format_tag); + swap32((unsigned char *)&chunkSize); +#endif + if ( format_tag == 0xFFFE ) { // WAVE_FORMAT_EXTENSIBLE + dataOffset_ = ftell(fd_); + if ( fseek(fd_, 14, SEEK_CUR) == -1 ) goto error; + unsigned short extSize; + if ( fread(&extSize, 2, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap16((unsigned char *)&extSize); +#endif + if ( extSize == 0 ) goto error; + if ( fseek(fd_, 6, SEEK_CUR) == -1 ) goto error; + if ( fread(&format_tag, 2, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap16((unsigned char *)&format_tag); +#endif + if ( fseek(fd_, dataOffset_, SEEK_SET) == -1 ) goto error; + } + if ( format_tag != 1 && format_tag != 3 ) { // PCM = 1, FLOAT = 3 + oStream_ << "FileRead: "<< fileName << " contains an unsupported data format type (" << format_tag << ")."; + return false; + } + + // Get number of channels from the header. + SINT16 temp; + if ( fread(&temp, 2, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap16((unsigned char *)&temp); +#endif + channels_ = (unsigned int ) temp; + + // Get file sample rate from the header. + SINT32 srate; + if ( fread(&srate, 4, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&srate); +#endif + fileRate_ = (StkFloat) srate; + + // Determine the data type. + dataType_ = 0; + if ( fseek(fd_, 6, SEEK_CUR) == -1 ) goto error; // Locate bits_per_sample info. + if ( fread(&temp, 2, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap16((unsigned char *)&temp); +#endif + if ( format_tag == 1 ) { + if ( temp == 8 ) + dataType_ = STK_SINT8; + else if ( temp == 16 ) + dataType_ = STK_SINT16; + else if ( temp == 24 ) + dataType_ = STK_SINT24; + else if ( temp == 32 ) + dataType_ = STK_SINT32; + } + else if ( format_tag == 3 ) { + if ( temp == 32 ) + dataType_ = STK_FLOAT32; + else if ( temp == 64 ) + dataType_ = STK_FLOAT64; + } + if ( dataType_ == 0 ) { + oStream_ << "FileRead: " << temp << " bits per sample with data format " << format_tag << " are not supported (" << fileName << ")."; + return false; + } + + // Jump over any remaining part of the "fmt" chunk. + if ( fseek(fd_, chunkSize-16, SEEK_CUR) == -1 ) goto error; + + // Find "data" chunk ... it must come after the "fmt" chunk. + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + + while ( strncmp(id, "data", 4) ) { + if ( fread(&chunkSize, 4, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&chunkSize); +#endif + chunkSize += chunkSize % 2; // chunk sizes must be even + if ( fseek(fd_, chunkSize, SEEK_CUR) == -1 ) goto error; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + } + + // Get length of data from the header. + SINT32 bytes; + if ( fread(&bytes, 4, 1, fd_) != 1 ) goto error; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + fileSize_ = bytes / temp / channels_; // sample frames + fileSize_ *= 8; // sample frames + + dataOffset_ = ftell(fd_); + byteswap_ = false; +#ifndef __LITTLE_ENDIAN__ + byteswap_ = true; +#endif + + wavFile_ = true; + return true; + + error: + oStream_ << "FileRead: error reading WAV file (" << fileName << ")."; + return false; +} + +bool FileRead :: getSndInfo( const char *fileName ) +{ + // Determine the data type. + UINT32 format; + if ( fseek(fd_, 12, SEEK_SET) == -1 ) goto error; // Locate format + if ( fread(&format, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&format); +#endif + + if (format == 2) dataType_ = STK_SINT8; + else if (format == 3) dataType_ = STK_SINT16; + else if (format == 4) dataType_ = STK_SINT24; + else if (format == 5) dataType_ = STK_SINT32; + else if (format == 6) dataType_ = STK_FLOAT32; + else if (format == 7) dataType_ = STK_FLOAT64; + else { + oStream_ << "FileRead: data format in file " << fileName << " is not supported."; + return false; + } + + // Get file sample rate from the header. + UINT32 srate; + if ( fread(&srate, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&srate); +#endif + fileRate_ = (StkFloat) srate; + + // Get number of channels from the header. + UINT32 chans; + if ( fread(&chans, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&chans); +#endif + channels_ = chans; + + UINT32 offset; + if ( fseek(fd_, 4, SEEK_SET) == -1 ) goto error; + if ( fread(&offset, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&offset); +#endif + dataOffset_ = offset; + + // Get length of data from the header. + if ( fread(&fileSize_, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&fileSize_); +#endif + // Convert to sample frames. + if ( dataType_ == STK_SINT8 ) + fileSize_ /= channels_; + if ( dataType_ == STK_SINT16 ) + fileSize_ /= 2 * channels_; + else if ( dataType_ == STK_SINT24 ) + fileSize_ /= 3 * channels_; + else if ( dataType_ == STK_SINT32 || dataType_ == STK_FLOAT32 ) + fileSize_ /= 4 * channels_; + else if ( dataType_ == STK_FLOAT64 ) + fileSize_ /= 8 * channels_; + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; +#endif + + return true; + + error: + oStream_ << "FileRead: Error reading SND file (" << fileName << ")."; + return false; +} + +bool FileRead :: getAifInfo( const char *fileName ) +{ + bool aifc = false; + char id[4]; + + // Determine whether this is AIFF or AIFC. + if ( fseek(fd_, 8, SEEK_SET) == -1 ) goto error; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + if ( !strncmp(id, "AIFC", 4) ) aifc = true; + + // Find "common" chunk + SINT32 chunkSize; + if ( fread(&id, 4, 1, fd_) != 1) goto error; + while ( strncmp(id, "COMM", 4) ) { + if ( fread(&chunkSize, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&chunkSize); +#endif + chunkSize += chunkSize % 2; // chunk sizes must be even + if ( fseek(fd_, chunkSize, SEEK_CUR) == -1 ) goto error; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + } + + // Get number of channels from the header. + SINT16 temp; + if ( fseek(fd_, 4, SEEK_CUR) == -1 ) goto error; // Jump over chunk size + if ( fread(&temp, 2, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&temp); +#endif + channels_ = temp; + + // Get length of data from the header. + SINT32 frames; + if ( fread(&frames, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&frames); +#endif + fileSize_ = frames; // sample frames + + // Read the number of bits per sample. + if ( fread(&temp, 2, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&temp); +#endif + + // Get file sample rate from the header. For AIFF files, this value + // is stored in a 10-byte, IEEE Standard 754 floating point number, + // so we need to convert it first. + unsigned char srate[10]; + unsigned char exp; + unsigned long mantissa; + unsigned long last; + if ( fread(&srate, 10, 1, fd_) != 1 ) goto error; + mantissa = (unsigned long) *(unsigned long *)(srate+2); +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&mantissa); +#endif + exp = 30 - *(srate+1); + last = 0; + while (exp--) { + last = mantissa; + mantissa >>= 1; + } + if (last & 0x00000001) mantissa++; + fileRate_ = (StkFloat) mantissa; + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; +#endif + + // Determine the data format. + dataType_ = 0; + if ( aifc == false ) { + if ( temp <= 8 ) dataType_ = STK_SINT8; + else if ( temp <= 16 ) dataType_ = STK_SINT16; + else if ( temp <= 24 ) dataType_ = STK_SINT24; + else if ( temp <= 32 ) dataType_ = STK_SINT32; + } + else { + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + if ( !strncmp(id, "sowt", 4) ) { // uncompressed little-endian + if ( byteswap_ == false ) byteswap_ = true; + else byteswap_ = false; + } + if ( !strncmp(id, "NONE", 4) || !strncmp(id, "sowt", 4) ) { + if ( temp <= 8 ) dataType_ = STK_SINT8; + else if ( temp <= 16 ) dataType_ = STK_SINT16; + else if ( temp <= 24 ) dataType_ = STK_SINT24; + else if ( temp <= 32 ) dataType_ = STK_SINT32; + } + else if ( (!strncmp(id, "fl32", 4) || !strncmp(id, "FL32", 4)) && temp == 32 ) dataType_ = STK_FLOAT32; + else if ( (!strncmp(id, "fl64", 4) || !strncmp(id, "FL64", 4)) && temp == 64 ) dataType_ = STK_FLOAT64; + } + if ( dataType_ == 0 ) { + oStream_ << "FileRead: AIFF/AIFC file (" << fileName << ") has unsupported data type (" << id << ")."; + return false; + } + + // Start at top to find data (SSND) chunk ... chunk order is undefined. + if ( fseek(fd_, 12, SEEK_SET) == -1 ) goto error; + + // Find data (SSND) chunk + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + while ( strncmp(id, "SSND", 4) ) { + if ( fread(&chunkSize, 4, 1, fd_) != 1 ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&chunkSize); +#endif + chunkSize += chunkSize % 2; // chunk sizes must be even + if ( fseek(fd_, chunkSize, SEEK_CUR) == -1 ) goto error; + if ( fread(&id, 4, 1, fd_) != 1 ) goto error; + } + + // Skip over chunk size, offset, and blocksize fields + if ( fseek(fd_, 12, SEEK_CUR) == -1 ) goto error; + + dataOffset_ = ftell(fd_); + return true; + + error: + oStream_ << "FileRead: Error reading AIFF file (" << fileName << ")."; + return false; +} + +bool FileRead :: findNextMatArray( SINT32 *chunkSize, SINT32 *rows, SINT32 *columns, SINT32 *nametype ) +{ + // Look for the next data array element. The file pointer should be + // at the data element type when this function is called. + SINT32 datatype; + *chunkSize = 0; + do { + if ( fseek(fd_, *chunkSize, SEEK_CUR) == -1 ) return false; + if ( fread(&datatype, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)&datatype); + if ( fread(chunkSize, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)chunkSize); + } while ( datatype != 14 ); + + // Check dimension subelement size to make sure 2D + if ( fseek(fd_, 20, SEEK_CUR) == -1 ) return false; + SINT32 size; + if ( fread(&size, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)&size); + if ( size != 8 ) return false; + + // Read dimensions data + if ( fread(rows, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)rows); + if ( fread(columns, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)columns); + + // Read array name subelement type + if ( fread(nametype, 4, 1, fd_) != 1 ) return false; + if ( byteswap_ ) swap32((unsigned char *)nametype); + + return true; +} + +bool FileRead :: getMatInfo( const char *fileName ) +{ + // MAT-file formatting information is available at: + // http://www.mathworks.com/access/helpdesk/help/pdf_doc/matlab/matfile_format.pdf + + // Verify this is a version 5 MAT-file format. + char head[5]; + if ( fseek(fd_, 0, SEEK_SET) == -1 ) goto error; + if ( fread(&head, 4, 1, fd_) != 1 ) goto error; + // If any of the first 4 characters of the header = 0, then this is + // a Version 4 MAT-file. + head[4] = '\0'; + if ( strstr(head, "0") ) { + oStream_ << "FileRead: " << fileName << " appears to be a Version 4 MAT-file, which is not currently supported."; + return false; + } + + // Determine the endian-ness of the file. + char mi[2]; + byteswap_ = false; + // Locate "M" and "I" characters in header. + if ( fseek(fd_, 126, SEEK_SET) == -1 ) goto error; + if ( fread(&mi, 2, 1, fd_) != 1) goto error; +#ifdef __LITTLE_ENDIAN__ + if ( !strncmp(mi, "MI", 2) ) + byteswap_ = true; + else if ( strncmp(mi, "IM", 2) ) goto error; +#else + if ( !strncmp(mi, "IM", 2)) + byteswap_ = true; + else if ( strncmp(mi, "MI", 2) ) goto error; +#endif + + // We are expecting a data element containing the audio data and an + // optional data element containing the sample rate (with an array + // name of "fs"). Both elements should be stored as a Matlab array + // type (14). + + bool doneParsing, haveData, haveSampleRate; + SINT32 chunkSize, rows, columns, nametype; + long dataoffset; + doneParsing = false; + haveData = false; + haveSampleRate = false; + while ( !doneParsing ) { + + dataoffset = ftell( fd_ ); // save location in file + if ( findNextMatArray( &chunkSize, &rows, &columns, &nametype ) == false ) { + // No more Matlab array type chunks found. + if ( !haveData ) { + oStream_ << "FileRead: No audio data found in MAT-file (" << fileName << ")."; + return false; + } + else if ( !haveSampleRate ) { + fileRate_ = 44100.0; + oStream_ << "FileRead: No sample rate found ... assuming 44100.0"; + handleError( StkError::WARNING ); + return true; + } + else return true; + } + + if ( !haveSampleRate && rows == 1 && columns == 1 ) { // Parse for sample rate. + + SINT32 namesize = 4; + if ( nametype == 1 ) { // array name > 4 characters + if ( fread(&namesize, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&namesize); + if ( namesize != 2 ) goto tryagain; // expecting name = "fs" + namesize = 8; // field must be padded to multiple of 8 bytes + } + char name[3]; name[2] = '\0'; + if ( fread(&name, 2, 1, fd_) != 1) goto error; + if ( strncmp(name, "fs", 2) ) goto tryagain; + + // Jump to real part data subelement, which is likely to be in a + // small data format. + if ( fseek(fd_, namesize-2, SEEK_CUR) == -1 ) goto error; + UINT32 type; + StkFloat srate; + if ( fread(&type, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&type); + if ( (type & 0xffff0000) != 0 ) // small data format + type = (type & 0x0000ffff); + else + if ( fseek(fd_, 4, SEEK_CUR) == -1 ) goto error; + if ( type == 1 ) { // SINT8 + signed char rate; + if ( fread(&rate, 1, 1, fd_) != 1 ) goto error; + srate = (StkFloat) rate; + } + else if ( type == 2 ) { // UINT8 + unsigned char rate; + if ( fread(&rate, 1, 1, fd_) != 1 ) goto error; + srate = (StkFloat) rate; + } + else if ( type == 3 ) { // SINT16 + SINT16 rate; + if ( fread(&rate, 2, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap16((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else if ( type == 4 ) { // UINT16 + UINT16 rate; + if ( fread(&rate, 2, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap16((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else if ( type == 5 ) { // SINT32 + SINT32 rate; + if ( fread(&rate, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else if ( type == 6 ) { // UINT32 + UINT32 rate; + if ( fread(&rate, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else if ( type == 7 ) { // FLOAT32 + FLOAT32 rate; + if ( fread(&rate, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else if ( type == 9 ) { // FLOAT64 + FLOAT64 rate; + if ( fread(&rate, 8, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap64((unsigned char *)&rate); + srate = (StkFloat) rate; + } + else + goto tryagain; + + if ( srate > 0 ) fileRate_ = srate; + haveSampleRate = true; + } + else if ( !haveData ) { // Parse for data. + + // Assume channels = smaller of rows or columns. + if ( rows < columns ) { + channels_ = rows; + fileSize_ = columns; + } + else { + oStream_ << "FileRead: Transpose the MAT-file array so that audio channels fill matrix rows (not columns)."; + return false; + } + + SINT32 namesize = 4; + if ( nametype == 1 ) { // array name > 4 characters + if ( fread(&namesize, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&namesize); + namesize = (SINT32) ceil((float)namesize / 8); + if ( fseek( fd_, namesize*8, SEEK_CUR) == -1 ) goto error; // jump over array name + } + else { + if ( fseek( fd_, 4, SEEK_CUR ) == -1 ) goto error; + } + + // Now at real part data subelement + SINT32 type; + if ( fread(&type, 4, 1, fd_) != 1 ) goto error; + if ( byteswap_ ) swap32((unsigned char *)&type); + if ( type == 1 ) dataType_ = STK_SINT8; + else if ( type == 3 ) dataType_ = STK_SINT16; + else if ( type == 5 ) dataType_ = STK_SINT32; + else if ( type == 7 ) dataType_ = STK_FLOAT32; + else if ( type == 9 ) dataType_ = STK_FLOAT64; + else { + oStream_ << "FileRead: The MAT-file array data format (" << type << ") is not supported."; + return false; + } + + // Jump to the data. + if ( fseek(fd_, 4, SEEK_CUR) == -1 ) goto error; + dataOffset_ = ftell(fd_); + haveData = true; + } + + tryagain: + if ( haveData && haveSampleRate ) doneParsing = true; + else // jump to end of data element and keep trying + if ( fseek( fd_, dataoffset+chunkSize+8, SEEK_SET) == -1 ) goto error; + } + + return true; + + error: + oStream_ << "FileRead: Error reading MAT-file (" << fileName << ") header."; + return false; +} + +void FileRead :: read( StkFrames& buffer, unsigned long startFrame, bool doNormalize ) +{ + // Make sure we have an open file. + if ( fd_ == 0 ) { + oStream_ << "FileRead::read: a file is not open!"; + Stk::handleError( StkError::WARNING ); return; + } + + // Check the buffer size. + unsigned long nFrames = buffer.frames(); + if ( nFrames == 0 ) { + oStream_ << "FileRead::read: StkFrames buffer size is zero ... no data read!"; + Stk::handleError( StkError::WARNING ); return; + } + + if ( buffer.channels() != channels_ ) { + oStream_ << "FileRead::read: StkFrames argument has incompatible number of channels!"; + Stk::handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( startFrame >= fileSize_ ) { + oStream_ << "FileRead::read: startFrame argument is greater than or equal to the file size!"; + Stk::handleError( StkError::FUNCTION_ARGUMENT ); + } + + // Check for file end. + if ( startFrame + nFrames > fileSize_ ) + nFrames = fileSize_ - startFrame; + + long i, nSamples = (long) ( nFrames * channels_ ); + unsigned long offset = startFrame * channels_; + + // Read samples into StkFrames data buffer. + if ( dataType_ == STK_SINT16 ) { + SINT16 *buf = (SINT16 *) &buffer[0]; + if ( fseek( fd_, dataOffset_+(offset*2), SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples * 2, 1, fd_ ) != 1 ) goto error; + if ( byteswap_ ) { + SINT16 *ptr = buf; + for ( i=nSamples-1; i>=0; i-- ) + swap16( (unsigned char *) ptr++ ); + } + if ( doNormalize ) { + StkFloat gain = 1.0 / 32768.0; + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i] * gain; + } + else { + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i]; + } + } + else if ( dataType_ == STK_SINT32 ) { + SINT32 *buf = (SINT32 *) &buffer[0]; + if ( fseek( fd_, dataOffset_+(offset*4 ), SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples * 4, 1, fd_ ) != 1 ) goto error; + if ( byteswap_ ) { + SINT32 *ptr = buf; + for ( i=nSamples-1; i>=0; i-- ) + swap32( (unsigned char *) ptr++ ); + } + if ( doNormalize ) { + StkFloat gain = 1.0 / 2147483648.0; + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i] * gain; + } + else { + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i]; + } + } + else if ( dataType_ == STK_FLOAT32 ) { + FLOAT32 *buf = (FLOAT32 *) &buffer[0]; + if ( fseek( fd_, dataOffset_+(offset*4), SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples * 4, 1, fd_ ) != 1 ) goto error; + if ( byteswap_ ) { + FLOAT32 *ptr = buf; + for ( i=nSamples-1; i>=0; i-- ) + swap32( (unsigned char *) ptr++ ); + } + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i]; + } + else if ( dataType_ == STK_FLOAT64 ) { + FLOAT64 *buf = (FLOAT64 *) &buffer[0]; + if ( fseek( fd_, dataOffset_+(offset*8), SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples * 8, 1, fd_ ) != 1 ) goto error; + if ( byteswap_ ) { + FLOAT64 *ptr = buf; + for ( i=nSamples-1; i>=0; i-- ) + swap64( (unsigned char *) ptr++ ); + } + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i]; + } + else if ( dataType_ == STK_SINT8 && wavFile_ ) { // 8-bit WAV data is unsigned! + unsigned char *buf = (unsigned char *) &buffer[0]; + if ( fseek( fd_, dataOffset_+offset, SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples, 1, fd_) != 1 ) goto error; + if ( doNormalize ) { + StkFloat gain = 1.0 / 128.0; + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = ( buf[i] - 128 ) * gain; + } + else { + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i] - 128.0; + } + } + else if ( dataType_ == STK_SINT8 ) { // signed 8-bit data + char *buf = (char *) &buffer[0]; + if ( fseek( fd_, dataOffset_+offset, SEEK_SET ) == -1 ) goto error; + if ( fread( buf, nSamples, 1, fd_ ) != 1 ) goto error; + if ( doNormalize ) { + StkFloat gain = 1.0 / 128.0; + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i] * gain; + } + else { + for ( i=nSamples-1; i>=0; i-- ) + buffer[i] = buf[i]; + } + } + else if ( dataType_ == STK_SINT24 ) { + // 24-bit values are harder to import efficiently since there is + // no native 24-bit type. The following routine works but is much + // less efficient than that used for the other data types. + SINT32 temp; + unsigned char *ptr = (unsigned char *) &temp; + StkFloat gain = 1.0 / 2147483648.0; + if ( fseek(fd_, dataOffset_+(offset*3), SEEK_SET ) == -1 ) goto error; + for ( i=0; i +#include +#include +#include + +namespace stk { + +const FileWrite::FILE_TYPE FileWrite :: FILE_RAW = 1; +const FileWrite::FILE_TYPE FileWrite :: FILE_WAV = 2; +const FileWrite::FILE_TYPE FileWrite :: FILE_SND = 3; +const FileWrite::FILE_TYPE FileWrite :: FILE_AIF = 4; +const FileWrite::FILE_TYPE FileWrite :: FILE_MAT = 5; + +// WAV header structure. See +// http://www-mmsp.ece.mcgill.ca/documents/audioformats/WAVE/Docs/rfc2361.txt +// for information regarding format codes. +struct WaveHeader { + char riff[4]; // "RIFF" + SINT32 fileSize; // in bytes + char wave[4]; // "WAVE" + char fmt[4]; // "fmt " + SINT32 chunkSize; // in bytes (16 for PCM) + SINT16 formatCode; // 1=PCM, 2=ADPCM, 3=IEEE float, 6=A-Law, 7=Mu-Law + SINT16 nChannels; // 1=mono, 2=stereo + SINT32 sampleRate; + SINT32 bytesPerSecond; + SINT16 bytesPerSample; // 2=16-bit mono, 4=16-bit stereo + SINT16 bitsPerSample; + SINT16 cbSize; // size of extension + SINT16 validBits; // valid bits per sample + SINT32 channelMask; // speaker position mask + char subformat[16]; // format code and GUID + char fact[4]; // "fact" + SINT32 factSize; // fact chunk size + SINT32 frames; // sample frames +}; + +// SND (AU) header structure (NeXT and Sun). +struct SndHeader { + char pref[4]; + SINT32 headerBytes; + SINT32 dataBytes; + SINT32 format; + SINT32 sampleRate; + SINT32 nChannels; + char comment[16]; +}; + +// AIFF/AIFC header structure ... only the part common to both +// formats. +struct AifHeader { + char form[4]; // "FORM" + SINT32 formSize; // in bytes + char aiff[4]; // "AIFF" or "AIFC" + char comm[4]; // "COMM" + SINT32 commSize; // "COMM" chunk size (18 for AIFF, 24 for AIFC) + SINT16 nChannels; // number of channels + unsigned long sampleFrames; // sample frames of audio data + SINT16 sampleSize; // in bits + unsigned char srate[10]; // IEEE 754 floating point format +}; + +struct AifSsnd { + char ssnd[4]; // "SSND" + SINT32 ssndSize; // "SSND" chunk size + unsigned long offset; // data offset in data block (should be 0) + unsigned long blockSize; // not used by STK (should be 0) +}; + +// MAT-file 5 header structure. +struct MatHeader { + char heading[124]; // Header text field + SINT16 hff[2]; // Header flag fields + SINT32 fs[16]; // Sample rate data element + SINT32 adf[11]; // Array data format fields + // There's more, but it's of variable length +}; + +FileWrite :: FileWrite() + : fd_( 0 ) +{ +} + +FileWrite::FileWrite( std::string fileName, unsigned int nChannels, FILE_TYPE type, Stk::StkFormat format ) + : fd_( 0 ) +{ + this->open( fileName, nChannels, type, format ); +} + +FileWrite :: ~FileWrite() +{ + this->close(); +} + +void FileWrite :: close( void ) +{ + if ( fd_ == 0 ) return; + + if ( fileType_ == FILE_RAW ) + fclose( fd_ ); + else if ( fileType_ == FILE_WAV ) + this->closeWavFile(); + else if ( fileType_ == FILE_SND ) + this->closeSndFile(); + else if ( fileType_ == FILE_AIF ) + this->closeAifFile(); + else if ( fileType_ == FILE_MAT ) + this->closeMatFile(); + + fd_ = 0; +} + +bool FileWrite :: isOpen( void ) +{ + if ( fd_ ) return true; + else return false; +} + +void FileWrite :: open( std::string fileName, unsigned int nChannels, FileWrite::FILE_TYPE type, Stk::StkFormat format ) +{ + // Call close() in case another file is already open. + this->close(); + + if ( nChannels < 1 ) { + oStream_ << "FileWrite::open: then channels argument must be greater than zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + channels_ = nChannels; + fileType_ = type; + + if ( format != STK_SINT8 && format != STK_SINT16 && + format != STK_SINT24 && format != STK_SINT32 && + format != STK_FLOAT32 && format != STK_FLOAT64 ) { + oStream_ << "FileWrite::open: unknown data type (" << format << ") specified!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + dataType_ = format; + + bool result = false; + if ( fileType_ == FILE_RAW ) { + if ( channels_ != 1 ) { + oStream_ << "FileWrite::open: STK RAW files are, by definition, always monaural (channels = " << nChannels << " not supported)!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + result = setRawFile( fileName ); + } + else if ( fileType_ == FILE_WAV ) + result = setWavFile( fileName ); + else if ( fileType_ == FILE_SND ) + result = setSndFile( fileName ); + else if ( fileType_ == FILE_AIF ) + result = setAifFile( fileName ); + else if ( fileType_ == FILE_MAT ) + result = setMatFile( fileName ); + else { + oStream_ << "FileWrite::open: unknown file type (" << fileType_ << ") specified!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( result == false ) + handleError( StkError::FILE_ERROR ); + + frameCounter_ = 0; +} + +bool FileWrite :: setRawFile( std::string fileName ) +{ + if ( fileName.find( ".raw" ) == std::string::npos ) fileName += ".raw"; + fd_ = fopen( fileName.c_str(), "wb" ); + if ( !fd_ ) { + oStream_ << "FileWrite: could not create RAW file: " << fileName << '.'; + return false; + } + + if ( dataType_ != STK_SINT16 ) { + dataType_ = STK_SINT16; + oStream_ << "FileWrite: using 16-bit signed integer data format for file " << fileName << '.'; + handleError( StkError::WARNING ); + } + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; +#endif + + oStream_ << "FileWrite: creating RAW file: " << fileName; + handleError( StkError::STATUS ); + return true; +} + +bool FileWrite :: setWavFile( std::string fileName ) +{ + if ( fileName.find( ".wav" ) == std::string::npos ) fileName += ".wav"; + fd_ = fopen( fileName.c_str(), "wb" ); + if ( !fd_ ) { + oStream_ << "FileWrite: could not create WAV file: " << fileName; + return false; + } + + struct WaveHeader hdr = { {'R','I','F','F'}, 44, {'W','A','V','E'}, {'f','m','t',' '}, 16, 1, 1, + (SINT32) Stk::sampleRate(), 0, 2, 16, 0, 0, 0, + {'\x01','\x00','\x00','\x00','\x00','\x00','\x10','\x00','\x80','\x00','\x00','\xAA','\x00','\x38','\x9B','\x71'}, + {'f','a','c','t'}, 4, 0 }; + hdr.nChannels = (SINT16) channels_; + if ( dataType_ == STK_SINT8 ) + hdr.bitsPerSample = 8; + else if ( dataType_ == STK_SINT16 ) + hdr.bitsPerSample = 16; + else if ( dataType_ == STK_SINT24 ) + hdr.bitsPerSample = 24; + else if ( dataType_ == STK_SINT32 ) + hdr.bitsPerSample = 32; + else if ( dataType_ == STK_FLOAT32 ) { + hdr.formatCode = 3; + hdr.bitsPerSample = 32; + } + else if ( dataType_ == STK_FLOAT64 ) { + hdr.formatCode = 3; + hdr.bitsPerSample = 64; + } + hdr.bytesPerSample = (SINT16) (channels_ * hdr.bitsPerSample / 8); + hdr.bytesPerSecond = (SINT32) (hdr.sampleRate * hdr.bytesPerSample); + + unsigned int bytesToWrite = 36; + if ( channels_ > 2 || hdr.bitsPerSample > 16 ) { // use extensible format + bytesToWrite = 72; + hdr.chunkSize += 24; + hdr.formatCode = 0xFFFE; + hdr.cbSize = 22; + hdr.validBits = hdr.bitsPerSample; + SINT16 *subFormat = (SINT16 *)&hdr.subformat[0]; + if ( dataType_ == STK_FLOAT32 || dataType_ == STK_FLOAT64 ) + *subFormat = 3; + else *subFormat = 1; + } + + byteswap_ = false; +#ifndef __LITTLE_ENDIAN__ + byteswap_ = true; + swap32((unsigned char *)&hdr.chunkSize); + swap16((unsigned char *)&hdr.formatCode); + swap16((unsigned char *)&hdr.nChannels); + swap32((unsigned char *)&hdr.sampleRate); + swap32((unsigned char *)&hdr.bytesPerSecond); + swap16((unsigned char *)&hdr.bytesPerSample); + swap16((unsigned char *)&hdr.bitsPerSample); + swap16((unsigned char *)&hdr.cbSize); + swap16((unsigned char *)&hdr.validBits); + swap16((unsigned char *)&hdr.subformat[0]); + swap32((unsigned char *)&hdr.factSize); +#endif + + char data[4] = {'d','a','t','a'}; + SINT32 dataSize = 0; + if ( fwrite(&hdr, 1, bytesToWrite, fd_) != bytesToWrite ) goto error; + if ( fwrite(&data, 4, 1, fd_) != 1 ) goto error; + if ( fwrite(&dataSize, 4, 1, fd_) != 1 ) goto error; + + oStream_ << "FileWrite: creating WAV file: " << fileName; + handleError( StkError::STATUS ); + return true; + + error: + oStream_ << "FileWrite: could not write WAV header for file: " << fileName; + return false; +} + +void FileWrite :: closeWavFile( void ) +{ + int bytesPerSample = 1; + if ( dataType_ == STK_SINT16 ) + bytesPerSample = 2; + else if ( dataType_ == STK_SINT24 ) + bytesPerSample = 3; + else if ( dataType_ == STK_SINT32 || dataType_ == STK_FLOAT32 ) + bytesPerSample = 4; + else if ( dataType_ == STK_FLOAT64 ) + bytesPerSample = 8; + + bool useExtensible = false; + int dataLocation = 40; + if ( bytesPerSample > 2 || channels_ > 2 ) { + useExtensible = true; + dataLocation = 76; + } + + SINT32 bytes = (SINT32) (frameCounter_ * channels_ * bytesPerSample); + if ( bytes % 2 ) { // pad extra byte if odd + signed char sample = 0; + fwrite( &sample, 1, 1, fd_ ); + } +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + fseek( fd_, dataLocation, SEEK_SET ); // jump to data length + fwrite( &bytes, 4, 1, fd_ ); + + bytes = (SINT32) (frameCounter_ * channels_ * bytesPerSample + 44); + if ( useExtensible ) bytes += 36; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + fseek( fd_, 4, SEEK_SET ); // jump to file size + fwrite( &bytes, 4, 1, fd_ ); + + if ( useExtensible ) { // fill in the "fact" chunk frames value + bytes = (SINT32) frameCounter_; +#ifndef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + fseek( fd_, 68, SEEK_SET ); + fwrite( &bytes, 4, 1, fd_ ); + } + + fclose( fd_ ); +} + +bool FileWrite :: setSndFile( std::string fileName ) +{ + std::string name( fileName ); + if ( fileName.find( ".snd" ) == std::string::npos ) fileName += ".snd"; + fd_ = fopen( fileName.c_str(), "wb" ); + if ( !fd_ ) { + oStream_ << "FileWrite: could not create SND file: " << fileName; + return false; + } + + struct SndHeader hdr = {".sn", 40, 0, 3, (SINT32) Stk::sampleRate(), 1, "Created by STK"}; + hdr.pref[3] = 'd'; + hdr.nChannels = channels_; + if ( dataType_ == STK_SINT8 ) + hdr.format = 2; + else if ( dataType_ == STK_SINT16 ) + hdr.format = 3; + else if ( dataType_ == STK_SINT24 ) + hdr.format = 4; + else if ( dataType_ == STK_SINT32 ) + hdr.format = 5; + else if ( dataType_ == STK_FLOAT32 ) + hdr.format = 6; + else if ( dataType_ == STK_FLOAT64 ) + hdr.format = 7; + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; + swap32 ((unsigned char *)&hdr.headerBytes); + swap32 ((unsigned char *)&hdr.format); + swap32 ((unsigned char *)&hdr.sampleRate); + swap32 ((unsigned char *)&hdr.nChannels); +#endif + + if ( fwrite(&hdr, 4, 10, fd_) != 10 ) { + oStream_ << "FileWrite: Could not write SND header for file " << fileName << '.'; + return false; + } + + oStream_ << "FileWrite: creating SND file: " << fileName; + handleError( StkError::STATUS ); + return true; +} + +void FileWrite :: closeSndFile( void ) +{ + int bytesPerSample = 1; + if ( dataType_ == STK_SINT16 ) + bytesPerSample = 2; + else if ( dataType_ == STK_SINT24 ) + bytesPerSample = 3; + else if ( dataType_ == STK_SINT32 ) + bytesPerSample = 4; + else if ( dataType_ == STK_FLOAT32 ) + bytesPerSample = 4; + else if ( dataType_ == STK_FLOAT64 ) + bytesPerSample = 8; + + SINT32 bytes = (SINT32) (frameCounter_ * bytesPerSample * channels_); +#ifdef __LITTLE_ENDIAN__ + swap32 ((unsigned char *)&bytes); +#endif + fseek(fd_, 8, SEEK_SET); // jump to data size + fwrite(&bytes, 4, 1, fd_); + fclose(fd_); +} + +bool FileWrite :: setAifFile( std::string fileName ) +{ + std::string name( fileName ); + if ( fileName.find( ".aif" ) == std::string::npos ) fileName += ".aif"; + fd_ = fopen( fileName.c_str(), "wb" ); + if ( !fd_ ) { + oStream_ << "FileWrite: could not create AIF file: " << fileName; + return false; + } + + // Common parts of AIFF/AIFC header. + struct AifHeader hdr = {{'F','O','R','M'}, 46, {'A','I','F','F'}, {'C','O','M','M'}, 18, 0, 0, 16, "0"}; + struct AifSsnd ssnd = {{'S','S','N','D'}, 8, 0, 0}; + hdr.nChannels = channels_; + if ( dataType_ == STK_SINT8 ) + hdr.sampleSize = 8; + else if ( dataType_ == STK_SINT16 ) + hdr.sampleSize = 16; + else if ( dataType_ == STK_SINT24 ) + hdr.sampleSize = 24; + else if ( dataType_ == STK_SINT32 ) + hdr.sampleSize = 32; + else if ( dataType_ == STK_FLOAT32 ) { + hdr.aiff[3] = 'C'; + hdr.sampleSize = 32; + hdr.commSize = 24; + } + else if ( dataType_ == STK_FLOAT64 ) { + hdr.aiff[3] = 'C'; + hdr.sampleSize = 64; + hdr.commSize = 24; + } + + // For AIFF files, the sample rate is stored in a 10-byte, + // IEEE Standard 754 floating point number, so we need to + // convert to that. + SINT16 i; + unsigned long exp; + unsigned long rate = (unsigned long) Stk::sampleRate(); + memset( hdr.srate, 0, 10 ); + exp = rate; + for ( i=0; i<32; i++ ) { + exp >>= 1; + if ( !exp ) break; + } + i += 16383; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&i); +#endif + memcpy( hdr.srate, &i, sizeof(SINT16) ); + + for ( i=32; i; i-- ) { + if ( rate & 0x80000000 ) break; + rate <<= 1; + } + +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&rate); +#endif + memcpy( hdr.srate + 2, &rate, sizeof(rate) ); + + byteswap_ = false; +#ifdef __LITTLE_ENDIAN__ + byteswap_ = true; + swap32((unsigned char *)&hdr.formSize); + swap32((unsigned char *)&hdr.commSize); + swap16((unsigned char *)&hdr.nChannels); + swap16((unsigned char *)&hdr.sampleSize); + swap32((unsigned char *)&ssnd.ssndSize); + swap32((unsigned char *)&ssnd.offset); + swap32((unsigned char *)&ssnd.blockSize); +#endif + + // The structure boundaries don't allow a single write of 54 bytes. + if ( fwrite(&hdr, 4, 5, fd_) != 5 ) goto error; + if ( fwrite(&hdr.nChannels, 2, 1, fd_) != 1 ) goto error; + if ( fwrite(&hdr.sampleFrames, 4, 1, fd_) != 1 ) goto error; + if ( fwrite(&hdr.sampleSize, 2, 1, fd_) != 1 ) goto error; + if ( fwrite(&hdr.srate, 10, 1, fd_) != 1 ) goto error; + + if ( dataType_ == STK_FLOAT32 ) { + char type[4] = {'f','l','3','2'}; + char zeroes[2] = { 0, 0 }; + if ( fwrite(&type, 4, 1, fd_) != 1 ) goto error; + if ( fwrite(&zeroes, 2, 1, fd_) != 1 ) goto error; + } + else if ( dataType_ == STK_FLOAT64 ) { + char type[4] = {'f','l','6','4'}; + char zeroes[2] = { 0, 0 }; + if ( fwrite(&type, 4, 1, fd_) != 1 ) goto error; + if ( fwrite(&zeroes, 2, 1, fd_) != 1 ) goto error; + } + + if ( fwrite(&ssnd, 4, 4, fd_) != 4 ) goto error; + + oStream_ << "FileWrite: creating AIF file: " << fileName; + handleError( StkError::STATUS ); + return true; + + error: + oStream_ << "FileWrite: could not write AIF header for file: " << fileName; + return false; +} + +void FileWrite :: closeAifFile( void ) +{ + unsigned long frames = (unsigned long) frameCounter_; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&frames); +#endif + fseek(fd_, 22, SEEK_SET); // jump to "COMM" sampleFrames + fwrite(&frames, 4, 1, fd_); + + int bytesPerSample = 1; + if ( dataType_ == STK_SINT16 ) + bytesPerSample = 2; + if ( dataType_ == STK_SINT24 ) + bytesPerSample = 3; + else if ( dataType_ == STK_SINT32 || dataType_ == STK_FLOAT32 ) + bytesPerSample = 4; + else if ( dataType_ == STK_FLOAT64 ) + bytesPerSample = 8; + + unsigned long bytes = frameCounter_ * bytesPerSample * channels_ + 46; + if ( dataType_ == STK_FLOAT32 || dataType_ == STK_FLOAT64 ) bytes += 6; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + fseek(fd_, 4, SEEK_SET); // jump to file size + fwrite(&bytes, 4, 1, fd_); + + bytes = frameCounter_ * bytesPerSample * channels_ + 8; + if ( dataType_ == STK_FLOAT32 || dataType_ == STK_FLOAT64 ) bytes += 6; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&bytes); +#endif + if ( dataType_ == STK_FLOAT32 || dataType_ == STK_FLOAT64 ) + fseek(fd_, 48, SEEK_SET); // jump to "SSND" chunk size + else + fseek(fd_, 42, SEEK_SET); // jump to "SSND" chunk size + fwrite(&bytes, 4, 1, fd_); + + fclose( fd_ ); +} + +bool FileWrite :: setMatFile( std::string fileName ) +{ + if ( fileName.find( ".mat" ) == std::string::npos ) fileName += ".mat"; + fd_ = fopen( fileName.c_str(), "w+b" ); + if ( !fd_ ) { + oStream_ << "FileWrite: could not create MAT file: " << fileName; + return false; + } + + if ( dataType_ != STK_FLOAT64 ) { + dataType_ = STK_FLOAT64; + oStream_ << "FileWrite: using 64-bit floating-point data format for file " << fileName << '.'; + handleError( StkError::DEBUG_PRINT ); + } + + struct MatHeader hdr; + strcpy( hdr.heading,"MATLAB 5.0 MAT-file, Generated using the Synthesis ToolKit in C++ (STK). By Perry R. Cook and Gary P. Scavone." ); + for ( size_t i =strlen(hdr.heading); i<124; i++ ) hdr.heading[i] = ' '; + + // Header Flag Fields + hdr.hff[0] = (SINT16) 0x0100; // Version field + hdr.hff[1] = (SINT16) 'M'; // Endian indicator field ("MI") + hdr.hff[1] <<= 8; + hdr.hff[1] += 'I'; + + // Write sample rate in array data element + hdr.fs[0] = (SINT32) 14; // Matlab array data type value + hdr.fs[1] = (SINT32) 56; // Size of data element to follow (in bytes) + + // Numeric Array Subelements (4): + // 1. Array Flags + hdr.fs[2] = (SINT32) 6; // Matlab 32-bit unsigned integer data type value + hdr.fs[3] = (SINT32) 8; // 8 bytes of data to follow + hdr.fs[4] = (SINT32) 6; // Double-precision array, no array flags set + hdr.fs[5] = (SINT32) 0; // 4 bytes undefined + // 2. Array Dimensions + hdr.fs[6] = (SINT32) 5; // Matlab 32-bit signed integer data type value + hdr.fs[7] = (SINT32) 8; // 8 bytes of data to follow (2D array) + hdr.fs[8] = (SINT32) 1; // 1 row + hdr.fs[9] = (SINT32) 1; // 1 column + // 3. Array Name (small data format element) + hdr.fs[10] = 0x00020001; + hdr.fs[11] = 's' << 8; + hdr.fs[11] += 'f'; + // 4. Real Part + hdr.fs[12] = 9; // Matlab IEEE 754 double data type + hdr.fs[13] = 8; // 8 bytes of data to follow + FLOAT64 *sampleRate = (FLOAT64 *)&hdr.fs[14]; + *sampleRate = (FLOAT64) Stk::sampleRate(); + + // Write audio samples in array data element + hdr.adf[0] = (SINT32) 14; // Matlab array data type value + hdr.adf[1] = (SINT32) 0; // Size of file after this point to end (in bytes) + + // Numeric Array Subelements (4): + // 1. Array Flags + hdr.adf[2] = (SINT32) 6; // Matlab 32-bit unsigned integer data type value + hdr.adf[3] = (SINT32) 8; // 8 bytes of data to follow + hdr.adf[4] = (SINT32) 6; // Double-precision array, no array flags set + hdr.adf[5] = (SINT32) 0; // 4 bytes undefined + // 2. Array Dimensions + hdr.adf[6] = (SINT32) 5; // Matlab 32-bit signed integer data type value + hdr.adf[7] = (SINT32) 8; // 8 bytes of data to follow (2D array) + hdr.adf[8] = (SINT32) channels_; // This is the number of rows + hdr.adf[9] = (SINT32) 0; // This is the number of columns + + // 3. Array Name We'll use fileName for the matlab array name (as + // well as the file name), though we might need to strip off a + // leading directory path. If fileName is 4 characters or less, we + // have to use a small data format element for the array name data + // element. Otherwise, the array name must be formatted in 8-byte + // increments (up to 31 characters + NULL). + std::string name = fileName; + size_t found; + found = name.find_last_of("/\\"); + name = name.substr(found+1); + SINT32 namelength = (SINT32) name.size() - 4; // strip off the ".mat" extension + if ( namelength > 31 ) namelength = 31; // Truncate name to 31 characters. + if ( namelength > 4 ) { + hdr.adf[10] = (SINT32) 1; // Matlab 8-bit signed integer data type value + } + else { // Compressed data element format + hdr.adf[10] = (namelength << 16) + 1; + } + + SINT32 headsize = 40; // Number of bytes in audio data element so far. + + // Write the fixed portion of the header + if ( fwrite(&hdr, 236, 1, fd_) != 1 ) goto error; + + // Write MATLAB array name + SINT32 tmp; + if ( namelength > 4 ) { + if ( fwrite(&namelength, 4, 1, fd_) != 1) goto error; + if ( fwrite(name.c_str(), namelength, 1, fd_) != 1 ) goto error; + tmp = (SINT32) ceil((float)namelength / 8); + if ( fseek(fd_, tmp*8-namelength, SEEK_CUR) == -1 ) goto error; + headsize += tmp * 8; + } + else { // Compressed data element format + if ( fwrite(name.c_str(), namelength, 1, fd_) != 1 ) goto error; + tmp = 4 - namelength; + if ( fseek(fd_, tmp, SEEK_CUR) == -1 ) goto error; + } + + // Finish writing known header information + //4. Real Part + tmp = 9; // Matlab IEEE 754 double data type + if ( fwrite(&tmp, 4, 1, fd_) != 1 ) goto error; + tmp = 0; // Size of real part subelement in bytes (8 per sample) + if ( fwrite(&tmp, 4, 1, fd_) != 1 ) goto error; + headsize += 8; // Total number of bytes in data element so far + + if ( fseek(fd_, 196, SEEK_SET) == -1 ) goto error; + if ( fwrite(&headsize, 4, 1, fd_) != 1 ) goto error; // Write header size ... will update at end + if ( fseek(fd_, 0, SEEK_END) == -1 ) goto error; + + byteswap_ = false; + oStream_ << "FileWrite: creating MAT-file: " << fileName; + handleError( StkError::STATUS ); + + return true; + + error: + oStream_ << "FileWrite: could not write MAT-file header for file " << fileName << '.'; + return false; +} + +void FileWrite :: closeMatFile( void ) +{ + fseek(fd_, 228, SEEK_SET); // jump to number of columns + fwrite(&frameCounter_, 4, 1, fd_); + + SINT32 headsize, temp; + fseek(fd_, 196, SEEK_SET); // jump to header size + if (fread(&headsize, 4, 1, fd_) != 1) { + oStream_ << "FileWrite: could not read MAT-file header size."; + handleError( StkError::WARNING ); + goto close_file; + } + + temp = headsize; + headsize += (SINT32) (frameCounter_ * 8 * channels_); + fseek(fd_, 196, SEEK_SET); + // Write file size (minus some header info) + fwrite(&headsize, 4, 1, fd_); + + fseek(fd_, temp+196, SEEK_SET); // jump to data size (in bytes) + temp = (SINT32) (frameCounter_ * 8 * channels_); + fwrite(&temp, 4, 1, fd_); + +close_file: + fclose(fd_); +} + +void FileWrite :: write( StkFrames& buffer ) +{ + if ( fd_ == 0 ) { + oStream_ << "FileWrite::write(): a file has not yet been opened!"; + handleError( StkError::WARNING ); + return; + } + + if ( buffer.channels() != channels_ ) { + oStream_ << "FileWrite::write(): number of channels in the StkFrames argument does not match that specified to open() function!"; + handleError( StkError::FUNCTION_ARGUMENT ); + return; + } + + unsigned long nSamples = buffer.size(); + if ( dataType_ == STK_SINT16 ) { + SINT16 sample; + for ( unsigned long k=0; k + +namespace stk { + +FileWvIn :: FileWvIn( unsigned long chunkThreshold, unsigned long chunkSize ) + : finished_(true), interpolate_(false), time_(0.0), rate_(0.0), + chunkThreshold_(chunkThreshold), chunkSize_(chunkSize) +{ + Stk::addSampleRateAlert( this ); +} + +FileWvIn :: FileWvIn( std::string fileName, bool raw, bool doNormalize, + unsigned long chunkThreshold, unsigned long chunkSize, + bool doInt2FloatScaling ) + : finished_(true), interpolate_(false), time_(0.0), rate_(0.0), + chunkThreshold_(chunkThreshold), chunkSize_(chunkSize) +{ + openFile( fileName, raw, doNormalize, doInt2FloatScaling ); + Stk::addSampleRateAlert( this ); +} + +FileWvIn :: ~FileWvIn() +{ + this->closeFile(); + Stk::removeSampleRateAlert( this ); +} + +void FileWvIn :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) + this->setRate( oldRate * rate_ / newRate ); +} + +void FileWvIn :: closeFile( void ) +{ + if ( file_.isOpen() ) file_.close(); + finished_ = true; + lastFrame_.resize( 0, 0 ); +} + +void FileWvIn :: openFile( std::string fileName, bool raw, bool doNormalize, bool doInt2FloatScaling ) +{ + // Call close() in case another file is already open. + this->closeFile(); + + // Attempt to open the file ... an error might be thrown here. + file_.open( fileName, raw ); + + // Determine whether chunking or not. + if ( file_.fileSize() > chunkThreshold_ ) { + chunking_ = true; + chunkPointer_ = 0; + data_.resize( chunkSize_, file_.channels() ); + } + else { + chunking_ = false; + data_.resize( (size_t) file_.fileSize(), file_.channels() ); + } + + if ( doInt2FloatScaling ) + int2floatscaling_ = true; + else + int2floatscaling_ = false; + + // Load all or part of the data. + file_.read( data_, 0, int2floatscaling_ ); + + // Resize our lastFrame container. + lastFrame_.resize( 1, file_.channels() ); + + // Close the file unless chunking + fileSize_ = file_.fileSize(); + if ( !chunking_ ) file_.close(); + + // Set default rate based on file sampling rate. + this->setRate( data_.dataRate() / Stk::sampleRate() ); + + if ( doNormalize & !chunking_ ) this->normalize(); + + this->reset(); +} + +void FileWvIn :: reset(void) +{ + time_ = (StkFloat) 0.0; + for ( unsigned int i=0; inormalize( 1.0 ); +} + +// Normalize all channels equally by the greatest magnitude in all of the data. +void FileWvIn :: normalize( StkFloat peak ) +{ + // When chunking, the "normalization" scaling is performed by FileRead. + if ( chunking_ ) return; + + size_t i; + StkFloat max = 0.0; + + for ( i=0; i max ) + max = (StkFloat) fabs((double) data_[i]); + } + + if ( max > 0.0 ) { + max = 1.0 / max; + max *= peak; + for ( i=0; i fileSize_ - 1.0 ) { + time_ = fileSize_ - 1.0; + for ( unsigned int i=0; i= data_.channels() ) { + oStream_ << "FileWvIn::tick(): channel argument and soundfile data are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + if ( finished_ ) return 0.0; + + if ( time_ < 0.0 || time_ > (StkFloat) ( fileSize_ - 1.0 ) ) { + for ( unsigned int i=0; i (StkFloat) ( chunkPointer_ + chunkSize_ - 1 ) ) ) { + + while ( time_ < (StkFloat) chunkPointer_ ) { // negative rate + chunkPointer_ -= chunkSize_ - 1; // overlap chunks by one frame + if ( chunkPointer_ < 0 ) chunkPointer_ = 0; + } + while ( time_ > (StkFloat) ( chunkPointer_ + chunkSize_ - 1 ) ) { // positive rate + chunkPointer_ += chunkSize_ - 1; // overlap chunks by one frame + if ( chunkPointer_ + chunkSize_ > fileSize_ ) // at end of file + chunkPointer_ = fileSize_ - chunkSize_; + } + + // Load more data. + file_.read( data_, chunkPointer_, int2floatscaling_ ); + } + + // Adjust index for the current buffer. + tyme -= chunkPointer_; + } + + if ( interpolate_ ) { + for ( unsigned int i=0; i frames.channels() - nChannels ) { + oStream_ << "FileWvIn::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int j, hop = frames.channels() - nChannels; + if ( nChannels == 1 ) { + for ( unsigned int i=0; iopenFile( fileName, nChannels, type, format ); +} + +FileWvOut :: ~FileWvOut() +{ + this->closeFile(); +} + +void FileWvOut :: closeFile( void ) +{ + if ( file_.isOpen() ) { + + // Output any remaining samples in the buffer before closing. + if ( bufferIndex_ > 0 ) { + data_.resize( bufferIndex_, data_.channels() ); + file_.write( data_ ); + } + + file_.close(); + frameCounter_ = 0; + } +} + +void FileWvOut :: openFile( std::string fileName, + unsigned int nChannels, + FileWrite::FILE_TYPE type, + Stk::StkFormat format ) +{ + closeFile(); + + if ( nChannels < 1 ) { + oStream_ << "FileWvOut::openFile: the channels argument must be greater than zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + // An StkError can be thrown by the FileWrite class here. + file_.open( fileName, nChannels, type, format ); + + // Allocate new memory if necessary. + data_.resize( bufferFrames_, nChannels ); + + bufferIndex_ = 0; + iData_ = 0; +} + +void FileWvOut :: incrementFrame( void ) +{ + frameCounter_++; + bufferIndex_++; + + if ( bufferIndex_ == bufferFrames_ ) { + file_.write( data_ ); + bufferIndex_ = 0; + iData_ = 0; + } +} + +void FileWvOut :: tick( const StkFloat sample ) +{ +#if defined(_STK_DEBUG_) + if ( !file_.isOpen() ) { + oStream_ << "FileWvOut::tick(): no file open!"; + handleError( StkError::WARNING ); + return; + } +#endif + + unsigned int nChannels = data_.channels(); + StkFloat input = sample; + clipTest( input ); + for ( unsigned int j=0; jincrementFrame(); +} + +void FileWvOut :: tick( const StkFrames& frames ) +{ +#if defined(_STK_DEBUG_) + if ( !file_.isOpen() ) { + oStream_ << "FileWvOut::tick(): no file open!"; + handleError( StkError::WARNING ); + return; + } + + if ( data_.channels() != frames.channels() ) { + oStream_ << "FileWvOut::tick(): incompatible channel value in StkFrames argument!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + unsigned int iFrames = 0; + unsigned int j, nChannels = data_.channels(); + for ( unsigned int i=0; iincrementFrame(); + } +} + +} // stk namespace diff --git a/7.x/src/stk/src/Fir.cpp b/7.x/src/stk/src/Fir.cpp new file mode 100644 index 0000000..42c4b7f --- /dev/null +++ b/7.x/src/stk/src/Fir.cpp @@ -0,0 +1,74 @@ +/***************************************************/ +/*! \class Fir + \brief STK general finite impulse response filter class. + + This class provides a generic digital filter structure that can be + used to implement FIR filters. For filters with feedback terms, + the Iir class should be used. + + In particular, this class implements the standard difference + equation: + + y[n] = b[0]*x[n] + ... + b[nb]*x[n-nb] + + The \e gain parameter is applied at the filter input and does not + affect the coefficient values. The default gain value is 1.0. + This structure results in one extra multiply per computed sample, + but allows easy control of the overall filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Fir.h" +#include + +namespace stk { + +Fir :: Fir() +{ + // The default constructor should setup for pass-through. + b_.push_back( 1.0 ); + + inputs_.resize( 1, 1, 0.0 ); +} + +Fir :: Fir( std::vector &coefficients ) +{ + // Check the arguments. + if ( coefficients.size() == 0 ) { + oStream_ << "Fir: coefficient vector must have size > 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + gain_ = 1.0; + b_ = coefficients; + + inputs_.resize( b_.size(), 1, 0.0 ); + this->clear(); +} + +Fir :: ~Fir() +{ +} + +void Fir :: setCoefficients( std::vector &coefficients, bool clearState ) +{ + // Check the argument. + if ( coefficients.size() == 0 ) { + oStream_ << "Fir::setCoefficients: coefficient vector must have size > 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( b_.size() != coefficients.size() ) { + b_ = coefficients; + inputs_.resize( b_.size(), 1, 0.0 ); + } + else { + for ( unsigned int i=0; iclear(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Flute.cpp b/7.x/src/stk/src/Flute.cpp new file mode 100644 index 0000000..887a472 --- /dev/null +++ b/7.x/src/stk/src/Flute.cpp @@ -0,0 +1,163 @@ +/***************************************************/ +/*! \Class Flute + \brief STK flute physical model class. + + This class implements a simple flute + physical model, as discussed by Karjalainen, + Smith, Waryznyk, etc. The jet model uses + a polynomial, a la Cook. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Jet Delay = 2 + - Noise Gain = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Flute.h" +#include "SKINImsg.h" + +namespace stk { + +Flute :: Flute( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Flute::Flute: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / lowestFrequency ); + boreDelay_.setMaximumDelay( nDelays + 1 ); + + jetDelay_.setMaximumDelay( nDelays + 1 ); + jetDelay_.setDelay( 49.0 ); + + vibrato_.setFrequency( 5.925 ); + filter_.setPole( 0.7 - ( 0.1 * 22050.0 / Stk::sampleRate() ) ); + dcBlock_.setBlockZero(); + + adsr_.setAllTimes( 0.005, 0.01, 0.8, 0.010 ); + endReflection_ = 0.5; + jetReflection_ = 0.5; + noiseGain_ = 0.15; // Breath pressure random component + vibratoGain_ = 0.05; // Breath periodic vibrato component + jetRatio_ = 0.32; + + maxPressure_ = 0.0; + this->clear(); + this->setFrequency( 220.0 ); +} + +Flute :: ~Flute( void ) +{ +} + +void Flute :: clear( void ) +{ + jetDelay_.clear(); + boreDelay_.clear(); + filter_.clear(); + dcBlock_.clear(); +} + +void Flute :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Flute::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // We're overblowing here. + lastFrequency_ = frequency * 0.66666; + + // Account for filter delay and one sample "lastOut" delay + // (previously approximated as 2.0 samples). The tuning is still + // not perfect but I'm not sure why. Also, we are not accounting + // for the dc blocking filter delay. + StkFloat delay = Stk::sampleRate() / lastFrequency_ - filter_.phaseDelay( lastFrequency_ ) - 1.0; + + boreDelay_.setDelay( delay ); + jetDelay_.setDelay( delay * jetRatio_ ); +} + +void Flute :: setJetDelay( StkFloat aRatio ) +{ + jetRatio_ = aRatio; + jetDelay_.setDelay( boreDelay_.getDelay() * aRatio ); // Scaled by ratio. +} + +void Flute :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Flute::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setAttackRate( rate ); + maxPressure_ = amplitude / (StkFloat) 0.8; + adsr_.keyOn(); +} + +void Flute :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Flute::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setReleaseRate( rate ); + adsr_.keyOff(); +} + +void Flute :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( 1.1 + (amplitude * 0.20), amplitude * 0.02 ); + outputGain_ = amplitude + 0.001; +} + +void Flute :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.02 ); +} + + +void Flute :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Flute::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_JetDelay_) // 2 + this->setJetDelay( (StkFloat) (0.08 + (0.48 * normalizedValue)) ); + else if (number == __SK_NoiseLevel_) // 4 + noiseGain_ = ( normalizedValue * 0.4); + else if (number == __SK_ModFrequency_) // 11 + vibrato_.setFrequency( normalizedValue * 12.0); + else if (number == __SK_ModWheel_) // 1 + vibratoGain_ = ( normalizedValue * 0.4 ); + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Flute::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/FormSwep.cpp b/7.x/src/stk/src/FormSwep.cpp new file mode 100644 index 0000000..dd8abaa --- /dev/null +++ b/7.x/src/stk/src/FormSwep.cpp @@ -0,0 +1,136 @@ +/***************************************************/ +/*! \class FormSwep + \brief STK sweepable formant filter class. + + This class implements a formant (resonance) which can be "swept" + over time from one frequency setting to another. It provides + methods for controlling the sweep rate and target frequency. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "FormSwep.h" +#include + +namespace stk { + +FormSwep :: FormSwep( void ) +{ + frequency_ = 0.0; + radius_ = 0.0; + targetGain_ = 1.0; + targetFrequency_ = 0.0; + targetRadius_ = 0.0; + deltaGain_ = 0.0; + deltaFrequency_ = 0.0; + deltaRadius_ = 0.0; + sweepState_ = 0.0; + sweepRate_ = 0.002; + dirty_ = false; + + b_.resize( 3, 0.0 ); + a_.resize( 3, 0.0 ); + a_[0] = 1.0; + inputs_.resize( 3, 1, 0.0 ); + outputs_.resize( 3, 1, 0.0 ); + + Stk::addSampleRateAlert( this ); +} + +FormSwep :: ~FormSwep() +{ + Stk::removeSampleRateAlert( this ); +} + +void FormSwep :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + oStream_ << "FormSwep::sampleRateChanged: you may need to recompute filter coefficients!"; + handleError( StkError::WARNING ); + } +} + +void FormSwep :: setResonance( StkFloat frequency, StkFloat radius ) +{ +#if defined(_STK_DEBUG_) + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "FormSwep::setResonance: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 || radius >= 1.0 ) { + oStream_ << "FormSwep::setResonance: radius argument (" << radius << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + radius_ = radius; + frequency_ = frequency; + + a_[2] = radius * radius; + a_[1] = -2.0 * radius * cos( TWO_PI * frequency / Stk::sampleRate() ); + + // Use zeros at +- 1 and normalize the filter peak gain. + b_[0] = 0.5 - 0.5 * a_[2]; + b_[1] = 0.0; + b_[2] = -b_[0]; +} + +void FormSwep :: setStates( StkFloat frequency, StkFloat radius, StkFloat gain ) +{ + dirty_ = false; + + if ( frequency_ != frequency || radius_ != radius ) + this->setResonance( frequency, radius ); + + gain_ = gain; + targetFrequency_ = frequency; + targetRadius_ = radius; + targetGain_ = gain; +} + +void FormSwep :: setTargets( StkFloat frequency, StkFloat radius, StkFloat gain ) +{ + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "FormSwep::setTargets: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 || radius >= 1.0 ) { + oStream_ << "FormSwep::setTargets: radius argument (" << radius << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + + dirty_ = true; + startFrequency_ = frequency_; + startRadius_ = radius_; + startGain_ = gain_; + targetFrequency_ = frequency; + targetRadius_ = radius; + targetGain_ = gain; + deltaFrequency_ = frequency - frequency_; + deltaRadius_ = radius - radius_; + deltaGain_ = gain - gain_; + sweepState_ = 0.0; +} + +void FormSwep :: setSweepRate( StkFloat rate ) +{ + if ( rate < 0.0 || rate > 1.0 ) { + oStream_ << "FormSwep::setSweepRate: argument (" << rate << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + + sweepRate_ = rate; +} + +void FormSwep :: setSweepTime( StkFloat time ) +{ + if ( time <= 0.0 ) { + oStream_ << "FormSwep::setSweepTime: argument (" << time << ") must be > 0.0!"; + handleError( StkError::WARNING ); return; + } + + this->setSweepRate( 1.0 / ( time * Stk::sampleRate() ) ); +} + +} // stk namespace diff --git a/7.x/src/stk/src/FreeVerb.cpp b/7.x/src/stk/src/FreeVerb.cpp new file mode 100644 index 0000000..2461777 --- /dev/null +++ b/7.x/src/stk/src/FreeVerb.cpp @@ -0,0 +1,227 @@ +/***********************************************************************/ +/*! \class FreeVerb + \brief Jezar at Dreampoint's FreeVerb, implemented in STK. + + Freeverb is a free and open-source Schroeder reverberator + originally implemented in C++. The parameters of the reverberation + model are exceptionally well tuned. FreeVerb uses 8 + lowpass-feedback-comb-filters in parallel, followed by 4 Schroeder + allpass filters in series. The input signal can be either mono or + stereo, and the output signal is stereo. The delay lengths are + optimized for a sample rate of 44100 Hz. + + Ported to STK by Gregory Burlet, 2012. +*/ +/***********************************************************************/ + +#include "FreeVerb.h" +#include +#include + +using namespace stk; + +// Set static delay line lengths +const StkFloat FreeVerb::fixedGain = 0.015; +const StkFloat FreeVerb::scaleWet = 3; +const StkFloat FreeVerb::scaleDry = 2; +const StkFloat FreeVerb::scaleDamp = 0.4; +const StkFloat FreeVerb::scaleRoom = 0.28; +const StkFloat FreeVerb::offsetRoom = 0.7; +int FreeVerb::cDelayLengths[] = {1617, 1557, 1491, 1422, 1356, 1277, 1188, 1116}; +int FreeVerb::aDelayLengths[] = {225, 556, 441, 341}; + +FreeVerb::FreeVerb( void ) +{ + // Resize lastFrame_ for stereo output + lastFrame_.resize( 1, 2, 0.0 ); + + // Initialize parameters + Effect::setEffectMix( 0.75 ); // set initially to 3/4 wet 1/4 dry signal (different than original freeverb) + roomSizeMem_ = (0.75 * scaleRoom) + offsetRoom; // feedback attenuation in LBFC + dampMem_ = 0.25 * scaleDamp; // pole of lowpass filters in the LBFC + width_ = 1.0; + frozenMode_ = false; + update(); + + gain_ = fixedGain; // input gain before sending to filters + g_ = 0.5; // allpass coefficient, immutable in FreeVerb + + // Scale delay line lengths according to the current sampling rate + double fsScale = Stk::sampleRate() / 44100.0; + if ( fsScale != 1.0 ) { + // scale comb filter delay lines + for ( int i = 0; i < nCombs; i++ ) { + cDelayLengths[i] = (int) floor(fsScale * cDelayLengths[i]); + } + + // Scale allpass filter delay lines + for ( int i = 0; i < nAllpasses; i++ ) { + aDelayLengths[i] = (int) floor(fsScale * aDelayLengths[i]); + } + } + + // Initialize delay lines for the LBFC filters + for ( int i = 0; i < nCombs; i++ ) { + combDelayL_[i].setMaximumDelay( cDelayLengths[i] ); + combDelayL_[i].setDelay( cDelayLengths[i] ); + combDelayR_[i].setMaximumDelay( cDelayLengths[i] + stereoSpread ); + combDelayR_[i].setDelay( cDelayLengths[i] + stereoSpread ); + } + + // initialize delay lines for the allpass filters + for (int i = 0; i < nAllpasses; i++) { + allPassDelayL_[i].setMaximumDelay( aDelayLengths[i] ); + allPassDelayL_[i].setDelay( aDelayLengths[i] ); + allPassDelayR_[i].setMaximumDelay( aDelayLengths[i] + stereoSpread ); + allPassDelayR_[i].setDelay( aDelayLengths[i] + stereoSpread ); + } +} + +FreeVerb::~FreeVerb() +{ +} + +void FreeVerb::setEffectMix( StkFloat mix ) +{ + Effect::setEffectMix( mix ); + update(); +} + +void FreeVerb::setRoomSize( StkFloat roomSize ) +{ + roomSizeMem_ = (roomSize * scaleRoom) + offsetRoom; + update(); +} + +StkFloat FreeVerb::getRoomSize() +{ + return (roomSizeMem_ - offsetRoom) / scaleRoom; +} + +void FreeVerb::setDamping( StkFloat damping ) +{ + dampMem_ = damping * scaleDamp; + update(); +} + +StkFloat FreeVerb::getDamping() +{ + return dampMem_ / scaleDamp; +} + +void FreeVerb::setWidth( StkFloat width ) +{ + width_ = width; + update(); +} + +StkFloat FreeVerb::getWidth() +{ + return width_; +} + +void FreeVerb::setMode( bool isFrozen ) +{ + frozenMode_ = isFrozen; + update(); +} + +StkFloat FreeVerb::getMode() +{ + return frozenMode_; +} + +void FreeVerb::update() +{ + StkFloat wet = scaleWet * effectMix_; + dry_ = scaleDry * (1.0-effectMix_); + + // Use the L1 norm so the output gain will sum to one while still + // preserving the ratio of scalings in original FreeVerb + wet /= (wet + dry_); + dry_ /= (wet + dry_); + + wet1_ = wet * (width_/2.0 + 0.5); + wet2_ = wet * (1.0 - width_)/2.0; + + if ( frozenMode_ ) { + // put into freeze mode + roomSize_ = 1.0; + damp_ = 0.0; + gain_ = 0.0; + } + else { + roomSize_ = roomSizeMem_; + damp_ = dampMem_; + gain_ = fixedGain; + } + + for ( int i=0; i= frames.channels() - 1 ) { + oStream_ << "FreeVerb::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() - 1 ) { + oStream_ << "FreeVerb::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(); + unsigned int oHop = oFrames.channels(); + bool stereoInput = ( iFrames.channels() > iChannel+1 ) ? true : false; + for ( unsigned int i=0; i + +namespace stk { + +Granulate :: Granulate( void ) +{ + this->setGrainParameters(); // use default values + this->setRandomFactor(); + gStretch_ = 0; + stretchCounter_ = 0; + gain_ = 1.0; +} + +Granulate :: Granulate( unsigned int nVoices, std::string fileName, bool typeRaw ) +{ + this->setGrainParameters(); // use default values + this->setRandomFactor(); + gStretch_ = 0; + stretchCounter_ = 0; + this->openFile( fileName, typeRaw ); + this->setVoices( nVoices ); +} + +Granulate :: ~Granulate( void ) +{ +} + +void Granulate :: setStretch( unsigned int stretchFactor ) +{ + if ( stretchFactor <= 1 ) + gStretch_ = 0; + else if ( gStretch_ >= 1000 ) + gStretch_ = 1000; + else + gStretch_ = stretchFactor - 1; +} + +void Granulate :: setGrainParameters( unsigned int duration, unsigned int rampPercent, + int offset, unsigned int delay ) +{ + gDuration_ = duration; + if ( gDuration_ == 0 ) { + gDuration_ = 1; + oStream_ << "Granulate::setGrainParameters: duration argument cannot be zero ... setting to 1 millisecond."; + handleError( StkError::WARNING ); + } + + gRampPercent_ = rampPercent; + if ( gRampPercent_ > 100 ) { + gRampPercent_ = 100; + oStream_ << "Granulate::setGrainParameters: rampPercent argument cannot be greater than 100 ... setting to 100."; + handleError( StkError::WARNING ); + } + + gOffset_ = offset; + gDelay_ = delay; +} + +void Granulate :: setRandomFactor( StkFloat randomness ) +{ + if ( randomness < 0.0 ) gRandomFactor_ = 0.0; + else if ( randomness > 1.0 ) gRandomFactor_ = 0.97; + else gRandomFactor_ = 0.97 * randomness; +} + +void Granulate :: openFile( std::string fileName, bool typeRaw ) +{ + // Attempt to load the soundfile data. + FileRead file( fileName, typeRaw ); + data_.resize( file.fileSize(), file.channels() ); + file.read( data_ ); + lastFrame_.resize( 1, file.channels(), 0.0 ); + + this->reset(); + +#if defined(_STK_DEBUG_) + std::ostringstream message; + message << "Granulate::openFile: file = " << fileName << ", file frames = " << file.fileSize() << '.'; + handleError( message.str(), StkError::DEBUG_PRINT ); +#endif + +} + +void Granulate :: reset( void ) +{ + gPointer_ = 0; + + // Reset grain parameters. + size_t count; + size_t nVoices = (unsigned int)grains_.size(); + for ( unsigned int i=0; icalculateGrain( grains_[i] ); + break; + + case GRAIN_FADEIN: + // We're done ramping up the envelope + if ( grains_[i].sustainCount > 0 ) { + grains_[i].counter = grains_[i].sustainCount; + grains_[i].state = GRAIN_SUSTAIN; + break; + } + // else no sustain state (i.e. perfect triangle window) + + case GRAIN_SUSTAIN: + // We're done with flat part of envelope ... setup to ramp down + if ( grains_[i].decayCount > 0 ) { + grains_[i].counter = grains_[i].decayCount; + grains_[i].eRate = -grains_[i].eRate; + grains_[i].state = GRAIN_FADEOUT; + break; + } + // else no fade out state (gRampPercent = 0) + + case GRAIN_FADEOUT: + // We're done ramping down ... setup for wait between grains + if ( grains_[i].delayCount > 0 ) { + grains_[i].counter = grains_[i].delayCount; + grains_[i].state = GRAIN_STOPPED; + break; + } + // else no delay (gDelay = 0) + + this->calculateGrain( grains_[i] ); + } + } + + // Accumulate the grain outputs. + if ( grains_[i].state > 0 ) { + for ( j=0; j= data_.frames() ) + grains_[i].pointer = 0; + } + + // Decrement counter for all states. + grains_[i].counter--; + } + + // Increment our global file pointer at the stretch rate. + if ( stretchCounter_++ == gStretch_ ) { + gPointer_++; + if ( (unsigned long) gPointer_ >= data_.frames() ) gPointer_ = 0; + stretchCounter_ = 0; + } + + return lastFrame_[channel]; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Guitar.cpp b/7.x/src/stk/src/Guitar.cpp new file mode 100644 index 0000000..2218348 --- /dev/null +++ b/7.x/src/stk/src/Guitar.cpp @@ -0,0 +1,244 @@ +/***************************************************/ +/*! \class Guitar + \brief STK guitar model class. + + This class implements a guitar model with an arbitrary number of + strings (specified during instantiation). Each string is + represented by an stk::Twang object. The model supports commuted + synthesis, as discussed by Smith and Karjalainen. It also includes + a basic body coupling model and supports feedback. + + This class does not attempt voice management. Rather, most + functions support a parameter to specify a particular string + number and string (voice) management is assumed to occur + externally. Note that this class does not inherit from + stk::Instrmnt because of API inconsistencies. + + This is a digital waveguide model, making its use possibly subject + to patents held by Stanford University, Yamaha, and others. + + Control Change Numbers: + - Bridge Coupling Gain = 2 + - Pluck Position = 4 + - Loop Gain = 11 + - Coupling Filter Pole = 1 + - Pick Filter Pole = 128 + + by Gary P. Scavone, 2012. +*/ +/***************************************************/ + +#include "Guitar.h" +#include "FileWvIn.h" +#include "Noise.h" +#include "SKINImsg.h" +#include + +namespace stk { + +#define BASE_COUPLING_GAIN 0.01 + +Guitar :: Guitar( unsigned int nStrings, std::string bodyfile ) +{ + strings_.resize( nStrings ); + stringState_.resize( nStrings, 0 ); + decayCounter_.resize( nStrings, 0 ); + filePointer_.resize( nStrings, 0 ); + pluckGains_.resize( nStrings, 0 ); + + setBodyFile( bodyfile ); + + couplingGain_ = BASE_COUPLING_GAIN; + couplingFilter_.setPole( 0.9 ); + pickFilter_.setPole( 0.95 ); + lastFrame_.resize(1, 1, 0.0); +} + +void Guitar :: clear( void ) +{ + for ( unsigned int i=0; i 1.0 ) { + oStream_ << "Guitar::setPluckPosition: position parameter out of range!"; + handleError( StkError::WARNING ); return; + } + + if ( string >= (int) strings_.size() ) { + oStream_ << "Guitar::setPluckPosition: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } + + if ( string < 0 ) // set all strings + for ( unsigned int i=0; i 1.0 ) { + oStream_ << "Guitar::setLoopGain: gain parameter out of range!"; + handleError( StkError::WARNING ); return; + } + + if ( string >= (int) strings_.size() ) { + oStream_ << "Guitar::setLoopGain: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } + + if ( string < 0 ) // set all strings + for ( unsigned int i=0; i= strings_.size() ) { + oStream_ << "Guitar::setFrequency: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } +#endif + + strings_[string].setFrequency( frequency ); +} + +void Guitar :: noteOn( StkFloat frequency, StkFloat amplitude, unsigned int string ) +{ +#if defined(_STK_DEBUG_) + if ( string >= strings_.size() ) { + oStream_ << "Guitar::noteOn: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } + + if ( Stk::inRange( amplitude, 0.0, 1.0 ) == false ) { + oStream_ << "Guitar::noteOn: amplitude parameter is outside range 0.0 - 1.0!"; + handleError( StkError::WARNING ); return; + } +#endif + + this->setFrequency( frequency, string ); + stringState_[string] = 2; + filePointer_[string] = 0; + strings_[string].setLoopGain( 0.995 ); + pluckGains_[string] = amplitude; +} + +void Guitar :: noteOff( StkFloat amplitude, unsigned int string ) +{ +#if defined(_STK_DEBUG_) + if ( string >= strings_.size() ) { + oStream_ << "Guitar::noteOff: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } + + if ( Stk::inRange( amplitude, 0.0, 1.0 ) == false ) { + oStream_ << "Guitar::noteOff: amplitude parameter is outside range 0.0 - 1.0!"; + handleError( StkError::WARNING ); return; + } +#endif + + strings_[string].setLoopGain( (1.0 - amplitude) * 0.9 ); + stringState_[string] = 1; +} + +void Guitar :: controlChange( int number, StkFloat value, int string ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Guitar::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + + if ( string > 0 && string >= (int) strings_.size() ) { + oStream_ << "Guitar::controlChange: string parameter is greater than number of strings!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == 2 ) + couplingGain_ = 1.5 * BASE_COUPLING_GAIN * normalizedValue; + else if ( number == __SK_PickPosition_ ) // 4 + this->setPluckPosition( normalizedValue, string ); + else if ( number == __SK_StringDamping_ ) // 11 + this->setLoopGain( 0.97 + (normalizedValue * 0.03), string ); + else if ( number == __SK_ModWheel_ ) // 1 + couplingFilter_.setPole( 0.98 * normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) // 128 + pickFilter_.setPole( 0.95 * normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Guitar::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/HevyMetl.cpp b/7.x/src/stk/src/HevyMetl.cpp new file mode 100644 index 0000000..e295fbb --- /dev/null +++ b/7.x/src/stk/src/HevyMetl.cpp @@ -0,0 +1,75 @@ +/***************************************************/ +/*! \class HevyMetl + \brief STK heavy metal FM synthesis instrument. + + This class implements 3 cascade operators with + feedback modulation, also referred to as + algorithm 3 of the TX81Z. + + Algorithm 3 is : 4--\ + 3-->2-- + -->1-->Out + + Control Change Numbers: + - Total Modulator Index = 2 + - Modulator Crossfade = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "HevyMetl.h" + +namespace stk { + +HevyMetl :: HevyMetl( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 1.0 * 1.000); + this->setRatio(1, 4.0 * 0.999); + this->setRatio(2, 3.0 * 1.001); + this->setRatio(3, 0.5 * 1.002); + + gains_[0] = fmGains_[92]; + gains_[1] = fmGains_[76]; + gains_[2] = fmGains_[91]; + gains_[3] = fmGains_[68]; + + adsr_[0]->setAllTimes( 0.001, 0.001, 1.0, 0.01); + adsr_[1]->setAllTimes( 0.001, 0.010, 1.0, 0.50); + adsr_[2]->setAllTimes( 0.010, 0.005, 1.0, 0.20); + adsr_[3]->setAllTimes( 0.030, 0.010, 0.2, 0.20); + + twozero_.setGain( 2.0 ); + vibrato_.setFrequency( 5.5 ); + modDepth_ = 0.0; +} + +HevyMetl :: ~HevyMetl( void ) +{ +} + +void HevyMetl :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[92]; + gains_[1] = amplitude * fmGains_[76]; + gains_[2] = amplitude * fmGains_[91]; + gains_[3] = amplitude * fmGains_[68]; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Iir.cpp b/7.x/src/stk/src/Iir.cpp new file mode 100644 index 0000000..d4a6936 --- /dev/null +++ b/7.x/src/stk/src/Iir.cpp @@ -0,0 +1,125 @@ +/***************************************************/ +/*! \class Iir + \brief STK general infinite impulse response filter class. + + This class provides a generic digital filter structure that can be + used to implement IIR filters. For filters containing only + feedforward terms, the Fir class is slightly more efficient. + + In particular, this class implements the standard difference + equation: + + a[0]*y[n] = b[0]*x[n] + ... + b[nb]*x[n-nb] - + a[1]*y[n-1] - ... - a[na]*y[n-na] + + If a[0] is not equal to 1, the filter coeffcients are normalized + by a[0]. + + The \e gain parameter is applied at the filter input and does not + affect the coefficient values. The default gain value is 1.0. + This structure results in one extra multiply per computed sample, + but allows easy control of the overall filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Iir.h" + +namespace stk { + +Iir :: Iir() +{ + // The default constructor should setup for pass-through. + b_.push_back( 1.0 ); + a_.push_back( 1.0 ); + + inputs_.resize( 1, 1, 0.0 ); + outputs_.resize( 1, 1, 0.0 ); +} + +Iir :: Iir( std::vector &bCoefficients, std::vector &aCoefficients ) +{ + // Check the arguments. + if ( bCoefficients.size() == 0 || aCoefficients.size() == 0 ) { + oStream_ << "Iir: a and b coefficient vectors must both have size > 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( aCoefficients[0] == 0.0 ) { + oStream_ << "Iir: a[0] coefficient cannot == 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + gain_ = 1.0; + b_ = bCoefficients; + a_ = aCoefficients; + + inputs_.resize( b_.size(), 1, 0.0 ); + outputs_.resize( a_.size(), 1, 0.0 ); + this->clear(); +} + +Iir :: ~Iir() +{ +} + +void Iir :: setCoefficients( std::vector &bCoefficients, std::vector &aCoefficients, bool clearState ) +{ + this->setNumerator( bCoefficients, false ); + this->setDenominator( aCoefficients, false ); + + if ( clearState ) this->clear(); +} + +void Iir :: setNumerator( std::vector &bCoefficients, bool clearState ) +{ + // Check the argument. + if ( bCoefficients.size() == 0 ) { + oStream_ << "Iir::setNumerator: coefficient vector must have size > 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( b_.size() != bCoefficients.size() ) { + b_ = bCoefficients; + inputs_.resize( b_.size(), 1, 0.0 ); + } + else { + for ( unsigned int i=0; iclear(); +} + +void Iir :: setDenominator( std::vector &aCoefficients, bool clearState ) +{ + // Check the argument. + if ( aCoefficients.size() == 0 ) { + oStream_ << "Iir::setDenominator: coefficient vector must have size > 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( aCoefficients[0] == 0.0 ) { + oStream_ << "Iir::setDenominator: a[0] coefficient cannot == 0!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + if ( a_.size() != aCoefficients.size() ) { + a_ = aCoefficients; + outputs_.resize( a_.size(), 1, 0.0 ); + } + else { + for ( unsigned int i=0; iclear(); + + // Scale coefficients by a[0] if necessary + if ( a_[0] != 1.0 ) { + unsigned int i; + for ( i=0; i + +namespace stk { + +JCRev :: JCRev( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "JCRev::JCRev: argument (" << T60 << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + lastFrame_.resize( 1, 2, 0.0 ); // resize lastFrame_ for stereo output + + // Delay lengths for 44100 Hz sample rate. + int lengths[9] = {1116, 1356, 1422, 1617, 225, 341, 441, 211, 179}; + double scaler = Stk::sampleRate() / 44100.0; + + int delay, i; + if ( scaler != 1.0 ) { + for ( i=0; i<9; i++ ) { + delay = (int) floor( scaler * lengths[i] ); + if ( (delay & 1) == 0) delay++; + while ( !this->isPrime(delay) ) delay += 2; + lengths[i] = delay; + } + } + + for ( i=0; i<3; i++ ) { + allpassDelays_[i].setMaximumDelay( lengths[i+4] ); + allpassDelays_[i].setDelay( lengths[i+4] ); + } + + for ( i=0; i<4; i++ ) { + combDelays_[i].setMaximumDelay( lengths[i] ); + combDelays_[i].setDelay( lengths[i] ); + combFilters_[i].setPole( 0.2 ); + } + + this->setT60( T60 ); + outLeftDelay_.setMaximumDelay( lengths[7] ); + outLeftDelay_.setDelay( lengths[7] ); + outRightDelay_.setMaximumDelay( lengths[8] ); + outRightDelay_.setDelay( lengths[8] ); + allpassCoefficient_ = 0.7; + effectMix_ = 0.3; + this->clear(); +} + +void JCRev :: clear() +{ + allpassDelays_[0].clear(); + allpassDelays_[1].clear(); + allpassDelays_[2].clear(); + combDelays_[0].clear(); + combDelays_[1].clear(); + combDelays_[2].clear(); + combDelays_[3].clear(); + outRightDelay_.clear(); + outLeftDelay_.clear(); + lastFrame_[0] = 0.0; + lastFrame_[1] = 0.0; +} + +void JCRev :: setT60( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "JCRev::setT60: argument (" << T60 << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + for ( int i=0; i<4; i++ ) + combCoefficient_[i] = pow(10.0, (-3.0 * combDelays_[i].getDelay() / (T60 * Stk::sampleRate()))); +} + +StkFrames& JCRev :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() - 1 ) { + oStream_ << "JCRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() - 1 ) { + oStream_ << "JCRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; isetFrequency( 220.0 ); + this->setPluckPosition( 0.4 ); +} + +Mandolin :: ~Mandolin( void ) +{ +} + +void Mandolin :: clear( void ) +{ + strings_[0].clear(); + strings_[1].clear(); +} + +void Mandolin :: setPluckPosition( StkFloat position ) +{ + if ( position < 0.0 || position > 1.0 ) { + oStream_ << "Mandolin::setPluckPosition: position parameter out of range!"; + handleError( StkError::WARNING ); return; + } + + strings_[0].setPluckPosition( position ); + strings_[1].setPluckPosition( position ); +} + +void Mandolin :: setDetune( StkFloat detune ) +{ + if ( detune <= 0.0 ) { + oStream_ << "Mandolin::setDeturn: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + detuning_ = detune; + strings_[1].setFrequency( frequency_ * detuning_ ); +} + +void Mandolin :: setBodySize( StkFloat size ) +{ + // Scale the commuted body response by its sample rate (22050). + StkFloat rate = size * 22050.0 / Stk::sampleRate(); + for ( int i=0; i<12; i++ ) + soundfile_[i].setRate( rate ); +} + +void Mandolin :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Mandolin::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + frequency_ = frequency; + strings_[0].setFrequency( frequency_ ); + strings_[1].setFrequency( frequency_ * detuning_ ); +} + +void Mandolin :: pluck( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Mandolin::pluck: amplitude parameter out of range!"; + handleError( StkError::WARNING ); return; + } + + soundfile_[mic_].reset(); + pluckAmplitude_ = amplitude; + + //strings_[0].setLoopGain( 0.97 + pluckAmplitude_ * 0.03 ); + //strings_[1].setLoopGain( 0.97 + pluckAmplitude_ * 0.03 ); +} + +void Mandolin :: pluck( StkFloat amplitude, StkFloat position ) +{ + this->setPluckPosition( position ); + this->pluck( amplitude ); +} + +void Mandolin :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->pluck( amplitude ); +} + +void Mandolin :: noteOff( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Mandolin::noteOff: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + //strings_[0].setLoopGain( 0.97 + (1 - amplitude) * 0.03 ); + //strings_[1].setLoopGain( 0.97 + (1 - amplitude) * 0.03 ); +} + +void Mandolin :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Mandolin::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == __SK_BodySize_ ) // 2 + this->setBodySize( normalizedValue * 2.0 ); + else if ( number == __SK_PickPosition_ ) // 4 + this->setPluckPosition( normalizedValue ); + else if ( number == __SK_StringDamping_ ) { // 11 + strings_[0].setLoopGain( 0.97 + (normalizedValue * 0.03) ); + strings_[1].setLoopGain( 0.97 + (normalizedValue * 0.03) ); + } + else if ( number == __SK_StringDetune_ ) // 1 + this->setDetune( 1.0 - (normalizedValue * 0.1) ); + else if ( number == __SK_AfterTouch_Cont_ ) // 128 + mic_ = (int) (normalizedValue * 11.0); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Mandolin::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Mesh2D.cpp b/7.x/src/stk/src/Mesh2D.cpp new file mode 100644 index 0000000..d126f99 --- /dev/null +++ b/7.x/src/stk/src/Mesh2D.cpp @@ -0,0 +1,367 @@ +/***************************************************/ +/*! \class Mesh2D + \brief Two-dimensional rectilinear waveguide mesh class. + + This class implements a rectilinear, + two-dimensional digital waveguide mesh + structure. For details, see Van Duyne and + Smith, "Physical Modeling with the 2-D Digital + Waveguide Mesh", Proceedings of the 1993 + International Computer Music Conference. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - X Dimension = 2 + - Y Dimension = 4 + - Mesh Decay = 11 + - X-Y Input Position = 1 + + by Julius Smith, 2000--2002. + Revised by Gary Scavone for STK, 2002. +*/ +/***************************************************/ + +#include "Mesh2D.h" +#include "SKINImsg.h" + +namespace stk { + +Mesh2D :: Mesh2D( unsigned short nX, unsigned short nY ) +{ + if ( nX == 0.0 || nY == 0.0 ) { + oStream_ << "Mesh2D::Mesh2D: one or more argument is equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + this->setNX( nX ); + this->setNY( nY ); + + StkFloat pole = 0.05; + unsigned short i; + for ( i=0; iclearMesh(); + + counter_ = 0; + xInput_ = 0; + yInput_ = 0; +} + +Mesh2D :: ~Mesh2D( void ) +{ +} + +void Mesh2D :: clear( void ) +{ + this->clearMesh(); + + unsigned short i; + for ( i=0; i NXMAX ) { + oStream_ << "Mesh2D::setNX(" << lenX << "): Maximum length is " << NXMAX << '!'; + handleError( StkError::WARNING ); return; + } + + NX_ = lenX; +} + +void Mesh2D :: setNY( unsigned short lenY ) +{ + if ( lenY < 2 ) { + oStream_ << "Mesh2D::setNY(" << lenY << "): Minimum length is 2!"; + handleError( StkError::WARNING ); return; + } + else if ( lenY > NYMAX ) { + oStream_ << "Mesh2D::setNY(" << lenY << "): Maximum length is " << NXMAX << '!'; + handleError( StkError::WARNING ); return; + } + + NY_ = lenY; +} + +void Mesh2D :: setDecay( StkFloat decayFactor ) +{ + if ( decayFactor < 0.0 || decayFactor > 1.0 ) { + oStream_ << "Mesh2D::setDecay: decayFactor is out of range!"; + handleError( StkError::WARNING ); return; + } + + int i; + for ( i=0; i 1.0 ) { + oStream_ << "Mesh2D::setInputPosition xFactor value is out of range!"; + handleError( StkError::WARNING ); return; + } + + if ( yFactor < 0.0 || yFactor > 1.0 ) { + oStream_ << "Mesh2D::setInputPosition yFactor value is out of range!"; + handleError( StkError::WARNING ); return; + } + + xInput_ = (unsigned short) (xFactor * (NX_ - 1)); + yInput_ = (unsigned short) (yFactor * (NY_ - 1)); +} + +void Mesh2D :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + // Input at corner. + if ( counter_ & 1 ) { + vxp1_[xInput_][yInput_] += amplitude; + vyp1_[xInput_][yInput_] += amplitude; + } + else { + vxp_[xInput_][yInput_] += amplitude; + vyp_[xInput_][yInput_] += amplitude; + } +} + +void Mesh2D :: noteOff( StkFloat amplitude ) +{ + return; +} + +StkFloat Mesh2D :: inputTick( StkFloat input ) +{ + if ( counter_ & 1 ) { + vxp1_[xInput_][yInput_] += input; + vyp1_[xInput_][yInput_] += input; + lastFrame_[0] = tick1(); + } + else { + vxp_[xInput_][yInput_] += input; + vyp_[xInput_][yInput_] += input; + lastFrame_[0] = tick0(); + } + + counter_++; + return lastFrame_[0]; +} + +StkFloat Mesh2D :: tick( unsigned int ) +{ + lastFrame_[0] = ((counter_ & 1) ? this->tick1() : this->tick0()); + counter_++; + return lastFrame_[0]; +} + +const StkFloat VSCALE = 0.5; + +StkFloat Mesh2D :: tick0( void ) +{ + int x, y; + StkFloat outsamp = 0; + + // Update junction velocities. + for (x=0; xsetNX( (unsigned short) (normalizedValue * (NXMAX-2) + 2) ); + else if ( number == 4 ) // 4 + this->setNY( (unsigned short) (normalizedValue * (NYMAX-2) + 2) ); + else if ( number == 11 ) // 11 + this->setDecay( 0.9 + (normalizedValue * 0.1) ); + else if ( number == __SK_ModWheel_ ) // 1 + this->setInputPosition( normalizedValue, normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Mesh2D::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/MidiFileIn.cpp b/7.x/src/stk/src/MidiFileIn.cpp new file mode 100644 index 0000000..d5c8384 --- /dev/null +++ b/7.x/src/stk/src/MidiFileIn.cpp @@ -0,0 +1,353 @@ +/**********************************************************************/ +/*! \class MidiFileIn + \brief A standard MIDI file reading/parsing class. + + This class can be used to read events from a standard MIDI file. + Event bytes are copied to a C++ vector and must be subsequently + interpreted by the user. The function getNextMidiEvent() skips + meta and sysex events, returning only MIDI channel messages. + Event delta-times are returned in the form of "ticks" and a + function is provided to determine the current "seconds per tick". + Tempo changes are internally tracked by the class and reflected in + the values returned by the function getTickSeconds(). + + by Gary P. Scavone, 2003--2021. +*/ +/**********************************************************************/ + +#include "MidiFileIn.h" +#include +#include + +namespace stk { + +MidiFileIn :: MidiFileIn( std::string fileName ) +{ + // Attempt to open the file. + file_.open( fileName.c_str(), std::ios::in | std::ios::binary ); + if ( !file_ ) { + oStream_ << "MidiFileIn: error opening or finding file (" << fileName << ")."; + handleError( StkError::FILE_NOT_FOUND ); + } + + // Parse header info. + char chunkType[4]; + char buffer[4]; + SINT32 *length; + if ( !file_.read( chunkType, 4 ) ) goto error; + if ( !file_.read( buffer, 4 ) ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap32((unsigned char *)&buffer); +#endif + length = (SINT32 *) &buffer; + if ( strncmp( chunkType, "MThd", 4 ) || ( *length != 6 ) ) { + oStream_ << "MidiFileIn: file (" << fileName << ") does not appear to be a MIDI file!"; + handleError( StkError::FILE_UNKNOWN_FORMAT ); + } + + // Read the MIDI file format. + SINT16 *data; + if ( !file_.read( buffer, 2 ) ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&buffer); +#endif + data = (SINT16 *) &buffer; + if ( *data < 0 || *data > 2 ) { + oStream_ << "MidiFileIn: the file (" << fileName << ") format is invalid!"; + handleError( StkError::FILE_ERROR ); + } + format_ = *data; + + // Read the number of tracks. + if ( !file_.read( buffer, 2 ) ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&buffer); +#endif + if ( format_ == 0 && *data != 1 ) { + oStream_ << "MidiFileIn: invalid number of tracks (>1) for a file format = 0!"; + handleError( StkError::FILE_ERROR ); + } + nTracks_ = *data; + + // Read the beat division. + if ( !file_.read( buffer, 2 ) ) goto error; +#ifdef __LITTLE_ENDIAN__ + swap16((unsigned char *)&buffer); +#endif + division_ = (int) *data; + double tickrate; + usingTimeCode_ = false; + if ( *data & 0x8000 ) { + // Determine ticks per second from time-code formats. + signed char tmp = -(*data & 0xFF00)>>8; + tickrate = (double) tmp; + // If frames per second value is 29, it really should be 29.97. + if ( tickrate == 29.0 ) tickrate = 29.97; + tickrate *= (*data & 0x00FF); + usingTimeCode_ = true; + } + else { + tickrate = (double) (*data & 0x7FFF); // ticks per quarter note + } + + // Now locate the track offsets and lengths. If not using time + // code, we can initialize the "tick time" using a default tempo of + // 120 beats per minute. We will then check for tempo meta-events + // afterward. + unsigned int i; + for ( i=0; i event; + unsigned long value, count; + + // We need to temporarily change the usingTimeCode_ value here so + // that the getNextEvent() function doesn't try to check the tempo + // map (which we're creating here). + usingTimeCode_ = true; + count = getNextEvent( &event, 0 ); + while ( event.size() ) { + if ( ( event.size() == 6 ) && ( event[0] == 0xff ) && + ( event[1] == 0x51 ) && ( event[2] == 0x03 ) ) { + tempoEvent.count = count; + value = ( event[3] << 16 ) + ( event[4] << 8 ) + event[5]; + tempoEvent.tickSeconds = (double) (0.000001 * value / tickrate); + if ( count > tempoEvents_.back().count ) + tempoEvents_.push_back( tempoEvent ); + else + tempoEvents_.back() = tempoEvent; + } + count += getNextEvent( &event, 0 ); + } + rewindTrack( 0 ); + for ( unsigned int i=0; i= nTracks_ ) { + oStream_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ")."; + handleError( StkError::WARNING ); return; + } + + trackPointers_[track] = trackOffsets_[track]; + trackStatus_[track] = 0; + tickSeconds_[track] = tempoEvents_[0].tickSeconds; +} + +double MidiFileIn :: getTickSeconds( unsigned int track ) +{ + // Return the current tick value in seconds for the given track. + if ( track >= nTracks_ ) { + oStream_ << "MidiFileIn::getTickSeconds: invalid track argument (" << track << ")."; + handleError( StkError::WARNING ); return 0.0; + } + + return tickSeconds_[track]; +} + +unsigned long MidiFileIn :: getNextEvent( std::vector *event, unsigned int track ) +{ + // Fill the user-provided vector with the next event in the + // specified track (default = 0) and return the event delta time in + // ticks. This function assumes that the stored track pointer is + // positioned at the start of a track event. If the track has + // reached its end, the event vector size will be zero. + // + // If we have a format 0 or 2 file and we're not using timecode, we + // should check every meta-event for tempo changes and make + // appropriate updates to the tickSeconds_ parameter if so. + // + // If we have a format 1 file and we're not using timecode, keep a + // running sum of ticks for each track and update the tickSeconds_ + // parameter as needed based on the stored tempo map. + + event->clear(); + if ( track >= nTracks_ ) { + oStream_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ")."; + handleError( StkError::WARNING ); return 0; + } + + // Check for the end of the track. + if ( (trackPointers_[track] - trackOffsets_[track]) >= trackLengths_[track] ) + return 0; + + unsigned long ticks = 0, bytes = 0; + bool isTempoEvent = false; + + // Read the event delta time. + file_.seekg( trackPointers_[track], std::ios_base::beg ); + if ( !readVariableLength( &ticks ) ) goto error; + + // Parse the event stream to determine the event length. + unsigned char c; + if ( !file_.read( (char *)&c, 1 ) ) goto error; + switch ( c ) { + + case 0xFF: // A Meta-Event + unsigned long position; + trackStatus_[track] = 0; + event->push_back( c ); + if ( !file_.read( (char *)&c, 1 ) ) goto error; + event->push_back( c ); + if ( format_ != 1 && ( c == 0x51 ) ) isTempoEvent = true; + position = (unsigned long)file_.tellg(); + if ( !readVariableLength( &bytes ) ) goto error; + bytes += ( (unsigned long)file_.tellg() - position ); + file_.seekg( position, std::ios_base::beg ); + break; + + case 0xF0: + case 0xF7: // The start or continuation of a Sysex event + trackStatus_[track] = 0; + event->push_back( c ); + position = (unsigned long)file_.tellg(); + if ( !readVariableLength( &bytes ) ) goto error; + bytes += ( (unsigned long)file_.tellg() - position ); + file_.seekg( position, std::ios_base::beg ); + break; + + default: // Should be a MIDI channel event + if ( c & 0x80 ) { // MIDI status byte + if ( c > 0xF0 ) goto error; + trackStatus_[track] = c; + event->push_back( c ); + c &= 0xF0; + if ( (c == 0xC0) || (c == 0xD0) ) bytes = 1; + else bytes = 2; + } + else if ( trackStatus_[track] & 0x80 ) { // Running status + event->push_back( trackStatus_[track] ); + event->push_back( c ); + c = trackStatus_[track] & 0xF0; + if ( (c != 0xC0) && (c != 0xD0) ) bytes = 1; + } + else goto error; + + } + + // Read the rest of the event into the event vector. + unsigned long i; + for ( i=0; ipush_back( c ); + } + + if ( !usingTimeCode_ ) { + if ( isTempoEvent ) { + // Parse the tempo event and update tickSeconds_[track]. + double tickrate = (double) (division_ & 0x7FFF); + unsigned long value = ( event->at(3) << 16 ) + ( event->at(4) << 8 ) + event->at(5); + tickSeconds_[track] = (double) (0.000001 * value / tickrate); + } + + if ( format_ == 1 ) { + // Update track counter and check the tempo map. + trackCounters_[track] += ticks; + TempoChange tempoEvent = tempoEvents_[ trackTempoIndex_[track] ]; + if ( trackCounters_[track] >= tempoEvent.count && trackTempoIndex_[track] < tempoEvents_.size() - 1 ) { + trackTempoIndex_[track]++; + tickSeconds_[track] = tempoEvent.tickSeconds; + } + } + } + + // Save the current track pointer value. + trackPointers_[track] = (long)file_.tellg(); + + return ticks; + + error: + oStream_ << "MidiFileIn::getNextEvent: file read error!"; + handleError( StkError::FILE_ERROR ); + return 0; +} + +unsigned long MidiFileIn :: getNextMidiEvent( std::vector *midiEvent, unsigned int track ) +{ + // Fill the user-provided vector with the next MIDI event in the + // specified track (default = 0) and return the event delta time in + // ticks. Meta-Events preceeding this event are skipped and ignored. + if ( track >= nTracks_ ) { + oStream_ << "MidiFileIn::getNextMidiEvent: invalid track argument (" << track << ")."; + handleError( StkError::WARNING ); return 0; + } + + unsigned long ticks = getNextEvent( midiEvent, track ); + while ( midiEvent->size() && ( midiEvent->at(0) >= 0xF0 ) ) { + //for ( unsigned int i=0; isize(); i++ ) + //std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; + ticks = getNextEvent( midiEvent, track ); + } + + //for ( unsigned int i=0; isize(); i++ ) + //std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; + + return ticks; +} + +bool MidiFileIn :: readVariableLength( unsigned long *value ) +{ + // It is assumed that this function is called with the file read + // pointer positioned at the start of a variable-length value. The + // function returns "true" if the value is successfully parsed and + // "false" otherwise. + *value = 0; + char c; + + if ( !file_.read( &c, 1 ) ) return false; + *value = (unsigned long) c; + if ( *value & 0x80 ) { + *value &= 0x7f; + do { + if ( !file_.read( &c, 1 ) ) return false; + *value = ( *value << 7 ) + ( c & 0x7f ); + } while ( c & 0x80 ); + } + + return true; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Modal.cpp b/7.x/src/stk/src/Modal.cpp new file mode 100644 index 0000000..f096abd --- /dev/null +++ b/7.x/src/stk/src/Modal.cpp @@ -0,0 +1,169 @@ +/***************************************************/ +/*! \class Modal + \brief STK resonance model abstract base class. + + This class contains an excitation wavetable, + an envelope, an oscillator, and N resonances + (non-sweeping BiQuad filters), where N is set + during instantiation. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Modal.h" +#include + +namespace stk { + +Modal :: Modal( unsigned int modes ) + : nModes_(modes) +{ + if ( nModes_ == 0 ) { + oStream_ << "Modal: 'modes' argument to constructor is zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + // We don't make the excitation wave here yet, because we don't know + // what it's going to be. + + ratios_.resize( nModes_ ); + radii_.resize( nModes_ ); + filters_ = (BiQuad **) calloc( nModes_, sizeof(BiQuad *) ); + for (unsigned int i=0; isetEqualGainZeroes(); + } + + // Set some default values. + vibrato_.setFrequency( 6.0 ); + vibratoGain_ = 0.0; + directGain_ = 0.0; + masterGain_ = 1.0; + baseFrequency_ = 440.0; + + this->clear(); + + stickHardness_ = 0.5; + strikePosition_ = 0.561; +} + +Modal :: ~Modal( void ) +{ + for ( unsigned int i=0; iclear(); +} + +void Modal :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Modal::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency; + for ( unsigned int i=0; isetRatioAndRadius( i, ratios_[i], radii_[i] ); +} + +void Modal :: setRatioAndRadius( unsigned int modeIndex, StkFloat ratio, StkFloat radius ) +{ + if ( modeIndex >= nModes_ ) { + oStream_ << "Modal::setRatioAndRadius: modeIndex parameter is greater than number of modes!"; + handleError( StkError::WARNING ); return; + } + + StkFloat nyquist = Stk::sampleRate() / 2.0; + StkFloat temp; + + if ( ratio * baseFrequency_ < nyquist ) { + ratios_[modeIndex] = ratio; + } + else { + temp = ratio; + while (temp * baseFrequency_ > nyquist) temp *= 0.5; + ratios_[modeIndex] = temp; +#if defined(_STK_DEBUG_) + oStream_ << "Modal::setRatioAndRadius: aliasing would occur here ... correcting."; + handleError( StkError::DEBUG_PRINT ); +#endif + } + radii_[modeIndex] = radius; + if (ratio < 0) + temp = -ratio; + else + temp = ratio * baseFrequency_; + + filters_[modeIndex]->setResonance(temp, radius); +} + +void Modal :: setModeGain( unsigned int modeIndex, StkFloat gain ) +{ + if ( modeIndex >= nModes_ ) { + oStream_ << "Modal::setModeGain: modeIndex parameter is greater than number of modes!"; + handleError( StkError::WARNING ); return; + } + + filters_[modeIndex]->setGain( gain ); +} + +void Modal :: strike( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Modal::strike: amplitude is out of range!"; + handleError( StkError::WARNING ); + } + + envelope_.setRate( 1.0 ); + envelope_.setTarget( amplitude ); + onepole_.setPole( 1.0 - amplitude ); + envelope_.tick(); + wave_->reset(); + + StkFloat temp; + for ( unsigned int i=0; isetResonance(temp, radii_[i]); + } +} + +void Modal :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->strike( amplitude ); + this->setFrequency( frequency ); +} + +void Modal :: noteOff( StkFloat amplitude ) +{ + // This calls damp, but inverts the meaning of amplitude (high + // amplitude means fast damping). + this->damp( 1.0 - (amplitude * 0.03) ); +} + +void Modal :: damp( StkFloat amplitude ) +{ + StkFloat temp; + for ( unsigned int i=0; isetResonance( temp, radii_[i]*amplitude ); + } +} + +} // stk namespace diff --git a/7.x/src/stk/src/ModalBar.cpp b/7.x/src/stk/src/ModalBar.cpp new file mode 100644 index 0000000..6274cb2 --- /dev/null +++ b/7.x/src/stk/src/ModalBar.cpp @@ -0,0 +1,182 @@ +/***************************************************/ +/*! \class ModalBar + \brief STK resonant bar instrument class. + + This class implements a number of different + struck bar instruments. It inherits from the + Modal class. + + Control Change Numbers: + - Stick Hardness = 2 + - Stick Position = 4 + - Vibrato Gain = 8 + - Vibrato Frequency = 11 + - Direct Stick Mix = 1 + - Volume = 128 + - Modal Presets = 16 + - Marimba = 0 + - Vibraphone = 1 + - Agogo = 2 + - Wood1 = 3 + - Reso = 4 + - Wood2 = 5 + - Beats = 6 + - Two Fixed = 7 + - Clump = 8 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "ModalBar.h" +#include "SKINImsg.h" +#include + +namespace stk { + +ModalBar :: ModalBar( void ) + : Modal() +{ + // Concatenate the STK rawwave path to the rawwave file + wave_ = new FileWvIn( (Stk::rawwavePath() + "marmstk1.raw").c_str(), true ); + wave_->setRate( 0.5 * 22050.0 / Stk::sampleRate() ); + + // Set the resonances for preset 0 (marimba). + this->setPreset( 0 ); +} + +ModalBar :: ~ModalBar( void ) +{ + delete wave_; +} + +void ModalBar :: setStickHardness( StkFloat hardness ) +{ + if ( hardness < 0.0 || hardness > 1.0 ) { + oStream_ << "ModalBar::setStickHardness: parameter is out of range!"; + handleError( StkError::WARNING ); return; + } + + stickHardness_ = hardness; + wave_->setRate( (0.25 * pow(4.0, stickHardness_) ) ); + masterGain_ = 0.1 + (1.8 * stickHardness_); +} + +void ModalBar :: setStrikePosition( StkFloat position ) +{ + if ( position < 0.0 || position > 1.0 ) { + oStream_ << "ModalBar::setStrikePosition: parameter is out of range!"; + handleError( StkError::WARNING ); return; + } + + strikePosition_ = position; + + // Hack only first three modes. + StkFloat temp2 = position * PI; + StkFloat temp = sin(temp2); + this->setModeGain(0, 0.12 * temp); + + temp = sin(0.05 + (3.9 * temp2)); + this->setModeGain(1, -0.03 * temp); + + temp = sin(-0.05 + (11 * temp2)); + this->setModeGain(2, 0.11 * temp); +} + +void ModalBar :: setPreset( int preset ) +{ + // Presets: + // First line: relative modal frequencies (negative number is + // a fixed mode that doesn't scale with frequency + // Second line: resonances of the modes + // Third line: mode volumes + // Fourth line: stickHardness, strikePosition, and direct stick + // gain (mixed directly into the output + static StkFloat presets[9][4][4] = { + {{1.0, 3.99, 10.65, -2443}, // Marimba + {0.9996, 0.9994, 0.9994, 0.999}, + {0.04, 0.01, 0.01, 0.008}, + {0.429688, 0.445312, 0.093750}}, + {{1.0, 2.01, 3.9, 14.37}, // Vibraphone + {0.99995, 0.99991, 0.99992, 0.9999}, + {0.025, 0.015, 0.015, 0.015 }, + {0.390625,0.570312,0.078125}}, + {{1.0, 4.08, 6.669, -3725.0}, // Agogo + {0.999, 0.999, 0.999, 0.999}, + {0.06, 0.05, 0.03, 0.02}, + {0.609375,0.359375,0.140625}}, + {{1.0, 2.777, 7.378, 15.377}, // Wood1 + {0.996, 0.994, 0.994, 0.99}, + {0.04, 0.01, 0.01, 0.008}, + {0.460938,0.375000,0.046875}}, + {{1.0, 2.777, 7.378, 15.377}, // Reso + {0.99996, 0.99994, 0.99994, 0.9999}, + {0.02, 0.005, 0.005, 0.004}, + {0.453125,0.250000,0.101562}}, + {{1.0, 1.777, 2.378, 3.377}, // Wood2 + {0.996, 0.994, 0.994, 0.99}, + {0.04, 0.01, 0.01, 0.008}, + {0.312500,0.445312,0.109375}}, + {{1.0, 1.004, 1.013, 2.377}, // Beats + {0.9999, 0.9999, 0.9999, 0.999}, + {0.02, 0.005, 0.005, 0.004}, + {0.398438,0.296875,0.070312}}, + {{1.0, 4.0, -1320.0, -3960.0}, // 2Fix + {0.9996, 0.999, 0.9994, 0.999}, + {0.04, 0.01, 0.01, 0.008}, + {0.453125,0.453125,0.070312}}, + {{1.0, 1.217, 1.475, 1.729}, // Clump + {0.999, 0.999, 0.999, 0.999}, + {0.03, 0.03, 0.03, 0.03 }, + {0.390625,0.570312,0.078125}}, + }; + + int temp = (preset % 9); + for (unsigned int i=0; isetRatioAndRadius(i, presets[temp][0][i], presets[temp][1][i]); + this->setModeGain(i, presets[temp][2][i]); + } + + this->setStickHardness(presets[temp][3][0]); + this->setStrikePosition(presets[temp][3][1]); + directGain_ = presets[temp][3][2]; + + if (temp == 1) // vibraphone + vibratoGain_ = 0.2; + else + vibratoGain_ = 0.0; +} + +void ModalBar :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "ModalBar::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_StickHardness_) // 2 + this->setStickHardness( normalizedValue ); + else if (number == __SK_StrikePosition_) // 4 + this->setStrikePosition( normalizedValue ); + else if (number == __SK_ProphesyRibbon_) // 16 + this->setPreset((int) value); + else if (number == __SK_Balance_) // 8 + vibratoGain_ = normalizedValue * 0.3; + else if (number == __SK_ModWheel_) // 1 + directGain_ = normalizedValue; + else if (number == __SK_ModFrequency_) // 11 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if (number == __SK_AfterTouch_Cont_) // 128 + envelope_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "ModalBar::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Modulate.cpp b/7.x/src/stk/src/Modulate.cpp new file mode 100644 index 0000000..c81eae5 --- /dev/null +++ b/7.x/src/stk/src/Modulate.cpp @@ -0,0 +1,49 @@ +/***************************************************/ +/*! \class Modulate + \brief STK periodic/random modulator. + + This class combines random and periodic + modulations to give a nice, natural human + modulation function. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Modulate.h" + +namespace stk { + +Modulate :: Modulate( void ) +{ + vibrato_.setFrequency( 6.0 ); + vibratoGain_ = 0.04; + + noiseRate_ = (unsigned int) ( 330.0 * Stk::sampleRate() / 22050.0 ); + noiseCounter_ = noiseRate_; + + randomGain_ = 0.05; + filter_.setPole( 0.999 ); + filter_.setGain( randomGain_ ); + + Stk::addSampleRateAlert( this ); +} + +Modulate :: ~Modulate( void ) +{ + Stk::removeSampleRateAlert( this ); +} + +void Modulate :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) + noiseRate_ = (unsigned int ) ( newRate * noiseRate_ / oldRate ); +} + +void Modulate :: setRandomGain( StkFloat gain ) +{ + randomGain_ = gain; + filter_.setGain( randomGain_ ); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Moog.cpp b/7.x/src/stk/src/Moog.cpp new file mode 100644 index 0000000..4451b21 --- /dev/null +++ b/7.x/src/stk/src/Moog.cpp @@ -0,0 +1,111 @@ +/***************************************************/ +/*! \class Moog + \brief STK moog-like swept filter sampling synthesis class. + + This instrument uses one attack wave, one + looped wave, and an ADSR envelope (inherited + from the Sampler class) and adds two sweepable + formant (FormSwep) filters. + + Control Change Numbers: + - Filter Q = 2 + - Filter Sweep Rate = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Gain = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Moog.h" +#include "SKINImsg.h" + +namespace stk { + +Moog :: Moog( void ) +{ + // Concatenate the STK rawwave path to the rawwave file + attacks_.push_back( new FileWvIn( (Stk::rawwavePath() + "mandpluk.raw").c_str(), true ) ); + loops_.push_back ( new FileLoop( (Stk::rawwavePath() + "impuls20.raw").c_str(), true ) ); + loops_.push_back ( new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ) ); // vibrato + loops_[1]->setFrequency( 6.122 ); + + filters_[0].setTargets( 0.0, 0.7 ); + filters_[1].setTargets( 0.0, 0.7 ); + + adsr_.setAllTimes( 0.001, 1.5, 0.6, 0.250 ); + filterQ_ = 0.85; + filterRate_ = 0.0001; + modDepth_ = 0.0; +} + +Moog :: ~Moog( void ) +{ +} + +void Moog :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Moog::setFrequency: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency; + StkFloat rate = attacks_[0]->getSize() * 0.01 * baseFrequency_ / Stk::sampleRate(); + attacks_[0]->setRate( rate ); + loops_[0]->setFrequency( baseFrequency_ ); +} + +void Moog :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + StkFloat temp; + + this->setFrequency( frequency ); + this->keyOn(); + attackGain_ = amplitude * 0.5; + loopGain_ = amplitude; + + temp = filterQ_ + 0.05; + filters_[0].setStates( 2000.0, temp ); + filters_[1].setStates( 2000.0, temp ); + + temp = filterQ_ + 0.099; + filters_[0].setTargets( frequency, temp ); + filters_[1].setTargets( frequency, temp ); + + filters_[0].setSweepRate( filterRate_ * 22050.0 / Stk::sampleRate() ); + filters_[1].setSweepRate( filterRate_ * 22050.0 / Stk::sampleRate() ); +} + +void Moog :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Moog::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_FilterQ_) // 2 + filterQ_ = 0.80 + ( 0.1 * normalizedValue ); + else if (number == __SK_FilterSweepRate_) // 4 + filterRate_ = normalizedValue * 0.0002; + else if (number == __SK_ModFrequency_) // 11 + this->setModulationSpeed( normalizedValue * 12.0 ); + else if (number == __SK_ModWheel_) // 1 + this->setModulationDepth( normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Moog::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/NRev.cpp b/7.x/src/stk/src/NRev.cpp new file mode 100644 index 0000000..63686c8 --- /dev/null +++ b/7.x/src/stk/src/NRev.cpp @@ -0,0 +1,119 @@ +/***************************************************/ +/*! \class NRev + \brief CCRMA's NRev reverberator class. + + This class takes a monophonic input signal and produces a stereo + output signal. It is derived from the CLM NRev function, which is + based on the use of networks of simple allpass and comb delay + filters. This particular arrangement consists of 6 comb filters + in parallel, followed by 3 allpass filters, a lowpass filter, and + another allpass in series, followed by two allpass filters in + parallel with corresponding right and left outputs. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "NRev.h" +#include + +namespace stk { + +NRev :: NRev( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "NRev::NRev: argument (" << T60 << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + lastFrame_.resize( 1, 2, 0.0 ); // resize lastFrame_ for stereo output + + int lengths[15] = {1433, 1601, 1867, 2053, 2251, 2399, 347, 113, 37, 59, 53, 43, 37, 29, 19}; + double scaler = Stk::sampleRate() / 25641.0; + + int delay, i; + for ( i=0; i<15; i++ ) { + delay = (int) floor(scaler * lengths[i]); + if ( (delay & 1) == 0) delay++; + while ( !this->isPrime(delay) ) delay += 2; + lengths[i] = delay; + } + + for ( i=0; i<6; i++ ) { + combDelays_[i].setMaximumDelay( lengths[i] ); + combDelays_[i].setDelay( lengths[i] ); + combCoefficient_[i] = pow(10.0, (-3 * lengths[i] / (T60 * Stk::sampleRate()))); + } + + for ( i=0; i<8; i++ ) { + allpassDelays_[i].setMaximumDelay( lengths[i+6] ); + allpassDelays_[i].setDelay( lengths[i+6] ); + } + + this->setT60( T60 ); + allpassCoefficient_ = 0.7; + effectMix_ = 0.3; + this->clear(); +} + +void NRev :: clear() +{ + int i; + for (i=0; i<6; i++) combDelays_[i].clear(); + for (i=0; i<8; i++) allpassDelays_[i].clear(); + lastFrame_[0] = 0.0; + lastFrame_[1] = 0.0; + lowpassState_ = 0.0; +} + +void NRev :: setT60( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "NRev::setT60: argument (" << T60 << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + for ( int i=0; i<6; i++ ) + combCoefficient_[i] = pow(10.0, (-3.0 * combDelays_[i].getDelay() / (T60 * Stk::sampleRate()))); +} + +StkFrames& NRev :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() - 1 ) { + oStream_ << "NRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() - 1 ) { + oStream_ << "NRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i + +namespace stk { + +Noise :: Noise( unsigned int seed ) +{ + // Seed the random number generator + this->setSeed( seed ); +} + +void Noise :: setSeed( unsigned int seed ) +{ + if ( seed == 0 ) + srand( (unsigned int) time( NULL ) ); + else + srand( seed ); +} + +} // stk namespace + + diff --git a/7.x/src/stk/src/OnePole.cpp b/7.x/src/stk/src/OnePole.cpp new file mode 100644 index 0000000..bbdfa61 --- /dev/null +++ b/7.x/src/stk/src/OnePole.cpp @@ -0,0 +1,61 @@ +/***************************************************/ +/*! \class OnePole + \brief STK one-pole filter class. + + This class implements a one-pole digital filter. A method is + provided for setting the pole position along the real axis of the + z-plane while maintaining a constant peak filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "OnePole.h" + +namespace stk { + +OnePole :: OnePole( StkFloat thePole ) +{ + b_.resize( 1 ); + a_.resize( 2 ); + a_[0] = 1.0; + inputs_.resize( 1, 1, 0.0 ); + outputs_.resize( 2, 1, 0.0 ); + + this->setPole( thePole ); +} + +OnePole :: ~OnePole() +{ +} + +void OnePole :: setPole( StkFloat thePole ) +{ + if ( std::abs( thePole ) >= 1.0 ) { + oStream_ << "OnePole::setPole: argument (" << thePole << ") should be less than 1.0!"; + handleError( StkError::WARNING ); return; + } + + // Normalize coefficients for peak unity gain. + if ( thePole > 0.0 ) + b_[0] = (StkFloat) (1.0 - thePole); + else + b_[0] = (StkFloat) (1.0 + thePole); + + a_[1] = -thePole; +} + +void OnePole :: setCoefficients( StkFloat b0, StkFloat a1, bool clearState ) +{ + if ( std::abs( a1 ) >= 1.0 ) { + oStream_ << "OnePole::setCoefficients: a1 argument (" << a1 << ") should be less than 1.0!"; + handleError( StkError::WARNING ); return; + } + + b_[0] = b0; + a_[1] = a1; + + if ( clearState ) this->clear(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/OneZero.cpp b/7.x/src/stk/src/OneZero.cpp new file mode 100644 index 0000000..bf16c85 --- /dev/null +++ b/7.x/src/stk/src/OneZero.cpp @@ -0,0 +1,49 @@ +/***************************************************/ +/*! \class OneZero + \brief STK one-zero filter class. + + This class implements a one-zero digital filter. A method is + provided for setting the zero position along the real axis of the + z-plane while maintaining a constant filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "OneZero.h" +#include + +namespace stk { + +OneZero :: OneZero( StkFloat theZero ) +{ + b_.resize( 2 ); + inputs_.resize( 2, 1, 0.0 ); + + this->setZero( theZero ); +} + +OneZero :: ~OneZero( void ) +{ +} + +void OneZero :: setZero( StkFloat theZero ) +{ + // Normalize coefficients for unity gain. + if ( theZero > 0.0 ) + b_[0] = 1.0 / ((StkFloat) 1.0 + theZero); + else + b_[0] = 1.0 / ((StkFloat) 1.0 - theZero); + + b_[1] = -theZero * b_[0]; +} + +void OneZero :: setCoefficients( StkFloat b0, StkFloat b1, bool clearState ) +{ + b_[0] = b0; + b_[1] = b1; + + if ( clearState ) this->clear(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/PRCRev.cpp b/7.x/src/stk/src/PRCRev.cpp new file mode 100644 index 0000000..f2ecd21 --- /dev/null +++ b/7.x/src/stk/src/PRCRev.cpp @@ -0,0 +1,120 @@ +/***************************************************/ +/*! \class PRCRev + \brief Perry's simple reverberator class. + + This class is based on some of the famous + Stanford/CCRMA reverbs (NRev, KipRev), which + were based on the Chowning/Moorer/Schroeder + reverberators using networks of simple allpass + and comb delay filters. This class implements + two series allpass units and two parallel comb + filters. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "PRCRev.h" +#include + +namespace stk { + +PRCRev :: PRCRev( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "PRCRev::PRCRev: argument (" << T60 << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + lastFrame_.resize( 1, 2, 0.0 ); // resize lastFrame_ for stereo output + + // Delay lengths for 44100 Hz sample rate. + int lengths[4]= {341, 613, 1557, 2137}; + double scaler = Stk::sampleRate() / 44100.0; + + // Scale the delay lengths if necessary. + int delay, i; + if ( scaler != 1.0 ) { + for (i=0; i<4; i++) { + delay = (int) floor(scaler * lengths[i]); + if ( (delay & 1) == 0) delay++; + while ( !this->isPrime(delay) ) delay += 2; + lengths[i] = delay; + } + } + + for ( i=0; i<2; i++ ) { + allpassDelays_[i].setMaximumDelay( lengths[i] ); + allpassDelays_[i].setDelay( lengths[i] ); + + combDelays_[i].setMaximumDelay( lengths[i+2] ); + combDelays_[i].setDelay( lengths[i+2] ); + } + + this->setT60( T60 ); + allpassCoefficient_ = 0.7; + effectMix_ = 0.5; + this->clear(); +} + +void PRCRev :: clear( void ) +{ + allpassDelays_[0].clear(); + allpassDelays_[1].clear(); + combDelays_[0].clear(); + combDelays_[1].clear(); + lastFrame_[0] = 0.0; + lastFrame_[1] = 0.0; +} + +void PRCRev :: setT60( StkFloat T60 ) +{ + if ( T60 <= 0.0 ) { + oStream_ << "PRCRev::setT60: argument (" << T60 << ") must be positive!"; + handleError( StkError::WARNING ); return; + } + + combCoefficient_[0] = pow(10.0, (-3.0 * combDelays_[0].getDelay() / (T60 * Stk::sampleRate()))); + combCoefficient_[1] = pow(10.0, (-3.0 * combDelays_[1].getDelay() / (T60 * Stk::sampleRate()))); +} + +StkFrames& PRCRev :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() - 1 ) { + oStream_ << "PRCRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() - 1 ) { + oStream_ << "PRCRev::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; i3--\ + 2-- + -->1-->Out + \endcode + + Control Change Numbers: + - Total Modulator Index = 2 + - Modulator Crossfade = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "PercFlut.h" + +namespace stk { + +PercFlut :: PercFlut( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 1.50 * 1.000); + this->setRatio(1, 3.00 * 0.995); + this->setRatio(2, 2.99 * 1.005); + this->setRatio(3, 6.00 * 0.997); + gains_[0] = fmGains_[99]; + gains_[1] = fmGains_[71]; + gains_[2] = fmGains_[93]; + gains_[3] = fmGains_[85]; + + adsr_[0]->setAllTimes( 0.05, 0.05, fmSusLevels_[14], 0.05); + adsr_[1]->setAllTimes( 0.02, 0.50, fmSusLevels_[13], 0.5); + adsr_[2]->setAllTimes( 0.02, 0.30, fmSusLevels_[11], 0.05); + adsr_[3]->setAllTimes( 0.02, 0.05, fmSusLevels_[13], 0.01); + + twozero_.setGain( 0.0 ); + modDepth_ = 0.005; +} + +PercFlut :: ~PercFlut( void ) +{ +} + +void PercFlut :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "PercFlut::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency; +} + +void PercFlut :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[99] * 0.5; + gains_[1] = amplitude * fmGains_[71] * 0.5; + gains_[2] = amplitude * fmGains_[93] * 0.5; + gains_[3] = amplitude * fmGains_[85] * 0.5; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Phonemes.cpp b/7.x/src/stk/src/Phonemes.cpp new file mode 100644 index 0000000..fa3d52b --- /dev/null +++ b/7.x/src/stk/src/Phonemes.cpp @@ -0,0 +1,292 @@ +/***************************************************/ +/*! \class Phonemes + \brief STK phonemes table. + + This class does nothing other than declare a + set of 32 static phoneme formant parameters + and provide access to those values. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Phonemes.h" +#include + +namespace stk { + +const char Phonemes :: phonemeNames[32][4] = + {"eee", "ihh", "ehh", "aaa", + "ahh", "aww", "ohh", "uhh", + "uuu", "ooo", "rrr", "lll", + "mmm", "nnn", "nng", "ngg", + "fff", "sss", "thh", "shh", + "xxx", "hee", "hoo", "hah", + "bbb", "ddd", "jjj", "ggg", + "vvv", "zzz", "thz", "zhh" + }; + +const StkFloat Phonemes :: phonemeGains[32][2] = + {{1.0, 0.0}, // eee + {1.0, 0.0}, // ihh + {1.0, 0.0}, // ehh + {1.0, 0.0}, // aaa + + {1.0, 0.0}, // ahh + {1.0, 0.0}, // aww + {1.0, 0.0}, // ohh + {1.0, 0.0}, // uhh + + {1.0, 0.0}, // uuu + {1.0, 0.0}, // ooo + {1.0, 0.0}, // rrr + {1.0, 0.0}, // lll + + {1.0, 0.0}, // mmm + {1.0, 0.0}, // nnn + {1.0, 0.0}, // nng + {1.0, 0.0}, // ngg + + {0.0, 0.7}, // fff + {0.0, 0.7}, // sss + {0.0, 0.7}, // thh + {0.0, 0.7}, // shh + + {0.0, 0.7}, // xxx + {0.0, 0.1}, // hee + {0.0, 0.1}, // hoo + {0.0, 0.1}, // hah + + {1.0, 0.1}, // bbb + {1.0, 0.1}, // ddd + {1.0, 0.1}, // jjj + {1.0, 0.1}, // ggg + + {1.0, 1.0}, // vvv + {1.0, 1.0}, // zzz + {1.0, 1.0}, // thz + {1.0, 1.0} // zhh + }; + +const StkFloat Phonemes :: phonemeParameters[32][4][3] = + {{ { 273, 0.996, 10}, // eee (beet) + {2086, 0.945, -16}, + {2754, 0.979, -12}, + {3270, 0.440, -17}}, + { { 385, 0.987, 10}, // ihh (bit) + {2056, 0.930, -20}, + {2587, 0.890, -20}, + {3150, 0.400, -20}}, + { { 515, 0.977, 10}, // ehh (bet) + {1805, 0.810, -10}, + {2526, 0.875, -10}, + {3103, 0.400, -13}}, + { { 773, 0.950, 10}, // aaa (bat) + {1676, 0.830, -6}, + {2380, 0.880, -20}, + {3027, 0.600, -20}}, + + { { 770, 0.950, 0}, // ahh (father) + {1153, 0.970, -9}, + {2450, 0.780, -29}, + {3140, 0.800, -39}}, + { { 637, 0.910, 0}, // aww (bought) + { 895, 0.900, -3}, + {2556, 0.950, -17}, + {3070, 0.910, -20}}, + { { 637, 0.910, 0}, // ohh (bone) NOTE:: same as aww (bought) + { 895, 0.900, -3}, + {2556, 0.950, -17}, + {3070, 0.910, -20}}, + { { 561, 0.965, 0}, // uhh (but) + {1084, 0.930, -10}, + {2541, 0.930, -15}, + {3345, 0.900, -20}}, + + { { 515, 0.976, 0}, // uuu (foot) + {1031, 0.950, -3}, + {2572, 0.960, -11}, + {3345, 0.960, -20}}, + { { 349, 0.986, -10}, // ooo (boot) + { 918, 0.940, -20}, + {2350, 0.960, -27}, + {2731, 0.950, -33}}, + { { 394, 0.959, -10}, // rrr (bird) + {1297, 0.780, -16}, + {1441, 0.980, -16}, + {2754, 0.950, -40}}, + { { 462, 0.990, +5}, // lll (lull) + {1200, 0.640, -10}, + {2500, 0.200, -20}, + {3000, 0.100, -30}}, + + { { 265, 0.987, -10}, // mmm (mom) + {1176, 0.940, -22}, + {2352, 0.970, -20}, + {3277, 0.940, -31}}, + { { 204, 0.980, -10}, // nnn (nun) + {1570, 0.940, -15}, + {2481, 0.980, -12}, + {3133, 0.800, -30}}, + { { 204, 0.980, -10}, // nng (sang) NOTE:: same as nnn + {1570, 0.940, -15}, + {2481, 0.980, -12}, + {3133, 0.800, -30}}, + { { 204, 0.980, -10}, // ngg (bong) NOTE:: same as nnn + {1570, 0.940, -15}, + {2481, 0.980, -12}, + {3133, 0.800, -30}}, + + { {1000, 0.300, 0}, // fff + {2800, 0.860, -10}, + {7425, 0.740, 0}, + {8140, 0.860, 0}}, + { {0, 0.000, 0}, // sss + {2000, 0.700, -15}, + {5257, 0.750, -3}, + {7171, 0.840, 0}}, + { { 100, 0.900, 0}, // thh + {4000, 0.500, -20}, + {5500, 0.500, -15}, + {8000, 0.400, -20}}, + { {2693, 0.940, 0}, // shh + {4000, 0.720, -10}, + {6123, 0.870, -10}, + {7755, 0.750, -18}}, + + { {1000, 0.300, -10}, // xxx NOTE:: Not Really Done Yet + {2800, 0.860, -10}, + {7425, 0.740, 0}, + {8140, 0.860, 0}}, + { { 273, 0.996, -40}, // hee (beet) (noisy eee) + {2086, 0.945, -16}, + {2754, 0.979, -12}, + {3270, 0.440, -17}}, + { { 349, 0.986, -40}, // hoo (boot) (noisy ooo) + { 918, 0.940, -10}, + {2350, 0.960, -17}, + {2731, 0.950, -23}}, + { { 770, 0.950, -40}, // hah (father) (noisy ahh) + {1153, 0.970, -3}, + {2450, 0.780, -20}, + {3140, 0.800, -32}}, + + { {2000, 0.700, -20}, // bbb NOTE:: Not Really Done Yet + {5257, 0.750, -15}, + {7171, 0.840, -3}, + {9000, 0.900, 0}}, + { { 100, 0.900, 0}, // ddd NOTE:: Not Really Done Yet + {4000, 0.500, -20}, + {5500, 0.500, -15}, + {8000, 0.400, -20}}, + { {2693, 0.940, 0}, // jjj NOTE:: Not Really Done Yet + {4000, 0.720, -10}, + {6123, 0.870, -10}, + {7755, 0.750, -18}}, + { {2693, 0.940, 0}, // ggg NOTE:: Not Really Done Yet + {4000, 0.720, -10}, + {6123, 0.870, -10}, + {7755, 0.750, -18}}, + + { {2000, 0.700, -20}, // vvv NOTE:: Not Really Done Yet + {5257, 0.750, -15}, + {7171, 0.840, -3}, + {9000, 0.900, 0}}, + { { 100, 0.900, 0}, // zzz NOTE:: Not Really Done Yet + {4000, 0.500, -20}, + {5500, 0.500, -15}, + {8000, 0.400, -20}}, + { {2693, 0.940, 0}, // thz NOTE:: Not Really Done Yet + {4000, 0.720, -10}, + {6123, 0.870, -10}, + {7755, 0.750, -18}}, + { {2693, 0.940, 0}, // zhh NOTE:: Not Really Done Yet + {4000, 0.720, -10}, + {6123, 0.870, -10}, + {7755, 0.750, -18}} + }; + +Phonemes :: Phonemes(void) +{ +} + +Phonemes :: ~Phonemes(void) +{ +} + +const char *Phonemes :: name( unsigned int index ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::name: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0; + } + return phonemeNames[index]; +} + +StkFloat Phonemes :: voiceGain( unsigned int index ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::voiceGain: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + return phonemeGains[index][0]; +} + +StkFloat Phonemes :: noiseGain( unsigned int index ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::noiseGain: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + return phonemeGains[index][1]; +} + +StkFloat Phonemes :: formantFrequency( unsigned int index, unsigned int partial ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::formantFrequency: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + if ( partial > 3 ) { + oStream_ << "Phonemes::formantFrequency: partial is greater than 3!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + return phonemeParameters[index][partial][0]; +} + +StkFloat Phonemes :: formantRadius( unsigned int index, unsigned int partial ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::formantRadius: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + if ( partial > 3 ) { + oStream_ << "Phonemes::formantRadius: partial is greater than 3!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + return phonemeParameters[index][partial][1]; +} + +StkFloat Phonemes :: formantGain( unsigned int index, unsigned int partial ) +{ + if ( index > 31 ) { + oStream_ << "Phonemes::formantGain: index is greater than 31!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + if ( partial > 3 ) { + oStream_ << "Phonemes::formantGain: partial is greater than 3!"; + handleError( oStream_.str(), StkError::WARNING ); + return 0.0; + } + return phonemeParameters[index][partial][2]; +} + +} // stk namespace diff --git a/7.x/src/stk/src/PitShift.cpp b/7.x/src/stk/src/PitShift.cpp new file mode 100644 index 0000000..eaead21 --- /dev/null +++ b/7.x/src/stk/src/PitShift.cpp @@ -0,0 +1,88 @@ +/***************************************************/ +/*! \class PitShift + \brief STK simple pitch shifter effect class. + + This class implements a simple pitch shifter + using delay lines. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "PitShift.h" +#include + +namespace stk { + +PitShift :: PitShift( void ) +{ + delayLength_ = maxDelay - 24; + halfLength_ = delayLength_ / 2; + delay_[0] = 12; + delay_[1] = maxDelay / 2; + + delayLine_[0].setMaximumDelay( maxDelay ); + delayLine_[0].setDelay( delay_[0] ); + delayLine_[1].setMaximumDelay( maxDelay ); + delayLine_[1].setDelay( delay_[1] ); + effectMix_ = 0.5; + rate_ = 1.0; +} + +void PitShift :: clear() +{ + delayLine_[0].clear(); + delayLine_[1].clear(); + lastFrame_[0] = 0.0; +} + +void PitShift :: setShift( StkFloat shift ) +{ + if ( shift < 1.0 ) { + rate_ = 1.0 - shift; + } + else if ( shift > 1.0 ) { + rate_ = 1.0 - shift; + } + else { + rate_ = 0.0; + delay_[0] = halfLength_ + 12; + } +} + +StkFrames& PitShift :: tick( StkFrames& frames, unsigned int channel ) +{ +#if defined(_STK_DEBUG_) + if ( channel >= frames.channels() ) { + oStream_ << "PitShift::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *samples = &frames[channel]; + unsigned int hop = frames.channels(); + for ( unsigned int i=0; i= iFrames.channels() || oChannel >= oFrames.channels() ) { + oStream_ << "PitShift::tick(): channel and StkFrames arguments are incompatible!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } +#endif + + StkFloat *iSamples = &iFrames[iChannel]; + StkFloat *oSamples = &oFrames[oChannel]; + unsigned int iHop = iFrames.channels(), oHop = oFrames.channels(); + for ( unsigned int i=0; isetFrequency( 220.0 ); +} + +Plucked :: ~Plucked( void ) +{ +} + +void Plucked :: clear( void ) +{ + delayLine_.clear(); + loopFilter_.clear(); + pickFilter_.clear(); +} + +void Plucked :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Plucked::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Delay = length - filter delay. + StkFloat delay = ( Stk::sampleRate() / frequency ) - loopFilter_.phaseDelay( frequency ); + delayLine_.setDelay( delay ); + + loopGain_ = 0.995 + (frequency * 0.000005); + if ( loopGain_ >= 1.0 ) loopGain_ = 0.99999; +} + +void Plucked :: pluck( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Plucked::pluck: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + pickFilter_.setPole( 0.999 - (amplitude * 0.15) ); + pickFilter_.setGain( amplitude * 0.5 ); + for ( unsigned long i=0; isetFrequency( frequency ); + this->pluck( amplitude ); +} + +void Plucked :: noteOff( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Plucked::noteOff: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + loopGain_ = 1.0 - amplitude; +} + +} // stk namespace diff --git a/7.x/src/stk/src/PoleZero.cpp b/7.x/src/stk/src/PoleZero.cpp new file mode 100644 index 0000000..2f2e821 --- /dev/null +++ b/7.x/src/stk/src/PoleZero.cpp @@ -0,0 +1,73 @@ +/***************************************************/ +/*! \class PoleZero + \brief STK one-pole, one-zero filter class. + + This class implements a one-pole, one-zero digital filter. A + method is provided for creating an allpass filter with a given + coefficient. Another method is provided to create a DC blocking + filter. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "PoleZero.h" + +namespace stk { + +PoleZero :: PoleZero() +{ + // Default setting for pass-through. + b_.resize( 2, 0.0 ); + a_.resize( 2, 0.0 ); + b_[0] = 1.0; + a_[0] = 1.0; + inputs_.resize( 2, 1, 0.0 ); + outputs_.resize( 2, 1, 0.0 ); +} + +PoleZero :: ~PoleZero() +{ +} + +void PoleZero :: setCoefficients( StkFloat b0, StkFloat b1, StkFloat a1, bool clearState ) +{ + if ( std::abs( a1 ) >= 1.0 ) { + oStream_ << "PoleZero::setCoefficients: a1 argument (" << a1 << ") should be less than 1.0!"; + handleError( StkError::WARNING ); return; + } + + b_[0] = b0; + b_[1] = b1; + a_[1] = a1; + + if ( clearState ) this->clear(); +} + +void PoleZero :: setAllpass( StkFloat coefficient ) +{ + if ( std::abs( coefficient ) >= 1.0 ) { + oStream_ << "PoleZero::setAllpass: argument (" << coefficient << ") makes filter unstable!"; + handleError( StkError::WARNING ); return; + } + + b_[0] = coefficient; + b_[1] = 1.0; + a_[0] = 1.0; // just in case + a_[1] = coefficient; +} + +void PoleZero :: setBlockZero( StkFloat thePole ) +{ + if ( std::abs( thePole ) >= 1.0 ) { + oStream_ << "PoleZero::setBlockZero: argument (" << thePole << ") makes filter unstable!"; + handleError( StkError::WARNING ); return; + } + + b_[0] = 1.0; + b_[1] = -1.0; + a_[0] = 1.0; // just in case + a_[1] = -thePole; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Recorder.cpp b/7.x/src/stk/src/Recorder.cpp new file mode 100644 index 0000000..8dc0eec --- /dev/null +++ b/7.x/src/stk/src/Recorder.cpp @@ -0,0 +1,345 @@ +/***************************************************/ +/*! \class Recorder + \brief A recorder / flute physical model. + + This class implements a physical model of a recorder / + flute instrument, based on the paper "Sound production + in recorderlike instruments. II. A simulation model." + by M.P. Verge, A. Hirschberg and R. Causse, Journal of + the Acoustical Society of America, 1997. + + Control Change Numbers: + - Softness = 2 + - Noise Gain = 4 + - Noise Cutoff = 16 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Mathias Bredholt, McGill University. + Formatted for STK by Gary Scavone, 2021. +*/ +/***************************************************/ + +#include "Recorder.h" +#include "SKINImsg.h" + +namespace stk { + +// Air constants +const stk::StkFloat rho = 1.2041; // density of air +const stk::StkFloat c0 = 343.21; // speed of sound in air + +// Flute constants +const stk::StkFloat lc = 0.02; // length of flue canal +const stk::StkFloat h = 0.001; // height of flue exit +const stk::StkFloat H = 0.02; // pipe diameter +const stk::StkFloat W = 4 * h; // width of mouth +const stk::StkFloat Sp = H * H; // cross-section of pipe +const stk::StkFloat Sm = W * H; // cross-section of mouth +const stk::StkFloat din = 0.0030; // end correction +const stk::StkFloat dout = 0.0063; // end correction +const stk::StkFloat dm = din + dout; // end correction of mouth +const stk::StkFloat dd = 0.0035; // acoustic distance between Q1 and Q2 +const stk::StkFloat rp = sqrt(Sp / stk::PI); +const stk::StkFloat b = 0.4 * h; // jet width + +// Calculation coefficients +const stk::StkFloat b2 = Sp / (rho * c0); + +Recorder :: Recorder() +{ + vibratoGain_ = 0.0; + noiseGain_ = 0.2; + breathCutoff_ = 0.0; + outputGain_ = 0.0; + psi_ = 1.0; + poutL_ = 0; + pout_ = 0; + poutm1_ = 0; + poutm2_ = 0; + pin_ = 0; + pinm1_ = 0; + pinm2_ = 0; + Uj_ = 0; + Ujm1_ = 0; + Qj_ = 0; + Qjm1_ = 0; + Qjm2_ = 0; + Q1_ = 0; + Q1m1_ = 0; + Q1m2_ = 0; + Qp_ = 0; + Qpm1_ = 0; + pm_ = 0; + + pinDelay_.tick( 0 ); + poutDelay_.tick( 0 ); + jetDelay_.tick( 0 ); + + jetDelay_.setDelay(200); + vibrato_.setFrequency(4); + + // Calculation coefficients ... would need to be recalculated if sample rate changes + StkFloat T = 1.0 / Stk::sampleRate(); + b1 = rho / (4.0 * PI * c0 * T * T); + b3 = dm * Sp / (T * Sm * c0); + b4 = rho * dout / (Sm * T); + + // Radiation loss filter + StkFloat A = rp * rp / (4 * c0 * c0 * T * T); + StkFloat B = 0.82 * rp / (c0*T); + StkFloat b_rad[3] = { 1 + A - B, B - 2 * A, A }; + StkFloat a_rad[3] = { A - B - 1, B - 2 * A, A }; + std::vector b_coeffs( &b_rad[0], &b_rad[0]+3 ); + std::vector a_coeffs( &a_rad[0], &a_rad[0]+3 ); + radiation_filter_.setCoefficients(b_coeffs, a_coeffs); + + // Visco-thermal loss filter + StkFloat b_visco[4] = { 0.83820223947141, -0.16888603248373, -0.64759781930259, 0.07424498608506 }; + StkFloat a_visco[4] = { 1.0, -0.33623476246554, -0.71257915055968, 0.14508304017256 }; + b_coeffs.clear(); + b_coeffs.assign( &b_visco[0], &b_visco[0]+4 ); + a_coeffs.clear(); + a_coeffs.assign( &a_visco[0], &a_visco[0]+4 ); + visco_in_filter_.setCoefficients(b_coeffs, a_coeffs); + visco_out_filter_.setCoefficients(b_coeffs, a_coeffs); + + setBreathCutoff( 500 ); + setFrequency( 880 ); +} + +Recorder :: ~Recorder( void ) +{ +} + +void Recorder :: clear( void ) +{ + pinDelay_.clear(); + poutDelay_.clear(); + jetDelay_.clear(); + radiation_filter_.clear(); + visco_in_filter_.clear(); + visco_out_filter_.clear(); + turbFilter_.clear(); +} + +void Recorder :: setFrequency( StkFloat val ) +{ +#if defined(_STK_DEBUG_) + if ( val <= 0.0 ) { + oStream_ << "Recorder::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat M = Stk::sampleRate() / val - 4 - 3; + pinDelay_.setDelay( M ); + poutDelay_.setDelay( M ); +} + +void Recorder :: setBlowPressure( StkFloat val ) +{ + maxPressure_ = val; +} + +void Recorder :: setVibratoGain( StkFloat val ) +{ + vibratoGain_ = val; +} + +void Recorder :: setVibratoFrequency( StkFloat val ) +{ + vibrato_.setFrequency( val ); +} + +void Recorder :: setNoiseGain( StkFloat val ) +{ + noiseGain_ = val; +} + +void Recorder :: setBreathCutoff( StkFloat val ) +{ + // The gain of this filter is quite high + breathCutoff_ = val; + StkFloat Q = 0.99; + StkFloat r = 2.0 * sin(PI * val / sampleRate()); + StkFloat q = 1.0 - r * Q; + StkFloat as[3] = { 1.0, r * r - q - 1, q }; + std::vector b_turb(1, r*r); + std::vector a_turb( &as[0], &as[0]+3 ); + turbFilter_.setCoefficients(b_turb, a_turb); +} + +void Recorder :: setSoftness( StkFloat val ) +{ + psi_ = val; +} + +void Recorder :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Recorder::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setAttackRate( rate ); + //maxPressure_ = amplitude / (StkFloat) 0.8; + maxPressure_ = 35 * amplitude; + adsr_.keyOn(); +} + +void Recorder :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Recorder::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + adsr_.setReleaseRate( rate ); + adsr_.keyOff(); +} + +void Recorder :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( 1.1 + (amplitude * 0.20), amplitude * 0.02 ); + outputGain_ = amplitude / 40.0; +} + +void Recorder :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.02 ); +} + +void Recorder :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Recorder::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == 2) // 2 + psi_ = 2.0 * normalizedValue; + else if (number == 16) + setBreathCutoff( normalizedValue * 2000 ); + else if (number == __SK_NoiseLevel_) // 4 + noiseGain_ = normalizedValue; + else if (number == __SK_ModFrequency_) // 11 + vibrato_.setFrequency( normalizedValue * 12.0); + else if (number == __SK_ModWheel_) // 1 + vibratoGain_ = ( normalizedValue * 0.4 ); + else if (number == __SK_AfterTouch_Cont_) // 128 + maxPressure_ = 35.0 * normalizedValue; +#if defined(_STK_DEBUG_) + else { + oStream_ << "Recorder::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +StkFloat Recorder::tick( unsigned int ) +{ + // Read in from delay lines + pinm2_ = pinm1_; + pinm1_ = pin_; + pin_ = pinDelay_.lastOut(); + + poutm2_ = poutm1_; + poutm1_ = pout_; + poutL_ = poutDelay_.lastOut(); + + // Filter wave components for visco-thermal losses + pin_ = visco_in_filter_.tick(pin_); + poutL_ = visco_out_filter_.tick(poutL_); + + // Get input blow pressure + StkFloat pf = maxPressure_ * adsr_.tick() * (vibrato_.tick() * vibratoGain_ + (1 - vibratoGain_)); + + StkFloat T = 1.0 / sampleRate(); + + // Jet velocity at flue exit + Ujm1_ = Uj_; + Uj_ = Ujm1_ + T / (rho * lc) * (pf - pm_ - 0.5 * rho * Ujm1_ * Ujm1_); + + // Jet flow at flue exit + Qjm2_ = Qjm1_; + Qjm1_ = Qj_; + Qj_ = h * H * Uj_; + + // Jet drive + StkFloat Uj_steady = fmax(sqrt(2 * pf / rho), 0.1); + StkFloat fc_jet = 0.36 / W * Uj_steady; + StkFloat g_jet = 0.002004 * exp(-0.06046 * Uj_steady); + StkFloat r_jet = 0.95 - Uj_steady * 0.015; + StkFloat b0_jet = g_jet * (1 - r_jet * r_jet) / 2; + + // Calculate coefficients for resonant filter + StkFloat b_jet[3] = { b0_jet, 0, -b0_jet }; + StkFloat a_jet[3] = { 1, -2 * r_jet * cos(2 * PI * fc_jet * T), r_jet * r_jet }; + std::vector b_jetcoeffs( &b_jet[0], &b_jet[0]+3 ); + std::vector a_jetcoeffs( &a_jet[0], &a_jet[0]+3 ); + jetFilter_.setCoefficients( b_jetcoeffs, a_jetcoeffs ); + StkFloat eta = jetFilter_.tick(jetDelay_.lastOut()); + + // Calculate flow source Q1 + Q1m1_ = Q1_; + Q1_ = b * H * Uj_ * (1 + tanh(eta / (psi_ * b))); + + // Calculate pressure pulse modeling the jet drive + StkFloat pjd = -rho * dd / Sm * (Q1_ - Q1m1_) / T; + + // Vortex shedding + int Qp_sign = 0; + if (Qp_ < 0) Qp_sign = -1; + else if (Qp_ > 0) Qp_sign = 1; + + StkFloat pa = -0.5 * rho * (Qp_ / (0.6 * Sm)) * (Qp_ / (0.6 * Sm)) * Qp_sign; + + // Turbulence + StkFloat pt = turbFilter_.tick(noiseGain_ * turb_.tick() * 0.5 * rho * Uj_ * Uj_); + + // Pressure pulse delta p + StkFloat dp = pjd + pa + pt; + + // Calculate outgoing pressure pout + pout_ = ((b3 - b1 * b2 - 1) * pin_ + + (2 * b1 * b2 - b3) * (pinm1_ - poutm1_) + + b1 * b2 * (poutm2_ - pinm2_) - + b1 * (Qj_ - 2 * Qjm1_ + Qjm2_) + + b4 * (Qj_ - Qjm1_) + dp) / (1 - b1 * b2 + b3); + + // Flow in the pipe + Qpm1_ = Qp_; + Qp_ = Sp / (rho * c0) * (pout_ - pin_); + + // Mouth pressure + pm_ = pout_ + pin_ - dp + rho * din / Sm * (Qp_ - Qpm1_)/T; + + // Calculate transverse acoustic velocity + StkFloat Q1d = Q1_ - 0.5 * b * H * Uj_; + StkFloat Vac = 2.0 / PI * Qp_ / Sm - 0.38 * Q1d / Sm; + jetDelay_.tick(Vac); + + // Calculate new jet delay line length + //jet_.setDelay(fmin(W / (0.6 * Uj_steady) * sampleRate(), 200.0)); + jetDelay_.setDelay(fmin(W / (0.6 * Uj_steady * T), 200.0)); + + // Radiation loss filtering + StkFloat pin_L = radiation_filter_.tick(poutL_); + + // Write to delay lines + poutDelay_.tick(pout_); + pinDelay_.tick(pin_L); + + lastFrame_[0] = outputGain_ * (pout_ + pin_); + return lastFrame_[0]; + + //return (pout_0 + pin_0) * 0.01; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Resonate.cpp b/7.x/src/stk/src/Resonate.cpp new file mode 100644 index 0000000..3dc42fd --- /dev/null +++ b/7.x/src/stk/src/Resonate.cpp @@ -0,0 +1,113 @@ +/***************************************************/ +/*! \class Resonate + \brief STK noise driven formant filter. + + This instrument contains a noise source, which + excites a biquad resonance filter, with volume + controlled by an ADSR. + + Control Change Numbers: + - Resonance Frequency (0-Nyquist) = 2 + - Pole Radii = 4 + - Notch Frequency (0-Nyquist) = 11 + - Zero Radii = 1 + - Envelope Gain = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Resonate.h" +#include "SKINImsg.h" + +namespace stk { + +Resonate :: Resonate( void ) +{ + poleFrequency_ = 4000.0; + poleRadius_ = 0.95; + // Set the filter parameters. + filter_.setResonance( poleFrequency_, poleRadius_, true ); + zeroFrequency_ = 0.0; + zeroRadius_ = 0.0; +} + +Resonate :: ~Resonate( void ) +{ +} + +void Resonate :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + adsr_.setTarget( amplitude ); + this->keyOn(); + this->setResonance( frequency, poleRadius_ ); +} + +void Resonate :: noteOff( StkFloat amplitude ) +{ + this->keyOff(); +} + +void Resonate :: setResonance( StkFloat frequency, StkFloat radius ) +{ + if ( frequency < 0.0 ) { + oStream_ << "Resonate::setResonance: frequency parameter is less than zero!"; + handleError( StkError::WARNING ); return; + } + + if ( radius < 0.0 || radius >= 1.0 ) { + oStream_ << "Resonate::setResonance: radius parameter is out of range!"; + handleError( StkError::WARNING ); return; + } + + poleFrequency_ = frequency; + poleRadius_ = radius; + filter_.setResonance( poleFrequency_, poleRadius_, true ); +} + +void Resonate :: setNotch( StkFloat frequency, StkFloat radius ) +{ + if ( frequency < 0.0 ) { + oStream_ << "Resonate::setNotch: frequency parameter is less than zero ... setting to 0.0!"; + handleError( StkError::WARNING ); return; + } + + if ( radius < 0.0 ) { + oStream_ << "Resonate::setNotch: radius parameter is less than 0.0!"; + handleError( StkError::WARNING ); return; + } + + zeroFrequency_ = frequency; + zeroRadius_ = radius; + filter_.setNotch( zeroFrequency_, zeroRadius_ ); +} + +void Resonate :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Resonate::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == 2) // 2 + setResonance( normalizedValue * Stk::sampleRate() * 0.5, poleRadius_ ); + else if (number == 4) // 4 + setResonance( poleFrequency_, normalizedValue * 0.9999 ); + else if (number == 11) // 11 + this->setNotch( normalizedValue * Stk::sampleRate() * 0.5, zeroRadius_ ); + else if (number == 1) + this->setNotch( zeroFrequency_, normalizedValue ); + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Resonate::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Rhodey.cpp b/7.x/src/stk/src/Rhodey.cpp new file mode 100644 index 0000000..f1a415c --- /dev/null +++ b/7.x/src/stk/src/Rhodey.cpp @@ -0,0 +1,92 @@ +/***************************************************/ +/*! \class Rhodey + \brief STK Fender Rhodes-like electric piano FM + synthesis instrument. + + This class implements two simple FM Pairs + summed together, also referred to as algorithm + 5 of the TX81Z. + + \code + Algorithm 5 is : 4->3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Rhodey.h" + +namespace stk { + +Rhodey :: Rhodey( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 1.0); + this->setRatio(1, 0.5); + this->setRatio(2, 1.0); + this->setRatio(3, 15.0); + + gains_[0] = fmGains_[99]; + gains_[1] = fmGains_[90]; + gains_[2] = fmGains_[99]; + gains_[3] = fmGains_[67]; + + adsr_[0]->setAllTimes( 0.001, 1.50, 0.0, 0.04); + adsr_[1]->setAllTimes( 0.001, 1.50, 0.0, 0.04); + adsr_[2]->setAllTimes( 0.001, 1.00, 0.0, 0.04); + adsr_[3]->setAllTimes( 0.001, 0.25, 0.0, 0.04); + + twozero_.setGain( 1.0 ); +} + +Rhodey :: ~Rhodey( void ) +{ +} + +void Rhodey :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Rhodey::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency * 2.0; + + for (unsigned int i=0; isetFrequency( baseFrequency_ * ratios_[i] ); +} + +void Rhodey :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[99]; + gains_[1] = amplitude * fmGains_[90]; + gains_[2] = amplitude * fmGains_[99]; + gains_[3] = amplitude * fmGains_[67]; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Sampler.cpp b/7.x/src/stk/src/Sampler.cpp new file mode 100644 index 0000000..f373b1f --- /dev/null +++ b/7.x/src/stk/src/Sampler.cpp @@ -0,0 +1,53 @@ +/***************************************************/ +/*! \class Sampler + \brief STK sampling synthesis abstract base class. + + This instrument provides an ADSR envelope, a one-pole filter, and + structures for an arbitrary number of attack and looped files. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Sampler.h" + +namespace stk { + +Sampler :: Sampler( void ) +{ + // We don't make the waves here yet, because + // we don't know what they will be. + baseFrequency_ = 440.0; + attackGain_ = 0.25; + loopGain_ = 0.25; +} + +Sampler :: ~Sampler( void ) +{ + unsigned int i; + for ( i=0; ireset(); + + // Start the envelope. + adsr_.keyOn(); + +} + +void Sampler :: keyOff( void ) +{ + adsr_.keyOff(); +} + +void Sampler :: noteOff( StkFloat amplitude ) +{ + this->keyOff(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Saxofony.cpp b/7.x/src/stk/src/Saxofony.cpp new file mode 100644 index 0000000..0877a54 --- /dev/null +++ b/7.x/src/stk/src/Saxofony.cpp @@ -0,0 +1,177 @@ +/***************************************************/ +/*! \class Saxofony + \brief STK faux conical bore reed instrument class. + + This class implements a "hybrid" digital + waveguide instrument that can generate a + variety of wind-like sounds. It has also been + referred to as the "blowed string" model. The + waveguide section is essentially that of a + string, with one rigid and one lossy + termination. The non-linear function is a + reed table. The string can be "blown" at any + point between the terminations, though just as + with strings, it is impossible to excite the + system at either end. If the excitation is + placed at the string mid-point, the sound is + that of a clarinet. At points closer to the + "bridge", the sound is closer to that of a + saxophone. See Scavone (2002) for more details. + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + Control Change Numbers: + - Reed Stiffness = 2 + - Reed Aperture = 26 + - Noise Gain = 4 + - Blow Position = 11 + - Vibrato Frequency = 29 + - Vibrato Gain = 1 + - Breath Pressure = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Saxofony.h" +#include "SKINImsg.h" + +namespace stk { + +Saxofony :: Saxofony( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Saxofony::Saxofony: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / lowestFrequency ); + delays_[0].setMaximumDelay( nDelays + 1 ); + delays_[1].setMaximumDelay( nDelays + 1 ); + + // Initialize blowing position to 0.2 of length. + position_ = 0.2; + + reedTable_.setOffset( 0.7 ); + reedTable_.setSlope( 0.3 ); + + vibrato_.setFrequency( 5.735 ); + + outputGain_ = 0.3; + noiseGain_ = 0.2; + vibratoGain_ = 0.1; + + this->setFrequency( 220.0 ); + this->clear(); +} + +Saxofony :: ~Saxofony( void ) +{ +} + +void Saxofony :: clear( void ) +{ + delays_[0].clear(); + delays_[1].clear(); + filter_.clear(); +} + +void Saxofony :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Saxofony::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + // Account for filter delay and one sample "lastOut" delay. + StkFloat delay = ( Stk::sampleRate() / frequency ) - filter_.phaseDelay( frequency ) - 1.0; + delays_[0].setDelay( (1.0-position_) * delay ); + delays_[1].setDelay( position_ * delay ); +} + +void Saxofony :: setBlowPosition( StkFloat position ) +{ + if ( position_ == position ) return; + + if ( position < 0.0 ) position_ = 0.0; + else if ( position > 1.0 ) position_ = 1.0; + else position_ = position; + + StkFloat totalDelay = delays_[0].getDelay(); + totalDelay += delays_[1].getDelay(); + + delays_[0].setDelay( (1.0-position_) * totalDelay ); + delays_[1].setDelay( position_ * totalDelay ); +} + +void Saxofony :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Saxofony::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( amplitude ); +} + +void Saxofony :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Saxofony::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.setTarget( 0.0 ); +} + +void Saxofony :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( 0.55 + (amplitude * 0.30), amplitude * 0.005 ); + outputGain_ = amplitude + 0.001; +} + +void Saxofony :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.01 ); +} + +void Saxofony :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Saxofony::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_ReedStiffness_) // 2 + reedTable_.setSlope( 0.1 + (0.4 * normalizedValue) ); + else if (number == __SK_NoiseLevel_) // 4 + noiseGain_ = ( normalizedValue * 0.4 ); + else if (number == 29) // 29 + vibrato_.setFrequency( normalizedValue * 12.0 ); + else if (number == __SK_ModWheel_) // 1 + vibratoGain_ = ( normalizedValue * 0.5 ); + else if (number == __SK_AfterTouch_Cont_) // 128 + envelope_.setValue( normalizedValue ); + else if (number == 11) // 11 + this->setBlowPosition( normalizedValue ); + else if (number == 26) // reed table offset + reedTable_.setOffset(0.4 + ( normalizedValue * 0.6)); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Saxofony::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Shakers.cpp b/7.x/src/stk/src/Shakers.cpp new file mode 100644 index 0000000..48ced91 --- /dev/null +++ b/7.x/src/stk/src/Shakers.cpp @@ -0,0 +1,729 @@ +/***************************************************/ +/*! \class Shakers + \brief PhISEM and PhOLIES class. + + PhISEM (Physically Informed Stochastic Event Modeling) is an + algorithmic approach for simulating collisions of multiple + independent sound producing objects. This class is a meta-model + that can simulate a Maraca, Sekere, Cabasa, Bamboo Wind Chimes, + Water Drops, Tambourine, Sleighbells, and a Guiro. + + PhOLIES (Physically-Oriented Library of Imitated Environmental + Sounds) is a similar approach for the synthesis of environmental + sounds. This class implements simulations of breaking sticks, + crunchy snow (or not), a wrench, sandpaper, and more. + + Control Change Numbers: + - Shake Energy = 2 + - System Decay = 4 + - Number Of Objects = 11 + - Resonance Frequency = 1 + - Shake Energy = 128 + - Instrument Selection = 1071 + - Maraca = 0 + - Cabasa = 1 + - Sekere = 2 + - Tambourine = 3 + - Sleigh Bells = 4 + - Bamboo Chimes = 5 + - Sand Paper = 6 + - Coke Can = 7 + - Sticks = 8 + - Crunch = 9 + - Big Rocks = 10 + - Little Rocks = 11 + - Next Mug = 12 + - Penny + Mug = 13 + - Nickle + Mug = 14 + - Dime + Mug = 15 + - Quarter + Mug = 16 + - Franc + Mug = 17 + - Peso + Mug = 18 + - Guiro = 19 + - Wrench = 20 + - Water Drops = 21 + - Tuned Bamboo Chimes = 22 + + by Perry R. Cook with updates by Gary Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Shakers.h" +#include "SKINImsg.h" + +namespace stk { + +// Maraca +const StkFloat MARACA_SOUND_DECAY = 0.95; +const StkFloat MARACA_SYSTEM_DECAY = 0.999; +const StkFloat MARACA_GAIN = 4.0; +const StkFloat MARACA_NUM_BEANS = 25; +const int MARACA_RESONANCES = 1; +const StkFloat MARACA_FREQUENCIES[MARACA_RESONANCES] = { 3200 }; +const StkFloat MARACA_RADII[MARACA_RESONANCES] = { 0.96 }; +const StkFloat MARACA_GAINS[MARACA_RESONANCES] = { 1.0 }; + +// Cabasa +const StkFloat CABASA_SOUND_DECAY = 0.96; +const StkFloat CABASA_SYSTEM_DECAY = 0.997; +const StkFloat CABASA_GAIN = 8.0; +const StkFloat CABASA_NUM_BEADS = 512; +const int CABASA_RESONANCES = 1; +const StkFloat CABASA_FREQUENCIES[CABASA_RESONANCES] = { 3000 }; +const StkFloat CABASA_RADII[CABASA_RESONANCES] = { 0.7 }; +const StkFloat CABASA_GAINS[CABASA_RESONANCES] = { 1.0 }; + +// Sekere +const StkFloat SEKERE_SOUND_DECAY = 0.96; +const StkFloat SEKERE_SYSTEM_DECAY = 0.999; +const StkFloat SEKERE_GAIN = 4.0; +const StkFloat SEKERE_NUM_BEANS = 64; +const int SEKERE_RESONANCES = 1; +const StkFloat SEKERE_FREQUENCIES[SEKERE_RESONANCES] = { 5500 }; +const StkFloat SEKERE_RADII[SEKERE_RESONANCES] = { 0.6 }; +const StkFloat SEKERE_GAINS[SEKERE_RESONANCES] = { 1.0 }; + +// Bamboo Wind Chimes +const StkFloat BAMBOO_SOUND_DECAY = 0.9; +const StkFloat BAMBOO_SYSTEM_DECAY = 0.9999; +const StkFloat BAMBOO_GAIN = 0.4; +const StkFloat BAMBOO_NUM_TUBES = 1.2; +const int BAMBOO_RESONANCES = 3; +const StkFloat BAMBOO_FREQUENCIES[BAMBOO_RESONANCES] = { 2800, 0.8 * 2800.0, 1.2 * 2800.0 }; +const StkFloat BAMBOO_RADII[BAMBOO_RESONANCES] = { 0.995, 0.995, 0.995 }; +const StkFloat BAMBOO_GAINS[BAMBOO_RESONANCES] = { 1.0, 1.0, 1.0 }; + +// Tambourine +const StkFloat TAMBOURINE_SOUND_DECAY = 0.95; +const StkFloat TAMBOURINE_SYSTEM_DECAY = 0.9985; +const StkFloat TAMBOURINE_GAIN = 1.0; +const StkFloat TAMBOURINE_NUM_TIMBRELS = 32; +const int TAMBOURINE_RESONANCES = 3; // Fixed shell + 2 moving cymbal resonances +const StkFloat TAMBOURINE_FREQUENCIES[TAMBOURINE_RESONANCES] = { 2300, 5600, 8100 }; +const StkFloat TAMBOURINE_RADII[TAMBOURINE_RESONANCES] = { 0.96, 0.99, 0.99 }; +const StkFloat TAMBOURINE_GAINS[TAMBOURINE_RESONANCES] = { 0.1, 0.8, 1.0 }; + +// Sleighbells +const StkFloat SLEIGH_SOUND_DECAY = 0.97; +const StkFloat SLEIGH_SYSTEM_DECAY = 0.9994; +const StkFloat SLEIGH_GAIN = 1.0; +const StkFloat SLEIGH_NUM_BELLS = 32; +const int SLEIGH_RESONANCES = 5; +const StkFloat SLEIGH_FREQUENCIES[SLEIGH_RESONANCES] = { 2500, 5300, 6500, 8300, 9800 }; +const StkFloat SLEIGH_RADII[SLEIGH_RESONANCES] = { 0.99, 0.99, 0.99, 0.99, 0.99 }; +const StkFloat SLEIGH_GAINS[SLEIGH_RESONANCES] = { 1.0, 1.0, 1.0, 0.5, 0.3 }; + +// Sandpaper +const StkFloat SANDPAPER_SOUND_DECAY = 0.999; +const StkFloat SANDPAPER_SYSTEM_DECAY = 0.999; +const StkFloat SANDPAPER_GAIN = 0.5; +const StkFloat SANDPAPER_NUM_GRAINS = 128; +const int SANDPAPER_RESONANCES = 1; +const StkFloat SANDPAPER_FREQUENCIES[SANDPAPER_RESONANCES] = { 4500 }; +const StkFloat SANDPAPER_RADII[SANDPAPER_RESONANCES] = { 0.6 }; +const StkFloat SANDPAPER_GAINS[SANDPAPER_RESONANCES] = { 1.0 }; + +// Cokecan +const StkFloat COKECAN_SOUND_DECAY = 0.97; +const StkFloat COKECAN_SYSTEM_DECAY = 0.999; +const StkFloat COKECAN_GAIN = 0.5; +const StkFloat COKECAN_NUM_PARTS = 48; +const int COKECAN_RESONANCES = 5; // Helmholtz + 4 metal resonances +const StkFloat COKECAN_FREQUENCIES[COKECAN_RESONANCES] = { 370, 1025, 1424, 2149, 3596 }; +const StkFloat COKECAN_RADII[COKECAN_RESONANCES] = { 0.99, 0.992, 0.992, 0.992, 0.992 }; +const StkFloat COKECAN_GAINS[COKECAN_RESONANCES] = { 1.0, 1.8, 1.8, 1.8, 1.8 }; + +// Tuned Bamboo Wind Chimes (Angklung) +const StkFloat ANGKLUNG_SOUND_DECAY = 0.95; +const StkFloat ANGKLUNG_SYSTEM_DECAY = 0.9999; +const StkFloat ANGKLUNG_GAIN = 0.5; +const StkFloat ANGKLUNG_NUM_TUBES = 1.2; +const int ANGKLUNG_RESONANCES = 7; +const StkFloat ANGKLUNG_FREQUENCIES[ANGKLUNG_RESONANCES] = { 1046.6, 1174.8, 1397.0, 1568, 1760, 2093.3, 2350 }; +const StkFloat ANGKLUNG_RADII[ANGKLUNG_RESONANCES] = { 0.996, 0.996, 0.996, 0.996, 0.996, 0.996, 0.996 }; +const StkFloat ANGKLUNG_GAINS[ANGKLUNG_RESONANCES] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + +// Guiro +const StkFloat GUIRO_SOUND_DECAY = 0.95; +const StkFloat GUIRO_GAIN = 0.4; +const StkFloat GUIRO_NUM_PARTS = 128; +const int GUIRO_RESONANCES = 2; +const StkFloat GUIRO_FREQUENCIES[GUIRO_RESONANCES] = { 2500, 4000 }; +const StkFloat GUIRO_RADII[GUIRO_RESONANCES] = { 0.97, 0.97 }; +const StkFloat GUIRO_GAINS[GUIRO_RESONANCES] = { 1.0, 1.0 }; + +// Wrench +const StkFloat WRENCH_SOUND_DECAY = 0.95; +const StkFloat WRENCH_GAIN = 0.4; +const StkFloat WRENCH_NUM_PARTS = 128; +const int WRENCH_RESONANCES = 2; +const StkFloat WRENCH_FREQUENCIES[WRENCH_RESONANCES] = { 3200, 8000 }; +const StkFloat WRENCH_RADII[WRENCH_RESONANCES] = { 0.99, 0.992 }; +const StkFloat WRENCH_GAINS[WRENCH_RESONANCES] = { 1.0, 1.0 }; + +// Water Drops +const StkFloat WATER_SOUND_DECAY = 0.95; +const StkFloat WATER_SYSTEM_DECAY = 0.996; +const StkFloat WATER_GAIN = 1.0; +const StkFloat WATER_NUM_SOURCES = 10; +const int WATER_RESONANCES = 3; +const StkFloat WATER_FREQUENCIES[WATER_RESONANCES] = { 450, 600, 750 }; +const StkFloat WATER_RADII[WATER_RESONANCES] = { 0.9985, 0.9985, 0.9985 }; +const StkFloat WATER_GAINS[WATER_RESONANCES] = { 1.0, 1.0, 1.0 }; + +// PhOLIES (Physically-Oriented Library of Imitated Environmental +// Sounds), Perry Cook, 1997-8 + +// Stix1 +const StkFloat STIX1_SOUND_DECAY = 0.96; +const StkFloat STIX1_SYSTEM_DECAY = 0.998; +const StkFloat STIX1_GAIN = 6.0; +const StkFloat STIX1_NUM_BEANS = 2; +const int STIX1_RESONANCES = 1; +const StkFloat STIX1_FREQUENCIES[STIX1_RESONANCES] = { 5500 }; +const StkFloat STIX1_RADII[STIX1_RESONANCES] = { 0.6 }; +const StkFloat STIX1_GAINS[STIX1_RESONANCES] = { 1.0 }; + +// Crunch1 +const StkFloat CRUNCH1_SOUND_DECAY = 0.95; +const StkFloat CRUNCH1_SYSTEM_DECAY = 0.99806; +const StkFloat CRUNCH1_GAIN = 4.0; +const StkFloat CRUNCH1_NUM_BEADS = 7; +const int CRUNCH1_RESONANCES = 1; +const StkFloat CRUNCH1_FREQUENCIES[CRUNCH1_RESONANCES] = { 800 }; +const StkFloat CRUNCH1_RADII[CRUNCH1_RESONANCES] = { 0.95 }; +const StkFloat CRUNCH1_GAINS[CRUNCH1_RESONANCES] = { 1.0 }; + +// Nextmug + Coins +const StkFloat NEXTMUG_SOUND_DECAY = 0.97; +const StkFloat NEXTMUG_SYSTEM_DECAY = 0.9995; +const StkFloat NEXTMUG_GAIN = 0.8; +const StkFloat NEXTMUG_NUM_PARTS = 3; +const int NEXTMUG_RESONANCES = 4; +const StkFloat NEXTMUG_FREQUENCIES[NEXTMUG_RESONANCES] = { 2123, 4518, 8856, 10753 }; +const StkFloat NEXTMUG_RADII[NEXTMUG_RESONANCES] = { 0.997, 0.997, 0.997, 0.997 }; +const StkFloat NEXTMUG_GAINS[NEXTMUG_RESONANCES] = { 1.0, 0.8, 0.6, 0.4 }; + +const int COIN_RESONANCES = 3; +const StkFloat PENNY_FREQUENCIES[COIN_RESONANCES] = { 11000, 5200, 3835 }; +const StkFloat PENNY_RADII[COIN_RESONANCES] = { 0.999, 0.999, 0.999 }; +const StkFloat PENNY_GAINS[COIN_RESONANCES] = { 1.0, 0.8, 0.5 }; + +const StkFloat NICKEL_FREQUENCIES[COIN_RESONANCES] = { 5583, 9255, 9805 }; +const StkFloat NICKEL_RADII[COIN_RESONANCES] = { 0.9992, 0.9992, 0.9992 }; +const StkFloat NICKEL_GAINS[COIN_RESONANCES] = { 1.0, 0.8, 0.5 }; + +const StkFloat DIME_FREQUENCIES[COIN_RESONANCES] = { 4450, 4974, 9945 }; +const StkFloat DIME_RADII[COIN_RESONANCES] = { 0.9993, 0.9993, 0.9993 }; +const StkFloat DIME_GAINS[COIN_RESONANCES] = { 1.0, 0.8, 0.5 }; + +const StkFloat QUARTER_FREQUENCIES[COIN_RESONANCES] = { 1708, 8863, 9045 }; +const StkFloat QUARTER_RADII[COIN_RESONANCES] = { 0.9995, 0.9995, 0.9995 }; +const StkFloat QUARTER_GAINS[COIN_RESONANCES] = { 1.0, 0.8, 0.5 }; + +const StkFloat FRANC_FREQUENCIES[COIN_RESONANCES] = { 5583, 11010, 1917 }; +const StkFloat FRANC_RADII[COIN_RESONANCES] = { 0.9995, 0.9995, 0.9995 }; +const StkFloat FRANC_GAINS[COIN_RESONANCES] = { 0.7, 0.4, 0.3 }; + +const StkFloat PESO_FREQUENCIES[COIN_RESONANCES] = { 7250, 8150, 10060 }; +const StkFloat PESO_RADII[COIN_RESONANCES] = { 0.9996, 0.9996, 0.9996 }; +const StkFloat PESO_GAINS[COIN_RESONANCES] = { 1.0, 1.2, 0.7 }; + +// Big Gravel +const StkFloat BIGROCKS_SOUND_DECAY = 0.98; +const StkFloat BIGROCKS_SYSTEM_DECAY = 0.9965; +const StkFloat BIGROCKS_GAIN = 4.0; +const StkFloat BIGROCKS_NUM_PARTS = 23; +const int BIGROCKS_RESONANCES = 1; +const StkFloat BIGROCKS_FREQUENCIES[BIGROCKS_RESONANCES] = { 6460 }; +const StkFloat BIGROCKS_RADII[BIGROCKS_RESONANCES] = { 0.932 }; +const StkFloat BIGROCKS_GAINS[BIGROCKS_RESONANCES] = { 1.0 }; + +// Little Gravel +const StkFloat LITTLEROCKS_SOUND_DECAY = 0.98; +const StkFloat LITTLEROCKS_SYSTEM_DECAY = 0.99586; +const StkFloat LITTLEROCKS_GAIN = 4.0; +const StkFloat LITTLEROCKS_NUM_PARTS = 1600; +const int LITTLEROCKS_RESONANCES = 1; +const StkFloat LITTLEROCKS_FREQUENCIES[LITTLEROCKS_RESONANCES] = { 9000 }; +const StkFloat LITTLEROCKS_RADII[LITTLEROCKS_RESONANCES] = { 0.843 }; +const StkFloat LITTLEROCKS_GAINS[LITTLEROCKS_RESONANCES] = { 1.0 }; + +Shakers :: Shakers( int type ) +{ + shakerType_ = -1; + this->setType( type ); +} + +void Shakers :: setType( int type ) +{ + if ( shakerType_ == type ) return; + varyFactor_ = 0.0; + shakerType_ = type; + if ( type == 1 ) { // Cabasa + nResonances_ = CABASA_RESONANCES; + filters_.resize( nResonances_ ); + baseFrequencies_.resize( nResonances_ ); + baseRadii_.resize( nResonances_ ); + doVaryFrequency_.resize( nResonances_ ); + baseObjects_ = CABASA_NUM_BEADS; + for ( unsigned int i=0; i 11 && type < 19 ) { // Nextmug + nResonances_ = NEXTMUG_RESONANCES; + if ( type > 12 ) // mug + coin + nResonances_ += COIN_RESONANCES; + filters_.resize( nResonances_ ); + baseFrequencies_.resize( nResonances_ ); + baseRadii_.resize( nResonances_ ); + doVaryFrequency_.resize( nResonances_ ); + baseObjects_ = NEXTMUG_NUM_PARTS; + for ( int i=0; isetType( noteNumber ); + + shakeEnergy_ += amplitude * MAX_SHAKE * 0.1; + if ( shakeEnergy_ > MAX_SHAKE ) shakeEnergy_ = MAX_SHAKE; + if ( shakerType_==19 || shakerType_==20 ) ratchetCount_ += 1; +} + +void Shakers :: noteOff( StkFloat amplitude ) +{ + shakeEnergy_ = 0.0; + if ( shakerType_==19 || shakerType_==20 ) ratchetCount_ = 0; +} + +void Shakers :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Shakers::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == __SK_Breath_ || number == __SK_AfterTouch_Cont_ ) { // 2 or 128 ... energy + if ( shakerType_ == 19 || shakerType_ == 20 ) { + if ( lastRatchetValue_ < 0.0 ) ratchetCount_++; + else ratchetCount_ = (int) fabs(value - lastRatchetValue_); + ratchetDelta_ = baseRatchetDelta_ * ratchetCount_; + lastRatchetValue_ = (int) value; + } + else { + shakeEnergy_ += normalizedValue * MAX_SHAKE * 0.1; + if ( shakeEnergy_ > MAX_SHAKE ) shakeEnergy_ = MAX_SHAKE; + } + } + else if ( number == __SK_ModFrequency_ ) { // 4 ... decay + systemDecay_ = baseDecay_ + ( 2.0 * (normalizedValue - 0.5) * decayScale_ * (1.0 - baseDecay_) ); + } + else if ( number == __SK_FootControl_ ) { // 11 ... number of objects + nObjects_ = (StkFloat) ( 2.0 * normalizedValue * baseObjects_ ) + 1.1; + currentGain_ = log( nObjects_ ) * baseGain_ / nObjects_; + } + else if ( number == __SK_ModWheel_ ) { // 1 ... resonance frequency + for ( unsigned int i=0; isetType( type ); + } +#if defined(_STK_DEBUG_) + else { + oStream_ << "Shakers::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Simple.cpp b/7.x/src/stk/src/Simple.cpp new file mode 100644 index 0000000..69fc83d --- /dev/null +++ b/7.x/src/stk/src/Simple.cpp @@ -0,0 +1,105 @@ +/***************************************************/ +/*! \class Simple + \brief STK wavetable/noise instrument. + + This class combines a looped wave, a + noise source, a biquad resonance filter, + a one-pole filter, and an ADSR envelope + to create some interesting sounds. + + Control Change Numbers: + - Filter Pole Position = 2 + - Noise/Pitched Cross-Fade = 4 + - Envelope Rate = 11 + - Gain = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Simple.h" +#include "SKINImsg.h" + +namespace stk { + +Simple :: Simple( void ) +{ + // Concatenate the STK rawwave path to the rawwave file + loop_ = new FileLoop( (Stk::rawwavePath() + "impuls10.raw").c_str(), true ); + + filter_.setPole( 0.5 ); + baseFrequency_ = 440.0; + setFrequency( baseFrequency_ ); + loopGain_ = 0.5; +} + +Simple :: ~Simple( void ) +{ + delete loop_; +} + +void Simple :: keyOn( void ) +{ + adsr_.keyOn(); +} + +void Simple :: keyOff( void ) +{ + adsr_.keyOff(); +} + +void Simple :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->keyOn(); + this->setFrequency( frequency ); + filter_.setGain( amplitude ); +} +void Simple :: noteOff( StkFloat amplitude ) +{ + this->keyOff(); +} + +void Simple :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Simple::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + biquad_.setResonance( frequency, 0.98, true ); + loop_->setFrequency( frequency ); +} + +void Simple :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Simple::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_Breath_) // 2 + filter_.setPole( 0.99 * (1.0 - (normalizedValue * 2.0)) ); + else if (number == __SK_NoiseLevel_) // 4 + loopGain_ = normalizedValue; + else if (number == __SK_ModFrequency_) { // 11 + normalizedValue /= 0.2 * Stk::sampleRate(); + adsr_.setAttackRate( normalizedValue ); + adsr_.setDecayRate( normalizedValue ); + adsr_.setReleaseRate( normalizedValue ); + } + else if (number == __SK_AfterTouch_Cont_) // 128 + adsr_.setTarget( normalizedValue ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "Simple::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/SineWave.cpp b/7.x/src/stk/src/SineWave.cpp new file mode 100644 index 0000000..c362968 --- /dev/null +++ b/7.x/src/stk/src/SineWave.cpp @@ -0,0 +1,78 @@ +/***************************************************/ +/*! \class SineWave + \brief STK sinusoid oscillator class. + + This class computes and saves a static sine "table" that can be + shared by multiple instances. It has an interface similar to the + WaveLoop class but inherits from the Generator class. Output + values are computed using linear interpolation. + + The "table" length, set in SineWave.h, is 2048 samples by default. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "SineWave.h" +#include + +namespace stk { + +StkFrames SineWave :: table_; + +SineWave :: SineWave( void ) + : time_(0.0), rate_(1.0), phaseOffset_(0.0) +{ + if ( table_.empty() ) { + table_.resize( TABLE_SIZE + 1, 1 ); + StkFloat temp = 1.0 / TABLE_SIZE; + for ( unsigned long i=0; i<=TABLE_SIZE; i++ ) + table_[i] = sin( TWO_PI * i * temp ); + } + + Stk::addSampleRateAlert( this ); +} + +SineWave :: ~SineWave() +{ + Stk::removeSampleRateAlert( this ); +} + +void SineWave :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) + this->setRate( oldRate * rate_ / newRate ); +} + +void SineWave :: reset( void ) +{ + time_ = 0.0; + lastFrame_[0] = 0; +} + +void SineWave :: setFrequency( StkFloat frequency ) +{ + // This is a looping frequency. + this->setRate( TABLE_SIZE * frequency / Stk::sampleRate() ); +} + +void SineWave :: addTime( StkFloat time ) +{ + // Add an absolute time in samples. + time_ += time; +} + +void SineWave :: addPhase( StkFloat phase ) +{ + // Add a time in cycles (one cycle = TABLE_SIZE). + time_ += TABLE_SIZE * phase; +} + +void SineWave :: addPhaseOffset( StkFloat phaseOffset ) +{ + // Add a phase offset relative to any previous offset value. + time_ += ( phaseOffset - phaseOffset_ ) * TABLE_SIZE; + phaseOffset_ = phaseOffset; +} + +} // stk namespace diff --git a/7.x/src/stk/src/SingWave.cpp b/7.x/src/stk/src/SingWave.cpp new file mode 100644 index 0000000..6d80770 --- /dev/null +++ b/7.x/src/stk/src/SingWave.cpp @@ -0,0 +1,53 @@ +/***************************************************/ +/*! \class SingWave + \brief STK "singing" looped soundfile class. + + This class loops a specified soundfile and modulates it both + periodically and randomly to produce a pitched musical sound, like + a simple voice or violin. In general, it is not be used alone + because of "munchkinification" effects from pitch shifting. + Within STK, it is used as an excitation source for other + instruments. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "SingWave.h" + +namespace stk { + +SingWave :: SingWave( std::string fileName, bool raw ) +{ + // An exception could be thrown here. + wave_.openFile( fileName, raw ); + + rate_ = 1.0; + sweepRate_ = 0.001; + + modulator_.setVibratoRate( 6.0 ); + modulator_.setVibratoGain( 0.04 ); + modulator_.setRandomGain( 0.005 ); + + this->setFrequency( 75.0 ); + pitchEnvelope_.setRate( 1.0 ); + this->tick(); + this->tick(); + pitchEnvelope_.setRate( sweepRate_ * rate_ ); +} + +SingWave :: ~SingWave() +{ +} + +void SingWave :: setFrequency( StkFloat frequency ) +{ + StkFloat temp = rate_; + rate_ = wave_.getSize() * frequency / Stk::sampleRate(); + temp -= rate_; + if ( temp < 0) temp = -temp; + pitchEnvelope_.setTarget( rate_ ); + pitchEnvelope_.setRate( sweepRate_ * temp ); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Sitar.cpp b/7.x/src/stk/src/Sitar.cpp new file mode 100644 index 0000000..ab5f405 --- /dev/null +++ b/7.x/src/stk/src/Sitar.cpp @@ -0,0 +1,95 @@ +/***************************************************/ +/*! \class Sitar + \brief STK sitar string model class. + + This class implements a sitar plucked string + physical model based on the Karplus-Strong + algorithm. + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + There exist at least two patents, assigned to + Stanford, bearing the names of Karplus and/or + Strong. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Sitar.h" + +namespace stk { + +Sitar :: Sitar( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Sitar::Sitar: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long length = (unsigned long) ( Stk::sampleRate() / lowestFrequency + 1 ); + delayLine_.setMaximumDelay( length ); + delay_ = 0.5 * length; + delayLine_.setDelay( delay_ ); + targetDelay_ = delay_; + + loopFilter_.setZero( 0.01 ); + loopGain_ = 0.999; + + envelope_.setAllTimes( 0.001, 0.04, 0.0, 0.5 ); + this->clear(); +} + +Sitar :: ~Sitar( void ) +{ +} + +void Sitar :: clear( void ) +{ + delayLine_.clear(); + loopFilter_.clear(); +} + +void Sitar :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Sitar::setFrequency: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + targetDelay_ = (Stk::sampleRate() / frequency); + // Do not modulate the delay line with noise here: doing so injects energy + // on every frequency change and can make the sitar build up clipped samples + // over time (csound/plugins#4). The noise excitation is applied in tick(). + delay_ = targetDelay_; + delayLine_.setDelay( delay_ ); + loopGain_ = 0.995 + (frequency * 0.0000005); + if ( loopGain_ > 0.9995 ) loopGain_ = 0.9995; +} + +void Sitar :: pluck( StkFloat amplitude ) +{ + envelope_.keyOn(); +} + +void Sitar :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->pluck( amplitude ); + amGain_ = 0.1 * amplitude; +} + +void Sitar :: noteOff( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "Sitar::noteOff: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + loopGain_ = (StkFloat) 1.0 - amplitude; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Skini.cpp b/7.x/src/stk/src/Skini.cpp new file mode 100644 index 0000000..aec29a5 --- /dev/null +++ b/7.x/src/stk/src/Skini.cpp @@ -0,0 +1,228 @@ +/***************************************************/ +/*! \class Skini + \brief STK SKINI parsing class + + This class parses SKINI formatted text + messages. It can be used to parse individual + messages or it can be passed an entire file. + The SKINI specification is Perry's and his + alone, but it's all text so it shouldn't be too + hard to figure out. + + SKINI (Synthesis toolKit Instrument Network + Interface) is like MIDI, but allows for + floating-point control changes, note numbers, + etc. The following example causes a sharp + middle C to be played with a velocity of 111.132: + + noteOn 60.01 111.132 + + See also SKINI.txt. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Skini.h" +#include "SKINItbl.h" +#include +#include + +namespace stk { + +Skini :: Skini() +{ +} + +Skini :: ~Skini() +{ +} + +bool Skini :: setFile( std::string fileName ) +{ + if ( file_.is_open() ) { + oStream_ << "Skini::setFile: already reaading a file!"; + handleError( StkError::WARNING ); + return false; + } + + file_.open( fileName.c_str() ); + if ( !file_ ) { + oStream_ << "Skini::setFile: unable to open file (" << fileName << ")"; + handleError( StkError::WARNING ); + return false; + } + + return true; +} + +long Skini :: nextMessage( Message& message ) +{ + if ( !file_.is_open() ) return 0; + + std::string line; + bool done = false; + while ( !done ) { + + // Read a line from the file and skip over invalid messages. + if ( std::getline( file_, line ).eof() ) { + oStream_ << "// End of Score. Thanks for using SKINI!!"; + handleError( StkError::STATUS ); + file_.close(); + message.type = 0; + done = true; + } + else if ( parseString( line, message ) > 0 ) done = true; + } + + return message.type; +} + +void Skini :: tokenize( const std::string& str, + std::vector& tokens, + const std::string& delimiters ) +{ + // Skip delimiters at beginning. + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); + // Find first "non-delimiter". + std::string::size_type pos = str.find_first_of(delimiters, lastPos); + + while (std::string::npos != pos || std::string::npos != lastPos) { + // Found a token, add it to the vector. + tokens.push_back(str.substr(lastPos, pos - lastPos)); + // Skip delimiters. Note the "not_of" + lastPos = str.find_first_not_of(delimiters, pos); + // Find next "non-delimiter" + pos = str.find_first_of(delimiters, lastPos); + } +} + +long Skini :: parseString( std::string& line, Message& message ) +{ + message.type = 0; + if ( line.empty() ) return message.type; + + // Check for comment lines. + std::string::size_type lastPos = line.find_first_not_of(" ,\t", 0); + std::string::size_type pos = line.find_first_of("/", lastPos); + if ( std::string::npos != pos ) { + oStream_ << "// Comment Line: " << line; + handleError( StkError::STATUS ); + return message.type; + } + + // Tokenize the string. + std::vector tokens; + this->tokenize( line, tokens, " ,\t"); + + // Valid SKINI messages must have at least three fields (type, time, + // and channel). + if ( tokens.size() < 3 ) return message.type; + + // Determine message type. + int iSkini = 0; + while ( iSkini < __SK_MaxMsgTypes_ ) { + if ( tokens[0] == skini_msgs[iSkini].messageString ) break; + iSkini++; + } + + if ( iSkini >= __SK_MaxMsgTypes_ ) { + oStream_ << "Skini::parseString: couldn't parse this line:\n " << line; + handleError( StkError::WARNING ); + return message.type; + } + + // Found the type. + message.type = skini_msgs[iSkini].type; + + // Parse time field. + if ( tokens[1][0] == '=' ) { + tokens[1].erase( tokens[1].begin() ); + if ( tokens[1].empty() ) { + oStream_ << "Skini::parseString: couldn't parse time field in line:\n " << line; + handleError( StkError::WARNING ); + return message.type = 0; + } + message.time = (StkFloat) -atof( tokens[1].c_str() ); + } + else + message.time = (StkFloat) atof( tokens[1].c_str() ); + + // Parse the channel field. + message.channel = atoi( tokens[2].c_str() ); + + // Parse the remaining fields (maximum of 2 more). + int iValue = 0; + unsigned int iToken = iValue + 3; //rgh: MIDI extension argument counts are different from regular MIDI + long dataType = skini_msgs[iSkini].data2; + while ( dataType != NOPE ) { + +// if ( tokens.size() <= (unsigned int) (iValue+3) ) { //rgh: test iToken rather than always testing iValue+3 +// if (tokens.size() <= iToken) { //rgh: iToken only tests it more tokens are to be consumed (SK_INT, SK_DBL, SK_STR) + if ((tokens.size() <= iToken) && (dataType < 0)) { //Don't fail if remaining iValues come from skini_msgs[] rather than tokens[]. + oStream_ << "Skini::parseString: inconsistency between type table and parsed line:\n " << line; + handleError( StkError::WARNING ); + return message.type = 0; + } + + switch ( dataType ) { + + case SK_INT: + message.intValues[iValue] = atoi( tokens[iToken].c_str() ); //rgh: use new index + message.floatValues[iValue] = (StkFloat) message.intValues[iValue]; + ++iToken; //rgh: increment token index and value index (below) + break; + + case SK_DBL: + message.floatValues[iValue] = atof( tokens[iToken].c_str() ); //rgh: use new index + message.intValues[iValue] = (long) message.floatValues[iValue]; + ++iToken; //rgh: increment token index and value index (below) + break; + + case SK_STR: // Must be the last field. + message.remainder = tokens[iToken]; //rgh: use new index + return message.type; + + default: // MIDI extension message + message.intValues[iValue] = dataType; + message.floatValues[iValue] = (StkFloat) message.intValues[iValue]; + //iValue--; //rgh: iValue must increment even when iToken does not; resetting iValue only works sometimes + } + + if ( ++iValue == 1 ) + dataType = skini_msgs[iSkini].data3; + else + dataType = NOPE; + } + + return message.type; +} + +std::string Skini :: whatsThisType(long type) +{ + std::string typeString; + + for ( int i=0; i<__SK_MaxMsgTypes_; i++ ) { + if ( type == skini_msgs[i].type ) { + typeString = skini_msgs[i].messageString; + break; + } + } + return typeString; +} + +std::string Skini :: whatsThisController( long number ) +{ + std::string controller; + + for ( int i=0; i<__SK_MaxMsgTypes_; i++) { + if ( skini_msgs[i].type == __SK_ControlChange_ && + number == skini_msgs[i].data2) { + controller = skini_msgs[i].messageString; + break; + } + } + return controller; +} + +} // stk namespace diff --git a/7.x/src/stk/src/Sphere.cpp b/7.x/src/stk/src/Sphere.cpp new file mode 100644 index 0000000..90b2a14 --- /dev/null +++ b/7.x/src/stk/src/Sphere.cpp @@ -0,0 +1,50 @@ +/***************************************************/ +/*! \class Sphere + \brief STK sphere class. + + This class implements a spherical ball with + radius, mass, position, and velocity parameters. + + by Perry R. Cook, 1995--2021. +*/ +/***************************************************/ + +#include "Sphere.h" +#include + +namespace stk { + +Vector3D* Sphere::getRelativePosition( Vector3D* position ) +{ + workingVector_.setXYZ(position->getX() - position_.getX(), + position->getY() - position_.getY(), + position->getZ() - position_.getZ()); + return &workingVector_; +}; + +StkFloat Sphere::getVelocity( Vector3D* velocity ) +{ + velocity->setXYZ( velocity_.getX(), velocity_.getY(), velocity_.getZ() ); + return velocity_.getLength(); +}; + +StkFloat Sphere::isInside( Vector3D *position ) +{ + // Return directed distance from aPosition to spherical boundary ( < + // 0 if inside). + StkFloat distance; + Vector3D *tempVector; + + tempVector = this->getRelativePosition( position ); + distance = tempVector->getLength(); + return distance - radius_; +}; + +void Sphere::addVelocity(StkFloat x, StkFloat y, StkFloat z) +{ + velocity_.setX(velocity_.getX() + x); + velocity_.setY(velocity_.getY() + y); + velocity_.setZ(velocity_.getZ() + z); +} + +} // stk namespace diff --git a/7.x/src/stk/src/StifKarp.cpp b/7.x/src/stk/src/StifKarp.cpp new file mode 100644 index 0000000..c8a4881 --- /dev/null +++ b/7.x/src/stk/src/StifKarp.cpp @@ -0,0 +1,181 @@ +/***************************************************/ +/*! \class StifKarp + \brief STK plucked stiff string instrument. + + This class implements a simple plucked string + algorithm (Karplus Strong) with enhancements + (Jaffe-Smith, Smith, and others), including + string stiffness and pluck position controls. + The stiffness is modeled with allpass filters. + + This is a digital waveguide model, making its + use possibly subject to patents held by + Stanford University, Yamaha, and others. + + Control Change Numbers: + - Pickup Position = 4 + - String Sustain = 11 + - String Stretch = 1 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "StifKarp.h" +#include "SKINImsg.h" +#include + +namespace stk { + +StifKarp :: StifKarp( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "StifKarp::StifKarp: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / lowestFrequency ); + delayLine_.setMaximumDelay( nDelays + 1 ); + combDelay_.setMaximumDelay( nDelays + 1 ); + + pluckAmplitude_ = 0.3; + pickupPosition_ = 0.4; + + stretching_ = 0.9999; + baseLoopGain_ = 0.995; + loopGain_ = 0.999; + + this->clear(); + this->setFrequency( 220.0 ); +} + +StifKarp :: ~StifKarp( void ) +{ +} + +void StifKarp :: clear( void ) +{ + delayLine_.clear(); + combDelay_.clear(); + filter_.clear(); +} + +void StifKarp :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "StifKarp::setFrequency: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + lastFrequency_ = frequency; + lastLength_ = Stk::sampleRate() / lastFrequency_; + StkFloat delay = lastLength_ - 0.5; + delayLine_.setDelay( delay ); + + loopGain_ = baseLoopGain_ + (frequency * 0.000005); + if (loopGain_ >= 1.0) loopGain_ = 0.99999; + + setStretch(stretching_); + + combDelay_.setDelay( 0.5 * pickupPosition_ * lastLength_ ); +} + +void StifKarp :: setStretch( StkFloat stretch ) +{ + stretching_ = stretch; + StkFloat coefficient; + StkFloat freq = lastFrequency_ * 2.0; + StkFloat dFreq = ( (0.5 * Stk::sampleRate()) - freq ) * 0.25; + StkFloat temp = 0.5 + (stretch * 0.5); + if ( temp > 0.99999 ) temp = 0.99999; + for ( int i=0; i<4; i++ ) { + coefficient = temp * temp; + biquad_[i].setA2( coefficient ); + biquad_[i].setB0( coefficient ); + biquad_[i].setB2( 1.0 ); + + coefficient = -2.0 * temp * cos(TWO_PI * freq / Stk::sampleRate()); + biquad_[i].setA1( coefficient ); + biquad_[i].setB1( coefficient ); + + freq += dFreq; + } +} + +void StifKarp :: setPickupPosition( StkFloat position ) { + + if ( position < 0.0 || position > 1.0 ) { + oStream_ << "StifKarp::setPickupPosition: parameter is out of range!"; + handleError( StkError::WARNING ); return; + } + + // Set the pick position, which puts zeroes at position * length. + pickupPosition_ = position; + combDelay_.setDelay( 0.5 * pickupPosition_ * lastLength_ ); +} + +void StifKarp :: setBaseLoopGain( StkFloat aGain ) +{ + baseLoopGain_ = aGain; + loopGain_ = baseLoopGain_ + (lastFrequency_ * 0.000005); + if ( loopGain_ > 0.99999 ) loopGain_ = (StkFloat) 0.99999; +} + +void StifKarp :: pluck( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "StifKarp::pluck: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + pluckAmplitude_ = amplitude; + for ( unsigned long i=0; itick() * pluckAmplitude_) ); + } +} + +void StifKarp :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->pluck( amplitude ); +} + +void StifKarp :: noteOff( StkFloat amplitude ) +{ + if ( amplitude < 0.0 || amplitude > 1.0 ) { + oStream_ << "StifKarp::noteOff: amplitude is out of range!"; + handleError( StkError::WARNING ); return; + } + + loopGain_ = (1.0 - amplitude) * 0.5; +} + +void StifKarp :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Clarinet::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_PickPosition_) // 4 + this->setPickupPosition( normalizedValue ); + else if (number == __SK_StringDamping_) // 11 + this->setBaseLoopGain( 0.97 + (normalizedValue * 0.03) ); + else if (number == __SK_StringDetune_) // 1 + this->setStretch( 0.9 + (0.1 * (1.0 - normalizedValue)) ); +#if defined(_STK_DEBUG_) + else { + oStream_ << "StifKarp::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Stk.cpp b/7.x/src/stk/src/Stk.cpp new file mode 100644 index 0000000..e080415 --- /dev/null +++ b/7.x/src/stk/src/Stk.cpp @@ -0,0 +1,401 @@ +/***************************************************/ +/*! \class Stk + \brief STK base class + + Nearly all STK classes inherit from this class. + The global sample rate can be queried and + modified via Stk. In addition, this class + provides error handling and byte-swapping + functions. + + The Synthesis ToolKit in C++ (STK) is a set of open source audio + signal processing and algorithmic synthesis classes written in the + C++ programming language. STK was designed to facilitate rapid + development of music synthesis and audio processing software, with + an emphasis on cross-platform functionality, realtime control, + ease of use, and educational example code. STK currently runs + with realtime support (audio and MIDI) on Linux, Macintosh OS X, + and Windows computer platforms. Generic, non-realtime support has + been tested under NeXTStep, Sun, and other platforms and should + work with any standard C++ compiler. + + STK WWW site: http://ccrma.stanford.edu/software/stk/ + + The Synthesis ToolKit in C++ (STK) + Copyright (c) 1995--2021 Perry R. Cook and Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/***************************************************/ + +#include "Stk.h" +#include + +namespace stk { + +StkFloat Stk :: srate_ = (StkFloat) SRATE; +std::string Stk :: rawwavepath_ = RAWWAVE_PATH; +const Stk::StkFormat Stk :: STK_SINT8 = 0x1; +const Stk::StkFormat Stk :: STK_SINT16 = 0x2; +const Stk::StkFormat Stk :: STK_SINT24 = 0x4; +const Stk::StkFormat Stk :: STK_SINT32 = 0x8; +const Stk::StkFormat Stk :: STK_FLOAT32 = 0x10; +const Stk::StkFormat Stk :: STK_FLOAT64 = 0x20; +bool Stk :: showWarnings_ = true; +bool Stk :: printErrors_ = true; +std::vector Stk :: alertList_; +std::ostringstream Stk :: oStream_; + +Stk :: Stk( void ) + : ignoreSampleRateChange_(false) +{ +} + +Stk :: ~Stk( void ) +{ +} + +void Stk :: setSampleRate( StkFloat rate ) +{ + if ( rate > 0.0 && rate != srate_ ) { + StkFloat oldRate = srate_; + srate_ = rate; + + for ( unsigned int i=0; isampleRateChanged( srate_, oldRate ); + } +} + +void Stk :: sampleRateChanged( StkFloat /*newRate*/, StkFloat /*oldRate*/ ) +{ + // This function should be reimplemented in classes that need to + // make internal variable adjustments in response to a global sample + // rate change. +} + +void Stk :: addSampleRateAlert( Stk *ptr ) +{ + for ( unsigned int i=0; i +#elif defined(__OS_WINDOWS__) + #include +#endif + +void Stk :: sleep(unsigned long milliseconds) +{ +#if defined(__OS_WINDOWS__) + Sleep((DWORD) milliseconds); +#elif (defined(__OS_IRIX__) || defined(__OS_LINUX__) || defined(__OS_MACOSX__)) + usleep( (unsigned long) (milliseconds * 1000.0) ); +#endif +} + +void Stk :: handleError( StkError::Type type ) const +{ + handleError( oStream_.str(), type ); + oStream_.str( std::string() ); // reset the ostringstream buffer +} + +void Stk :: handleError( const char *message, StkError::Type type ) +{ + std::string msg( message ); + handleError( msg, type ); +} + +void Stk :: handleError( std::string message, StkError::Type type ) +{ + if ( type == StkError::WARNING || type == StkError::STATUS ) { + if ( !showWarnings_ ) return; + std::cerr << '\n' << message << '\n' << std::endl; + } + else if (type == StkError::DEBUG_PRINT) { +#if defined(_STK_DEBUG_) + std::cerr << '\n' << message << '\n' << std::endl; +#endif + } + else { + if ( printErrors_ ) { + // Print error message before throwing. + std::cerr << '\n' << message << '\n' << std::endl; + } + throw StkError(message, type); + } +} + +// +// StkFrames definitions +// + +StkFrames :: StkFrames( unsigned int nFrames, unsigned int nChannels ) + : data_( 0 ), nFrames_( nFrames ), nChannels_( nChannels ) +{ + size_ = nFrames_ * nChannels_; + bufferSize_ = size_; + + if ( size_ > 0 ) { + data_ = (StkFloat *) calloc( size_, sizeof( StkFloat ) ); +#if defined(_STK_DEBUG_) + if ( data_ == NULL ) { + std::string error = "StkFrames: memory allocation error in constructor!"; + Stk::handleError( error, StkError::MEMORY_ALLOCATION ); + } +#endif + } + + dataRate_ = Stk::sampleRate(); +} + +StkFrames :: StkFrames( const StkFloat& value, unsigned int nFrames, unsigned int nChannels ) + : data_( 0 ), nFrames_( nFrames ), nChannels_( nChannels ) +{ + size_ = nFrames_ * nChannels_; + bufferSize_ = size_; + if ( size_ > 0 ) { + data_ = (StkFloat *) malloc( size_ * sizeof( StkFloat ) ); +#if defined(_STK_DEBUG_) + if ( data_ == NULL ) { + std::string error = "StkFrames: memory allocation error in constructor!"; + Stk::handleError( error, StkError::MEMORY_ALLOCATION ); + } +#endif + for ( long i=0; i<(long)size_; i++ ) data_[i] = value; + } + + dataRate_ = Stk::sampleRate(); +} + +StkFrames :: ~StkFrames() +{ + if ( data_ ) free( data_ ); +} + +StkFrames :: StkFrames( const StkFrames& f ) + : data_(0), size_(0), bufferSize_(0) +{ + resize( f.frames(), f.channels() ); + dataRate_ = Stk::sampleRate(); + for ( unsigned int i=0; i bufferSize_ ) { + if ( data_ ) free( data_ ); + data_ = (StkFloat *) malloc( size_ * sizeof( StkFloat ) ); +#if defined(_STK_DEBUG_) + if ( data_ == NULL ) { + std::string error = "StkFrames::resize: memory allocation error!"; + Stk::handleError( error, StkError::MEMORY_ALLOCATION ); + } +#endif + bufferSize_ = size_; + } +} + +void StkFrames :: resize( size_t nFrames, unsigned int nChannels, StkFloat value ) +{ + this->resize( nFrames, nChannels ); + + for ( size_t i=0; i channels() - 1) { + std::ostringstream error; + error << "StkFrames::getChannel invalid sourceChannel (" << sourceChannel << ")"; + Stk::handleError( error.str(), StkError::FUNCTION_ARGUMENT); + } + if (destinationChannel > destinationFrames.channels() - 1) { + std::ostringstream error; + error << "StkFrames::getChannel invalid destinationChannel (" << destinationChannel << ")"; + Stk::handleError( error.str(), StkError::FUNCTION_ARGUMENT ); + } + if (destinationFrames.frames() < frames()) { + std::ostringstream error; + error << "StkFrames::getChannel destination.frames() < frames()"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS); + } +#endif + int sourceHop = nChannels_; + int destinationHop = destinationFrames.nChannels_; + for (unsigned int i = sourceChannel, j= destinationChannel; + i < nFrames_ * nChannels_; + i+=sourceHop,j+=destinationHop) + { + destinationFrames[j] = data_[i]; + } + return destinationFrames; + +} + +void StkFrames::setChannel(unsigned int destinationChannel, const stk::StkFrames &sourceFrames,unsigned int sourceChannel) +{ +#if defined(_STK_DEBUG_) + if (sourceChannel > sourceFrames.channels() - 1) { + std::ostringstream error; + error << "StkFrames::setChannel invalid sourceChannel (" << sourceChannel << ")"; + Stk::handleError( error.str(), StkError::FUNCTION_ARGUMENT); + } + if (destinationChannel > channels() - 1) { + std::ostringstream error; + error << "StkFrames::setChannel invalid channel (" << destinationChannel << ")"; + Stk::handleError( error.str(), StkError::FUNCTION_ARGUMENT ); + } + if (sourceFrames.frames() != frames()) { + std::ostringstream error; + error << "StkFrames::setChannel sourceFrames.frames() != frames()"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS); + } +#endif + unsigned int sourceHop = sourceFrames.nChannels_; + unsigned int destinationHop = nChannels_; + for (unsigned int i = destinationChannel,j = sourceChannel; + i < nFrames_ * nChannels_; + i+=destinationHop,j+=sourceHop) + { + data_[i] = sourceFrames[j]; + } +} + +StkFloat StkFrames :: interpolate( StkFloat frame, unsigned int channel ) const +{ +#if defined(_STK_DEBUG_) + if ( frame < 0.0 || frame > (StkFloat) ( nFrames_ - 1 ) || channel >= nChannels_ ) { + std::ostringstream error; + error << "StkFrames::interpolate: invalid frame (" << frame << ") or channel (" << channel << ") value!"; + Stk::handleError( error.str(), StkError::MEMORY_ACCESS ); + } +#endif + + size_t iIndex = ( size_t ) frame; // integer part of index + StkFloat output, alpha = frame - (StkFloat) iIndex; // fractional part of index + + iIndex = iIndex * nChannels_ + channel; + output = data_[ iIndex ]; + if ( alpha > 0.0 ) + output += ( alpha * ( data_[ iIndex + nChannels_ ] - output ) ); + + return output; +} + +} // stk namespace diff --git a/7.x/src/stk/src/TapDelay.cpp b/7.x/src/stk/src/TapDelay.cpp new file mode 100644 index 0000000..49bca6a --- /dev/null +++ b/7.x/src/stk/src/TapDelay.cpp @@ -0,0 +1,87 @@ +/***************************************************/ +/*! \class TapDelay + \brief STK non-interpolating tapped delay line class. + + This class implements a non-interpolating digital delay-line with + an arbitrary number of output "taps". If the maximum length and + tap delays are not specified during instantiation, a fixed maximum + length of 4095 and a single tap delay of zero is set. + + A non-interpolating delay line is typically used in fixed + delay-length applications, such as for reverberation. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "TapDelay.h" + +namespace stk { + +TapDelay :: TapDelay( std::vector taps, unsigned long maxDelay ) +{ + // Writing before reading allows delays from 0 to length-1. + // If we want to allow a delay of maxDelay, we need a + // delayline of length = maxDelay+1. + if ( maxDelay < 1 ) { + oStream_ << "TapDelay::TapDelay: maxDelay must be > 0!\n"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + for ( unsigned int i=0; i maxDelay ) { + oStream_ << "TapDelay::TapDelay: maxDelay must be > than all tap delay values!\n"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + } + + if ( ( maxDelay + 1 ) > inputs_.size() ) + inputs_.resize( maxDelay + 1, 1, 0.0 ); + + inPoint_ = 0; + this->setTapDelays( taps ); +} + +TapDelay :: ~TapDelay() +{ +} + +void TapDelay :: setMaximumDelay( unsigned long delay ) +{ + if ( delay < inputs_.size() ) return; + + for ( unsigned int i=0; i taps ) +{ + for ( unsigned int i=0; i inputs_.size() - 1 ) { // The value is too big. + oStream_ << "TapDelay::setTapDelay: argument (" << taps[i] << ") greater than maximum!\n"; + handleError( StkError::WARNING ); return; + } + } + + if ( taps.size() != outPoint_.size() ) { + outPoint_.resize( taps.size() ); + delays_.resize( taps.size() ); + lastFrame_.resize( 1, (unsigned int)taps.size(), 0.0 ); + } + + for ( unsigned int i=0; i= taps[i] ) outPoint_[i] = inPoint_ - taps[i]; + else outPoint_[i] = inputs_.size() + inPoint_ - taps[i]; + delays_[i] = taps[i]; + } +} + +} // stk namespace + diff --git a/7.x/src/stk/src/TubeBell.cpp b/7.x/src/stk/src/TubeBell.cpp new file mode 100644 index 0000000..83eac78 --- /dev/null +++ b/7.x/src/stk/src/TubeBell.cpp @@ -0,0 +1,78 @@ +/***************************************************/ +/*! \class TubeBell + \brief STK tubular bell (orchestral chime) FM + synthesis instrument. + + This class implements two simple FM Pairs + summed together, also referred to as algorithm + 5 of the TX81Z. + + \code + Algorithm 5 is : 4->3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "TubeBell.h" + +namespace stk { + +TubeBell :: TubeBell( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 1.0 * 0.995); + this->setRatio(1, 1.414 * 0.995); + this->setRatio(2, 1.0 * 1.005); + this->setRatio(3, 1.414 * 1.000); + + gains_[0] = fmGains_[94]; + gains_[1] = fmGains_[76]; + gains_[2] = fmGains_[99]; + gains_[3] = fmGains_[71]; + + adsr_[0]->setAllTimes( 0.005, 4.0, 0.0, 0.04); + adsr_[1]->setAllTimes( 0.005, 4.0, 0.0, 0.04); + adsr_[2]->setAllTimes( 0.001, 2.0, 0.0, 0.04); + adsr_[3]->setAllTimes( 0.004, 4.0, 0.0, 0.04); + + twozero_.setGain( 0.5 ); + vibrato_.setFrequency( 2.0 ); +} + +TubeBell :: ~TubeBell( void ) +{ +} + +void TubeBell :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[94]; + gains_[1] = amplitude * fmGains_[76]; + gains_[2] = amplitude * fmGains_[99]; + gains_[3] = amplitude * fmGains_[71]; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/Twang.cpp b/7.x/src/stk/src/Twang.cpp new file mode 100644 index 0000000..17f0925 --- /dev/null +++ b/7.x/src/stk/src/Twang.cpp @@ -0,0 +1,103 @@ +/***************************************************/ +/*! \class Twang + \brief STK enhanced plucked string class. + + This class implements an enhanced plucked-string + physical model, a la Jaffe-Smith, Smith, + Karjalainen and others. It includes a comb + filter to simulate pluck position. The tick() + function takes an input sample, which is added + to the delayline input. This can be used to + implement commuted synthesis (if the input + samples are derived from the impulse response of + a body filter) or feedback (as in an electric + guitar model). + + This is a digital waveguide model, making its + use possibly subject to patents held by Stanford + University, Yamaha, and others. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Twang.h" + +namespace stk { + +Twang :: Twang( StkFloat lowestFrequency ) +{ + if ( lowestFrequency <= 0.0 ) { + oStream_ << "Twang::Twang: argument is less than or equal to zero!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + this->setLowestFrequency( lowestFrequency ); + + std::vector coefficients( 2, 0.5 ); + loopFilter_.setCoefficients( coefficients ); + + loopGain_ = 0.995; + pluckPosition_ = 0.4; + this->setFrequency( 220.0 ); +} + +void Twang :: clear( void ) +{ + delayLine_.clear(); + combDelay_.clear(); + loopFilter_.clear(); + lastOutput_ = 0.0; +} + +void Twang :: setLowestFrequency( StkFloat frequency ) +{ + unsigned long nDelays = (unsigned long) ( Stk::sampleRate() / frequency ); + delayLine_.setMaximumDelay( nDelays + 1 ); + combDelay_.setMaximumDelay( nDelays + 1 ); +} + +void Twang :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Twang::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + frequency_ = frequency; + // Delay = length - filter delay. + StkFloat delay = ( Stk::sampleRate() / frequency ) - loopFilter_.phaseDelay( frequency ); + delayLine_.setDelay( delay ); + + this->setLoopGain( loopGain_ ); + + // Set the pluck position, which puts zeroes at position * length. + combDelay_.setDelay( 0.5 * pluckPosition_ * delay ); +} + +void Twang :: setLoopGain( StkFloat loopGain ) +{ + if ( loopGain < 0.0 || loopGain >= 1.0 ) { + oStream_ << "Twang::setLoopGain: parameter is out of range!"; + handleError( StkError::WARNING ); return; + } + + loopGain_ = loopGain; + StkFloat gain = loopGain_ + (frequency_ * 0.000005); + if ( gain >= 1.0 ) gain = 0.99999; + loopFilter_.setGain( gain ); +} + +void Twang :: setPluckPosition( StkFloat position ) +{ + if ( position < 0.0 || position > 1.0 ) { + oStream_ << "Twang::setPluckPosition: argument (" << position << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + + pluckPosition_ = position; +} + +} // stk namespace diff --git a/7.x/src/stk/src/TwoPole.cpp b/7.x/src/stk/src/TwoPole.cpp new file mode 100644 index 0000000..8964a1b --- /dev/null +++ b/7.x/src/stk/src/TwoPole.cpp @@ -0,0 +1,76 @@ +/***************************************************/ +/*! \class TwoPole + \brief STK two-pole filter class. + + This class implements a two-pole digital filter. A method is + provided for creating a resonance in the frequency response while + maintaining a nearly constant filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "TwoPole.h" +#include + +namespace stk { + +TwoPole :: TwoPole( void ) +{ + b_.resize( 1 ); + a_.resize( 3 ); + inputs_.resize( 1, 1, 0.0 ); + outputs_.resize( 3, 1, 0.0 ); + b_[0] = 1.0; + a_[0] = 1.0; + + Stk::addSampleRateAlert( this ); +} + +TwoPole :: ~TwoPole() +{ + Stk::removeSampleRateAlert( this ); +} + +void TwoPole :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + oStream_ << "TwoPole::sampleRateChanged: you may need to recompute filter coefficients!"; + handleError( StkError::WARNING ); + } +} + +void TwoPole :: setResonance( StkFloat frequency, StkFloat radius, bool normalize ) +{ +#if defined(_STK_DEBUG_) + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "TwoPole::setResonance: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 || radius >= 1.0 ) { + oStream_ << "TwoPole::setResonance: radius argument (" << radius << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + a_[2] = radius * radius; + a_[1] = (StkFloat) -2.0 * radius * cos(TWO_PI * frequency / Stk::sampleRate()); + + if ( normalize ) { + // Normalize the filter gain ... not terribly efficient. + StkFloat real = 1 - radius + (a_[2] - radius) * cos(TWO_PI * 2 * frequency / Stk::sampleRate()); + StkFloat imag = (a_[2] - radius) * sin(TWO_PI * 2 * frequency / Stk::sampleRate()); + b_[0] = sqrt( pow(real, 2) + pow(imag, 2) ); + } +} + +void TwoPole :: setCoefficients( StkFloat b0, StkFloat a1, StkFloat a2, bool clearState ) +{ + b_[0] = b0; + a_[1] = a1; + a_[2] = a2; + + if ( clearState ) this->clear(); +} + +} // stk namespace diff --git a/7.x/src/stk/src/TwoZero.cpp b/7.x/src/stk/src/TwoZero.cpp new file mode 100644 index 0000000..8742bf4 --- /dev/null +++ b/7.x/src/stk/src/TwoZero.cpp @@ -0,0 +1,74 @@ +/***************************************************/ +/*! \class TwoZero + \brief STK two-zero filter class. + + This class implements a two-zero digital filter. A method is + provided for creating a "notch" in the frequency response while + maintaining a constant filter gain. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "TwoZero.h" +#include + +namespace stk { + +TwoZero :: TwoZero( void ) +{ + b_.resize( 3, 0.0 ); + inputs_.resize( 3, 1, 0.0 ); + b_[0] = 1.0; + + Stk::addSampleRateAlert( this ); +} + +TwoZero :: ~TwoZero() +{ + Stk::removeSampleRateAlert( this ); +} + +void TwoZero :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + oStream_ << "TwoZero::sampleRateChanged: you may need to recompute filter coefficients!"; + handleError( StkError::WARNING ); + } +} + +void TwoZero :: setCoefficients( StkFloat b0, StkFloat b1, StkFloat b2, bool clearState ) +{ + b_[0] = b0; + b_[1] = b1; + b_[2] = b2; + + if ( clearState ) this->clear(); +} + +void TwoZero :: setNotch( StkFloat frequency, StkFloat radius ) +{ +#if defined(_STK_DEBUG_) + if ( frequency < 0.0 || frequency > 0.5 * Stk::sampleRate() ) { + oStream_ << "TwoZero::setNotch: frequency argument (" << frequency << ") is out of range!"; + handleError( StkError::WARNING ); return; + } + if ( radius < 0.0 ) { + oStream_ << "TwoZero::setNotch: radius argument (" << radius << ") is negative!"; + handleError( StkError::WARNING ); return; + } +#endif + + b_[2] = radius * radius; + b_[1] = -2.0 * radius * cos(TWO_PI * frequency / Stk::sampleRate()); + + // Normalize the filter gain. + if ( b_[1] > 0.0 ) // Maximum at z = 0. + b_[0] = 1.0 / ( 1.0 + b_[1] + b_[2] ); + else // Maximum at z = -1. + b_[0] = 1.0 / ( 1.0 - b_[1] + b_[2] ); + b_[1] *= b_[0]; + b_[2] *= b_[0]; +} + +} // stk namespace diff --git a/7.x/src/stk/src/VoicForm.cpp b/7.x/src/stk/src/VoicForm.cpp new file mode 100644 index 0000000..ef1b5d0 --- /dev/null +++ b/7.x/src/stk/src/VoicForm.cpp @@ -0,0 +1,189 @@ +/***************************************************/ +/*! \class VoicForm + \brief Four formant synthesis instrument. + + This instrument contains an excitation singing + wavetable (looping wave with random and + periodic vibrato, smoothing on frequency, + etc.), excitation noise, and four sweepable + complex resonances. + + Measured formant data is included, and enough + data is there to support either parallel or + cascade synthesis. In the floating point case + cascade synthesis is the most natural so + that's what you'll find here. + + Control Change Numbers: + - Voiced/Unvoiced Mix = 2 + - Vowel/Phoneme Selection = 4 + - Vibrato Frequency = 11 + - Vibrato Gain = 1 + - Loudness (Spectral Tilt) = 128 + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "VoicForm.h" +#include "Phonemes.h" +#include "SKINImsg.h" +#include +#include + +namespace stk { + +VoicForm :: VoicForm( void ) : Instrmnt() +{ + // Concatenate the STK rawwave path to the rawwave file + voiced_ = new SingWave( (Stk::rawwavePath() + "impuls20.raw").c_str(), true ); + voiced_->setGainRate( 0.001 ); + voiced_->setGainTarget( 0.0 ); + + for ( int i=0; i<4; i++ ) + filters_[i].setSweepRate( 0.001 ); + + onezero_.setZero( -0.9 ); + onepole_.setPole( 0.9 ); + + noiseEnv_.setRate( 0.001 ); + noiseEnv_.setTarget( 0.0 ); + + this->setPhoneme( "eee" ); + this->clear(); +} + +VoicForm :: ~VoicForm( void ) +{ + delete voiced_; +} + +void VoicForm :: clear( void ) +{ + onezero_.clear(); + onepole_.clear(); + for ( int i=0; i<4; i++ ) { + filters_[i].clear(); + } +} + +void VoicForm :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "VoicForm::setFrequency: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + voiced_->setFrequency( frequency ); +} + +bool VoicForm :: setPhoneme( const char *phoneme ) +{ + bool found = false; + unsigned int i = 0; + while( i < 32 && !found ) { + if ( !strcmp( Phonemes::name(i), phoneme ) ) { + found = true; + filters_[0].setTargets( Phonemes::formantFrequency(i, 0), Phonemes::formantRadius(i, 0), pow(10.0, Phonemes::formantGain(i, 0 ) / 20.0) ); + filters_[1].setTargets( Phonemes::formantFrequency(i, 1), Phonemes::formantRadius(i, 1), pow(10.0, Phonemes::formantGain(i, 1 ) / 20.0) ); + filters_[2].setTargets( Phonemes::formantFrequency(i, 2), Phonemes::formantRadius(i, 2), pow(10.0, Phonemes::formantGain(i, 2 ) / 20.0) ); + filters_[3].setTargets( Phonemes::formantFrequency(i, 3), Phonemes::formantRadius(i, 3), pow(10.0, Phonemes::formantGain(i, 3 ) / 20.0) ); + this->setVoiced( Phonemes::voiceGain( i ) ); + this->setUnVoiced( Phonemes::noiseGain( i ) ); + } + i++; + } + + if ( !found ) { + oStream_ << "VoicForm::setPhoneme: phoneme " << phoneme << " not found!"; + handleError( StkError::WARNING ); + } + + return found; +} + +void VoicForm :: setFilterSweepRate( unsigned int whichOne, StkFloat rate ) +{ + if ( whichOne > 3 ) { + oStream_ << "VoicForm::setFilterSweepRate: filter select argument outside range 0-3!"; + handleError( StkError::WARNING ); return; + } + + filters_[whichOne].setSweepRate(rate); +} + +void VoicForm :: quiet( void ) +{ + voiced_->noteOff(); + noiseEnv_.setTarget( 0.0 ); +} + +void VoicForm :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + voiced_->setGainTarget( amplitude ); + onepole_.setPole( 0.97 - (amplitude * 0.2) ); +} + +void VoicForm :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "VoicForm::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if (number == __SK_Breath_) { // 2 + this->setVoiced( 1.0 - normalizedValue ); + this->setUnVoiced( 0.01 * normalizedValue ); + } + else if (number == __SK_FootControl_) { // 4 + StkFloat temp = 0.0; + unsigned int i = (int) value; + if (i < 32) { + temp = 0.9; + } + else if (i < 64) { + i -= 32; + temp = 1.0; + } + else if (i < 96) { + i -= 64; + temp = 1.1; + } + else if (i < 128) { + i -= 96; + temp = 1.2; + } + else if (i == 128) { + i = 0; + temp = 1.4; + } + filters_[0].setTargets( temp * Phonemes::formantFrequency(i, 0), Phonemes::formantRadius(i, 0), pow(10.0, Phonemes::formantGain(i, 0 ) / 20.0) ); + filters_[1].setTargets( temp * Phonemes::formantFrequency(i, 1), Phonemes::formantRadius(i, 1), pow(10.0, Phonemes::formantGain(i, 1 ) / 20.0) ); + filters_[2].setTargets( temp * Phonemes::formantFrequency(i, 2), Phonemes::formantRadius(i, 2), pow(10.0, Phonemes::formantGain(i, 2 ) / 20.0) ); + filters_[3].setTargets( temp * Phonemes::formantFrequency(i, 3), Phonemes::formantRadius(i, 3), pow(10.0, Phonemes::formantGain(i, 3 ) / 20.0) ); + this->setVoiced( Phonemes::voiceGain( i ) ); + this->setUnVoiced( Phonemes::noiseGain( i ) ); + } + else if (number == __SK_ModFrequency_) // 11 + voiced_->setVibratoRate( normalizedValue * 12.0); // 0 to 12 Hz + else if (number == __SK_ModWheel_) // 1 + voiced_->setVibratoGain( normalizedValue * 0.2); + else if (number == __SK_AfterTouch_Cont_) { // 128 + this->setVoiced( normalizedValue ); + onepole_.setPole( 0.97 - ( normalizedValue * 0.2) ); + } +#if defined(_STK_DEBUG_) + else { + oStream_ << "VoicForm::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Voicer.cpp b/7.x/src/stk/src/Voicer.cpp new file mode 100644 index 0000000..543cffb --- /dev/null +++ b/7.x/src/stk/src/Voicer.cpp @@ -0,0 +1,224 @@ +/***************************************************/ +/*! \class Voicer + \brief STK voice manager class. + + This class can be used to manage a group of STK instrument + classes. Individual voices can be controlled via unique note + tags. Instrument groups can be controlled by group number. + + A previously constructed STK instrument class is linked with a + voice manager using the addInstrument() function. An optional + group number argument can be specified to the addInstrument() + function as well (default group = 0). The voice manager does not + delete any instrument instances ... it is the responsibility of + the user to allocate and deallocate all instruments. + + The tick() function returns the mix of all sounding voices. Each + noteOn returns a unique tag (credits to the NeXT MusicKit), so you + can send control changes to specific voices within an ensemble. + Alternately, control changes can be sent to all voices in a given + group. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Voicer.h" +#include + +namespace stk { + +Voicer :: Voicer( StkFloat decayTime ) +{ + if ( decayTime < 0.0 ) { + oStream_ << "Voicer::Voicer: argument (" << decayTime << ") must be positive!"; + handleError( StkError::FUNCTION_ARGUMENT ); + } + + tags_ = 23456; + muteTime_ = (int) ( decayTime * Stk::sampleRate() ); + lastFrame_.resize( 1, 1, 0.0 ); +} + +void Voicer :: addInstrument( Instrmnt *instrument, int group ) +{ + Voicer::Voice voice; + voice.instrument = instrument; + voice.group = group; + voice.noteNumber = -1; + voices_.push_back( voice ); + + // Check output channels and resize lastFrame_ if necessary. + if ( instrument->channelsOut() > lastFrame_.channels() ) { + unsigned int startChannel = lastFrame_.channels(); + lastFrame_.resize( 1, instrument->channelsOut() ); + for ( unsigned int i=startChannel; i::iterator i; + for ( i=voices_.begin(); i!=voices_.end(); ++i ) { + if ( (*i).instrument != instrument ) continue; + voices_.erase( i ); + found = true; + break; + } + + if ( found ) { + // Check output channels and resize lastFrame_ if necessary. + unsigned int maxChannels = 1; + for ( i=voices_.begin(); i!=voices_.end(); ++i ) { + if ( (*i).instrument->channelsOut() > maxChannels ) maxChannels = (*i).instrument->channelsOut(); + } + if ( maxChannels < lastFrame_.channels() ) + lastFrame_.resize( 1, maxChannels ); + } + else { + oStream_ << "Voicer::removeInstrument: instrument pointer not found in current voices!"; + handleError( StkError::WARNING ); + } +} + +long Voicer :: noteOn(StkFloat noteNumber, StkFloat amplitude, int group ) +{ + unsigned int i; + StkFloat frequency = (StkFloat) 220.0 * pow( 2.0, (noteNumber - 57.0) / 12.0 ); + for ( i=0; inoteOn( frequency, amplitude * ONE_OVER_128 ); + voices_[i].sounding = 1; + return voices_[i].tag; + } + } + + // All voices are sounding, so interrupt the oldest voice. + int voice = -1; + for ( i=0; i= 0 ) { + voices_[voice].tag = tags_++; + voices_[voice].group = group; + voices_[voice].noteNumber = noteNumber; + voices_[voice].frequency = frequency; + voices_[voice].instrument->noteOn( frequency, amplitude * ONE_OVER_128 ); + voices_[voice].sounding = 1; + return voices_[voice].tag; + } + + return -1; +} + +void Voicer :: noteOff( StkFloat noteNumber, StkFloat amplitude, int group ) +{ + for ( unsigned int i=0; inoteOff( amplitude * ONE_OVER_128 ); + voices_[i].sounding = -muteTime_; + } + } +} + +void Voicer :: noteOff( long tag, StkFloat amplitude ) +{ + for ( unsigned int i=0; inoteOff( amplitude * ONE_OVER_128 ); + voices_[i].sounding = -muteTime_; + break; + } + } +} + +void Voicer :: setFrequency( StkFloat noteNumber, int group ) +{ + StkFloat frequency = (StkFloat) 220.0 * pow( 2.0, (noteNumber - 57.0) / 12.0 ); + for ( unsigned int i=0; isetFrequency( frequency ); + } + } +} + +void Voicer :: setFrequency( long tag, StkFloat noteNumber ) +{ + StkFloat frequency = (StkFloat) 220.0 * pow( 2.0, (noteNumber - 57.0) / 12.0 ); + for ( unsigned int i=0; isetFrequency( frequency ); + break; + } + } +} + +void Voicer :: pitchBend( StkFloat value, int group ) +{ + StkFloat pitchScaler; + if ( value < 8192.0 ) + pitchScaler = pow( 0.5, (8192.0-value) / 8192.0 ); + else + pitchScaler = pow( 2.0, (value-8192.0) / 8192.0 ); + for ( unsigned int i=0; isetFrequency( (StkFloat) (voices_[i].frequency * pitchScaler) ); + } +} + +void Voicer :: pitchBend( long tag, StkFloat value ) +{ + StkFloat pitchScaler; + if ( value < 8192.0 ) + pitchScaler = pow( 0.5, (8192.0-value) / 8192.0 ); + else + pitchScaler = pow( 2.0, (value-8192.0) / 8192.0 ); + for ( unsigned int i=0; isetFrequency( (StkFloat) (voices_[i].frequency * pitchScaler) ); + break; + } + } +} + +void Voicer :: controlChange( int number, StkFloat value, int group ) +{ + for ( unsigned int i=0; icontrolChange( number, value ); + } +} + +void Voicer :: controlChange( long tag, int number, StkFloat value ) +{ + for ( unsigned int i=0; icontrolChange( number, value ); + break; + } + } +} + +void Voicer :: silence( void ) +{ + for ( unsigned int i=0; i 0 ) + voices_[i].instrument->noteOff( 0.5 ); + } +} + +} // stk namespace diff --git a/7.x/src/stk/src/Whistle.cpp b/7.x/src/stk/src/Whistle.cpp new file mode 100644 index 0000000..636861e --- /dev/null +++ b/7.x/src/stk/src/Whistle.cpp @@ -0,0 +1,256 @@ +/***************************************************/ +/*! \Class Whistle + \brief STK police/referee whistle instrument class. + + This class implements a hybrid physical/spectral + model of a police whistle (a la Cook). + + Control Change Numbers: + - Noise Gain = 4 + - Fipple Modulation Frequency = 11 + - Fipple Modulation Gain = 1 + - Blowing Frequency Modulation = 2 + - Volume = 128 + + by Perry R. Cook 1995--2021. +*/ +/***************************************************/ + +#include "Whistle.h" +#include "SKINImsg.h" +#include + +namespace stk { + +const int CAN_RADIUS = 100; +const int PEA_RADIUS = 30; +const int BUMP_RADIUS = 5; + +const StkFloat NORM_CAN_LOSS = 0.97; +//const StkFloat SLOW_CAN_LOSS = 0.90; +const StkFloat GRAVITY = 20.0; + +const StkFloat NORM_TICK_SIZE = 0.004; +//const StkFloat SLOW_TICK_SIZE = 0.0001; + +const StkFloat ENV_RATE = 0.001; + +Whistle :: Whistle( void ) +{ + sine_.setFrequency( 2800.0 ); + + can_.setRadius( CAN_RADIUS ); + can_.setPosition(0, 0, 0); // set can location + can_.setVelocity(0, 0, 0); // and the velocity + + onepole_.setPole(0.95); // 0.99 + + bumper_.setRadius( BUMP_RADIUS ); + bumper_.setPosition(0.0, CAN_RADIUS-BUMP_RADIUS, 0); + bumper_.setPosition(0.0, CAN_RADIUS-BUMP_RADIUS, 0); + + pea_.setRadius( PEA_RADIUS ); + pea_.setPosition(0, CAN_RADIUS/2, 0); + pea_.setVelocity(35, 15, 0); + + envelope_.setRate( ENV_RATE ); + envelope_.keyOn(); + + fippleFreqMod_ = 0.5; + fippleGainMod_ = 0.5; + blowFreqMod_ = 0.25; + noiseGain_ = 0.125; + baseFrequency_ = 2000; + + tickSize_ = NORM_TICK_SIZE; + canLoss_ = NORM_CAN_LOSS; + + subSample_ = 1; + subSampCount_ = subSample_; +} + +Whistle :: ~Whistle( void ) +{ +#ifdef WHISTLE_ANIMATION + printf("Exit, Whistle bye bye!!\n"); +#endif +} + +void Whistle :: clear( void ) +{ +} + +void Whistle :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Whistle::setFrequency: parameter is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency * 4; // the whistle is a transposing instrument +} + +void Whistle :: startBlowing( StkFloat amplitude, StkFloat rate ) +{ + if ( amplitude <= 0.0 || rate <= 0.0 ) { + oStream_ << "Whistle::startBlowing: one or more arguments is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( ENV_RATE ); + envelope_.setTarget( amplitude ); +} + +void Whistle :: stopBlowing( StkFloat rate ) +{ + if ( rate <= 0.0 ) { + oStream_ << "Whistle::stopBlowing: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } + + envelope_.setRate( rate ); + envelope_.keyOff(); +} + +void Whistle :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + this->setFrequency( frequency ); + this->startBlowing( amplitude*2.0 ,amplitude * 0.2 ); +} + +void Whistle :: noteOff( StkFloat amplitude ) +{ + this->stopBlowing( amplitude * 0.02 ); +} + +int frameCount = 0; + +StkFloat Whistle :: tick( unsigned int ) +{ + StkFloat soundMix, tempFreq; + StkFloat envOut = 0, temp, temp1, temp2, tempX, tempY; + double phi, cosphi, sinphi; + double gain = 0.5, mod = 0.0; + + if ( --subSampCount_ <= 0 ) { + tempVectorP_ = pea_.getPosition(); + subSampCount_ = subSample_; + temp = bumper_.isInside( tempVectorP_ ); +#ifdef WHISTLE_ANIMATION + frameCount += 1; + if ( frameCount >= (1470 / subSample_) ) { + frameCount = 0; + printf("%f %f %f\n",tempVectorP_->getX(),tempVectorP_->getY(),envOut); + fflush(stdout); + } +#endif + envOut = envelope_.tick(); + + if (temp < (BUMP_RADIUS + PEA_RADIUS)) { + tempX = envOut * tickSize_ * 2000 * noise_.tick(); + tempY = -envOut * tickSize_ * 1000 * (1.0 + noise_.tick()); + pea_.addVelocity( tempX, tempY, 0 ); + pea_.tick( tickSize_ ); + } + + mod = exp(-temp * 0.01); // exp. distance falloff of fipple/pea effect + temp = onepole_.tick(mod); // smooth it a little + gain = (1.0 - (fippleGainMod_*0.5)) + (2.0 * fippleGainMod_ * temp); + gain *= gain; // squared distance/gain + // tempFreq = 1.0 // Normalized Base Freq + // + (fippleFreqMod_ * 0.25) - (fippleFreqMod_ * temp) // fippleModulation + // - (blowFreqMod_) + (blowFreqMod_ * envOut); // blowingModulation + // short form of above + tempFreq = 1.0 + fippleFreqMod_*(0.25-temp) + blowFreqMod_*(envOut-1.0); + tempFreq *= baseFrequency_; + + sine_.setFrequency(tempFreq); + + tempVectorP_ = pea_.getPosition(); + temp = can_.isInside(tempVectorP_); + temp = -temp; // We know (hope) it's inside, just how much?? + if (temp < (PEA_RADIUS * 1.25)) { + pea_.getVelocity( &tempVector_ ); // This is the can/pea collision + tempX = tempVectorP_->getX(); // calculation. Could probably + tempY = tempVectorP_->getY(); // simplify using tables, etc. + phi = -atan2(tempY,tempX); + + cosphi = cos(phi); + sinphi = sin(phi); + temp1 = (cosphi*tempVector_.getX()) - (sinphi*tempVector_.getY()); + temp2 = (sinphi*tempVector_.getX()) + (cosphi*tempVector_.getY()); + temp1 = -temp1; + tempX = (cosphi*temp1) + (sinphi*temp2); + tempY = (-sinphi*temp1) + (cosphi*temp2); + pea_.setVelocity(tempX, tempY, 0); + pea_.tick(tickSize_); + pea_.setVelocity( tempX*canLoss_, tempY*canLoss_, 0 ); + pea_.tick(tickSize_); + } + + temp = tempVectorP_->getLength(); + if (temp > 0.01) { + tempX = tempVectorP_->getX(); + tempY = tempVectorP_->getY(); + phi = atan2( tempY, tempX ); + phi += 0.3 * temp / CAN_RADIUS; + cosphi = cos(phi); + sinphi = sin(phi); + tempX = 3.0 * temp * cosphi; + tempY = 3.0 * temp * sinphi; + } + else { + tempX = 0.0; + tempY = 0.0; + } + + temp = (0.9 + 0.1*subSample_*noise_.tick()) * envOut * 0.6 * tickSize_; + pea_.addVelocity( temp * tempX, (temp*tempY) - (GRAVITY*tickSize_), 0 ); + pea_.tick( tickSize_ ); + + // bumper_.tick(0.0); + } + + temp = envOut * envOut * gain / 2; + soundMix = temp * ( sine_.tick() + ( noiseGain_*noise_.tick() ) ); + lastFrame_[0] = 0.20 * soundMix; // should probably do one-zero filter here + + return lastFrame_[0]; +} + +void Whistle :: controlChange( int number, StkFloat value ) +{ +#if defined(_STK_DEBUG_) + if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { + oStream_ << "Whistle::controlChange: value (" << value << ") is out of range!"; + handleError( StkError::WARNING ); return; + } +#endif + + StkFloat normalizedValue = value * ONE_OVER_128; + if ( number == __SK_NoiseLevel_ ) // 4 + noiseGain_ = 0.25 * normalizedValue; + else if ( number == __SK_ModFrequency_ ) // 11 + fippleFreqMod_ = normalizedValue; + else if ( number == __SK_ModWheel_ ) // 1 + fippleGainMod_ = normalizedValue; + else if ( number == __SK_AfterTouch_Cont_ ) // 128 + envelope_.setTarget( normalizedValue * 2.0 ); + else if ( number == __SK_Breath_ ) // 2 + blowFreqMod_ = normalizedValue * 0.5; + else if ( number == __SK_Sustain_ ) { // 64 + subSample_ = (int) value; + if ( subSample_ < 1.0 ) subSample_ = 1; + envelope_.setRate( ENV_RATE / subSample_ ); + } +#if defined(_STK_DEBUG_) + else { + oStream_ << "Whistle::controlChange: undefined control number (" << number << ")!"; + handleError( StkError::WARNING ); + } +#endif +} + +} // stk namespace diff --git a/7.x/src/stk/src/Wurley.cpp b/7.x/src/stk/src/Wurley.cpp new file mode 100644 index 0000000..6842539 --- /dev/null +++ b/7.x/src/stk/src/Wurley.cpp @@ -0,0 +1,94 @@ +/***************************************************/ +/*! \class Wurley + \brief STK Wurlitzer electric piano FM + synthesis instrument. + + This class implements two simple FM Pairs + summed together, also referred to as algorithm + 5 of the TX81Z. + + \code + Algorithm 5 is : 4->3--\ + + --> Out + 2->1--/ + \endcode + + Control Change Numbers: + - Modulator Index One = 2 + - Crossfade of Outputs = 4 + - LFO Speed = 11 + - LFO Depth = 1 + - ADSR 2 & 4 Target = 128 + + The basic Chowning/Stanford FM patent expired + in 1995, but there exist follow-on patents, + mostly assigned to Yamaha. If you are of the + type who should worry about this (making + money) worry away. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "Wurley.h" + +namespace stk { + +Wurley :: Wurley( void ) + : FM() +{ + // Concatenate the STK rawwave path to the rawwave files + for ( unsigned int i=0; i<3; i++ ) + waves_[i] = new FileLoop( (Stk::rawwavePath() + "sinewave.raw").c_str(), true ); + waves_[3] = new FileLoop( (Stk::rawwavePath() + "fwavblnk.raw").c_str(), true ); + + this->setRatio(0, 1.0); + this->setRatio(1, 4.0); + this->setRatio(2, -510.0); + this->setRatio(3, -510.0); + + gains_[0] = fmGains_[99]; + gains_[1] = fmGains_[82]; + gains_[2] = fmGains_[92]; + gains_[3] = fmGains_[68]; + + adsr_[0]->setAllTimes( 0.001, 1.50, 0.0, 0.04); + adsr_[1]->setAllTimes( 0.001, 1.50, 0.0, 0.04); + adsr_[2]->setAllTimes( 0.001, 0.25, 0.0, 0.04); + adsr_[3]->setAllTimes( 0.001, 0.15, 0.0, 0.04); + + twozero_.setGain( 2.0 ); + vibrato_.setFrequency( 8.0 ); +} + +Wurley :: ~Wurley( void ) +{ +} + +void Wurley :: setFrequency( StkFloat frequency ) +{ +#if defined(_STK_DEBUG_) + if ( frequency <= 0.0 ) { + oStream_ << "Wurley::setFrequency: argument is less than or equal to zero!"; + handleError( StkError::WARNING ); return; + } +#endif + + baseFrequency_ = frequency; + waves_[0]->setFrequency( baseFrequency_ * ratios_[0]); + waves_[1]->setFrequency( baseFrequency_ * ratios_[1]); + waves_[2]->setFrequency( ratios_[2] ); // Note here a 'fixed resonance'. + waves_[3]->setFrequency( ratios_[3] ); +} + +void Wurley :: noteOn( StkFloat frequency, StkFloat amplitude ) +{ + gains_[0] = amplitude * fmGains_[99]; + gains_[1] = amplitude * fmGains_[82]; + gains_[2] = amplitude * fmGains_[82]; + gains_[3] = amplitude * fmGains_[68]; + this->setFrequency( frequency ); + this->keyOn(); +} + +} // stk namespace diff --git a/7.x/src/stk/stkOpcodes.cpp b/7.x/src/stk/stkOpcodes.cpp new file mode 100644 index 0000000..0f721fd --- /dev/null +++ b/7.x/src/stk/stkOpcodes.cpp @@ -0,0 +1,616 @@ +/* + This file is part of Csound. + + The Csound Library is free software; you can redistribute it + and/or modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + Csound is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with Csound; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA +*/ +/* + * CSOUND 7 OPCODES FOR PERRY COOK'S SYNTHESIS TOOLKIT IN C++ (STK) INSTRUMENT + * + * This code is copyright (C) 2005 by Michael Gogins. It is licensed under the + * same terms as the Synthesis Toolkit in C++ by Perry R. Cook and Gary P. + * Scavone. + * + * The STK sources are bundled in this directory (include/, src/ and + * rawwaves/) so that a patched copy is always used, independent of any system + * or package-manager STK installation. The rawwaves path defaults to the + * installed location (DEFAULT_RAWWAVE_PATH) and can be overridden with the + * RAWWAVE_PATH environment variable. + * + * All these opcodes are named "STK" + the STK classname, + * e.g. "STKBowed" for the Bowed instrument. + * + * All the STK opcodes have the same signature: + * + * aout STKName ifrequency igain {kcontroller0, kvalue1,...,kcontroller7, + * kvalue7} + * + * They take a frequency in Hertz and a gain parameter in the range [0, 1], + * plus up to eight optional krate controller-value pairs, and return an arate + * signal that should be more or less in the range [-1, +1]. + * See the STK class documentation to determine the controller numbers + * used by each instrument. + */ + +/* Standard library headers must be included before any STK or Csound headers so + that STK's and Csound's "PI" do not clash with the standard library. */ +#include +#include +#include +#include +#include +#include + +#include "BandedWG.h" +#include "BeeThree.h" +#include "BlowBotl.h" +#include "BlowHole.h" +#include "Bowed.h" +#include "Brass.h" +#include "Clarinet.h" +#include "Drummer.h" +#include "FMVoices.h" +#include "Flute.h" +#include "HevyMetl.h" +#include "Mandolin.h" +#include "Stk.h" +#include "ModalBar.h" +#include "Moog.h" +#include "PercFlut.h" +#include "Plucked.h" +#include "Resonate.h" +#include "Rhodey.h" +#include "Saxofony.h" +#include "Shakers.h" +#include "Simple.h" +#include "Sitar.h" +#include "StifKarp.h" +#include "TubeBell.h" +#include "VoicForm.h" +#include "Whistle.h" +#include "Wurley.h" + +extern "C" { +#include +} + +#include "OpcodeBase.hpp" + +using namespace stk; +using namespace csound; +using namespace std; + +template +class STKInstrumentAdapter : public OpcodeBase> { +public: + // Outputs. + cs_float *aoutput; + // Inputs. + cs_float *ifrequency; + cs_float *igain; + cs_float *kcontroller0; + cs_float *kvalue0; + cs_float *kcontroller1; + cs_float *kvalue1; + cs_float *kcontroller2; + cs_float *kvalue2; + cs_float *kcontroller3; + cs_float *kvalue3; + cs_float *kcontroller4; + cs_float *kvalue4; + cs_float *kcontroller5; + cs_float *kvalue5; + cs_float *kcontroller6; + cs_float *kvalue6; + cs_float *kcontroller7; + cs_float *kvalue7; + // State. + T *instrument; + bool released; + cs_float oldkcontroller0; + cs_float oldkvalue0; + cs_float oldkcontroller1; + cs_float oldkvalue1; + cs_float oldkcontroller2; + cs_float oldkvalue2; + cs_float oldkcontroller3; + cs_float oldkvalue3; + cs_float oldkcontroller4; + cs_float oldkvalue4; + cs_float oldkcontroller5; + cs_float oldkvalue5; + cs_float oldkcontroller6; + cs_float oldkvalue6; + cs_float oldkcontroller7; + cs_float oldkvalue7; + STKInstrumentAdapter() : instrument(0) {} + int32_t init(CSOUND *csound) { + if (!instrument) { + try { + instrument = new T(); + } catch (StkError &e) { + return csound->InitError(csound, "STK: %s", e.getMessage().c_str()); + } + } + instrument->noteOn(*ifrequency, *igain); + released = false; + oldkcontroller0 = -1.0; + oldkvalue0 = -1.0; + oldkcontroller1 = -1.0; + oldkvalue1 = -1.0; + oldkcontroller2 = -1.0; + oldkvalue2 = -1.0; + oldkcontroller3 = -1.0; + oldkvalue3 = -1.0; + oldkcontroller4 = -1.0; + oldkvalue4 = -1.0; + oldkcontroller5 = -1.0; + oldkvalue5 = -1.0; + oldkcontroller6 = -1.0; + oldkvalue6 = -1.0; + oldkcontroller7 = -1.0; + oldkvalue7 = -1.0; + return OK; + } + int32_t kontrol(CSOUND *csound) { + if (!released) { + if (*kcontroller0 != oldkcontroller0 || *kvalue0 != oldkvalue0) { + instrument->controlChange(static_cast(*kcontroller0), *kvalue0); + oldkcontroller0 = *kcontroller0; + oldkvalue0 = *kvalue0; + } + if (*kcontroller1 != oldkcontroller1 || *kvalue1 != oldkvalue1) { + instrument->controlChange(static_cast(*kcontroller1), *kvalue1); + oldkcontroller1 = *kcontroller1; + oldkvalue1 = *kvalue1; + } + if (*kcontroller2 != oldkcontroller2 || *kvalue2 != oldkvalue2) { + instrument->controlChange(static_cast(*kcontroller2), *kvalue2); + oldkcontroller2 = *kcontroller2; + oldkvalue2 = *kvalue2; + } + if (*kcontroller3 != oldkcontroller3 || *kvalue3 != oldkvalue3) { + instrument->controlChange(static_cast(*kcontroller3), *kvalue3); + oldkcontroller3 = *kcontroller3; + oldkvalue3 = *kvalue3; + } + if (*kcontroller4 != oldkcontroller4 || *kvalue4 != oldkvalue4) { + instrument->controlChange(static_cast(*kcontroller4), *kvalue4); + oldkcontroller4 = *kcontroller4; + oldkvalue4 = *kvalue4; + } + if (*kcontroller5 != oldkcontroller5 || *kvalue5 != oldkvalue5) { + instrument->controlChange(static_cast(*kcontroller5), *kvalue5); + oldkcontroller5 = *kcontroller5; + oldkvalue5 = *kvalue5; + } + if (*kcontroller6 != oldkcontroller6 || *kvalue6 != oldkvalue6) { + instrument->controlChange(static_cast(*kcontroller6), *kvalue6); + oldkcontroller6 = *kcontroller6; + oldkvalue6 = *kvalue6; + } + if (*kcontroller7 != oldkcontroller7 || *kvalue7 != oldkvalue7) { + instrument->controlChange(static_cast(*kcontroller7), *kvalue7); + oldkcontroller7 = *kcontroller7; + oldkvalue7 = *kvalue7; + } + } + uint32_t offset = this->kperiodOffset(); + uint32_t end = this->kperiodEnd(); + uint32_t nsmps = this->ksmps(); + for (uint32_t i = 0; i < offset; i++) { + aoutput[i] = 0; + } + if (!released) { + for (uint32_t i = offset; i < end; i++) { + aoutput[i] = instrument->tick(); + } + } else { + for (uint32_t i = offset; i < end; i++) { + aoutput[i] = 0; + } + } + for (uint32_t i = end; i < nsmps; i++) { + aoutput[i] = 0; + } + return OK; + } + int32_t deinit(CSOUND *csound) { + if (instrument) { + delete instrument; + instrument = 0; + } + return OK; + } + static int32_t deinit_(CSOUND *csound, void *opcode) { + return reinterpret_cast *>(opcode)->deinit(csound); + } +}; + +template +class STKInstrumentAdapter1 : public OpcodeBase> { +public: + // Outputs. + cs_float *aoutput; + // Inputs. + cs_float *ifrequency; + cs_float *igain; + cs_float *kcontroller0; + cs_float *kvalue0; + cs_float *kcontroller1; + cs_float *kvalue1; + cs_float *kcontroller2; + cs_float *kvalue2; + cs_float *kcontroller3; + cs_float *kvalue3; + cs_float *kcontroller4; + cs_float *kvalue4; + cs_float *kcontroller5; + cs_float *kvalue5; + cs_float *kcontroller6; + cs_float *kvalue6; + cs_float *kcontroller7; + cs_float *kvalue7; + // State. + T *instrument; + bool released; + cs_float oldkcontroller0; + cs_float oldkvalue0; + cs_float oldkcontroller1; + cs_float oldkvalue1; + cs_float oldkcontroller2; + cs_float oldkvalue2; + cs_float oldkcontroller3; + cs_float oldkvalue3; + cs_float oldkcontroller4; + cs_float oldkvalue4; + cs_float oldkcontroller5; + cs_float oldkvalue5; + cs_float oldkcontroller6; + cs_float oldkvalue6; + cs_float oldkcontroller7; + cs_float oldkvalue7; + STKInstrumentAdapter1() : instrument(0) {} + int32_t init(CSOUND *csound) { + if (!instrument) { + try { + instrument = new T((StkFloat)10.0); + } catch (StkError &e) { + return csound->InitError(csound, "STK: %s", e.getMessage().c_str()); + } + } + instrument->noteOn(*ifrequency, *igain); + released = false; + oldkcontroller0 = -1.0; + oldkvalue0 = -1.0; + oldkcontroller1 = -1.0; + oldkvalue1 = -1.0; + oldkcontroller2 = -1.0; + oldkvalue2 = -1.0; + oldkcontroller3 = -1.0; + oldkvalue3 = -1.0; + oldkcontroller4 = -1.0; + oldkvalue4 = -1.0; + oldkcontroller5 = -1.0; + oldkvalue5 = -1.0; + oldkcontroller6 = -1.0; + oldkvalue6 = -1.0; + oldkcontroller7 = -1.0; + oldkvalue7 = -1.0; + return OK; + } + int32_t kontrol(CSOUND *csound) { + if (!released) { + if (*kcontroller0 != oldkcontroller0 || *kvalue0 != oldkvalue0) { + instrument->controlChange(static_cast(*kcontroller0), *kvalue0); + oldkcontroller0 = *kcontroller0; + oldkvalue0 = *kvalue0; + } + if (*kcontroller1 != oldkcontroller1 || *kvalue1 != oldkvalue1) { + instrument->controlChange(static_cast(*kcontroller1), *kvalue1); + oldkcontroller1 = *kcontroller1; + oldkvalue1 = *kvalue1; + } + if (*kcontroller2 != oldkcontroller2 || *kvalue2 != oldkvalue2) { + instrument->controlChange(static_cast(*kcontroller2), *kvalue2); + oldkcontroller2 = *kcontroller2; + oldkvalue2 = *kvalue2; + } + if (*kcontroller3 != oldkcontroller3 || *kvalue3 != oldkvalue3) { + instrument->controlChange(static_cast(*kcontroller3), *kvalue3); + oldkcontroller3 = *kcontroller3; + oldkvalue3 = *kvalue3; + } + if (*kcontroller4 != oldkcontroller4 || *kvalue4 != oldkvalue4) { + instrument->controlChange(static_cast(*kcontroller4), *kvalue4); + oldkcontroller4 = *kcontroller4; + oldkvalue4 = *kvalue4; + } + if (*kcontroller5 != oldkcontroller5 || *kvalue5 != oldkvalue5) { + instrument->controlChange(static_cast(*kcontroller5), *kvalue5); + oldkcontroller5 = *kcontroller5; + oldkvalue5 = *kvalue5; + } + if (*kcontroller6 != oldkcontroller6 || *kvalue6 != oldkvalue6) { + instrument->controlChange(static_cast(*kcontroller6), *kvalue6); + oldkcontroller6 = *kcontroller6; + oldkvalue6 = *kvalue6; + } + if (*kcontroller7 != oldkcontroller7 || *kvalue7 != oldkvalue7) { + instrument->controlChange(static_cast(*kcontroller7), *kvalue7); + oldkcontroller7 = *kcontroller7; + oldkvalue7 = *kvalue7; + } + } + uint32_t offset = this->kperiodOffset(); + uint32_t end = this->kperiodEnd(); + uint32_t nsmps = this->ksmps(); + for (uint32_t i = 0; i < offset; i++) { + aoutput[i] = 0; + } + if (!released) { + for (uint32_t i = offset; i < end; i++) { + aoutput[i] = instrument->tick(); + } + } else { + for (uint32_t i = offset; i < end; i++) { + aoutput[i] = 0; + } + } + for (uint32_t i = end; i < nsmps; i++) { + aoutput[i] = 0; + } + return OK; + } + int32_t deinit(CSOUND *csound) { + if (instrument) { + delete instrument; + instrument = 0; + } + return OK; + } + static int32_t deinit_(CSOUND *csound, void *opcode) { + return reinterpret_cast *>(opcode)->deinit(csound); + } +}; + +extern "C" { +OENTRY oentries[] = { + { + (char *)"STKBandedWG", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKBeeThree", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKBlowBotl", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKBlowHole", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKBowed", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKBrass", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKClarinet", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKDrummer", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKFlute", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKFMVoices", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKHevyMetl", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKMandolin", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKModalBar", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKMoog", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKPercFlut", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKPlucked", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKResonate", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKRhodey", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKSaxofony", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKShakers", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKSimple", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKSitar", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKStifKarp", sizeof(STKInstrumentAdapter1), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter1::init_, + (SUBR)STKInstrumentAdapter1::kontrol_, + (SUBR)STKInstrumentAdapter1::deinit_, 0, 0, + }, + { + (char *)"STKTubeBell", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKVoicForm", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKWhistle", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + (char *)"STKWurley", sizeof(STKInstrumentAdapter), 0, + (char *)"a", (char *)"iiJJJJJJJJJJJJJJJJ", + (SUBR)STKInstrumentAdapter::init_, + (SUBR)STKInstrumentAdapter::kontrol_, + (SUBR)STKInstrumentAdapter::deinit_, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }}; +} + +PUBLIC int32_t csoundModuleCreate(CSOUND *csound) { return 0; } + +PUBLIC int32_t csoundModuleInit(CSOUND *csound) { + const char *path = csound->GetEnv(csound, "RAWWAVE_PATH"); +#ifdef DEFAULT_RAWWAVE_PATH + if (path == NULL || strlen(path) == 0) + path = DEFAULT_RAWWAVE_PATH; +#endif + if (path == NULL || strlen(path) == 0) { + if (UNLIKELY(csound->GetDebug(csound))) + csound->Warning(csound, + "%s", Str("STK opcodes not available: define environment " + "variable RAWWAVE_PATH\n(points " + "to rawwaves directory) to use STK opcodes.")); + return 0; + } else { + Stk::setRawwavePath(path); + } + int32_t status = 0; + for (OENTRY *ep = &oentries[0]; ep->opname; ep++) { + status |= csound->AppendOpcode( + csound, ep->opname, ep->dsblksiz, ep->flags, ep->outypes, ep->intypes, + (int32_t (*)(CSOUND *, void *))ep->init, + (int32_t (*)(CSOUND *, void *))ep->perf, + (int32_t (*)(CSOUND *, void *))ep->deinit); + } + return status; +} + +PUBLIC int32_t csoundModuleDestroy(CSOUND *csound) { return 0; } + +PUBLIC int32_t csoundModuleInfo(void) { return CSOUND_MODULE_INFO; }