impl
Skill334 starsChanged 34 days agoMITView on GitHub
Implement an APC audio plugin's DSP and parameter processing during the implementation phase.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- Daeguk-Sun/dcNesstheir own version, first seen Nov 8, 2026
- devjiwonchoi/skillstheir own version, first seen Nov 8, 2026
- victor-hac-work/ai-unknowns-resolvertheir own version, first seen Jul 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- devjiwonchoi/skills★ 2same name
- Daeguk-Sun/dcNess★ 1same name
- victor-hac-work/ai-unknowns-resolver★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository33 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .cursor/rules/no-ai-attribution.mdcCursor rule
- apc-setupSkill
- audio-plugin-coderSkill
- debugSkill
- designSkill
- dreamSkill
- orchestrateSkill
- planSkill
- shipSkill
- skill-debugSkill
- skill-designSkill
- juce-webview-windowsSkill
- skill-ideationSkill
- skill-implementationSkill
- skill-packagingSkill
- skill-planningSkill
- skill-testingSkill
- skill-troubleshootingSkill
- testSkill
- apc-setupSkill
- skill_debugSkill
- skill_designSkill
- dreamSkill
- implSkill
- orchestrateSkill
- planSkill
- shipSkill
- juce-webview-windowsSkill
- skill_testingSkill
- skill_troubleshootingSkill
- testSkill
- audio-plugin-coderSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
48 rules. About 0 are common, 34 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈34
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 45
If ui_framework == webview: use templates from templates/webview/
- Very specific: line 46
If ui_framework == visage: use templates from templates/visage/ and do not generate HTML
- Very specific: line 51
Read Design/v[N]-ui-spec.md (latest approved version)
- Very specific: line 53
Read .ideas/parameter-spec.md for parameter definitions
- Distinctive: line 155
If user chooses 1: Wait for user to test and return to this menu
- Distinctive: line 156
If user chooses 2: Mark WebView GUI as approved and proceed to Phase 4.1
- Distinctive: line 157
If user chooses 3: Allow revisions to the design conversion
- Very specific: line 161
Use templates from templates/visage/ and the shared host in common/VisageJuceHost.h.
- Very specific: line 188
CRITICAL: When implementing WebView plugins, you MUST verify ALL 8 points below.
- Distinctive: line 209
NOT using default backend (must be explicit)
- Very specific: line 234
NOT using data:text/html;base64,...
- Distinctive: line 235
NOT using hardcoded HTML strings
- Distinctive: line 263
Verification: Run validation script before building:
- Very specific: line 311
If the plugin did NOT opt in, the emitted block must leave {{PLUGIN_NAME_LOWER}}_ENABLE_CLAP OFF.
- Very specific: line 316
Check templates in templates/webview/
- Very specific: line 318
Ensure Source/ui/public/index.html exists with ALL JavaScript inline (no separate .js files)
- Distinctive: line 319
Verify CMakeLists.txt embeds files correctly
- Very specific: line 327
Read $PluginPath/.ideas/plan.md to determine implementation approach:
- Distinctive: line 337
All DSP components at once
- Distinctive: line 360
Add DSP member variables:
- Very specific: line 420
Ensure all parameters from .ideas/parameter-spec.md are:
- Distinctive: line 421
Read from APVTS in processBlock using the unified parameter handling system
- Distinctive: line 476
Phase 4.1.2: - Add dynamics components
- Distinctive: line 509
NO locks in audio thread: - Use atomic values for parameter access
- Distinctive: line 510
NO file I/O in processBlock(): - All resources loaded beforehand
- Very specific: line 511
Use juce::ScopedNoDenormals at start of processBlock()
- Distinctive: line 760
Read error messages carefully
- Distinctive: line 761
Verify JUCE modules are properly included
- Distinctive: line 762
Ensure CMakeLists.txt is correctly configured
- Very specific: line 807
Check for existing mockup in .ideas/mockups/
- Distinctive: line 808
If none: Invoke ui-mockup skill
- Distinctive: line 809
If exists: Proceed to SHIP phase (Phase 5)
- Distinctive: line 826
Ensure plugin state saves/loads correctly:
- Distinctive: line 941
After Phase 3 (DESIGN) complete
- Distinctive: line 957
Real-time safety: - No allocations in audio thread
- Distinctive: line 958
Parameter zero-drift: - Use exact IDs from parameter-spec.md
- Distinctive: line 959
Use unified parameter system: - Always validate, smooth, and map parameters
- Distinctive: line 960
Test after each phase: - Verify before continuing
- Distinctive: line 963
Build from root: - Not from plugin directory
- Distinctive: line 964
Consistency check: - Verify all spec parameters are implemented
- Distinctive: line 971
Verify JUCE module includes
- Distinctive: line 972
Check parameter ID typos
- Distinctive: line 977
Verify DSP components initialized in prepareToPlay()
- Distinctive: line 981
Check buffer size handling
- Distinctive: line 982
Verify real-time safety (no allocations)
- Distinctive: line 985
Verify APVTS getRawParameterValue() calls
- Distinctive: line 986
Check parameter ID strings match spec
- Distinctive: line 987
Verify parameter ranges correct
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SKILL: DSP IMPLEMENTATION
Goal: Implement audio processing where parameters control DSP
Focus: PluginProcessor.h, PluginProcessor.cpp
Output Location: $PluginPath/Source/
📊 PHASE 4: CODE (DSP Implementation)
Trigger: /impl [Name] (after DESIGN phase complete)
Input: Reads $PluginPath/status.json and .ideas/parameter-spec.md
Prerequisites: Architecture plan complete, UI framework selected
State Validation:
# Import state management module
. "$PSScriptRoot\..\scripts\state-management.ps1"
$PluginPath = Get-ApcPluginPath -PluginName $PluginName
# Validate prerequisites
if (-not (Test-PluginState -PluginPath $PluginPath -RequiredPhase "design_complete" -RequiredFiles @(".ideas/architecture.md", ".ideas/plan.md"))) {
Write-Error "Prerequisites not met. Complete design phase first."
exit 1
}
# Check framework selection
$state = Get-PluginState -PluginPath $PluginPath
if ($state.ui_framework -eq "pending") {
Write-Error "UI framework not selected. Cannot proceed with implementation."
exit 1
}
Write-Host "Framework: $($state.ui_framework)" -ForegroundColor Cyan🎨 PHASE 4.0: DESIGN-TO-FRAMEWORK CONVERSION (CRITICAL - BEFORE DSP CODE)
Framework Routing:
- If
ui_framework == webview: use templates fromtemplates/webview/ - If
ui_framework == visage: use templates fromtemplates/visage/and do not generate HTML
IMPORTANT: This phase converts the approved design specifications into framework-specific code. User must approve the conversion before DSP implementation begins.
4.0.1 Read Approved Design
- Read
Design/v[N]-ui-spec.md(latest approved version) - Read
Design/v[N]-style-guide.md(latest approved version) - Read
.ideas/parameter-spec.mdfor parameter definitions
4.0.2 Framework-Specific Conversion
For WebView Framework: Convert the approved design specs into production JUCE WebView code.
Create the required directory structure:
$PluginPath/Source/ui/
└───public/
│ index.html # Production UI based on approved design - ALL CSS/JS inline (webview-008)
│ test-local.html # Browser-test copy of index.htmlImplementation Steps:
- Create directory:
$PluginPath/Source/ui/public/ - Inline the JUCE frontend library: Take
_tools/JUCE/modules/juce_gui_extra/native/typescript/webview-interop/dist/index.js, remove itsimport/exportstatements, expose it aswindow.Juce, and paste it into a single<script>block inindex.html(ES6 modules fail silently in WebView - webview-008) - Convert design to HTML: Transform approved design specs into
index.htmlwith embedded CSS - Inline UI JavaScript: Append parameter state setup and UI controls to the SAME
<script>block - NO external.jsfiles - Create browser-test copy: Duplicate
index.htmlastest-local.html
Conversion Process:
- Extract layout, colors, and styling from
v[N]-style-guide.md - Extract control specifications from
v[N]-ui-spec.md - Generate HTML structure matching the approved design
- Implement interactive controls using JUCE parameter states
- Apply approved color palette and visual style
Example Output (based on approved design):
ui/public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[Name] Plugin</title>
<!-- webview-008: ALL JavaScript must be INLINE - external scripts fail silently -->
<style>
/* Styles based on approved v[N]-style-guide.md */
body {
background: [approved-background-color];
color: [approved-text-color];
font-family: [approved-font-family];
margin: 0;
padding: 20px;
}
/* Additional styles from approved design */
</style>
</head>
<body>
<!-- Layout based on approved v[N]-ui-spec.md -->
<div id="plugin-ui">
<!-- Controls generated from parameter-spec.md -->
</div>
</body>
</html>Inline JavaScript (inside ui/public/index.html):
// NO imports allowed (webview-008):
const Juce = window.Juce;
// Initialize parameter states from parameter-spec.md
const parameterStates = {};
document.addEventListener("DOMContentLoaded", () => {
// Create UI controls based on approved design
initializeUI();
console.log("WebView UI initialized from approved design");
});
function initializeUI() {
// Generate controls based on v[N]-ui-spec.md specifications
// Bind to JUCE parameter states
}USER APPROVAL REQUIRED - CRITICAL STOP POINT:
✅ Design converted to WebView code
Files created:
- $PluginPath/Source/ui/public/index.html (ALL JavaScript inline)
⚠️ **MANDATORY STOP** - You MUST test the WebView setup before proceeding to DSP implementation!
What would you like to do?
1. Test WebView - Open $PluginPath/Source/ui/public/index.html in browser and verify appearance
2. Approve - Proceed with DSP implementation (confirms WebView GUI is acceptable)
3. Revise - Make changes to the conversion
**YOU MUST CHOOSE OPTION 1 OR 2 BEFORE CONTINUING**
**DO NOT PROCEED TO PHASE 4.1 WITHOUT USER APPROVAL**
Choose (1-3): _Approval Validation:
- If user chooses 1: Wait for user to test and return to this menu
- If user chooses 2: Mark WebView GUI as approved and proceed to Phase 4.1
- If user chooses 3: Allow revisions to the design conversion
For Visage Framework:
Convert approved design to Visage C++ code (Source/VisageControls.h).
Use templates from templates/visage/ and the shared host in common/VisageJuceHost.h.
✅ VISAGE IMPLEMENTATION CHECKLIST (MANDATORY)
Before proceeding to DSP implementation, validate Visage setup:
.\scripts\validate-visage-setup.ps1 -PluginName [Name]Mandatory Checklist (All Must Pass):
- ✅ CMakeLists.txt links Visage
- Contains
visage::visageintarget_link_libraries
- Contains
- ✅ Visage controls exist
Source/VisageControls.hpresent
- ✅ Editor uses Visage host
PluginEditor.hincludesVisageJuceHost.h- Editor inherits
VisagePluginEditor
- ✅ No WebView-only flags
NEEDS_WEBVIEW2 TRUEandJUCE_WEB_BROWSER=1not present
✅ WEBVIEW IMPLEMENTATION CHECKLIST (MANDATORY)
CRITICAL: When implementing WebView plugins, you MUST verify ALL 8 points below. Use templates from templates/webview/ and run validation script.
WebView Setup Validation (Run Before DSP Implementation)
Before proceeding to Phase 4.1, validate WebView setup:
# Run validation script
.\scripts\validate-webview-setup.ps1 -PluginName [Name]Mandatory Checklist (All Must Pass):
- ✅ CMakeLists.txt embeds web files
- Contains
juce_add_binary_data([Name]_WebUI ...) - Links binary data target:
target_link_libraries([Name] PRIVATE [Name]_WebUI ...) - Has
NEEDS_WEBVIEW2 TRUEinjuce_add_plugin() - Has compile definitions:
JUCE_WEB_BROWSER=1andJUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1
- Contains
- ✅ WebBrowserComponent uses WebView2 backend
.withBackend(WebBrowserComponent::Options::Backend::webview2)is present- NOT using default backend (must be explicit)
- ✅ WebBrowserComponent has user data folder
.withUserDataFolder(File::getSpecialLocation(File::SpecialLocationType::tempDirectory))is present- Required for Windows plugins to work
- ✅ Native integration enabled
.withNativeIntegrationEnabled()is present- Enables JavaScript ↔ C++ communication
- ✅ Resource provider implemented
.withResourceProvider([this](const auto& url) { return getResource(url); })is presentgetResource()function exists and loads from embedded zipgetZipFile()helper function exists
- ✅ Parameter relays created BEFORE WebBrowserComponent
- Relays created before
std::make_unique<WebBrowserComponent>() - Relays passed via
.withOptionsFrom(*relay)for each parameter
- Relays created before
- ✅ Parameter attachments created AFTER WebBrowserComponent
- Attachments created after
addAndMakeVisible(*webView) - Attachments connect parameters to relays
- Attachments created after
- ✅ Web content loaded via resource provider
- Uses
webView->goToURL(WebBrowserComponent::getResourceProviderRoot()) - NOT using
data:text/html;base64,...orloadHTML() - NOT using hardcoded HTML strings
- Uses
🔴 CRITICAL: Member Declaration Order (PluginEditor.h)
⚠️ #1 CAUSE OF DAW CRASHES - VERIFY THIS FIRST
C++ destroys members in REVERSE order of declaration. If WebView is declared before relays, it will be destroyed AFTER relays, causing a crash when it tries to access freed relay memory.
✅ CORRECT ORDER (in PluginEditor.h):
private:
// 1. RELAYS FIRST (destroyed last)
juce::WebSliderRelay gainRelay { "GAIN" };
// 2. WEBVIEW SECOND (destroyed middle)
std::unique_ptr<juce::WebBrowserComponent> webView;
// 3. ATTACHMENTS LAST (destroyed first)
std::unique_ptr<juce::WebSliderParameterAttachment> gainAttachment;❌ WRONG ORDER (causes DAW crash on unload):
private:
std::unique_ptr<juce::WebBrowserComponent> webView; // ❌ Too early!
juce::WebSliderRelay gainRelay { "GAIN" }; // ❌ Too late!Verification: Run validation script before building:
.\scripts\validate-webview-member-order.ps1 -PluginName [Name]See: .agents/troubleshooting/resolutions/webview-member-order-crash.md
Common Mistakes to Avoid:
❌ DON'T: Declare webView before relays in header file ✅ DO: Always use order: Relays → WebView → Attachments
❌ DON'T: Use data URIs (data:text/html;base64,...)
✅ DO: Use getResourceProviderRoot() with embedded files
❌ DON'T: Create WebBrowserComponent without WebView2 backend
✅ DO: Explicitly specify .withBackend(webview2)
❌ DON'T: Create parameter attachments before WebBrowserComponent ✅ DO: Create relays → WebBrowserComponent → attachments (in that order)
❌ DON'T: Skip resource provider
✅ DO: Implement getResource() function to serve embedded files
❌ DON'T: Forget to embed web files in CMakeLists.txt
✅ DO: Use juce_add_binary_data() to embed all web UI files
Template Usage:
Copy templates from: templates/webview/
PluginEditor.h.template→Source/PluginEditor.hPluginEditor.cpp.template→Source/PluginEditor.cppCMakeLists.txt.template→CMakeLists.txt
Replace placeholders:
{{PLUGIN_NAME}}→ Your plugin class name{{PLUGIN_NAME_LOWER}}→ Lowercase plugin name{{PARAMETER_RELAYS}}→ Your parameter relay declarations{{CREATE_PARAMETER_RELAYS}}→ Code to create relays{{WITH_OPTIONS_FROM_RELAYS}}→.withOptionsFrom()calls{{CREATE_PARAMETER_ATTACHMENTS}}→ Code to create attachments
CLAP placeholders (opt-in — read status.json -> formats first):
{{CLAP_ENABLED}}→ONonly whenformats.clapistrue, elseOFF{{CLAP_ID}}→formats.clap_id(assigned during /apc-plan, e.g.com.noizefield.myplugin){{CLAP_FEATURES}}→formats.clap_features(e.g.audio-effect; synth:instrument synthesizer)- If the plugin did NOT opt in, the emitted block must leave
{{PLUGIN_NAME_LOWER}}_ENABLE_CLAP OFF.
If Validation Fails:
- Review error messages from validation script
- Check templates in
templates/webview/ - Compare your code with JUCE example:
_tools/JUCE/examples/Plugins/WebViewPluginDemo.h - Ensure
Source/ui/public/index.htmlexists with ALL JavaScript inline (no separate .js files) - Verify CMakeLists.txt embeds files correctly
DO NOT PROCEED TO DSP IMPLEMENTATION UNTIL ALL 8 CHECKS PASS
🔧 PHASE 4.1: DSP IMPLEMENTATION
Read $PluginPath/.ideas/plan.md to determine implementation approach:
Complexity Score: [N]
If score ≤2: Single-pass implementation (all at once)
If score ≥3: Phased implementation (multiple passes)Single-pass (Simple plugins):
- One implementation session
- All DSP components at once
- Example: Simple gain, filter, compressor
Phased (Complex plugins):
- Multiple implementation phases
- Break into logical chunks
- Example: Multi-band processing, synthesis engines
🔧 PHASE 4.1: DSP IMPLEMENTATION
Prerequisites: UI structure must be created (Phase 4.0) before DSP implementation begins.
FOR SINGLE-PASS (Complexity ≤2):
Step 1: Read contracts
.ideas/creative-brief.md- Plugin purpose and behavior.ideas/architecture.md- DSP components and math.ideas/parameter-spec.md- Parameter bindings
Step 2: Update PluginProcessor.h
Add DSP member variables:
private:
// DSP Components from .ideas/architecture.md
juce::dsp::Gain inputGain;
juce::dsp::IIR::Filter filter;
juce::dsp::Compressor compressor;
// State
double currentSampleRate = 44100.0;Step 3: Implement prepareToPlay()
Initialize DSP at sample rate:
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
currentSampleRate = sampleRate;
juce::dsp::ProcessSpec spec;
spec.sampleRate = sampleRate;
spec.maximumBlockSize = samplesPerBlock;
spec.numChannels = getTotalNumOutputChannels();
inputGain.prepare(spec);
filter.prepare(spec);
compressor.prepare(spec);
}Step 4: Implement processBlock()
Add DSP processing:
void processBlock(juce::AudioBuffer& buffer,
juce::MidiBuffer& midiMessages) override
{
juce::ScopedNoDenormals noDenormals;
// Get parameter values from APVTS
auto gainValue = apvts.getRawParameterValue("gain")->load();
auto thresholdValue = apvts.getRawParameterValue("threshold")->load();
// Update DSP components
inputGain.setGainDecibels(gainValue);
compressor.setThreshold(thresholdValue);
// Process audio
juce::dsp::AudioBlock block(buffer);
juce::dsp::ProcessContextReplacing context(block);
inputGain.process(context);
filter.process(context);
compressor.process(context);
}Step 5: Connect parameters to DSP
Ensure all parameters from .ideas/parameter-spec.md are:
- Read from APVTS in processBlock using the unified parameter handling system
- Applied to DSP components with proper validation and smoothing
- Mapped correctly (linear, logarithmic, exponential as specified)
- Consistent with parameter ranges defined in the specification
Consistency Verification:
// Verify parameter consistency during development
#ifdef DEBUG
void verifyParameterConsistency()
{
// Check that parameter IDs match between spec and implementation
// This helps catch typos and ensures all specified parameters are implemented
const auto& parameters = apvts.processor.getParameters();
for (int i = 0; i < parameters.size(); ++i)
{
auto* param = dynamic_cast<juce::AudioProcessorParameterWithID*>(parameters[i]);
if (param != nullptr)
{
// Verify this parameter exists in parameter-spec.md
// (Implementation would read and parse the spec file)
}
}
}
#endifFOR PHASED (Complexity ≥3):
plan.md will define phases like:
### Phase 4.1.1: Core Processing
- Input/output gain
- Basic filtering
### Phase 4.1.2: Dynamics
- Compressor
- Limiter
### Phase 4.1.3: Modulation
- LFO
- Envelope followerExecute each phase sequentially:
- Phase 4.1.1 - Implement core components
- Add member variables
- Update prepareToPlay()
- Add to processBlock()
- Build and test
- Git commit
- Phase 4.1.2 - Add dynamics components
- Build on Phase 4.1.1 code (preserve everything)
- Add new components
- Build and test
- Git commit
- Phase 4.1.3 - Add modulation
- Build on Phase 4.1.2 code
- Complete implementation
- Build and test
- Git commit
Decision menu after each phase:
✓ Phase 4.1.1 complete
Progress: 1 of 3 phases
What's next?
1. Continue to Phase 4.1.2 (recommended)
2. Test current state in DAW
3. Review Phase 4.1.1 code
4. Pause here
Choose (1-4): _🎯 CRITICAL IMPLEMENTATION RULES
Real-Time Safety:
- NO heap allocations in processBlock() - Pre-allocate in prepareToPlay()
- NO locks in audio thread - Use atomic values for parameter access
- NO file I/O in processBlock() - All resources loaded beforehand
- Use
juce::ScopedNoDenormalsat start of processBlock()
🔄 UNIFIED PARAMETER HANDLING SYSTEM
Parameter Validation & Range Checking
// Helper function for parameter validation
float validateParameter(float value, float minVal, float maxVal, const char* paramName)
{
if (value < minVal || value > maxVal)
{
// Log warning but clamp to valid range
jassertfalse; // Debug warning
return juce::jlimit(minVal, maxVal, value);
}
return value;
}
// In processBlock() - with validation
auto* gainParam = apvts.getRawParameterValue("gain");
float rawGainValue = gainParam->load();
float validatedGain = validateParameter(rawGainValue, -60.0f, 24.0f, "gain");
inputGain.setGainDecibels(validatedGain);Parameter Smoothing (Anti-Zipper Noise)
// Member variables for smoothed parameters
juce::SmoothedValue<float> smoothedGain;
juce::SmoothedValue<float> smoothedCutoff;
juce::SmoothedValue<float> smoothedResonance;
// In prepareToPlay()
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
// Initialize smoothing with appropriate time constants
smoothedGain.reset(sampleRate, 0.020); // 20ms for gain
smoothedCutoff.reset(sampleRate, 0.050); // 50ms for filter
smoothedResonance.reset(sampleRate, 0.010); // 10ms for resonance
}
// In processBlock() - with smoothing
auto* gainParam = apvts.getRawParameterValue("gain");
smoothedGain.setTargetValue(gainParam->load());
for (int sample = 0; sample < numSamples; ++sample)
{
float currentGain = smoothedGain.getNextValue();
// Apply to DSP with per-sample smoothing
inputGain.setGainDecibels(currentGain);
}Parameter Mapping Templates
// Template for different parameter types
enum class ParameterType
{
Linear,
Logarithmic,
Exponential,
Boolean
};
// Parameter mapping helper
float mapParameter(float normalizedValue, ParameterType type, float minVal, float maxVal)
{
switch (type)
{
case ParameterType::Linear:
return juce::jmap(normalizedValue, 0.0f, 1.0f, minVal, maxVal);
case ParameterType::Logarithmic:
return juce::jmap(juce::jlimit(0.0f, 1.0f, normalizedValue),
0.0f, 1.0f, minVal, maxVal, true);
case ParameterType::Exponential:
return minVal * std::pow(maxVal / minVal, normalizedValue);
case ParameterType::Boolean:
return normalizedValue > 0.5f ? maxVal : minVal;
default:
return normalizedValue;
}
}
// Usage example
auto* cutoffParam = apvts.getRawParameterValue("cutoff");
float normalizedCutoff = cutoffParam->load();
float mappedCutoff = mapParameter(normalizedCutoff,
ParameterType::Logarithmic,
20.0f, 20000.0f);
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
sampleRate, mappedCutoff);Consistency Verification
// Helper to verify parameter consistency between spec and implementation
void verifyParameterConsistency()
{
// This should be called during development/debug builds
#ifdef DEBUG
// Check that all parameters in parameter-spec.md are implemented
// This is a development-time check, not runtime
#endif
}Parameter Binding:
// Standardized parameter binding pattern
void processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) override
{
juce::ScopedNoDenormals noDenormals;
// 1. Read all parameters with validation
auto* gainParam = apvts.getRawParameterValue("gain");
auto* cutoffParam = apvts.getRawParameterValue("cutoff");
auto* resonanceParam = apvts.getRawParameterValue("resonance");
// 2. Update smoothed values
smoothedGain.setTargetValue(gainParam->load());
smoothedCutoff.setTargetValue(cutoffParam->load());
smoothedResonance.setTargetValue(resonanceParam->load());
// 3. Process audio with per-sample parameter updates
const int numSamples = buffer.getNumSamples();
for (int sample = 0; sample < numSamples; ++sample)
{
// Get current smoothed values
float currentGain = smoothedGain.getNextValue();
float currentCutoff = smoothedCutoff.getNextValue();
float currentResonance = smoothedResonance.getNextValue();
// Apply to DSP components
inputGain.setGainDecibels(currentGain);
// Update filter with mapped values
float mappedCutoff = mapParameter(currentCutoff,
ParameterType::Logarithmic,
20.0f, 20000.0f);
float mappedResonance = mapParameter(currentResonance,
ParameterType::Linear,
0.1f, 10.0f);
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
currentSampleRate, mappedCutoff, mappedResonance);
// Process this sample
juce::dsp::AudioBlock block(buffer.getArrayOfWritePointers(),
buffer.getNumChannels(), 1);
juce::dsp::ProcessContextReplacing context(block);
inputGain.process(context);
filter.process(context);
}
}Edge Cases:
// Handle zero-length buffers
if (buffer.getNumSamples() == 0)
return;
// Handle silent input
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());Buffer Management:
// Pre-allocate buffers in prepareToPlay()
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
tempBuffer.setSize(2, samplesPerBlock);
// Use tempBuffer in processBlock() - no allocation
}✅ PHASE 4.2: BUILD & VERIFY
After implementation complete:
Step 1: Validate JUCE/CMake setup
# Import state management module
. "$PSScriptRoot\..\scripts\state-management.ps1"
$PluginPath = Get-ApcPluginPath -PluginName $PluginName
# Validate prerequisites using standardized function
if (-not (Validate-PhasePrerequisites -PluginPath $PluginPath -CurrentPhase "code" -RequiredPhase "design_complete" -RequiredFiles @(".ideas/architecture.md", ".ideas/plan.md"))) {
Write-Host "ERROR: Prerequisites not met. Complete design phase first." -ForegroundColor Red
exit 1
}
# Check JUCE installation
if (-not (Test-Path "C:\JUCE")) {
Write-Host "ERROR: JUCE not found at C:\JUCE" -ForegroundColor Red
Write-Host "Please install JUCE 9 (pin 9.0.1) and set up the project correctly" -ForegroundColor Yellow
exit 1
}
# Check CMake availability
if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) {
Write-Host "ERROR: CMake not found" -ForegroundColor Red
Write-Host "Please install CMake and ensure it's in your PATH" -ForegroundColor Yellow
exit 1
}
# Validate project structure
if (-not (Test-Path "CMakeLists.txt")) {
Write-Host "ERROR: CMakeLists.txt not found in project root" -ForegroundColor Red
exit 1
}
# Validate canvas implementation for WebView framework
if ($state.ui_framework -eq "webview") {
if (-not (Test-CanvasImplementation -PluginPath $PluginPath)) {
Write-Host "ERROR: Canvas implementation required for WebView framework" -ForegroundColor Red
Write-Host "WebView plugins must use HTML5 Canvas API with JUCE frontend library" -ForegroundColor Yellow
Write-Host "Please ensure Design/index.html uses canvas-based rendering" -ForegroundColor Yellow
exit 1
}
}Step 2: Build plugin
# Build plugin with validation
try {
powershell -ExecutionPolicy Bypass -File .\scripts\build-and-install.ps1 -PluginName [Name]
Write-Host "Build completed successfully" -ForegroundColor Green
} catch {
Write-Host "Build failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Please check the build script and JUCE configuration" -ForegroundColor Yellow
exit 1
}If build fails:
- Read error messages carefully
- Verify JUCE modules are properly included
- Ensure CMakeLists.txt is correctly configured
- Fix issues and rebuild
If build succeeds:
- Plugin compiles with DSP processing
- Parameters control audio engine
- JUCE/CMake integration validated
- Ready for testing
🧪 PHASE 4.3: AUTOMATED TESTING
Run 5 automated tests:
- Build test - Compiles successfully (already passed)
- Load test - Plugin loads in DAW without crash
- Process test - Audio processing works
- Parameter test - Parameters affect audio output
- State test - Save/load preserves settings
If tests fail: Cannot proceed to SHIP phase until audio engine is stable.
🎨 PHASE 4.4: GUI DECISION GATE
CRITICAL CHOICE: Custom UI or headless?
✓ Audio Engine Working
DSP components: [N]
Parameters: [N]
Tests: All passed
What type of interface?
1. Add custom UI - WebView interface with mockup
2. Ship headless - DAW controls only (fast path)
3. Test in DAW first
Choose (1-3): _Option 1: Custom UI Path
- Check for existing mockup in
.ideas/mockups/ - If none: Invoke ui-mockup skill
- If exists: Proceed to SHIP phase (Phase 5)
Option 2: Headless Path
- Generate minimal PluginEditor (simple window)
- DAW provides parameter controls automatically
- Fast path to v1.0.0
- Can add custom UI later via
/improve [Name]
Option 3: Test First
- Load plugin in DAW
- Verify DSP behavior
- Return to decision menu
📦 PHASE 4.5: STATE MANAGEMENT
Ensure plugin state saves/loads correctly:
// getStateInformation()
void getStateInformation(juce::MemoryBlock& destData) override
{
auto state = apvts.copyState();
std::unique_ptr xml(state.createXml());
copyXmlToBinary(*xml, destData);
}
// setStateInformation()
void setStateInformation(const void* data, int sizeInBytes) override
{
std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes));
if (xmlState.get() != nullptr)
if (xmlState->hasTagName(apvts.state.getType()))
apvts.replaceState(juce::ValueTree::fromXml(*xmlState));
}Critical: State must preserve all parameter values between sessions.
🔄 VERSIONING & COMMITS
Git commit after each phase:
# Backup state before commit
Backup-PluginState -PluginPath $PluginPath
git add $PluginPath/Source/
git add $PluginPath/.ideas/plan.md
git commit -m "feat([Name]): Phase 4.1 DSP - [Phase description]
Implemented: [list components]
Parameters connected: [N]
Real-time safe: Yes
Generated with Kilo Code"For single-pass:
# Backup state before final commit
Backup-PluginState -PluginPath $PluginPath
git commit -m "feat([Name]): Phase 4 CODE complete
All DSP components implemented
[N] parameters connected
Real-time safe audio processing
Generated with Kilo Code"Update state after implementation:
# Mark implementation complete using standardized function
Complete-Phase -PluginPath $PluginPath -Phase "code" -Updates @{
"validation.code_complete" = $true
"validation.tests_passed" = $false # Will be set after testing
}🎵 COMMON DSP PATTERNS
Gain/Volume Control:
juce::dsp::Gain gain;
gain.setGainDecibels(dbValue);
gain.process(context);Filtering:
juce::dsp::IIR::Filter filter;
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
sampleRate, cutoffFreq, resonance);
filter.process(context);Compression:
juce::dsp::Compressor compressor;
compressor.setThreshold(thresholdDb);
compressor.setRatio(ratio);
compressor.setAttack(attackMs);
compressor.setRelease(releaseMs);
compressor.process(context);Smoothing (Anti-Zipper):
juce::SmoothedValue smoothedGain;
smoothedGain.reset(sampleRate, 0.05); // 50ms ramp
// In processBlock
smoothedGain.setTargetValue(newGainValue);
for (int sample = 0; sample < numSamples; ++sample)
{
float currentGain = smoothedGain.getNextValue();
// Apply currentGain to audio
}📚 INTEGRATION
Invoked by:
- Natural language: "Implement DSP for [Name]"
- After Phase 3 (DESIGN) complete
- Part of plugin-workflow automation
Updates:
Source/PluginProcessor.h- DSP member variablesSource/PluginProcessor.cpp- Audio processing logicPLUGINS.md- Phase status$PluginPath/status.json- Project state
Next phase:
- Phase 5: SHIP (if headless chosen or custom UI complete)
⚠️ CRITICAL REMINDERS
- Real-time safety - No allocations in audio thread
- Parameter zero-drift - Use exact IDs from parameter-spec.md
- Use unified parameter system - Always validate, smooth, and map parameters
- Test after each phase - Verify before continuing
- Commit frequently - Preserve progress
- Edge cases - Handle silent input, zero buffers
- Build from root - Not from plugin directory
- Consistency check - Verify all spec parameters are implemented
🐛 TROUBLESHOOTING
Build errors:
- Verify JUCE module includes
- Check parameter ID typos
No audio output:
- Verify processBlock() called
- Check channel routing
- Verify DSP components initialized in prepareToPlay()
Crackling/artifacts:
- Add parameter smoothing
- Check buffer size handling
- Verify real-time safety (no allocations)
Parameters don't respond:
- Verify APVTS getRawParameterValue() calls
- Check parameter ID strings match spec
- Verify parameter ranges correct
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

