Mercurial > hg > audio_effects_textbook_code
comparison effects/vibrato/Source/PluginEditor.cpp @ 0:e32fe563e124
First commit
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Fri, 10 Oct 2014 15:41:23 +0100 |
parents | |
children | 04e171d2a747 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e32fe563e124 |
---|---|
1 /* | |
2 This code accompanies the textbook: | |
3 | |
4 Digital Audio Effects: Theory, Implementation and Application | |
5 Joshua D. Reiss and Andrew P. McPherson | |
6 | |
7 --- | |
8 | |
9 Vibrato: frequency modulation using delay lines | |
10 See textbook Chapter 2: Delay Line Effects | |
11 | |
12 Code by Andrew McPherson, Brecht De Man and Joshua Reiss | |
13 | |
14 --- | |
15 | |
16 This program is free software: you can redistribute it and/or modify | |
17 it under the terms of the GNU General Public License as published by | |
18 the Free Software Foundation, either version 3 of the License, or | |
19 (at your option) any later version. | |
20 | |
21 This program is distributed in the hope that it will be useful, | |
22 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 GNU General Public License for more details. | |
25 | |
26 You should have received a copy of the GNU General Public License | |
27 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
28 */ | |
29 | |
30 #include "PluginProcessor.h" | |
31 #include "PluginEditor.h" | |
32 #include <cstring> | |
33 | |
34 #ifdef _MSC_VER | |
35 #define snprintf _snprintf_s //support for pre-2014 versions of Visual Studio | |
36 #endif // _MSC_VER | |
37 | |
38 //============================================================================== | |
39 VibratoAudioProcessorEditor::VibratoAudioProcessorEditor (VibratoAudioProcessor* ownerFilter) | |
40 : AudioProcessorEditor (ownerFilter), | |
41 sweepWidthLabel_("", "Sweep Width (sec.):"), | |
42 frequencyLabel_("", "LFO Frequency:"), | |
43 waveformLabel_("", "LFO Waveform:"), | |
44 interpolationLabel_("", "Interpolation Type:"), | |
45 pitchShiftLabel_("", "Maximum Pitch Shift:") | |
46 { | |
47 // Initialise variables | |
48 oldSweepWidth_ = oldFrequency_ = -1.0; | |
49 oldWaveform_ = -1; | |
50 | |
51 // Set up the sliders | |
52 addAndMakeVisible (&sweepWidthSlider_); | |
53 sweepWidthSlider_.setSliderStyle (Slider::Rotary); | |
54 sweepWidthSlider_.addListener (this); | |
55 sweepWidthSlider_.setRange (.001, VibratoAudioProcessor::kMaximumSweepWidth, 0.0005); | |
56 | |
57 addAndMakeVisible (&frequencySlider_); | |
58 frequencySlider_.setSliderStyle (Slider::Rotary); | |
59 frequencySlider_.addListener (this); | |
60 frequencySlider_.setRange (0.5, 5.0, 0.025); | |
61 | |
62 addAndMakeVisible(&waveformComboBox_); | |
63 waveformComboBox_.setEditableText(false); | |
64 waveformComboBox_.setJustificationType(Justification::left); | |
65 waveformComboBox_.addItem("Sine", VibratoAudioProcessor::kWaveformSine); | |
66 waveformComboBox_.addItem("Triangle", VibratoAudioProcessor::kWaveformTriangle); | |
67 waveformComboBox_.addItem("Sawtooth (rising)", VibratoAudioProcessor::kWaveformSawtooth); | |
68 waveformComboBox_.addItem("Sawtooth (falling)", VibratoAudioProcessor::kWaveformInverseSawtooth); | |
69 waveformComboBox_.addListener(this); | |
70 | |
71 addAndMakeVisible(&interpolationComboBox_); | |
72 interpolationComboBox_.setEditableText(false); | |
73 interpolationComboBox_.setJustificationType(Justification::left); | |
74 interpolationComboBox_.addItem("None", VibratoAudioProcessor::kInterpolationNearestNeighbour); | |
75 interpolationComboBox_.addItem("Linear", VibratoAudioProcessor::kInterpolationLinear); | |
76 interpolationComboBox_.addItem("Cubic", VibratoAudioProcessor::kInterpolationCubic); | |
77 interpolationComboBox_.addListener(this); | |
78 | |
79 // This label is informational and exists apart from other controls | |
80 // The other labels are attached to sliders and combo boxes | |
81 addAndMakeVisible(&pitchShiftLabel_); | |
82 pitchShiftLabel_.setFont(Font (12.0f)); | |
83 | |
84 sweepWidthLabel_.attachToComponent(&sweepWidthSlider_, false); | |
85 sweepWidthLabel_.setFont(Font (11.0f)); | |
86 | |
87 frequencyLabel_.attachToComponent(&frequencySlider_, false); | |
88 frequencyLabel_.setFont(Font (11.0f)); | |
89 | |
90 waveformLabel_.attachToComponent(&waveformComboBox_, false); | |
91 waveformLabel_.setFont(Font (11.0f)); | |
92 | |
93 interpolationLabel_.attachToComponent(&interpolationComboBox_, false); | |
94 interpolationLabel_.setFont(Font (11.0f)); | |
95 | |
96 // add the triangular resizer component for the bottom-right of the UI | |
97 addAndMakeVisible(resizer_ = new ResizableCornerComponent (this, &resizeLimits_)); | |
98 resizeLimits_.setSizeLimits(370, 160, 600, 300); | |
99 | |
100 // set our component's initial size to be the last one that was stored in the filter's settings | |
101 setSize(ownerFilter->lastUIWidth_, | |
102 ownerFilter->lastUIHeight_); | |
103 | |
104 startTimer(50); | |
105 } | |
106 | |
107 VibratoAudioProcessorEditor::~VibratoAudioProcessorEditor() | |
108 { | |
109 } | |
110 | |
111 //============================================================================== | |
112 void VibratoAudioProcessorEditor::paint (Graphics& g) | |
113 { | |
114 g.fillAll (Colours::grey); | |
115 } | |
116 | |
117 void VibratoAudioProcessorEditor::resized() | |
118 { | |
119 sweepWidthSlider_.setBounds (20, 20, 150, 40); | |
120 frequencySlider_.setBounds(200, 20, 150, 40); | |
121 pitchShiftLabel_.setBounds(20, 60, 350, 20); | |
122 waveformComboBox_.setBounds(20, 100, 150, 30); | |
123 interpolationComboBox_.setBounds(200, 100, 150, 30); | |
124 | |
125 resizer_->setBounds(getWidth() - 16, getHeight() - 16, 16, 16); | |
126 | |
127 getProcessor()->lastUIWidth_ = getWidth(); | |
128 getProcessor()->lastUIHeight_ = getHeight(); | |
129 } | |
130 | |
131 //============================================================================== | |
132 // This timer periodically checks whether any of the filter's parameters have changed... | |
133 void VibratoAudioProcessorEditor::timerCallback() | |
134 { | |
135 VibratoAudioProcessor* ourProcessor = getProcessor(); | |
136 | |
137 sweepWidthSlider_.setValue(ourProcessor->sweepWidth_, dontSendNotification); | |
138 frequencySlider_.setValue(ourProcessor->frequency_, dontSendNotification); | |
139 waveformComboBox_.setSelectedId(ourProcessor->waveform_, false); | |
140 interpolationComboBox_.setSelectedId(ourProcessor->interpolation_, false); | |
141 | |
142 // Update the pitch shift label only when something changes to avoid | |
143 // needless calculations | |
144 if(ourProcessor->sweepWidth_ != oldSweepWidth_ || | |
145 ourProcessor->frequency_ != oldFrequency_ || | |
146 ourProcessor->waveform_ != oldWaveform_) | |
147 { | |
148 updatePitchShiftLabel(); | |
149 oldSweepWidth_ = ourProcessor->sweepWidth_; | |
150 oldFrequency_ = ourProcessor->frequency_; | |
151 oldWaveform_ = ourProcessor->waveform_; | |
152 } | |
153 } | |
154 | |
155 // This is our Slider::Listener callback, when the user drags a slider. | |
156 void VibratoAudioProcessorEditor::sliderValueChanged (Slider* slider) | |
157 { | |
158 // It's vital to use setParameterNotifyingHost to change any parameters that are automatable | |
159 // by the host, rather than just modifying them directly, otherwise the host won't know | |
160 // that they've changed. | |
161 | |
162 if (slider == &sweepWidthSlider_) | |
163 { | |
164 getProcessor()->setParameterNotifyingHost (VibratoAudioProcessor::kSweepWidthParam, | |
165 (float)sweepWidthSlider_.getValue()); | |
166 updatePitchShiftLabel(); | |
167 } | |
168 else if (slider == &frequencySlider_) | |
169 { | |
170 getProcessor()->setParameterNotifyingHost (VibratoAudioProcessor::kFrequencyParam, | |
171 (float)frequencySlider_.getValue()); | |
172 updatePitchShiftLabel(); | |
173 } | |
174 } | |
175 | |
176 // Similar callback to sliderValueChanged for ComboBox updates | |
177 void VibratoAudioProcessorEditor::comboBoxChanged (ComboBox *comboBox) | |
178 { | |
179 if(comboBox == &waveformComboBox_) | |
180 { | |
181 getProcessor()->setParameterNotifyingHost (VibratoAudioProcessor::kWaveformParam, | |
182 (float)waveformComboBox_.getSelectedId()); | |
183 updatePitchShiftLabel(); | |
184 } | |
185 else if(comboBox == &interpolationComboBox_) | |
186 { | |
187 getProcessor()->setParameterNotifyingHost (VibratoAudioProcessor::kInterpolationParam, | |
188 (float)interpolationComboBox_.getSelectedId()); | |
189 // This parameter doesn't affect the maximum pitch shift | |
190 } | |
191 } | |
192 | |
193 // Update the content of the pitch shift label (whenever controls change) | |
194 void VibratoAudioProcessorEditor::updatePitchShiftLabel() | |
195 { | |
196 VibratoAudioProcessor* ourProcessor = getProcessor(); | |
197 | |
198 // The amount of pitch shift depends on the derivative of the delay, which | |
199 // is given by: delay = width * f(frequency * t) | |
200 // where f(x) is one of: | |
201 // sine --> 0.5 + 0.5*sin(2*pi*x) --> derivative pi*cos(x)*dx | |
202 // triangle --> {2.0*x or 1.0-(2.0*(x-0.5)) ---> derivative +/- 2.0*dx | |
203 // sawtooth rising --> x --> derivative 1.0*dx | |
204 // sawtooth falling --> 1.0 - x --> derivative -1.0*dx | |
205 // For f(frequency*t), "dx" = frequency | |
206 | |
207 float maxSpeed = 1.0, minSpeed = 1.0; | |
208 float maxPitch = 0.0, minPitch = 0.0; | |
209 char str[256]; | |
210 | |
211 switch(ourProcessor->waveform_) | |
212 { | |
213 case VibratoAudioProcessor::kWaveformSine: | |
214 maxSpeed = 1.0 + M_PI * ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
215 minSpeed = 1.0 - M_PI * ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
216 break; | |
217 case VibratoAudioProcessor::kWaveformTriangle: | |
218 maxSpeed = 1.0 + 2.0 * ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
219 minSpeed = 1.0 - 2.0 * ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
220 break; | |
221 case VibratoAudioProcessor::kWaveformSawtooth: | |
222 // Standard (rising) sawtooth means delay is increasing --> pitch is lower | |
223 maxSpeed = 1.0; | |
224 minSpeed = 1.0 - ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
225 break; | |
226 case VibratoAudioProcessor::kWaveformInverseSawtooth: | |
227 // Inverse (falling) sawtooth means delay is decreasing --> pitch is higher | |
228 maxSpeed = 1.0 + ourProcessor->frequency_ * ourProcessor->sweepWidth_; | |
229 minSpeed = 1.0; | |
230 break; | |
231 } | |
232 | |
233 // Convert speed to pitch shift --> semitones = 12*log2(speed) | |
234 maxPitch = 12.0*logf(maxSpeed)/logf(2.0); | |
235 | |
236 if(minSpeed > 0) | |
237 { | |
238 minPitch = 12.0*logf(minSpeed)/logf(2.0); | |
239 snprintf(str, 256, "Vibrato range: %+.2f to %+.2f semitones (speed %.3f to %.3f)", | |
240 minPitch, maxPitch, minSpeed, maxSpeed); | |
241 } | |
242 else | |
243 { | |
244 snprintf(str, 256, "Vibrato range: --- to %+.2f semitones (speed %.3f to %.3f)", | |
245 maxPitch, minSpeed, maxSpeed); | |
246 } | |
247 | |
248 pitchShiftLabel_.setText(str, dontSendNotification); | |
249 } |