stmonexit
Outputs 1 when a node of a runner has just been left.
Use it to stop a process when the graph moves out of a node, for example by releasing an instrument.
Syntax
Initialization
irunner -- a runner handle, returned by stminstance.
Snode -- the name of the node to watch. An unknown name causes an initialization error.
ktrig -- 1 on the first pass after a transition out of Snode, 0 otherwise.
A transition is a move accepted by stmadvance (status 1 or 3), or an stmreset or stmrecall that changes the current node. A self transition fires both the enter and the exit trigger of its node. Pause, resume and delete are not transitions.
The node that is current when the runner is created is not an event, so nothing fires for it. Each opcode instance only reacts to transitions that happen after it was initialized: an observer that starts while a node is already current does not see an enter for it.
The opcode compares the latest transition with the one it saw on its previous pass. If the runner made several transitions in between, only the last one is seen. Call it in every control period, ideally in the instrument that advances the graph, or use stmevent to read every event.
This opcode only reads the runner. Any number of instruments can use it at the same time.
Examples
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>
|
Watching transitions and events
One instrument drives a runner through transitions, a pause, a resume and a deletion. A second instrument watches it with stmonenter, stmonexit and stmevent. It uses the file stm-events.csd.
| Watching transitions and events. |
|---|
| <CsoundSynthesizer>
<CsOptions>
-n -d -m0
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 480
nchnls = 1
0dbfs = 1
; Watching a runner: stmonenter and stmonexit fire on transitions, and
; stmevent reads every runner event in order, including pause, resume
; and delete. The writer and the observers are in different instruments.
; A <-> B, plus a self edge on B
builder:i = stmcreate()
stmaddnode(builder, "A") ; id 0
stmaddnode(builder, "B") ; id 1
stmaddedge(builder, "A", "B")
stmaddedge(builder, "B", "A")
stmaddedge(builder, "B", "B")
gi_runner@global:i = stminstance(stmcompile(builder))
; the writer: a scripted sequence of requests and controls
instr 1
r:i = gi_runner
cycle:k init 0
cycle += 1
if cycle == 1 then
stmnext(r, "B")
elseif cycle == 2 then
stmnext(r, "B") ; self transition
elseif cycle == 4 then
stmnext(r, "A") ; ignored: the runner is paused
elseif cycle == 6 then
stmnext(r, "A")
elseif cycle == 8 then
stmnext(r, "B") ; ignored: the runner is deleted
endif
stmpause(r, cycle == 3 ? 1 : 0)
stmresume(r, cycle == 5 ? 1 : 0)
stmdelete(r, cycle == 7 ? 1 : 0)
status:k, from:k, to:k = stmadvance(r)
printks("cycle %d: advance status %d\n", 0, cycle, status)
endin
; an observer, scheduled after the writer in each control period
instr 2
r:i = gi_runner
enterB:k = stmonenter(r, "B")
exitB:k = stmonexit(r, "B")
if enterB == 1 || exitB == 1 then
printks(" enter B %d, exit B %d\n", 0, enterB, exitB)
endif
; drain every event published since the previous pass
more:k = 1
while more == 1 do
kind:k, seq:k, lost:k, more, from:k, to:k = stmevent(r)
if more == 1 then
printks(" event %d: kind %d, %d -> %d, overflow %d\n", 0, seq, kind, from, to, lost)
endif
od
endin
</CsInstruments>
<CsScore>
i 1 0 0.09
i 2 0 0.09
e
</CsScore>
</CsoundSynthesizer>
|
Its output is:
cycle 1: advance status 1
enter B 1, exit B 0
event 1: kind 1, 0 -> 1, overflow 0
cycle 2: advance status 3
enter B 1, exit B 1
event 2: kind 2, 1 -> 1, overflow 0
cycle 3: advance status 5
event 3: kind 5, 1 -> 1, overflow 0
cycle 4: advance status 5
cycle 5: advance status 0
event 4: kind 6, 1 -> 1, overflow 0
cycle 6: advance status 1
enter B 0, exit B 1
event 5: kind 1, 1 -> 0, overflow 0
cycle 7: advance status 6
event 6: kind 7, 0 -> 0, overflow 0
cycle 8: advance status 6
cycle 9: advance status 6
Event kinds: 1 transition, 2 self transition, 5 pause, 6 resume, 7 delete. The self transition in cycle 2 fires both the enter and the exit trigger of B. The request in cycle 4 is ignored because the runner is paused, and the one in cycle 8 because it is deleted. Pause, resume and delete appear as events but do not fire the enter and exit triggers.
See also
State Machine Graphs
stmonenter, stmevent
Credits
Author: Pasquale Mainolfi
Italy
New in Csound version 7.0
October 2026