Skip to content

k35lpf

Zero-delay feedback implementation of Korg35 resonant low-pass filter.

📝 Note

Up to Csound 6, this opcode was called K35_lpf.

This filter design is found in the Korg MS10, early MS20, and Monotron series.

Syntax

asig = k35lpf(ain, xcf, xQ [, inlp, isaturation, istor])
asig K35_lpf ain, xcf, xQ [, inlp, isaturation, istor]

Initialization

istor --initial disposition of internal data space. Since filtering incorporates a feedback loop of previous output, the initial status of the storage space used is significant. A zero value will clear the space; a non-zero value will allow previous information to remain. The default value is 0.

Performance

asig -- output signal.

ain -- input signal.

xcf -- filter cutoff frequency (i-, k-, or a-rate).

xQ -- filter Q value (i-, k-, or a-rate). Range 1.0-10.0 (clamped by opcode). Self-oscillation occurs at 10.0.

knlp (optional, default=0) -- Non-linear processing method. 0 = no processing, 1 = non-linear processing. Method 1 uses tanh(ksaturation * input). Enabling NLP may increase the overall output of filter above unity and should be compensated for outside of the filter.

ksaturation (optional, default=1) -- saturation amount to use for non-linear processing. Values > 1 increase the steepness of the NLP curve.

Examples

Here is an example of the k35lpf opcode. It uses the file k35-modern.csd.

Example of the k35lpf opcode.
<CsoundSynthesizer>
<CsOptions>
  -o dac
</CsOptions>
; ==============================================
<CsInstruments>

sr     = 48000
ksmps  = 1
nchnls = 2
0dbfs  = 1

;; test instruments to demo filter cutoff sweep with high resonance

instr 1 
  sig:a = vco2(0.5, cps2pch(6.00, 12))
  sig = k35lpf(sig, expseg:a(10000, p3, 30), 9.9, 0, 1)
  sig *= 0.25
  sig = limit(sig, -1.0, 1.0)
  out(sig, sig)
endin

instr 2 
  sig:a = vco2(0.5, cps2pch(6.00, 12))
  sig = k35lpf(sig, expseg:k(10000, p3, 30), 9.9, 0, 1)
  sig *= 0.25
  sig = limit(sig, -1.0, 1.0)
  out(sig, sig)
endin

instr 3 
  sig:a = vco2(0.5, cps2pch(6.00, 12))
  sig = k35hpf(sig, expseg:a(10000, p3, 30), 9.9, 0, 1)
  sig *= 0.25
  sig = limit(sig, -1.0, 1.0)
  out(sig, sig)
endin

instr 4 
  sig:a = vco2(0.5, cps2pch(6.00, 12))
  sig = k35hpf(sig, expseg:k(10000, p3, 30), 9.9, 0, 1)
  sig *= 0.25
  sig = limit(sig, -1.0, 1.0)
  out(sig, sig)
endin

;; beat instruments

instr ms20_drum
  pch:i = cps2pch(p4, 12)
  amp:i = ampdbfs(p5)
  env:a = expseg(10000, 0.05, pch, p3 - 0.05, pch)

  sig:a = rand(-1.0, 1.0)
  sig = k35hpf(sig, 60, 7, 1, 1)
  sig = k35lpf(sig, env, 9.8, 1, 1)

  sig = tanh(sig * 16)
  sig *= expon(amp, p3, 0.0001)
  out(sig, sig)
endin

instr ms20_bass 
  pch:i = cps2pch(p4, 12)
  amp:i = ampdbfs(p5)
  env:a = expseg(1000, 0.1, pch * 2, p3 - 0.05, pch * 2)

  sig:a = vco2(1.0, pch)
  sig = k35hpf(sig, pch, 5, 0, 1)
  sig = k35lpf(sig, env, 8, 0, 1)

  sig *= expon(amp, p3, 0.0001) * 0.8
  out(sig, sig)
endin

;; perf code

Tempo@global:k = init(122)

opcode beat_dur():(i)
  xout(60 / i(Tempo)) 
endop

instr bass_player
  dur:i = beat_dur() / int(random(1, 3)) 
  pch:i = 6.00 + int(random(1,3)) + int(random(1,3)) / 100

  schedule("ms20_bass", 0, dur, pch, -11) 

  if (p2 < 37.5) then
    schedule("bass_player", dur, 0.1)
  endif
  turnoff()
endin

instr beat_player 
  step_total:i = p4 
  step:i = step_total % 16

  if step % 4 == 0 then
    pch:i = ((step_total % 128) < 112) ? 4.00 : 8.00
    amp:i = (step == 0)  ? -9 : -12
    schedule("ms20_drum", 0, 0.5, pch, amp)
  endif

  schedule("ms20_drum", 0, 0.125, 14.00, 
           (step % 4 == 0) ? -12 : -18)

  if p2 < 37.5 then
    schedule("beat_player", beat_dur() / 4, 0.1, step_total + 1)
  endif
  turnoff()
endin

;; start play of beats

instr start_beats
  schedule("beat_player", 0, 0.1, 0)
  schedule("bass_player", 0, 0.1)
endin

</CsInstruments>
; ==============================================
<CsScore>
i1 0 5.0
i2 5 5.0
i3 10 5.0
i4 15 5.0

i "start_beats" 22 0.5 0

</CsScore>
</CsoundSynthesizer>

Here is an example of the K35_lpf opcode. It uses the file k35.csd.

Example of the K35_lpf opcode.
<CsoundSynthesizer>
<CsOptions>
  -o dac
</CsOptions>
; ==============================================
<CsInstruments>

sr      =       48000
ksmps   =       1
nchnls  =       2
0dbfs   =       1

;; test instruments to demo filter cutoff sweep with high resonance

instr 1 

asig = vco2(0.5, cps2pch(6.00, 12))
asig = K35_lpf(asig, expseg:a(10000, p3, 30), 9.9, 0, 1)
asig *= 0.25
asig  = limit(asig, -1.0, 1.0)

outc(asig, asig)

endin


instr 2 

asig = vco2(0.5, cps2pch(6.00, 12))
asig = K35_lpf(asig, expseg:k(10000, p3, 30), 9.9, 0, 1)
asig *= 0.25
asig  = limit(asig, -1.0, 1.0)

outc(asig, asig)

endin

instr 3 

asig = vco2(0.5, cps2pch(6.00, 12))
asig = K35_hpf(asig, expseg:a(10000, p3, 30), 9.9, 0, 1)
asig *= 0.25
asig  = limit(asig, -1.0, 1.0)

outc(asig, asig)

endin


instr 4 

asig = vco2(0.5, cps2pch(6.00, 12))
asig = K35_hpf(asig, expseg:k(10000, p3, 30), 9.9, 0, 1)
asig *= 0.25
asig  = limit(asig, -1.0, 1.0)

outc(asig, asig)

endin

;; beat instruments

instr ms20_drum

  ipch = cps2pch(p4, 12)
  iamp = ampdbfs(p5)
  aenv = expseg:a(10000, 0.05, ipch, p3 - .05, ipch)

  asig = rand:a(-1.0, 1.0)
  asig = K35_hpf(asig, 60, 7, 1, 1)
  asig = K35_lpf(asig, aenv, 9.8, 1, 1)

  asig = tanh(asig * 16)

  asig *= expon(iamp, p3, 0.0001)

  outc(asig, asig)

endin

instr ms20_bass 
  ipch = cps2pch(p4, 12)
  iamp = ampdbfs(p5)
  aenv = expseg(1000, 0.1, ipch * 2, p3 - .05, ipch * 2)

  asig = vco2(1.0, ipch)
  asig = K35_hpf(asig, ipch, 5, 0, 1)
  asig = K35_lpf(asig, aenv, 8, 0, 1)

  asig *= expon:a(iamp, p3, 0.0001) * 0.8

  outc(asig, asig)
endin

;; perf code

gktempo init 122

opcode beat_dur,i,0
  xout 60 / i(gktempo) 
endop

instr bass_player
  idur = beat_dur() / int(random(1,3)) 
  ipch = 6.00 + int(random(1,3)) + int(random(1,3)) / 100

  schedule("ms20_bass", 0, idur, ipch, -11) 

  if(p2 < 37.5) then
    schedule("bass_player", idur, 0.1)
  endif
  turnoff
endin

instr beat_player 
  istep_total = p4 
  istep = istep_total % 16

  if(istep % 4 == 0) then
    ipch = ((istep_total % 128) < 112) ? 4.00 : 8.00
    iamp = (istep == 0)  ? -9 : -12
    schedule("ms20_drum", 0, 0.5, ipch, iamp)
  endif

  schedule("ms20_drum", 0, 0.125, 14.00, 
           (istep % 4 == 0) ? -12 : -18)

  if(p2 < 37.5) then
    schedule("beat_player", beat_dur() / 4, 0.1, istep_total + 1)
  endif
  turnoff
endin

;; start play of beats

instr start_beats
  schedule("beat_player", 0, 0.1, 0)
  schedule("bass_player", 0, 0.1)
endin


</CsInstruments>
; ==============================================
<CsScore>
i1 0 5.0
i2 5 5.0
i3 10 5.0
i4 15 5.0

i "start_beats" 22 0.5 0

</CsScore>
</CsoundSynthesizer>

References

This filter is based on the work of Will Pirkle that employs Vadim Zavalishin's work with bilinear tranforms to create topology-preserving transform (TPT) implementations of analog filters.

  1. Pirkle, Will. Designing Software Synthesizer Plug-ins in C++: For RackAFX, VST3, and Audio Units. CRC Press, 2014.
  2. Pirkle, Will. AN-5: Virtual Analog (VA) Korg35 Lowpass Filter v3.5. 2013.
  3. Zavalishin, Vadim. "The Art of VA filter design." Native Instruments, 2012.

See also

Standard filters: Zero-delay Feedback Filters (Virtual Analog)

Credits

Author: Steven Yi
April 2017

New in Csound 6.09.0