1 /***************************************************************************
2  *                                                                         *
3  *   Copyright (C) 2005 - 2019 Christian Schoenebeck                       *
4  *                                                                         *
5  *   This library is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This library is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this library; if not, write to the Free Software           *
17  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
18  *   MA  02111-1307  USA                                                   *
19  ***************************************************************************/
20 
21 #ifndef __LS_LFOTRIANGLEDIHARMONIC_H__
22 #define __LS_LFOTRIANGLEDIHARMONIC_H__
23 
24 #include "LFOBase.h"
25 
26 // amplitue of 2nd harmonic (to approximate the triangular wave)
27 #define AMP2	-0.11425509f
28 
29 namespace LinuxSampler {
30 
31     /** @brief Triangle LFO (di-harmonic implementation)
32      *
33      * This is a triangle Low Frequency Oscillator implementation which uses
34      * a di-harmonic solution. This means it sums up two harmonics
35      * (sinusoids) to approximate a triangular wave.
36      *
37      * @deprecated This class will probably be removed in future. Reason: The
38      * resulting wave form is not similar enough to a triangular wave. to
39      * achieve a more appropriate triangular wave form, this class would need
40      * to use more harmonics, but that in turn would make runtime performance of
41      * this class even worse. And since it currently seems to perform worst
42      * already among all triangular wave implementations on all known
43      * architectures, doing that required harmonics change currently does not
44      * make sense. Furthermore the detailed behaviour of the other triangular
45      * LFO implementations had been fixed in the meantime; this one not.
46      */
47     template<LFO::range_type_t RANGE>
48     class DEPRECATED_API LFOTriangleDiHarmonic : public LFOBase<RANGE> {
49         public:
50 
51             /**
52              * Constructor
53              *
54              * @param Max - maximum value of the output levels
55              */
LFOTriangleDiHarmonic(float Max)56             LFOTriangleDiHarmonic(float Max) : LFOBase<RANGE>::LFOBase(Max) {
57                 //NOTE: DO NOT add any custom initialization here, since it would break LFOCluster construction !
58             }
59 
60             /**
61              * Calculates exactly one sample point of the LFO wave.
62              *
63              * @returns next LFO level
64              */
render()65             inline float render() {
66                 real1 -= c1 * imag1;
67                 imag1 += c1 * real1;
68                 real2 -= c2 * imag2;
69                 imag2 += c2 * real2;
70                 if (RANGE == LFO::range_unsigned)
71                     return (real1 + real2 * AMP2) * normalizer + offset;
72                 else /* signed range */
73                     return (real1 + real2 * AMP2) * normalizer;
74             }
75 
76             /**
77              * Update LFO depth with a new external controller value.
78              *
79              * @param ExtControlValue - new external controller value
80              */
updateByMIDICtrlValue(const uint16_t & ExtControlValue)81             inline void updateByMIDICtrlValue(const uint16_t& ExtControlValue) {
82                 this->ExtControlValue = ExtControlValue;
83 
84                 const float max = (this->InternalDepth + ExtControlValue * this->ExtControlDepthCoeff) * this->ScriptDepthFactor;
85                 if (RANGE == LFO::range_unsigned) {
86                     const float harmonicCompensation = 1.0f + fabsf(AMP2); // to compensate the compensation ;) (see trigger())
87                     normalizer = max * 0.5f;
88                     offset     = normalizer * harmonicCompensation;
89                 } else { // signed range
90                     normalizer = max;
91                 }
92             }
93 
94             /**
95              * Will be called by the voice when the key / voice was triggered.
96              *
97              * @param Frequency       - frequency of the oscillator in Hz
98              * @param StartLevel      - on which level the wave should start
99              * @param InternalDepth   - firm, internal oscillator amplitude
100              * @param ExtControlDepth - defines how strong the external MIDI
101              *                          controller has influence on the
102              *                          oscillator amplitude
103              * @param FlipPhase       - inverts the oscillator wave against
104              *                          a horizontal axis
105              * @param SampleRate      - current sample rate of the engines
106              *                          audio output signal
107              */
trigger(float Frequency,LFO::start_level_t StartLevel,uint16_t InternalDepth,uint16_t ExtControlDepth,bool FlipPhase,unsigned int SampleRate)108             void trigger(float Frequency, LFO::start_level_t StartLevel, uint16_t InternalDepth, uint16_t ExtControlDepth, bool FlipPhase, unsigned int SampleRate) {
109                 this->Frequency = Frequency;
110                 this->ScriptFrequencyFactor = this->ScriptDepthFactor = 1.f; // reset for new voice
111                 const float harmonicCompensation = 1.0f + fabsf(AMP2); // to compensate the 2nd harmonic's amplitude overhead
112                 this->InternalDepth        = (InternalDepth / 1200.0f) * this->Max / harmonicCompensation;
113                 this->ExtControlDepthCoeff = (((float) ExtControlDepth / 1200.0f) / 127.0f) * this->Max / harmonicCompensation;
114                 this->pFinalDepth = NULL;
115                 this->pFinalFrequency = NULL;
116 
117                 const float freq = Frequency * this->ScriptFrequencyFactor;
118                 c1 = 2.0f * M_PI * freq / (float) SampleRate;
119                 c2 = 2.0f * M_PI * freq / (float) SampleRate * 3.0f;
120 
121                 double phi; // phase displacement
122                 switch (StartLevel) {
123                     case LFO::start_level_mid:
124                         //FIXME: direct jumping to 90� and 270� doesn't work out due to numeric accuracy problems (causes wave deformation)
125                         //phi = (FlipPhase) ? 0.5 * M_PI : 1.5 * M_PI; // 90� or 270�
126                         //break;
127                     case LFO::start_level_max:
128                         phi = (FlipPhase) ? M_PI : 0.0; // 180� or 0�
129                         break;
130                     case LFO::start_level_min:
131                         phi = (FlipPhase) ? 0.0 : M_PI; // 0� or 180�
132                         break;
133                 }
134                 real1 = real2 = cos(phi);
135                 imag1 = imag2 = sin(phi);
136             }
137 
138             /**
139              * Should be invoked after the LFO is triggered with StartLevel
140              * start_level_min.
141              * @param phase From 0 to 360 degrees.
142              */
setPhase(float phase)143             void setPhase(float phase) {
144                 if (phase < 0) phase = 0;
145                 if (phase > 360) phase = 360;
146                 phase /= 360.0f;
147 
148                 // FIXME: too heavy?
149                 float steps = 1.0f / (c1 / (2.0f * M_PI)); // number of steps for one cycle
150                 steps *= phase + 0.25f;
151                 for (int i = 0; i < steps; i++) render();
152             }
153 
setFrequency(float Frequency,unsigned int SampleRate)154             void setFrequency(float Frequency, unsigned int SampleRate) {
155                 this->Frequency = Frequency;
156                 const float freq = Frequency * this->ScriptFrequencyFactor;
157                 c1 = 2.0f * M_PI * freq / (float) SampleRate;
158                 c2 = 2.0f * M_PI * freq / (float) SampleRate * 3.0f;
159             }
160 
setScriptDepthFactor(float factor,bool isFinal)161             void setScriptDepthFactor(float factor, bool isFinal) {
162                 this->ScriptDepthFactor = factor;
163                 // set or reset this script depth parameter to be the sole
164                 // source for the LFO depth
165                 if (isFinal && !this->pFinalDepth)
166                     this->pFinalDepth = &this->ScriptDepthFactor;
167                 else if (!isFinal && this->pFinalDepth == &this->ScriptDepthFactor)
168                     this->pFinalDepth = NULL;
169                 // recalculate upon new depth
170                 updateByMIDICtrlValue(this->ExtControlValue);
171             }
172 
setScriptFrequencyFactor(float factor,unsigned int SampleRate)173             void setScriptFrequencyFactor(float factor, unsigned int SampleRate) {
174                 this->ScriptFrequencyFactor = factor;
175                 // in case script frequency was set as "final" value before,
176                 // reset it so that all sources are processed from now on
177                 if (this->pFinalFrequency == &this->ScriptFrequencyFactor)
178                     this->pFinalFrequency = NULL;
179                 // recalculate upon new frequency
180                 setFrequency(this->Frequency, SampleRate);
181             }
182 
setScriptFrequencyFinal(float hz,unsigned int SampleRate)183             void setScriptFrequencyFinal(float hz, unsigned int SampleRate) {
184                 this->ScriptFrequencyFactor = hz;
185                 // assign script's given frequency as sole source for the LFO
186                 // frequency, thus ignore all other sources
187                 if (!this->pFinalFrequency)
188                     this->pFinalFrequency = &this->ScriptFrequencyFactor;
189                 // recalculate upon new frequency
190                 setFrequency(this->Frequency, SampleRate);
191             }
192 
193         private:
194             float c1;
195             float c2;
196             float real1;
197             float imag1;
198             float real2;
199             float imag2;
200             float normalizer;
201             float offset;
202     };
203 
204 } // namespace LinuxSampler
205 
206 #endif // __LS_LFOTRIANGLEDIHARMONIC_H__
207