Skip to content

stmnext

Requests a transition to another node.

The request is not applied at once. The next stmadvance applies it if the current node has an edge to the requested node, and reports the outcome.

Syntax

stmnext(irunner, Snode)
stmnext(irunner, knode)
stmnext irunner, Snode
stmnext irunner, knode

Initialization

irunner -- a runner handle, returned by stminstance.

Performance

Snode -- the name of the requested node. An unknown name causes a performance error.

knode -- the id of the requested node. It must be an integer from 0 to the number of nodes minus 1, otherwise a performance error is reported.

A runner has one pending request. The first request in a control period is kept, and repeating it is harmless. Requesting a different node before stmadvance is a conflict: the advance applies neither request and reports status 4.

Requests made while the runner is paused or deleted are ignored.

This opcode changes the runner, so it counts as a writer: a runner accepts writers from one instrument instance at a time. See Writers and observers.

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>

A scripted run showing every advance status

A scripted run of a small graph. It prints, for each control period, the current node and the graph clock as read before stmadvance, the request made, and what stmadvance returned. Each control period lasts 10 ms. It uses the file stm-trace.csd.

A scripted run showing every advance status.
<CsoundSynthesizer>
<CsOptions>
-n -d -m0
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 480
nchnls = 1
0dbfs = 1

; A scripted run of a small graph. Each control period lasts 10 ms.
; The trace shows what stmadvance returns for each kind of request,
; and how the graph clock moves.

; Idle -> Play, Play -> Play (self edge), Play -> Hold, Hold -> Play or Idle
builder:i = stmcreate()
stmaddnode(builder, "Play")    ; id 0
stmaddnode(builder, "Idle")    ; id 1
stmaddnode(builder, "Hold")    ; id 2
stmentry(builder, "Idle")      ; start from Idle instead of id 0
stmaddedge(builder, "Idle", "Play")
stmaddedge(builder, "Play", "Play")
stmaddedge(builder, "Play", "Hold")
stmaddcondedge(builder, "Hold", ["Play", "Idle"])
definition:i = stmcompile(builder)
gi_runner@global:i = stminstance(definition)

instr 1
  r:i = gi_runner
  cycle:k init 0
  cycle += 1

  if cycle == 1 then
    printks("%d nodes, %d edges: %s %s %s\n", 0, stmnodecount(r), stmedgecount(r), \
            stmnodename(r, 0), stmnodename(r, 1), stmnodename(r, 2))
    printks("cyc node   id  tick  time  nodetime  request       status from to\n", 0)
  endif

  ; read the node and the clock before stmadvance
  node:S = stmcurrent(r)
  id:k = stmcurrentid(r)
  tick:k = stmtick(r)
  time:k = stmtime(r)
  nodetime:k = stmnodetime(r)

  request:S = strcpyk("-")
  if cycle == 2 then
    stmnext(r, "Play")
    request = strcpyk("Play")
  elseif cycle == 3 then
    stmnext(r, "Idle")              ; there is no Play -> Idle edge
    request = strcpyk("Idle")
  elseif cycle == 4 then
    stmnext(r, "Play")              ; self edge
    request = strcpyk("Play")
  elseif cycle == 5 then
    stmnext(r, "Hold")              ; two different requests
    stmnext(r, "Idle")
    request = strcpyk("Hold+Idle")
  elseif cycle == 6 then
    stmnext(r, stmnodeid(r, "Hold"))  ; request by id
    request = strcpyk("id of Hold")
  elseif cycle == 8 then
    stmreset(r, 1)                  ; back to the entry node, Idle
    request = strcpyk("reset")
  endif

  status:k, from:k, to:k = stmadvance(r)
  printks("%3d %-5s %3d %5d %5.2f %9.2f  %-12s %6d %4d %2d\n", 0, \
          cycle, node, id, tick, time, nodetime, request, status, from, to)

  if cycle == 9 then
    turnoff
  endif
endin
</CsInstruments>
<CsScore>
i 1 0 1
e
</CsScore>
</CsoundSynthesizer>

See also

State Machine Graphs

stmadvance, stmaddedge, stmaddcondedge

Credits

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