2026-08-05 19:55:57 +02:00
# include "PluginProcessor.h"
# ifndef MONOSTEP_HEADLESS
# include "PluginEditor.h"
# endif
static const char * paramId ( int index )
{
static const char * ids [ ] =
{
2026-08-05 23:12:17 +02:00
" osc1Wave " , " osc2Wave " , " osc1Coarse " , " osc2Coarse " , " detune " , " mix " ,
2026-08-06 15:51:56 +02:00
" cutoff " , " res " , " filterType " , " attack " , " decay " , " sustain " , " release " , " glide " , " master " ,
2026-08-05 23:12:17 +02:00
" seqLen " , " seqRate " , " seqRoot " , " seqOctave " , " seqSwing " , " seqGateLen " ,
2026-08-06 15:51:56 +02:00
" drive " , " ringmod " , " accent " , " fine1 " ,
" filterAttack " , " filterDecay " , " filterSustain " , " filterRelease " , " filterEnvAmt " ,
2026-08-06 16:35:34 +02:00
" seqRetrig " ,
2026-08-06 18:38:32 +02:00
" delayTime " , " delayStrength " , " delaySync " , " delayDryWet " ,
" reverbRoom " , " reverbStrength " , " reverbDiffusion " , " reverbDryWet "
2026-08-05 19:55:57 +02:00
} ;
return ids [ index ] ;
}
enum ParamIndex
{
2026-08-05 23:12:17 +02:00
pOsc1Wave , pOsc2Wave , pOsc1Coarse , pOsc2Coarse , pDetune , pMix ,
2026-08-06 15:51:56 +02:00
pCutoff , pRes , pFilterType , pAttack , pDecay , pSustain , pRelease , pGlide , pMaster ,
2026-08-05 19:55:57 +02:00
pSeqLen , pSeqRate , pSeqRoot , pSeqOctave , pSeqSwing , pSeqGateLen ,
2026-08-05 23:12:17 +02:00
pDrive , pRingMod , pAccent , pFine1 ,
2026-08-06 15:51:56 +02:00
pFilterAttack , pFilterDecay , pFilterSustain , pFilterRelease , pFilterEnvAmt ,
pSeqRetrig ,
2026-08-06 18:38:32 +02:00
pDelayTime , pDelayStrength , pDelaySync , pDelayDryWet ,
pReverbRoom , pReverbStrength , pReverbDiffusion , pReverbDryWet ,
2026-08-05 19:55:57 +02:00
numParams
} ;
2026-08-05 23:12:17 +02:00
MonostepAudioProcessor : : MonostepAudioProcessor ( )
2026-08-05 19:55:57 +02:00
: AudioProcessor ( BusesProperties ( )
. withOutput ( " Output " , juce : : AudioChannelSet : : stereo ( ) , true ) )
, apvts ( * this , nullptr , " PARAMS " , createParameterLayout ( ) )
{
osc1WaveParam = apvts . getRawParameterValue ( paramId ( pOsc1Wave ) ) ;
osc2WaveParam = apvts . getRawParameterValue ( paramId ( pOsc2Wave ) ) ;
2026-08-05 23:12:17 +02:00
osc1CoarseParam = apvts . getRawParameterValue ( paramId ( pOsc1Coarse ) ) ;
osc2CoarseParam = apvts . getRawParameterValue ( paramId ( pOsc2Coarse ) ) ;
2026-08-05 19:55:57 +02:00
detuneParam = apvts . getRawParameterValue ( paramId ( pDetune ) ) ;
mixParam = apvts . getRawParameterValue ( paramId ( pMix ) ) ;
2026-08-05 23:12:17 +02:00
osc1PhaseParam = apvts . getRawParameterValue ( paramId ( pFine1 ) ) ;
2026-08-05 19:55:57 +02:00
cutoffParam = apvts . getRawParameterValue ( paramId ( pCutoff ) ) ;
resParam = apvts . getRawParameterValue ( paramId ( pRes ) ) ;
2026-08-06 15:51:56 +02:00
filterTypeParam = apvts . getRawParameterValue ( paramId ( pFilterType ) ) ;
2026-08-05 19:55:57 +02:00
attackParam = apvts . getRawParameterValue ( paramId ( pAttack ) ) ;
decayParam = apvts . getRawParameterValue ( paramId ( pDecay ) ) ;
sustainParam = apvts . getRawParameterValue ( paramId ( pSustain ) ) ;
releaseParam = apvts . getRawParameterValue ( paramId ( pRelease ) ) ;
2026-08-06 15:51:56 +02:00
filterAttackParam = apvts . getRawParameterValue ( paramId ( pFilterAttack ) ) ;
filterDecayParam = apvts . getRawParameterValue ( paramId ( pFilterDecay ) ) ;
filterSustainParam = apvts . getRawParameterValue ( paramId ( pFilterSustain ) ) ;
filterReleaseParam = apvts . getRawParameterValue ( paramId ( pFilterRelease ) ) ;
filterEnvAmtParam = apvts . getRawParameterValue ( paramId ( pFilterEnvAmt ) ) ;
2026-08-05 19:55:57 +02:00
glideParam = apvts . getRawParameterValue ( paramId ( pGlide ) ) ;
masterParam = apvts . getRawParameterValue ( paramId ( pMaster ) ) ;
seqLenParam = apvts . getRawParameterValue ( paramId ( pSeqLen ) ) ;
seqRateParam = apvts . getRawParameterValue ( paramId ( pSeqRate ) ) ;
seqRootParam = apvts . getRawParameterValue ( paramId ( pSeqRoot ) ) ;
seqOctaveParam = apvts . getRawParameterValue ( paramId ( pSeqOctave ) ) ;
seqSwingParam = apvts . getRawParameterValue ( paramId ( pSeqSwing ) ) ;
seqGateLenParam = apvts . getRawParameterValue ( paramId ( pSeqGateLen ) ) ;
2026-08-05 23:12:17 +02:00
driveParam = apvts . getRawParameterValue ( paramId ( pDrive ) ) ;
ringModParam = apvts . getRawParameterValue ( paramId ( pRingMod ) ) ;
accentParam = apvts . getRawParameterValue ( paramId ( pAccent ) ) ;
2026-08-06 15:51:56 +02:00
seqRetrigParam = apvts . getRawParameterValue ( paramId ( pSeqRetrig ) ) ;
2026-08-06 16:35:34 +02:00
delayTimeParam = apvts . getRawParameterValue ( paramId ( pDelayTime ) ) ;
delayStrengthParam = apvts . getRawParameterValue ( paramId ( pDelayStrength ) ) ;
delaySyncParam = apvts . getRawParameterValue ( paramId ( pDelaySync ) ) ;
2026-08-06 18:38:32 +02:00
delayDryWetParam = apvts . getRawParameterValue ( paramId ( pDelayDryWet ) ) ;
2026-08-06 16:35:34 +02:00
reverbRoomParam = apvts . getRawParameterValue ( paramId ( pReverbRoom ) ) ;
reverbStrengthParam = apvts . getRawParameterValue ( paramId ( pReverbStrength ) ) ;
reverbDiffusionParam = apvts . getRawParameterValue ( paramId ( pReverbDiffusion ) ) ;
2026-08-06 18:38:32 +02:00
reverbDryWetParam = apvts . getRawParameterValue ( paramId ( pReverbDryWet ) ) ;
2026-08-05 19:55:57 +02:00
setDefaultPattern ( ) ;
}
2026-08-05 23:12:17 +02:00
juce : : AudioProcessorValueTreeState : : ParameterLayout MonostepAudioProcessor : : createParameterLayout ( )
2026-08-05 19:55:57 +02:00
{
juce : : AudioProcessorValueTreeState : : ParameterLayout layout ;
2026-08-05 23:45:59 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pOsc1Wave ) , " Osc 1 Wave " , 0 , monostep : : numWaveforms - 1 , 2 ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pOsc2Wave ) , " Osc 2 Wave " , 0 , monostep : : numWaveforms - 1 , 3 ) ) ;
2026-08-05 23:12:17 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pOsc1Coarse ) , " Osc 1 Coarse " , - 12 , 12 , 0 ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pOsc2Coarse ) , " Osc 2 Coarse " , - 12 , 12 , 0 ) ) ;
2026-08-05 19:55:57 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDetune ) ,
" Osc 2 Detune " , juce : : NormalisableRange < float > ( - 100.0f , 100.0f ) , 0.0f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " ct " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pMix ) ,
" Osc Mix " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.5f ) ) ;
2026-08-05 23:12:17 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFine1 ) ,
" Phase Offset " , juce : : NormalisableRange < float > ( - 1.0f , 1.0f ) , 0.0f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " ±1 " ) ) ) ;
2026-08-05 19:55:57 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pCutoff ) ,
" Filter Cutoff " , juce : : NormalisableRange < float > ( 40.0f , 16000.0f , 0.01f , 0.3f ) , 7000.0f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " Hz " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pRes ) ,
2026-08-06 15:51:56 +02:00
" Filter Res " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.15f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pFilterType ) ,
" Filter Type " , 0 ,
( int ) monostep : : SynthVoice : : FilterType : : numFilterTypes - 1 ,
0 ) ) ;
2026-08-05 19:55:57 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pAttack ) ,
" Attack " , juce : : NormalisableRange < float > ( 0.001f , 2.0f , 0.001f , 0.3f ) , 0.005f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDecay ) ,
" Decay " , juce : : NormalisableRange < float > ( 0.001f , 3.0f , 0.001f , 0.3f ) , 0.3f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pSustain ) ,
" Sustain " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.7f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pRelease ) ,
" Release " , juce : : NormalisableRange < float > ( 0.01f , 4.0f , 0.001f , 0.3f ) , 0.4f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
2026-08-06 15:51:56 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFilterAttack ) ,
" Filter Env Attack " , juce : : NormalisableRange < float > ( 0.001f , 2.0f , 0.001f , 0.3f ) , 0.005f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFilterDecay ) ,
" Filter Env Decay " , juce : : NormalisableRange < float > ( 0.001f , 3.0f , 0.001f , 0.3f ) , 0.3f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFilterSustain ) ,
" Filter Env Sustain " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.7f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFilterRelease ) ,
" Filter Env Release " , juce : : NormalisableRange < float > ( 0.01f , 4.0f , 0.001f , 0.3f ) , 0.4f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pFilterEnvAmt ) ,
" Filter Env Amount " , juce : : NormalisableRange < float > ( - 4.0f , 4.0f ) , 1.0f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " oct " ) ) ) ;
2026-08-05 19:55:57 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pGlide ) ,
" Glide " , juce : : NormalisableRange < float > ( 0.0f , 1.0f , 0.001f , 0.5f ) , 0.12f ,
juce : : AudioParameterFloatAttributes ( ) . withLabel ( " s " ) ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pMaster ) ,
" Master " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.9f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pSeqLen ) , " Pattern Length " , 1 , 16 , 16 ) ) ;
juce : : StringArray rates { " 1/4 " , " 1/8 " , " 1/8T " , " 1/16 " , " 1/32 " } ;
layout . add ( std : : make_unique < juce : : AudioParameterChoice > ( paramId ( pSeqRate ) , " Rate " , rates , 3 ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterInt > ( paramId ( pSeqRoot ) , " Root " , - 24 , 24 , 0 ) ) ;
juce : : StringArray octaves { " -2 " , " -1 " , " 0 " , " +1 " , " +2 " } ;
layout . add ( std : : make_unique < juce : : AudioParameterChoice > ( paramId ( pSeqOctave ) , " Octave " , octaves , 3 ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pSeqSwing ) ,
" Swing " , juce : : NormalisableRange < float > ( 0.0f , 0.6f ) , 0.0f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pSeqGateLen ) ,
" Gate Length " , juce : : NormalisableRange < float > ( 0.05f , 1.0f ) , 0.8f ) ) ;
2026-08-06 15:51:56 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterBool > ( paramId ( pSeqRetrig ) ,
" Restart Pattern on Note " , false ) ) ;
2026-08-05 23:12:17 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDrive ) ,
" Distortion Drive " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.0f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pRingMod ) ,
" Ring Mod " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.0f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pAccent ) ,
" Accent " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.7f ) ) ;
2026-08-06 16:35:34 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDelayTime ) ,
" Delay Time " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.35f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDelayStrength ) ,
2026-08-06 18:38:32 +02:00
" Delay Feedback " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.5f ) ) ;
2026-08-06 16:35:34 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterBool > ( paramId ( pDelaySync ) ,
" Delay Sync " , true ) ) ;
2026-08-06 18:38:32 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pDelayDryWet ) ,
" Delay Dry/Wet " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.25f ) ) ;
2026-08-06 16:35:34 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pReverbRoom ) ,
" Reverb Roomsize " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.5f ) ) ;
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pReverbStrength ) ,
2026-08-06 18:38:32 +02:00
" Reverb Strength " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 1.0f ) ) ;
2026-08-06 16:35:34 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pReverbDiffusion ) ,
" Reverb Diffusion " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.6f ) ) ;
2026-08-06 18:38:32 +02:00
layout . add ( std : : make_unique < juce : : AudioParameterFloat > ( paramId ( pReverbDryWet ) ,
" Reverb Dry/Wet " , juce : : NormalisableRange < float > ( 0.0f , 1.0f ) , 0.25f ) ) ;
2026-08-05 19:55:57 +02:00
return layout ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setDefaultPattern ( )
2026-08-05 19:55:57 +02:00
{
struct Def { int step ; bool gate ; int semitone ; bool slide ; } ;
const Def defs [ ] =
{
{ 0 , true , 0 , false } ,
{ 1 , true , 0 , true } ,
{ 2 , true , 3 , false } ,
{ 4 , true , 5 , false } ,
{ 5 , true , 3 , true } ,
{ 6 , true , 0 , false } ,
{ 8 , true , 7 , false } ,
{ 9 , true , 5 , true } ,
{ 10 , true , 3 , false } ,
{ 12 , true , 0 , false } ,
{ 14 , true , - 2 , true } ,
} ;
for ( const auto & d : defs )
{
sequencer . step ( d . step ) . gate = d . gate ;
sequencer . step ( d . step ) . semitone = d . semitone ;
sequencer . step ( d . step ) . slide = d . slide ;
}
2026-08-05 23:12:17 +02:00
sequencer . step ( 0 ) . accent = true ;
sequencer . step ( 4 ) . accent = true ;
sequencer . step ( 8 ) . accent = true ;
2026-08-05 19:55:57 +02:00
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : applyParamValue ( const juce : : String & id , float value )
{
if ( auto * param = apvts . getParameter ( id ) )
{
param - > setValueNotifyingHost ( param - > getNormalisableRange ( ) . convertTo0to1 ( value ) ) ;
apvts . getParameterAsValue ( id ) = value ;
}
}
int MonostepAudioProcessor : : getNumPresets ( ) const
{
return 18 ;
}
const char * MonostepAudioProcessor : : getPresetName ( int index ) const
{
static const char * names [ ] =
{
" Wet Tape " ,
" Acid Wash " ,
" Tin Can " ,
" Bent Toy " ,
" Neon Arcade " ,
" Glitch Garden " ,
" Underwater FM " ,
" Robo Funk " ,
" Lo-Fi Noir " ,
" Spectral Sweep " ,
" Broken Radio " ,
" Plasma Organ " ,
" Glass Bells " ,
" Warm Pad " ,
" Pulse Groove " ,
" Steel Pluck " ,
" Sub Rumble " ,
" Tape Bounce "
} ;
return names [ juce : : jlimit ( 0 , getNumPresets ( ) - 1 , index ) ] ;
}
void MonostepAudioProcessor : : applyPreset ( int index )
{
struct Preset
{
int osc1 ; int osc2 ; int coarse ; float detune ; float mix ;
float cutoff ; float res ; float attack ; float decay ; float sustain ; float release ;
float glide ; float gateLen ; float swing ; int rate ; int root ; int octave ;
float drive ; float ring ; float accent ;
2026-08-06 15:51:56 +02:00
float fAttack ; float fDecay ; float fSustain ; float fRelease ; float fAmt ;
2026-08-06 18:38:32 +02:00
float delayTime = 0.35f ; // only audible when delayWet > 0
float delayFeedback = 0.45f ;
bool delaySync = true ;
float delayWet = 0.0f ;
float reverbRoom = 0.5f ; // only audible when reverbWet > 0
float reverbLevel = 0.6f ;
float reverbDiff = 0.6f ;
float reverbWet = 0.0f ;
2026-08-05 23:12:17 +02:00
} ;
static const Preset presets [ ] =
{
// --- gritty / glidy -----------------------------------------------------
2026-08-06 18:38:32 +02:00
{ 0 , 1 , 0 , 15.0f , 0.60f , 4200.0f , 0.15f , 0.02f , 0.25f , 0.70f , 0.35f , 0.25f , 0.80f , 0.10f , 2 , 0 , 3 , 0.55f , 0.00f , 0.5f , 0.02f , 0.30f , 0.60f , 0.25f , 1.5f , 0.21f , 0.55f , true , 0.50f , 0.60f , 0.60f , 0.50f , 0.15f } , // Wet Tape
2026-08-06 15:51:56 +02:00
{ 2 , 0 , - 12 , - 8.0f , 0.45f , 1100.0f , 0.65f , 0.003f , 0.40f , 0.10f , 0.08f , 0.12f , 0.90f , 0.00f , 3 , 0 , 3 , 0.85f , 0.00f , 0.4f , 0.005f , 0.35f , 0.10f , 0.10f , 3.0f } , // Acid Wash
{ 3 , 3 , 0 , 5.0f , 0.50f , 380.0f , 0.30f , 0.005f , 0.10f , 0.40f , 0.10f , 0.20f , 0.95f , 0.05f , 3 , 0 , 3 , 0.60f , 0.00f , 0.3f , 0.005f , 0.15f , 0.30f , 0.15f , 0.5f } , // Tin Can
{ 0 , 3 , 7 , 25.0f , 0.35f , 2500.0f , 0.20f , 0.04f , 0.20f , 0.30f , 0.60f , 0.45f , 0.85f , 0.25f , 2 , 0 , 3 , 0.40f , 0.00f , 0.5f , 0.03f , 0.25f , 0.50f , 0.40f , 1.0f } , // Bent Toy
{ 2 , 3 , 0 , 0.0f , 0.55f , 650.0f , 0.45f , 0.002f , 0.15f , 0.20f , 0.05f , 0.10f , 0.90f , 0.10f , 3 , 0 , 3 , 0.95f , 0.60f , 0.3f , 0.005f , 0.20f , 0.40f , 0.15f , 1.5f } , // Broken Radio
2026-08-06 18:38:32 +02:00
{ 1 , 2 , 0 , 20.0f , 0.50f , 6000.0f , 0.10f , 0.003f , 0.15f , 0.80f , 0.20f , 0.10f , 0.90f , 0.15f , 3 , 0 , 3 , 0.20f , 0.00f , 0.6f , 0.01f , 0.30f , 0.70f , 0.30f , 1.0f , 0.50f , 0.45f , true , 0.30f , 0.50f , 0.50f , 0.60f , 0.30f } , // Neon Arcade
{ 0 , 1 , - 12 , - 35.0f , 0.55f , 1800.0f , 0.40f , 0.10f , 0.35f , 0.20f , 0.80f , 0.50f , 0.70f , 0.20f , 2 , 0 , 3 , 0.00f , 0.00f , 0.5f , 0.01f , 0.40f , 0.50f , 0.20f , - 1.5f , 0.36f , 0.70f , true , 0.50f , 0.50f , 0.60f , 0.60f , 0.00f } , // Glitch Garden
{ 2 , 2 , 5 , 10.0f , 0.40f , 3200.0f , 0.35f , 0.002f , 0.20f , 0.60f , 0.12f , 0.15f , 0.95f , 0.40f , 3 , 0 , 3 , 0.70f , 0.20f , 0.5f , 0.01f , 0.25f , 0.60f , 0.20f , 2.0f , 0.36f , 0.50f , true , 0.40f , 0.50f , 0.60f , 0.60f , 0.00f } , // Robo Funk
{ 1 , 1 , - 7 , - 15.0f , 0.50f , 2600.0f , 0.20f , 0.03f , 0.30f , 0.35f , 0.60f , 0.60f , 0.80f , 0.05f , 2 , 0 , 3 , 0.10f , 0.00f , 0.4f , 0.02f , 0.30f , 0.50f , 0.30f , - 0.5f , 0.21f , 0.40f , true , 0.20f , 0.60f , 0.50f , 0.50f , 0.25f } , // Lo-Fi Noir
2026-08-06 15:51:56 +02:00
{ 0 , 2 , 0 , 8.0f , 0.60f , 7000.0f , 0.08f , 0.01f , 0.20f , 0.90f , 0.25f , 0.30f , 0.75f , 0.00f , 3 , 0 , 3 , 0.35f , 0.35f , 0.6f , 0.01f , 0.30f , 0.20f , 0.30f , 3.5f } , // Spectral Sweep
2026-08-06 18:38:32 +02:00
{ 3 , 0 , - 12 , 12.0f , 0.45f , 5000.0f , 0.25f , 0.02f , 0.50f , 0.70f , 0.30f , 0.40f , 0.60f , 0.20f , 2 , 0 , 3 , 0.30f , 0.15f , 0.5f , 0.02f , 0.40f , 0.80f , 0.30f , 0.5f , 0.35f , 0.45f , true , 0.00f , 0.70f , 0.80f , 0.70f , 0.40f } , // Plasma Organ
{ 0 , 0 , 0 , 0.0f , 0.50f , 900.0f , 0.50f , 0.05f , 0.30f , 0.50f , 0.50f , 0.35f , 0.85f , 0.30f , 2 , 0 , 3 , 0.25f , 0.55f , 0.3f , 0.05f , 0.30f , 0.50f , 0.40f , - 2.0f , 0.64f , 0.50f , true , 0.35f , 0.90f , 0.70f , 0.90f , 0.35f } , // Underwater FM
2026-08-05 23:12:17 +02:00
// --- clean / no glide / no drive ---------------------------------------
2026-08-06 15:51:56 +02:00
{ 0 , 0 , 0 , 0.0f , 0.50f , 7000.0f , 0.10f , 0.002f , 0.25f , 0.20f , 0.30f , 0.00f , 0.35f , 0.00f , 3 , 0 , 4 , 0.00f , 0.00f , 0.6f , 0.005f , 0.20f , 0.30f , 0.40f , 0.3f } , // Glass Bells
2026-08-06 18:38:32 +02:00
{ 1 , 1 , 0 , 12.0f , 0.60f , 1200.0f , 0.25f , 0.30f , 1.20f , 0.80f , 1.50f , 0.20f , 1.00f , 0.10f , 2 , 0 , 2 , 0.00f , 0.00f , 0.2f , 0.50f , 1.00f , 0.70f , 1.20f , 1.0f , 0.35f , 0.45f , true , 0.00f , 0.80f , 0.90f , 0.80f , 0.45f } , // Warm Pad
2026-08-06 15:51:56 +02:00
{ 2 , 2 , 0 , 5.0f , 0.50f , 2500.0f , 0.15f , 0.002f , 0.20f , 0.30f , 0.12f , 0.00f , 0.50f , 0.30f , 3 , 0 , 3 , 0.00f , 0.00f , 0.5f , 0.01f , 0.25f , 0.40f , 0.20f , 1.5f } , // Pulse Groove
{ 0 , 3 , 12 , 0.0f , 0.40f , 1500.0f , 0.60f , 0.001f , 0.35f , 0.15f , 0.20f , 0.00f , 0.45f , 0.00f , 3 , 0 , 4 , 0.00f , 0.35f , 0.7f , 0.002f , 0.30f , 0.20f , 0.25f , 2.5f } , // Steel Pluck
{ 0 , 0 , 0 , - 5.0f , 0.40f , 500.0f , 0.20f , 0.01f , 0.40f , 0.90f , 0.30f , 0.00f , 0.90f , 0.00f , 2 , 0 , 1 , 0.00f , 0.00f , 0.3f , 0.01f , 0.30f , 0.60f , 0.30f , - 1.0f } , // Sub Rumble
2026-08-06 18:38:32 +02:00
{ 1 , 0 , 0 , 8.0f , 0.55f , 3000.0f , 0.20f , 0.005f , 0.30f , 0.60f , 0.25f , 0.30f , 0.70f , 0.15f , 2 , 0 , 3 , 0.25f , 0.00f , 0.5f , 0.01f , 0.30f , 0.50f , 0.30f , 1.0f , 0.64f , 0.60f , true , 0.45f , 0.50f , 0.50f , 0.50f , 0.20f } , // Tape Bounce
2026-08-05 23:12:17 +02:00
} ;
const auto & pr = presets [ juce : : jlimit ( 0 , getNumPresets ( ) - 1 , index ) ] ;
applyParamValue ( " osc1Wave " , ( float ) pr . osc1 ) ;
applyParamValue ( " osc2Wave " , ( float ) pr . osc2 ) ;
applyParamValue ( " osc2Coarse " , ( float ) pr . coarse ) ;
applyParamValue ( " detune " , pr . detune ) ;
applyParamValue ( " mix " , pr . mix ) ;
applyParamValue ( " cutoff " , pr . cutoff ) ;
applyParamValue ( " res " , pr . res ) ;
applyParamValue ( " attack " , pr . attack ) ;
applyParamValue ( " decay " , pr . decay ) ;
applyParamValue ( " sustain " , pr . sustain ) ;
applyParamValue ( " release " , pr . release ) ;
applyParamValue ( " glide " , pr . glide ) ;
applyParamValue ( " seqGateLen " , pr . gateLen ) ;
applyParamValue ( " seqSwing " , pr . swing ) ;
applyParamValue ( " seqRate " , ( float ) pr . rate ) ;
applyParamValue ( " seqRoot " , ( float ) pr . root ) ;
applyParamValue ( " seqOctave " , ( float ) pr . octave ) ;
applyParamValue ( " drive " , pr . drive ) ;
applyParamValue ( " ringmod " , pr . ring ) ;
applyParamValue ( " accent " , pr . accent ) ;
2026-08-06 15:51:56 +02:00
applyParamValue ( " filterAttack " , pr . fAttack ) ;
applyParamValue ( " filterDecay " , pr . fDecay ) ;
applyParamValue ( " filterSustain " , pr . fSustain ) ;
applyParamValue ( " filterRelease " , pr . fRelease ) ;
applyParamValue ( " filterEnvAmt " , pr . fAmt ) ;
2026-08-06 18:38:32 +02:00
applyParamValue ( " delayTime " , pr . delayTime ) ;
applyParamValue ( " delayStrength " , pr . delayFeedback ) ;
applyParamValue ( " delaySync " , pr . delaySync ? 1.0f : 0.0f ) ;
applyParamValue ( " delayDryWet " , pr . delayWet ) ;
applyParamValue ( " reverbRoom " , pr . reverbRoom ) ;
applyParamValue ( " reverbStrength " , pr . reverbLevel ) ;
applyParamValue ( " reverbDiffusion " , pr . reverbDiff ) ;
applyParamValue ( " reverbDryWet " , pr . reverbWet ) ;
2026-08-05 23:12:17 +02:00
}
void MonostepAudioProcessor : : randomizePattern ( )
{
const int length = juce : : roundToInt ( seqLenParam - > load ( ) ) ;
int filled = 0 ;
for ( int i = 0 ; i < monostep : : numSteps ; + + i )
{
auto & s = sequencer . step ( i ) ;
if ( i > = length )
{
s . gate = false ;
continue ;
}
const float chance = ( i = = 0 ) ? 0.9f : 0.45f ;
s . gate = random . nextFloat ( ) < chance ;
if ( s . gate )
{
+ + filled ;
s . semitone = random . nextInt ( monostep : : maxSemitone - monostep : : minSemitone + 1 ) + monostep : : minSemitone ;
s . cents = random . nextFloat ( ) * 100.0f - 50.0f ;
s . slide = random . nextFloat ( ) < 0.25f ;
s . accent = random . nextFloat ( ) < 0.3f ;
}
}
if ( filled = = 0 )
{
auto & s = sequencer . step ( random . nextInt ( length ) ) ;
s . gate = true ;
s . semitone = 0 ;
}
if ( onPatternChanged )
onPatternChanged ( ) ;
}
void MonostepAudioProcessor : : randomizeOsc ( )
{
2026-08-05 23:45:59 +02:00
applyParamValue ( " osc1Wave " , ( float ) random . nextInt ( monostep : : numWaveforms ) ) ;
applyParamValue ( " osc2Wave " , ( float ) random . nextInt ( monostep : : numWaveforms ) ) ;
2026-08-05 23:12:17 +02:00
applyParamValue ( " osc2Coarse " , ( float ) ( random . nextInt ( 25 ) - 12 ) ) ;
applyParamValue ( " detune " , ( float ) ( random . nextInt ( 101 ) - 50 ) ) ;
applyParamValue ( " mix " , 0.2f + random . nextFloat ( ) * 0.6f ) ;
}
void MonostepAudioProcessor : : randomizeFilter ( )
{
applyParamValue ( " cutoff " , 200.0f * std : : pow ( 2.0f , random . nextFloat ( ) * 5.0f ) ) ;
applyParamValue ( " res " , random . nextFloat ( ) * 0.8f ) ;
2026-08-06 15:51:56 +02:00
applyParamValue ( " filterEnvAmt " , - 3.0f + random . nextFloat ( ) * 6.0f ) ;
applyParamValue ( " filterAttack " , 0.001f * std : : pow ( 500.0f , random . nextFloat ( ) ) ) ;
applyParamValue ( " filterDecay " , 0.05f * std : : pow ( 30.0f , random . nextFloat ( ) ) ) ;
applyParamValue ( " filterSustain " , random . nextFloat ( ) ) ;
applyParamValue ( " filterRelease " , 0.05f * std : : pow ( 40.0f , random . nextFloat ( ) ) ) ;
2026-08-05 23:12:17 +02:00
}
void MonostepAudioProcessor : : randomizeEnv ( )
{
applyParamValue ( " attack " , 0.001f * std : : pow ( 500.0f , random . nextFloat ( ) ) ) ;
applyParamValue ( " decay " , 0.05f * std : : pow ( 30.0f , random . nextFloat ( ) ) ) ;
applyParamValue ( " sustain " , random . nextFloat ( ) ) ;
applyParamValue ( " release " , 0.05f * std : : pow ( 40.0f , random . nextFloat ( ) ) ) ;
applyParamValue ( " glide " , random . nextFloat ( ) * 0.5f ) ;
}
void MonostepAudioProcessor : : randomizeSeqSettings ( )
{
applyParamValue ( " seqGateLen " , 0.3f + random . nextFloat ( ) * 0.7f ) ;
applyParamValue ( " seqSwing " , random . nextFloat ( ) * 0.4f ) ;
applyParamValue ( " seqRate " , ( float ) random . nextInt ( 5 ) ) ;
applyParamValue ( " seqLen " , ( float ) ( random . nextInt ( 13 ) + 4 ) ) ;
}
void MonostepAudioProcessor : : randomizeFx ( )
{
applyParamValue ( " drive " , random . nextFloat ( ) ) ;
applyParamValue ( " ringmod " , random . nextFloat ( ) ) ;
2026-08-06 16:35:34 +02:00
applyParamValue ( " delayTime " , random . nextFloat ( ) ) ;
2026-08-06 18:38:32 +02:00
applyParamValue ( " delayStrength " , random . nextFloat ( ) * 0.9f ) ;
2026-08-06 16:35:34 +02:00
applyParamValue ( " delaySync " , random . nextFloat ( ) < 0.8f ? 1.0f : 0.0f ) ;
2026-08-06 18:38:32 +02:00
applyParamValue ( " delayDryWet " , random . nextFloat ( ) * 0.8f ) ;
2026-08-06 16:35:34 +02:00
applyParamValue ( " reverbRoom " , 0.2f + random . nextFloat ( ) * 0.7f ) ;
2026-08-06 18:38:32 +02:00
applyParamValue ( " reverbStrength " , random . nextFloat ( ) ) ;
2026-08-06 16:35:34 +02:00
applyParamValue ( " reverbDiffusion " , random . nextFloat ( ) ) ;
2026-08-06 18:38:32 +02:00
applyParamValue ( " reverbDryWet " , random . nextFloat ( ) * 0.8f ) ;
}
void MonostepAudioProcessor : : randomizeAccent ( )
{
const int length = juce : : roundToInt ( seqLenParam - > load ( ) ) ;
for ( int i = 0 ; i < length ; + + i )
{
auto & s = sequencer . step ( i ) ;
if ( ! s . gate )
continue ;
s . accent = random . nextFloat ( ) < 0.3f ;
}
if ( onPatternChanged )
onPatternChanged ( ) ;
}
void MonostepAudioProcessor : : randomizeSlide ( )
{
const int length = juce : : roundToInt ( seqLenParam - > load ( ) ) ;
for ( int i = 0 ; i < length ; + + i )
{
auto & s = sequencer . step ( i ) ;
if ( ! s . gate )
continue ;
s . slide = random . nextFloat ( ) < 0.25f ;
}
if ( onPatternChanged )
onPatternChanged ( ) ;
}
void MonostepAudioProcessor : : randomizeFine ( )
{
const int length = juce : : roundToInt ( seqLenParam - > load ( ) ) ;
for ( int i = 0 ; i < length ; + + i )
{
auto & s = sequencer . step ( i ) ;
if ( ! s . gate )
continue ;
s . cents = random . nextFloat ( ) * 100.0f - 50.0f ;
}
if ( onPatternChanged )
onPatternChanged ( ) ;
}
void MonostepAudioProcessor : : randomizeRate ( )
{
applyParamValue ( " seqRate " , ( float ) random . nextInt ( 5 ) ) ;
2026-08-05 23:12:17 +02:00
}
void MonostepAudioProcessor : : randomizeParams ( )
{
randomizeOsc ( ) ;
randomizeFilter ( ) ;
randomizeEnv ( ) ;
randomizeSeqSettings ( ) ;
randomizeFx ( ) ;
}
void MonostepAudioProcessor : : prepareToPlay ( double sr , int samplesPerBlock )
2026-08-05 19:55:57 +02:00
{
sampleRate = sr ;
voice . prepare ( sr ) ;
scratch . setSize ( 1 , samplesPerBlock ) ;
2026-08-06 16:35:34 +02:00
fx . prepare ( sr , juce : : jmax ( 1 , getTotalNumOutputChannels ( ) ) ) ;
2026-08-05 19:55:57 +02:00
lastStep = - 1 ;
lastGateApplied = false ;
lastFreqApplied = - 1.0f ;
freePpq = 0.0 ;
wasPlaying = false ;
lastHostPpq = - 1.0 ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : releaseResources ( )
2026-08-05 19:55:57 +02:00
{
voice . reset ( ) ;
2026-08-06 16:35:34 +02:00
fx . reset ( ) ;
2026-08-05 19:55:57 +02:00
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : updateVoiceParams ( )
2026-08-05 19:55:57 +02:00
{
monostep : : SynthVoice : : Params p ;
p . waveA = ( monostep : : Waveform ) ( int ) osc1WaveParam - > load ( ) ;
p . waveB = ( monostep : : Waveform ) ( int ) osc2WaveParam - > load ( ) ;
2026-08-06 15:51:56 +02:00
p . detuneRatioA = std : : pow ( 2.0f , detuneParam - > load ( ) / 1200.0f ) ; // OSC1: follows the knob
p . detuneRatio = std : : pow ( 2.0f , osc2CoarseParam - > load ( ) / 12.0f ) // OSC2: coarse
* std : : pow ( 2.0f , - detuneParam - > load ( ) / 1200.0f ) ; // OSC2: detune inverted
2026-08-05 19:55:57 +02:00
p . mix = mixParam - > load ( ) ;
2026-08-06 15:51:56 +02:00
p . cutoff = cutoffParam - > load ( ) ;
2026-08-05 19:55:57 +02:00
p . resonance = resParam - > load ( ) ;
2026-08-06 15:51:56 +02:00
p . filterType = ( monostep : : SynthVoice : : FilterType ) ( int ) filterTypeParam - > load ( ) ;
2026-08-05 19:55:57 +02:00
p . attack = attackParam - > load ( ) ;
p . decay = decayParam - > load ( ) ;
p . sustain = sustainParam - > load ( ) ;
p . release = releaseParam - > load ( ) ;
2026-08-06 15:51:56 +02:00
p . fAttack = filterAttackParam - > load ( ) ;
p . fDecay = filterDecayParam - > load ( ) ;
p . fSustain = filterSustainParam - > load ( ) ;
p . fRelease = filterReleaseParam - > load ( ) ;
p . fAmount = filterEnvAmtParam - > load ( ) ;
2026-08-05 19:55:57 +02:00
p . glide = glideParam - > load ( ) ;
p . master = masterParam - > load ( ) ;
2026-08-05 23:12:17 +02:00
p . drive = driveParam - > load ( ) ;
p . ringMod = ringModParam - > load ( ) ;
2026-08-05 19:55:57 +02:00
voice . setParams ( p ) ;
}
2026-08-05 23:12:17 +02:00
float MonostepAudioProcessor : : midiToFreq ( float note ) const
2026-08-05 19:55:57 +02:00
{
return 440.0f * std : : pow ( 2.0f , ( note - 69.0f ) / 12.0f ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : processBlock ( juce : : AudioBuffer < float > & buffer , juce : : MidiBuffer & midiMessages )
2026-08-05 19:55:57 +02:00
{
juce : : ScopedNoDenormals noDenormals ;
const int numSamples = buffer . getNumSamples ( ) ;
const int numChannels = buffer . getNumChannels ( ) ;
buffer . clear ( ) ;
if ( numSamples = = 0 )
return ;
updateVoiceParams ( ) ;
const int rateIdx = juce : : roundToInt ( seqRateParam - > load ( ) ) ;
const int root = juce : : roundToInt ( seqRootParam - > load ( ) ) ;
const int octave = juce : : roundToInt ( seqOctaveParam - > load ( ) ) - 2 ;
const float swing = seqSwingParam - > load ( ) ;
const float stepLen = sequencer . stepLenPpq ( rateIdx ) ;
const int seqLen = juce : : roundToInt ( seqLenParam - > load ( ) ) ;
const float cyclePpq = ( float ) seqLen * stepLen ;
// --- MIDI notes (gate/trigger for the sequencer) -------------------------
2026-08-06 15:51:56 +02:00
bool noteOnSeen = false ;
2026-08-05 19:55:57 +02:00
for ( const auto metadata : midiMessages )
{
const auto msg = metadata . getMessage ( ) ;
if ( msg . isNoteOn ( ) )
{
midiHeld = true ;
2026-08-06 15:51:56 +02:00
noteOnSeen = true ;
2026-08-05 19:55:57 +02:00
lastMidiNote = msg . getNoteNumber ( ) ;
}
else if ( msg . isNoteOff ( ) )
{
if ( msg . getNoteNumber ( ) = = lastMidiNote | | ! midiHeld )
midiHeld = false ;
}
}
// --- transport / clock --------------------------------------------------
bool hostPlaying = false ;
double hostPpq = 0.0 ;
double bpm = 120.0 ;
if ( auto * playHead = getPlayHead ( ) )
if ( auto pos = playHead - > getPosition ( ) )
{
hostPlaying = pos - > getIsPlaying ( ) ;
if ( auto ppq = pos - > getPpqPosition ( ) )
hostPpq = * ppq ;
if ( auto tempo = pos - > getBpm ( ) )
bpm = * tempo ;
}
const double ppqPerSample = ( bpm / 60.0 ) / sampleRate ;
// The sequencer only runs while a MIDI note is held. When the host
// transport is running we follow it, otherwise we free-run at the
// fallback BPM so the pattern still steps when there is no DAW clock.
if ( midiHeld )
{
if ( hostPlaying )
{
if ( lastHostPpq > = 0.0 & & hostPpq < lastHostPpq - 1.0 )
{
lastStep = - 1 ;
lastGateApplied = false ;
2026-08-06 15:57:58 +02:00
retrigPpq = 0.0 ;
2026-08-05 19:55:57 +02:00
}
2026-08-06 15:57:58 +02:00
// Optional retrigger: a fresh note-on restarts the pattern from
// step 1, offset from the host transport position.
const bool retrigOn = seqRetrigParam - > load ( ) > = 0.5f ;
if ( noteOnSeen & & retrigOn )
{
retrigPpq = hostPpq ;
lastStep = - 1 ;
lastGateApplied = false ;
}
else if ( ! retrigOn )
{
retrigPpq = 0.0 ;
}
freePpq = hostPpq - retrigPpq ;
2026-08-05 19:55:57 +02:00
}
else
{
if ( wasPlaying )
{
lastStep = - 1 ;
lastGateApplied = false ;
}
2026-08-06 15:51:56 +02:00
// Optional retrigger: a fresh note-on restarts the pattern from
2026-08-06 15:57:58 +02:00
// step 1 in free-run mode.
2026-08-06 15:51:56 +02:00
if ( noteOnSeen & & seqRetrigParam - > load ( ) > = 0.5f )
{
freePpq = 0.0 ;
lastStep = - 1 ;
lastGateApplied = false ;
}
2026-08-05 19:55:57 +02:00
freePpq + = ppqPerSample * numSamples ;
}
}
else
{
2026-08-05 23:12:17 +02:00
if ( lastGateApplied )
voice . noteOff ( ) ;
2026-08-05 19:55:57 +02:00
lastStep = - 1 ;
lastGateApplied = false ;
2026-08-05 23:12:17 +02:00
lastFreqApplied = - 1.0f ;
2026-08-05 19:55:57 +02:00
playStep . store ( - 1 ) ;
}
wasPlaying = hostPlaying ;
lastHostPpq = hostPpq ;
// --- sequencer step -----------------------------------------------------
bool seqGate = false ;
float seqFreq = 0.0f ;
bool seqSlide = false ;
2026-08-05 23:12:17 +02:00
bool seqAccent = false ;
2026-08-05 19:55:57 +02:00
if ( midiHeld )
{
const float gateLen = seqGateLenParam - > load ( ) ;
float ppqInCycle = ( float ) std : : fmod ( freePpq , ( double ) cyclePpq ) ;
if ( ppqInCycle < 0.0f )
ppqInCycle + = cyclePpq ;
int stepIdx = 0 ;
float stepStart = 0.0f ;
for ( int i = 0 ; i < seqLen ; + + i )
{
const float start = i * stepLen + ( ( i % 2 ) ? swing * stepLen : 0.0f ) ;
const float nextStart = ( i + 1 ) * stepLen + ( ( ( i + 1 ) % 2 ) ? swing * stepLen : 0.0f ) ;
if ( ppqInCycle > = start & & ppqInCycle < nextStart )
{
stepIdx = i ;
stepStart = start ;
break ;
}
}
lastStep = stepIdx ;
playStep . store ( stepIdx ) ;
const auto & s = sequencer . step ( stepIdx ) ;
seqSlide = s . slide ;
2026-08-05 23:12:17 +02:00
seqAccent = s . accent ;
// A step whose successor slides keeps its gate open until the next
// step so the glide into it is seamless; otherwise the gate closes
// after gateLen of the step.
const int nextIdx = ( stepIdx + 1 ) % seqLen ;
const bool slideIntoNext = sequencer . step ( nextIdx ) . slide ;
const float nextStart = ( stepIdx + 1 ) * stepLen
+ ( ( ( stepIdx + 1 ) % 2 ) ? swing * stepLen : 0.0f ) ;
2026-08-05 19:55:57 +02:00
const float ppqWithinStep = ppqInCycle - stepStart ;
2026-08-05 23:12:17 +02:00
const float gateClose = slideIntoNext ? ( nextStart - stepStart )
: stepLen * gateLen ;
seqGate = s . gate & & ( ppqWithinStep < gateClose ) ;
2026-08-05 19:55:57 +02:00
if ( seqGate )
{
// the held MIDI note transposes the pattern; root/octave shift it further
const float note = ( float ) lastMidiNote + ( float ) root + octave * 12.0f + s . semitone ;
seqFreq = midiToFreq ( note + s . cents / 100.0f ) ;
}
}
// --- decide voice event ---------------------------------------------------
bool wantGate = false ;
float wantFreq = 0.0f ;
bool wantLegato = false ;
if ( seqGate )
{
wantGate = true ;
wantFreq = seqFreq ;
wantLegato = seqSlide ;
}
2026-08-05 23:12:17 +02:00
// Accent tracks the current step (not just gate changes), so it holds for
2026-08-06 18:38:32 +02:00
// the whole step even when the note repeats without retriggering. Kept
// gentle: a modest volume lift plus a bit of brightness/timing (below).
voice . setAccentLevel ( seqAccent ? 1.0f + accentParam - > load ( ) * 0.4f : 1.0f ) ;
2026-08-05 23:12:17 +02:00
2026-08-05 19:55:57 +02:00
if ( wantGate ! = lastGateApplied )
{
if ( wantGate )
voice . noteOn ( wantFreq , wantLegato ) ;
else
voice . noteOff ( ) ;
lastGateApplied = wantGate ;
lastFreqApplied = wantGate ? wantFreq : - 1.0f ;
}
else if ( wantGate & & std : : fabs ( wantFreq - lastFreqApplied ) > 0.01f )
{
voice . noteOn ( wantFreq , wantLegato ) ;
lastFreqApplied = wantFreq ;
}
( void ) lastStep ;
// --- render ---------------------------------------------------------------
voice . render ( scratch , numSamples ) ;
for ( int ch = 0 ; ch < numChannels ; + + ch )
buffer . addFrom ( ch , 0 , scratch , 0 , 0 , numSamples ) ;
2026-08-06 16:35:34 +02:00
monostep : : FxProcessor : : Params fp ;
2026-08-06 18:38:32 +02:00
fp . delayTime = delayTimeParam - > load ( ) ;
fp . delayFeedback = delayStrengthParam - > load ( ) ;
fp . delaySync = delaySyncParam - > load ( ) > = 0.5f ;
fp . delayMix = delayDryWetParam - > load ( ) ;
fp . reverbRoom = reverbRoomParam - > load ( ) ;
fp . reverbLevel = reverbStrengthParam - > load ( ) ;
fp . reverbMix = reverbDryWetParam - > load ( ) ;
fp . reverbDiff = reverbDiffusionParam - > load ( ) ;
fp . bpm = bpm ;
2026-08-06 16:35:34 +02:00
fx . setParams ( fp ) ;
fx . process ( buffer , numSamples ) ;
2026-08-05 19:55:57 +02:00
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStepGate ( int idx , bool gate )
2026-08-05 19:55:57 +02:00
{
if ( idx < 0 | | idx > = monostep : : numSteps )
return ;
sequencer . step ( idx ) . gate = gate ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStepNote ( int idx , int semitone )
2026-08-05 19:55:57 +02:00
{
if ( idx < 0 | | idx > = monostep : : numSteps )
return ;
sequencer . step ( idx ) . semitone = juce : : jlimit ( monostep : : minSemitone , monostep : : maxSemitone , semitone ) ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStepCents ( int idx , float cents )
2026-08-05 19:55:57 +02:00
{
if ( idx < 0 | | idx > = monostep : : numSteps )
return ;
sequencer . step ( idx ) . cents = juce : : jlimit ( - 50.0f , 50.0f , cents ) ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStepSlide ( int idx , bool slide )
2026-08-05 19:55:57 +02:00
{
if ( idx < 0 | | idx > = monostep : : numSteps )
return ;
sequencer . step ( idx ) . slide = slide ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStepAccent ( int idx , bool accent )
{
if ( idx < 0 | | idx > = monostep : : numSteps )
return ;
sequencer . step ( idx ) . accent = accent ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
void MonostepAudioProcessor : : shiftPattern ( int dir )
{
sequencer . shift ( dir ) ;
if ( onPatternChanged )
onPatternChanged ( ) ;
}
juce : : String MonostepAudioProcessor : : getStepNoteName ( int idx ) const
2026-08-05 19:55:57 +02:00
{
const int root = juce : : roundToInt ( seqRootParam - > load ( ) ) ;
const int octave = juce : : roundToInt ( seqOctaveParam - > load ( ) ) - 2 ;
int note = root + octave * 12 + sequencer . step ( idx ) . semitone ;
if ( lastMidiNote > = 0 )
note + = lastMidiNote ;
return juce : : MidiMessage : : getMidiNoteName ( note , true , true , 3 ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : storeSequence ( juce : : ValueTree & seq ) const
2026-08-05 19:55:57 +02:00
{
juce : : String gates ;
juce : : String notes ;
juce : : String cents ;
juce : : String slides ;
2026-08-05 23:12:17 +02:00
juce : : String accents ;
2026-08-05 19:55:57 +02:00
for ( int i = 0 ; i < monostep : : numSteps ; + + i )
{
const auto & s = sequencer . step ( i ) ;
gates + = s . gate ? " 1 " : " 0 " ;
notes + = juce : : String ( s . semitone ) + " , " ;
cents + = juce : : String ( s . cents , 2 ) + " , " ;
slides + = s . slide ? " 1 " : " 0 " ;
2026-08-05 23:12:17 +02:00
accents + = s . accent ? " 1 " : " 0 " ;
2026-08-05 19:55:57 +02:00
}
seq . setProperty ( " gates " , gates , nullptr ) ;
seq . setProperty ( " notes " , notes . dropLastCharacters ( 1 ) , nullptr ) ;
seq . setProperty ( " cents " , cents . dropLastCharacters ( 1 ) , nullptr ) ;
seq . setProperty ( " slides " , slides , nullptr ) ;
2026-08-05 23:12:17 +02:00
seq . setProperty ( " accents " , accents , nullptr ) ;
2026-08-05 19:55:57 +02:00
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : loadSequence ( const juce : : ValueTree & seq )
2026-08-05 19:55:57 +02:00
{
const auto splitFloats = [ ] ( const juce : : String & text )
{
juce : : StringArray tokens ;
tokens . addTokens ( text , " , " , " " ) ;
juce : : Array < float > result ;
for ( const auto & t : tokens )
result . add ( t . getFloatValue ( ) ) ;
return result ;
} ;
const auto gates = seq . getProperty ( " gates " ) . toString ( ) ;
const auto notes = splitFloats ( seq . getProperty ( " notes " ) . toString ( ) ) ;
const auto cents = splitFloats ( seq . getProperty ( " cents " ) . toString ( ) ) ;
const auto slides = seq . getProperty ( " slides " ) . toString ( ) ;
2026-08-05 23:12:17 +02:00
const auto accents = seq . getProperty ( " accents " ) . toString ( ) ;
2026-08-05 19:55:57 +02:00
for ( int i = 0 ; i < monostep : : numSteps ; + + i )
{
auto & s = sequencer . step ( i ) ;
if ( i < gates . length ( ) )
s . gate = gates [ i ] = = ' 1 ' ;
if ( i < notes . size ( ) )
s . semitone = juce : : roundToInt ( notes [ i ] ) ;
if ( i < cents . size ( ) )
s . cents = cents [ i ] ;
if ( i < slides . length ( ) )
s . slide = slides [ i ] = = ' 1 ' ;
2026-08-05 23:12:17 +02:00
if ( i < accents . length ( ) )
s . accent = accents [ i ] = = ' 1 ' ;
2026-08-05 19:55:57 +02:00
}
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : getStateInformation ( juce : : MemoryBlock & destData )
2026-08-05 19:55:57 +02:00
{
2026-08-05 23:12:17 +02:00
juce : : ValueTree state = apvts . copyState ( ) ;
2026-08-05 19:55:57 +02:00
juce : : ValueTree seq = state . getOrCreateChildWithName ( " SEQ " , nullptr ) ;
storeSequence ( seq ) ;
2026-08-06 15:51:56 +02:00
state . setProperty ( " zoom " , zoomIndex , nullptr ) ;
2026-08-05 19:55:57 +02:00
std : : unique_ptr < juce : : XmlElement > xml ( state . createXml ( ) ) ;
copyXmlToBinary ( * xml , destData ) ;
}
2026-08-05 23:12:17 +02:00
void MonostepAudioProcessor : : setStateInformation ( const void * data , int sizeInBytes )
2026-08-05 19:55:57 +02:00
{
std : : unique_ptr < juce : : XmlElement > xml ( juce : : AudioProcessor : : getXmlFromBinary ( data , sizeInBytes ) ) ;
if ( xml = = nullptr )
return ;
const juce : : ValueTree state = juce : : ValueTree : : fromXml ( * xml ) ;
apvts . replaceState ( state ) ;
if ( auto seq = state . getChildWithName ( " SEQ " ) ; seq . isValid ( ) )
loadSequence ( seq ) ;
2026-08-06 15:51:56 +02:00
zoomIndex = juce : : jlimit ( 0 , 4 , ( int ) state . getProperty ( " zoom " , 0 ) ) ;
2026-08-05 19:55:57 +02:00
if ( onPatternChanged )
onPatternChanged ( ) ;
}
2026-08-05 23:12:17 +02:00
juce : : AudioProcessorEditor * MonostepAudioProcessor : : createEditor ( )
2026-08-05 19:55:57 +02:00
{
# ifndef MONOSTEP_HEADLESS
2026-08-05 23:12:17 +02:00
return new MonostepAudioProcessorEditor ( * this ) ;
2026-08-05 19:55:57 +02:00
# else
return nullptr ;
# endif
}
juce : : AudioProcessor * JUCE_CALLTYPE createPluginFilter ( )
{
2026-08-05 23:12:17 +02:00
return new MonostepAudioProcessor ( ) ;
2026-08-05 19:55:57 +02:00
}