VU Meter
About VU and Peak Metering
VU metering and peak metering are two types of audio level meters commonly used in recording studios and live sound applications. VU stands for Volume Units, and the meter displays the signal level in decibels relative to a reference level. VU meters measure the average level of an audio signal over time, providing a visual representation of the perceived loudness of the signal. Peak meters, on the other hand, measure the highest level of an audio signal, indicating the maximum level that the signal has reached. While VU meters are useful for monitoring overall levels and ensuring that a signal is not too quiet or too loud, peak meters are essential for preventing distortion and clipping, which can occur when a signal exceeds the maximum level that a system can handle. Together, VU and peak metering provide a comprehensive view of an audio signal's level and dynamics, allowing engineers to make informed decisions about gain staging and signal processing.
Switchboard Editor example
This example plays an audio file and calculates the RMS level and amplitude peak value of the audio signal real-time by using a VU meter node.
Code Example
- JSON
- Swift
- Kotlin
- C++
- JavaScript
{
"nodes": {
{ "id": "audioPlayerNode", "type": "AudioPlayerNode" },
{ "id": "splitterNode", "type": "BusSplitterNode" },
{ "id": "multiChannelToMonoNode", "type": "MultiChannelToMonoNode" },
{ "id": "vuMeterNode", "type": "VUMeterNode" }
},
"connections": {
{ "sourceNode": "audioPlayerNode", "destinationNode": "splitterNode" },
{ "sourceNode": "splitterNode", "destinationNode": "multiChannelToMonoNode" },
{ "sourceNode": "multiChannelToMonoNode", "destinationNode": "vuMeterNode" },
{ "sourceNode": "splitterNode", "destinationNode": "OutputNode" }
},
"tracks": {
"audioPlayerNode": "example.mp3"
}
}
import SwitchboardSDK
class VUMeterExample {
let audioEngine = SBAudioEngine()
let audioGraph = SBAudioGraph()
let audioPlayerNode = SBAudioPlayerNode()
let splitterNode = SBBusSplitterNode()
let multiChannelToMonoNode = SBMultiChannelToMonoNode()
let vuMeterNode = SBVUMeterNode()
init() {
audioGraph.addNode(audioPlayerNode)
audioGraph.addNode(splitterNode)
audioGraph.addNode(multiChannelToMonoNode)
audioGraph.addNode(vuMeterNode)
audioGraph.connect(audioPlayerNode, to: splitterNode)
audioGraph.connect(splitterNode, to: multiChannelToMonoNode)
audioGraph.connect(multiChannelToMonoNode, to: vuMeterNode)
audioGraph.connect(splitterNode, to: audioGraph.outputNode)
audioPlayerNode.load("example.mp3", withFormat: .mp3)
audioPlayerNode.isLoopingEnabled = true
}
func start() {
audioEngine.start(audioGraph)
audioPlayerNode.play()
}
func stop() {
audioPlayerNode.stop()
audioEngine.stop()
}
var level: Float {
vuMeterNode.level
}
var peak: Float {
vuMeterNode.peak
}
}
import com.synervoz.switchboard.sdk.AudioEngine
import com.synervoz.switchboard.sdk.Codec
import com.synervoz.switchboard.sdk.audiograph.AudioGraph
import com.synervoz.switchboard.sdk.audiographnodes.AudioPlayerNode
import com.synervoz.switchboard.sdk.audiographnodes.BusSplitterNode
import com.synervoz.switchboard.sdk.audiographnodes.MultiChannelToMonoNode
import com.synervoz.switchboard.sdk.audiographnodes.VUMeterNode
class VUMeterExample(context: Context) {
val audioEngine = AudioEngine(context)
val audioGraph = AudioGraph()
val audioPlayerNode = AudioPlayerNode()
val splitterNode = BusSplitterNode()
val multiChannelToMonoNode = MultiChannelToMonoNode()
val vuMeterNode = VUMeterNode()
init() {
audioGraph.addNode(audioPlayerNode)
audioGraph.addNode(splitterNode)
audioGraph.addNode(multiChannelToMonoNode)
audioGraph.addNode(vuMeterNode)
audioGraph.connect(audioPlayerNode, splitterNode)
audioGraph.connect(splitterNode, multiChannelToMonoNode)
audioGraph.connect(multiChannelToMonoNode, vuMeterNode)
audioGraph.connect(splitterNode, audioGraph.outputNode)
audioPlayerNode.load("example.mp3", Codec.MP3)
audioPlayerNode.isLoopingEnabled = true
}
fun start() {
audioEngine.start(audioGraph)
audioPlayerNode.play()
}
fun stop() {
audioPlayerNode.stop()
audioEngine.stop()
}
val level: Float
get() = vuMeterNode.level
val peak: Float
get() = vuMeterNode.peak
}
#include "AudioGraph.hpp"
#include "AudioPlayerNode.hpp"
#include "BusSplitterNode.hpp"
#include "MultiChannelToMonoNode.hpp"
#include "VUMeterNode.hpp"
using namespace switchboard;
class VUMeterExample {
public:
VUMeterExample() {
// Adding nodes to audio graph
audioGraph.addNode(audioPlayerNode);
audioGraph.addNode(splitterNode);
audioGraph.addNode(multiChannelToMonoNode);
audioGraph.addNode(vuMeterNode);
// Connecting audio nodes
audioGraph.connect(audioPlayerNode, splitterNode);
audioGraph.connect(splitterNode, multiChannelToMonoNode);
audioGraph.connect(multiChannelToMonoNode, vuMeterNode);
audioGraph.connect(splitterNode, audioGraph.getOutputNode());
// Loading the track and starting the player
audioPlayerNode.load("example.mp3", ::Mp3);
audioPlayerNode.setLoopingEnabled(true);
audioPlayerNode.play();
// Starting the graph
audioGraph.start();
}
float getLevel() const {
return vuMeterNode.getLevel();
}
float getPeak() const {
return vuMeterNode.getPeak();
}
// Example method called by the audio processing pipeline on each buffer
bool process(float** buffers, const uint numberOfChannels, const uint numberOfFrames, const uint sampleRate) {
AudioBuffer<float> outBuffer = AudioBuffer<float>(numberOfChannels, numberOfFrames, false, sampleRate, buffers);
const bool result = audioGraph->processBuffer(nullptr, &outBuffer);
return result;
}
private:
AudioGraph audioGraph;
AudioPlayerNode audioPlayerNode;
BusSplitterNode splitterNode;
MultiChannelToMonoNode multiChannelToMonoNode
VUMeterNode vuMeterNode;
};
// Change the import to reflect the location of library
// relative to this publically accessible AudioWorklet
import { SwitchboardSDK } from '/libs/switchboard-sdk/index.js'
class VUMeterWorkletProcessor extends AudioWorkletProcessor {
constructor(options) {
super()
console.log(options)
this.port.onmessage = (e) => {
let event = e.data.event
if (event === 'wasmLoaded') {
let sdkConfig = e.data.data
console.log('VUMeterWorkletProcessor - wasmLoaded', sdkConfig)
this.configure(sdkConfig)
} else if (event === 'destruct') {
console.log('VUMeterWorkletProcessor - destruct')
this.destruct()
} else if (event === 'requestVUMeterLevels') {
this.sendMessage({
event: 'vuMeterLevelsData',
data: {
level: this.vuMeterNode.getLevel(),
peak: this.vuMeterNode.getPeak(),
},
})
}
}
}
sendMessage(message, transfer = []) {
this.port.postMessage(message, transfer)
}
configure(sdkConfig) {
this.switchboard = new SwitchboardSDK()
this.switchboard.configure(sdkConfig).then((response) => {
this.constructAudioGraph()
})
}
constructAudioGraph() {
const inputChannelLayout = [2]
const outputChannelLayout = [2]
const sampleRate = 48000
const maxNumFrames = 128
let audioGraph = this.switchboard.createAudioGraph(
inputChannelLayout,
outputChannelLayout,
maxNumFrames,
sampleRate
)
let audioGraphInputNode = audioGraph.getInputNode()
let audioGraphOutputNode = audioGraph.getOutputNode()
let multiChannelToMonoNode =
new this.switchboard.classes.MultiChannelToMonoNode()
let musicSplitterNode = new this.switchboard.classes.BusSplitterNode()
let vuMeterNode = new this.switchboard.classes.VUMeterNode()
audioGraph.addNode(multiChannelToMonoNode)
audioGraph.addNode(musicSplitterNode)
audioGraph.addNode(vuMeterNode)
audioGraph.connect(audioGraphInputNode, musicSplitterNode)
audioGraph.connect(musicSplitterNode, multiChannelToMonoNode)
audioGraph.connect(multiChannelToMonoNode, vuMeterNode)
audioGraph.connect(musicSplitterNode, audioGraphOutputNode)
audioGraph.start()
this.audioGraph = audioGraph
this.multiChannelToMonoNode = multiChannelToMonoNode
this.musicSplitterNode = musicSplitterNode
this.vuMeterNode = vuMeterNode
}
destruct() {
this.audioGraph.destruct()
this.multiChannelToMonoNode.destruct()
this.musicSplitterNode.destruct()
this.vuMeterNode.destruct()
}
process(inputs, outputs, parameters) {
return this.audioGraph.processGraph(inputs, outputs)
}
}
registerProcessor('vu-meter-worklet-processor', VUMeterWorkletProcessor)