Episode 2: When a Node Refuses

Read a refusal from start() for what it says, two sources at different rates joined in a Router, and fix it with a Decimator.

Before it runs, a node checks what its input ports carry. If two streams cannot be combined, it says so and refuses, rather than producing a number that looks plausible and means nothing. The refusal you are most likely to meet is two sources at different sampling rates, and this episode provokes it on purpose.

Provoke it

A 500 Hz source and a 250 Hz source into one Router. One step of a node consumes one frame from every input at once, so the inputs cannot run at different rates, and start() raises. The script catches the exception and prints it.

 7    # Two sources that do not agree. One step of a node consumes one
 8    # frame from every input at once, so the inputs cannot run at
 9    # different rates
10    fast = gp.Generator(sampling_rate=500, channel_count=2, name="fast")
11    slow = gp.Generator(sampling_rate=250, channel_count=2, name="slow")
12
13    print("--- joining 500 Hz and 250 Hz")
14    try:
15        p = gp.Pipeline()
16        router = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL])
17        p.connect(fast, router["in1"])
18        p.connect(slow, router["in2"])
19        p.connect(router, gp.Collector())
20        p.start()
21    except Exception as error:
22        print(type(error).__name__ + ":", error)
23    finally:
24        p.stop()
25        p.close()
Read the whole message

The pipeline says which node could not set up, and the node says why:

--- joining 500 Hz and 250 Hz
RuntimeError: Router could not set up, so the pipeline is not running:
ValueError: All ports must have the same sampling rate; Router got
in1=500, in2=250. One step reads one frame from every input at once,
so the inputs cannot run at different rates. Insert a Decimator on
the faster branch, or take both inputs from the same source. Nothing
was emitted and no sink wrote a row; fix what the node reported and
start again.

It names the node, both ports and both rates, and says what to do about it. A message like this is written to tell you which connection is wrong, not merely that something is, so read it to the end before changing anything.

Fix it

Do what the message says: a Decimator by two on the fast branch brings it to 250 Hz, and the same pipeline starts without complaint.

27    # The message names the node, both ports and both rates, and says
28    # what to do: so decimate the fast branch down to 250 Hz
29    print()
30    print("--- the same pipeline, with a Decimator on the fast branch")
31    p = gp.Pipeline()
32    fast = gp.Generator(sampling_rate=500, channel_count=2, name="fast")
33    slow = gp.Generator(sampling_rate=250, channel_count=2, name="slow")
34    router = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL])
35    halve = gp.Decimator(decimation_factor=2)
36    p.connect(fast, halve)
37    p.connect(halve, router["in1"])
38    p.connect(slow, router["in2"])
39    p.connect(router, gp.Collector())
40    p.start()
41    print("started without complaint")
42    p.stop()
43    p.close()
Result

Run the script. The first half prints the refusal above; the second prints started without complaint. Other refusals read the same way: a frame size that differs between inputs, as Season 2, Episode 5 met, or a channel count a node cannot take. Each one comes from start(), before a single sample flows, because it is knowable then.

Summary

In this episode, you learned how to:

  • recognise a refusal at start(), and which node it comes from

  • read the message for the ports, the rates and the remedy it names

  • fix a rate mismatch with a Decimator on the faster branch

Proceed to Episode 3, where a pipeline that runs is asked whether it is keeping up.

Nodes in this episode: Generator, Router, Decimator, Collector

File s6e2_when_a_node_refuses.py – View file on GitHub

 1"""Season 6, Episode 2: a refusal at start, read and then fixed."""
 2
 3import gpype as gp
 4
 5if __name__ == "__main__":
 6
 7    # Two sources that do not agree. One step of a node consumes one
 8    # frame from every input at once, so the inputs cannot run at
 9    # different rates
10    fast = gp.Generator(sampling_rate=500, channel_count=2, name="fast")
11    slow = gp.Generator(sampling_rate=250, channel_count=2, name="slow")
12
13    print("--- joining 500 Hz and 250 Hz")
14    try:
15        p = gp.Pipeline()
16        router = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL])
17        p.connect(fast, router["in1"])
18        p.connect(slow, router["in2"])
19        p.connect(router, gp.Collector())
20        p.start()
21    except Exception as error:
22        print(type(error).__name__ + ":", error)
23    finally:
24        p.stop()
25        p.close()
26
27    # The message names the node, both ports and both rates, and says
28    # what to do: so decimate the fast branch down to 250 Hz
29    print()
30    print("--- the same pipeline, with a Decimator on the fast branch")
31    p = gp.Pipeline()
32    fast = gp.Generator(sampling_rate=500, channel_count=2, name="fast")
33    slow = gp.Generator(sampling_rate=250, channel_count=2, name="slow")
34    router = gp.Router(input_channels=[gp.Router.ALL, gp.Router.ALL])
35    halve = gp.Decimator(decimation_factor=2)
36    p.connect(fast, halve)
37    p.connect(halve, router["in1"])
38    p.connect(slow, router["in2"])
39    p.connect(router, gp.Collector())
40    p.start()
41    print("started without complaint")
42    p.stop()
43    p.close()