The Roland SH-101 (1982) is a single-oscillator monophonic synth with a
ladder filter and a simple ADSR. This circuit reproduces its signal path in
Valis. The full Turtle is at examples/sh101.ttl.
MidiPitch ──────────────────────────────────► VCO (sawtooth)
│
MidiPitch × 0.5 ─► Sub-oscillator (square) ─► subGain
│
Noise ─► noiseGain
│
LFO (vibrato) ── depth=0 ───────────────────► (VCO freq)
│
┌─────────────────────────┘
▼
Mixer (sum VCO + sub + noise)
│ audio
Envelope ──► Scale(400–8000 Hz) ── control ──► Ladder VCF
│ audio
Envelope ─────────────────────── control ──► VCA
│ audio
Output
Outputs the currently held MIDI note as a frequency in Hz. Holds the last value between notes (default A4 = 440 Hz until the first note-on arrives).
:pitch a val:MidiPitch .
:aPitchOsc a val:ControlArc ;
val:from [ val:node :pitch ; val:port "out" ] ;
val:to [ val:node :osc ; val:port "frequency" ] .
The SH-101 has a dedicated sub-oscillator generating a square wave one or two
octaves below the VCO. This circuit implements the one-octave version using
val:ControlMultiply to halve the MidiPitch frequency before feeding a second
oscillator. The b port is set to 0.5 on the instance; no control arc
connects to it, so it keeps that baked value each block.
:subMult a val:ControlMultiply ;
val:b 0.5 .
:subOsc a val:Oscillator ;
val:shape 2 . # square
:aPitchSubMult a val:ControlArc ;
val:from [ val:node :pitch ; val:port "out" ] ;
val:to [ val:node :subMult ; val:port "a" ] .
:aSubMultSubOsc a val:ControlArc ;
val:from [ val:node :subMult ; val:port "out" ] ;
val:to [ val:node :subOsc ; val:port "frequency" ] .
The "Sub Level" knob (slot 9) controls a val:Gain element between the
sub-oscillator and the mixer. Default is -12 dB; reduce to -60 to silence it.
The SH-101 has a white noise source that blends into the filter input. Here
a val:Noise element feeds a level-control val:Gain before the mixer. The
"Noise Level" knob (slot 10) defaults to -60 dB (near silent). Raise it for
breath-noise texture or to thicken the filter resonance.
All three audio sources (VCO, sub-oscillator, noise) are summed in a
val:Mixer element before entering the VCF. Multiple audio arcs may target
the mixer's in port; the mixer sums them.
:vcfMix a val:Mixer .
:aOscMix a val:Arc ; val:from [ val:node :osc ; val:port "out" ] ;
val:to [ val:node :vcfMix ; val:port "in" ] .
:aSubGainMix a val:Arc ; val:from [ val:node :subGain ; val:port "out" ] ;
val:to [ val:node :vcfMix ; val:port "in" ] .
:aNoiseGainMix a val:Arc ; val:from [ val:node :noiseGain ; val:port "out" ] ;
val:to [ val:node :vcfMix ; val:port "in" ] .
Takes a 0–1 control signal at cv and multiplies the audio linearly. Connecting
the Envelope output here gives a natural amplitude contour.
:vca a val:VCA .
:aEnvVca a val:ControlArc ;
val:from [ val:node :env ; val:port "out" ] ;
val:to [ val:node :vca ; val:port "cv" ] .
A control arc that reaches a port replaces the port's declared value every
block. The consequence for filter control: you cannot have both a fixed val:cutoff
on the Ladder instance and a control arc targeting cutoff — the arc wins every
block, making the fixed value unreachable. The Ladder's resting cutoff must live
somewhere in the control path, not on the element itself.
:vcf a val:Ladder ;
val:resonance 0.3 ;
val:drive 1.0 .
# val:cutoff is intentionally absent - the control arc from :envScale
# sets the cutoff every block, overriding any fixed value here.
The Envelope output is 0–1. The Ladder filter expects a cutoff in Hz. val:Scale
maps the 0–1 control signal to a [min, max] range. The Scale's min value is
the resting cutoff (envelope closed). The Scale's max value is the cutoff at
the envelope's peak.
:envScale a val:Scale ;
val:min 400.0 ; # Hz - base cutoff when envelope is silent
val:max 8000.0 . # Hz - cutoff at envelope peak
The LFO drives the oscillator frequency with val:depth 0.0 by default — set
depth to a small positive value (e.g. 0.02 for about a semitone) to introduce
vibrato:
:aLfoOsc a val:ControlArc ; val:depth 0.02 ;
val:from [ val:node :lfo ; val:port "out" ] ;
val:to [ val:node :osc ; val:port "frequency" ] .
Control sources (MidiPitch, Envelope, Scale) must be processed before the
elements they drive, so that each block sees fresh values. Valis's compiler
performs a topological sort that includes control arcs — a control arc A → B
guarantees A is processed before B in the same block.
Load examples/sh101.ttl via File → Load Circuit…, or paste it into the
Code tab. The eleven host parameters cover:
| Slot | Name | What it controls |
|---|---|---|
| 0 | Osc Shape | Waveform (0=sine … 3=square) |
| 1 | Cutoff | Filter resting frequency (Hz) |
| 2 | Resonance | Filter self-resonance (0–1) |
| 3 | Attack | Envelope attack time (ms) |
| 4 | Decay | Envelope decay time (ms) |
| 5 | Sustain | Envelope sustain level (0–1) |
| 6 | Release | Envelope release time (ms) |
| 7 | Filter Peak | Filter cutoff at envelope peak (Hz) |
| 8 | Vibrato Rate | LFO rate (Hz) |
| 9 | Sub Level | Sub-oscillator level (dB) |
| 10 | Noise Level | Noise level (dB) |