Skip to content

Commit

Permalink
Basic gain metering
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-thompson committed Jul 4, 2019
1 parent 0d5cb4b commit 17f43ed
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 4 deletions.
14 changes: 14 additions & 0 deletions examples/GainPlugin/Source/PluginEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ GainPluginAudioProcessorEditor::GainPluginAudioProcessorEditor (GainPluginAudioP
p->addListener(this);
p->sendValueChangedMessageToListeners(p->getValue());
}

// Lastly, start our timer for reporting meter values
startTimerHz(30);

// And of course set our editor size before we're done.
setResizable(true, true);
Expand All @@ -76,6 +79,8 @@ GainPluginAudioProcessorEditor::GainPluginAudioProcessorEditor (GainPluginAudioP

GainPluginAudioProcessorEditor::~GainPluginAudioProcessorEditor()
{
stopTimer();

// Tear down parameter listeners
for (auto& p : processor.getParameters())
p->removeListener(this);
Expand Down Expand Up @@ -119,3 +124,12 @@ void GainPluginAudioProcessorEditor::parameterValueChanged (int parameterIndex,
stringValue);
});
}

void GainPluginAudioProcessorEditor::timerCallback()
{
appRoot.dispatchEvent(
"gainPeakValues",
processor.getLeftChannelPeak(),
processor.getRightChannelPeak()
);
}
6 changes: 4 additions & 2 deletions examples/GainPlugin/Source/PluginEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@
*/
class GainPluginAudioProcessorEditor
: public AudioProcessorEditor,
public AudioProcessorParameter::Listener
public AudioProcessorParameter::Listener,
public Timer
{
public:
GainPluginAudioProcessorEditor (GainPluginAudioProcessor&);
~GainPluginAudioProcessorEditor();

//==============================================================================
void parameterValueChanged (int parameterIndex, float newValue) override;
void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override {}
void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override {}
void timerCallback() override;

//==============================================================================
void paint (Graphics&) override;
Expand Down
10 changes: 9 additions & 1 deletion examples/GainPlugin/Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,16 @@ void GainPluginAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuf
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());

// Our intense dsp processing
gain.setValue(*params.getRawParameterValue("MainGain"));
gain.applyGain(buffer, buffer.getNumSamples());
gain.applyGain(buffer, buffer.getNumSamples());

// We'll also report peak values for our meter. This isn't an ideal way to do this
// as the rate between the audio processing callback and the timer on which the
// editor reads these values could mean missing peaks in the visual display, but
// this is a simple example plugin so let's not worry about it.
lcPeak.store(buffer.getMagnitude(0, 0, buffer.getNumSamples()));
rcPeak.store(buffer.getMagnitude(1, 0, buffer.getNumSamples()));
}

//==============================================================================
Expand Down
5 changes: 4 additions & 1 deletion examples/GainPlugin/Source/PluginProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ class GainPluginAudioProcessor : public AudioProcessor
void setStateInformation (const void* data, int sizeInBytes) override;

//==============================================================================
AudioProcessorValueTreeState& getValueTreeState() { return params; }
AudioProcessorValueTreeState& getValueTreeState() { return params; }
float getLeftChannelPeak() { return lcPeak; }
float getRightChannelPeak() { return rcPeak; }

private:
//==============================================================================
AudioProcessorValueTreeState params;
LinearSmoothedValue<float> gain;
std::atomic<float> lcPeak, rcPeak;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainPluginAudioProcessor)
Expand Down
7 changes: 7 additions & 0 deletions examples/GainPlugin/Source/jsui/src/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Label from './Label';
import Meter from './Meter';
import React, { Component } from 'react';
import Slider from './Slider';
import {
Expand All @@ -17,6 +18,7 @@ class App extends Component {
<Slider paramId="MainGain" {...styles.knob}>
<Label paramId="MainGain" {...styles.label} />
</Slider>
<Meter {...styles.meter} />
</View>
</View>
);
Expand Down Expand Up @@ -58,6 +60,11 @@ const styles = {
'align-items': 'center',
'interceptClickEvents': false,
},
meter: {
'flex': 0.0,
'width': 100.0,
'height': 16.0,
},
};

export default App;
102 changes: 102 additions & 0 deletions examples/GainPlugin/Source/jsui/src/Meter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { Component } from 'react';
import {
EventBridge,
Image,
Text,
View,
} from 'juce-blueprint';


class Meter extends Component {
constructor(props) {
super(props);

this._onMeasure = this._onMeasure.bind(this);
this._onMeterValues = this._onMeterValues.bind(this);

this.state = {
width: 0,
height: 0,
lcPeak: 0.0,
rcPeak: 0.0,
};
}

componentDidMount() {
EventBridge.addListener('gainPeakValues', this._onMeterValues);
}

componentWillUnmount() {
EventBridge.removeListener('gainPeakValues', this._onMeterValues);
}

_onMeterValues(lcPeak, rcPeak) {
this.setState({
lcPeak,
rcPeak,
});
}

_onMeasure(width, height) {
this.setState({
width: width,
height: height,
});
}

_renderVectorGraphics(lcPeak, rcPeak, width, height) {
// Similar to the audio side of this, this is a pretty rudimentary
// way of drawing a gain meter; we'd get a much nicer response by using
// a peak envelope follower with instant attack and a smooth release for
// each channel, but this is just a demo plugin.
return `
<svg
width="${width}"
height="${height}"
viewBox="0 0 ${width} ${height}"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
<rect
x="${0}" y="${0}"
width="${width}" height="${height * 0.45}"
fill="#626262" />
<rect
x="${0}" y="${0}"
width="${width * Math.min(1.0, lcPeak)}" height="${height * 0.45}"
fill="#66FDCF" />
<rect
x="${0}" y="${height * 0.5}"
width="${width}" height="${height * 0.45}"
fill="#626262" />
<rect
x="${0}" y="${height * 0.5}"
width="${width * Math.min(1.0, rcPeak)}" height="${height * 0.45}"
fill="#66FDCF" />
</svg>
`;
}

render() {
const {lcPeak, rcPeak, width, height} = this.state;

return (
<View {...this.props} onMeasure={this._onMeasure}>
<Image {...styles.canvas} source={this._renderVectorGraphics(lcPeak, rcPeak, width, height)} />
</View>
);
}
}

const styles = {
canvas: {
'flex': 1.0,
'height': '100%',
'width': '100%',
'position': 'absolute',
'left': 0.0,
'top': 0.0,
'interceptClickEvents': false,
},
};

export default Meter;

0 comments on commit 17f43ed

Please sign in to comment.