quantumVERB  1.0.0
A FOSS convolution reverb plugin
UIGraphBlock.cpp
1 /*
2  ==============================================================================
3 
4  UIGraphBlock.cpp
5 
6  ==============================================================================
7 */
8 
9 #include "UIGraphBlock.h"
10 #include "PluginEditor.h"
11 
12 namespace reverb
13 {
14 
15  //==============================================================================
16  /**
17  * @brief Constructs a UIGraphBlock object
18  *
19  * Creates a UIGraphBlock and each of its components. Contains the IR graph which
20  * is updated periodically and on notify in a separate thread.
21  */
23  : processor(processor), juce::GroupComponent("IR Graph", "waveform - impulse response")
24  {
25  graphThread.reset(new std::thread(&UIGraphBlock::updateGraph, this, 100));
26  }
27 
28  //==============================================================================
29  /**
30  * @brief Destroys a UIGraphBlock object
31  *
32  * Sends the stop signal to the graph thread
33  */
35  {
36  stopGraphThread = true;
37  mustUpdate.notify_all();
38 
39  graphThread->join();
40  }
41 
42  //==============================================================================
43  void UIGraphBlock::paint(juce::Graphics& g)
44  {
45  g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
46 
47  g.setColour(juce::Colours::white);
48  g.setFont(15.0f);
49 
50  g.setColour(juce::Colour(0xddffffff));
51 
52  for (auto& irPlot : irChannelPlots)
53  {
54  g.strokePath(irPlot, juce::PathStrokeType(1.0f));
55  }
56 
57  juce::Rectangle<int> bounds(getLocalBounds());
58  getLookAndFeel().drawGroupComponentOutline(g, bounds.getWidth(), bounds.getHeight(), getText(),
59  juce::Justification(juce::Justification::centredTop), *this);
60  }
61 
62  //==============================================================================
63  /**
64  * @brief Manages the layout of UIGraphBlock when the block is resized
65  *
66  * This function defines all the relative positioning of the various UIGraphBlock
67  * elements.
68  */
70  {
71  juce::Rectangle<int> bounds(getLocalBounds());
72  int padding = (int)std::round(bounds.getHeight() * AudioProcessorEditor::PADDING_REL);
73 
74  bounds.reduce(4*padding, 4*padding);
75 
76  // Prepare coordinates
77  float x = (float)bounds.getX();
78  float width = (float)bounds.getWidth();
79 
80  float height = (float)bounds.getHeight();
81  float yCentre = (float)bounds.getCentreY();
82 
83  // Read one sample per GRAPH_TIME_STEP_S per IR
84  irChannelPlots.clear();
85 
86  for (int channel = 0; channel < processor.mainPipelines.size(); ++channel)
87  {
88  // Get IR and sample rate
89  AudioBlock ir = processor.mainPipelines[channel]->ir;
90  double sampleRate = processor.mainPipelines[channel]->sampleRate;
91 
92  int samplesPerStep = (int)std::ceil(GRAPH_TIME_STEP_S * sampleRate);
93  int numSteps = (int)std::ceil(GRAPH_TOTAL_TIME_S / GRAPH_TIME_STEP_S);
94 
95  float xScale = width / numSteps;
96  float yScale = height;
97 
98  // Create new plot
99  irChannelPlots.emplace_back();
100  auto& irPlot = irChannelPlots.back();
101 
102  float * irSamples = ir.getChannelPointer(0);
103 
104  // Preallocate memory for efficiency
105  irPlot.preallocateSpace(numSteps);
106 
107  // Prologue: begin path at first sample
108  irPlot.startNewSubPath(x, irSamples[0] + yCentre);
109 
110  // Retain maxima for each step
111  int numStepsInIR = (int)ir.getNumSamples() / samplesPerStep;
112  std::vector<float> maxValues(numStepsInIR, 0.0f);
113 
114  auto cmpAbsMax = [](float f1, float f2) { return std::abs(f1) < std::abs(f2); };
115 
116  for (int step = 0; step < numStepsInIR; ++step)
117  {
118  float * start = irSamples + step * samplesPerStep;
119  float * end = start + samplesPerStep;
120 
121  maxValues[step] = *std::max_element(start, end, cmpAbsMax);
122  }
123 
124  // Draw one sample per step
125  for (int step = 0; step < numStepsInIR; step++)
126  {
127  irPlot.lineTo(x + step * xScale, yCentre + maxValues[step] * yScale);
128  }
129 
130  // Epilogue: draw remaining 0-value points
131  irPlot.lineTo(x + numStepsInIR * xScale, yCentre);
132  irPlot.lineTo(x + numSteps * xScale, yCentre);
133  }
134  }
135 
136  //==============================================================================
137  /**
138  * @brief Updates IR graph
139  *
140  * Worker thread to update IR when condition variable is notified. Ends once
141  * stopGraphThread is set to true and once notified through mustUpdate.
142  *
143  * @param [in] updatePeriodMs Lazy update period to catch any missed parameter
144  changes
145  */
146  void UIGraphBlock::updateGraph(int updatePeriodMs)
147  {
148  std::unique_lock<std::mutex> lock(updatingGraph);
149 
150  while (!stopGraphThread)
151  {
152  // Lock JUCE message manager (necessary for repaint() method)
153  juce::Component::SafePointer<juce::Component> sp(this);
154 
155  // Run update mechanism on message manager thread
156  juce::MessageManager::callAsync(
157  [sp]
158  {
159  if (!sp.getComponent())
160  {
161  return;
162  }
163 
164  UIGraphBlock * gb = dynamic_cast<UIGraphBlock*>(sp.getComponent());
165 
166  if (!gb)
167  {
168  return;
169  }
170 
171  // Wait until parameter processing is complete
172  std::lock_guard<std::mutex> paramsLock(gb->getProcessorInstance()->updatingParams);
173 
174  // Update graph and send repaint signal
175  std::unique_lock<std::mutex> lock(gb->updatingGraph);
176 
177  gb->resized();
178  gb->repaint();
179 
180  // Notify update thread about task completion
181  gb->updateComplete.notify_all();
182  }
183  );
184 
185  // Wait for graph update completion (time out after update period)
186  updateComplete.wait_for(lock, std::chrono::milliseconds(updatePeriodMs));
187 
188  // Wait for new notification
189  mustUpdate.wait_for(lock, std::chrono::milliseconds(updatePeriodMs));
190  }
191  }
192 
193  /**
194  * @brief Signals thread to update graph
195  *
196  * Sends a notification to the worker thread through the mustUpdate condition
197  * variable.
198  */
200  {
201  mustUpdate.notify_one();
202  }
203 
204 }
float getParam(const juce::AudioProcessorValueTreeState &params, const juce::String &blockId) const
Internal method used to get (and check) a parameter&#39;s value.
Definition: Task.h:111
~UIGraphBlock()
Destroys a UIGraphBlock object.
void notifyToUpdate()
Signals thread to update graph.
void resized() override
Manages the layout of UIGraphBlock when the block is resized.
UIGraphBlock(AudioProcessor &processor)
Constructs a UIGraphBlock object.
void updateGraph(int updatePeriodMs)
Updates IR graph.