Hello, world!, debugged¶
Info
See the full example source code on GitHub.
Let’s revisit aspects of the previous two “hello, world!” examples and introduce a variety of debugging techniques.
It’s not uncommon to wonder “why isn’t the audio I expect happening?” We have a variety of ways to debug this with Supriya (although your volume could always just be too low).
We’ll employ:
Server logging: Did it boot up? Has it reported any errors?
Server status: Is my sample rate correct? Do the counts of nodes, of synths, of synthdefs make sense to me?
Node tree queries: Is the structure of nodes on the server what I expect?
OSC transcripts: Did I send the messages I thought I sent? Did I receive the message I should have received?
Performance logic¶
The performance logic is almost exactly the same as in the Hello, world!, context-agnostic example:
def play_synths(context: supriya.Context) -> list[supriya.Synth]:
"""
Play a C-major chord on ``context``.
"""
# Start an OSC bundle to run immediately:
with context.at():
# A C-major chord
frequencies = [261.63, 329.63, 392.00]
# Create an empty list to store synths in:
synths: list[supriya.Synth] = []
# Add the default synthdef to the server and open a "completion" context
# manager to group further commands for when the synthdef finishes loading:
with context.add_synthdefs(supriya.default):
# Loop over the frequencies:
for frequency in frequencies:
# Create a synth using the default synthdef and the frequency
# and add it to the list of synths:
synths.append(
context.add_synth(synthdef=supriya.default, frequency=frequency)
)
return synths
def stop_synths(context: supriya.Context, synths: list[supriya.Synth]) -> None:
"""
Stop ``synths``.
"""
# Start an OSC bundle to run immediately:
with context.at():
# Loop over the synths and free them
for synth in synths:
synth.free()
One small difference, just to save some vertical space in the
main()
function is to move the use of
at()
inside the two performance
functions. Because we’re querying the context state, the context must be
realtime, so we can rely on it supporting timestamp-free OSC bundling.
Debugging¶
OK, let’s debug our performance.
We’ll write a context manager, facilitated by Python’s
contextlib.contextmanager()
decorator, to debug the state of the
server and (optionally!) any operations we performed against it:
@contextlib.contextmanager
def debug(
header: str,
server: supriya.Server,
) -> Generator[None, None, None]:
"""
A context manager for printing debugging information.
"""
# Capture any OSC messages sent or received
with server.osc_protocol.capture() as transcript:
# Yield to the with block body
yield
# Print a header
print(header)
# Print the server status
print(f" Status: {server.status}")
# Filter out any /status or /status.reply messages from the transcript
if len(entries := transcript.filtered()):
print(" Transcript:")
# Print the OSC transcript's filtered, captured messages
for entry in entries:
# A received message
if entry.label == "R":
print(f" Received: {entry.message!r}")
# A sent message
else:
print(f" Sent: {entry.message!r}")
# Print the node tree
for line in str(server.query_tree()).splitlines():
print(f" Tree: {line}")
The context manager will let us start capturing OSC messages sent and received into a transcript, yield so we can perform an action against the server if we wish, and when we’re done yielding we’ll print out:
a header string,
the server’s status,
any messages we sent to or received from the server, excluding the
/status
and/status.reply
messages that are always coming and going during standard operations,and the server’s node tree.
Integration¶
Now let’s sprinkle that debugging context manager throughout our
main()
function.
We’ll also turn on logging output (set to a base level of WARNING
so we
don’t see too much) and then dial in the supriya.scsynth
logger to
INFO
so we can see info about the server during booting and quitting.
def main() -> None:
"""
The example entry-point function.
"""
# Turn on basic logging output interpreter-wide.
# Explicitly set the stream to stdout so that the output looks the same in
# your terminal as it does in the documentation! Normally you don't need to
# worry about this.
logging.basicConfig(level=logging.WARNING, stream=sys.stdout)
# Set Supriya's supriya.scsynth logger level to INFO
logging.getLogger("supriya.scsynth").setLevel(logging.INFO)
# Create a server and boot it:
server = supriya.Server().boot()
# Print debug info before we play anything ...
# Should be just an empty server with the default groups.
with debug("BEFORE PLAYING:", server):
pass
# Print debug info immediately after we play the synths ...
# No synths should be allocated yet because their SynthDef hasn't finished
# allocation.
with debug("IMMEDIATELY AFTER PLAYING:", server):
synths = play_synths(context=server)
# Print debug info after syncing with the server ...
# The synths should now be present.
with debug("PLAYING:", server):
server.sync()
# Print debug info after 4 seconds of waiting ...
# Nothing should have changed since the synths allocated.
with debug("JUST BEFORE STOPPING:", server):
time.sleep(4)
# Print debug info immediately after stopping the synths
# The synths are still present, but their "gate" parameters have flipped to
# zero.
with debug("IMMEDIATELY AFTER STOPPING:", server):
stop_synths(context=server, synths=synths)
# Print debug info after 1 second of waiting ...
# The synths should now be gone.
with debug("AFTER RELEASING:", server):
time.sleep(1)
# Quit the server
server.quit()
Note
While the with debug(...):`
lines come before the operations we want
to debug, their output actually comes after. Inside our debugging context
manager, the body of our with
block executes at the moment we
yield
, and everything after the yield
keyword occurs when we exit
that with
block. This might look a little “backwards” in our
main()
function, but sit with it.
Invocation¶
You can invoke the script and see its (very verbose) output with …
josephine@laptop:~/supriya$ python -m examples.hello_world_debugged
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] booting ...
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] command: /usr/local/bin/scsynth -R 0 -l 1 -u 57110
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] received: Cannot lock down 107341340 byte memory area (Cannot allocate memory)
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] received: JackDriver: client name is 'SuperCollider'
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] received: SC_AudioDriver: sample rate = 44100.000000, driver's block size = 1024
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] received: SuperCollider 3 server ready.
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] ... booted!
BEFORE PLAYING:
Status: StatusInfo(actual_sample_rate=44106.022607717845, average_cpu_usage=0.06155474856495857, group_count=2, peak_cpu_usage=0.22713929414749146, synth_count=0, synthdef_count=32, target_sample_rate=44100.0, ugen_count=0)
Tree: NODE TREE 0 group
Tree: 1 group
IMMEDIATELY AFTER PLAYING:
Status: StatusInfo(actual_sample_rate=44106.022607717845, average_cpu_usage=0.06155474856495857, group_count=2, peak_cpu_usage=0.22713929414749146, synth_count=0, synthdef_count=32, target_sample_rate=44100.0, ugen_count=0)
Transcript:
Sent: OscMessage('/d_recv', b'SCgf\x00\x00\x00\x02\x00\x01\x0fsupriya:default\x00\x00\x00\x0c\x00\x00\x00\x00>\x99\x99\x9a<#\xd7\n?333@\x00\x00\x00\xbe\xcc\xcc\xcd>\xcc\xcc\xcdEz\x00\x00E\x9c@\x00E\x1c@\x00EH\x00\x00?\x80\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00=\xcc\xcc\xcdC\xdc\x00\x00?\x80\x00\x00?\x00\x00\x00\x00\x00\x00\x05\x03out\x00\x00\x00\x00\tamplitude\x00\x00\x00\x01\tfrequency\x00\x00\x00\x02\x04gate\x00\x00\x00\x03\x03pan\x00\x00\x00\x04\x00\x00\x00\x14\x07Control\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07Control\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x01\x01\x01\x01\x01\x06VarSaw\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x01\x02\x05Linen\x01\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\xff\xff\xff\xff\x00\x00\x00\x02\xff\xff\xff\xff\x00\x00\x00\x03\xff\xff\xff\xff\x00\x00\x00\x01\xff\xff\xff\xff\x00\x00\x00\x04\x01\x04Rand\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x05\xff\xff\xff\xff\x00\x00\x00\x00\x00\x0cBinaryOpUGen\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x01\x06VarSaw\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x01\x02\x04Rand\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x06\x00\x0cBinaryOpUGen\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x01\x06VarSaw\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x01\x02\x04Sum3\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x02\x0cBinaryOpUGen\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\x02\x00\x00\x00\n\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x01\x02\x04Rand\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\x07\xff\xff\xff\xff\x00\x00\x00\x08\x00\x04Rand\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\xff\xff\xff\xff\x00\x00\x00\t\xff\xff\xff\xff\x00\x00\x00\n\x00\x05XLine\x01\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x0b\xff\xff\xff\xff\x00\x00\x00\x00\x01\x03LPF\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x02\x0cBinaryOpUGen\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\x02\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x02\x0cBinaryOpUGen\x02\x00\x00\x00\x02\x00\x00\x00\x01\x00\x02\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02\x04Pan2\x02\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\xff\xff\xff\xff\x00\x00\x00\x0b\x02\x02\tOffsetOut\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x01\x00\x00', OscBundle(contents=[OscMessage('/s_new', 'supriya:default', 1000, 0, 1, 'frequency', 261.63), OscMessage('/s_new', 'supriya:default', 1001, 0, 1, 'frequency', 329.63), OscMessage('/s_new', 'supriya:default', 1002, 0, 1, 'frequency', 392.0)]))
Tree: NODE TREE 0 group
Tree: 1 group
PLAYING:
Status: StatusInfo(actual_sample_rate=44106.022607717845, average_cpu_usage=0.06155474856495857, group_count=2, peak_cpu_usage=0.22713929414749146, synth_count=0, synthdef_count=32, target_sample_rate=44100.0, ugen_count=0)
Transcript:
Sent: OscMessage('/sync', 1)
Received: OscMessage('/n_go', 1000, 1, -1, -1, 0)
Received: OscMessage('/n_go', 1001, 1, -1, 1000, 0)
Received: OscMessage('/n_go', 1002, 1, -1, 1001, 0)
Received: OscMessage('/done', '/d_recv')
Received: OscMessage('/synced', 1)
Tree: NODE TREE 0 group
Tree: 1 group
Tree: 1002 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 392.0, gate: 1.0, pan: 0.5
Tree: 1001 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 329.630005, gate: 1.0, pan: 0.5
Tree: 1000 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 261.630005, gate: 1.0, pan: 0.5
JUST BEFORE STOPPING:
Status: StatusInfo(actual_sample_rate=44105.22998562333, average_cpu_usage=0.39666128158569336, group_count=2, peak_cpu_usage=0.396695613861084, synth_count=3, synthdef_count=33, target_sample_rate=44100.0, ugen_count=60)
Tree: NODE TREE 0 group
Tree: 1 group
Tree: 1002 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 392.0, gate: 1.0, pan: 0.5
Tree: 1001 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 329.630005, gate: 1.0, pan: 0.5
Tree: 1000 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 261.630005, gate: 1.0, pan: 0.5
IMMEDIATELY AFTER STOPPING:
Status: StatusInfo(actual_sample_rate=44105.22998562333, average_cpu_usage=0.39666128158569336, group_count=2, peak_cpu_usage=0.396695613861084, synth_count=3, synthdef_count=33, target_sample_rate=44100.0, ugen_count=60)
Transcript:
Sent: OscBundle(contents=[OscMessage('/n_set', 1000, 'gate', 0.0), OscMessage('/n_set', 1001, 'gate', 0.0), OscMessage('/n_set', 1002, 'gate', 0.0)])
Tree: NODE TREE 0 group
Tree: 1 group
Tree: 1002 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 392.0, gate: 0.0, pan: 0.5
Tree: 1001 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 329.630005, gate: 0.0, pan: 0.5
Tree: 1000 supriya:default
Tree: out: 0.0, amplitude: 0.1, frequency: 261.630005, gate: 0.0, pan: 0.5
AFTER RELEASING:
Status: StatusInfo(actual_sample_rate=44104.89029916077, average_cpu_usage=0.4097205400466919, group_count=2, peak_cpu_usage=0.42003315687179565, synth_count=0, synthdef_count=33, target_sample_rate=44100.0, ugen_count=0)
Transcript:
Received: OscMessage('/n_end', 1002, 1, -1, 1001, 0)
Received: OscMessage('/n_end', 1001, 1, -1, 1000, 0)
Received: OscMessage('/n_end', 1000, 1, -1, -1, 0)
Tree: NODE TREE 0 group
Tree: 1 group
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] quitting ...
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] ... quit!
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] quitting ...
INFO:supriya.scsynth:[127.0.0.1:57110/0x7f97f2f28830] ... already quit!
That’s a lot of output! Depending on your application, you may need to dial it down, or dial it up. Now you have the building blocks to do so.