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