Skip to content

Commit 683bf84

Browse files
committed
More metering goodness
1 parent e648688 commit 683bf84

24 files changed

Lines changed: 4179 additions & 12 deletions

examples/graphics/source/examples/AudioFileDemo.h

Lines changed: 256 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,92 @@ class AudioFileWaveform : public yup::AudioViewComponent
127127

128128
//==============================================================================
129129

130+
/**
131+
Wraps an AudioSource and taps the audio stream to send to a KMeterState.
132+
*/
133+
class MeteringAudioSource : public yup::PositionableAudioSource
134+
{
135+
public:
136+
MeteringAudioSource (yup::PositionableAudioSource* sourceToWrap, yup::KMeterState& meterState)
137+
: source (sourceToWrap)
138+
, meter (meterState)
139+
{
140+
}
141+
142+
void prepareToPlay (int samplesPerBlockExpected, double newSampleRate) override
143+
{
144+
if (source != nullptr)
145+
source->prepareToPlay (samplesPerBlockExpected, newSampleRate);
146+
147+
meter.prepare (newSampleRate, 2);
148+
}
149+
150+
void releaseResources() override
151+
{
152+
if (source != nullptr)
153+
source->releaseResources();
154+
}
155+
156+
void getNextAudioBlock (const yup::AudioSourceChannelInfo& bufferToFill) override
157+
{
158+
if (source != nullptr)
159+
{
160+
source->getNextAudioBlock (bufferToFill);
161+
162+
// Tap the audio and push to meter
163+
const int numChannels = yup::jmin (bufferToFill.buffer->getNumChannels(), 2);
164+
if (numChannels > 0 && bufferToFill.numSamples > 0)
165+
{
166+
const float* channels[2] = { nullptr, nullptr };
167+
for (int i = 0; i < numChannels; ++i)
168+
channels[i] = bufferToFill.buffer->getReadPointer (i, bufferToFill.startSample);
169+
170+
meter.pushSamples (channels, numChannels, bufferToFill.numSamples);
171+
meter.processPendingAudio();
172+
}
173+
}
174+
else
175+
{
176+
bufferToFill.clearActiveBufferRegion();
177+
}
178+
}
179+
180+
void setNextReadPosition (yup::int64 newPosition) override
181+
{
182+
if (source != nullptr)
183+
source->setNextReadPosition (newPosition);
184+
}
185+
186+
yup::int64 getNextReadPosition() const override
187+
{
188+
return source != nullptr ? source->getNextReadPosition() : 0;
189+
}
190+
191+
yup::int64 getTotalLength() const override
192+
{
193+
return source != nullptr ? source->getTotalLength() : 0;
194+
}
195+
196+
bool isLooping() const override
197+
{
198+
return source != nullptr ? source->isLooping() : false;
199+
}
200+
201+
void setLooping (bool shouldLoop) override
202+
{
203+
if (source != nullptr)
204+
source->setLooping (shouldLoop);
205+
}
206+
207+
private:
208+
yup::PositionableAudioSource* source;
209+
yup::KMeterState& meter;
210+
211+
YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MeteringAudioSource)
212+
};
213+
214+
//==============================================================================
215+
130216
/**
131217
Demonstrates loading, visualizing, playing, and exporting audio files.
132218
*/
@@ -143,13 +229,22 @@ class AudioFileDemo : public yup::Component
143229
, loopButton ("Loop")
144230
, labelsButton ("Labels")
145231
, waveformDisplay (waveformCache)
232+
, meterState (48000.0, 2)
233+
, leftMeter (meterState, 0)
234+
, rightMeter (meterState, 1)
235+
, meteringSource (&transportSource, meterState)
146236
{
147237
formatManager.registerDefaultFormats (
148238
yup::AudioFormatType::all & ~yup::AudioFormatType::coreAudio);
149239

150240
deviceManager.initialiseWithDefaultDevices (0, 2);
151241
deviceManager.addAudioCallback (&sourcePlayer);
152-
sourcePlayer.setSource (&transportSource);
242+
sourcePlayer.setSource (&meteringSource);
243+
244+
// Configure K-Meters
245+
meterState.setScale (yup::KMeterState::Scale::k20);
246+
leftMeter.setShowPeakHold (true);
247+
rightMeter.setShowPeakHold (true);
153248

154249
// Configure the waveform cache
155250
waveformCache->setThreadPool (&waveformThreadPool);
@@ -161,6 +256,7 @@ class AudioFileDemo : public yup::Component
161256
{
162257
stopPlayback();
163258
transportSource.setSource (nullptr);
259+
meteringSource.setLooping (false);
164260
sourcePlayer.setSource (nullptr);
165261
deviceManager.removeAudioCallback (&sourcePlayer);
166262
deviceManager.closeAudioDevice();
@@ -169,12 +265,15 @@ class AudioFileDemo : public yup::Component
169265
void resized() override
170266
{
171267
auto bounds = getLocalBounds().reduced (8);
172-
auto header = bounds.removeFromTop (38);
268+
auto header = bounds.removeFromTop (122);
173269

174270
const int buttonHeight = 28;
175271
const int buttonWidth = 100;
272+
const int smallButtonWidth = 60;
273+
const int mediumButtonWidth = 85;
176274
const int buttonMargin = 6;
177275

276+
// First row of buttons
178277
auto buttonRow = header.removeFromTop (buttonHeight);
179278
loadButton.setBounds (buttonRow.removeFromLeft (buttonWidth));
180279
buttonRow.removeFromLeft (buttonMargin);
@@ -188,11 +287,48 @@ class AudioFileDemo : public yup::Component
188287
buttonRow.removeFromLeft (buttonMargin);
189288
labelsButton.setBounds (buttonRow.removeFromLeft (buttonWidth));
190289

290+
// Second row for K-scale buttons
291+
header.removeFromTop (4);
292+
auto scaleRow = header.removeFromTop (buttonHeight);
293+
k20Button.setBounds (scaleRow.removeFromLeft (smallButtonWidth));
294+
scaleRow.removeFromLeft (buttonMargin);
295+
k14Button.setBounds (scaleRow.removeFromLeft (smallButtonWidth));
296+
scaleRow.removeFromLeft (buttonMargin);
297+
k12Button.setBounds (scaleRow.removeFromLeft (smallButtonWidth));
298+
299+
// Third row for metering standard buttons
300+
header.removeFromTop (4);
301+
auto standardRow = header.removeFromTop (buttonHeight);
302+
rmsButton.setBounds (standardRow.removeFromLeft (mediumButtonWidth));
303+
standardRow.removeFromLeft (buttonMargin);
304+
ituButton.setBounds (standardRow.removeFromLeft (mediumButtonWidth));
305+
standardRow.removeFromLeft (buttonMargin);
306+
ebuButton.setBounds (standardRow.removeFromLeft (mediumButtonWidth));
307+
308+
// Fourth row for over counter mode buttons
309+
header.removeFromTop (4);
310+
auto modeRow = header.removeFromTop (buttonHeight);
311+
contiguousButton.setBounds (modeRow.removeFromLeft (mediumButtonWidth));
312+
modeRow.removeFromLeft (buttonMargin);
313+
totalButton.setBounds (modeRow.removeFromLeft (mediumButtonWidth));
314+
191315
bounds.removeFromTop (6);
192316
infoLabel.setBounds (bounds.removeFromTop (22));
193317
statusLabel.setBounds (bounds.removeFromTop (22));
194318
bounds.removeFromTop (6);
195319

320+
// Reserve space for K-Meters on the right
321+
const int meterWidth = 60;
322+
const int meterGap = 8;
323+
const int meterSectionWidth = (meterWidth * 2) + (meterGap * 3);
324+
325+
auto meterArea = bounds.removeFromRight (meterSectionWidth);
326+
leftMeter.setBounds (meterArea.removeFromLeft (meterWidth));
327+
meterArea.removeFromLeft (meterGap);
328+
rightMeter.setBounds (meterArea.removeFromLeft (meterWidth));
329+
330+
// Rest is for waveform
331+
bounds.removeFromRight (meterGap);
196332
waveformDisplay.setBounds (bounds);
197333
}
198334

@@ -218,6 +354,9 @@ class AudioFileDemo : public yup::Component
218354
private:
219355
void setupUi()
220356
{
357+
addAndMakeVisible (leftMeter);
358+
addAndMakeVisible (rightMeter);
359+
221360
addAndMakeVisible (loadButton);
222361
loadButton.onClick = [this]
223362
{
@@ -267,6 +406,107 @@ class AudioFileDemo : public yup::Component
267406
waveformDisplay.setChannelLabelsVisible (labelsButton.getToggleState());
268407
};
269408

409+
// K-Scale selection buttons (manual radio button behavior)
410+
addAndMakeVisible (k20Button);
411+
k20Button.setButtonText ("K-20");
412+
k20Button.setToggleState (true, yup::NotificationType::dontSendNotification);
413+
k20Button.onClick = [this]
414+
{
415+
k20Button.setToggleState (true, yup::NotificationType::dontSendNotification);
416+
k14Button.setToggleState (false, yup::NotificationType::dontSendNotification);
417+
k12Button.setToggleState (false, yup::NotificationType::dontSendNotification);
418+
meterState.setScale (yup::KMeterState::Scale::k20);
419+
leftMeter.repaint();
420+
rightMeter.repaint();
421+
};
422+
423+
addAndMakeVisible (k14Button);
424+
k14Button.setButtonText ("K-14");
425+
k14Button.setToggleState (false, yup::NotificationType::dontSendNotification);
426+
k14Button.onClick = [this]
427+
{
428+
k20Button.setToggleState (false, yup::NotificationType::dontSendNotification);
429+
k14Button.setToggleState (true, yup::NotificationType::dontSendNotification);
430+
k12Button.setToggleState (false, yup::NotificationType::dontSendNotification);
431+
meterState.setScale (yup::KMeterState::Scale::k14);
432+
leftMeter.repaint();
433+
rightMeter.repaint();
434+
};
435+
436+
addAndMakeVisible (k12Button);
437+
k12Button.setButtonText ("K-12");
438+
k12Button.setToggleState (false, yup::NotificationType::dontSendNotification);
439+
k12Button.onClick = [this]
440+
{
441+
k20Button.setToggleState (false, yup::NotificationType::dontSendNotification);
442+
k14Button.setToggleState (false, yup::NotificationType::dontSendNotification);
443+
k12Button.setToggleState (true, yup::NotificationType::dontSendNotification);
444+
meterState.setScale (yup::KMeterState::Scale::k12);
445+
leftMeter.repaint();
446+
rightMeter.repaint();
447+
};
448+
449+
// Metering standard selection buttons
450+
addAndMakeVisible (rmsButton);
451+
rmsButton.setButtonText ("RMS Flat");
452+
rmsButton.setToggleState (true, yup::NotificationType::dontSendNotification);
453+
rmsButton.onClick = [this]
454+
{
455+
rmsButton.setToggleState (true, yup::NotificationType::dontSendNotification);
456+
ituButton.setToggleState (false, yup::NotificationType::dontSendNotification);
457+
ebuButton.setToggleState (false, yup::NotificationType::dontSendNotification);
458+
meterState.setMeteringStandard (yup::KMeterState::MeteringStandard::rmsFlat);
459+
leftMeter.repaint();
460+
rightMeter.repaint();
461+
};
462+
463+
addAndMakeVisible (ituButton);
464+
ituButton.setButtonText ("ITU BS.1770-4");
465+
ituButton.setToggleState (false, yup::NotificationType::dontSendNotification);
466+
ituButton.onClick = [this]
467+
{
468+
rmsButton.setToggleState (false, yup::NotificationType::dontSendNotification);
469+
ituButton.setToggleState (true, yup::NotificationType::dontSendNotification);
470+
ebuButton.setToggleState (false, yup::NotificationType::dontSendNotification);
471+
meterState.setMeteringStandard (yup::KMeterState::MeteringStandard::ituBS1770_4);
472+
leftMeter.repaint();
473+
rightMeter.repaint();
474+
};
475+
476+
addAndMakeVisible (ebuButton);
477+
ebuButton.setButtonText ("EBU R128");
478+
ebuButton.setToggleState (false, yup::NotificationType::dontSendNotification);
479+
ebuButton.onClick = [this]
480+
{
481+
rmsButton.setToggleState (false, yup::NotificationType::dontSendNotification);
482+
ituButton.setToggleState (false, yup::NotificationType::dontSendNotification);
483+
ebuButton.setToggleState (true, yup::NotificationType::dontSendNotification);
484+
meterState.setMeteringStandard (yup::KMeterState::MeteringStandard::ebuR128);
485+
leftMeter.repaint();
486+
rightMeter.repaint();
487+
};
488+
489+
// Over counter mode selection buttons
490+
addAndMakeVisible (contiguousButton);
491+
contiguousButton.setButtonText ("Contiguous");
492+
contiguousButton.setToggleState (true, yup::NotificationType::dontSendNotification);
493+
contiguousButton.onClick = [this]
494+
{
495+
contiguousButton.setToggleState (true, yup::NotificationType::dontSendNotification);
496+
totalButton.setToggleState (false, yup::NotificationType::dontSendNotification);
497+
meterState.setOverCounterMode (yup::KMeterState::OverCounterMode::contiguous);
498+
};
499+
500+
addAndMakeVisible (totalButton);
501+
totalButton.setButtonText ("Total");
502+
totalButton.setToggleState (false, yup::NotificationType::dontSendNotification);
503+
totalButton.onClick = [this]
504+
{
505+
contiguousButton.setToggleState (false, yup::NotificationType::dontSendNotification);
506+
totalButton.setToggleState (true, yup::NotificationType::dontSendNotification);
507+
meterState.setOverCounterMode (yup::KMeterState::OverCounterMode::total);
508+
};
509+
270510
addAndMakeVisible (infoLabel);
271511
infoLabel.setText ("No audio loaded.", yup::NotificationType::dontSendNotification);
272512
infoLabel.setColor (yup::Label::Style::textFillColorId, yup::Colors::white);
@@ -454,11 +694,25 @@ class AudioFileDemo : public yup::Component
454694
yup::TextButton saveButton;
455695
yup::ToggleButton loopButton;
456696
yup::ToggleButton labelsButton;
697+
yup::ToggleButton k20Button;
698+
yup::ToggleButton k14Button;
699+
yup::ToggleButton k12Button;
700+
yup::ToggleButton rmsButton;
701+
yup::ToggleButton ituButton;
702+
yup::ToggleButton ebuButton;
703+
yup::ToggleButton contiguousButton;
704+
yup::ToggleButton totalButton;
457705

458706
yup::Label infoLabel;
459707
yup::Label statusLabel;
460708
AudioFileWaveform waveformDisplay;
461709

710+
// K-Meter components
711+
yup::KMeterState meterState;
712+
yup::KMeterComponent leftMeter;
713+
yup::KMeterComponent rightMeter;
714+
MeteringAudioSource meteringSource;
715+
462716
yup::String currentFileName { "No audio loaded" };
463717
double loadedSampleRate = 0.0;
464718
double audioLengthSeconds = 0.0;

0 commit comments

Comments
 (0)