├── .gitattributes ├── .gitignore ├── APCompView.jucer ├── CompView test tone 96k.wav ├── LICENSE └── Source ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp └── PluginProcessor.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /APCompView.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /CompView test tone 96k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apmastering/CompView/540d1276d0731380f697faa631275ca01225acfd/CompView test tone 96k.wav -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Alain Paul 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission (including but not limited to the use of the AP Masteting brand name). 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PluginProcessor.h" 3 | #include "PluginEditor.h" 4 | #include 5 | 6 | //============================================================================== 7 | 8 | 9 | CompViewAudioProcessorEditor::CompViewAudioProcessorEditor(CompViewAudioProcessor& p) 10 | : AudioProcessorEditor(&p), audioProcessor(p) 11 | { 12 | refreshRate = 33; 13 | historySize = 350; 14 | currentIndex = 0; 15 | 16 | for (int i = 0; i < historySize; ++i) 17 | { 18 | peakHistoryLeft.add(0.0f); 19 | peakHistoryRight.add(0.0f); 20 | } 21 | 22 | setSize(1600, 800); 23 | startTimer(refreshRate); 24 | } 25 | 26 | CompViewAudioProcessorEditor::~CompViewAudioProcessorEditor() 27 | { 28 | } 29 | 30 | //============================================================================== 31 | void CompViewAudioProcessorEditor::paint(juce::Graphics& g) 32 | { 33 | float x1, x2, y1l, y2l, y1r, y2r; 34 | 35 | int canvasHeight = getHeight(); 36 | int canvasWidth = getWidth(); 37 | int thresholdY = canvasHeight * (1 - audioProcessor.triggerThreshold); 38 | 39 | g.fillAll(juce::Colours::whitesmoke); 40 | g.setColour(juce::Colours::black); 41 | g.setFont(20.0f); 42 | g.drawFittedText("AP Mastering - CompView", getLocalBounds(), juce::Justification::centredTop, 1); 43 | g.drawFittedText(juce::String(audioProcessor.countdown), getLocalBounds(), juce::Justification::topRight, 1); 44 | 45 | g.setColour(juce::Colour::fromRGB(240, 240, 240)); 46 | g.fillRect(offset, offset, canvasWidth - (offset * 2), canvasHeight - (offset * 2)); 47 | g.setColour(juce::Colour::fromRGB(0, 0, 0)); 48 | g.drawRect(offset, offset, canvasWidth - (offset * 2), canvasHeight - (offset * 2), 3); 49 | 50 | g.drawLine(offset, thresholdY + offsetY, canvasWidth - offset, thresholdY + offsetY, 1.0f); 51 | 52 | for (int i = 1; i < audioProcessor.peakLevelLeftSec.size(); ++i) 53 | { 54 | x1 = i + 20; 55 | x2 = i + 21; 56 | y1l = canvasHeight * (1 - audioProcessor.peakLevelLeftSec[(i - 1)]); 57 | y2l = canvasHeight * (1 - audioProcessor.peakLevelLeftSec[i]); 58 | y1r = canvasHeight * (1 - audioProcessor.peakLevelRightSec[(i - 1)]); 59 | y2r = canvasHeight * (1 - audioProcessor.peakLevelRightSec[i]); 60 | 61 | g.setColour(juce::Colours::red); 62 | g.drawLine(x1 + offset, y1l + offsetY, x2 + offset, y2l + offsetY, 2.0f); 63 | g.setColour(juce::Colours::blue); 64 | g.drawLine(x1 + offset, y1r + offsetY, x2 + offset, y2r + offsetY, 2.0f); 65 | } 66 | } 67 | 68 | 69 | void CompViewAudioProcessorEditor::resized() 70 | { 71 | } 72 | 73 | 74 | void CompViewAudioProcessorEditor::timerCallback() 75 | { 76 | if(audioProcessor.peakLevelLeftSec.size() > 0) { 77 | repaint(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | #include 6 | 7 | 8 | class CompViewAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer 9 | { 10 | public: 11 | CompViewAudioProcessorEditor (CompViewAudioProcessor&); 12 | ~CompViewAudioProcessorEditor() override; 13 | 14 | //============================================================================== 15 | void paint (juce::Graphics&) override; 16 | void resized() override; 17 | 18 | private: 19 | CompViewAudioProcessor& audioProcessor; 20 | void timerCallback() override; 21 | 22 | juce::Array peakHistoryLeft; 23 | juce::Array peakHistoryRight; 24 | 25 | int currentIndex; 26 | int historySize; 27 | int refreshRate; 28 | int historyClearedCount = 0; 29 | int offset = 24; 30 | int offsetY = 200; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CompViewAudioProcessorEditor) 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | //============================================================================== 5 | CompViewAudioProcessor::CompViewAudioProcessor() 6 | #ifndef JucePlugin_PreferredChannelConfigurations 7 | : AudioProcessor (BusesProperties() 8 | #if ! JucePlugin_IsMidiEffect 9 | #if ! JucePlugin_IsSynth 10 | .withInput ("Input", juce::AudioChannelSet::stereo(), true) 11 | #endif 12 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true) 13 | #endif 14 | ) 15 | #endif 16 | { 17 | } 18 | 19 | 20 | CompViewAudioProcessor::~CompViewAudioProcessor() 21 | { 22 | } 23 | 24 | //============================================================================== 25 | const juce::String CompViewAudioProcessor::getName() const 26 | { 27 | return JucePlugin_Name; 28 | } 29 | 30 | bool CompViewAudioProcessor::acceptsMidi() const 31 | { 32 | #if JucePlugin_WantsMidiInput 33 | return true; 34 | #else 35 | return false; 36 | #endif 37 | } 38 | 39 | bool CompViewAudioProcessor::producesMidi() const 40 | { 41 | #if JucePlugin_ProducesMidiOutput 42 | return true; 43 | #else 44 | return false; 45 | #endif 46 | } 47 | 48 | bool CompViewAudioProcessor::isMidiEffect() const 49 | { 50 | #if JucePlugin_IsMidiEffect 51 | return true; 52 | #else 53 | return false; 54 | #endif 55 | } 56 | 57 | double CompViewAudioProcessor::getTailLengthSeconds() const 58 | { 59 | return 0.0; 60 | } 61 | 62 | int CompViewAudioProcessor::getNumPrograms() 63 | { 64 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 65 | // so this should be at least 1, even if you're not really implementing programs. 66 | } 67 | 68 | int CompViewAudioProcessor::getCurrentProgram() 69 | { 70 | return 0; 71 | } 72 | 73 | void CompViewAudioProcessor::setCurrentProgram (int index) 74 | { 75 | } 76 | 77 | const juce::String CompViewAudioProcessor::getProgramName (int index) 78 | { 79 | return {}; 80 | } 81 | 82 | void CompViewAudioProcessor::changeProgramName (int index, const juce::String& newName) 83 | { 84 | } 85 | 86 | //============================================================================== 87 | void CompViewAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 88 | { 89 | // Use this method as the place to do any pre-playback 90 | // initialisation that you need.. 91 | } 92 | 93 | void CompViewAudioProcessor::releaseResources() 94 | { 95 | // When playback stops, you can use this as an opportunity to free up any 96 | // spare memory, etc. 97 | } 98 | 99 | #ifndef JucePlugin_PreferredChannelConfigurations 100 | bool CompViewAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 101 | { 102 | #if JucePlugin_IsMidiEffect 103 | juce::ignoreUnused (layouts); 104 | return true; 105 | #else 106 | // This is the place where you check if the layout is supported. 107 | // In this template code we only support mono or stereo. 108 | // Some plugin hosts, such as certain GarageBand versions, will only 109 | // load plugins that support stereo bus layouts. 110 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 111 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 112 | return false; 113 | 114 | // This checks if the input layout matches the output layout 115 | #if ! JucePlugin_IsSynth 116 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 117 | return false; 118 | #endif 119 | 120 | return true; 121 | #endif 122 | } 123 | #endif 124 | 125 | void CompViewAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 126 | { 127 | 128 | int numSamples = buffer.getNumSamples(); 129 | 130 | float maxLeft = 0.0f; 131 | float maxRight = 0.0f; 132 | 133 | if (buffer.getNumChannels() < 2) return; 134 | 135 | auto* leftChannel = buffer.getReadPointer(0); 136 | auto* rightChannel = buffer.getReadPointer(1); 137 | 138 | for (int i = 0; i < numSamples; ++i) 139 | { 140 | if (countdown < 1) { 141 | if (leftChannel[i] > triggerThreshold || rightChannel[i] > triggerThreshold) { 142 | countdown = 30000; 143 | peakLevelLeftSec.clear(); 144 | peakLevelRightSec.clear(); 145 | } 146 | } else { 147 | countdown--; 148 | } 149 | 150 | 151 | sampleGrouping = (sampleGrouping + 1) % 8; 152 | 153 | if (leftChannel[i] > maxLeft) maxLeft = leftChannel[i]; 154 | if (rightChannel[i] > maxRight) maxRight = rightChannel[i]; 155 | 156 | if (sampleGrouping == 0 & countdown > 18000) { 157 | peakLevelLeftSec.add(maxLeft); 158 | peakLevelRightSec.add(maxRight); 159 | maxLeft = 0; 160 | maxRight = 0; 161 | } 162 | } 163 | } 164 | 165 | 166 | 167 | //============================================================================== 168 | bool CompViewAudioProcessor::hasEditor() const 169 | { 170 | return true; // (change this to false if you choose to not supply an editor) 171 | } 172 | 173 | juce::AudioProcessorEditor* CompViewAudioProcessor::createEditor() 174 | { 175 | return new CompViewAudioProcessorEditor (*this); 176 | } 177 | 178 | //============================================================================== 179 | void CompViewAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 180 | { 181 | // You should use this method to store your parameters in the memory block. 182 | // You could do that either as raw data, or use the XML or ValueTree classes 183 | // as intermediaries to make it easy to save and load complex data. 184 | } 185 | 186 | void CompViewAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 187 | { 188 | // You should use this method to restore your parameters from this memory block, 189 | // whose contents will have been created by the getStateInformation() call. 190 | } 191 | 192 | //============================================================================== 193 | // This creates new instances of the plugin.. 194 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 195 | { 196 | return new CompViewAudioProcessor(); 197 | } 198 | 199 | 200 | /* 201 | juce::AudioProcessorValueTreeState::ParameterLayout CompViewAudioProcessor::createParameterLayout() 202 | { 203 | std::vector> params; 204 | 205 | params.push_back(std::make_unique("loopLength", "Loop length", 100, 1000, 349)); 206 | params.push_back(std::make_unique("batches", "Batches", 1, 8, 4)); 207 | params.push_back(std::make_unique("nudge", "Nudge", -10, 10, 0)); 208 | 209 | return { params.begin(), params.end() }; 210 | } 211 | */ 212 | -------------------------------------------------------------------------------- /Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | //============================================================================== 7 | /** 8 | */ 9 | class CompViewAudioProcessor : public juce::AudioProcessor 10 | { 11 | public: 12 | //============================================================================== 13 | CompViewAudioProcessor(); 14 | ~CompViewAudioProcessor() override; 15 | 16 | //============================================================================== 17 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 18 | void releaseResources() override; 19 | 20 | #ifndef JucePlugin_PreferredChannelConfigurations 21 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 22 | #endif 23 | 24 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 25 | 26 | juce::Array peakLevelLeftSec; 27 | juce::Array peakLevelRightSec; 28 | 29 | int countdown = 0; 30 | int sampleGrouping = 0; 31 | float triggerThreshold = 0.8; 32 | 33 | //============================================================================== 34 | juce::AudioProcessorEditor* createEditor() override; 35 | bool hasEditor() const override; 36 | 37 | //============================================================================== 38 | const juce::String getName() const override; 39 | 40 | bool acceptsMidi() const override; 41 | bool producesMidi() const override; 42 | bool isMidiEffect() const override; 43 | double getTailLengthSeconds() const override; 44 | 45 | //============================================================================== 46 | int getNumPrograms() override; 47 | int getCurrentProgram() override; 48 | void setCurrentProgram (int index) override; 49 | const juce::String getProgramName (int index) override; 50 | void changeProgramName (int index, const juce::String& newName) override; 51 | 52 | //============================================================================== 53 | void getStateInformation (juce::MemoryBlock& destData) override; 54 | void setStateInformation (const void* data, int sizeInBytes) override; 55 | 56 | private: 57 | //============================================================================== 58 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CompViewAudioProcessor) 59 | }; 60 | --------------------------------------------------------------------------------