From ecc55305849515717d38b48a826251117ecfa415 Mon Sep 17 00:00:00 2001 From: vlazzarini Date: Tue, 13 Oct 2026 09:38:02 +0100 Subject: [PATCH 1/5] Add STK opcodes Port the Csound 6.x STK opcode plugin (27 Synthesis ToolKit instruments) to the Csound 7 plugin API, building STK 4.6.2 from sources vendored under 7.x/src/stk so that a patched copy is always used. Patch Sitar::setFrequency to remove the noise modulation of the delay line that could make STKSitar build up clipped samples over time (see csound/plugins#4). Build the stkops plugin by default, install its rawwaves, and add it to the Linux, macOS and Windows release packages. --- .github/workflows/plugins_build_7.yml | 13 +- 7.x/CMakeLists.txt | 1 + 7.x/README.md | 34 +- 7.x/src/stk/CMakeLists.txt | 28 + 7.x/src/stk/LICENSE | 27 + 7.x/src/stk/OpcodeBase.hpp | 499 ++++++++++++++ 7.x/src/stk/include/ADSR.h | 179 +++++ 7.x/src/stk/include/Asymp.h | 146 +++++ 7.x/src/stk/include/BandedWG.h | 153 +++++ 7.x/src/stk/include/BeeThree.h | 127 ++++ 7.x/src/stk/include/BiQuad.h | 186 ++++++ 7.x/src/stk/include/Blit.h | 151 +++++ 7.x/src/stk/include/BlitSaw.h | 148 +++++ 7.x/src/stk/include/BlitSquare.h | 170 +++++ 7.x/src/stk/include/BlowBotl.h | 144 ++++ 7.x/src/stk/include/BlowHole.h | 185 ++++++ 7.x/src/stk/include/BowTable.h | 150 +++++ 7.x/src/stk/include/Bowed.h | 157 +++++ 7.x/src/stk/include/Brass.h | 147 +++++ 7.x/src/stk/include/Chorus.h | 172 +++++ 7.x/src/stk/include/Clarinet.h | 152 +++++ 7.x/src/stk/include/Cubic.h | 138 ++++ 7.x/src/stk/include/Delay.h | 188 ++++++ 7.x/src/stk/include/DelayA.h | 207 ++++++ 7.x/src/stk/include/DelayL.h | 230 +++++++ 7.x/src/stk/include/Drummer.h | 130 ++++ 7.x/src/stk/include/Echo.h | 120 ++++ 7.x/src/stk/include/Effect.h | 79 +++ 7.x/src/stk/include/Envelope.h | 131 ++++ 7.x/src/stk/include/FM.h | 119 ++++ 7.x/src/stk/include/FMVoices.h | 135 ++++ 7.x/src/stk/include/FileLoop.h | 165 +++++ 7.x/src/stk/include/FileRead.h | 141 ++++ 7.x/src/stk/include/FileWrite.h | 116 ++++ 7.x/src/stk/include/FileWvIn.h | 208 ++++++ 7.x/src/stk/include/FileWvOut.h | 102 +++ 7.x/src/stk/include/Filter.h | 124 ++++ 7.x/src/stk/include/Fir.h | 155 +++++ 7.x/src/stk/include/Flute.h | 167 +++++ 7.x/src/stk/include/FormSwep.h | 190 ++++++ 7.x/src/stk/include/FreeVerb.h | 251 +++++++ 7.x/src/stk/include/Function.h | 41 ++ 7.x/src/stk/include/Generator.h | 50 ++ 7.x/src/stk/include/Granulate.h | 209 ++++++ 7.x/src/stk/include/Guitar.h | 202 ++++++ 7.x/src/stk/include/HevyMetl.h | 126 ++++ 7.x/src/stk/include/Iir.h | 204 ++++++ 7.x/src/stk/include/Instrmnt.h | 108 +++ 7.x/src/stk/include/JCRev.h | 166 +++++ 7.x/src/stk/include/JetTable.h | 112 ++++ 7.x/src/stk/include/LentPitShift.h | 263 ++++++++ 7.x/src/stk/include/Mandolin.h | 143 ++++ 7.x/src/stk/include/Mesh2D.h | 144 ++++ 7.x/src/stk/include/MidiFileIn.h | 135 ++++ 7.x/src/stk/include/Modal.h | 154 +++++ 7.x/src/stk/include/ModalBar.h | 65 ++ 7.x/src/stk/include/Modulate.h | 111 ++++ 7.x/src/stk/include/Moog.h | 125 ++++ 7.x/src/stk/include/NRev.h | 160 +++++ 7.x/src/stk/include/Noise.h | 84 +++ 7.x/src/stk/include/OnePole.h | 134 ++++ 7.x/src/stk/include/OneZero.h | 134 ++++ 7.x/src/stk/include/PRCRev.h | 140 ++++ 7.x/src/stk/include/PercFlut.h | 127 ++++ 7.x/src/stk/include/Phonemes.h | 55 ++ 7.x/src/stk/include/PitShift.h | 107 +++ 7.x/src/stk/include/Plucked.h | 117 ++++ 7.x/src/stk/include/PoleZero.h | 113 ++++ 7.x/src/stk/include/Recorder.h | 167 +++++ 7.x/src/stk/include/ReedTable.h | 143 ++++ 7.x/src/stk/include/Resonate.h | 124 ++++ 7.x/src/stk/include/Rhodey.h | 128 ++++ 7.x/src/stk/include/SKINImsg.h | 129 ++++ 7.x/src/stk/include/SKINItbl.h | 135 ++++ 7.x/src/stk/include/Sampler.h | 75 +++ 7.x/src/stk/include/Saxofony.h | 166 +++++ 7.x/src/stk/include/Shakers.h | 332 ++++++++++ 7.x/src/stk/include/Simple.h | 126 ++++ 7.x/src/stk/include/SineWave.h | 159 +++++ 7.x/src/stk/include/SingWave.h | 136 ++++ 7.x/src/stk/include/Sitar.h | 129 ++++ 7.x/src/stk/include/Skini.h | 122 ++++ 7.x/src/stk/include/Sphere.h | 79 +++ 7.x/src/stk/include/StifKarp.h | 153 +++++ 7.x/src/stk/include/Stk.h | 637 ++++++++++++++++++ 7.x/src/stk/include/TapDelay.h | 220 +++++++ 7.x/src/stk/include/TubeBell.h | 125 ++++ 7.x/src/stk/include/Twang.h | 160 +++++ 7.x/src/stk/include/TwoPole.h | 153 +++++ 7.x/src/stk/include/TwoZero.h | 149 +++++ 7.x/src/stk/include/Vector3D.h | 68 ++ 7.x/src/stk/include/VoicForm.h | 161 +++++ 7.x/src/stk/include/Voicer.h | 219 +++++++ 7.x/src/stk/include/Whistle.h | 128 ++++ 7.x/src/stk/include/Wurley.h | 128 ++++ 7.x/src/stk/include/WvIn.h | 46 ++ 7.x/src/stk/include/WvOut.h | 85 +++ 7.x/src/stk/rawwaves/ahh.raw | Bin 0 -> 184 bytes 7.x/src/stk/rawwaves/bassdrum.raw | Bin 0 -> 4288 bytes 7.x/src/stk/rawwaves/britestk.raw | Bin 0 -> 4096 bytes 7.x/src/stk/rawwaves/cowbell1.raw | Bin 0 -> 6438 bytes 7.x/src/stk/rawwaves/crashcym.raw | Bin 0 -> 40776 bytes 7.x/src/stk/rawwaves/dope.raw | Bin 0 -> 20480 bytes 7.x/src/stk/rawwaves/eee.raw | Bin 0 -> 184 bytes 7.x/src/stk/rawwaves/fwavblnk.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/halfwave.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/hihatcym.raw | Bin 0 -> 4976 bytes 7.x/src/stk/rawwaves/impuls10.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/impuls20.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/impuls40.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/makefunc.c | 55 ++ 7.x/src/stk/rawwaves/makemidi.c | 33 + 7.x/src/stk/rawwaves/makewavs.c | 116 ++++ 7.x/src/stk/rawwaves/mand1.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand10.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand11.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand12.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand2.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand3.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand4.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand5.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand6.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand7.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand8.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mand9.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/mandpluk.raw | Bin 0 -> 8900 bytes 7.x/src/stk/rawwaves/marmstk1.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/ooo.raw | Bin 0 -> 184 bytes 7.x/src/stk/rawwaves/peksblnk.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/ppksblnk.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/ridecymb.raw | Bin 0 -> 32394 bytes 7.x/src/stk/rawwaves/silence.raw | Bin 0 -> 448 bytes 7.x/src/stk/rawwaves/sine.c | 22 + 7.x/src/stk/rawwaves/sineblnk.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/sinewave.raw | Bin 0 -> 2048 bytes 7.x/src/stk/rawwaves/snardrum.raw | Bin 0 -> 16660 bytes 7.x/src/stk/rawwaves/snglpeak.raw | Bin 0 -> 512 bytes 7.x/src/stk/rawwaves/tambourn.raw | Bin 0 -> 9240 bytes 7.x/src/stk/rawwaves/tomhidrm.raw | Bin 0 -> 27642 bytes 7.x/src/stk/rawwaves/tomlowdr.raw | Bin 0 -> 39200 bytes 7.x/src/stk/rawwaves/tommiddr.raw | Bin 0 -> 28292 bytes 7.x/src/stk/rawwaves/twopeaks.raw | Bin 0 -> 512 bytes 7.x/src/stk/src/ADSR.cpp | 181 ++++++ 7.x/src/stk/src/Asymp.cpp | 112 ++++ 7.x/src/stk/src/BandedWG.cpp | 364 +++++++++++ 7.x/src/stk/src/BeeThree.cpp | 79 +++ 7.x/src/stk/src/BiQuad.cpp | 103 +++ 7.x/src/stk/src/Blit.cpp | 78 +++ 7.x/src/stk/src/BlitSaw.cpp | 91 +++ 7.x/src/stk/src/BlitSquare.cpp | 95 +++ 7.x/src/stk/src/BlowBotl.cpp | 122 ++++ 7.x/src/stk/src/BlowHole.cpp | 224 +++++++ 7.x/src/stk/src/Bowed.cpp | 180 +++++ 7.x/src/stk/src/Brass.cpp | 158 +++++ 7.x/src/stk/src/Chorus.cpp | 56 ++ 7.x/src/stk/src/Clarinet.cpp | 138 ++++ 7.x/src/stk/src/Delay.cpp | 110 ++++ 7.x/src/stk/src/DelayA.cpp | 117 ++++ 7.x/src/stk/src/DelayL.cpp | 73 +++ 7.x/src/stk/src/Drummer.cpp | 141 ++++ 7.x/src/stk/src/Echo.cpp | 51 ++ 7.x/src/stk/src/Envelope.cpp | 87 +++ 7.x/src/stk/src/FM.cpp | 179 +++++ 7.x/src/stk/src/FMVoices.cpp | 164 +++++ 7.x/src/stk/src/FileLoop.cpp | 237 +++++++ 7.x/src/stk/src/FileRead.cpp | 903 ++++++++++++++++++++++++++ 7.x/src/stk/src/FileWrite.cpp | 799 +++++++++++++++++++++++ 7.x/src/stk/src/FileWvIn.cpp | 267 ++++++++ 7.x/src/stk/src/FileWvOut.cpp | 140 ++++ 7.x/src/stk/src/Fir.cpp | 74 +++ 7.x/src/stk/src/Flute.cpp | 163 +++++ 7.x/src/stk/src/FormSwep.cpp | 136 ++++ 7.x/src/stk/src/FreeVerb.cpp | 227 +++++++ 7.x/src/stk/src/Granulate.cpp | 296 +++++++++ 7.x/src/stk/src/Guitar.cpp | 244 +++++++ 7.x/src/stk/src/HevyMetl.cpp | 75 +++ 7.x/src/stk/src/Iir.cpp | 125 ++++ 7.x/src/stk/src/JCRev.cpp | 136 ++++ 7.x/src/stk/src/LentPitShift.cpp | 52 ++ 7.x/src/stk/src/Mandolin.cpp | 186 ++++++ 7.x/src/stk/src/Mesh2D.cpp | 367 +++++++++++ 7.x/src/stk/src/MidiFileIn.cpp | 353 ++++++++++ 7.x/src/stk/src/Modal.cpp | 169 +++++ 7.x/src/stk/src/ModalBar.cpp | 182 ++++++ 7.x/src/stk/src/Modulate.cpp | 49 ++ 7.x/src/stk/src/Moog.cpp | 111 ++++ 7.x/src/stk/src/NRev.cpp | 119 ++++ 7.x/src/stk/src/Noise.cpp | 34 + 7.x/src/stk/src/OnePole.cpp | 61 ++ 7.x/src/stk/src/OneZero.cpp | 49 ++ 7.x/src/stk/src/PRCRev.cpp | 120 ++++ 7.x/src/stk/src/PercFlut.cpp | 85 +++ 7.x/src/stk/src/Phonemes.cpp | 292 +++++++++ 7.x/src/stk/src/PitShift.cpp | 88 +++ 7.x/src/stk/src/Plucked.cpp | 98 +++ 7.x/src/stk/src/PoleZero.cpp | 73 +++ 7.x/src/stk/src/Recorder.cpp | 345 ++++++++++ 7.x/src/stk/src/Resonate.cpp | 113 ++++ 7.x/src/stk/src/Rhodey.cpp | 92 +++ 7.x/src/stk/src/Sampler.cpp | 53 ++ 7.x/src/stk/src/Saxofony.cpp | 177 +++++ 7.x/src/stk/src/Shakers.cpp | 729 +++++++++++++++++++++ 7.x/src/stk/src/Simple.cpp | 105 +++ 7.x/src/stk/src/SineWave.cpp | 78 +++ 7.x/src/stk/src/SingWave.cpp | 53 ++ 7.x/src/stk/src/Sitar.cpp | 95 +++ 7.x/src/stk/src/Skini.cpp | 228 +++++++ 7.x/src/stk/src/Sphere.cpp | 50 ++ 7.x/src/stk/src/StifKarp.cpp | 181 ++++++ 7.x/src/stk/src/Stk.cpp | 401 ++++++++++++ 7.x/src/stk/src/TapDelay.cpp | 87 +++ 7.x/src/stk/src/TubeBell.cpp | 78 +++ 7.x/src/stk/src/Twang.cpp | 103 +++ 7.x/src/stk/src/TwoPole.cpp | 76 +++ 7.x/src/stk/src/TwoZero.cpp | 74 +++ 7.x/src/stk/src/VoicForm.cpp | 189 ++++++ 7.x/src/stk/src/Voicer.cpp | 224 +++++++ 7.x/src/stk/src/Whistle.cpp | 256 ++++++++ 7.x/src/stk/src/Wurley.cpp | 94 +++ 7.x/src/stk/stkOpcodes.cpp | 610 +++++++++++++++++ 220 files changed, 28103 insertions(+), 3 deletions(-) create mode 100644 7.x/src/stk/CMakeLists.txt create mode 100644 7.x/src/stk/LICENSE create mode 100644 7.x/src/stk/OpcodeBase.hpp create mode 100644 7.x/src/stk/include/ADSR.h create mode 100644 7.x/src/stk/include/Asymp.h create mode 100644 7.x/src/stk/include/BandedWG.h create mode 100644 7.x/src/stk/include/BeeThree.h create mode 100644 7.x/src/stk/include/BiQuad.h create mode 100644 7.x/src/stk/include/Blit.h create mode 100644 7.x/src/stk/include/BlitSaw.h create mode 100644 7.x/src/stk/include/BlitSquare.h create mode 100644 7.x/src/stk/include/BlowBotl.h create mode 100644 7.x/src/stk/include/BlowHole.h create mode 100644 7.x/src/stk/include/BowTable.h create mode 100644 7.x/src/stk/include/Bowed.h create mode 100644 7.x/src/stk/include/Brass.h create mode 100644 7.x/src/stk/include/Chorus.h create mode 100644 7.x/src/stk/include/Clarinet.h create mode 100644 7.x/src/stk/include/Cubic.h create mode 100644 7.x/src/stk/include/Delay.h create mode 100644 7.x/src/stk/include/DelayA.h create mode 100644 7.x/src/stk/include/DelayL.h create mode 100644 7.x/src/stk/include/Drummer.h create mode 100644 7.x/src/stk/include/Echo.h create mode 100644 7.x/src/stk/include/Effect.h create mode 100644 7.x/src/stk/include/Envelope.h create mode 100644 7.x/src/stk/include/FM.h create mode 100644 7.x/src/stk/include/FMVoices.h create mode 100644 7.x/src/stk/include/FileLoop.h create mode 100644 7.x/src/stk/include/FileRead.h create mode 100644 7.x/src/stk/include/FileWrite.h create mode 100644 7.x/src/stk/include/FileWvIn.h create mode 100644 7.x/src/stk/include/FileWvOut.h create mode 100644 7.x/src/stk/include/Filter.h create mode 100644 7.x/src/stk/include/Fir.h create mode 100644 7.x/src/stk/include/Flute.h create mode 100644 7.x/src/stk/include/FormSwep.h create mode 100644 7.x/src/stk/include/FreeVerb.h create mode 100644 7.x/src/stk/include/Function.h create mode 100644 7.x/src/stk/include/Generator.h create mode 100644 7.x/src/stk/include/Granulate.h create mode 100644 7.x/src/stk/include/Guitar.h create mode 100644 7.x/src/stk/include/HevyMetl.h create mode 100644 7.x/src/stk/include/Iir.h create mode 100644 7.x/src/stk/include/Instrmnt.h create mode 100644 7.x/src/stk/include/JCRev.h create mode 100644 7.x/src/stk/include/JetTable.h create mode 100644 7.x/src/stk/include/LentPitShift.h create mode 100644 7.x/src/stk/include/Mandolin.h create mode 100644 7.x/src/stk/include/Mesh2D.h create mode 100644 7.x/src/stk/include/MidiFileIn.h create mode 100644 7.x/src/stk/include/Modal.h create mode 100644 7.x/src/stk/include/ModalBar.h create mode 100644 7.x/src/stk/include/Modulate.h create mode 100644 7.x/src/stk/include/Moog.h create mode 100644 7.x/src/stk/include/NRev.h create mode 100644 7.x/src/stk/include/Noise.h create mode 100644 7.x/src/stk/include/OnePole.h create mode 100644 7.x/src/stk/include/OneZero.h create mode 100644 7.x/src/stk/include/PRCRev.h create mode 100644 7.x/src/stk/include/PercFlut.h create mode 100644 7.x/src/stk/include/Phonemes.h create mode 100644 7.x/src/stk/include/PitShift.h create mode 100644 7.x/src/stk/include/Plucked.h create mode 100644 7.x/src/stk/include/PoleZero.h create mode 100644 7.x/src/stk/include/Recorder.h create mode 100644 7.x/src/stk/include/ReedTable.h create mode 100644 7.x/src/stk/include/Resonate.h create mode 100644 7.x/src/stk/include/Rhodey.h create mode 100644 7.x/src/stk/include/SKINImsg.h create mode 100644 7.x/src/stk/include/SKINItbl.h create mode 100644 7.x/src/stk/include/Sampler.h create mode 100644 7.x/src/stk/include/Saxofony.h create mode 100644 7.x/src/stk/include/Shakers.h create mode 100644 7.x/src/stk/include/Simple.h create mode 100644 7.x/src/stk/include/SineWave.h create mode 100644 7.x/src/stk/include/SingWave.h create mode 100644 7.x/src/stk/include/Sitar.h create mode 100644 7.x/src/stk/include/Skini.h create mode 100644 7.x/src/stk/include/Sphere.h create mode 100644 7.x/src/stk/include/StifKarp.h create mode 100644 7.x/src/stk/include/Stk.h create mode 100644 7.x/src/stk/include/TapDelay.h create mode 100644 7.x/src/stk/include/TubeBell.h create mode 100644 7.x/src/stk/include/Twang.h create mode 100644 7.x/src/stk/include/TwoPole.h create mode 100644 7.x/src/stk/include/TwoZero.h create mode 100644 7.x/src/stk/include/Vector3D.h create mode 100644 7.x/src/stk/include/VoicForm.h create mode 100644 7.x/src/stk/include/Voicer.h create mode 100644 7.x/src/stk/include/Whistle.h create mode 100644 7.x/src/stk/include/Wurley.h create mode 100644 7.x/src/stk/include/WvIn.h create mode 100644 7.x/src/stk/include/WvOut.h create mode 100644 7.x/src/stk/rawwaves/ahh.raw create mode 100644 7.x/src/stk/rawwaves/bassdrum.raw create mode 100644 7.x/src/stk/rawwaves/britestk.raw create mode 100644 7.x/src/stk/rawwaves/cowbell1.raw create mode 100644 7.x/src/stk/rawwaves/crashcym.raw create mode 100644 7.x/src/stk/rawwaves/dope.raw create mode 100644 7.x/src/stk/rawwaves/eee.raw create mode 100644 7.x/src/stk/rawwaves/fwavblnk.raw create mode 100644 7.x/src/stk/rawwaves/halfwave.raw create mode 100644 7.x/src/stk/rawwaves/hihatcym.raw create mode 100644 7.x/src/stk/rawwaves/impuls10.raw create mode 100644 7.x/src/stk/rawwaves/impuls20.raw create mode 100644 7.x/src/stk/rawwaves/impuls40.raw create mode 100644 7.x/src/stk/rawwaves/makefunc.c create mode 100644 7.x/src/stk/rawwaves/makemidi.c create mode 100644 7.x/src/stk/rawwaves/makewavs.c create mode 100644 7.x/src/stk/rawwaves/mand1.raw create mode 100644 7.x/src/stk/rawwaves/mand10.raw create mode 100644 7.x/src/stk/rawwaves/mand11.raw create mode 100644 7.x/src/stk/rawwaves/mand12.raw create mode 100644 7.x/src/stk/rawwaves/mand2.raw create mode 100644 7.x/src/stk/rawwaves/mand3.raw create mode 100644 7.x/src/stk/rawwaves/mand4.raw create mode 100644 7.x/src/stk/rawwaves/mand5.raw create mode 100644 7.x/src/stk/rawwaves/mand6.raw create mode 100644 7.x/src/stk/rawwaves/mand7.raw create mode 100644 7.x/src/stk/rawwaves/mand8.raw create mode 100644 7.x/src/stk/rawwaves/mand9.raw create mode 100644 7.x/src/stk/rawwaves/mandpluk.raw create mode 100644 7.x/src/stk/rawwaves/marmstk1.raw create mode 100644 7.x/src/stk/rawwaves/ooo.raw create mode 100644 7.x/src/stk/rawwaves/peksblnk.raw create mode 100644 7.x/src/stk/rawwaves/ppksblnk.raw create mode 100644 7.x/src/stk/rawwaves/ridecymb.raw create mode 100644 7.x/src/stk/rawwaves/silence.raw create mode 100644 7.x/src/stk/rawwaves/sine.c create mode 100644 7.x/src/stk/rawwaves/sineblnk.raw create mode 100644 7.x/src/stk/rawwaves/sinewave.raw create mode 100644 7.x/src/stk/rawwaves/snardrum.raw create mode 100644 7.x/src/stk/rawwaves/snglpeak.raw create mode 100644 7.x/src/stk/rawwaves/tambourn.raw create mode 100644 7.x/src/stk/rawwaves/tomhidrm.raw create mode 100644 7.x/src/stk/rawwaves/tomlowdr.raw create mode 100644 7.x/src/stk/rawwaves/tommiddr.raw create mode 100644 7.x/src/stk/rawwaves/twopeaks.raw create mode 100644 7.x/src/stk/src/ADSR.cpp create mode 100644 7.x/src/stk/src/Asymp.cpp create mode 100644 7.x/src/stk/src/BandedWG.cpp create mode 100644 7.x/src/stk/src/BeeThree.cpp create mode 100644 7.x/src/stk/src/BiQuad.cpp create mode 100644 7.x/src/stk/src/Blit.cpp create mode 100644 7.x/src/stk/src/BlitSaw.cpp create mode 100644 7.x/src/stk/src/BlitSquare.cpp create mode 100644 7.x/src/stk/src/BlowBotl.cpp create mode 100644 7.x/src/stk/src/BlowHole.cpp create mode 100644 7.x/src/stk/src/Bowed.cpp create mode 100644 7.x/src/stk/src/Brass.cpp create mode 100644 7.x/src/stk/src/Chorus.cpp create mode 100644 7.x/src/stk/src/Clarinet.cpp create mode 100644 7.x/src/stk/src/Delay.cpp create mode 100644 7.x/src/stk/src/DelayA.cpp create mode 100644 7.x/src/stk/src/DelayL.cpp create mode 100644 7.x/src/stk/src/Drummer.cpp create mode 100644 7.x/src/stk/src/Echo.cpp create mode 100644 7.x/src/stk/src/Envelope.cpp create mode 100644 7.x/src/stk/src/FM.cpp create mode 100644 7.x/src/stk/src/FMVoices.cpp create mode 100644 7.x/src/stk/src/FileLoop.cpp create mode 100644 7.x/src/stk/src/FileRead.cpp create mode 100644 7.x/src/stk/src/FileWrite.cpp create mode 100644 7.x/src/stk/src/FileWvIn.cpp create mode 100644 7.x/src/stk/src/FileWvOut.cpp create mode 100644 7.x/src/stk/src/Fir.cpp create mode 100644 7.x/src/stk/src/Flute.cpp create mode 100644 7.x/src/stk/src/FormSwep.cpp create mode 100644 7.x/src/stk/src/FreeVerb.cpp create mode 100644 7.x/src/stk/src/Granulate.cpp create mode 100644 7.x/src/stk/src/Guitar.cpp create mode 100644 7.x/src/stk/src/HevyMetl.cpp create mode 100644 7.x/src/stk/src/Iir.cpp create mode 100644 7.x/src/stk/src/JCRev.cpp create mode 100644 7.x/src/stk/src/LentPitShift.cpp create mode 100644 7.x/src/stk/src/Mandolin.cpp create mode 100644 7.x/src/stk/src/Mesh2D.cpp create mode 100644 7.x/src/stk/src/MidiFileIn.cpp create mode 100644 7.x/src/stk/src/Modal.cpp create mode 100644 7.x/src/stk/src/ModalBar.cpp create mode 100644 7.x/src/stk/src/Modulate.cpp create mode 100644 7.x/src/stk/src/Moog.cpp create mode 100644 7.x/src/stk/src/NRev.cpp create mode 100644 7.x/src/stk/src/Noise.cpp create mode 100644 7.x/src/stk/src/OnePole.cpp create mode 100644 7.x/src/stk/src/OneZero.cpp create mode 100644 7.x/src/stk/src/PRCRev.cpp create mode 100644 7.x/src/stk/src/PercFlut.cpp create mode 100644 7.x/src/stk/src/Phonemes.cpp create mode 100644 7.x/src/stk/src/PitShift.cpp create mode 100644 7.x/src/stk/src/Plucked.cpp create mode 100644 7.x/src/stk/src/PoleZero.cpp create mode 100644 7.x/src/stk/src/Recorder.cpp create mode 100644 7.x/src/stk/src/Resonate.cpp create mode 100644 7.x/src/stk/src/Rhodey.cpp create mode 100644 7.x/src/stk/src/Sampler.cpp create mode 100644 7.x/src/stk/src/Saxofony.cpp create mode 100644 7.x/src/stk/src/Shakers.cpp create mode 100644 7.x/src/stk/src/Simple.cpp create mode 100644 7.x/src/stk/src/SineWave.cpp create mode 100644 7.x/src/stk/src/SingWave.cpp create mode 100644 7.x/src/stk/src/Sitar.cpp create mode 100644 7.x/src/stk/src/Skini.cpp create mode 100644 7.x/src/stk/src/Sphere.cpp create mode 100644 7.x/src/stk/src/StifKarp.cpp create mode 100644 7.x/src/stk/src/Stk.cpp create mode 100644 7.x/src/stk/src/TapDelay.cpp create mode 100644 7.x/src/stk/src/TubeBell.cpp create mode 100644 7.x/src/stk/src/Twang.cpp create mode 100644 7.x/src/stk/src/TwoPole.cpp create mode 100644 7.x/src/stk/src/TwoZero.cpp create mode 100644 7.x/src/stk/src/VoicForm.cpp create mode 100644 7.x/src/stk/src/Voicer.cpp create mode 100644 7.x/src/stk/src/Whistle.cpp create mode 100644 7.x/src/stk/src/Wurley.cpp create mode 100644 7.x/src/stk/stkOpcodes.cpp diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 00bf9d5..5316a50 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/rawwaves + cp 7.x/src/stk/rawwaves/*.wav package/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/rawwaves + cp 7.x/src/stk/rawwaves/*.wav package/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 'rawwaves' + New-Item -ItemType Directory -Force -Path $rawwaves | Out-Null + Copy-Item '7.x\src\stk\rawwaves\*.wav' -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..49a724f --- /dev/null +++ b/7.x/src/stk/OpcodeBase.hpp @@ -0,0 +1,499 @@ +/* + 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 + { + uint32_t end = opds.insdshead->ksmps_no_end; + if (end) { + return end; + } else { + return ksmps(); + } + } + 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 + { + uint32_t end = opds.insdshead->ksmps_no_end; + if (end) { + return end; + } else { + return ksmps(); + } + } + 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 0000000000000000000000000000000000000000..dda5f557f55beb212e4d1561e29ec0bef5577a1c GIT binary patch literal 184 zcmV;p07w7*m-&VAgX=x#J>~M~!}VDT103`#-8qaw2R$pwOYkS>qvBxVZs?-$L-wuwRR2%^&;kPm zy$HJw{Ta9*ups;%T^KM81^+PfL+uXjkn?E#TLy~|Bo>wzJrarxiU+m?1p$lz`U5=% mjRznE&H`Qlll}VpCHsK@5(Q8P`3RZ{O$ zQL#lLcB8Kv6a`u8Qg(Od-nsM6@6X>k@44sPIlFVuo_(JAJfHc39v~IGfH$ccnrE)D z5$rv#R=Hb!Lwi}*Y3OMjZEQ0gaoFcH#XN*T{P#TZSJS zl0PUssx34&WJq|IxUZ7e4!@gvYUF`I{UY1~cDd{HdT<@x#(GpLO=8>>Rr({2I(I`) zml!xOF_9YBrSHbRWzlXy1-{+}E1KGPw7T&>Yf->PYJQ^%)|;~&P}^t-wI z*47*NPT=F`k5ArT{nsB)qF<%HX|DP0W4JllQf!U4-E5RvCY}wl-s4%PuX)@@VcPj9w&R3hbDxq2rdrV z*6UHP{=q+nybj6_GV~bdNBm2ArG=TJj`iI*Fd^y5$kf!TVfus({i&!a!3X?jd%yBA zbRW`vhyRoA7Qa{iU3?dK>fL_zaPkZ4aWvpjK#9MfU%F3}SFuNLHy4*ymjdHm?M-bT z%_t2~$8rbROl}b8%tmqxxfFUW(RQA(^tX6h?ziu-_Tje*TlfmQTW3*ccIS$A=e9)4 z+P0M)C+rlNC}~AKzK=~pvhXc##M6Y8R@7G1da5a`;Yh1W7{lJwb#b`kI?Q#nL$0Ar zdtLdHI!RNfey3Wme4)~)59>os7H5U)A@_rx`@FyNdlEne#CNaveeQePcd~E2593DZ zv(%$hlNDo`zVsXJg8HQDm9j)vXxO7`*9zK1t)MS7b}<$i*PB9389ION5L6=znjmzb zYpB*fz;>P=#s~8MijS!T_7Uevxf5?RT~PBjzM5w{ueIqL{;oYn=9`)Um`CCxoHVw*8jomWakm^Pq-`Ce#|!ZnM$${~&MN7c69kF^P0ks+ zeVTk*yxKkQc0KK3@d)rVcr5F>v)l4+d%NX%zV+#7_jvB`~8eMYTmb$)kyk;8cIL0;8?K_u1m+P+II!!XB=_1vGl)otUt4?ThHLFz% zxV20#@EKYrn0PO2faBN%HK&=be9T(eHFPEX9*VS?O<<-lE|f*|=W7wm_p}DJH(Q>y zp|+WA+d4Mc1QH9+)5qwKR4i>^r*qwu{^~Mqxh_FpU|Qug+4ZekKc^PMW~Ex%X`RsG zXwIvORR$=aS>>=+xU`v#EWH&IcVD4X4y!F^)tKKH67XQ`!?-k2KwFNchyN zrt;ml)x+Pud|UFbYjvNR=DJ?>!^}UJ6U_nUN%dKEeC^!2FX}rR);2C|N@~t%Uf!Z= z(Kalv9bc__HR$=KCwCrgsap8pY1P(8lb>zawHs}>?W_3V{1PEhQ1NetTC@!BCZW=4 zPy@C}gT-ooc>8wC=$4$;dluYQ-I?09s*P$FEj^mQYIySL?@vSPLh3GmGS?mcw4%Oe z{rHBQ=G0cFmIck@T6(tjZ^>)+YaZHi$8xyip-pLD$xpX;2#e4n#G!J$2Iq)Jq&cvO z8?3sa8K+xl_-Im?&N=db4toUU9SX>im#>hiTWHBeQm%u(dAjdTZ`N)BU! zh6u4phtGl-${P|ooEgb@(YvXI&>d*VX>kDBE~MI9ZPTq8oj#r2I<*}$Iu^B4mM>a< zZQj(pusODAL}O;d=!Ss}JI#LPS&g7Es_{y5ee2EE#MT^3iv?Q>ER}87+siwMb&73( zU=rgLkTbe=)t;KNaChUj0CGT6<5|N4G}5Sl?B@P9Lc|r@NwEtev5Gtolcl zpdPF_qI1{H)s513(_0N63_t7D+Nl~h^?Y@NYLUuCm7+SV-mG4vny9Q&{-g?3WhkB5 zR;HYp#7t#;=p=|hA)uv7X$3h&c9SwGR@x!;B$@aQS|_{`-lC_X3mHJ}iyi?o~E zAXCUZGKM?=6xa(+fwy3`luF!07yL7xjf3!5QAJjRI5>=g)C>9vGmkmJoMU>@ec=z% zJaIRUM8((%?-7GZm{bJJ@E&{%YhW8Z46ne;@G5MlBB@c-ms9}kgw=2Kcwn&SmUqB+b55r+TEPzKT9Ti9YNc+=i z^w0EVI-UNSq2yG)nyuhMIW=3ve$Rf%4v;%g!+JCA%vIglTeo5b@Ptm{8@iI$UGFxg8CdH9s;!AOvct9*8USuP=M;ggBvYB{@ znfM{j!LfJuBak1h&!!IwYB_ve*- z7XMC|FVr9p6oY`UPe>5FglJ)fa1uqKG8}_#WS}%ldJ1B|E!YR%GM3mSE64{=;ZSI%GAJAEO-Io4=>c>sy3z%##XUA*+}LS^9M7L=IQgaH?hvH!{wCl75|MQ_j^l!?AUE744J zRQ~iD5!t4`s7209Ji3myqd6!7u`)vskqdf_-;-czDaeO=sl{}toXJ{bwma!Ux{O9l z1hbk+WX3QXnFCB6>&N=AySQ|&jH}_Ea}N}>g64=~lpxM|=t&z(3;oI8lCoJVJ$Xf;fOM&KFC`F0zAMkrJhi(jlM$D&PR>;dFS7 z(o?C_McPH?yPo-s$z?7w$C=;R5H^n4%Ve>TE#@3pJ6pz5 zjBGz9o9W7UF@E$%x`sYM|G|VZvzhTsFnxzUOCP6m>1e7`o~Etx^%Ye?4S)xrBRCAc z15>10X|kjzuVf!YiATg}x!O6{4_%RIjzUvW0E$N`f{2u;8`2<8v;dW$b!ag15Q>Eq z{3;I5A!r0j5~_qH!ZYM7+q6x1BG1Zx^bwV# xGIR?)!~JA?s>I%6lBmNs2r8;b{`6*##P8K#Qo<-D`bG`bTUB=n4{9GHMGBzsRjPO=y87@?@{%?X@9e24O&mpMS&1^)>7i0~7d?QA91H_M6Zt)A!@V$7KEwCy zWc1cvqHM8mXq)&=6oWoh`<9&#cvn;Xh)F))^FgMY%3eqiY>0zsB5fq9)dZp;bqm0LeS5;gEb}0=|Ot&5P4C& z0Rzx#eBJrfc=l#+pid4oX4?wX-J#nZG zeW;HD$CR03D^Cj|;Sa2UDfj+>NUDfJ5QD5WBa~-zh&?>PN zj1Z~r4wV_&Yx`eO3Y+qAVmH|YcHk(u)>$Fi!%k!$f2*7m&v1rxsgacr_1df+d}&`% z7t`M2iCj-QSxwYEc9E>E$16c9 zT*Ref(ND;GPQDr;mg7b6AnXFifWP2m8m{fOGhN{#78HX>r!^~5_KGqVsyB85U;yb< zJ}Ds6drglQob3ocT)ebexi1_)0OhP1Sz_HY9$8b#DAq~dLWWm!S2MpvBhb(68>QI% zTeqAyTJa+RCQca)ZOVNI^?xHiSh1%6S(_LD5Ui+N473;O)H;-!lQ`W_( zMTd}$EJ^#!ISr~JFZ-Rh4AhEq>eqSxvie?gKA$fSTIsHp?5H|ZI{dkI(Yrffr{{~H z{bs6?jLPgiM#0y$zEYjrB0E^9E+p2BH?o~ka+UZ?))TkUU{3H8cng%sPazgdX)_3|LPiF8ZZ*_N=XNA%Z<+A%~p*db@36ofkKGHm@M){T-apooW z(8@MOA)PLM2A5>G^L^kbJ4kDcE>w0gh}}n72`<7DGD8!VPx%!RdwJjj-$5;wv@#pG zez8l@X8g>)L2AjT7W zP#{UNccUq64ZEpK1eoMGsd%5KNzfa!x;9;&haKf1G|RbyN7H=mGN`IV@FEcdE`w#D z2ptnQL>JexN)#w)Z#>({)Bo`eHdZ^Kz*Gk~lazcO%42Xd9z~|hJ8%g2#~N!SStX<@ z&nC6lSEw^QZ0^&3athQVAXf|nt=KZ$lzT}pr;sF(du9i1ws(q}Pg7j!>{mRSk46Qc z6~6)&!6Y&QE|+<5AKQx_%1dMmdrxU;JyDMGdZ@9SE%M26Ib4pHt9T#HO^3Ml(eLCd z8N|2YOg<40qWiTxM}UuEHs3Cjw69qh`apKE=PF@f8OfzS6v^`Vc(R2CqHI!%Qprvp zC&ts?Ui{TY*b?OotwW~bwZ1>Kh2k^u zr5evoaY1$h(|IL6DdIsLG9NbK>)}ulEe?_+&QrY2pY{a!BkO?ogA)2ed(J-qh3I46 zkra!kARL|GSMX|jiu}SJ&%nm$THpl)fQc$CbQvr^uRx#L0}6o!95uYs*+ZAn)W9P1~Zi#^eP{z{$){h z6PQG^T;bM-+G^*#L~^@$tqo?=m67(33ZcOy+rF)a^90yIPFLeSZS{+yAv}xjqL$7R z(o;M^jYT%{i678$5F*3mL3miMAg|C-`=ya%Zc`HdiHSwy@MT+S-&+^dZBCK0UY>w^ zKsw;OfjX48<@fXx=2Y#nvl3lLU$Iaa1qOmJk%C%^Y48NRPNS4GQAaEzWo!~@%dWu% z;%|9VT}5kBe_J+kedt;H`xTV!%x^4UJI=PjK0$A*yqthP($g% zbCvs`zLNr;p)9-woB^H1RQ$qM7&y@L&Q*=Z=n2lhu$a$g?X~4#r%WSsI0cp|RB zAK~HrIlk{Vgy~p@Uwj>*<${WG=fnw4cVZGMUhb=3D)2>h~OS@0Dmk0r7Qr2;EL=GCgDH)KGhh# zXW!Bios0T!bStSz&yw5DAL>@N3Vs33f}dcxf3h9H9?=RP0`=H4ZJu>R-{vG}5$rhN z@-mtUKR`&n1S9xB6o^VhA`16+#!z>3&q>n^zMNd6&-Ddn6;+C1u#?~NyJNvyqrUJj z&=sA9$$X&}_YZxs#21{H)@4SKWW-fMV-F@!hw8}7} zA)hMuiUcL!nW%;HC!&fr#IB+;lI3ARV?7s0x^l!RF;bi_VF)bY9c7lXiMddLve4NF zKU0>O3tiOLCg7dbNO>!!_+2>>MLP`Nm6nV_hd?TL9~av}%5-_3|A5Am?oNWzmli0m z`3_hv-^mj=U7VCda2A`O&ZGHwHw=-JQCoDw?+KIjIhDiRE1co*251Gp5JB<>Whiuu z6=b8BfpS2)6QO0WozR1Rls&kqUi017Q>{zdXxb5#V(i~R4b{4wvjX+H1C-ZtJm?Fr z`AK$5ZlLu7j(g{5IpQ8PaWwTR^Q_Zq3GbmUm$zUalxfv*QEMJtuY}thLt@MK1pue1 zderwrXjqvYFq#qB*w4R2yg_`~tj#hZxtIapK6l?SniDc0*3HF3xN>4xeK7z-@$Lv4uu<}B88a?9QxU-dy=~K7{ z-4W}Qkxo0K$g|nKlh(w`U}q->#o&YVj*=)&_+97-eqk88Rv;3L+>1Dy|6wMqFJnr&TfQs%s9Ib5>SY7vnYSD&~m0 zCR}xmC2J9fH$x^rFH1J`j{?H-?iZiNcFhXc4+JS zthr`ob#=~xH|sJ|PK-;D&)&V>>FVekBX8GdPIwz#x(Me9&wYKHjMm;`T7@q4YuNa5 z;6?A^MjpP5AFscpPYw(VQv`L2+d4CR(z)?zGoqI_nBmd0UGsy|a&uUI*zCFj^PA+h&CYt0^Z18nTkSbAEo1w_D zTC<~GWxQN}>zC81r!F5|e(?H1)49pHqq+61jlD~{mUrBpur1+kM=5%FupAT@(j%g< z*{slWjjsA>8$;y8E=k?@#dc~nKXP-_owoW`zXfbIeo@b?@c%pPOzd7*|7=~#hTy$_ zTui??JnirM;fp(!L+|Z*zM&zGu z=Xbx->tP=up=bBOQ7xM$>gugrN&lRCZ|6Sle#84}?tM$Pn7#W==l7W(4FzLr_6w&} zO+7v;c0q>hXTSfoZ_bIwo1d@DTAjJ*LqtUy-V!{olmCy`CWp>hH-66$NBy)Vu#3GTLm$R+(Qz@EJr>3nw%r_5rXQq@^cbg0Rc=I`WnM@YW#t9q5%06cHeO5p zt-dde%M>r2YbipS7 z^+h*|#uesQ_?WudjMgY_ex-(EVscxlbg)&To0 zsRLS~>Sp-eSYXh&t>h;tZfUpqIGX&`xR+Oy=8SR;)6W%Ld!}S;-aj86e%w`jv0SJb zT0hCM$N7SJqkN5%%?C_F9CPGcS?SPOEG}8vDLP1m;mQXK&2xSd3(l9^EYGR=$@T=fD|c}XrG~;F>5#a__H*^<0>#Itc~d`j z$O->gQ~0LLr_K#zGO%dv$35B4-x;!B<@GGyU3$CB`L&UGH}>JL^Y6Kh z+zoa_6N`10% zj^VUhF&l^13Pq-aU*6`#z3crpHFMe9hVK^^{8Fv5H6xy?58VifXG?G#>QM@j2s(4V$&qNMkCkJY61DUSGDkXiCAlqF$vj z6+Np~)Wm(iWU||5fK~I*W1jmsZExKcokMe+eJK3pY$f=K3y2$cOLN|kZ#-gH;T7UN z-LO*AKs8Nc^Vq3BuDzg+RVtOUxW3XkD^v5KEWfy>w8=Lg%YEAg#}N6ELa!?1UdsQ7 zoVd<4&A!OG*rv6ftY29dX+Goh6q>rW3nx*zuBYG6fvtnTH9y#*zIlhhm7WuLn=8ds zQv2q6im8*S$dqq8j$Nv`+DL7Z)<-+R&6{gXG{QK03QAr zjz@tuLV1ZPBg4eqw$S>&ENh*ui79ZOZVH-O{Ki0ojAQxl^3JJ<>80z_>$__R;#+7K< zQG2O+X61jXW`0fie%W%{`J22Lr713HjM{B(&-gm%FD|j+dgj~lud13U-%D(ZNtSxM zuD$k%-rKX%=cK2gD^lH3Bype6d^|?lFE^A7VwCtvdLZ{A3`*ty)XZ^jsKM$m#d-E` zGG2IRUu3yn-^0|(gf02bPOgd06xTy}JvoZcio2cP#R2e+*D3ZYeH7>TH|%D_z<4qn zx0ll8rQ{3qnyukRsYd7y7}^=I;e^LTU84G|LR1b@1fdSDFiXq2pqhY+>e7G99#oI3 zSGxe8U_L8HYTM}d8Z`Q!+{#&_xY({WXPN`dTW#UaPx1&fg5AgOR=w23X)E;q7*>0j zv~9RB>@Q{r)y_WF!{#4tQ-wV^i@l&O^9b~g_V4VU=3T433UBO3>#lu!UwxvysB~x9 z>&p2x?M&@#qO<``=7*~cZo{>V=BfIkO08<4{Kh{euY?NMDrr81Fi({2wOc*98D!%% zLqA=LYB{?U-jZEXt)r#6O?^vCwmsdk-g(Ux=@gwEoRPvu{4@Ll00>ocDkhCMKpN)zBd-D809k@tHaU*lfwdzF?QMf9RwP`g$+ zBW&Rof7>YMMKME~CRss`zM_2q@)O~pBgxvdKA~=QZHI6A?_I5Zq@mnCb%k!X(co3& z?d{n}-%As$9LCNh*Q6c7T34#`jq9R3i;3rLiZW%PTQgmu$9j)ux=yMQ%xK}5`Np?a zl{Mv_<;|*we`7E|Ixg^~Xzu`300R0VC6v+I@Y{zxNAp3309?NBGfupNX zERT}wu$r_2C1IpFqF#)Vj$sYq$tQTpT<4ZiX4NX=_#`focdC-zg4N-?lUWJ89O@cv zn_@m}wp&*?WT_fi6ra>qZ8!Ht`fT^rZY#NdL?b?Q^>MVdjk0yHMcG?9_lVo&FL)C) zV2<-uDo@2B)}MTqJp^wDvP78Po7S1n+y5gLF+)_lbPElGy=r|DyaNqyw7IHA+#m=N zQyd}I&6ap`sWsXD(e*+)j-8B=&rng?su;yYzLpgrR3y#_dn4O5dspX1!6|#-AiR*g zM1L}?Q4-0*X3~)O<6U?F{!A`IJlfCl>|y31l9=7>4Sp0qh5Mb&AYNU9f+}YdiZd+#G;@m2X5R36l)PNbpTx8Wu5_FZ@i^Ier;vg|f zh;coYe8^d5DxaXVxp`=N>F&6D>(rXgs!iM#l!zaRBOH@$N~>&t;(8?I!gzLxV!X<% z#_DR-VLll7%iDy>j`@~aQ(9d=Q$I_*eXy&Y#E}VX8^uD^GWB%zAXOLsF`I<^akTiS zYm}?AI1Ou%ii=i8tFyI8ciVlb?!0EW@&k92*##S9tFXtBYkzKk;F#{%<(T65%hg=4 zN;Bl=xUn1|O%y+iMdDvVwNM}h;<;!p`sm!zc6(<)P&$ zo{3-=a&Bx-REJ`bH_}2jECU0H!#!|_Y?2;{=Y&V%blDSMChs7Cti?#IbD5mp_BgA< znr(0Dd@X%L@!SPvj^-!#&hA)uQxm1~=&J=ELZ_5-K;RVRTk5yKx1}cB#r?azBCOjqoVp-0T1nIq8 zM(&~Zj1P02eal}}4pB8#ja9mok^B;7BY7kT3Y(n2JClT_(%-n85V(OZGP9VrXc)O6 z6KRXsTA1XjalH~Ii92NupCh#}8MYt;(}i8b-eWJYC)p;4PMa&nz30Fm<)I}E*HlNjv#V=`sFAAi30TXtVejxi@VEI^Tt_yE z`4zPz^Wh<9ZHe3^DE z<|ivID#s~(_++*RIu5-^cf3npE3K0_@u_%Kd@h}k^NA9UFx}WwTnqL*vkE0c26=$P zq%tX0W@SwJfR%}4Rctobp6kg5LK)7Nv*Z)<2lLWwwA>M#F@E1;Zt#WmnA zaCO{&*m0;Fej{UWE2&s~C?<+)#4}Q$^bJQ72|3x#Tnf)}$5|JvL`AR=uax$S1I32o zcey=23G$I?1{496D|58s8>@kXaJms{l$!LMI@nQUyOdyYdg>`5jlgXavBDh2@kiE&gfDGJQiWQ!^ z1i@P<6H8vq0C$+pK&rbndWE&y*ped20TtS%hfmz7m?ZIATTI|xy{}o3b+jUNKEsH+CXBpN(U7(4N(Zw8U>H zyI3-X6u~kSNBjLArkFW}%8(!Y3ayBlbirG(T0SMulSj%+<KnHk4+V_&h4*=fvsrWd+{ykH91hu`5^Psu!@rxpJZC*WITISc?9twL2y3=@qo+6wugC#~^D z94x<-kIR4K2wXu9QMFBjHE<5TqaYLkc34Bo$$Zj-G$)=!L*hv(JVP5$ITOqbWFk=p z>I{p>Ldu9WvYTv>_v7C98?E9^sCUQsf=*R$?S)pe_7MRkIc?K#gEOc#y*+k@Tn5 zmn4xxM1-GUE^LL<@Br!%f@gG2#nPIeBSUDo5AaERiMW#+umt&0>@QFOI)lPtJ~>JN zpQjnzhHv3&5W-bK_8jZH2%_s>qg?rGNW-uC?aX#%N zT3XG2pdqY+LYkjQ6pB14_H7VKF4G%B9ZtuocrQMMKavi#H^xz}OQ;D7L;$bhSE~Kp zq#uc+IaxvWlgFT@dN>I0Xy$B?2e)AZ^dJJQ=M>VL?(axdlT23t%D|~)J4vUvh2}6CR>K}R z3I|{#%z|FvPEWW_*|~`-d?ID~9s1w#7|PK?%JDjQLNnEn+$Ga!4TDGsJ!=%%MGBw| zETSGxP!(*TUiw2bIzK+si1w2$WFwuPM`_ovKrBUd04`Eaq{19%M=Ug=$)po$OCwrL znGSU4R``=zDn&Jk&Z1@n(|B%DW$h;W$Pt>$yW|617x}-MPlbsvk}|-TRM0gk6#rRz zrvr$foESs1G6e=g3yS*_xkq=Vl08&`*GMKYffkxlR>s3Hm`-DwMYGTa{K$98^t0qQ zde#~8lGvaDbfjFK3bP@JuJtCBMkQT_MU5=)=8q29dd{Y9jh zN=YFpAbHe^XjiQzE~;`5@S}au3p4x8z04r(ik`oB z`n2Zg=;zF|s;jC20O$y`ZHV?jn}*12h&DhgpgGW@A)5o~|Nm^(u>LQa0Zo83AO%PT zk{dGV|1+s!{oh+!!?$#xH;^X-$4CzrbQ&p4zOj#pX!4tD?l@+a_5Gb`A9KSReIYC5 zr!xCYd~zytlI_kS$MLd*4d`jGxE92yDx_om>=hMIiAP#V?E!v>#&rC#B2q;jT*I<+1tD zDBkNB7k6gaHRp-Ua3FvCe{mQ4FSs`4+rz;(zExC?l^^yO>&MgyVou~9)_$@-v^^GW z`~4N>c`^gx{^6iCac+g=uRLqN&Z?XV_E6 ze+9!cj+u@5g$V=WKj!}Q?amOGS7#4atV*8GdS=IHb^Oqh+Qgy(&7Q;T;q#^z{jIv3 zIyB>;6!3(w2tpfNqZ%!2Rd|ZCK;(GGk+#(KzrLT>4=8(=%38^Md-qZxvUklfEd*H;L_Zjb^i!1o@bsc8s>g8J!{$+Y>05Bpt9py4S-D@I%4(~YRR77$M(P}!$u)yN%TKbGi*5uC%gVdO z`@RVh(((dx1g!;I-PO{+fp6Zbl7L8ob~p8|jNrCl*1f&WdRWw@5YZM2+SPZhDJ9=D zN}@5_!weK!Q@5sBuR+bx@>7DB`KTq7 zC3kJfQas$SA%BTzuRP$34ZPQX8-QY+a@Iugxw%qT^KouN(|sAazVUV9uuox7AU=6p z@vZu*w1>u9CiU-1sl|QuUPq?5_(h;oMZa$oSkLJ>AD&lE6c3;u2FIWYPhyii%|(Sp zramzS_SUqfubS~+rS7T!jFd|6(Zz}JjgN|}XmN9YTiU-EeBNz#tin5X6nlXA71;Py z{QDAa_|!mKpVN^CSDJTR*;A4wc=*kny}9IpWBT_N7Wcs?H(n5~^)C=?JA%@+kP!+e&!v#b4Q;Cc=W6 zlJne5OCdhevZb~X?CJ_F8>q3jHL^#z*2rF{N*o`=6lVy2wWJ!_MfMB4s=MmLOTUy+ zya}9jm3ifV;!qx!s5X`fExaZ0ccumYH1wSDR?UUfa3+$N=e5}%c>NrYfqpCYubXS*M61yUI%BZ1j|avh>nj$AGt%5y9ckD(&ZNLx;48d+e+n$tn;0$ zqHE?p217WW{j})3_Xgdx_bzZ0=a^z-dQZ*$02*a~jis1NkV-?CZTKC8>0u}`@N z5@(F9>(=O#f}4z;-Ai7?{aq8o)pszwqb?Qp;V3gY*WZ8>l~dh)!VKZ2rEKen5Jzs_1HT3 zs_^AKdfBV0YU6gWxW?`N2j2(17n&=4C!SUOy~pnFj_|TdH>JByj0elwHylzJk}7hY*V-$2}Bw09;gAY3S0oUkssR%7mvNQtn2y+ulr{8BW6TK}BFZdMn)I(Rv@Y@I3Z!Mz73 zvins9YQOxLmRm^1Qg`Hs9Xq9yQ%;AkkG~LSbIpj_+5c6fCE|PIRh?6#h6?t#4AX1~ z`a5#Us<50{pEcPc*IV2WF-?aRoFgIBi#$^O47?0ox_ofI&4prapu zhID3d+e6kj^E~iEyoXs{H_Az{pE?wlZ-uK9-&UJ(MQ}*@JaA}g{~WvRkaD4Sm@bc* z#QX^WoE70I*cGrvZK_<#?T!1iG}Er>KOzb3jvUVfi;U-^9~s_{t1@J@emSFfEGM^SVd-En4oa8rR}BW&fCmyD zWu|ss>-?kqDLLBhUnsTIw$(3+Rh>Pf*BV-~$h zQtWkm+JTSi@37ZfT2_XY>C5ADCRPn7-U1Z=8f8zSyD@_`_e6K>1AYGqM@D@VjL;Q_ zE)+f1n3GDpvnb2v zvNq2mezo~L4*XPB|GQp1qWY=l2j|dVmvxZmC3a3YpGtLniOg$zD|NSdqTp`C-E?_O zch$(yO#`PjiJ~9@b7mX z-1tg4u4T#J%!Ayf6Qh$rmnNCdz2&pYCk_=Adfq7iti=ZFl3<9Rm& zD&cGo67APaV*8?owQI^>>+6}cGxiA6Oesrxr(#~pMLTL84789fVo6LPlH>k| zP*&tHSubi%=unX#u+aBI!zJ$mICqdN2wElISdo0N_6wLqvYcz#Q@={Rqnv;1c9vey zH!CQ#Zqmyg63>3u1?VPj5ieywf>tA6VpU=RaG?3Ai0C9u)N5pcafk3s$Uvk8I5I>b zoGJgDDve$u`H#^k*YTc|^5rI|thBMJYuPo7^>cnrz!q=4~*R8oHeE@!Q^s%-; zhT0MXgP5*?+g{4@k24*)r#S@uwk{K;dfxEPMc6F^=uU#i@=3OT6n}WVS?lU^=nJ(6 z{uYS`^1k3-S^LoDc^QsAky>K2?_kYw-Z$WMb}zx&`pTL#{AyuO-&1dOeIb--zE(d? zZ?9UE^~1UX*+A>CU*41Cx^7LwW<@vENK@~0nb31&qm;P2Ee`em(j|)DPb-tMqc(#e zI567Mqj5Y5!zrea9v$Ld%vDs?aq}y8x%n1JZZ5 zi`30%znr^Uo{*#w!@Tq1Ni)-s!BBHt|igY*!=#pwqhL1DEj0O2~jMf z{e-{+`-P~B(E|0`xDjD}!rt_1J^YTmO+sCl#HLR(UP|3ArtxvfN6$7|L{a{`A1}G5 zmJP`r{o7sgqWt_zx~7AEvF&v!{k_@*+uL!ZrRPh+MP$@z<8Uz)yFmU}CvPujhj1NT z`?T!J&&YVFSSzhex$IgcKI~F=~gjySu$KH*JR~hZM&ZRt)4O=~nBbSr6!m+%v@0>|NMJ;Dhjy zW%gf9Wxn+b@i}{TUOxfX+x}e%69Ptp7i6|3-K7IKonsDB26&tfiHH|&4rKa5#F4gU zIG3EuoU2GI!wD_eESMAS0*4guvps`Gx^^4B$!`_cx*odP@sEqlo-EEt2QT>0AN=|@ z*e^KC`Ci}s_YbH`O;KGR_Y!l<5?y6Wac0qe4y#$AStUs6x=PkXxS++~kWQ)>nerAF z6B-L2N9>G|ss2m2sc-8V$6OIlu7WBFq>aC_g!^`2#Z2mBk^fq$B->e@XyEV!1*`5%n<+Q-R$i4oPCU03v5rr0W!fMO- z&6$30C@U zBCqwwcsq2zJoUol_IuK!Sr?Rjk%@TAXm-TiO0!|6s6D>UxfPZP?)$Vw?R*ZsynYh@ z2RGF-1aUAETwmcTNjtK}^~b|w+}Lx`MEP-EBbAvq+P@7gkd9E?vU8lzLZpFthHS7E zD~UfuGoo&G)p8M+BO=No3WVoaJW)RUAhLbntMa=#B{0xE*f&uGR= zwmvc_@ghlrwt_oNx2rB7SeUW(`G_mLw1_Awg*P~Y$LnU?E)QWudDX04%rN?01_5_8pV=Nx#l)W~@#w?VSpm+G6NVBhJEziEV{;a(j zHN|Sp@5l>Lu&7e`IUV9@k5Un1lw{OP*xz^6CdYKsts0pr)#G%Us7>NbbcZO5p9UWwxB4ftW_nR_ytm3< z2Gs>S0B;F1laGcnp9M>$?bLBe{|dRh`^k%pg{=PUcco(MR%t)a{;b1w5riK&5}0f* zu3qTruSk=Pvb6KA2yZIxXmJB@R0!1(O4M}#4@kN-Imy|?s&A5_sRDOuKJm85e}$Gp zhpRzDoUaeO)OMh%vM8xy0iTVLcFF)*mWQ5mseEIQEzDM`h{wfTlC!C3ng~!V5T2N|?>L2@CzBuxH;_A|Bu?sz;dL*6c zyk{%N)(D0Z+~756E8piE71~)i!QE2uP^PZCl#_08OBB|wGskL}rxj{GXm5q;B!ddp*Pw>j|du5b9{U|*ldLd0!@oLn0kEx)S!jlE*x z;U~(Sjs6p5Isd@DLRIJu>M|T}zv-K>vTgweC;exT=3Ee;1%`wQ8UIAydt3 zB3YWK(=}4wmCeb>iz_Gg=&Ca#TZW09!I7f7F)cZ2`v%`ff0pMf>zRJMJst>{cifn2J4R#GV0=lN5I3Zzn%RaU#e`PF;cxEm-Yde$G%bIi}{$MHLa zS6AqQX2AfsJzpT**t}=l$LIygNnu5b)$yh@idbXrtY1s~gmiVoZM%#g>|;ym`rd!^ zSZ}@Gy0+|(;RExZop9H1Gl3_bT4|#oa1Zi^mlrHi zzNK#q5W%UyZFk$s-rk}751!){uWOC97OJ~gU(muZ2;XD=?)>F@z<@#$o(?VG)ZrI& z`=k4)vf?u%$4HxKIVn3*`a`Eee!>-|H|X8?1W!jxjESav{)>8UZfpf^9aEiCzwh^l zAAgPY#8OzqD)1>GC$v^1r5DokwNE)~Wz(X&3!7^3xHrl|TnNdful;qL-{Gy0wOosQ zBX=}rYV2%u(Qoqar-rD_!c^NStD3}o@tp2Hk@bYT6<-o)Uljmuf-AiHglDLCD2le% zc4y7Nc5*?qk7zfOiFQ$`U;#Wwt`o#d-)k>0$$@f|&ztZ3%-pd{UW~>mNp;1LiXs88 za<`?}IbOeu+gu*&-j4NFTx=3-^gQmeFk5bj(?=(>2gyc7RYpEl{fEyb_#r*Ry=;j4 z!STi$LmsYamfyMVsBLC(d&Bpa@;05CWR5XceJ7kY&$PO1yo9rylGjM=TZuT=8sguo1@2bT!Q>bt;_Zws!!%$Y%}weo&?j_2 z$WH)|^(Q$ZS178Oi`*s2e>4l&d+3cpez-N5_dTQTspl-`j4`h8%daJ-<=B=A+%Vmq z;%redh&~F9mn!VfkxdNBI>_4%RZ0(tGI)`S>GqSNbFym!E`lQC>(1AVM)#;XgB7H= zKE*pWx*KrbCU@wAmynbGPrrwl4kq1%l8e;#F^*zQbFkhq9d|^oWh$7poc@su)pJ5K z1>FOM(zD7Sb(MRR*@cW`M+eWCG4g;w;D`#`ugR`PDz^Im62_9N?^T7{3S+9*>Svk; zbH00DyTKY=(TI>iihA;$_ZiR3^Mv|a?u?i!EE7t=%bF`GZ4_GWEcr(DP31<(IpIsF zOZuY_wyC`+6&e_f29l`ezuVfIGgDlx3|A`lX7#F>UR7Gq^miX~Pure6*gwphT|bTR z6B$g3YOk_a*iq3tutVs75hpdC(4PrwqmD=oI$el_^;jw5vu%+TcU-Z-3ivTHG`DM= z#50m^!}(ZOZXZGYhg|~KOMVLJ2tjCputc(6wKHmKSTvoR7{eLqnV^k{$@6W5p5dtb zIs{a&@SXNvuRUbB7RUgfxjt8&vF>5LW^FSLEdad(xfAtsU>$ISbix1^2$F&bty*h| zxgeDY7sa)V>zdAQ`7LsXP#p`!?F`Qk8ObMY)1W`l2rOJ2N-gtu{cFY#SSwxqs`0$< z6_aF<{D|U zq%vN*urVAFiN!?gM({UeSW+y~U*ZX>oNwk>MigQ^9gYcZrd6)wI&T!M(_rn)USM zb;D}Y)bf8#lS>QgPxv;vrk5Y|eS;nZfso(qU*_2&Zb;wQw~BS@Yf=iFC~vN65i&_s z3VvsYhTRsPQPDClmhC=o`REw$6WAhaDZ?+*v)mldc=v8U%I@LASbprURIiwf9_6dT z9|&hDR-$(#WTTPF(Lotks+~eD=FOy^n!C$FsMWqAW3pj|@w_?8Isf;gKXGJ4;qAgv zxVvzQp@mJTUrkp_w*~uA0lprcqh6TMOwv!06LLP55yhwm#K^)5Vjn1a<3pt%V!FyV zhVk%3cRPD$uRs>YK2aH0Bc}8G6~6ymRaU4h^V^@Q&%`F%i=wguUF|M#iS3}?>pDvJ zlSV*8^gVz?O?9|Q_6>1sA|g)4gvOHEw#p9*22Bi|rwOt=OH*_Ahaer+y|>A8`zf} zKXhYw8=*VR{z=`V`XC}=?`V^f$_1y|Ea#L{UA0|Ll74)nZ2|fd49A@Of5Q?4Ufx~x*3gMjj)Z6UQfNeYcVs;8 ztF9XC#6h*`>U-kpfZF*T=|QLeZB?IY=wb>F+^uF6!Omx7i@G|06+8wP`gv$2a#wOR zAx&oHg=mK~85>H)Y*d{E%0nE{hjsHK5GWqb)aCLg3Npy{0d+8jF*_!fJhYVA*MD#S zb(y7s{Vl#!NBse9S)A0m4zz_-_!c!F`*pNM9p0!)X%MDM`I^nLwxLgC*G1D+K3K*- zk=`fVW3Cc>l-J{p`46}}b6e;P_{5aAQwqw?Qu7A-&bKCMzYzlGN zvd`1rF@Teaq#4edI)t@@g}`1(Tj5{cmEam~mgH&JXw`(kC6R*b<;SaMNv07igb#vi zY=flFw5l*Ru-DJ22YYThT~<4D{8@gA@5%;7oXrz(TBM?+ElD zqImn2cR2Cv@1o+Mi3gyKI2qClz(UhH^9r973r14bB-2-N89pdR2X!oM@Tk4zsNHt@Ol~OfFy< z(ke&RnauslW*K5hu&A2@7p}vj>pr4K#R1+J;cGTYer392SNLx6Hczohx~?;d?$^MMYHdevf_;&5q3Z-+%v1ZyEZyZr#9>pD zfRC)Q-S+x?6UZX^Hl{-Ea>h~@=}LiH{)h)fFp`17eDD_Ia)@~UD31E4JNA-IL6PVN z+)Cdw&`ds+_ttLry@+`hZ4y(gbKnNF3+JZ9EILPyvWbG_#7~D5m2!IcH`$f=O86~z z6s7a%SRz3q^8v6Y0@ENzVno00)IxRpLM+K961qgk2mpW`&QNO3oL|p8ZQzz zfIa@p&=29o-~-!Eo`ye~N2?FWF=ja>(@Yk(R%}#m)@H%)gHGvbp@Hs89TDNsF`V*j z_C1juCziSEEW256xwS|qGb^dg_o%$fD$G3Sqc}FrYxOZtWdk^;>cQm>{ z^D#k(PEa<6B{CX22MtkoBL@Yy1<1+zu4LseyTpo2Rh~hS0gQGC*iSBsk$BdOpz=s7w=PM@zcTU?A@G5!Cm;K^7lLlz5Z7!c2xS=f49aAPL z`%BJO56kLj41sVg(P*Q}8FG%twPwR!?4Q`d?lyj-`qcz0Y#j?x3 zgL!HjlzYg%+W)35ns~-OX5ATzHY=p!!3Q-?*iSPhX%8PZ57P^V1_Hpu6YBSBPdP!nJ zFR%@r%iO_puuq(lKnw_@4?HeMpSruY<^eI{CpH8B8gk4#4XX@y9S4Y5Y6ta$zUd#2 z7OHEaeB?L(300+Pth_B>jc$P}MH9dg&W=!PrXG=aGW-_sxS^vh52~h$v009b%t*>d znE)$3H0bAs`kD%NhlHWA?iNmd?M~wslAr zVz&^$FI{8h7G+n>U)~H=+tjC_q-v5nRdrw2EL`TETsud2pDFju^Lo(9xY{|D+s{15 zmqf++!hpx-Np(I>FEq?J$sxjyqZfcwGA3{zWXUz8Slo%1tm`VNQ~eYjp$=;g@B?m5 zaI5H>xSGZFw_)D{pX0sl^SozR{VADbhUmHLKNeql52~+t6s_d%Bwj7x{G7$H5rP7WmGN z=cE}C>q}-bYq}_pk+~uJ0_HiP#>dK@fIMNEdMJI>yI5k6{SSG{?kTvhI1utmaR#l> ztP}dmuT^x0Fh!Jait|kk!HE$3vPU^qU?Pf3Z(tjOm5$58#%cywfwAp3?3)8SxdU-m zbqDurbSe}d{AGTxZKi96?=rooE+-9$EAze~+R0NzC1jO{^y>J%P#x#Kd90fQi}-uN zLrgQ*Zq|8ml7A<1K|y;UO9+_i)+26sg$FYB@O+}y0H;6&ZvwHO-H4rrMHp8bO?Zu< zEzWicxu2MF#q#L;d>j*@ zHm-o@XU;k0cW{e+8Y`L}$y7;9 z+-6WyNxp2e=aXEnts`(QD8vP5NPF}r)rdWu|3luGY;Aw#Md9AcCH#%9mfmu*(AOlu zGo)DuquCq@J{9K`(cGuPX%zabxQbEY%w0xdDdQKU>%9MgGM$UG*MYuDvhTS#}0zc{s#$ zQ$OESjX>@o#=#zLr?3yN~$ zBBU@NA7A~CHL0N@Z9wO+h6k5&!UTgvdytEvZFOhCb(&&%n0luMm&GD; zV7X`+dYcnwDf29<0jjC)hGHV+Os@`#@@NNT$wVL>w2Bh`KwxB zYZ2fFE;=go0cyMIRm5}HN*&;jRwgK8>11e(CPRB%Cg5_8nFiHKH_OoL7N)?%mQA>TX26e}+9?|By^`<=8E* z9i+q-Wgg~jTQ8(9Gf%MoNDg-;Gmuy!l*z}aAaA$&>fl4^OyM8bW`{6vox6#-WEbN* z#R~2>nMZ-!rovl7&T-bEDc}cA9CcVEV|E84~b&u>SvKo#-+=4Z_ zF6^n2yCI6up7Pni3(q=;FaPc9UKQ&;fESv1m-)8#_B23vrU*)GF&~ z{efT~as>uqb?j+m>wp>np%-BH;AT^yV=pmWbp!2dqnvimarS`Va_?>?CUl>)6PCrv zh%>7ZZA66J)60HGK1!=WJ>0jT7SW4F2lYfQ6U(`drJHN5Zc52}j5l{LsTi-1q+?yT zYbzbuthUY)izs+dx>wx^nMh|y72;H~N-%@{l)A{Plt!|CW6L-j`LQxPHLk|u9f;(! zR{yU9_I0#xpr-_TJN9@@5SxniHfJlLF%7kD7m+5OD9aF*@Q(0ahsK7pxJCX^G{i22 ziY49HC*8Sxd8FCj#nqV8f(iJVn)Lci5RMH#ay4gaR6~SIm@_b?E>W(R4wU$!W+~&y4csM)Ug!qaMY5r9kwiV1An@ED7g_&^px_)asKc~5iM|Nk9|TB-=Mj9On$HFP_Ws^Bu$`;J$5$3nR`mt?@7JT9UDE zW~!;r#zpQKtYX_Q!zBMu?2;ml{N`!Ha*|Rk2xBD48ZB&tpAeM1@O{>(l0Mb1FtaWF?7uPXI6=lLT`)ei(Kz%1Dlah6OAH$qw< zFNHIKhT0u$XWBumK|2yc(|pf3-aqW_^dw>`w!pbVaT}gXU10SFZ;6kxBk>~kY;*+3 zV%`xQ*snNZw~>8DO5>y_j_5$l2Ziu>&oQvd^_je{RhI~fQ=vr2kEKwA?j^*Bu zpor@3b-0E?Jp@VAB;O(90`wSrlj92E1E=FtIEQ#|$p9Fp?1W1_Yk9>Sp{Nx+hng2; z!Y9ZHmz^!-{t`amR|MMt8_?FQSF8^7W}M}%h8EL%YPDDw$z=4pE6r@Abzo;c8$W31 zANcG$jt$2R_+oO1Zvwu~o^PGzZwBY!MUF86IV%d-1vlXp3fHk#0w?GiAVvp=o`Jg{ zFL>WMW3^=>3w?t5tDFG|v1OjosFJsw)>-Zt#H{y>pW)!=13$o8IGxkVX8~7n0pTuB zP2DH>xGsv(0e#80E+e+ue#-QM7z%Hu_Tmfc7n{1mUy<=tNv z*{wdz*GIHIBRu04qh9#E%f|rT3Ekc?S$*N{f9vz-`v%xgV+^qChIDK5sP#MFhq13%l97 znWCk`Bv*84sM*y@^&ufzz8O0Vb>S`)&yjQ#iiw-lSKt-#2i)mgV2vjp=$-l)as*Rr z-RE57>dt<{BkecI&u~-TC+95UUwAdrMZktA|5uA1SuOY~Xbh&KHqn>xo#^=BL&anv zsTB#91t-DX;S+oUkEVA9!Yqe5D;jv!cieonOy9G{Odj)p1G0pR=|*4=)iU>RB344{ zxab7Y8Y0c>4gXKeVy|IN@`OMe!9_?!&q9jvCH{5J#)j3-D{yPX>*#1TJK8a$saW3; zTfnJj4+>o6ER>{jD(O#%nLC4P4Sg@zF8~lf$~k2-sejEx3ma5T8b# z8fDZ>@m@L(Y#JQsQ3hV)j4RiX?)=AprhKr$Rrl4z_3pIX4ovi)!q4*j@M3>Y@$ZQJ ztP4o3dZ2Emd`$RhO%G*eBP?_sI7@g!zMfX`#$bb;W5l}~I=haR;nlTvQ;=KlDUb2b z@xAdr53YxX5kuHnf+KVcl!H_t#heV;*WiDi9nebb1Gb!=N&j`F>OXr5*?UNkTIM=w z0a?w_Cmd^_rG69M8@?92Yo0>=ho8hp1#Vit`C38%dWU%|9Ix6B%ojx~pP?)8T?imn zizYTQj`qaO+&$aMRE)yIL3fH`IIVc6IVacwrZvlh ze&K3aS?>G7I0UpE^VE|n?oS#*q`(E?OldD-F`a^iP!#!$?$F@8-wZqly25YWh6V*F z)!M9{4_s;BySfnsD~82kpA(k(8U?~9{~>0P1p;Gq-Q(?nPxD`sY{8Bi200{wWUtph-1E`7TinHGsP1Dtg(hnDbB@!80)5EQ zLMql4wHvLMaN@Rxl=06-q{Od@tyOnr^4J%2`GTP9Cu?kA0`Qp}>lb=*+kd`UBiOV@n_(y~>%pPO9$h#&9U?Nz*-e!jS7&9sG)g!i`-a_FNW9_NI4`k^WN-*jM06V#T9P6g}ZR zbQ+LGua=d_7SZ=fR`4ipMGsITgN?*upwOAmY9jISzac?jrL#EjKzTB> z%4OK)24=xe-J$$}{l1QF0khQo$u=aIAwI(TMa*))^vnYzm_5NQ9+R^iC&VGzHSDiI zX#-kYiM?cptJ2}oz&W}t&&co24m6zmV)6mH40-JvYYQ+*!N>Tgz#`Wr_hv;dT;%JD ze&v;tU8P+Wx9vu3F83Pn4f%k}Oc)(N0@M>?G&|Jw$uieF8d^qO0;jr8A7KYr^8+vFBdkrtJ%aMpkbOkG zcyF*7&I>FLa0I>tw{Twct%g#)eZ6Zq25O3BOK>)K084{(bguBG@YJe#&>t*^Z4tLs z8BhiBFVQ@(oM%=Yp*lIIz?0b-SSRE(e`~Nc-HE*wYede(w|jpALVS^Rxf|ekf{_4= zI}MA*%RCdY@#1^TRsGw5k9rS^E5ky$_M2W7sp;vlTzJ z0ePnQEZ&CA7mm?(Rpqc}c%DGv$j?9&_R*Ka6wmYnY@~k)zQHl98Q|nvZd3(MszOyBa*qi}b+dwU(CfL4u7HQO(>_Yzx51eh`4*16f+#!r3g44?d=i_U8Cc|FDMXOa=84`^1vMHP|qgn499+LYzk4 zGZ&F1Y!!Pmpk;BnYSseq6{{dMPR=0k4LSLIz!JlCw;&uBq|;2CEU zne1x?oDYr-{0`b23tU>i-V^B=i_bEB@pU1()nk@>zE(sJ_WEFj|2b)7&lepB5=7@! z^TEf=0PrXGowB!J5W0=i2}$87fPdjc$iPNeN3iV#0>0yP27{Iz?(4+BU|qvs<(+#3 zG?8-%K7}uG{p(djDq8J*K-ZCt;F%5TUM^=aFpZll5V41oz0h*r6yURXqCgEDM5m$% zq7}u0i~Qq)k#tYH&Fa8AyB?Tl`~ItEc|lLAIT2I%`nf%xvq1(O#~H`|32Nm%L$4@C z3**G&RBz)Vb{^`tJ;6e3d1aCSJz#~O~2f^!*2GV zraO+F3@+l~Uwt-ONcBZ;Xj&t(Kq1x?Fmkp0wBS|j38E(q{{Fxqauy*5x4Z!vna0 z9WT`=|5Jf<4U?&vD-ObJ$l>uiyR*7B)Xr$XBDjb<487((?e%)wnEs(7*-s1)Dw<*} zj+`SzYlst&TU9Dv#-FL-h<*v4hbknK1ubLxse1DRqA9G^_F+51rsYkmV( z$>XMi0sjDcE4)QymB^v7?9ucjo{eWFM*0=3VS+u(4aX;39vr|u2qQ!U@h_D{I`A5z z2U^6sXzlNA&3(mR4uM1(2DxUl1js%2O(GomjakkA25$my>8b3s-1{JekLJgz@=y!x zX>bjNptNUdF8l)M;;y6-z8MH{(7R2W5hHd!@CA+*fV}UMY*DVC!iw4 z34aZQ(7#0+B-yZod51g`A7|GD3PD2p)OSzMFfn9XTXXMZ&wghHK=~W|i_jLqR?dCW z5m=8M;kH3$auUHA9J>fsUQ)N=?BT59GNKRAAG^}`gfo{8U{Syu-wsj~2>RODG3QqD zq2sc7wzu5z$zM;6gD3JwP*Hwe@HDGPoEUPEcatd)y_U5^&q{iV%iuJkr(le77qlKf z%tC=&mK$H^i1AGnHU@@U-r_ba-Y+1dJPzAhmza6SJoH-JV_o|oEs^6T&}90H`=hX2 zJ^+I(KS+qmuA>Hh)@>8l!k|IH=cA{vp;9_VVeKo3e61mLU$mIadY4$p6g#pwr)`;J8_o6NMLxdgBdGK5?S5P8b3+w&!yj`g}`UZ2_ z^Tgc&O5<;YvY5xg<>+nEb~$vwir@|~i$6-Drr~e;CXBizuwngD9WnJB! zjuJH3;O_43Fu1$BySu~SFu1$>;O-8=9RkGKO0T?nKUst(54yxms#D^zOV99_4?^FRk&YZH5#B)0G)%s{Fu}QCa=SaNcn{6 zflXjOUSTe@7ElPg+xftBs^QNOH@q1m@|gpF)i6Ir6mz-t^PmX-CKZlZ5LaD27##RJ zNy}z^*T$wbW+T{AEhW5-c6Y9(B6X(^!Yc}^L4U^zfucIi_@y6 zT{W}HzcZHbWYT|FGXtaa3!WFAYOHQj2Qox`*M}R{wro(PksApa`mw0TxW&s!4XzNqqo&F>Sg4Mz};XDPsPZ5 z>@CmCf58z-O{24XOk8HvrIXlx?Sy>_Ul!b?B`tyXi#epJXl=y)=&xX-m0h?QJ5~D= zEQU^-K5-zJtPYgE>D2>W=t1*Mc(pYV## zuY6~5#rWCT8^qmEODnk|cg2^4H%WK*VqcnkMp!4^cb-rR9<9r1Wl&#Xp1vuxEa?Xc zTN_eeh8NoBLebV#yVIYAUwg5?qu!BlXe-Q|VPotVIY)F}C+G1?e4&q2H^%=P8*L6Y z^2apsO6ZBVAu6Enl~;&oLn-ug?2-s#KKXGvJlIzNO5jjB_j}Xl$(N&#f?L`R92<2l z?gK6og04In6X8m8f#WKS*j8yS81F)=eApXQS1c{p)`b=s~j5 z4Q38~ow35B-u9=x;T2yYY+g2(gz8$zyI1}x+CojU8LBPSLvP%v zp5>12Tuxhojz)()m2g151|u_Ei-a)0kzH3kdC3ZYk@Uf4Mb|#P$Jc9tcS?4%SIR=m zYyA%I$9~u^R0S0ky}Y~jouBh&@(vgO3B-5{#S0NL#P{YqP&KYu##zRj&_ZJz-znD) zZum9OlB}52&;E^RA7L?i)Ow&@)8@D{d+v$4-Ogy|J|aI%|Din+n|P}8d`T^=vk}{U zoxyzC-qn{CbsumSgD;F}yj;dnJbT#7dJ0*j5irIm1l_c2xG!Hr_PYk~sfH)qom2pg z{TF!b-)Z*s<@aW?mxdSV6I^#9ZnDaFBf3dTcnT`b%!;&d#CcC&bDZ@S zPm@N8z;2-pwM1IiY-ssOs&>yh7S8%1IW$#D=R*QL{*+3qBTRB4|RjBTub$NSW4>fiJNrt%)aiKF{Rn03iFRy!yJP9)uW*TEmpGAF0} zoN1n+mkf<%xARJK)ETr{_#r(Y9;cN(yHO>+)8SJ`sQ2X^&ZJ!+dYCH{3Gu|JIi8_p zrl*%XiyrvEFyqo91pTtXAhHJ@Cb$t=Z3K#Jh;UVltUNf&A2(%TWNv>f1 zz?0BOE4P-P?xVZt6LcR?R4&w)))$t`O@fQVb2MO|jXW=KyOo?GU6U`kZrO*;UR>et zgh$9mj7*T4#$JtD;%K+Ckiz?sZK@@Gr49Z2wDoEStGPZNmeu0YCWM{_=UE@jZy}$a zA-XlZp^b?glEDkt>D|dDP{}=@Eq-?fNb5A7|rga4c-<+T<;U7;1r+`ajts9^wBht(mrvZuXyN#~t1|3dLES zX-lQ2cNbVFPLL0?cEVS6yx1FkCwa{YR{zkbv^9ZE=>xQ~dJkb1DWhFB#)LcZ?z*X4 zN@hBB;itP;vd0M-vOk$M6h^Vmp>zMA(a{5zDg0W>rr6$3a!jyNdWxPIS2VL2la1A2otgz_LmOR> zU=GdiFhMb7K;VYe11)u5l!}mj!bsB59IjR;_YBeVPOM-P`hoYBexVtr&C3ftNef5Y zXhSy%4+Bg8M8Hq>9VZJBVh#Ki0(mjv!PEuGw`o4k{n>tvGV2{*9G|;KLq>m1-v)OZcZWN^+KeEJtt7# z2$(133pk$i!{_*MVJf>Ue^5^w^WYr!QMlbo#$(JmMrEE$QP5KQA0J}nl#e1sY7_ZW zUdSfkcF|YWWd2&Z7yT?U8+vXgs#5$j&n=}l?nZaeLt-U+vNp*~ORZ;3N$-}b(0WQq zvrniM%Nkn9hRR+~iuj52kfvwy`Hu2=C|NkB91WCAZD!@O>xk8i1%cAGmw(f0WvTCT-W3%y=VlDbQujk3wt(^m$@Y8{R5 zDGyS8KL`A-o?gWs6*~3%F8xYk;al-wR6e0FS_j@p$TI<<rs20ly z3P=;!8qw{`hS%y_%r0a(FRpwGhC^Xi8rFhU7{FcF3p4@sPMfa%tDF%kLkrfmuJc5Y z-)IagdHc}$>4*6-?{?oFd;pYSN5G3vIV(}T75FcBM(6{k887TtU?N)V?r3nSzw*R= z%rjQ%AKN#==e^{rE9_xMSxr46s2CCcvt|=^^!KXdp{!{*I`k=={O2xs3~Lxm?1%J$ z@IzeWdMu{murDc7bM=6#dy?P^_volO(iQ(0F{}*2Pt9D`B{P^@FmyAJ)A$&;ADnOh zb}PQh>^onH2P*~1XzMjC$v#;-gj=|7$i+uH6#8p4&E5~Aj1I(d-}_ zZ#QJ|Y0>E^)Mx+2Gt$DLPc$Lh_z26tQiEa#}`(vTSxhK`XyzR|0oZdPlA7?rRdS%*!B6ytwMLySLQkX3JZ2qE z*%_`TALWs{px&D36CrTd;BV<(e3m>yHQ^=XF?N9>xC*%f zI-76Akdnb20|t@Jpt0|xl1?(fM-d0z-;KvON?iv3GMk{W;(g@_{r=}?u!2@3eWRmS z&b1Ecz05J7q8YG`%ju+F(gia~ibnNlF~O%QQVZ>~KG<4`eTpx=?eD(wzfw>07K$|H zn;StEZMapH3?#%H4OCQ|7Xj;}yq*NB9$gW?Fn$95<6mMbaUH#VtPAPm^`*pTehwE8 zm$i=u_4KDful^RcQi{8)(&k`UrY~9C-ki$u=tYhyel@g~eGuQ%U8YLw;A0lE@6&33 zc=C|c+ExylS*xEqh>iBm5xcS$!XEnx9_lE7cOpM}j;cO8N~#fY*e$tbp`7-OEf-(f zO>`+xFK~vC@Ey8C`6YZM=bgzdFU;rO&ufIonq%NNFoqGSoza`0SCWL0mQSrH4$!-h zS%IzL(cVVBV|EHpmAWW(*kW1}KNKWky?%^zms*O2VM|*J?l7yGbJOaDa|I8CBf?eu zX9K^5(bAsad(htZMed;#RI|v-d|zBIlt12%uA9bpcqjg$(|VI&NAjGaTU;0V5eP)~ z1$|S#!_HI-+793MAo$ukM$7+x?@urvVnizF(dI@;@wt^Q`jV6buwYDgb(((OP7;W7 zH)T?flP`J^Z&mzCoFdP(yGoZc`h+rp7AU8ZKyIZ@`1y?OK}V81r?z4Rr2HUN|6)BB zCgUDL8(Iear#6;Vc@!K#?|1~&XH>C2I$a+N)#EVQmhfsZo>!2Z88VaYP& z#!pcbT|gxwBb6L>R+ht)C&L)&ic1yS@~$=k<*gfO*LZE=4w1v1gL$}LeXPt2A1C#t z7kn30m`P6Q`S?O0@Z|Q#`-0+u^1IMjmUlQCnL_ z7Wlu=6`o1L7Lu9uC(()q-`l0}6J9#Jg_IPQ>Q~tvySF{uc#wS7T1U>2>GtB3vcX%p z8BL+5S$Z(qXsB-`l#Jqa+}mk|;AiwC3z9$TZQ*fm@A%H*V*9a_K^QFb5{{ZR(Irqt z?<6=}yPlmca8&QCfk)x}CXuGGutLQn&T~Bnoi0t#OBiG1siHtPTM^m->5MwUIPPSn z#|C1w(zvp4K0HMqWQ-DK;p@^d-)MJ9cEsF98jGu4%jJHGqW3|y1gGs#90n6m72=_P z>DAdwskZeynBXsNO=kzB->h%og*l4t5TnHt3v>$)W+~(V3xTf|F%BD8?(WKh#+VO` zjCLCOEnlV^Q3YXwWz!+Jf~ygTw2KBmsrkG|LviqmsDSbm1w&?bc1dZYHA#C5%4rWl zKcy5a8D4~Q+fT?o&mi|*{?^|`%R?4QtIhW5JM`}aJB-8xvf5Zd+S`6)W9M_kPb=}h zAACIM?;R{Ivmf(T@(spNZFpCx%C^z(%HOW`@=>c*FwLw%9!beO$QHr6zV)6dQAPZqt>&xK~#p3n&doiew4Id**dAqPH?U^%;jUyd6_N@hZjQqwf zUMRARry{%&D$4?%lhNbUiORajF}{t_pIyH}n%R^MQJUei{w#sy@%Dw0s z+CXm;jx(Q-MQjNDZ7J5$;AdLU%4RQNtIUtqS-H2f-xLvg%Wq;%IIQkRd!?(TT0|O7 zo@-5A71caaF20_8k`9tD_7@nVXC{e)A{MuPkSXRJRu;3AbYq@bM!yZ`u?A)`7!7FX z1<7yKgnQrsqo=;cj!};)H$mNC$MATmyu5%VfEmcrCX+E>9_bcdgAU7crMjR(D9Dzx z8*&HGkN3i9;ew{eo{6qFyMRn4m~E8jG#X}cxT^71A#H}u5%xB9XJ@mMRskR;f`#;= zxt)X%F%lc$6+iXue1v8_bG57Ip zGaCrSQ}vQEhi5XQB4EU6rI0M6tF&UoZ0WlB%b(BM&Tg}g<}H6QVI|F{J>!$5&$w7f z6%2Wnv%?**2S_XI5Y>b)3WLRPRrpESBg_PA^~7Krm`c{dBKRqdPfave35jT~yiMB? zSYwuig99N@&b2#gU(|DLnCn&4SXX8~!;Wyx79UDO!+AqT<-6W8G|!(UG&9c#8p38~ z*L0nEp(0dJg4P$&iYdwOg#Dl#zYiwHJTS$y-{w2|7>_nD5vDgw@1m6rRUwAy{pI&t z`ad9EJi}&(Gt)}$z2Yz{k1$;HJAT|~ZArv>PbYL7UUjWN5A?^{F!s&Yk;SIx^`G-- za)38ywv-G0NO{1=;2P*(J>Z(^xxu#ESuH15t{fE(8SPW1TD=vZJ^%%+1ti5gUJ_7G zu{@i~?~&Q=arQ@7O?jVjT52Y}Q!bNd;y0Lxj=~2Z9=C_JrQ+rjJVG3WCb9weWW;=P zJbfal$`5A)u0YO#Vv6aFRZ`R>&@7Z2w00em3)>_0ZbC#vEm5~7JKnd-cL{ln&t@6p z8{HGErKPg>azV$NmW4I)I4uzB?c5iMc!V2!%d3M(XFgy35WAVlJfr)dXNFozuI<^X zl=JR)zqiJRM8S3!1g3q)sG0WDx^FxRj1DXka@((i6O$%dGf_d>QL1YG4sLfCvX4&6 zG=uPiB`G}^7qF{n#f zb;29X=`s(s)(Z$ewq34|ciIKfvgrBV4N@<$s9Z=Hjjmet3@D`v?>u>^U?cY}_i!2N z>2w4v%8&79c(+(v7)qO_G}hY+3C{g}oSq5|2yaI=eX2E0EzJ7Li|i3W5!Uf6v6gG4 z9dfHV&Pf|s8|0NL=UJpr>Ja6moTk1OCh&R@^QGS?A9BMaa$LJcHX439jwA5pKeQ0v4!#n}VZY+1-I z*7bg5KiO!Ws!VW=A;(}Vdo~Z+eTysq?$WZT z0ZOItd+3*zhk9vop(~^quFhWA8F3xBJ9q))#XFrw#RKd=*Ldqwpa^*<&E;R5t?nhS z&41a6$l{NMQFsyR3{MINabDI+?n4h-b$P70AJl=*_1;=Hb5v+p_@bWc_Z?G01C54S zbLk&`!fXT@l03p$5Kvd4d$5V?y3|Xs!kU%EsEIn@7v>}SNgEh?lakxg6tCVjw9F_4 zKk$+KCOB%}gf-3AMk(CKRlxB|PEx_`Q$9!nZmJY>catlM(~P%Zg#MLQ^<)!kvs7!5 znuO*UXTuwLSNB~w)Nypy%1sgE`&m6U#}#khc0S+FwP)5lE7go}==vVQTOq}6Y-~oQ zUBysStBcr6tt>d$3Ok3&(nM*f)J=-Cp!P#<>Um{nwnO4|JPeJ}kFXrBg>nb81YM<6 zLU*k7S`;~hLv)}toqjQWWVIlH%Eo>&pWk3JVVtlUwk9v=EMu-#7;Xb%;G1rMsXP*G z#>Z$5D5D1Q2m6Tm#{E({PLH!%@>2Iv?-++4ZI7cpIoK~fRl2G?MGN_DK}40ncR15( z!kY1);HvS&Y+-#5thJ{EKBs+8A7S<;J#|5=3e#x3_0Zh{9Shq$kCf<*5@l;Ht{q!Z z7-HsN8xixipc9Q*LMa)6ZPajP2Y|3$0CKeVBKkY9Am@;g0)P+I96fe_KY zI;s4rqCadpJm3{|H5n*ThXi&q{mwIbIoiO&Ds#_vHpZ;*6)9_NWySagB}+tI*AMbR z&f-1Gi@-wsl*8eryYdo0>dT8+#n>BiDRr{BSiVGO*vaM`xK;jyd#QQJYe%70<#C?s zVx+687*f~BVKt{GSxQs~;w|hqY61V^hxG`tAb6kNFSNpZ1#jt#lg0&eFqiO?9JdP_ z1s!c{HZ5q6j(F&;D`ln^!E^7dm?=;SRB~O6+bSGkNn$a_%5oW&d)==@#Fk_CB@NeL~ z9G)t*bnn1@g$zb!woi?Mxk-Zb#?zh~v>SZsz9lvkw$S5J75LDoV%{SMt%biE+9SnQ z))^KyH<4Y$)<2OBY(02x54VSdV^Q0qSAhXm66oX2;AWJB$EKAgI&2@hr!8Q)jY7I7 zbyzTnF40nVAj^u^Yn9$enTy(oSZs7$rvhDo&IBa=qm{P#xA4Rfj8C zW6}q%6V9_ubT=36_NW|=Hy)dN+=)V+WWPNdx!`Hgmi|k&Iz;A75ahFktGJBLgWljN zYYJTK^eg`zDiO{kHN(-yy7c3r9%^+pqi_nBL8$N=zqn~&jd`PF*OYMMBlXn6=4L@3CwqyhK zZQ<+QX+D`v2n;vt332j~p!V%<=n9@CZ01&Iy?Gz^lqN~nv`TvM2+iYm)#rDF(!NPb zKpLbxjQZ?e?z$^XQX57#1}UtfSdiqiQczE2kVBDI;IqsDj`~TV%Krq?e$+zGB4_gK zv_JR<)RQCB98w9;Ux{`7fJ;yX{I9P?<}4l?bQd~#c1o@BKzz7I|dI19PRaAkJHP`XRM6X}!x25-dw_86~6d&MCAZckSmh+~AdAPDoI6wrn(0RfOn|IDgNL!@LU3D#6-0?#;9QggkN_DMhP z?~s;1ZLg!ol;yRoV0bcIq@;r}Qpgj56U0pFPi1smri_`{K&`E7M%33*0LS;_l>r}9$Hr@Mgg2j|e% z;pJdIZ>Vn!23>o^ijFq5CfFQi;E}0E!!yn3-#6Jr2^o#`OJWpVNhgR6KtESUsTQv) zuZ=zzfnh|1;=Uo|q~&QN;UC`KA#^X{Mxm%+Ha;E+Rw>%X`oCj%inQnT@NDaScv&E) zng=Zll(Dw+RQl1lWJIww!Y7B!S?Fw^8Bk?ZSGW-R>}b9To;YwoA4Wg;>SXNZs4&x` z*GHyB9ui-;qSOWg0MC_ZX$Xz8-^(`~|7mZ^d3X#OsUP*{09kiJP3c2-5%aL|4W`1o zs36K8inogin{gtXY&R3)%|6x#S79LzCc_$_kmGn1mUb!4`AOmxAHhM&0=2&F2L)a2 zoJNFNl1uF4ZGfIS&-B}Wzv)pU$?781qX)v@tt_w?Ps63$bzzO*X*rKFEAUFM z#1o|4j*A!pMuF+xX-XQN&O^9_5siO&pDOivGH52xQU=g#Y>m4Jx&c1py8e9Ox1qSe z7rBi-`Oh(9r#yO3n|ZIJ4-#_ zI6j@XMeFGuzL_SH2SyP>@HEtbPD456L3F2iMXIPSLYkInscm=652fD_ zkXJF?D$PstRC$lmj@G8bVN0P0S53c@0%)C*E7-}lg-5OnaDe#&C#cn}ScM6TK&mhMVnXbPO#6H8IHwrTW9k zNWhYvkHo=6dTDE{*o@j%d*cDf0j^kmX(xM)*40=F{3r|E8*XYeQs>Eo^}EjbJwn(a zRbY1_D|&kKI=GMfwW}H3Z8n6RmCm*&d{k$&4G5+e{nJSsY`)YQSr?5Gfyrr&w8Da; zMWuB$tMaxu4|CbEazU32x{yxxA*mIrh3ZRTIE_D+$}73-r|>V&s0hp6ls<#!2Oq58 z_C33xtAuFyyMu?%f{4VEY zbP_&pv68Q9oQqe~6X1QNs5cO)s5x90m3ZTO)!8w0+Sn+)f&p@8zdRFse{=f>H@Lg9|;KqxyL+(ogWI zJHnb|A5c5;KdG719<#q-NAxk}TVSzC?Q9`;sJuCxzIUw=+VcWD?8}znKkq^@Pv~FQ z+K54BR&d$#T5hJzH_MTtLVMOO<%Cw(oI#5)pOJ2DWOB%`Z{yC+lsLoLg)ZA!|2z<8 zsvfin#0NfxpUEYKfL4Idai_{x>@_T)RP?=;=c9-0spoLi8t3<}+oS1GcTCrm|}OhI}iT9IhzTWTyk)$qTWKb;c4=yj??iDM?_j+|;+qHCtWdt&Q(_ zS9xk1)zg#tQ`k%zNv5T4wpu%M@Mqf#V@Yo~Nh#0o!zj;VFkjE>?(1ny9yv5oEc3{{ z(C$zM$IIGa{wrsar_z?;nvo@9n@Z)V3p7`NE7h)`EpeT3$D%4Yil2lo`8VB)Q{`ob zZtrx|*nb>V;{p;{h43D@Lve*Jr_{6Fx~I9N&`k5C<6O^2Qgf@}Uuq|u#p-WQl21o}@D&92#I>HT?2s_YHCoLGJ{Y3;3#;~7+TMRDb(6Ks zEMiX9G9~MJ3|JR@>+cu1Z70HB`gT$e#pqMO-y(!{$p9x^-CE9VPjXbo?vhXGr&ZV1 zqg9}_QQDltmxIHOo7TcLEPB7MDX*!IgMKj%%;F=o#=7V}53i?BM{C$$xHkF~s!TSB z-RT{6Q2I%*^%ad46c`AoL%@O?E#<34JeXx#&XsWCgJ0t zpqW$KF913!{VeIC8UC-S$ zk5MLLT1qF-$vur`FgM|wpo>r~Dkv7^`w(VN?5XM^*HkT^lPbw#FL&PuFU`_ob(qgs z4GW+otui@hLqjaRTkVl$N{1164&sS&n%EA+cnq(kuCsbj3)c@q#F4Ogqb)xS) z?X8EY*&JTxyTeA;4|EAF5zlHKDb@aZH zbO~DOI>Oe0WB3QVOv*q}8em4zG^q=Gz(v;A@vR(qGPKZ~D+SS_FctHHzt}^ti2#`B zBpz!ybA5ANAbH>}?HmOVhzG^2_G0}R80?-cR@Vf79dK6)x^r?EtYAO2O4-xdZgXg0 zHff7WYdiIo{1p`#3z-RDK24#RIKzC_-p0-GO7R({kyEfpBOj6_j$FcCWI!hXU}d za*!gj$8mxI`CA((E+@oK?deVj-&Ji0ivg{{C~Ic;ER78sW;eJN)#2HM=W>EHM#&(x zm-pbCVt3d<+9JQ_EzDssm-~^^-|_eFT>2(u6v~0;0>mcb9?_-M{AfA4gZC)s#G!PA zfMpeg=v6w<44B`U2OSEH)1iJ`D@5JX-EAHE0c z!3A_Y2|E6CHE}e4$qB6s=7Ng!fD|EiAqjetm0A4Z%El&HZGZ#;envK?@5NPo8E}4a zqgzFV$e=CtM%55;~ViRw$mD9p0d4R zn3{%4ie59nDTy1UCQ44JyXzmvvCOU>P%6n&z&NWVYl3z7nsp)Lpvt!R3C-yOe5ieb zpO&w~{JbofDSVNhumbd}xHKXctVR}_Cyg^8>?o2qK_T%bPh|n1&6vFYT)X|D7 zh_zVbzE*QbPEylDjm&kCBO`xViS`C@i2MkDbi6W0 zPDe9zpFPhU?>P8>v{r1lli7G>-Z7cc5$zOLTeE{X?cog4`KCwjO$PAB&R6~`^b$7- zdC*)KLHnUC3Kb&gY}nW;L~=WN<|ba2OwhW99%yG^LA46_JA93Oj5wh*X6x~4+|xcr z+S99`tmqNvTlw@FX>Mbz^U2u}eq+UNI)^nXLhOl~9V+U;nLL<{fZbYbdy{o<;q{tueNDtdnD@B`0L| z`R3c3VUj~CHl+-e3!A~asUL$IpdZ?1gny1Ujtzqctw-TzW)dE+d{fHNr)-hh!u`NI z*U@#>d)`D%b=@J273aEeC&NR}*+&hNJOcI1Gr@PD2%K-l@cC#KpYObhkX-s|_DP7~wL_9w67Mz>9A~(@=K*&b)7W8fTzCjt z%a7Hc@)K7ru`(1;4N)b%v@zk_S_gWX+@sY<)9^2?3oXH2u2OK06;Im3!~7oX4oaXr ze1!ef`I_4HBHIL$P%NxyFGE)qO}+xghVQYBt}Hlc+geNWl{05#vks8cU=A4$;@w_% zH=&?>6{hpgVjmTP_T(!~X33IH$62eDJbX`bJyJ(3gd^lf?5VWWx#lBSTWPP*FVqt@ z5^mtB*0NA`yNoQr;l@_pSUl>eW}`q=G)o>th%r_AB$ngnlx)uIve`^hw&KUaW>;A` z1%${%6z~-Fyu?Go!$E#!yX+iVCJU8xXuu9=f_2$8t(#(3k(mGJ>-a@sfgDM51sxfKN(;WYaO{R`B#K1r`!t?(G2!iEmZ z`BKPAvZZwa2VAnyob0fhoLa_Cq+LCWl#JLEGr5Z4 zeFArFbq|#q*m<<)VU={1N~PcZzTDZri>5c%!dezRjC9!3zG+TC|5^&`j0)Nk`zgEJ z_xKL8H_R*igtHyR|1_HoVnA2X<~K+`dn9;=+MA7obwjK1Vlr3HYPKg^Y}sramF8Ya z3!t^~CAGXa20uZI+#g6j))El(pY+RKN!p`(^n7Y2klUTX(!*!LR(^sPb+6!Yb}QV9 zXECb7eF`*gg63i|e2W8zM4t!R3a42#&*x;71XpD+2Cl}VtnTbDWsh{p%*&hv9(m4bkleGOfg|`pKJN|JO z>lErBKr#>Yk$Qs?Rx+3+_Yhmqqs|N6OvmXtr9HEU3m=^Jh=0P}#3H_e{G+*AEG8^N zDSWzpo5*lBd%-*7Uk=6BN;u-K0Fee@Nns^Bk3)E-;|X14LmgFg9SfM(g5%BF##L># zKGwBgT4WS4dy(662VseLS~_QiSTX#sP(aCuo(C_ouAVI*zrKQn;8(37&BmIOUe-0- zNhu1m@)qEha7JDxoMfj}9_TqBd(MbgOXgZBRIgKQ98ToD! zyB^+14yA2pAEl;zBtg#(`;?M$6DdJ|N59PB@ z(+GQnxJR;p&u(wLw^p+jy+>#Z#3Bj6{|d ziMTQv7F2M$Tfo;W`#8>>NRX@}~xCTaB^I2Ay2BrgZ`#S>)gd1V3Gd8TS|4!3-I4dOhml=)0NqS#2>r=P>JUG&2#H)2l?=7# zO!??J3%*;|%xSEXXSdWTd^~)}$ut*muIN)MbMQm3FHd8q^+fwHEQE$|!D;)?prop= z#D>nPbpWQI&*DG02FowD@a_bAc`2ok_?Lg1u~pguk2u{XCR**fz?-Fa4=0d&pfQ@L zm(=%*@5BMZWu-A$%b!Oykad@=CXP4s{bQ4T|#*dUvyy)K9voPYT}F--{#E zNIuE@$_k@y_8D_F`6v-y*vh5!m0uExwsk!ap7T9)iCGThQR^vF#iMvLKPZew*R7$X zmtMt|rQYamXt@2}-BFtFlHnF(ywhK^-R)z|U<0(8?S`LVR(mpdL))eGO!r%wxP#0K zQ+(eXc8S;eejyesNr458=bRyf7%${L_6sPpg^S+1EYAhUV-H^+{+(v>~3U;#9TtnJRZ??$!7GhDLAfO<|wZ&_-3 zARXQXv*9tljlM}5433jM_7>j2PPFU56S#u79_#WPGzd-=Hp)IHi5DY0XCeMtAl_d1 zBR-FoJKcSYT#xWR)`%|wkz^v@1u6fcP0(jqxx$vQmldN`?AG>u{esqs=D;QRJ$^|k zAQg0a@C!?Om7DHM!dyNVOp$X*{ZJITDLmk^UE4Zq&ZAwyQNu8!jW4jMlXe(tb|pQn zJ-{Q@bKa#^kzW^k$g|`FVioa%+QQX_53(X1Dq=pUEOl^L`6A>HsYDirJ7{ys2eVuF zoZW-_KxeYY+@{A_DRu`?Q_03{>xx}Tcq=QSn;qbt#reDms~|2`RteQO;@`{?gz2IYp_ z4J4RvXiItqHG-G;A0enjIdfr7@gm&9C7}e0w;T^&45&j~tL#K85nSZeZ~}L_KEegr z3}G)i?hte_sJy$sFh1CdeN|4=0kj&LCYj(1_dEJ*CZVvhUvOr8VV2ZI$m}RoD&!9WmF8cudZ2s+k&SIpR=#%_J=nGQC{W!tw2tVPis60lXE2=Ej zM=S7XTu09zxXBmUoAr+NEFiI7nr-di*LgZz=k$)~QafcI2=I^Y4Bo~liWI@~+*Fy$ z53vdI7j+w-=Xijd$UL&yPGg7J_>kjpi=#rjlQ&!MStl*GzR>tbP-L$?PH&61x|{F@ zR zc%losx1uX{vZE+?*lD)L=o>zz_XTfZwa`p|oN<>1=pk!^{ZVpAM;IpGo&L3YYP5ZX zj10g_XPpVnFzX7tB;6Wp zynr9j9a2*FTcx-rEOb1B*>EA+!4L80_Aa7}mNXE1X;=B2B#^gu4f7z)E>A$R*~ifm zo3R8dM8l+*-JVo4kCRqp609a)7M)fzQbhVks(^iVTjrI&Ip4Pv@R<&sp!uSg@bocG`iaqvhf)-6DtEKPCi04>EwpafdR5TFNi(^R{%FrJS$PKX`65U(do>^*3m`wD`=ZD60f zE50M0hwb@l`6tNYP`5+*F<2Z=bo{4{W^Pv1VSVb*Hlal8hNIz}vf4{S`701Bl?P#v zA7Y}i0ZuCMCdtAQ>_>bbY(tXmEUX>~;0)4ac%3^KIDfrj`IpNSWX zu%h%|c%76-ccdwxA^MEJ(Us2mQ_vh>?{pY~D)he5Ny}`PwYKozViXX~`A);5W7nV< zlq0m7yH%CjU^yCZ)irMmPv99)4}M3vr8Y2=RTZ|z7Hv)XkSJam4Y8Y;1{jNPI`haC zFc7b!cdQ%INa>-F53Hblojlifn9lN}a#SQW@P2$j7>IIMGmZId1sa;PNGO%h$hQfl_wSJ(^EjsL|bfK=PB zcd%w!k-U_UpWm}@0)RT8`|vO`#lyTG|BkQoMMA824Cr{Sc+S|s%SuVe=iJ-t#aD0! z`tCUFtB_@W3pX_zvf}0|>xuP-?BZgulUW{3sMu|f)> zNVYff?2ZrF4V>g}?Wv*R#!(>+)X>jx4?aS6Q;lAh%F2siFUjRD?R2kS$N%Aga)>vg z*TqTTH@&Ghry2M;O*HzE@kZEs0M{^y@3X%cW62}@fQNYu>WS*0D{ztck8;v^UG#e7 zbA5ItNuQnG%DbSqY{J|0Bdl#~vnmTG^djNgw29?$zQ_n>L#=bHkhq;!w^r%vnNI;CG!K$v zLS0lI6ksQL8&sHf;uGaVXsq4Mi059AndPC*8&`NUl471S3)=bZyKFo^X>BuVkOjhB z=Ukj?)+WVJIT4D-P-eCpYz8S}fCnuXt<4*vtaO><7Q=g@sr zlx?LMy!BMc#`G4RCBH__^V-n_8-vNLyRZhtumskbc4Hgi9azLkGlkF(@r#fXl;tmQ zSM<%iV;u%%q~^j~n5w60#ht6^ye!9BL}rt3{2uPgGh5GjR_6pB@0={-K{~VTbbQOz z84rZp#4bXFxDK6W7+w}iA|I^S*2bIg9au#?jYpE^W*0jo z)PotoSu};z1Y2Pya*Rat`=~AI#JYmUus{8X7CJju4m8VdOA25`?u(N_S6o%D#Ysz<1f5=BvU2vI^ zq!|3g*Rs>`;d?iLeh zL;E?Ofk%nE;br3EN!B}3m*?QWK_769ULvDGThdD?Eo5*~T+vPwMM?1#oWQGqtY9bq zn``VC&H$c(W^4v23W~wExCNR4&alz!6KWx-C>tydx`Vbj9)4mO#f5mU#V9~~gg0tG zzLU%l;_)!p3!AVz{e?fk7|_o-Ki;8ce4SY*u+vxr*0CvebA7q<{(yrBlWmmJAb2j8 z!bLzK&>k)pE{REC73qX~iw98$`bEeECH_`ghh{hm%vxun)_Ea4X#FSF5^xup|;i*L{PFk=iD>z&-?xHUEd#HTvzfq&spw!?pb~__sksn#fgx39l{Omed%aI zazwlD;~hqa{K>|#yO5)CMkZ%>5J&sl59~RKB$sCJ1Io|+%xTWzEPYqH%Lx;XSLV#L z%aFTWl%64nX`4;v!SZiA+?KH4g)|7A;3wHel#Gr}#c7(~Q8&>fn*f?!q2q{TSM<8b zI@#ilv`KnXPx?LmBD%&)v{n28)Rw2&Ci14f;J$RgjWr2=Gx?YcnZhzj_i8b3i$4|J zocfz|?B^};)y^X83~{E?CT|z)?;UrcoYiP|5s>N6f_LE2SzYTF3!Z|d+U8%@JGvyB zX{|ZSP3&xcHkUIk0?w73Q?nFW+PAI*KAa9xqZud1U%TpT$bVJ={J>E&_ngSP`!h!>|eN(jO2ba zL9?9-Zan95`{cajd}Yu1h0RBSM0Y5)^a}Y0^@Z7^PdUV1(V|k(U(BPBJ@F&`G1z1i@V23Z%=6hNw2WUx-_~&7bJ|iTF2#M^=I&;<32(4viKi(KoWl?y#f%IT}E%XKFASx!HNNHMiq>_KJx$y=`}?gQ&$izbzL>j!XN%5T0o3 z;(IZ_n%vn_>(EncqVop2X_fXbanl@;RMJcE4|toLf=)lL zr+1iE`5E#)_69^YP_{XbPAOabk6pMSOQ*{`j~ot=DiS$jwa>Z)hB8|}591TUg8EJ|*2Rb-fk$};o? z-{`M0=t)CIbQwijbw;9g?S%N=N-x|`FnMU0-RNc7Kc&B)D8IS|or>8PywkkQ%;enW zRa$614UIGHG}%pcg^xRt)FS&hoiZzc6qjV3%Zkj}>A#zi;1_nfXc2FVt;+WTEu3ez zE6;R}nj=okOH{?Z%);Q1te?Se19ZAQ=4E-cm1Vx)${#2DOkOQ5^VoHh_-*O#Gz=^? zdx=eTKFHU(jan_JwRJ8x;|bYop07ep(6V5_yM=0zzr16%uFL_C$C~f?EcbIeht+j^ zJJSMb*oDdt6wyTOh5e?>emj{@xv4-fBdfQ6mumuBN7#2z&DGs08R+2_unX-Obn9Fd z7!}xSPMd6ZvQw1qn%lftDtTLjN1PC3xOWZSwn@RbtiL5qPueBY$3N)pM>Mjm3DK|Q zA3bc&IF(F{Q_fw4D8Xy)gTSkJ&&Osn+05Wt)F0K^88n@@@F@GcHw?SU)}SEa@YE(%qF6f*7AVdc)YpmU4W3;l}M8|o9bP4|Kwa{o}Le`=$ zP`{cjA;=54fOyDE$xGo>AN|251QuJB<3Lhm#yL5_{aa0bO+>AF=Rk}bX=|d&u7i^g zwefMFYiCdFWcgCR=VM%jp4t`uME|;uGhyaBzY#od>zGer`I^#1e#84BGhR1_^yEh1 zi^}Gj43I;C?IAmn{hn^u2V?1K=)3N(-yJ1*o)5BsAZ)mF#k}%UxkQ`1OGH9%#Bf6l`w?InB&?e}*ltLurbW z*OUU9K$khcvbLsl{mFgJacG^E)rIt_9 zW7Xpl-Xd?DT}zkIZ|SPdPuH*x`^i#o}igiL~mvdgB}f?l$%W|t$%{hs_m z`?$I+k{c!;C(AHIIbWkpc4#5r=VMNFKFKpo2iZ;6;ID+r5!oP_rifYSCwp-g)nNV+ zKS_?87;0nl$X3S<)Miq=HQOy_dOPi~8t*xYkeMJ>te8IL*<4UN`@Q@)Xyz_9O*&{{ zUFkguhRYhxt&O=h)s+4^7+MQMdTu`97} zDq2pP(>JusLCz9(V2*GFo8kXLVZdT_Z6Ar_+UV33V_H)Y^-MQvD3>+A|5%EcFip(9 zXXDUT0vQrOGb$P|k=l0yw zlE%|JxH83lCoz;@LV-1A(G_i}Rgr^q%1Jbp?MS~muY&)!#s22qkZs&iPg4O&wuiaA zQ_-e*54j7q(J6A(U!nD##=KEsGx=_ynOT4wq7i(}1Sy)X^IR$=qiMJ~Cab(t-j_CB zzM(>#+YYfM^>spQ0ejFbbq#b&6`evIXtqrWuJM;ZcQrDz(I;#VSEO(43I2-Xcnufz z&UmwQJ`mo1iSgd@ADH`uY;0hX5)_M7o=EZT=8zDR$KJO$M$oZ0!s9cX%+9CU_5cm{d~ z-qF1s~9P)9TWqJKh1yB)FSU1Sp#G<(f4O0$`Wr@w=p{&<~b_h}p2BIEGRuid5; zEJrEyz6g^Pa=5gX*AQ;OFL)WJaf(yk>0%}Vd&HV*PB|W=(xGxm zR_b|))G+-PK5#*qq3wBxadkXzf}Kp#_4?b0~=}@Ru4Z(f(SXsxBHT z9^#x+Y)4PIn2B~~1BEx{_D+a91)P!_xr~)~tSQ0P%*R|~fT@b`J=256+gGGGqQBdu zq>i_L!vDFBdZOJ@%vO=5n89Yye;fLkwJ>q$pjwinU*?qHNf)(0{DmnQj-?%ZgASObrZBbPuiP;vRd?tJy1`wwlwRU$6bpZ|n?0?sP==GpZ`;ulr@vEE z)7kt>>dRcZVe*)GLe07f=iasf?J=n|UjC7ReiHEFC|lWEjTsNsntF!f3Eg(646-nG znuShtX8_hUA1wy5TcvlY26h+1fjn2yEXdBkQSJ0Zr&A+`bhmt9H%N?~jt-a8bTzM` zF(N#Kc2WsWp{3G+b4wZNV8$S>UBg={qv#g@NFk7gWEmlQ^|;Q}wdP%uOm(!AF4sA9 zil@NZT!Zbs3Dh?n+N334mYbA|Zv(O3fQM4kE~WhE_mN!w1SzK( z)Q5kkudu`Wfg~YXVI|WZMI@p+_pqz&UERrT`F&kWhmbpeih2Nhe#tMuwFR-FZNcFS zAcDf$e2On2xr9p`PdeW(ijj2EzScWENM4?A>)4x8g9N*6+!Kcs9*p=YSPZ;Y6F zW#qHIXIi38qOOjU@_H5sDnhT=a@ahq58b_-hSDvnB>ia|{|x%B05)+`mPMZV}UqR?xdzMy5~#atT(^dYvUv z_FG8b9;(K}mV!oX?A69&?4v^c>o{i`t;w_c&7KO%oQoA7nGnB@Hu5+++c}#s8 zLPQmKh71Bdzn0Zf5&m&6IzT~;?h!Wxr?i*ryq%g;UK&C0t8m|swH&>w7i0nUEA!Ak zdJp@O#i$KspcY{_AJ-4{2aZN}!d!Ao_9KI+r<|waTGWm~KI92JbASn_CCsjf%GsFT(a4^f+iCI<2vM^8b`)dZYR^Qeoom%TI}+&9sR=GS8Aim*W= zbw1WO3bD13)DW@#e*R56P(IT_=+W3nlVlf0Fo{}ojOjyFp{pmDr%q0usa>@=y^p=m zFhs*Xf@eL^POu$q3iR#(*st-DWYejMJb^52AWI2$wynl_`Ad301L*|C>2oT~Cme^v zVSBGpr0EC$50wIt$gYx%7)6{;(puVC_QNyWg$T`e($iMc-Q3o6VoQ6e8>spdDfOeG zpxQ*_*QDzsSkP#@Tt}haCBojPAAx(LB@MQtCdLe$NBOBMHRtE1FAb-4K=+gRTe?hx zc`a~)r*G3+xL+OcL#Q+XF5W8xkfX6)#-L->ZVJ#Y?L}p{H!SmZ>WE`0s2}`P=2{P#qe(IysXeH**8WI<$>**P?_0G{;>=W$*?kmWxOc}=emvB}j6G!O)-Kg7P zk4tN5JKuf+u4*dF>|;bMFY^wXARBd=W+A7t9yDfAny1&Kj$VS5D4-K5T&_qB%7==I z66o?!SbM`8!X6~w!qYCDm`jDdZABv(Kw2k8UA&)_Hvni0>DB3NlGEEcdpjN;;Xx8Zu(Bh3@-IDk% zeoOAl6-doeL~7rLop~&CXq^e8KM*M>K{0fY^Uy1x*;IZ`jbPu7Lbm&9sy!f9=gSE6 zFFj1{=_BaU=7>lZr8JI!znB5*x`9f-Gw6fSd7%3{S_#@_A!2P2(3VYj65>3LcBgh+ zS$j!+a9lK3pw5u=2Jnz}BO*}_`gpi@gavFQe`+OE+aAyh_BUw&t%D zjTw-p!t(8aO!t7^9H>#?=jX7gsWgEO0o&9EULH$ZA^QXy6{FwiZD^|7x>$dsXwHj0 zW7M#58E*Pg3qt;{BJwl=^NXOTz>06`eQ-y*GIZK5is$@1o@)Vlp9lS_X>aiISlFn` zhP>#MYzM$>FPf^R2i7Ud!XPF)3b&=R`&jQ*lUkR?_R zenvXw)T_v{>p=zS2#v)57l1c&>GxU#V_gp&d;+U96l?S~_0VdKx3SOydv*8He~|ICZ2^7{OH z1T}(H?x9~wUEK;UX^ZC+*PMEgsv{?=4~>AI)JY%eM|vLDB;flKIupK9Z%RjR<6Bf( zFTkRF&acp4R0-94`Jincf@8L5N3Eu%wK=-lHO9Rbffh+XK?CS}AfDHejZpx6JL11` z_&Ib`EPShdknin48DX-Sig9U5h8&L+A8WYR{;m!h--EvY zTkk+&K)RJPX?G215Q$4{A{Vq_U8VCm3%S)ul+RYC}DaC$@q19Rsv> z036T*TD$jaaGsD`kaX- zLYMyziZ8_Y4W4=e$CDw2Ux1cvp}~gh0A!-QL#3%bXfOg}y$`#U4qb6i7wHRVj$WWp zCrBn^-yjWWq%-dQ9NyCg-9yEx2gW@HbGi@jI|6vA9hKAt`V+0fbrG1sQ>^(zP&5Pg zcnz!ZExMfe7+(u;$B*EJlF&09fg0kdJY)>DBlI0DgB|$?-#^4`dx7dV;60s$E>g;i zwYaNK!G*`6S%yOz{=|CjryN*=1{l>k&^?-(0H6B$4BDX}c;y+Wln?sb!OHHz7@kAI zj_L~V?HfQTL9AyDDo>%XVb!P^u6%(pw5PJrjA!tSD4Y+D0YzTLu}o-*efT~j@T6TY*MX{Ho)@qV^}*dW@h<>7pBps4iBHck!n~luL0r)gc=r}YdKEM;kJzgZ z?Rf=bL8p3*<#h_f*(HH%YJ(G=K^8n*cZ>=HhZD5Taqtt`ZIXe07ij#lkRZbR%7G(a zKuf>Cr=psT)jA9>JOmUhhU=c|S)5fETwGY6;Oso0`bB(O3fHKn;i@P|UNwv+9kyAp zu94uYYRgim literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/dope.raw b/7.x/src/stk/rawwaves/dope.raw new file mode 100644 index 0000000000000000000000000000000000000000..6a7a1ff6831fc3d254809aea99e51acb83430cc7 GIT binary patch literal 20480 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 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/fwavblnk.raw b/7.x/src/stk/rawwaves/fwavblnk.raw new file mode 100644 index 0000000000000000000000000000000000000000..14da0748110b02d3f8acff38c37b3948cdc42d55 GIT binary patch literal 512 zcmZQzU~}i`7CJ8RLtauFB zGKY%hs+t;D6BV%?nD&Kk|Cnu_K!hvKaT4|7ju<)ocU`WSaDsxkCS ops|m=yNu&mn=rGT2EVoCRMq7rC4LAU=jmp1XBcq%8Adb!00;GMEdT%j literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/halfwave.raw b/7.x/src/stk/rawwaves/halfwave.raw new file mode 100644 index 0000000000000000000000000000000000000000..b34639b0be745e0779f14f7afd5a93398ba73310 GIT binary patch literal 512 zcmZQzU{+ys=gi{i=3gyzT=b#D4;gNGNhM`fbqzIbIXwY`-^TaMc3Sq>gxQNZo^`2m zm+{=}WAC>=&^UNj=$8nmsK%Jhan}-FCw)wLo^~!{X;w~-a_-6exPpg8A;nuunado? zvn!e_JF9A{V`?;O9@Tc%$<)oM=crGtUsiv;{%iezAifS{C)RV+&8d^A?W}oJqgfqO zRa4nn(OjNg=1|I9ytOE#;9-7T?#UeGtelLcY3EX&Cw)wK9d|8eb5vu5Q|OoAS%JoW z`+e*^H@nNYR5_lt7qbbo>@nMEe9z#wo`ANThMKCnlCr#{47bD&(T76E`B(FFb7rx* KGpjI+Z~y@L*J#TC literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/hihatcym.raw b/7.x/src/stk/rawwaves/hihatcym.raw new file mode 100644 index 0000000000000000000000000000000000000000..f61389ec8eb6b5e14b9b3967b03bee47c680d7bd GIT binary patch literal 4976 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 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/impuls20.raw b/7.x/src/stk/rawwaves/impuls20.raw new file mode 100644 index 0000000000000000000000000000000000000000..9d89aec1040720ac5d9cb26d74fce5cf04c7cbb6 GIT binary patch literal 512 zcmWNOJxC)_6ouc*7>O{kkQ9Phf2u5+N`}~p7@JiQM6{GeK|~CKg@Vl@SSVtlSS66y zSt>@>Mr6Q3(ghI_Z5Cpum>H9odH24*o&Lag&UgOko&7a7cQWGkEJpginziG9*_!w) z1-rD~aUI{bYq3(K6TK$$m%ZmkIw;6GeTt^re?{Z;L%EP|ZLxNC5Il%pU8d*Olr>3j zOq+OzWU$a;+`?h$nIpOyNzhGGR@3;xeIDbpa49M}K?v=pNz$dqWj{Kc@e5c-lnwn# zGUP8A)xH`K6HtX8Fpn$JR&QpBjFGI#sc-4v8RX$N7%?NcJ~g-Gr*U;r*&>H2ID|XA zl7p&ZQpPjB8B$f5!FzawB06G25xr*$gqVWfQ$%c_14Vemdzn#HGZf}MlTsBqh*xk2 zhnNyMW$U7GL&mK>m0Zk#3E6o(laBf}Ig%w~W=XwC8&_aH>{}HRYC!vBG~AKVud;y% z>->TH*NIBnzW|q zxh{*|;DH^m_SP2Ul}kUO@$io{eUkN{&`5jF{&JI{7wL?Z>{@)g&|;Vyt}hF&vSm~dGb7U+z*k=$55qH??q$rVx&dazBhGMRU0-O z7d<`jH_NWmzSga%q1Yx7U-DkY1YvkdK3y=XF?t z0+(>X)aU{_R9v?3(`M(%jv9qUDw;|2!Y3-H=J?R8*57DNB|

qiqiiGvTzlMn7n+ qhos$!I!)~U&FTU49JgUZRdv;NT8p&Ai_w_gt8{)0MKb*`b^H&@0XE41 literal 0 HcmV?d00001 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;i4U<*XzxtyK?6J+I&Ux_18)`^~w3xXyj9>%Kmp>-v0d0Q|+;t90|t zRWD2Z2E6Yduvb~vZfD>cuPi0TG{rJdOEb0U7}D)Uk4uKqdjGmq+j!Fj!*+7EENxuf zvOw%|4Q~!_N^2Nqb=u1G^Y-6re|m)V_n^00(3;!u%6hoj;)a8bcdWY_-mQJBHrl%Q_0(6Rn}Z!8&P6Ym)*fw~X8XqFYnyEU z8KO-YZ3@f-Ob1juXNs*DIn3had%VLHi*6{_5=>Q|S)#5r)>&`3 zWqD}Y#JgN2?j7W$P!Rxr$_7;^NA$;z+xE5&v-@k&TW`^KayuNtjiN-{pc9~}gN^xy zKWM+B%uz`N%C9(`m%(=Rsh*BpR=NTV(bT=iNjvKpbUfOWO zWy53bOG8_a3eVRb<=PbWjM2v<-q=U8Yf;JsDig~o9!iy7fO@ODST7La`WbhQbA`Rd zk>;Ktz?E;Wu{Ss`y9PU+JEC1)`exy&57HM1IS-k|Hk!$4e4A^a0cSul?8ax>YU5>( zRmLWxmsYHd^JzwuHX46%U=RMFJeuuwc zDEm-Pu~>hg7wSiJtMC^|;+j~dKh;m_6=JMtp%qlcV|Xdm(kYJM3ck*{5Qx*1wQ65A zLhU4Z>)KN7n3gX2C#c?PKXr+ksSZ;fDy8aY8mKSSP{kXENVGWU%sSWbez^%>`j#|t zTI>`FqNi@xAM0229eSo@SR?w0d1AXL6)u`c#XOd)pd0veJ)f2D+EWSbrE~O>!}v55 z;wB`lz(ni@MKFk+@D41J^X`L6jKD11iHk4_$KotZ#>v`AZ5A| zuZ&Q-pi}XdF-GyjBAL&^5-h_^w7_B50NIcPA3#?KfI#q+O04H`+>!1{q%~5d6pEu! z6hdJ%myXd>s;4S?!Ui768@W){PUJp3m_O%C-p3dCCO_m_c0&+Mg%pT~DA_p#!XOet zW%myy)?KKELWwvBHp-X}UqLG5z(ug(0GugVhoTvz?cpCd2P+^N2Ek}ZlkO{m^YUDj zF5CjiFad@@ko0s3=Wqgl$|HC@C-HZDh!1cU&*3N@$bC78J*Zml^cK~#g@dKGVH_@V ziM)>UWL*v)=1S@5NSF+BB}zKvNi7RtuT=UZ+?SjX9MA$n{)k|yT`<0lCiFyK?1U;j Og7P=#%?AFz4g3d5T&>dp literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand10.raw b/7.x/src/stk/rawwaves/mand10.raw new file mode 100644 index 0000000000000000000000000000000000000000..4b35376aeacdfc50cbef89ea76259403ac542b69 GIT binary patch literal 2048 zcmeHF`BRov6h8O9tA4nYpkfT8d@_h;peUoc1nLk$8n}R?;*ySNpbai#iA#!!1}bXm zFp7+rVkRy)WT-(z>P*r&G^hxfD2igEv3%S6-uJzy`%m@qeR<*-8@Dpx8UT7Vp)^wZ|a8MKt_fo^yp{irU{gL>rYL)91Rc6kgP%^k)C zGt`*mE%TmnZ|hpr-Q>yleD9jmS=I5PE6IJ&9pEyoNmM59*y+{{s8)*fMAct@OPlD3 zaCAEKp01v44%zQ^pAo$q93!-!)I#;KR-iKJ8ie3VI3i7uyCi}atN`1 z1Q?8MGuzDmzD}><`ObaXRprX^NWNCKkY$@qb}teq=PQ*;rt(1jTB(s6XdPUF0 ztRyHWmGjDOHCQXrB9-GxfaIe0>F?4rxkOqHQ|%5r-tt-%qRu^5YJG0)WOZhZQD-bP zf_;_|0M()u~s|V&cGD1n3l;%IG@Fbf-$CDS-%Q^KZ@ag_+v2VD2701B zC0@gHSkvvtSVS`DCp4B`mCniMl^iuxEmk^}Xf;f0*RScD^elac9<3c!)0HTBhvbs> zORFR&O(zo|3a{Zj9%B8#?yyvr!E)IZ##j@pXKpJ{_;Az3X0iFEX+C7l_Brf>9dL=n zkWHkNj-n4}H!YwG=oP6iMOrQ<2%+ILltzLYcH{H*cMObRX7eSAP_c!kK~ee8buT7Y?@4y=yrOVmeGAQQ=B5HA9+ol zkUOM?ToidJq&M7!n{Wj(@ii{xjJNQoyn)}~ukc-*kLehLL(m@`+~m*r-@KmRMGdE8 z5xx?=Cc|nd7yV?YgF{dNr=SM@5cTfDW!Md$!cc6+ay*JT0+ETR^G06A5AtLDrohqU zh&np3FAhK_j=@-*himbG$gjoc5Co~P70$wQ5=17DSTd7*MmCVI$Zk?XD#&q?MP`#E zl1{dhy<`vBO45iETHpj^!28e(pWqo>ff3y1&0>>Uep?`UlrQGvxRZzS(R>n56sOJn zqYFd0u$Ne0OzbID{fj0_+;xS$a##J1~UE=+|8 z2!{U916_jcdi+f={w?mu!*~pfu~ewC5nZ5(n&A)$qlArx@h}mlz(+6xk|6=6i5edW z9H+p?;wE1BI7ops5sw0Yw6Fz##)D#)M8QK0j>j+@C@fUW8-c@we-Ec&8m<<4+lV^_ z(pT}3=r>aQUjdt7k3i%)Gz*6O$RH9zoC3{$#6c85@IouxhDs=dlW-XFAqO_VQkW?w zih@A&U;|#py_kt9n2bsIp-^?0;3*uV1e5VNN3gY6s3Q~C;TGJ5`69Ov%Y^Fd@dfl0 s+a?GG)5H`T1dm%_t%#PwN?0S}Z{Psz6FvhLh>Tf~^v2#G@P8ukAJ5hp%>V!Z literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand11.raw b/7.x/src/stk/rawwaves/mand11.raw new file mode 100644 index 0000000000000000000000000000000000000000..94889be6f0d6dfeaa63449e06a39df1356508dac GIT binary patch literal 2048 zcmeH``EyTa6vm(P-ft2imKrLUPLYXff1tHjky?sQgrI62YY|(SAf(oIbc$+4Ofb|^ zBGQsB7!{&w9W6nW+DRp?C2do!toQrAPfz>}{n0#g-@D8`=RWV}ITv88{mZrr&h+b7 z`mU3=BT$Yw2NMXr6H;oD%z%*akn3hMZhEakhlSjfbUQIP(&b^iOZP|lbNI--&D6?6 zWfRIb`v1B@oU?N)QC_XAU&TWIt{d&T*noe}o-uvAp%TXrINvl35+?Z7bkR>HmZq16Lfxa}&~2tLx>tl+%hKI>eJjrV6&3@KY) zp6CR~z;lee$|w@|=Tx5g#27B{+V8|M4gv*os%KdCZ0*v^$Q$NlWm@T(W= zEioB#N+OYqBe*G_m@@CWcf-3EXdf6BXzk54ZKXDrVH3_ueaVy*Sljl5FN_g0&74_>mDWU`G&vFT+B%w9PlJ!O??XL?Fs^ko4vIEVe&lH%68Np7b* zuiEYE*c6-R2D^Q(#1**9F2lvUsV>#!y0b2uk=%n$B$sh2i?IhKIEsmA%ODT)LvBNT zBw-$+c$F7nSjKe5v4`$(GbgjFer+~~>$(<#mdxi4UO+3vBLXe37C{*-{iUCbl&;4>vx~Ha_L{wD zkJ`ibq&;gNIB_-YW1DBsxezzUopSjMa2aX}Ew{W>u z#&e_+T*+-br0-v55uQOi-9djO;A3pZ0i6MnX3}0>f1()W6))W-TEfMX0E%!3tFREs z%5EH9LnJGBhv%5h?b>w)W7*VIxEt=G=3eN0He@??VH-AMV@9(B`*IW$bXO~McB&j) zXNe}>9M7Syo}vTou%LF6BRb45;6lst8n9MWBJQ?tm`JDHd^0 P99Ox1s;310cLM(bY{@Uw literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand12.raw b/7.x/src/stk/rawwaves/mand12.raw new file mode 100644 index 0000000000000000000000000000000000000000..a128642bf6de4fdac74a1fa03bf32bc6003bb685 GIT binary patch literal 2048 zcmeH``BPR^7{|ZQ``*hwfI=>$Ah<atq$A=ExTNBe8y2P`3QlM>BW-H# zD`{%DP`RW|7AlG(I)UOOxgd(5rYyeid+#|<&-6F+qx1dYo^#K==lMRL^F08n?Gvv1 z9%tj;`a&zm$2;(=z-s?<&Jy-;r7nWuw2W*oZ|^9t0_RXWR~74Hjt|TTdRgcJ{JZ0|9R+=?ey+*?* zLC>~B+{fJc?%u9KPl~<8?%_&*neOgkC0O@7yFFo6KP%Dp;VU#vKd^GF?s|iBy!T4q zOa62G-ZFx?t^S>UFiMS`G@S1`CL529SnN>StuIufexxt*UbMis80|Y$EP*HlLpuXd?2wbpi6 zBkeprm}g)Db|L|tv6C7cb&mOtAZM5nYs5QGJGU80W{P>;JZgSt&N1#eD;!&BG4^0G z>gXcfq_xxr1-xIC+l%e)_I=f;r-~kME4@~S>r8!8N2@(5&d##RtQyrxSMY6Kq0ey~ zR-qPMIUm6kO!2sf8@Nv)xQ!S-&82(?2F4->tJuZqynxs7e!j&w`2cT|@p$gRgV=?K zG>t~$3I4$MSdD@5t&5*Z&G5HWTzDovO2SF%x&@6yA>z zER_09BAY^G&(XAlDyfvx$zQ515=Kq*Gv$h#4Dy$*+i?>s^u|1?nVG4$z8M_gNQCNWKvR5el_$B|wnLL3< zaW8J6EnTIH^*+5tAJCQDio1#10P!5m?Rg-l@p`_%FQocdakNVqoI)8a3Y0Ex;3}@8 z5-w^-skD){(#Jw>BF&^UN}y0w;V`nL?hK3*-)UHZZ?Fc5Z1YK8!&A8rhiKLfTC+c! z`nj&qMf$9GxvB5U_^dvm%e9AFipwOt1hAerBiwcW@Q>T@s>ebH6^ z?IN87Nd00y%-e#Fdvy%CW*=vEtg6w<+(((5Hm4JGBXqdg~gk~qJt#h4Pn_{Mv;=IPy~wt Nh1x5M-Jr8-F<{I8I-o3?rz|+Pvsm0Gd+jB?t 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 Pta~8a3%pR^e^cN;QOOs4 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand5.raw b/7.x/src/stk/rawwaves/mand5.raw new file mode 100644 index 0000000000000000000000000000000000000000..9b308a860b4966a4bfa6148f01bdfa70fdc99495 GIT binary patch literal 2048 zcmeH``Bzm{6vy{I_ulv5f%s4asiXP`OQ&HN9)y>(#z(WkOzxv6K%7#F#GwIY{zs6mEKVe z>I-(m=;J-ZHznY?B`7f8T;ByP2ZP@W85=yWd%ITJ9&=W<~_^&&^MsNBClUNBzYZC zx~Pxr)$Sr9QKT*6o%jK_r7hXk%eh~@?!4RfXWIkUPHxmH)fHN>7*BC97!z?gJ1%cA zY&E7S9hEg6#iny!Zm+jI5)9$WXyt;ECm)k@DmXH3VYZ?tfPUd(O%q| zYm0Xdb+@=avpelMj%AL}wxn0i=6Y*>YoE55_7jfl?r7Cl9iuj@bG1&CrwsQ5k5Ww9SN=`EnM5Go>9}aHu1CIB1goKCv=CkFdjNW z8M~)+HTWtgp@gF#o@yW#cQ7B=F7&++ipUpXJenV&M6f|E zM1mh37fx}S{>B1!oP~lD=D~UlWA93{q+02;++Qg)WGd&SMyaPHM7^ud(uVO#JW7+?SKKGnB(+X`tTAn_mZbTsC)8psLc7N& z3KRw6mRKRuX$gFb+t?6xN*X4GvnQ-kx+>Qw8ohDYKiW1#)obVH7v5NZAS#r}ON}U08*j&0)t_IjdpStVuFU5mKZyL^81gHX6%t9~RG9QxBO zohpPpb%xEt!f*3Kd>=o`Pw|6%6`#)I_%i+x@1eD6cJ8C+2h=;uhBzxKMYAC4NP*Oi2G9grK+9mLLUBmsiG$*Xa8Pd=M{{TmWzl}xOU2Xx78nN$V5d&= z0Lr0M->g!ofNI@OEm*;c2JDO$^hZzqHX5M}3LzI#V5xp5Nw5j_!(RR6b-gz5KN$EA DK%KG2 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand6.raw b/7.x/src/stk/rawwaves/mand6.raw new file mode 100644 index 0000000000000000000000000000000000000000..05f083d8912b7622d17e058ced75e9c06d65f25d GIT binary patch literal 2048 zcmeHG`%_g_82!F|&OLB17mO>$wa{qtL8j&tA3YFi%v2P`C+ZNHWh(eWv!c;LkwnLV zP|-3;F>8!;G##HMY7fh_2P%wd778e+TsW6|&pmtZul+~#qqAoJvS;>Q>sxDm0I(tW z?%f~j`M-neUTPZRG#i^uFI}&&J$9#qHFaaksCxEU#aFJ?47C?PcbGfDstnQrZw*cdMb!cb4Z_ zFZxQ_t{BgF@;dB@A=ZHgV}DfGU>>SUf=!Q-=A719( z#RiGRatF-kZS9Bd*WA0=KA0bca?OxcZLc)0t6y_>vTwxwtb3DvlLD_=qg#hI^=b}l zIpV+JZ}DyQ75j68cPyuU(48KRFV7p z@a-&#mcYj>7J69Krq|bO9toyeaaMj10+)l|23|6=S}WUoTIqI}=mRfky;!DtiW`w`)x)rg+IojCe*yz};Wzi}A zuC+Ii?XNT2%qo9E`&)r6?Y3W->)U#?o@(W7d(4IAY;&Z)EU+)6+lkx|{iq}S2q$r> zHq)`hm}L}(EpWPBc@g{F+afcgDmxsHYIPU8E=Bwtnc@D_wLE-h*g<2sHXp{yw?&S< z)zSiATFLfA`;*nWroK zHeM^7yox_zPYe}>%Iq)1bTS|u_FxP?iL0;zccaM$uzanvc2w`Hr|W5Yv*9&P8)FCv=7yp(9L#3aEz5@F#k3HQH4d_58X*^n3h_9vQTb%@QG6!P;~()8yj{eL-oh(7 zi&0{esF!`@GFe3qdX`oy_8hp6$(WAMVLW!mZkULfSdOsks%{4e3yTETy z02weD-h@?93ZK9_m<9bImO}Ikt*2Qug%;3z>d$plLdCS27E=zrO@*{Y&ACb8W7?%u zo}{1YGTop%WPuyHLlTUGm*Hs$r#d=Id)3AhYjK@A`O3`?a4Am3q4*fO;2!(|-#{_s z!fcoT10e=bsc)p8sDkn+Rc%O({7R*ir#R;-<}{UEmRu|gWraK@f0h48zw#}Xdee)P zN%?e;>Y)P+fHat=JSkF2N}(L~!7;Vd)o=yQ!wJ=~L+~~1P;TYIRCpfxDI_ZH9uN&q S#cxrAO7P4BJaFLuao|5Krp?L# literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand7.raw b/7.x/src/stk/rawwaves/mand7.raw new file mode 100644 index 0000000000000000000000000000000000000000..64941e9f98421aa1b2b353abbfa5a25cd9942295 GIT binary patch literal 2048 zcmeH``E!m}6vxlG&-+>;jiiJS6U3S-(ay9|RHLnF2vW4QG}hQ=q)|$0i#=#8iO|Fn zV(FwxFoM=rOJf@ojZqB}OU)Evm@u?S-uJoZ+w(v4S99jvXSvV4_j^9y^AO>Y?6DDU zg>`F5c#bSL@> zd`o?mz6#%JUk5irqTE_vbj_{WyY2$9SS#)9I}&VfN(e^sY0m)fQg2shzZ37ttkcxH z-8-x9?)vfdCe?eh&JpLH)6m>BPt0A-i=2wh2$H>0BvsPc7P~LpZvJSW_3!pi@ZI-^ zxu4oJyV@P@Hgm7p`fi-R%En5u%(G+dN!dy9CeFF)`NI=nGEJa~<#am3lbn{G8qY%~ znKSVcE#)@G;C&e@lO^1iN*7yd6Kr>RK*Mka5i&d=5T(S6DbP`5@V;jcicw)9=;l1x#kVu zO26?Ra#P%eZnk~hCfZ8z*yqw*#>qW|V?D*Nm%rp-`in|OaNAV}zkn$-K>*aAKB00Y9E17rpSB_|O0yFaifqf*lwvw`8d- zl47vDl%29nE+P)ch^Y&GPPz1$J8~2o0{7%Ke3;j968GcDe1fY@ePg&<jT6o6k*sPQ9~V;p*;u2e{#d@RORY1Y^>yWeKn44Y#wNu*4dgF0U>mt~ja z%Vp)a7&oXH^`%iXfu>P9?V&pyzzyhMxBq%0rdE^gxq)uC`| z1s~2~502m(E-0^Mn2Q-0jsA$m2k3`kn23>h4mv9h>7J7P;R<=UxT#G#H*MFdz8tFb^6r>e-DS#SN z6ve8l>8fj`p0-YX&Q*0=C{M>+^)5r{Eu;mSX_QPus26qC9U4&+YO8(m21QZ}^`?ow z3Ds)^wb2MurB9VzCH_VQO7RUOq{={hr;zE$5R z&cRmQ-D)7+N71r5h#D zMp8);npoPXE`({Qs=c;hR2ze7g_6lV=RAGoZ|INiJLjJ7J@?%AJkRHS&joma!ETg2 zXj5HpUc@bl^(a))s?dw@s`U)**IKPbu`1Hlbv~=pMW6RFR0zvls<~m3O@TROd)QUB zSsL+FZ-7GuK9}fQT^cc zleW6Q7pfA~I2DUduFAEurFNVRHRa}rv9_)qWK-;TyWO6%o9$fp1P7uSlF$_kR8Lju z#i@I0pW3CqQCF4ox_Z+*zc<87kdt1O+=<=*m8JTq(L$gA#rRXjsUBFuGPZUlu8~c% zkC?3{-Hb9DY^beocA4P7AEuv4GpS~zDX@|DJzMI+T`n7Q0?UvBs)=f~y6L5OrQQy2 zp0~oQ(o^&by;|q%lfF~+?vdisu z7vcuFSU1$=xyy{@3Z6q_bihE OuR0QK$HzhzV#4`G?c4&m8r5-j>=b?)OO*w zUtN*D@YH>EQ+=-%sdU*9g3BmECWfOu?{T-}eVOC=BBNY|Tkc+SX>OXE>_)pd7wr;V zf{SrUZmKJCm*m7)rt+0qZ69zis}PFTh{r@MlURx@%!ePd@Q%!@kc(~j8I>wXHC9bj zePIlxRf?ea0-`j1!qx?9oUGK;&80=YHxJJW1_uU za-HRV7V;A=<2<1=jB$)%Pj+G}>9FQ(!${GS#4&uGe({#i67J(sR!YSip$#I@Numon zOU5X)MLU_}B!4&Qng~S8dbG@uayxpWNA0IB=!3yR;%VvK6nRe;J^hi0r_f6{b(4;2 zfhIz>6|0LU&*DyatRlSkC}Xf8A2|d zljJFr3&h>W!n%k%#qCL6<6R-xTq>A^QJ5foJyR+_2XD(f9W!LSDZ`HicvsdkWXEU> zmAds32T|gut>_5_gx_7>mKtA`9ylSlYCrdgny-Xsk&I1p;zo%a>56sCl^)q9-|mqr z9u?geqvA*aNS^`oREM`{dQ`sVDzBt?{Jj=`8FX L#zO-C1A+elz2fQ< literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mand9.raw b/7.x/src/stk/rawwaves/mand9.raw new file mode 100644 index 0000000000000000000000000000000000000000..9e88a0c91317db8a3f2ba7969fe7aa0e0b2e029f GIT binary patch literal 2048 zcmeHF`%_g_7+w3EdoSSSDJY5g7{y0HC|V?z!eA;E8kJ+FH7a6^`AU0eXg*4-8D}(8 z(@e(@b(#t%9YIQL98yC=bbR0ogz$l>sDoFz+;h%eeeyr_oA;YN_w4=cy}oa)^#SPX z>}tIgNYzUi??2*8YprmHc`tiQLVO``?M9;UdQd}fi6_&hss7?O_)<5xT+{Xq;AS(MQd9Pxt)bicmdO zym>HaLeOS+llxN-f@8g%y)7X%K~)~Fdy8wQ*~5%9maDJT5Vct4bE$LD4p@T%hXXZM zcp%;XdCLlaxm~1BJ2mzy`=;*hT(b{Y8CIOL7F|>{k~l+ecPgB5R;ks=KEj{P1lJ`q zTaChTb=bIITsJ4W4|p!PGt61WEwjOO*fqpVGWWVRnN`?pSGR6xIqKgWsI*7e@z#6x z82y~C*DpI4^+s$kGL0-%t_I>7erGd3!ycp5+-8)jT&%@AC|2WDmC@cE(J0 zUF@un>2hY{MU|n>sT-#G3dMZh zW)T;$tDfS_w2SOsw%2~&u5(5^cl8PSuoK@R2Lmt`g(_6#r~xSBMMmhO`UVrI^lfJG z3?HzB3A#wvGY4T4Wxt`^S!xHSr z-zda9yhNM(IhF0TPdgkYH@@dq)?+xn!4A2Rj|}vLqQ)TXQr*>5HC837B=w4#tNP)8yi@Nu(=*Lh$1 zYe({5>G3QtOD-JU(F=XhA48EYIw#6khIlnuRw6CIt6&}dXeSCkk8zkTnKW@g^S+>5 z=UvJ4#(329wy0U6{y`%oPt;a24Y!ug&k7%X^E@;Xj<^lXb>;@2Zay``59XfOFliSV|%rs(zf L_gI1dL4p4OW?%Wt literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/mandpluk.raw b/7.x/src/stk/rawwaves/mandpluk.raw new file mode 100644 index 0000000000000000000000000000000000000000..162a0da9e1d448ee915c7b4ba441c8da46b6298a GIT binary patch literal 8900 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 PsADLOD`3`rMg6CLjECH- literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/ooo.raw b/7.x/src/stk/rawwaves/ooo.raw new file mode 100644 index 0000000000000000000000000000000000000000..1b8331e3d9a9ce5522961877759e898b3dc23fdb GIT binary patch literal 184 zcmV;p07w7-VFBF#F8pNu8~wBVo%_uDJNmWz%Kbb1Tlxt3FZ%!dTlx3))%R5P)9vWt zN!?52qv}HCJmo?9s}e*K3kz}?-7GgJO&IAHJstNU0T|y4{S3_)aS`VHi}Murm+pGo zqtVpSZ_{zrqt%7jF5<`SL+~E)f%aJO mEe0C@h4^&&ukdvB zXAXG~bv@x}I$N%JadGAL`hS(%i;Hv3)7cW9MqLki;Lq&o>b%0v(BhHte!bILT&lH- z8nV2S2BMRMO!;MbJUREVSF<)TUu7y~Ok$V|w434o&Hu^&=Kbydqy4MyN9#AI&)Yvd zetY^=-m?pjKHs}>tNH4;i>7BBkINlAzb9hb?hSueD=gKVFFoVeq?0|pZJrHGZJs^7 zlTOa~HD7wE=4yovf4A-46LIkTak(>&7fr8zyVZQ}#-q>AF1*Tnd-}uU&)dH_{b>DF T_ecA0_rH1nlmFjj7~uc_6d!%U literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/ppksblnk.raw b/7.x/src/stk/rawwaves/ppksblnk.raw new file mode 100644 index 0000000000000000000000000000000000000000..95a44464af8f4edbe304e7ed4a7fc70fd6d4b8d3 GIT binary patch literal 512 zcmZQzU{Gh6%b3Je%6yfziM^V0ACD)$tdObbBuN8VUPTSnS}iWU)5iNP9@!Z>ukdvB zXAXG~bv@x}I$N%JadGAL`hS(%i;Hv3)7cW9MqLki;Lq&o>b%0v(BhHte!bILT&lH- p8nV2S2BMRMO!;MbJUREVSF<)TUu7y~Ok$V|w0q#~2RdQo0sv6FP*(r| literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/ridecymb.raw b/7.x/src/stk/rawwaves/ridecymb.raw new file mode 100644 index 0000000000000000000000000000000000000000..722fa5a1477517a733d858d9a2d610051a22358f GIT binary patch literal 32394 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άsPNm-(DyaSbE?~RdQf2xOG(>;fG_8(zQWpz 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 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/silence.raw b/7.x/src/stk/rawwaves/silence.raw new file mode 100644 index 0000000000000000000000000000000000000000..4b8a1b3e49c7b867bb4cb3d412403b88b765c9de GIT binary patch literal 448 NcmZQz7zIN)1ONfR00961 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/sine.c b/7.x/src/stk/rawwaves/sine.c new file mode 100644 index 0000000..c43dc82 --- /dev/null +++ b/7.x/src/stk/rawwaves/sine.c @@ -0,0 +1,22 @@ +// Utility to make a rawwave sine table (assumes big-endian machine). + +#include +#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; iFB zGKY%hs+t;D6BV%?nD&Kk|Cnu_K!hvKaT4|7ju<)ocU`WSaDsxkCS zps|m=yNu&mn=rGT2EVoCRMq7rC4LAU=jmp1XJGib^K;Yme#N0S$B5x!5vTb86RGC()~Q^l}ER3K6w6| PCfhm3QL%Zqyz_bpy6-YGsc zzB7I`K`22t0h5rH(3}8x znt)~@2J`{?4DCZ~=m`1_oq%%$?)%{F1E|e{oQ%cEhMWz`-txE3yr6;g9G5jCjLa!tKJ?fSY0H3Sg)ZG7#b(d;qv=1kr*pfh@pU zF`z5J+<(zO(C-NRgQRbruO_w^YvMEM-{_x9cm$5uxfhcx>y_8xXLJ1yHXTh6<_cP8(k zY)Y&Ttle(+-q;CC0 zlzb_{l0f|!bgn^sMO^9!`?J|%V`3bldLrS%O+srzQi8T;G6aVC5BarD`}0*H<`E*i zraXA=POeW}N~c^n^Eqf7949*8gOBTumX6LInja9^-K=ev$~TX%#ru={JYNj8BB)K-G$3SsYc&T~t_vEbJ^aE1(yI7x3j%@^$j2^8)gCbE|U= zbCz@PIda(p*x|Vc0aW=6bF(%PGQI@z%d`+Yfqlq>| zbs~bWP8cFo6Nm(Ff+^u5K@?`e444MD!xT6Z#=-uuGi(W?VHB(e%fk||C@cUYU~c#n z%n1%RxC?+c2~bl5J!sJD3}$d(mI5?rK#N5X1-hm{p9uC05!Q(au-69crvM+jiL$^` z4DdFabQ<^!0e%;g#enxDkl~1=2l8~0SSk7--(V^NWGzZvPE!Th2hs%rhvxL-3~RvU zYo-NYbdYs7t2rA1xDDjU0fx)DhPl;wynyR;zD_=+fDf>y7n&7z79ooYi@Cu+FiTuY zrb=&?z94In8_N{SDj!KcDk>K%&#e%y$f*>m%%_M`$d6?o*Hm4oYOB_-91?wvsE;aN$v3augG`jIjV^xz*(`2(x^U1T)7Oj?v-~3v6TWi{k+8DpXRC(%P zyH^Kq$J0)$&fTt(=LXN$yEA*Vd+0Any&AoXFH`zZeT@FBfvW?XgXAHzA@*?NE7uXB zk-^c(G1al9*LgHk+VLOMKmErQ#up|ECM~CUruwIGZ?xZR|JD4~cLq7L`i}D6ZB}Y_ zVU9fSv>>@Kzerwkrc2Z5%ap${3?&A0rExWAO>=F3y=Mdek1>;vIq{+3U#CsE&5e&u z|Al|j+v3<7`<%UPzaz7=vHNT<>WjfX&;I1s;%^=-71lPp`+#_8esuO|>A3ED@QKd< DB?bC1 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/snardrum.raw b/7.x/src/stk/rawwaves/snardrum.raw new file mode 100644 index 0000000000000000000000000000000000000000..93005ad04e1adee215a3a7cbcdd949311f2513bf GIT binary patch literal 16660 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 L0_bl}h7k?`T})ND literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/tambourn.raw b/7.x/src/stk/rawwaves/tambourn.raw new file mode 100644 index 0000000000000000000000000000000000000000..fa8125220f692abc78cd1554ab2fc4899219b646 GIT binary patch literal 9240 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 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/tomhidrm.raw b/7.x/src/stk/rawwaves/tomhidrm.raw new file mode 100644 index 0000000000000000000000000000000000000000..0cf482b93778deb85fe70762346d3c4133a6a0e7 GIT binary patch literal 27642 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 B9JBxc literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/tomlowdr.raw b/7.x/src/stk/rawwaves/tomlowdr.raw new file mode 100644 index 0000000000000000000000000000000000000000..9813513dd9c05dc3b2aad5eef14ad08cc5e839c9 GIT binary patch literal 39200 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 N^G5$K9{%6E{XcWfP;dYM literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/tommiddr.raw b/7.x/src/stk/rawwaves/tommiddr.raw new file mode 100644 index 0000000000000000000000000000000000000000..4d042575a03819ac4ee527ee89b2d50f9f63c464 GIT binary patch literal 28292 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 literal 0 HcmV?d00001 diff --git a/7.x/src/stk/rawwaves/twopeaks.raw b/7.x/src/stk/rawwaves/twopeaks.raw new file mode 100644 index 0000000000000000000000000000000000000000..d383a24bda8d6364f31a4d99f0eba84db414bd05 GIT binary patch literal 512 zcmV+b0{{I0000R9CjeLgodEU$Vgcd;bpsp&)dZpif(CU5dkB*Wz6$#cNe!wG9T1=r zH51zvk{4MSFdG*g4IdLBC?iWHeJ8vs3M^zU%P~JQzcx!a;yZ*sF+k`;s77*0MNA-1 z15xf&;8)mM*InCS zCjbcm0RQ*@+5f2jWB&^Np8g*Fi~QvLEBjvhe))^|h4*dtLiGjnxAIo-jdX&%YSFSh=*x>Er^W^E~ z(CDY?ZtNxP%kNh53v6e*0ehEBxgBi~b(|p8pE}WB;iC+5h(j C?EQiO literal 0 HcmV?d00001 diff --git a/7.x/src/stk/src/ADSR.cpp b/7.x/src/stk/src/ADSR.cpp new file mode 100644 index 0000000..7c13948 --- /dev/null +++ b/7.x/src/stk/src/ADSR.cpp @@ -0,0 +1,181 @@ +/***************************************************/ +/*! \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 must be positive. + + by Perry R. Cook and Gary P. Scavone, 1995--2021. +*/ +/***************************************************/ + +#include "ADSR.h" + +namespace stk { + +ADSR :: ADSR( void ) +{ + target_ = 0.0; + value_ = 0.0; + attackRate_ = 0.001; + decayRate_ = 0.001; + releaseRate_ = 0.005; + releaseTime_ = -1.0; + sustainLevel_ = 0.5; + state_ = IDLE; + Stk::addSampleRateAlert( this ); +} + +ADSR :: ~ADSR( void ) +{ + Stk::removeSampleRateAlert( this ); +} + +void ADSR :: sampleRateChanged( StkFloat newRate, StkFloat oldRate ) +{ + if ( !ignoreSampleRateChange_ ) { + attackRate_ = oldRate * attackRate_ / newRate; + decayRate_ = oldRate * decayRate_ / newRate; + releaseRate_ = oldRate * releaseRate_ / newRate; + } +} + +void ADSR :: keyOn() +{ + if ( target_ <= 0.0 ) target_ = 1.0; + state_ = ATTACK; +} + +void ADSR :: keyOff() +{ + target_ = 0.0; + state_ = RELEASE; + + // FIXED October 2010 - Nick Donaldson + // Need to make release rate relative to current value!! + // Only update if we have set a TIME rather than a RATE, + // in which case releaseTime_ will be -1 + if ( releaseTime_ > 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..87df13a --- /dev/null +++ b/7.x/src/stk/stkOpcodes.cpp @@ -0,0 +1,610 @@ +/* + 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) { + Stk::setSampleRate((StkFloat)this->opds.insdshead->esr); + instrument = new T(); + } + 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) { + Stk::setSampleRate((StkFloat)this->opds.insdshead->esr); + instrument = new T((StkFloat)10.0); + } + 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; } From 40b8db1987af1d6aaae06e671009bfaf4b8a5486 Mon Sep 17 00:00:00 2001 From: vlazzarini Date: Tue, 13 Oct 2026 09:45:26 +0100 Subject: [PATCH 2/5] Stage STK rawwaves with the correct .raw extension --- .github/workflows/plugins_build_7.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 5316a50..82f160b 100644 --- a/.github/workflows/plugins_build_7.yml +++ b/.github/workflows/plugins_build_7.yml @@ -119,7 +119,7 @@ jobs: build/src/OpenCL/*.so \ build/src/stk/*.so package/ mkdir -p package/rawwaves - cp 7.x/src/stk/rawwaves/*.wav package/rawwaves/ + cp 7.x/src/stk/rawwaves/*.raw package/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -225,7 +225,7 @@ jobs: build/src/OpenCL/*.dylib \ build/src/stk/*.dylib package/ mkdir -p package/rawwaves - cp 7.x/src/stk/rawwaves/*.wav package/rawwaves/ + cp 7.x/src/stk/rawwaves/*.raw package/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -327,7 +327,7 @@ jobs: } $rawwaves = Join-Path $package 'rawwaves' New-Item -ItemType Directory -Force -Path $rawwaves | Out-Null - Copy-Item '7.x\src\stk\rawwaves\*.wav' -Destination $rawwaves + Copy-Item '7.x\src\stk\rawwaves\*.raw' -Destination $rawwaves - name: Upload plugins uses: actions/upload-artifact@v4 From ef0c14e5f91d09fe227b6c19fd3563981a1059c4 Mon Sep 17 00:00:00 2001 From: vlazzarini Date: Tue, 13 Oct 2026 11:58:01 +0100 Subject: [PATCH 3/5] Add CodeRabbit config excluding vendored STK sources --- .coderabbit.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coderabbit.yaml 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/**" From d2e2057e20870fa44ab48bc00bd4a54d563781d4 Mon Sep 17 00:00:00 2001 From: vlazzarini Date: Tue, 13 Oct 2026 12:13:03 +0100 Subject: [PATCH 4/5] Fix STK adapter issues - Correct kperiodEnd() in OpcodeBase to return the end index (ksmps - ksmps_no_end). - Catch StkError from instrument construction and return via csound->InitError. --- 7.x/src/stk/OpcodeBase.hpp | 14 ++------------ 7.x/src/stk/stkOpcodes.cpp | 14 ++++++++++---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/7.x/src/stk/OpcodeBase.hpp b/7.x/src/stk/OpcodeBase.hpp index 49a724f..f0e3843 100644 --- a/7.x/src/stk/OpcodeBase.hpp +++ b/7.x/src/stk/OpcodeBase.hpp @@ -199,12 +199,7 @@ namespace csound { } uint32_t kperiodEnd() const { - uint32_t end = opds.insdshead->ksmps_no_end; - if (end) { - return end; - } else { - return ksmps(); - } + return ksmps() - opds.insdshead->ksmps_no_end; } uint32_t ksmps() const { @@ -325,12 +320,7 @@ namespace csound { } uint32_t kperiodEnd() const { - uint32_t end = opds.insdshead->ksmps_no_end; - if (end) { - return end; - } else { - return ksmps(); - } + return ksmps() - opds.insdshead->ksmps_no_end; } uint32_t ksmps() const { diff --git a/7.x/src/stk/stkOpcodes.cpp b/7.x/src/stk/stkOpcodes.cpp index 87df13a..0f721fd 100644 --- a/7.x/src/stk/stkOpcodes.cpp +++ b/7.x/src/stk/stkOpcodes.cpp @@ -138,8 +138,11 @@ class STKInstrumentAdapter : public OpcodeBase> { STKInstrumentAdapter() : instrument(0) {} int32_t init(CSOUND *csound) { if (!instrument) { - Stk::setSampleRate((StkFloat)this->opds.insdshead->esr); - instrument = new T(); + try { + instrument = new T(); + } catch (StkError &e) { + return csound->InitError(csound, "STK: %s", e.getMessage().c_str()); + } } instrument->noteOn(*ifrequency, *igain); released = false; @@ -282,8 +285,11 @@ class STKInstrumentAdapter1 : public OpcodeBase> { STKInstrumentAdapter1() : instrument(0) {} int32_t init(CSOUND *csound) { if (!instrument) { - Stk::setSampleRate((StkFloat)this->opds.insdshead->esr); - instrument = new T((StkFloat)10.0); + 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; From 23c397d662640d1278551cc9e9d2593b13d998bd Mon Sep 17 00:00:00 2001 From: vlazzarini Date: Tue, 13 Oct 2026 12:24:06 +0100 Subject: [PATCH 5/5] Fix STK rawwaves staging path in CI Stage rawwaves under package/stk/rawwaves to match DEFAULT_RAWWAVE_PATH (/stk/rawwaves) so release packages find them without requiring RAWWAVE_PATH. --- .github/workflows/plugins_build_7.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 82f160b..4d2ca45 100644 --- a/.github/workflows/plugins_build_7.yml +++ b/.github/workflows/plugins_build_7.yml @@ -118,8 +118,8 @@ jobs: build/src/py/*.so \ build/src/OpenCL/*.so \ build/src/stk/*.so package/ - mkdir -p package/rawwaves - cp 7.x/src/stk/rawwaves/*.raw package/rawwaves/ + mkdir -p package/stk/rawwaves + cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -224,8 +224,8 @@ jobs: build/src/py/*.dylib \ build/src/OpenCL/*.dylib \ build/src/stk/*.dylib package/ - mkdir -p package/rawwaves - cp 7.x/src/stk/rawwaves/*.raw package/rawwaves/ + mkdir -p package/stk/rawwaves + cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -325,7 +325,7 @@ jobs: if (Test-Path $vcpkgBin) { Copy-Item (Join-Path $vcpkgBin '*.dll') -Destination $package -ErrorAction SilentlyContinue } - $rawwaves = Join-Path $package 'rawwaves' + $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