1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 /*
22 ??? I could create an LFO that has an extra parameter which could be anything
23 from a falling saw to a triangle to a rising saw but adjusting how the angles
24 are aimed. think of a V in a square where the bottom axis is 0 to 360 degrees
25 and the tops of the V hit the top two corners. Then let the extra parameter
26 say where the bottom point of the V hits on the bottom axis. So, 180 degrees would
27 be a triangle wave at a phase of 90 deg. and zero or 359 degrees would be sawtooth
28 waves.
29
30 The only reason I haven't done this already is that the frontend would need a 4th
31 slider which would have to be appropriately names, and second, because I would
32 probably need an explanation of it by showing the user an image.
33
34 If I did have a 4th parameter, I could also use that to define the number of
35 steps for a step function LFO. But it would have a different name.. so CLFORegistry
36 would also have to return what the call the optional extra slider
37 */
38
39 /*
40 ??? OPTIMIZATION: I could conceivably make the constructor of the derived LFO
41 class calculate a lookup table and the base class could implement nextSample()
42 to just use the calculated lookup table. I would have to base the number of
43 entries in the table on a sample rate and the frequency. I should calculated it
44 in the derived class's constructor, but I should also use static members (mutex
45 protected) to remember a cache of the last fwe calculated table and only
46 recalculated it if the none of the parameters for the cached tables match the
47 current parameters. This way when an action calculates it for 2 channels but
48 reinstantiates the LFO for both channels it won't have to recalculate the table.
49 */
50
51 #include "ALFO.h"
52
53 #include <math.h>
54
55 #include <stdexcept>
56 #include <istring>
57
58 #include "unit_conv.h"
59
60 // --------------------------------
ALFO()61 ALFO::ALFO()
62 {
63 }
64
~ALFO()65 ALFO::~ALFO()
66 {
67 }
68
69
70
71
72
73
74 // -----------------------------------
75 // --- some LFO implementations ------
76 // -----------------------------------
77 // --- These should ordinarily be constructed by CLFORegistry::createLFO, not directly constructed
78 // - I would have to move them into the header file if I wanted to use them directly
79
80 /*
81 * - LFO with simply a constant value of 1.0
82 */
83 class CConstantLFO : public ALFO
84 {
85 public:
CConstantLFO()86 CConstantLFO()
87 {
88 }
89
~CConstantLFO()90 virtual ~CConstantLFO()
91 {
92 }
93
nextValue()94 const float nextValue()
95 {
96 return 1.0;
97 }
98
getValue(const sample_pos_t time) const99 const float getValue(const sample_pos_t time) const
100 {
101 return 1.0;
102 }
103 };
104
105
106
107 /*
108 * - Sine shaped LFO with a range of [-1,1] and a frequenct and initial value specified by the constructor's arguments
109 * - Note: I use sinf which is (from glibc) a faster float version of sine rather than double percision
110 * - This function should be available on other platforms because it is mentioned in the ANSI C99 specification
111 */
112 class CSinLFO : public ALFO
113 {
114 public:
115 // frequency is in hertz
116 // initialAngle is in degrees
117 // sampleRate is given to know how often nextValue will be called per cycle
CSinLFO(float _frequency,float initialAngle,unsigned sampleRate)118 CSinLFO(float _frequency,float initialAngle,unsigned sampleRate) :
119 // convert from herz to scalar to mul with counter
120 frequency((1.0/((float)sampleRate/_frequency))*(2.0*M_PI)),
121 initial(degrees_to_radians(initialAngle)/frequency),
122
123 // initialize the counter to return the initial angle
124 counter(initial)
125 {
126 }
127
~CSinLFO()128 virtual ~CSinLFO()
129 {
130 }
131
nextValue()132 const float nextValue()
133 {
134 return sinf((counter++)*frequency);
135 }
136
getValue(const sample_pos_t time) const137 const float getValue(const sample_pos_t time) const
138 {
139 return sinf((time+initial)*frequency);
140 }
141
142 protected:
143 float frequency;
144 float initial;
145 // ??? I probably do need to worry about counter wrap around
146 // perhaps I could know a threshold when to fmod the counter
147 float counter;
148 };
149
150
151
152 /*
153 * - Same as CSinLFO except it has a range of [0,1] (but it still has the same shape as sine)
154 */
155 class CPosSinLFO : public CSinLFO
156 {
157 public:
CPosSinLFO(float frequency,float initialAngle,unsigned sampleRate)158 CPosSinLFO(float frequency,float initialAngle,unsigned sampleRate) :
159 CSinLFO(frequency,initialAngle,sampleRate)
160 {
161 }
162
~CPosSinLFO()163 virtual ~CPosSinLFO()
164 {
165 }
166
nextValue()167 const float nextValue()
168 {
169 return (sinf((counter++)*frequency)+1.0)/2.0;
170 }
171
getValue(const sample_pos_t time) const172 const float getValue(const sample_pos_t time) const
173 {
174 return (sinf((time+initial)*frequency)+1.0)/2.0;
175 }
176 };
177
178 /*
179 * - Same as CSinLFO except it has a range of [0,1] by taking abs(value)
180 */
181 class CABSSinLFO : public CSinLFO
182 {
183 public:
CABSSinLFO(float frequency,float initialAngle,unsigned sampleRate)184 CABSSinLFO(float frequency,float initialAngle,unsigned sampleRate) :
185 CSinLFO(frequency,initialAngle,sampleRate)
186 {
187 }
188
~CABSSinLFO()189 virtual ~CABSSinLFO()
190 {
191 }
192
nextValue()193 const float nextValue()
194 {
195 return fabs(CSinLFO::nextValue());
196 }
197
getValue(const sample_pos_t time) const198 const float getValue(const sample_pos_t time) const
199 {
200 return fabs(CSinLFO::getValue(time));
201 }
202 };
203
204
205
206
207
208 class CRisingSawtoothLFO : public ALFO
209 {
210 public:
211 // frequency is in hertz
212 // initialAngle is in degrees
213 // sampleRate is given to know how often nextValue will be called per cycle
CRisingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate)214 CRisingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate) :
215 mod((unsigned)ceil(sampleRate/frequency)),
216 div(sampleRate/frequency/2.0),
217 counter((unsigned)((initialAngle+180.0)/360.0*sampleRate/frequency)%mod)
218
219 {
220 }
221
~CRisingSawtoothLFO()222 virtual ~CRisingSawtoothLFO()
223 {
224 }
225
nextValue()226 const float nextValue()
227 {
228 const float v=(counter++)/div-1.0;
229 counter%=mod;
230 return v;
231 }
232
getValue(const sample_pos_t time) const233 const float getValue(const sample_pos_t time) const
234 {
235 return (time%mod)/div-1.0;
236 }
237
238 protected:
239 const unsigned mod;
240 float div;
241
242 unsigned counter;
243 };
244
245 class CPosRisingSawtoothLFO : public CRisingSawtoothLFO
246 {
247 public:
CPosRisingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate)248 CPosRisingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate) :
249 CRisingSawtoothLFO(frequency,initialAngle,sampleRate)
250
251 {
252 div*=2.0;
253 }
254
~CPosRisingSawtoothLFO()255 virtual ~CPosRisingSawtoothLFO()
256 {
257 }
258
nextValue()259 const float nextValue()
260 {
261 const float v=(counter++)/div;
262 counter%=mod;
263 return v;
264 }
265
getValue(const sample_pos_t time) const266 const float getValue(const sample_pos_t time) const
267 {
268 return (time%mod)/div;
269 }
270 };
271
272 class CFallingSawtoothLFO : public CRisingSawtoothLFO
273 {
274 public:
275 // frequency is in hertz
276 // initialAngle is in degrees
277 // sampleRate is given to know how often nextValue will be called per cycle
CFallingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate)278 CFallingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate) :
279 CRisingSawtoothLFO(frequency,initialAngle,sampleRate)
280
281 {
282 }
283
~CFallingSawtoothLFO()284 virtual ~CFallingSawtoothLFO()
285 {
286 }
287
nextValue()288 const float nextValue()
289 {
290 const float v=(counter++)/div-1.0;
291 counter%=mod;
292 return -v;
293 }
294
getValue(const sample_pos_t time) const295 const float getValue(const sample_pos_t time) const
296 {
297 return -(time%mod)/div-1.0;
298 }
299
300 };
301
302
303 class CPosFallingSawtoothLFO : public CRisingSawtoothLFO
304 {
305 public:
CPosFallingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate)306 CPosFallingSawtoothLFO(float frequency,float initialAngle,unsigned sampleRate) :
307 CRisingSawtoothLFO(frequency,initialAngle,sampleRate)
308
309 {
310 div*=2.0;
311 }
312
~CPosFallingSawtoothLFO()313 virtual ~CPosFallingSawtoothLFO()
314 {
315 }
316
nextValue()317 const float nextValue()
318 {
319 const float v=(counter++)/div;
320 counter%=mod;
321 return 1.0-v;
322 }
323
getValue(const sample_pos_t time) const324 const float getValue(const sample_pos_t time) const
325 {
326 return 1.0-(time%mod)/div;
327 }
328 };
329
330
331 /* square is just derived from SinLFO and if the Sin is >=0 then we return 1 else -1 */
332 class CSquareLFO : public CSinLFO
333 {
334 public:
CSquareLFO(float frequency,float initialAngle,unsigned sampleRate)335 CSquareLFO(float frequency,float initialAngle,unsigned sampleRate) :
336 CSinLFO(frequency,initialAngle,sampleRate)
337 {
338 }
339
~CSquareLFO()340 virtual ~CSquareLFO()
341 {
342 }
343
nextValue()344 const float nextValue()
345 {
346 return CSinLFO::nextValue()>=0.0 ? 1.0 : -1.0;
347 }
348
getValue(const sample_pos_t time) const349 const float getValue(const sample_pos_t time) const
350 {
351 return CSinLFO::getValue(time)>=0.0 ? 1.0 : -1.0;
352 }
353 };
354
355 /* square is just derived from SinLFO and if the Sin is >=0 then we return 1 else 0 */
356 class CPosSquareLFO : public CSinLFO
357 {
358 public:
CPosSquareLFO(float frequency,float initialAngle,unsigned sampleRate)359 CPosSquareLFO(float frequency,float initialAngle,unsigned sampleRate) :
360 CSinLFO(frequency,initialAngle,sampleRate)
361 {
362 }
363
~CPosSquareLFO()364 virtual ~CPosSquareLFO()
365 {
366 }
367
nextValue()368 const float nextValue()
369 {
370 return CSinLFO::nextValue()>=0.0 ? 1.0 : 0.0;
371 }
372
getValue(const sample_pos_t time) const373 const float getValue(const sample_pos_t time) const
374 {
375 return CSinLFO::getValue(time)>=0.0 ? 1.0 : 0.0;
376 }
377 };
378
379
380
381
382
383
384
385
386
387 #if 0 // not implemented yet..
388
389 The sine is deformable in that you can set where the positive peak
390 of the sine should be, and the negative peak will be semetric with it.
391
392 I had to do a piecewise function where you use a multiplier on the x
393 coordinate to do the two pieces from 0..target and (360-target)..360 (called 'R1')
394 And there is another multiplier as well as a phase to use between
395 target..(360-target) (called 'R2')
396
397 A natural sine will be produced at target=90
398
399 I suppose target has to be between 0..180
400
401 The formulas: (target IS in degrees)
402 R1Mul=90.0/target
403
404 R2Mul=1.0/(2.0-(1.0/R1Mul))
405 R2Phase=0.75*M_PI - M_PI/(2.0*R1Mul)
406
407 //I derived these on paper which looks really nasty.. so I'll just use them
408
409
410 // ---------------------------------------------
411 class CDeformableSinLFO : public ALFO
412 {
413 public:
414 // frequency is in hertz
415 // initialAngle is in degrees
416 // sampleRate is given to know how often nextValue will be called per cycle
417 CDeformableSinLFO(float _frequency,float initialAngle,float peakAngle,unsigned sampleRate) :
418 // convert from herz to scalar to mul with counter
419 frequency((1.0/((float)sampleRate/_frequency))*(2.0*M_PI)),
420 initial(degrees_to_radians(initialAngle)/frequency),
421
422 // initialize the counter to return the initial angle
423 counter(initial)
424 {
425 }
426
427 virtual ~CDeformableSinLFO()
428 {
429 }
430
431 const float nextValue()
432 {
433 return sinf((counter++)*frequency);
434 }
435
436 const float getValue(const sample_pos_t time) const
437 {
438 return sinf((time+initial)*frequency);
439 }
440
441 protected:
442 float frequency;
443 float initial;
444 // ??? I probably do need to worry about counter wrap around
445 // perhaps I could know a threshold when to fmod the counter
446 float counter;
447
448
449 };
450
451 #endif
452
453 // -----------------------------------------------
454
455
456
457
458
459
460
461
462
463
464
465
466 // --- CLFORegistry definition --------------------------------------------
467
468 /* ??? I should make this a real registry where each ALFO has to return a name, an the registry searches thru a vector instead fo hardcoding... then I could possibly load LFOs dynamically */
469 const CLFORegistry gLFORegistry;
470
CLFORegistry()471 CLFORegistry::CLFORegistry()
472 {
473 }
474
~CLFORegistry()475 CLFORegistry::~CLFORegistry()
476 {
477 }
478
getCount() const479 const size_t CLFORegistry::getCount() const
480 {
481 return 10;
482 }
483
getName(const size_t index) const484 const string CLFORegistry::getName(const size_t index) const
485 {
486 switch(index)
487 {
488 case 0:
489 return N_("Sine Wave [-1,1]");
490 case 1:
491 return N_("Sine Wave [ 0,1]");
492 case 2:
493 return N_("ABS Sine Wave [ 0,1]");
494 case 3:
495 return N_("Constant");
496 case 4:
497 return N_("Rising Sawtooth Wave [-1,1]");
498 case 5:
499 return N_("Rising Sawtooth Wave [ 0,1]");
500 case 6:
501 return N_("Falling Sawtooth Wave [-1,1]");
502 case 7:
503 return N_("Falling Sawtooth Wave [ 0,1]");
504 case 8:
505 return N_("Square Wave [-1,1]");
506 case 9:
507 return N_("Square Wave [ 0,1]");
508 }
509 throw runtime_error(string(__func__)+" -- unhandled index: "+istring(index));
510 }
511
isBipolar(const size_t index) const512 const bool CLFORegistry::isBipolar(const size_t index) const
513 {
514 switch(index)
515 {
516 case 0: // sin
517 return true;
518 case 1: // pos sin
519 return false;
520 case 2: // abs(sin)
521 return false;
522 case 3: // constant
523 return false;
524 case 4: // rising saw
525 return true;
526 case 5: // pos rising saw
527 return false;
528 case 6: // falling saw
529 return true;
530 case 7: // pos falling saw
531 return false;
532 case 8: // square
533 return true;
534 case 9: // pos square
535 return false;
536 }
537 throw runtime_error(string(__func__)+" -- unhandled index: "+istring(index));
538 }
539
getIndexByName(const string name) const540 const size_t CLFORegistry::getIndexByName(const string name) const
541 {
542 if(name=="Sine Wave [-1,1]")
543 return 0;
544 else if(name=="Sine Wave [ 0,1]")
545 return 1;
546 else if(name=="ABS Sine Wave [ 0,1]")
547 return 2;
548 else if(name=="Constant")
549 return 3; // ??? the frontend relies on this being 3
550 else if(name=="Rising Sawtooth Wave [-1,1]")
551 return 4;
552 else if(name=="Rising Sawtooth Wave [ 0,1]")
553 return 5;
554 else if(name=="Falling Sawtooth Wave [-1,1]")
555 return 6;
556 else if(name=="Falling Sawtooth Wave [ 0,1]")
557 return 7;
558 else if(name=="Square Wave [-1,1]")
559 return 8;
560 else if(name=="Square Wave [ 0,1]")
561 return 9;
562 else
563 throw runtime_error(string(__func__)+" -- unhandled name: '"+name+"'");
564 }
565
createLFO(const CLFODescription & desc,const unsigned sampleRate) const566 ALFO *CLFORegistry::createLFO(const CLFODescription &desc,const unsigned sampleRate) const
567 {
568 switch(desc.LFOType)
569 {
570 case 0:
571 return new CSinLFO(desc.freq,desc.phase,sampleRate);
572 case 1:
573 return new CPosSinLFO(desc.freq,desc.phase,sampleRate);
574 case 2:
575 return new CABSSinLFO(desc.freq,desc.phase,sampleRate);
576 case 3:
577 return new CConstantLFO; // ??? the frontend relies on this being 3
578 case 4:
579 return new CRisingSawtoothLFO(desc.freq,desc.phase,sampleRate);
580 case 5:
581 return new CPosRisingSawtoothLFO(desc.freq,desc.phase,sampleRate);
582 case 6:
583 return new CFallingSawtoothLFO(desc.freq,desc.phase,sampleRate);
584 case 7:
585 return new CPosFallingSawtoothLFO(desc.freq,desc.phase,sampleRate);
586 case 8:
587 return new CSquareLFO(desc.freq,desc.phase,sampleRate);
588 case 9:
589 return new CPosSquareLFO(desc.freq,desc.phase,sampleRate);
590 }
591 throw runtime_error(string(__func__)+" -- unhandled LFOType (index): "+istring(desc.LFOType));
592 }
593
writeToFile(CNestedDataFile * f,const string key) const594 void CLFODescription::writeToFile(CNestedDataFile *f,const string key) const
595 {
596 f->setValue<float>(key DOT "amp",amp);
597 f->setValue<float>(key DOT "freq",freq);
598 f->setValue<float>(key DOT "phase",phase);
599 f->setValue<size_t>(key DOT "type",LFOType);
600 }
601
readFromFile(const CNestedDataFile * f,const string key)602 void CLFODescription::readFromFile(const CNestedDataFile *f,const string key)
603 {
604 amp=f->getValue<float>(key DOT "amp");
605 freq=f->getValue<float>(key DOT "freq");
606 phase=f->getValue<float>(key DOT "phase");
607 LFOType=f->getValue<size_t>(key DOT "type");
608 }
609
610