mirror of
https://codeberg.org/armin/pascalcompressor.git
synced 2026-10-11 16:51:56 +02:00
Add compressor plugin source with build files
This commit is contained in:
parent
b0fef386ee
commit
ef5739546f
10 changed files with 925 additions and 0 deletions
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Build directory (contains CMake artifacts, object files, and fetched dependencies)
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Compiled binaries
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# Editor/IDE
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
98
CMakeLists.txt
Normal file
98
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
cmake_minimum_required(VERSION 3.23)
|
||||||
|
|
||||||
|
project(Pascalcompressor
|
||||||
|
VERSION 1.0.0
|
||||||
|
LANGUAGES C CXX OBJC OBJCXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
|
message(FATAL_ERROR "Pascalcompressor targets macOS arm64 (VST3 + AU).")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0 CACHE STRING "Minimum macOS deployment target")
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "Architecture")
|
||||||
|
|
||||||
|
set(JUCE_TAG "8.0.15" CACHE STRING "JUCE git tag to fetch")
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
if(JUCE_DIR)
|
||||||
|
add_subdirectory(${JUCE_DIR} juce)
|
||||||
|
else()
|
||||||
|
set(JUCE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
|
set(JUCE_BUILD_EXTRAS OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_Declare(juce
|
||||||
|
GIT_REPOSITORY https://github.com/juce-framework/JUCE.git
|
||||||
|
GIT_TAG ${JUCE_TAG}
|
||||||
|
GIT_SHALLOW TRUE)
|
||||||
|
FetchContent_MakeAvailable(juce)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(PASCAL_GENERATED_DIR "${CMAKE_BINARY_DIR}/generated")
|
||||||
|
file(MAKE_DIRECTORY "${PASCAL_GENERATED_DIR}")
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND /bin/sh -c "date '+%Y-%m-%d %H:%M:%S %Z'"
|
||||||
|
OUTPUT_VARIABLE PASCAL_BUILD_TIME
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
if(NOT PASCAL_BUILD_TIME)
|
||||||
|
string(TIMESTAMP PASCAL_BUILD_TIME "%Y-%m-%d %H:%M:%S %Z")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(WRITE "${PASCAL_GENERATED_DIR}/BuildInfo.h"
|
||||||
|
"#pragma once\n"
|
||||||
|
"#define PASCAL_BUILD_TIME \"${PASCAL_BUILD_TIME}\"\n")
|
||||||
|
|
||||||
|
juce_add_plugin(Pascalcompressor
|
||||||
|
PRODUCT_NAME Pascalcompressor
|
||||||
|
COMPANY_NAME "Pascal"
|
||||||
|
BUNDLE_ID com.pascal.pascalcompressor
|
||||||
|
PLUGIN_MANUFACTURER_CODE PscC
|
||||||
|
PLUGIN_CODE Pscp
|
||||||
|
FORMATS AU VST3
|
||||||
|
IS_SYNTH NO
|
||||||
|
NEEDS_MIDI_INPUT NO
|
||||||
|
NEEDS_MIDI_OUTPUT NO
|
||||||
|
IS_MIDI_EFFECT NO
|
||||||
|
COPY_PLUGIN_AFTER_BUILD FALSE)
|
||||||
|
|
||||||
|
target_include_directories(Pascalcompressor PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/Source"
|
||||||
|
"${PASCAL_GENERATED_DIR}")
|
||||||
|
|
||||||
|
target_compile_definitions(Pascalcompressor PUBLIC JUCE_VST3_CAN_REPLACE_VST2=0)
|
||||||
|
|
||||||
|
target_sources(Pascalcompressor PRIVATE
|
||||||
|
Source/Compressor.cpp
|
||||||
|
Source/PluginProcessor.cpp
|
||||||
|
Source/PluginEditor.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(Pascalcompressor PRIVATE
|
||||||
|
juce::juce_core
|
||||||
|
juce::juce_events
|
||||||
|
juce::juce_graphics
|
||||||
|
juce::juce_gui_basics
|
||||||
|
juce::juce_gui_extra
|
||||||
|
juce::juce_audio_basics
|
||||||
|
juce::juce_audio_processors
|
||||||
|
juce::juce_audio_utils
|
||||||
|
juce::juce_dsp)
|
||||||
|
|
||||||
|
set(PASCAL_BUILDINFO "${PASCAL_GENERATED_DIR}/BuildInfo.h")
|
||||||
|
set_source_files_properties(Source/PluginEditor.cpp PROPERTIES OBJECT_DEPENDS "${PASCAL_BUILDINFO}")
|
||||||
|
|
||||||
|
# Sign each format bundle from its own target. Signing on the shared-library
|
||||||
|
# target rewrote the bundle binaries' mtimes every build, which made make treat
|
||||||
|
# them as always up to date and never relinked them.
|
||||||
|
add_custom_command(TARGET Pascalcompressor_AU POST_BUILD
|
||||||
|
COMMAND codesign --force --sign -
|
||||||
|
"${CMAKE_BINARY_DIR}/Pascalcompressor_artefacts/$<CONFIG>/AU/Pascalcompressor.component"
|
||||||
|
VERBATIM)
|
||||||
|
|
||||||
|
add_custom_command(TARGET Pascalcompressor_VST3 POST_BUILD
|
||||||
|
COMMAND codesign --force --sign -
|
||||||
|
"${CMAKE_BINARY_DIR}/Pascalcompressor_artefacts/$<CONFIG>/VST3/Pascalcompressor.vst3"
|
||||||
|
VERBATIM)
|
||||||
89
Makefile
Normal file
89
Makefile
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
CMAKE ?= cmake
|
||||||
|
CONFIG ?= Release
|
||||||
|
BUILD_DIR ?= build
|
||||||
|
JOBS ?= $(shell sysctl -n hw.availcpu 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 8)
|
||||||
|
CC ?= cc
|
||||||
|
|
||||||
|
ART_DIR := $(BUILD_DIR)/Pascalcompressor_artefacts/$(CONFIG)
|
||||||
|
GEN_DIR := $(BUILD_DIR)/generated
|
||||||
|
GEN_H := $(GEN_DIR)/BuildInfo.h
|
||||||
|
|
||||||
|
VST3_SRC := $(ART_DIR)/VST3/Pascalcompressor.vst3
|
||||||
|
AU_SRC := $(ART_DIR)/AU/Pascalcompressor.component
|
||||||
|
|
||||||
|
VST3_DEST := $(HOME)/Library/Audio/Plug-Ins/VST3/Pascalcompressor.vst3
|
||||||
|
AU_DEST := $(HOME)/Library/Audio/Plug-Ins/Components/Pascalcompressor.component
|
||||||
|
|
||||||
|
AU_BIN := $(AU_DEST)/Contents/MacOS/Pascalcompressor
|
||||||
|
AUTEST_BIN := $(BUILD_DIR)/autest
|
||||||
|
|
||||||
|
PLUGIN_DIRS := $(dir $(VST3_DEST)) $(dir $(AU_DEST))
|
||||||
|
AU_CODE := aufx Pscp PscC
|
||||||
|
|
||||||
|
MAKEFLAGS += --no-print-directory
|
||||||
|
|
||||||
|
.PHONY: all build configure install uninstall clean smoke auval FORCE
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
configure:
|
||||||
|
$(CMAKE) -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(CONFIG)
|
||||||
|
|
||||||
|
FORCE:
|
||||||
|
|
||||||
|
$(GEN_H): FORCE
|
||||||
|
@mkdir -p "$(GEN_DIR)"
|
||||||
|
@printf '#pragma once\n#define PASCAL_BUILD_TIME "%s"\n' "$$(date '+%Y-%m-%d %H:%M:%S %Z')" > "$(GEN_H)"
|
||||||
|
|
||||||
|
build: configure $(GEN_H)
|
||||||
|
$(CMAKE) --build $(BUILD_DIR) --config $(CONFIG) --parallel $(JOBS)
|
||||||
|
@printf 'built at %s (local)\n' "$$(sed -n 's/^#define PASCAL_BUILD_TIME "\(.*\)"/\1/p' "$(GEN_H)")"
|
||||||
|
|
||||||
|
$(AUTEST_BIN): Tests/autest.c
|
||||||
|
@mkdir -p "$(BUILD_DIR)"
|
||||||
|
$(CC) -O2 -framework AudioToolbox -framework CoreFoundation -o "$(AUTEST_BIN)" Tests/autest.c
|
||||||
|
|
||||||
|
smoke: $(AUTEST_BIN)
|
||||||
|
@if [ -e "$(AU_SRC)/Contents/MacOS/Pascalcompressor" ]; then \
|
||||||
|
"$(AUTEST_BIN)" "$(AU_SRC)/Contents/MacOS/Pascalcompressor"; \
|
||||||
|
elif [ -e "$(AU_BIN)" ]; then \
|
||||||
|
"$(AUTEST_BIN)" "$(AU_BIN)"; \
|
||||||
|
else \
|
||||||
|
echo "Nothing to test yet: run 'make build' or 'make install' first."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
install: build
|
||||||
|
@rm -rf "$(VST3_DEST)" "$(AU_DEST)"
|
||||||
|
@mkdir -p $(PLUGIN_DIRS)
|
||||||
|
@cp -R "$(VST3_SRC)" "$(VST3_DEST)"
|
||||||
|
@cp -R "$(AU_SRC)" "$(AU_DEST)"
|
||||||
|
@chmod -R a+rX "$(VST3_DEST)" "$(AU_DEST)"
|
||||||
|
@xattr -cr "$(VST3_DEST)" "$(AU_DEST)"
|
||||||
|
@codesign --force --sign - "$(VST3_DEST)" "$(AU_DEST)"
|
||||||
|
@codesign -dv "$(VST3_DEST)" 2>&1 | head -2
|
||||||
|
@echo ""
|
||||||
|
@echo "Pascalcompressor installed to:"
|
||||||
|
@echo " $(VST3_DEST)"
|
||||||
|
@echo " $(AU_DEST)"
|
||||||
|
@echo ""
|
||||||
|
@$(MAKE) smoke
|
||||||
|
|
||||||
|
auval:
|
||||||
|
@if auval -v $(AU_CODE) 2>&1 | grep -q "PASS\|* *NOT OK\|FAIL\|didn't find"; then \
|
||||||
|
auval -v $(AU_CODE) 2>&1 | tail -20; \
|
||||||
|
if auval -l $(AU_CODE) 2>/dev/null | grep -q Pscp; then \
|
||||||
|
echo "AU validation PASSED."; \
|
||||||
|
else \
|
||||||
|
echo "auval did not find the component yet. The stock AudioUnit Registrar only scans"; \
|
||||||
|
echo "for new .component bundles at logout/login (or reboot); this is a machine-wide"; \
|
||||||
|
echo "state, not a plugin problem. The functional check already passed: run 'make smoke'."; \
|
||||||
|
fi; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
@rm -rf "$(VST3_DEST)" "$(AU_DEST)"
|
||||||
|
@echo "Pascalcompressor uninstalled."
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(CMAKE) -E remove_directory $(BUILD_DIR)
|
||||||
80
Source/Compressor.cpp
Normal file
80
Source/Compressor.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include "Compressor.h"
|
||||||
|
|
||||||
|
namespace pascal
|
||||||
|
{
|
||||||
|
|
||||||
|
void GlueCompressor::prepare (const juce::dsp::ProcessSpec& spec)
|
||||||
|
{
|
||||||
|
sampleRate = spec.sampleRate > 0.0 ? spec.sampleRate : 44100.0;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlueCompressor::reset()
|
||||||
|
{
|
||||||
|
detectorLevel = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlueCompressor::processBlock (juce::AudioBuffer<float>& buffer)
|
||||||
|
{
|
||||||
|
const auto numChannels = buffer.getNumChannels();
|
||||||
|
const auto numSamples = buffer.getNumSamples();
|
||||||
|
|
||||||
|
if (numChannels == 0 || numSamples == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float inputGain = juce::Decibels::decibelsToGain (juce::jlimit (-60.0f, 60.0f, inputDb));
|
||||||
|
const float makeupGain = juce::Decibels::decibelsToGain (juce::jlimit (-60.0f, 60.0f, makeupDb));
|
||||||
|
const float fs = static_cast<float> (sampleRate);
|
||||||
|
const float attackCoef = std::exp (-1.0f / (juce::jmax (0.2f, attackMs) * 0.001f * fs));
|
||||||
|
const float releaseCoef = std::exp (-1.0f / (juce::jmax (10.0f, releaseMs) * 0.001f * fs));
|
||||||
|
const float wet = juce::jlimit (0.0f, 1.0f, dryWet);
|
||||||
|
const float dry = 1.0f - wet;
|
||||||
|
const float knee = 6.0f;
|
||||||
|
const float ratioV = juce::jmax (1.0f, ratio);
|
||||||
|
|
||||||
|
juce::AudioBuffer<float> input;
|
||||||
|
input.makeCopyOf (buffer, true);
|
||||||
|
|
||||||
|
const auto* inChannels = input.getArrayOfReadPointers();
|
||||||
|
auto* outChannels = buffer.getArrayOfWritePointers();
|
||||||
|
|
||||||
|
for (int n = 0; n < numSamples; ++n)
|
||||||
|
{
|
||||||
|
float detect = 0.0f;
|
||||||
|
for (int c = 0; c < numChannels; ++c)
|
||||||
|
detect += inChannels[c][n];
|
||||||
|
detect = std::fabs (detect / static_cast<float> (numChannels)) * inputGain;
|
||||||
|
|
||||||
|
const float coeff = detect > detectorLevel ? (1.0f - attackCoef)
|
||||||
|
: (1.0f - releaseCoef);
|
||||||
|
detectorLevel += coeff * (detect - detectorLevel);
|
||||||
|
|
||||||
|
const float overDb = juce::Decibels::gainToDecibels (detectorLevel + 1.0e-9f) - thresholdDb;
|
||||||
|
|
||||||
|
float grDb;
|
||||||
|
if (overDb <= -knee * 0.5f)
|
||||||
|
{
|
||||||
|
grDb = 0.0f;
|
||||||
|
}
|
||||||
|
else if (overDb >= knee * 0.5f)
|
||||||
|
{
|
||||||
|
grDb = -overDb * (1.0f - 1.0f / ratioV);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const float q = overDb + knee * 0.5f;
|
||||||
|
grDb = -(q * q / (2.0f * knee)) * (1.0f - 1.0f / ratioV);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float gain = juce::Decibels::decibelsToGain (juce::jlimit (-80.0f, 24.0f, grDb)) * makeupGain;
|
||||||
|
|
||||||
|
for (int c = 0; c < numChannels; ++c)
|
||||||
|
{
|
||||||
|
const float in = inChannels[c][n] * inputGain;
|
||||||
|
const float wetSample = in * gain;
|
||||||
|
outChannels[c][n] = c < 2 ? in * dry + wetSample * wet : wetSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
Source/Compressor.h
Normal file
37
Source/Compressor.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <juce_audio_utils/juce_audio_utils.h>
|
||||||
|
#include <juce_dsp/juce_dsp.h>
|
||||||
|
|
||||||
|
namespace pascal
|
||||||
|
{
|
||||||
|
|
||||||
|
class GlueCompressor final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (const juce::dsp::ProcessSpec& spec);
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void setInputDb (float v) { inputDb = v; }
|
||||||
|
void setThresholdDb (float v) { thresholdDb = v; }
|
||||||
|
void setRatio (float v) { ratio = v; }
|
||||||
|
void setAttackMs (float v) { attackMs = v; }
|
||||||
|
void setReleaseMs (float v) { releaseMs = v; }
|
||||||
|
void setMakeupDb (float v) { makeupDb = v; }
|
||||||
|
void setDryWet (float v) { dryWet = v; }
|
||||||
|
|
||||||
|
void processBlock (juce::AudioBuffer<float>& buffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
float inputDb = 0.0f;
|
||||||
|
float thresholdDb = -18.0f;
|
||||||
|
float ratio = 2.0f;
|
||||||
|
float attackMs = 10.0f;
|
||||||
|
float releaseMs = 250.0f;
|
||||||
|
float makeupDb = 0.0f;
|
||||||
|
float dryWet = 1.0f;
|
||||||
|
double sampleRate = 44100.0;
|
||||||
|
float detectorLevel = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
230
Source/PluginEditor.cpp
Normal file
230
Source/PluginEditor.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
#include "PluginEditor.h"
|
||||||
|
#include "BuildInfo.h"
|
||||||
|
|
||||||
|
namespace pascal
|
||||||
|
{
|
||||||
|
|
||||||
|
void MinimalLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
|
||||||
|
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
|
||||||
|
juce::Slider& slider)
|
||||||
|
{
|
||||||
|
const auto centre = juce::Point<float> (x + width * 0.5f, y + height * 0.5f);
|
||||||
|
const auto radius = juce::jmin (width, height) * 0.5f - 7.0f;
|
||||||
|
const auto angle = rotaryStartAngle + (rotaryEndAngle - rotaryStartAngle) * sliderPos;
|
||||||
|
|
||||||
|
juce::Path trackPath;
|
||||||
|
trackPath.addArc (centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f,
|
||||||
|
rotaryStartAngle, rotaryEndAngle, true);
|
||||||
|
|
||||||
|
juce::Path fillPath;
|
||||||
|
fillPath.addArc (centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f,
|
||||||
|
rotaryStartAngle, angle, true);
|
||||||
|
|
||||||
|
const auto thickness = juce::jmax (1.0f, radius * 0.10f);
|
||||||
|
juce::PathStrokeType stroke (thickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded);
|
||||||
|
|
||||||
|
g.setColour (track);
|
||||||
|
g.strokePath (trackPath, stroke);
|
||||||
|
|
||||||
|
g.setColour (slider.isMouseOverOrDragging() ? juce::Colours::white : accent);
|
||||||
|
g.strokePath (fillPath, stroke);
|
||||||
|
|
||||||
|
const auto indicator = centre + juce::Point<float> (std::cos (angle - juce::MathConstants<float>::halfPi),
|
||||||
|
std::sin (angle - juce::MathConstants<float>::halfPi)) * radius;
|
||||||
|
g.fillEllipse (indicator.x - 2.0f, indicator.y - 2.0f, 4.0f, 4.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
KnobComponent::KnobComponent (juce::AudioProcessorValueTreeState& apvts,
|
||||||
|
const juce::String& paramId,
|
||||||
|
const juce::String& displayName,
|
||||||
|
float defaultValue,
|
||||||
|
juce::LookAndFeel& lnf,
|
||||||
|
std::function<juce::String (double)> fmt)
|
||||||
|
: format (std::move (fmt))
|
||||||
|
{
|
||||||
|
slider.setSliderStyle (juce::Slider::RotaryVerticalDrag);
|
||||||
|
slider.setRotaryParameters (juce::MathConstants<float>::pi * 1.25f,
|
||||||
|
juce::MathConstants<float>::pi * 2.75f,
|
||||||
|
true);
|
||||||
|
slider.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
|
||||||
|
slider.setLookAndFeel (&lnf);
|
||||||
|
slider.setDoubleClickReturnValue (true, defaultValue);
|
||||||
|
slider.setWantsKeyboardFocus (true);
|
||||||
|
slider.addListener (this);
|
||||||
|
addAndMakeVisible (slider);
|
||||||
|
|
||||||
|
nameLabel.setText (displayName, juce::dontSendNotification);
|
||||||
|
nameLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
nameLabel.setColour (juce::Label::textColourId, labelColour);
|
||||||
|
nameLabel.setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack);
|
||||||
|
nameLabel.setColour (juce::Label::outlineColourId, juce::Colours::transparentBlack);
|
||||||
|
addAndMakeVisible (nameLabel);
|
||||||
|
|
||||||
|
valueLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
valueLabel.setColour (juce::Label::textColourId, valueColour);
|
||||||
|
valueLabel.setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack);
|
||||||
|
valueLabel.setColour (juce::Label::outlineColourId, juce::Colours::transparentBlack);
|
||||||
|
addAndMakeVisible (valueLabel);
|
||||||
|
|
||||||
|
attachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment> (
|
||||||
|
apvts, paramId, slider);
|
||||||
|
|
||||||
|
valueLabel.setText (format (slider.getValue()), juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KnobComponent::resized()
|
||||||
|
{
|
||||||
|
auto bounds = getLocalBounds();
|
||||||
|
auto valueArea = bounds.removeFromBottom (18);
|
||||||
|
auto nameArea = bounds.removeFromTop (16);
|
||||||
|
|
||||||
|
const auto square = juce::jmin (bounds.getWidth(), bounds.getHeight());
|
||||||
|
nameLabel.setBounds (nameArea);
|
||||||
|
slider.setBounds (bounds.withSizeKeepingCentre (square, square));
|
||||||
|
valueLabel.setBounds (valueArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KnobComponent::sliderValueChanged (juce::Slider* slider)
|
||||||
|
{
|
||||||
|
valueLabel.setText (format (slider->getValue()), juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
juce::String buildArch()
|
||||||
|
{
|
||||||
|
#if defined (__arm64__) || defined (_M_ARM64) || defined (__aarch64__)
|
||||||
|
return "arm64";
|
||||||
|
#elif defined (__x86_64__) || defined (_M_X64)
|
||||||
|
return "x64";
|
||||||
|
#else
|
||||||
|
return "x86";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FooterBar::FooterBar (PascalcompressorAudioProcessor& p)
|
||||||
|
: processor (p)
|
||||||
|
{
|
||||||
|
leftText = juce::String ("Pascalcompressor 1.0.0")
|
||||||
|
+ " \u00B7 " + buildArch()
|
||||||
|
+ " \u00B7 VST3 + AU"
|
||||||
|
+ " \u00B7 built " + juce::String (PASCAL_BUILD_TIME) + " (local)";
|
||||||
|
startTimerHz (2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FooterBar::timerCallback()
|
||||||
|
{
|
||||||
|
const auto newRight = juce::String (juce::roundToInt (processor.getSampleRate())) + " Hz"
|
||||||
|
+ " \u00B7 " + juce::String (processor.getBlockSize()) + " smp";
|
||||||
|
if (newRight != rightText)
|
||||||
|
{
|
||||||
|
rightText = newRight;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FooterBar::paint (juce::Graphics& g)
|
||||||
|
{
|
||||||
|
g.fillAll (background);
|
||||||
|
g.setColour (ruleColour);
|
||||||
|
g.fillRect (0, 0, getWidth(), 1);
|
||||||
|
|
||||||
|
g.setColour (footerColour);
|
||||||
|
g.setFont (juce::FontOptions (11.0f));
|
||||||
|
|
||||||
|
auto bounds = getLocalBounds().reduced (14, 0);
|
||||||
|
g.drawText (leftText, bounds, juce::Justification::centredLeft);
|
||||||
|
g.drawText (rightText, bounds, juce::Justification::centredRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
PascalcompressorAudioProcessorEditor::PascalcompressorAudioProcessorEditor (PascalcompressorAudioProcessor& p)
|
||||||
|
: AudioProcessorEditor (&p),
|
||||||
|
processorRef (p),
|
||||||
|
footer (p)
|
||||||
|
{
|
||||||
|
setOpaque (true);
|
||||||
|
setResizeLimits (500, 340, 1100, 860);
|
||||||
|
setResizable (true, true);
|
||||||
|
|
||||||
|
struct Spec
|
||||||
|
{
|
||||||
|
const char* id;
|
||||||
|
const char* name;
|
||||||
|
float defaultValue;
|
||||||
|
std::function<juce::String (double)> format;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Spec specs[] =
|
||||||
|
{
|
||||||
|
{ "input", "Input", 0.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%+.1f dB", v); } },
|
||||||
|
{ "threshold", "Threshold", -18.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%.1f dB", v); } },
|
||||||
|
{ "ratio", "Ratio", 2.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%.2f : 1", v); } },
|
||||||
|
{ "attack", "Attack", 10.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%.1f ms", v); } },
|
||||||
|
{ "release", "Release", 250.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%.0f ms", v); } },
|
||||||
|
{ "makeup", "Makeup", 0.0f,
|
||||||
|
[] (double v) { return juce::String::formatted ("%+.1f dB", v); } },
|
||||||
|
{ "drywet", "Dry/Wet", 100.0f,
|
||||||
|
[] (double v) { return juce::String (juce::roundToInt (v)) + " %"; } },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& spec : specs)
|
||||||
|
{
|
||||||
|
auto* knob = new KnobComponent (processorRef.apvts, spec.id, spec.name,
|
||||||
|
spec.defaultValue, lookAndFeel, spec.format);
|
||||||
|
knobs.add (knob);
|
||||||
|
addAndMakeVisible (knob);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAndMakeVisible (footer);
|
||||||
|
ready = true;
|
||||||
|
setSize (620, 420);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessorEditor::paint (juce::Graphics& g)
|
||||||
|
{
|
||||||
|
jassert (ready);
|
||||||
|
g.fillAll (background);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessorEditor::resized()
|
||||||
|
{
|
||||||
|
if (! ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto bounds = getLocalBounds();
|
||||||
|
footer.setBounds (bounds.removeFromBottom (24));
|
||||||
|
bounds.reduce (24, 16);
|
||||||
|
|
||||||
|
const auto knobSize = juce::jmin (bounds.getWidth() / 4 - 42,
|
||||||
|
(bounds.getHeight() - 20) / 2 - 40,
|
||||||
|
120);
|
||||||
|
const auto knobHeight = knobSize + 40;
|
||||||
|
const auto rowGap = 20;
|
||||||
|
const auto rowsTotal = knobHeight * 2 + rowGap;
|
||||||
|
const auto rowsTop = bounds.getY() + (bounds.getHeight() - rowsTotal) / 2;
|
||||||
|
|
||||||
|
const auto xStepTop = bounds.getWidth() / 4;
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const auto x = bounds.getX() + xStepTop * i + (xStepTop - knobSize) / 2;
|
||||||
|
knobs.getUnchecked (i)->setBounds (x, rowsTop, knobSize, knobHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto xStepBottom = bounds.getWidth() / 3;
|
||||||
|
const auto rowY = rowsTop + knobHeight + rowGap;
|
||||||
|
for (int i = 4; i < 7; ++i)
|
||||||
|
{
|
||||||
|
const auto x = bounds.getX() + xStepBottom * (i - 4) + (xStepBottom - knobSize) / 2;
|
||||||
|
knobs.getUnchecked (i)->setBounds (x, rowY, knobSize, knobHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
Source/PluginEditor.h
Normal file
77
Source/PluginEditor.h
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <juce_audio_utils/juce_audio_utils.h>
|
||||||
|
#include <functional>
|
||||||
|
#include "PluginProcessor.h"
|
||||||
|
|
||||||
|
namespace pascal
|
||||||
|
{
|
||||||
|
|
||||||
|
inline const juce::Colour background (0xFF101014);
|
||||||
|
inline const juce::Colour track (0xFF2A2A30);
|
||||||
|
inline const juce::Colour accent (0xFFEDEDF2);
|
||||||
|
inline const juce::Colour labelColour (0xFF9A9AA2);
|
||||||
|
inline const juce::Colour valueColour (0xFFC8C8D0);
|
||||||
|
inline const juce::Colour footerColour (0xFF6A6A72);
|
||||||
|
inline const juce::Colour ruleColour (0xFF1E1E23);
|
||||||
|
|
||||||
|
class MinimalLookAndFeel final : public juce::LookAndFeel_V4
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
|
||||||
|
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
|
||||||
|
juce::Slider& slider) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KnobComponent final : public juce::Component, private juce::Slider::Listener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KnobComponent (juce::AudioProcessorValueTreeState& apvts, const juce::String& paramId,
|
||||||
|
const juce::String& displayName, float defaultValue,
|
||||||
|
juce::LookAndFeel& lnf, std::function<juce::String (double)> format);
|
||||||
|
|
||||||
|
void resized() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sliderValueChanged (juce::Slider*) override;
|
||||||
|
|
||||||
|
juce::Slider slider;
|
||||||
|
juce::Label nameLabel;
|
||||||
|
juce::Label valueLabel;
|
||||||
|
std::function<juce::String (double)> format;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> attachment;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FooterBar final : public juce::Component, private juce::Timer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FooterBar (PascalcompressorAudioProcessor& p);
|
||||||
|
|
||||||
|
void paint (juce::Graphics&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void timerCallback() override;
|
||||||
|
|
||||||
|
PascalcompressorAudioProcessor& processor;
|
||||||
|
juce::String leftText;
|
||||||
|
juce::String rightText;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PascalcompressorAudioProcessorEditor final : public juce::AudioProcessorEditor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PascalcompressorAudioProcessorEditor (PascalcompressorAudioProcessor&);
|
||||||
|
~PascalcompressorAudioProcessorEditor() override = default;
|
||||||
|
|
||||||
|
void paint (juce::Graphics&) override;
|
||||||
|
void resized() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PascalcompressorAudioProcessor& processorRef;
|
||||||
|
MinimalLookAndFeel lookAndFeel;
|
||||||
|
juce::OwnedArray<KnobComponent> knobs;
|
||||||
|
FooterBar footer;
|
||||||
|
bool ready = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
109
Source/PluginProcessor.cpp
Normal file
109
Source/PluginProcessor.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
#include "PluginProcessor.h"
|
||||||
|
#include "PluginEditor.h"
|
||||||
|
|
||||||
|
PascalcompressorAudioProcessor::PascalcompressorAudioProcessor()
|
||||||
|
: AudioProcessor (BusesProperties()
|
||||||
|
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)),
|
||||||
|
apvts (*this, nullptr, "PARAMS", createParameterLayout())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PascalcompressorAudioProcessor::~PascalcompressorAudioProcessor() = default;
|
||||||
|
|
||||||
|
juce::AudioProcessorValueTreeState::ParameterLayout PascalcompressorAudioProcessor::createParameterLayout()
|
||||||
|
{
|
||||||
|
juce::AudioProcessorValueTreeState::ParameterLayout layout;
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("input",
|
||||||
|
"Input", juce::NormalisableRange<float> (-24.0f, 24.0f, 0.1f, 1.0f), 0.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("threshold",
|
||||||
|
"Threshold", juce::NormalisableRange<float> (-60.0f, 0.0f, 0.1f, 1.0f), -18.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("ratio",
|
||||||
|
"Ratio", juce::NormalisableRange<float> (1.0f, 20.0f, 0.05f, 0.4f), 2.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("attack",
|
||||||
|
"Attack", juce::NormalisableRange<float> (0.1f, 200.0f, 0.1f, 0.35f), 10.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("release",
|
||||||
|
"Release", juce::NormalisableRange<float> (10.0f, 3000.0f, 1.0f, 0.35f), 250.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("makeup",
|
||||||
|
"Makeup", juce::NormalisableRange<float> (0.0f, 24.0f, 0.1f, 1.0f), 0.0f));
|
||||||
|
|
||||||
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("drywet",
|
||||||
|
"Dry/Wet", juce::NormalisableRange<float> (0.0f, 100.0f, 1.0f, 1.0f), 100.0f));
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
|
||||||
|
{
|
||||||
|
juce::dsp::ProcessSpec spec;
|
||||||
|
spec.sampleRate = sampleRate;
|
||||||
|
spec.maximumBlockSize = static_cast<juce::uint32> (samplesPerBlock);
|
||||||
|
spec.numChannels = static_cast<juce::uint32> (juce::jmax (1, getTotalNumInputChannels()));
|
||||||
|
compressor.prepare (spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::releaseResources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::updateParameters()
|
||||||
|
{
|
||||||
|
compressor.setInputDb (*apvts.getRawParameterValue ("input"));
|
||||||
|
compressor.setThresholdDb (*apvts.getRawParameterValue ("threshold"));
|
||||||
|
compressor.setRatio (*apvts.getRawParameterValue ("ratio"));
|
||||||
|
compressor.setAttackMs (*apvts.getRawParameterValue ("attack"));
|
||||||
|
compressor.setReleaseMs (*apvts.getRawParameterValue ("release"));
|
||||||
|
compressor.setMakeupDb (*apvts.getRawParameterValue ("makeup"));
|
||||||
|
compressor.setDryWet (*apvts.getRawParameterValue ("drywet") * 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
|
||||||
|
juce::MidiBuffer&)
|
||||||
|
{
|
||||||
|
updateParameters();
|
||||||
|
|
||||||
|
if (getTotalNumInputChannels() > 0)
|
||||||
|
compressor.processBlock (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PascalcompressorAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
||||||
|
{
|
||||||
|
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
|
||||||
|
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::AudioProcessorEditor* PascalcompressorAudioProcessor::createEditor()
|
||||||
|
{
|
||||||
|
return new pascal::PascalcompressorAudioProcessorEditor (*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
||||||
|
{
|
||||||
|
auto state = apvts.copyState();
|
||||||
|
std::unique_ptr<juce::XmlElement> xml (state.createXml());
|
||||||
|
copyXmlToBinary (*xml, destData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PascalcompressorAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||||
|
{
|
||||||
|
std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
|
||||||
|
if (xmlState != nullptr && xmlState->hasTagName (apvts.state.getType()))
|
||||||
|
apvts.replaceState (juce::ValueTree::fromXml (*xmlState));
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||||
|
{
|
||||||
|
return new PascalcompressorAudioProcessor();
|
||||||
|
}
|
||||||
47
Source/PluginProcessor.h
Normal file
47
Source/PluginProcessor.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <juce_audio_utils/juce_audio_utils.h>
|
||||||
|
#include <juce_dsp/juce_dsp.h>
|
||||||
|
#include "Compressor.h"
|
||||||
|
|
||||||
|
class PascalcompressorAudioProcessor final : public juce::AudioProcessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PascalcompressorAudioProcessor();
|
||||||
|
~PascalcompressorAudioProcessor() override;
|
||||||
|
|
||||||
|
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
||||||
|
void releaseResources() override;
|
||||||
|
|
||||||
|
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
|
||||||
|
|
||||||
|
juce::AudioProcessorEditor* createEditor() override;
|
||||||
|
bool hasEditor() const override { return true; }
|
||||||
|
|
||||||
|
const juce::String getName() const override { return "Pascalcompressor"; }
|
||||||
|
|
||||||
|
bool acceptsMidi() const override { return false; }
|
||||||
|
bool producesMidi() const override { return false; }
|
||||||
|
bool isMidiEffect() const override { return false; }
|
||||||
|
double getTailLengthSeconds() const override { return 0.0; }
|
||||||
|
|
||||||
|
int getNumPrograms() override { return 1; }
|
||||||
|
int getCurrentProgram() override { return 0; }
|
||||||
|
void setCurrentProgram (int) override {}
|
||||||
|
const juce::String getProgramName (int) override { return {}; }
|
||||||
|
void changeProgramName (int, const juce::String&) override {}
|
||||||
|
|
||||||
|
void getStateInformation (juce::MemoryBlock&) override;
|
||||||
|
void setStateInformation (const void*, int) override;
|
||||||
|
|
||||||
|
bool isBusesLayoutSupported (const BusesLayout&) const override;
|
||||||
|
|
||||||
|
juce::AudioProcessorValueTreeState apvts;
|
||||||
|
|
||||||
|
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateParameters();
|
||||||
|
|
||||||
|
pascal::GlueCompressor compressor;
|
||||||
|
};
|
||||||
141
Tests/autest.c
Normal file
141
Tests/autest.c
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
#include <AudioToolbox/AudioToolbox.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static void check (OSStatus err, const char* what)
|
||||||
|
{
|
||||||
|
if (err != noErr)
|
||||||
|
printf ("FAIL: %s -> %d\n", what, (int) err);
|
||||||
|
else
|
||||||
|
printf ("PASS: %s\n", what);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
printf ("usage: %s <Pascalcompressor executable path>\n", argv[0]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* handle = dlopen (argv[1], RTLD_NOW | RTLD_LOCAL);
|
||||||
|
if (!handle)
|
||||||
|
{
|
||||||
|
printf ("FAIL: dlopen -> %s\n", dlerror());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf ("PASS: dlopen bundle\n");
|
||||||
|
|
||||||
|
AudioComponentDescription desc;
|
||||||
|
memset (&desc, 0, sizeof (desc));
|
||||||
|
desc.componentType = kAudioUnitType_Effect;
|
||||||
|
desc.componentSubType = 'Pscp';
|
||||||
|
desc.componentManufacturer = 'PscC';
|
||||||
|
desc.componentFlags = 0;
|
||||||
|
desc.componentFlagsMask = 0;
|
||||||
|
|
||||||
|
void* (*factory) (const AudioComponentDescription*) = dlsym (handle, "PascalcompressorAUFactory");
|
||||||
|
if (!factory)
|
||||||
|
{
|
||||||
|
printf ("FAIL: dlsym factory -> %s\n", dlerror());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf ("PASS: dlsym factory\n");
|
||||||
|
|
||||||
|
AudioComponent comp = AudioComponentRegister (&desc,
|
||||||
|
CFSTR ("Pascal: Pascalcompressor"),
|
||||||
|
0x00010000,
|
||||||
|
(AudioComponentFactoryFunction) factory);
|
||||||
|
if (!comp)
|
||||||
|
{
|
||||||
|
printf ("FAIL: AudioComponentRegister\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf ("PASS: AudioComponentRegister\n");
|
||||||
|
|
||||||
|
AudioUnit au = 0;
|
||||||
|
check (AudioComponentInstanceNew (comp, &au), "AudioComponentInstanceNew");
|
||||||
|
if (!au) return 1;
|
||||||
|
printf (" instance %p\n", (void*) au);
|
||||||
|
|
||||||
|
AudioUnitParameterInfo info;
|
||||||
|
memset (&info, 0, sizeof (info));
|
||||||
|
UInt32 infoSz = sizeof (info);
|
||||||
|
for (UInt32 pid = 0; pid < 7; ++pid)
|
||||||
|
{
|
||||||
|
if (AudioUnitGetProperty (au, kAudioUnitProperty_ParameterInfo,
|
||||||
|
kAudioUnitScope_Global, pid, &info, &infoSz) == noErr)
|
||||||
|
printf (" parameter %u: default %.3f name='%s'\n", (unsigned) pid,
|
||||||
|
(double) info.defaultValue,
|
||||||
|
info.cfNameString ? CFStringGetCStringPtr (info.cfNameString, kCFStringEncodingUTF8) : "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStreamBasicDescription fmt;
|
||||||
|
memset (&fmt, 0, sizeof (fmt));
|
||||||
|
fmt.mSampleRate = 44100.0;
|
||||||
|
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||||
|
fmt.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
|
||||||
|
fmt.mBytesPerPacket = 4;
|
||||||
|
fmt.mFramesPerPacket = 1;
|
||||||
|
fmt.mBytesPerFrame = 4;
|
||||||
|
fmt.mChannelsPerFrame = 2;
|
||||||
|
fmt.mBitsPerChannel = 32;
|
||||||
|
|
||||||
|
check (AudioUnitSetProperty (au, kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Input, 0, &fmt, sizeof (fmt)),
|
||||||
|
"Set input format");
|
||||||
|
check (AudioUnitSetProperty (au, kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Output, 0, &fmt, sizeof (fmt)),
|
||||||
|
"Set output format");
|
||||||
|
|
||||||
|
check (AudioUnitInitialize (au), "AudioUnitInitialize");
|
||||||
|
|
||||||
|
AudioUnitParameter p;
|
||||||
|
memset (&p, 0, sizeof (p));
|
||||||
|
p.mAudioUnit = au;
|
||||||
|
for (int i = 0; i < 7; ++i)
|
||||||
|
{
|
||||||
|
p.mParameterID = (AudioUnitParameterID) i;
|
||||||
|
p.mScope = kAudioUnitScope_Global;
|
||||||
|
p.mElement = 0;
|
||||||
|
AudioUnitParameterValue value = 0.0f;
|
||||||
|
OSStatus err = AudioUnitGetParameter (au, p.mParameterID, p.mScope, p.mElement, &value);
|
||||||
|
if (err == noErr)
|
||||||
|
printf (" parameter %d = %.3f\n", i, (double) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioUnitParameterValue setTo = 0.5f;
|
||||||
|
check (AudioUnitSetParameter (au, 1, kAudioUnitScope_Global, 0, setTo, 0),
|
||||||
|
"Set threshold parameter");
|
||||||
|
|
||||||
|
const int numFrames = 512;
|
||||||
|
AudioBufferList abl;
|
||||||
|
abl.mNumberBuffers = 2;
|
||||||
|
for (int c = 0; c < 2; ++c)
|
||||||
|
{
|
||||||
|
abl.mBuffers[c].mNumberChannels = 1;
|
||||||
|
abl.mBuffers[c].mDataByteSize = numFrames * sizeof (float);
|
||||||
|
abl.mBuffers[c].mData = malloc (abl.mBuffers[c].mDataByteSize);
|
||||||
|
memset (abl.mBuffers[c].mData, 0, abl.mBuffers[c].mDataByteSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioTimeStamp ts;
|
||||||
|
memset (&ts, 0, sizeof (ts));
|
||||||
|
ts.mSampleTime = 0.0;
|
||||||
|
ts.mFlags = kAudioTimeStampSampleTimeValid;
|
||||||
|
|
||||||
|
check (AudioUnitRender (au, NULL, &ts, 0, numFrames, &abl), "AudioUnitRender (silence)");
|
||||||
|
|
||||||
|
float* ch0 = (float*) abl.mBuffers[0].mData;
|
||||||
|
float peak = 0.0f;
|
||||||
|
for (int n = 0; n < numFrames; ++n)
|
||||||
|
peak = peak > ch0[n] ? peak : ch0[n];
|
||||||
|
printf (" rendered peak: %.6f\n", (double) peak);
|
||||||
|
|
||||||
|
for (int c = 0; c < 2; ++c) free (abl.mBuffers[c].mData);
|
||||||
|
|
||||||
|
check (AudioComponentInstanceDispose (au), "AudioComponentInstanceDispose");
|
||||||
|
dlclose (handle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue