|
- /*
- ==============================================================================
-
- This file is part of the JUCE examples.
- Copyright (c) 2022 - Raw Material Software Limited
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
- WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
- PURPOSE, ARE DISCLAIMED.
-
- ==============================================================================
- */
-
- /*******************************************************************************
- The block below describes the properties of this PIP. A PIP is a short snippet
- of code that can be read by the Projucer and used to generate a JUCE project.
-
- BEGIN_JUCE_PIP_METADATA
-
- name: SurroundPlugin
- version: 1.0.0
- vendor: JUCE
- website: http://juce.com
- description: Surround audio plugin.
-
- dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
- juce_audio_plugin_client, juce_audio_processors,
- juce_audio_utils, juce_core, juce_data_structures,
- juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
- exporters: xcode_mac, vs2022, linux_make
-
- moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
-
- type: AudioProcessor
- mainClass: SurroundProcessor
-
- useLocalCopy: 1
-
- END_JUCE_PIP_METADATA
-
- *******************************************************************************/
-
- #pragma once
-
-
- //==============================================================================
- class ProcessorWithLevels : public AudioProcessor,
- private AsyncUpdater,
- private Timer
- {
- public:
- ProcessorWithLevels()
- : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
- .withInput ("Aux", AudioChannelSet::stereo(), false)
- .withOutput ("Output", AudioChannelSet::stereo())
- .withOutput ("Aux", AudioChannelSet::stereo(), false))
- {
- startTimerHz (60);
- applyBusLayouts (getBusesLayout());
- }
-
- ~ProcessorWithLevels() override
- {
- stopTimer();
- cancelPendingUpdate();
- }
-
- void prepareToPlay (double, int) override
- {
- samplesToPlay = (int) getSampleRate();
- reset();
- }
-
- void processBlock (AudioBuffer<float>& audio, MidiBuffer&) override { processAudio (audio); }
- void processBlock (AudioBuffer<double>& audio, MidiBuffer&) override { processAudio (audio); }
-
- void releaseResources() override { reset(); }
-
- float getLevel (int bus, int channel) const
- {
- return readableLevels[(size_t) getChannelIndexInProcessBlockBuffer (true, bus, channel)];
- }
-
- bool isBusesLayoutSupported (const BusesLayout& layouts) const override
- {
- const auto isSetValid = [] (const AudioChannelSet& set)
- {
- return ! set.isDisabled()
- && ! (set.isDiscreteLayout() && set.getChannelIndexForType (AudioChannelSet::discreteChannel0) == -1);
- };
-
- return isSetValid (layouts.getMainOutputChannelSet())
- && isSetValid (layouts.getMainInputChannelSet());
- }
-
- void reset() override
- {
- channelClicked = 0;
- samplesPlayed = samplesToPlay;
- }
-
- bool applyBusLayouts (const BusesLayout& layouts) override
- {
- // Some very badly-behaved hosts will call this during processing!
- const SpinLock::ScopedLockType lock (levelMutex);
-
- const auto result = AudioProcessor::applyBusLayouts (layouts);
-
- size_t numInputChannels = 0;
-
- for (auto i = 0; i < getBusCount (true); ++i)
- numInputChannels += (size_t) getBus (true, i)->getLastEnabledLayout().size();
-
- incomingLevels = readableLevels = std::vector<float> (numInputChannels, 0.0f);
-
- triggerAsyncUpdate();
- return result;
- }
-
- //==============================================================================
- const String getName() const override { return "Surround PlugIn"; }
- bool acceptsMidi() const override { return false; }
- bool producesMidi() const override { return false; }
- double getTailLengthSeconds() const override { return 0; }
-
- //==============================================================================
- int getNumPrograms() override { return 1; }
- int getCurrentProgram() override { return 0; }
- void setCurrentProgram (int) override {}
- const String getProgramName (int) override { return "None"; }
- void changeProgramName (int, const String&) override {}
-
- //==============================================================================
- void getStateInformation (MemoryBlock&) override {}
- void setStateInformation (const void*, int) override {}
-
- void channelButtonClicked (int bus, int channelIndex)
- {
- channelClicked = getChannelIndexInProcessBlockBuffer (false, bus, channelIndex);
- samplesPlayed = 0;
- }
-
- std::function<void()> updateEditor;
-
- private:
- void handleAsyncUpdate() override
- {
- NullCheckedInvocation::invoke (updateEditor);
- }
-
- template <typename Float>
- void processAudio (AudioBuffer<Float>& audio)
- {
- {
- SpinLock::ScopedTryLockType lock (levelMutex);
-
- if (lock.isLocked())
- {
- const auto numInputChannels = (size_t) getTotalNumInputChannels();
-
- for (size_t i = 0; i < numInputChannels; ++i)
- {
- const auto minMax = audio.findMinMax ((int) i, 0, audio.getNumSamples());
- const auto newMax = (float) std::max (std::abs (minMax.getStart()), std::abs (minMax.getEnd()));
-
- auto& toUpdate = incomingLevels[i];
- toUpdate = jmax (toUpdate, newMax);
- }
- }
- }
-
- audio.clear (0, audio.getNumSamples());
-
- auto fillSamples = jmin (samplesToPlay - samplesPlayed, audio.getNumSamples());
-
- if (isPositiveAndBelow (channelClicked, audio.getNumChannels()))
- {
- auto* channelBuffer = audio.getWritePointer (channelClicked);
- auto freq = (float) (440.0 / getSampleRate());
-
- for (auto i = 0; i < fillSamples; ++i)
- channelBuffer[i] += std::sin (MathConstants<float>::twoPi * freq * (float) samplesPlayed++);
- }
- }
-
- void timerCallback() override
- {
- const SpinLock::ScopedLockType lock (levelMutex);
-
- for (size_t i = 0; i < readableLevels.size(); ++i)
- readableLevels[i] = std::max (readableLevels[i] * 0.95f, std::exchange (incomingLevels[i], 0.0f));
- }
-
- SpinLock levelMutex;
- std::vector<float> incomingLevels;
- std::vector<float> readableLevels;
-
- int channelClicked;
- int samplesPlayed;
- int samplesToPlay;
- };
-
- //==============================================================================
- const Colour textColour = Colours::white.withAlpha (0.8f);
-
- inline void drawBackground (Component& comp, Graphics& g)
- {
- g.setColour (comp.getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker (0.8f));
- g.fillRoundedRectangle (comp.getLocalBounds().toFloat(), 4.0f);
- }
-
- inline void configureLabel (Label& label, const AudioProcessor::Bus* layout)
- {
- const auto text = layout != nullptr
- ? (layout->getName() + ": " + layout->getCurrentLayout().getDescription())
- : "";
- label.setText (text, dontSendNotification);
- label.setJustificationType (Justification::centred);
- label.setColour (Label::textColourId, textColour);
- }
-
- class InputBusViewer : public Component,
- private Timer
- {
- public:
- InputBusViewer (ProcessorWithLevels& proc, int busNumber)
- : processor (proc),
- bus (busNumber)
- {
- configureLabel (layoutName, processor.getBus (true, bus));
- addAndMakeVisible (layoutName);
-
- startTimerHz (60);
- }
-
- ~InputBusViewer() override
- {
- stopTimer();
- }
-
- void paint (Graphics& g) override
- {
- drawBackground (*this, g);
-
- auto* layout = processor.getBus (true, bus);
-
- if (layout == nullptr)
- return;
-
- const auto channelSet = layout->getCurrentLayout();
- const auto numChannels = channelSet.size();
-
- Grid grid;
-
- grid.autoFlow = Grid::AutoFlow::column;
- grid.autoColumns = grid.autoRows = Grid::TrackInfo (Grid::Fr (1));
- grid.items.insertMultiple (0, GridItem(), numChannels);
- grid.performLayout (getLocalBounds());
-
- const auto minDb = -50.0f;
- const auto maxDb = 6.0f;
-
- for (auto i = 0; i < numChannels; ++i)
- {
- g.setColour (Colours::orange.darker());
-
- const auto levelInDb = Decibels::gainToDecibels (processor.getLevel (bus, i), minDb);
- const auto fractionOfHeight = jmap (levelInDb, minDb, maxDb, 0.0f, 1.0f);
- const auto bounds = grid.items[i].currentBounds;
- const auto trackBounds = bounds.withSizeKeepingCentre (16, bounds.getHeight() - 10).toFloat();
- g.fillRect (trackBounds.withHeight (trackBounds.proportionOfHeight (fractionOfHeight)).withBottomY (trackBounds.getBottom()));
-
- g.setColour (textColour);
-
- g.drawText (channelSet.getAbbreviatedChannelTypeName (channelSet.getTypeOfChannel (i)),
- bounds,
- Justification::centredBottom);
- }
- }
-
- void resized() override
- {
- layoutName.setBounds (getLocalBounds().removeFromTop (20));
- }
-
- int getNumChannels() const
- {
- if (auto* b = processor.getBus (true, bus))
- return b->getCurrentLayout().size();
-
- return 0;
- }
-
- private:
- void timerCallback() override { repaint(); }
-
- ProcessorWithLevels& processor;
- int bus = 0;
- Label layoutName;
- };
-
- //==============================================================================
- class OutputBusViewer : public Component
- {
- public:
- OutputBusViewer (ProcessorWithLevels& proc, int busNumber)
- : processor (proc),
- bus (busNumber)
- {
- auto* layout = processor.getBus (false, bus);
-
- configureLabel (layoutName, layout);
- addAndMakeVisible (layoutName);
-
- if (layout == nullptr)
- return;
-
- const auto& channelSet = layout->getCurrentLayout();
-
- const auto numChannels = channelSet.size();
-
- for (auto i = 0; i < numChannels; ++i)
- {
- const auto channelName = channelSet.getAbbreviatedChannelTypeName (channelSet.getTypeOfChannel (i));
-
- channelButtons.emplace_back (channelName, channelName);
-
- auto& newButton = channelButtons.back();
- newButton.onClick = [&proc = processor, bus = bus, i] { proc.channelButtonClicked (bus, i); };
- addAndMakeVisible (newButton);
- }
-
- resized();
- }
-
- void paint (Graphics& g) override
- {
- drawBackground (*this, g);
- }
-
- void resized() override
- {
- auto b = getLocalBounds();
-
- layoutName.setBounds (b.removeFromBottom (20));
-
- Grid grid;
- grid.autoFlow = Grid::AutoFlow::column;
- grid.autoColumns = grid.autoRows = Grid::TrackInfo (Grid::Fr (1));
-
- for (auto& channelButton : channelButtons)
- grid.items.add (GridItem (channelButton));
-
- grid.performLayout (b.reduced (2));
- }
-
- int getNumChannels() const
- {
- if (auto* b = processor.getBus (false, bus))
- return b->getCurrentLayout().size();
-
- return 0;
- }
-
- private:
- ProcessorWithLevels& processor;
- int bus = 0;
- Label layoutName;
- std::list<TextButton> channelButtons;
- };
-
- //==============================================================================
- class SurroundEditor : public AudioProcessorEditor
- {
- public:
- explicit SurroundEditor (ProcessorWithLevels& parent)
- : AudioProcessorEditor (parent),
- customProcessor (parent),
- scopedUpdateEditor (customProcessor.updateEditor, [this] { updateGUI(); })
- {
- updateGUI();
- setResizable (true, true);
- }
-
- void resized() override
- {
- auto r = getLocalBounds();
- doLayout (inputViewers, r.removeFromTop (proportionOfHeight (0.5f)));
- doLayout (outputViewers, r);
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
- }
-
- private:
- template <typename Range>
- void doLayout (Range& range, Rectangle<int> bounds) const
- {
- FlexBox fb;
-
- for (auto& viewer : range)
- {
- if (viewer.getNumChannels() != 0)
- {
- fb.items.add (FlexItem (viewer)
- .withFlex ((float) viewer.getNumChannels())
- .withMargin (4.0f));
- }
- }
-
- fb.performLayout (bounds);
- }
-
- void updateGUI()
- {
- inputViewers.clear();
- outputViewers.clear();
-
- const auto inputBuses = getAudioProcessor()->getBusCount (true);
-
- for (auto i = 0; i < inputBuses; ++i)
- {
- inputViewers.emplace_back (customProcessor, i);
- addAndMakeVisible (inputViewers.back());
- }
-
- const auto outputBuses = getAudioProcessor()->getBusCount (false);
-
- for (auto i = 0; i < outputBuses; ++i)
- {
- outputViewers.emplace_back (customProcessor, i);
- addAndMakeVisible (outputViewers.back());
- }
-
- const auto channels = jmax (processor.getTotalNumInputChannels(),
- processor.getTotalNumOutputChannels());
- setSize (jmax (150, channels * 40), 200);
-
- resized();
- }
-
- ProcessorWithLevels& customProcessor;
- ScopedValueSetter<std::function<void()>> scopedUpdateEditor;
- std::list<InputBusViewer> inputViewers;
- std::list<OutputBusViewer> outputViewers;
- };
-
- //==============================================================================
- struct SurroundProcessor : public ProcessorWithLevels
- {
- AudioProcessorEditor* createEditor() override { return new SurroundEditor (*this); }
- bool hasEditor() const override { return true; }
- };
|