| name | add-source-machine |
| description | How to add a new source machine to Smart Grid One. Covers VoiceMachineEnums, creating the DSP class, integrating into SquiggleBoySource, SetEncoderParameters wiring, and MachineFlags updates. Use when adding a new oscillator, synth engine, or audio source. |
Adding a New Source Machine to Smart Grid One
Overview
A source machine generates audio before it reaches the filter. Examples: DualWaveShapingVCO (wavetable/phase-shaping VCO), PhysicalModeling (noise-excited comb filter), Thru (passes external audio). Adding a new source machine requires:
- Update the enum in
VoiceMachineEnums.hpp
- Create a DSP class file for the new machine
- Add the machine instance and input struct to
SquiggleBoySource
- Integrate into
ProcessUBlock and dispatch in SetEncoderParams in SquiggleBoySource
- Update machine flags (
MachineFlags.hpp and VoiceMachineEnums.hpp) if the machine count changes
- Add machine-specific parameters to
ForEachSmartGridOneParam.hpp
- If the source has an internal AHD envelope, connect it in
SquiggleBoyVoice
Step 1: Update VoiceMachineEnums.hpp
Add your new machine to the SourceMachine enum and update NumSourceMachines:
enum class SourceMachine : int
{
DualWaveShapingVCO = 0,
Thru = 1,
PhysicalModeling = 2,
MyNewSource = 3,
NumSourceMachines = 4,
};
Add a case to the ToString function:
inline const char* ToString(SourceMachine machine)
{
switch (machine)
{
case SourceMachine::DualWaveShapingVCO:
return "Dual VCO";
case SourceMachine::Thru:
return "Thru";
case SourceMachine::PhysicalModeling:
return "PhysMod";
case SourceMachine::MyNewSource:
return "MySource";
}
return "Unknown";
}
If you add a new source-machine enum value, also update VoiceMachine::SourceMachineFlags in VoiceMachineEnums.hpp:
- add a bit constant for the new machine
- include it in
x_all
- add a helper (for example
MyNewSourceOnly()) if needed for visualizer gating
Step 2: Create the DSP Class
Create a new header file private/src/MyNewSource.hpp following the pattern of DualWaveShapingVCO.hpp or PhysicalModelingSource.hpp:
#pragma once
#include "PhaseUtils.hpp"
#include "SampleTimer.hpp"
#include "Slew.hpp"
struct MyNewSource
{
static constexpr size_t x_oversample = 4;
static constexpr size_t x_uBlockSize = SampleTimer::x_controlFrameRate * x_oversample;
bool m_uBlockTop[SampleTimer::x_controlFrameRate];
float m_uBlockOutput[x_uBlockSize];
float m_phase;
ParamSlew m_myParamSlew;
struct Input
{
float m_baseFreq;
float m_myParam;
Input()
: m_baseFreq(PhaseUtils::VOctToNatural(0.0, 1.0 / 48000.0))
, m_myParam(0.5f)
{
}
};
MyNewSource()
: m_phase(0.0f)
, m_myParamSlew(x_oversample)
{
}
void ProcessUBlock(const Input& input)
{
m_myParamSlew.Update(input.m_myParam);
for (size_t i = 0; i < x_uBlockSize; ++i)
{
float myParam = m_myParamSlew.Process();
float baseFreq = input.m_baseFreq / x_oversample;
m_phase += baseFreq;
if (m_phase >= 1.0f)
{
m_phase -= 1.0f;
}
m_uBlockOutput[i] = ;
if ((i + 1) % x_oversample == 0)
{
size_t baseIndex = i / x_oversample;
m_uBlockTop[baseIndex] = ;
}
}
}
};
Key requirements:
m_uBlockOutput[x_uBlockSize]: oversampled audio output buffer
m_uBlockTop[SampleTimer::x_controlFrameRate]: cycle-top flags for filter key tracking
- Use
ParamSlew for any parameter consumed in the oversampled loop
Step 3: Add to SquiggleBoySource
In private/src/SquiggleBoySource.hpp, add the include, member, and input struct:
#include "DualWaveShapingVCO.hpp"
#include "MyNewSource.hpp"
DualWaveShapingVCO m_dualWaveShapingVCO;
PhysicalModelingSource m_physicalModeling;
MyNewSource m_myNewSource;
DualWaveShapingVCO::Input m_dualWaveShapingVCOInput;
PhysicalModelingSource::Input m_physicalModelingInput;
MyNewSource::Input m_myNewSourceInput;
Step 4: Integrate into ProcessUBlock and SetEncoderParams
In SquiggleBoySource::ProcessUBlock, add a case for your machine:
void ProcessUBlock(Input& input)
{
switch (input.m_sourceMachine)
{
case SourceMachine::DualWaveShapingVCO:
{
m_dualWaveShapingVCO.ProcessUBlock(input.m_dualWaveShapingVCOInput);
m_uBlockOutput = m_dualWaveShapingVCO.m_uBlockOutput;
m_uBlockTop = m_dualWaveShapingVCO.m_uBlockTop;
break;
}
case SourceMachine::MyNewSource:
{
m_myNewSource.ProcessUBlock(input.m_myNewSourceInput);
m_uBlockOutput = m_myNewSource.m_uBlockOutput;
m_uBlockTop = m_myNewSource.m_uBlockTop;
break;
}
default:
break;
}
}
In SquiggleBoySource::SetEncoderParams, add a case that dispatches to the machine's own setter:
void SetEncoderParams(
SmartGridOneEncoders& encoders,
Input& input,
size_t voiceIx,
float baseFreq,
float wtBlend0,
float wtBlend1)
{
switch (input.m_sourceMachine)
{
case SourceMachine::MyNewSource:
{
m_myNewSource.SetEncoderParams(encoders, input.m_myNewSourceInput, voiceIx, baseFreq);
break;
}
default:
break;
}
}
Recommended pattern: keep machine-specific parameter mapping inside the machine class (MyNewSource::SetEncoderParams) to keep SquiggleBoySource as a thin dispatcher.
Step 5: Update Machine Flags
If the total number of source machines changes, update MachineFlags.hpp:
static constexpr uint8_t x_all = 0b1111;
static constexpr uint8_t x_dualWaveShapingVCOOnly = 0b0001;
static constexpr uint8_t x_physicalModelingOnly = 0b0100;
static constexpr uint8_t x_myNewSourceOnly = 0b1000;
static constexpr size_t x_numSourceMachines = 4;
Also update VoiceMachine::SourceMachineFlags in VoiceMachineEnums.hpp so source-specific visualizers can gate correctly.
Step 6: Add Machine-Specific Parameters
In ForEachSmartGridOneParam.hpp, add parameters for your machine using the machine-specific flag:
F(MyNewParam, MNP, Source, 0, 0, 0.5, "My New Param", SmartGrid::Color::Cyan,
MachineFlags::x_myNewSourceOnly,
MachineFlags::x_all)
Parameters at the same (x, y) position can coexist if they have different machine flags - only the matching machine's params will appear.
Step 7: Connect Internal AHD (if applicable)
If your source has an internal AHD envelope (like PhysicalModeling), you need to:
- In
SquiggleBoyVoice::Input::SetGates(), set the AHD input from the voice's AHDControl:
void SetGates()
{
m_filterInput.m_ahdInput.Set(m_ahdControl);
m_ampInput.m_ahdInput.Set(m_ahdControl);
m_sourceInput.m_myNewSourceInput.m_ahdInput.Set(m_ahdControl);
}
- In
SquiggleBoyVoice::Processs(), process the AHD at control rate:
float Processs(Input& input)
{
if (input.m_voiceConfig.m_sourceMachine == VoiceConfig::SourceMachine::MyNewSource)
{
m_source.m_myNewSource.m_ahd.Process(input.m_sourceInput.m_myNewSourceInput.m_ahdInput);
}
}
Files Modified
| File | Changes |
|---|
private/src/VoiceMachineEnums.hpp | Add enum value, update count, add ToString case |
private/src/MyNewSource.hpp | Create new file with DSP implementation |
private/src/SquiggleBoySource.hpp | Add include, member, input struct, ProcessUBlock case, SetEncoderParams dispatch |
private/src/MachineFlags.hpp | Update x_numSourceMachines, update x_all, add bitmask constant |
private/src/VoiceMachineEnums.hpp | Update SourceMachineFlags constants/helpers for visualizer gating |
private/src/ForEachSmartGridOneParam.hpp | Add machine-specific parameters |
private/src/SquiggleBoy.hpp | (Optional) Connect internal AHD in SetGates and Processs |
Checklist