1 /**
2 * Copyright 2017 Cut Through Recordings
3 * License: MIT License
4 * Author(s): Ethan Reker
5 */
6 module ddsp.effect.digitaldelay;
7 
8 import ddsp.effect.effect;
9 import ddsp.util.functions;
10 
11 /**
12 * A general purpose Digital Delay with support for external feedback,
13   fractional delay, and feedback path effects.
14 */
15 class DigitalDelay : AudioEffect
16 {
17     import core.stdc.stdlib : malloc, free;
18     import core.stdc..string : memset;
19     import dplug.core.alignedbuffer;
20     
21 public:
22 
23     this() nothrow @nogc
24     {
25         buffer = null;
26         _size = 0;
27         _feedback = 0.0f;
28         _mix = 0.0f;
29         _useExternalFeedback = false;
30         
31         _readIndex = 0;
32         _writeIndex = 0;
33         
34         feedbackFX = makeVec!AudioEffect;
35     }
36     
37     ~this() nothrow @nogc { free(buffer); }
38     
39     /// Set the sample rate and initialize the buffer.
40     override void setSampleRate(float sampleRate) nothrow @nogc
41     {
42         _sampleRate = sampleRate;
43         _feedback = 0;
44         _mix = 0;
45         _size = cast(uint)sampleRate * 2;
46 
47         if(buffer != null)
48             free(buffer);
49 
50         buffer = cast(float*) malloc(_size * float.sizeof);
51         reset();
52     }
53     
54     /// calculates and sets the number of samples required.
55     void setDelay(float msDelay) nothrow @nogc
56     {
57         _delayInSamples = msToSamples(msDelay, _sampleRate);
58         if(_delayInSamples > _size)
59         {
60             reset();
61         }
62         else
63         {
64             resetIndices();
65         }
66     }
67 
68     /// Sets delay time, feedback, and mix
69     void setParams(float msDelay, float feedback, float mix) nothrow @nogc
70     {
71         setDelay(msDelay);
72         setFeedbackAmount(feedback);
73         setMixAmount(mix);
74     }
75     
76     override float getNextSample(const float input) nothrow @nogc
77     {
78         float xn = input;
79         float yn = buffer[cast(size_t)_readIndex];
80         
81         if(_readIndex == _writeIndex && _delayInSamples < 1.0f)
82         {
83             yn = xn;
84         }
85         
86         size_t _readIndex_1 = cast(size_t)(_readIndex - 1);
87         if(_readIndex_1 < 0)
88             _readIndex_1 = _size - 1;
89         
90         float yn_1 = buffer[_readIndex_1];
91         
92         float fracDelay = _delayInSamples - cast(int)_delayInSamples;
93         
94         float interp = linearInterp(0, 1, yn, yn_1, fracDelay);
95         
96         if(_delayInSamples == 0)
97             yn = xn;
98         else
99             yn = interp;
100         
101         if(!_useExternalFeedback)
102             buffer[cast(size_t)_writeIndex] = xn + _feedback * yn;
103         else
104             buffer[cast(size_t)_writeIndex] = xn + _feedbackIn * _feedback;
105         
106         float output = _mix * yn + (1.0 - _mix) * xn;
107         
108         if(++_writeIndex >= _size)
109             _writeIndex = 0;
110         if(++_readIndex >= _size)
111             _readIndex = 0;
112             
113         return output;
114     }
115     
116     //set all elements in the buffer to 0, and reset indices.
117     override void reset() nothrow @nogc
118     {
119         memset(buffer, 0, _size * float.sizeof);
120 
121         resetIndices();
122     }
123 
124     void resetIndices() nothrow @nogc
125     {
126         _readIndex = cast(long)(_writeIndex - cast(long)_delayInSamples);
127         if(_readIndex < 0)
128             _readIndex += _size;
129     }
130     
131     float getCurrentFeedbackOutput() nothrow @nogc { return _feedback * buffer[cast(size_t)_readIndex]; }
132     
133     float getFeedbackAmount() nothrow @nogc { return _feedback; }
134     
135     float getMixAmount() nothrow @nogc { return _mix; }
136     
137     void setCurrentFeedbackInput(float f) nothrow @nogc { _feedbackIn = f; }
138     
139     void setUseExternalFeedback(bool b) nothrow @nogc { _useExternalFeedback = b; }
140     
141     void addFeedbackEffect(AudioEffect effect) nothrow @nogc { feedbackFX.pushBack(effect); }
142     
143     void setFeedbackAmount(float feedback) nothrow @nogc { _feedback = feedback; }
144     
145     void setMixAmount(float mix) nothrow @nogc { _mix = mix; }
146 
147     /// used for debuging purposes.
148     override string toString()
149     {
150         import std.conv;
151         string output = "Rindex " ~ to!string(_readIndex) ~ " Windex " ~ to!string(_writeIndex) ~ " Size " ~ to!string(_size) ~ " Delay " ~ to!string(_delayInSamples);
152         return output;
153     }
154     
155 protected:
156 
157     float *buffer;
158     float _sampleRate;
159     float _feedback;
160     float _mix;
161     
162     size_t _size;
163     float _delayInSamples;
164     float _delayInMS;
165     
166     long _writeIndex;
167     long _readIndex;
168     
169     bool _useExternalFeedback;
170     float _feedbackIn;
171     
172     Vec!AudioEffect feedbackFX;
173 
174     float maxDelayTime = 3.0f;
175 
176 }
177 
178 unittest
179 {
180     import dplug.core.nogc;
181     
182     DigitalDelay d = mallocNew!DigitalDelay();
183     //d.initialize(44100);
184     //d.update(22050, 0.5, 0.5);
185     //testEffect(d, "DDelay", 44100 * 2, false);
186 }