/[svn]/linuxsampler/trunk/src/engines/common/SignalUnit.h
ViewVC logotype

Contents of /linuxsampler/trunk/src/engines/common/SignalUnit.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2232 - (show annotations) (download) (as text)
Mon Aug 8 13:40:04 2011 UTC (7 years, 1 month ago) by iliev
File MIME type: text/x-c++hdr
File size: 20216 byte(s)
* sfz engine: implemented opcode volume_smoothccN

1 /***************************************************************************
2 * *
3 * LinuxSampler - modular, streaming capable sampler *
4 * *
5 * Copyright (C) 2011 Grigor Iliev *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the Free Software *
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
20 * MA 02111-1307 USA *
21 ***************************************************************************/
22
23 #ifndef __LS_SIGNALUNIT_H__
24 #define __LS_SIGNALUNIT_H__
25
26 #include "../../common/ArrayList.h"
27
28
29 namespace LinuxSampler {
30
31 template<typename T>
32 class FixedArray {
33 public:
34 FixedArray(int capacity) {
35 iSize = 0;
36 iCapacity = capacity;
37 pData = new T[iCapacity];
38 }
39
40 ~FixedArray() {
41 delete pData;
42 pData = NULL;
43 }
44
45 inline int size() const { return iSize; }
46 inline int capacity() { return iCapacity; }
47
48 void add(T element) {
49 if (iSize >= iCapacity) throw Exception("Array out of bounds");
50 pData[iSize++] = element;
51 }
52
53
54 T increment() {
55 if (iSize >= iCapacity) throw Exception("Array out of bounds");
56 return pData[iSize++];
57 }
58
59 void clear() { iSize = 0; }
60
61 void copy(const FixedArray<T>& array) {
62 if(array.size() >= capacity()) throw Exception("Not enough space to copy array");
63 for (int i = 0; i < array.size(); i++) pData[i] = array[i];
64 iSize = array.size();
65 }
66
67 inline T& operator[](int idx) const {
68 return pData[idx];
69 }
70
71 private:
72 T* pData;
73 int iSize;
74 int iCapacity;
75 };
76
77 class SignalUnitRack;
78
79 /**
80 * A signal unit consist of internal signal generator (like envelope generator,
81 * low frequency oscillator, etc) with a number of generator parameters which
82 * influence/modulate/dynamically change the generator's signal in some manner.
83 * Each generator parameter (also called signal unit parameter) can receive
84 * signal from another signal unit and use this signal to dynamically change the
85 * behavior of the signal generator. In turn, the signal of this unit can be fed
86 * to another unit(s) and influence its parameters.
87 */
88 class SignalUnit {
89 public:
90
91 /**
92 * This class represents a parameter which will influence the signal
93 * unit to which it belongs in certain way.
94 * For example, let's say the signal unit is a low frequency oscillator
95 * with frequency 1Hz. If we want to modulate the LFO to start with 1Hz
96 * and increment its frequency to 5Hz in 1 second, we can add
97 * a parameter which signal source is an envelope
98 * generator with attack time of 1 second and coefficient 4. Thus, the
99 * normalized level of the EG will move from 0 to 1 in one second.
100 * On every time step (sample point) the normalized level
101 * will be multiplied by 4 (the parameter coefficient) and added to the
102 * LFO's frequency.
103 * So, after 1 second, the LFO frequency will be 1x4 + 1 = 5Hz.
104 * We can also add another parameter for modulating the LFO's pitch depth
105 * and so on.
106 */
107 class Parameter {
108 public:
109 SignalUnit* pUnit; /* The source unit whose output signal
110 * will modulate the parameter.
111 */
112
113 float Coeff; // The modulation coefficient
114
115
116 Parameter() : Coeff(1), pUnit(NULL) { }
117
118 /**
119 * @param unit The source unit used to influence this parameter.
120 * @param coeff The coefficient by which the normalized signal
121 * received from the source unit should be multiplied when a
122 * default transformation is done.
123 */
124 Parameter(SignalUnit* unit, float coeff = 1) {
125 pUnit = unit;
126 Coeff = coeff;
127 }
128
129 Parameter(const Parameter& Prm) { Copy(Prm); }
130 void operator=(const Parameter& Prm) { Copy(Prm); }
131
132 void Copy(const Parameter& Prm) {
133 if (this == &Prm) return;
134
135 pUnit = Prm.pUnit;
136 Coeff = Prm.Coeff;
137 }
138
139
140 /**
141 * Calculates the transformation for this parameter
142 * which should be applied to the parameter's value
143 * and multiplies by Coeff.
144 * This implementation of the method just multiplies by Coeff.
145 */
146 virtual float Transform(float val) {
147 return val * Coeff;
148 }
149
150 /**
151 * Gets the current value of the parameter.
152 * This implementation returns the current signal level of the
153 * source unit with applied transformation if the source unit is
154 * active, otherwise returns 1.
155 * Note that this method assume that pUnit is not NULL.
156 */
157 virtual float GetValue() {
158 return pUnit->Active() ? Transform(pUnit->GetLevel()) : 1.0f;
159 }
160 };
161
162
163 public:
164 ArrayList<SignalUnit::Parameter> Params; // The list of parameters which are modulating the signal unit
165
166 SignalUnit(SignalUnitRack* rack): pRack(rack), bActive(false), Level(0.0f), bCalculating(false), uiDelayTrigger(0) { }
167 SignalUnit(const SignalUnit& Unit): pRack(Unit.pRack) { Copy(Unit); }
168 void operator=(const SignalUnit& Unit) { Copy(Unit); }
169
170 void Copy(const SignalUnit& Unit) {
171 if (this == &Unit) return;
172
173 bActive = Unit.bActive;
174 Level = Unit.Level;
175 Params = Unit.Params;
176 uiDelayTrigger = Unit.uiDelayTrigger;
177 bCalculating = false;
178 }
179
180 /*
181 * Determines whether the unit is active.
182 * If the unit is not active, its level should be ignored.
183 * For endpoint unit this method determines whether
184 * the rendering should be stopped.
185 */
186 virtual bool Active() { return bActive; }
187
188 /**
189 * Override this method to process the current control change events.
190 * @param itEvent - iterator pointing to the event to be processed.
191 */
192 virtual void ProcessCCEvent(uint8_t Controller, uint8_t Value) { }
193
194 virtual void EnterReleaseStage() { }
195
196 virtual void CancelRelease() { }
197
198 /**
199 * Gets the normalized level of the unit for the current
200 * time step (sample point). The level is calculated if it's not
201 * calculated for the current step yet. Because the level depends on
202 * the parameters, their levels are calculated too.
203 */
204 virtual float GetLevel() {
205 if (!bRecalculate) return Level;
206
207 if (bCalculating) {
208 std::cerr << "SignalUnit: Loop detected. Aborted!";
209 return Level;
210 }
211
212 bCalculating = true;
213
214 for(int i = 0; i < Params.size(); i++) {
215 Params[i].GetValue();
216 }
217
218 bRecalculate = bCalculating = false;
219 return Level;
220 }
221
222 /**
223 * Will be called to increment the time with one sample point.
224 * The unit should recalculate or prepare for recalculation
225 * its current level on every call of this function.
226 * Note that it is not known whether all source signal unit's levels
227 * are recalculated before the call of this method. So, the calculations
228 * that depends on the unit's parameters should be postponed to
229 * the call of GetLevel().
230 */
231 virtual void Increment() { bRecalculate = true; }
232
233 /**
234 * Initializes and triggers the unit.
235 * Note that when a voice is the owner of a unit rack, all settings
236 * should be reset when this method is called, because the sampler
237 * is reusing the voice objects.
238 */
239 virtual void Trigger() = 0;
240
241 /**
242 * When the signal unit rack is triggered, it triggers all signal
243 * units it holds. If for some reason the triggering of a unit
244 * should be delayed, this method can be set to return non-zero value
245 * specifying the delay in time steps.
246 * Note that this is only a helper method and the implementation
247 * should be done manually.
248 */
249 virtual uint DelayTrigger() { return uiDelayTrigger; }
250
251 /**
252 * A helper method which checks whether the delay
253 * stage is finished.
254 */
255 bool DelayStage();
256
257 protected:
258 SignalUnitRack* const pRack;
259
260 bool bActive; /* Don't use it to check the active state of the unit!!!
261 * Use Active() instead! */
262 float Level;
263 bool bRecalculate; /* Determines whether the unit's level should be recalculated. */
264 bool bCalculating; /* Determines whether the unit is in process of calculating
265 * its level. Used for preventing infinite loops.
266 */
267
268 uint uiDelayTrigger; /* in time steps */
269
270 };
271
272 class EndpointSignalUnit: public SignalUnit {
273 public:
274 EndpointSignalUnit(SignalUnitRack* rack): SignalUnit(rack) { }
275
276 /**
277 * Gets the volume modulation value
278 * for the current time step (sample point).
279 */
280 virtual float GetVolume() = 0;
281
282 /**
283 * Gets the filter cutoff frequency modulation value
284 * for the current time step (sample point).
285 */
286 virtual float GetFilterCutoff() = 0;
287
288 /**
289 * Gets the pitch modulation value
290 * for the current time step (sample point).
291 */
292 virtual float GetPitch() = 0;
293
294 /**
295 * Gets the resonance modulation value
296 * for the current time step (sample point).
297 */
298 virtual float GetResonance() = 0;
299
300 /** Should return value in the range [-100, 100] (L <-> R) */
301 virtual float GetPan() = 0;
302
303 virtual float CalculateFilterCutoff(float cutoff) {
304 cutoff *= GetFilterCutoff();
305 return cutoff > 13500 ? 13500 : cutoff;
306 }
307
308 virtual float CalculatePitch(float pitch) {
309 return GetPitch() * pitch;
310 }
311
312 virtual float CalculateResonance(float res) {
313 return GetResonance() * res;
314 }
315
316 /** Should return value in the range [0, 127] (L <-> R) */
317 virtual uint8_t CaluclatePan(uint8_t pan) {
318 int p = pan + GetPan() * 0.63;
319 if (p < 0) return 0;
320 if (p > 127) return 127;
321 return p;
322 }
323 };
324
325 /**
326 * Used to smooth out the parameter changes.
327 */
328 class Smoother {
329 protected:
330 uint timeSteps; // The number of time steps to reach the goal
331 uint currentTimeStep;
332 uint8_t goal; // 0 - 127
333 uint8_t prev; // 0 - 127
334
335 public:
336 /**
337 *
338 * @param time The time (in seconds) to reach the goal
339 * @param sampleRate
340 * @param val The initial value
341 */
342 void trigger(float time, float sampleRate, uint8_t val = 0) {
343 currentTimeStep = timeSteps = time * sampleRate;
344 prev = goal = val;
345 }
346
347 /**
348 * Set the current value, which the smoother will not smooth out.
349 * If you want the value to be smoothen out, use update() instead.
350 */
351 void setValue( uint8_t val) {
352 currentTimeStep = timeSteps;
353 prev = goal = val;
354 }
355
356 /**
357 * Sets a new value. The render function will return
358 * values gradually approaching this value.
359 */
360 void update(uint8_t val) {
361 if (val == goal) return;
362
363 prev = prev + (goal - prev) * (currentTimeStep / (float)timeSteps);
364 goal = val;
365 currentTimeStep = 0;
366 }
367
368 uint8_t render() {
369 if (currentTimeStep >= timeSteps) return goal;
370 return prev + (goal - prev) * (currentTimeStep++ / (float)timeSteps);
371 }
372
373 bool isSmoothingOut() { return currentTimeStep < timeSteps; }
374 };
375
376 /**
377 * Continuous controller signal unit.
378 * The level of this unit corresponds to the controllers changes
379 * and their influences.
380 */
381 class CCSignalUnit: public SignalUnit {
382 public:
383 /** Listener which will be notified when the level of the unit is changed. */
384 class Listener {
385 public:
386 virtual void ValueChanged(CCSignalUnit* pUnit) = 0;
387 };
388
389 protected:
390 class CC {
391 public:
392 uint8_t Controller; ///< MIDI controller number.
393 uint8_t Value; ///< Controller Value.
394 short int Curve; ///< specifies the curve type
395 float Influence;
396
397 Smoother* pSmoother;
398
399 CC(uint8_t Controller = 0, float Influence = 0.0f, short int Curve = -1, Smoother* pSmoother = NULL) {
400 this->Controller = Controller;
401 this->Value = 0;
402 this->Curve = Curve;
403 this->Influence = Influence;
404 this->pSmoother = pSmoother;
405 }
406
407 CC(const CC& cc) { Copy(cc); }
408 void operator=(const CC& cc) { Copy(cc); }
409
410 void Copy(const CC& cc) {
411 Controller = cc.Controller;
412 Value = cc.Value;
413 Influence = cc.Influence;
414 Curve = cc.Curve;
415 pSmoother = cc.pSmoother;
416 }
417 };
418
419 FixedArray<CC> Ctrls; // The MIDI controllers which modulates this signal unit.
420 Listener* pListener;
421 bool hasSmoothCtrls; // determines whether there are smooth controllers (used for optimization)
422 bool isSmoothingOut; // determines whether there is a CC which is in process of smoothing out (used for optimization)
423
424 public:
425
426 CCSignalUnit(SignalUnitRack* rack, Listener* l = NULL): SignalUnit(rack), Ctrls(128) {
427 pListener = l;
428 hasSmoothCtrls = isSmoothingOut = false;
429 }
430
431 CCSignalUnit(const CCSignalUnit& Unit): SignalUnit(Unit.pRack), Ctrls(128) { Copy(Unit); }
432 void operator=(const CCSignalUnit& Unit) { Copy(Unit); }
433
434 void Copy(const CCSignalUnit& Unit) {
435 Ctrls.copy(Unit.Ctrls);
436 pListener = Unit.pListener;
437 hasSmoothCtrls = Unit.hasSmoothCtrls;
438 isSmoothingOut = Unit.isSmoothingOut;
439 SignalUnit::Copy(Unit);
440 }
441
442 void AddCC(uint8_t Controller, float Influence, short int Curve = -1, Smoother* pSmoother = NULL) {
443 Ctrls.add(CC(Controller, Influence, Curve, pSmoother));
444 if (pSmoother != NULL) hasSmoothCtrls = true;
445 }
446
447 void RemoveAllCCs() {
448 Ctrls.clear();
449 }
450
451 virtual void Increment() {
452 if (hasSmoothCtrls && isSmoothingOut) Calculate();
453 }
454
455 virtual void Trigger() {
456 Calculate();
457 bActive = Level != 0;
458 }
459
460 virtual void ProcessCCEvent(uint8_t Controller, uint8_t Value) {
461 bool recalculate = false;
462
463 for (int i = 0; i < Ctrls.size(); i++) {
464 if (Controller != Ctrls[i].Controller) continue;
465 if (Ctrls[i].Value == Value) continue;
466 Ctrls[i].Value = Value;
467 if (Ctrls[i].pSmoother != NULL) Ctrls[i].pSmoother->update(Value);
468 if (!bActive) bActive = true;
469 recalculate = true;
470 }
471
472 if (!(hasSmoothCtrls && isSmoothingOut) && recalculate) Calculate();
473 }
474
475 virtual void Calculate() {
476 float l = 0;
477 isSmoothingOut = false;
478 for (int i = 0; i < Ctrls.size(); i++) {
479 if (Ctrls[i].pSmoother == NULL) {
480 l += Normalize(Ctrls[i].Value, Ctrls[i].Curve) * Ctrls[i].Influence;
481 } else {
482 if (Ctrls[i].pSmoother->isSmoothingOut()) isSmoothingOut = true;
483 l += Normalize(Ctrls[i].pSmoother->render(), Ctrls[i].Curve) * Ctrls[i].Influence;
484 }
485 }
486 if (Level != l) {
487 Level = l;
488 if (pListener != NULL) pListener->ValueChanged(this);
489 }
490 }
491
492 virtual float Normalize(uint8_t val, short int curve = -1) {
493 return val / 127.0f;
494 }
495 };
496
497 } // namespace LinuxSampler
498
499 #endif /* __LS_SIGNALUNIT_H__ */

  ViewVC Help
Powered by ViewVC