Skip to content

stminstance

Creates a runner from a graph definition.

The runner is the part of a state machine that changes during performance: it holds the current node, the pending request, the clock and the event history. Every runner is independent, even when several are created from the same definition.

Syntax

irunner = stminstance(idef)
irunner stminstance idef

Initialization

idef -- a definition handle, returned by stmcompile.

irunner -- the handle of the new runner. It starts on the entry node, with its clock at 0 and no events.

The runner belongs to the opcode instance that created it, and its handle is removed when that instance ends. Created in an instrument, a runner lasts as long as the note; created in the global space, it lasts for the whole performance. Opcodes that were initialized with the handle keep the runner alive until they end, while an opcode initialized after the handle was removed reports an initialization error.

Handles are plain numbers, so they can be passed to other instruments, for example as p-fields or global variables.

Examples

A state machine that shapes a texture

A texture moves through three nodes, Calm, Build and Burst. Each node is a UDO that writes a shared struct and asks for the next node with stmnext when its time is up. The runner is created inside the instrument, so it lasts as long as the note. It uses the file stm.csd.

A state machine that shapes a texture.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac     ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o stm.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1

; A state machine that shapes a texture. The graph only says which node is
; current and which moves are allowed:
;
;   Calm -> Build -> Burst -> Calm or Build
;
; What each node does is an ordinary UDO. It writes the shared state and,
; when its time is up, asks for the next node with stmnext.

struct Texture cutoff:k, rate:k

opcode CalmNode(r:i, tx:Texture, t:k):void
  tx.cutoff = 500
  tx.rate = 1
  if t >= 3 then
    stmnext(r, "Build")
  endif
endop

opcode BuildNode(r:i, tx:Texture, t:k):void
  tx.cutoff = 500 + t * 1500     ; opens up during the node
  tx.rate = 1 + t * 3
  if t >= 2 then
    stmnext(r, "Burst")
  endif
endop

opcode BurstNode(r:i, tx:Texture, t:k):void
  tx.cutoff = 6000
  tx.rate = 12
  if t >= 1.5 then
    ; a conditional edge: Burst may go to either node
    if random:k(0, 1) < 0.5 then
      stmnext(r, "Calm")
    else
      stmnext(r, "Build")
    endif
  endif
endop

; build the graph once, in the global space
builder:i = stmcreate()
stmaddnode(builder, "Calm")
stmaddnode(builder, "Build")
stmaddnode(builder, "Burst")
stmaddedge(builder, "Calm", "Build")
stmaddedge(builder, "Build", "Burst")
stmaddcondedge(builder, "Burst", ["Calm", "Build"])
gi_definition@global:i = stmcompile(builder)

instr 1
  r:i = stminstance(gi_definition)
  tx:Texture = init(500, 1)

  ; the runtime loop: current node -> its UDO -> stmadvance
  node:S = stmcurrent(r)
  t:k = stmnodetime(r)
  if strcmpk(node, "Calm") == 0 then
    CalmNode(r, tx, t)
  elseif strcmpk(node, "Build") == 0 then
    BuildNode(r, tx, t)
  else
    BurstNode(r, tx, t)
  endif
  status:k, from:k, to:k = stmadvance(r)
  if status == 1 then
    printks("%5.2f s: %s -> %s\n", 0, timeinsts(), stmnodename(r, from), stmnodename(r, to))
  endif

  ; the sound only reads the shared state
  pulse:k = 0.5 + 0.5 * lfo(1, tx.rate, 0)
  cutoff:k = portk(tx.cutoff, 0.1)
  env:k = linseg(0, 0.5, 1, p3 - 1, 1, 0.5, 0)
  sig:a = vco2(0.1, 110) + vco2(0.1, 165) + vco2(0.1, 220.5)
  sig = moogladder(sig, cutoff, 0.4) * pulse * env
  out(sig, sig)
endin
</CsInstruments>
<CsScore>
i 1 0 20
e
</CsScore>
</CsoundSynthesizer>

Nodes bound to instruments

Each node starts an instrument when it is entered and releases it when it is left. One definition backs two runners, one per voice, each with its own current node and clock. It uses the file stm-processes.csd.

Nodes bound to instruments.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac     ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o stm-processes.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1

; Nodes bound to instruments. When a node is entered, its instrument is
; started with a held note; when the node is left, the note is released.
; One definition backs two runners, one per voice, each with its own
; current node and its own clock.
;
;   Drone -> Pulse -> Rest -> Drone

builder:i = stmcreate()
stmaddnode(builder, "Drone")
stmaddnode(builder, "Pulse")
stmaddnode(builder, "Rest")
stmaddedge(builder, "Drone", "Pulse")
stmaddedge(builder, "Pulse", "Rest")
stmaddedge(builder, "Rest", "Drone")
gi_definition@global:i = stmcompile(builder)

; the supervisor of one voice: p4 = voice (1 or 2), p5 = pitch, p6 = tempo
instr 1
  r:i = stminstance(gi_definition)
  drone:i = 10 + p4 / 10      ; 10.1 or 10.2: one note per voice
  pulse:i = 11 + p4 / 10

  ; the entry node has no enter event: start its process explicitly
  schedule(drone, 0, -1, p5)

  node:S = stmcurrent(r)
  t:k = stmnodetime(r) * p6
  if strcmpk(node, "Drone") == 0 && t >= 3 then
    stmnext(r, "Pulse")
  elseif strcmpk(node, "Pulse") == 0 && t >= 2 then
    stmnext(r, "Rest")
  elseif strcmpk(node, "Rest") == 0 && t >= 1 then
    stmnext(r, "Drone")
  endif
  status:k, from:k, to:k = stmadvance(r)

  ; enter and exit checks in the loop that advances the graph
  if stmonenter(r, "Drone") == 1 then
    schedulek(drone, 0, -1, p5)
  endif
  if stmonexit(r, "Drone") == 1 then
    turnoff2(drone, 4, 1)
  endif
  if stmonenter(r, "Pulse") == 1 then
    schedulek(pulse, 0, -1, p5)
  endif
  if stmonexit(r, "Pulse") == 1 then
    turnoff2(pulse, 4, 1)
  endif

  ; release whatever is still playing when the supervisor ends
  if lastcycle() == 1 then
    turnoff2(drone, 4, 1)
    turnoff2(pulse, 4, 1)
  endif
endin

instr 10 ; Drone process
  env:k = madsr(1, 0, 1, 1)
  sig:a = vco2(0.06, p4) + vco2(0.06, p4 * 1.003)
  sig = moogladder(sig, 1200, 0.2) * env
  out(sig, sig)
endin

instr 11 ; Pulse process
  env:k = madsr(0.01, 0, 1, 0.3)
  hit:k = loopseg(4, 0, 0, 1, 0.1, 0, 0.9, 0)
  sig:a = vco2(0.12, p4 * 2, 10) * hit
  sig = moogladder(sig, 3000, 0.1) * env
  out(sig, sig)
endin
</CsInstruments>
<CsScore>
; p4 = voice, p5 = pitch, p6 = tempo of the graph
i 1 0 20 1 110 1
i 1 0 20 2 165 1.5
e 22
</CsScore>
</CsoundSynthesizer>

See also

State Machine Graphs

stmcompile, stmcurrent, stmadvance

Credits

Author: Pasquale Mainolfi
Italy
New in Csound version 7.0
October 2026