Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement setting node controls by index and range #340

Merged
merged 4 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions supriya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
AsyncServer,
BaseServer,
Buffer,
BufferGroup,
Bus,
BusGroup,
Context,
Group,
Node,
Score,
Expand All @@ -37,6 +40,7 @@
SynthDefBuilder,
synthdef,
)
from .ugens import UGen, UGenArray, UGenMethodMixin
from .assets.synthdefs import default
from .scsynth import Options

Expand All @@ -60,10 +64,13 @@
"BaseClock",
"BaseServer",
"Buffer",
"BufferGroup",
"Bus",
"BusGroup",
"CalculationRate",
"Clock",
"ClockContext",
"Context",
"DoneAction",
"Group",
"HeaderFormat",
Expand All @@ -80,6 +87,9 @@
"Synth",
"SynthDef",
"SynthDefBuilder",
"UGen",
"UGenArray",
"UGenMethodMixin",
"__version__",
"__version_info__",
"default",
Expand Down
51 changes: 45 additions & 6 deletions supriya/contexts/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
SetControlBus,
SetControlBusRange,
SetNodeControl,
SetNodeControlRange,
WriteBuffer,
ZeroBuffer,
)
Expand Down Expand Up @@ -1205,21 +1206,59 @@ def set_bus_range(self, bus: Bus, values: Sequence[float]) -> None:
request = SetControlBusRange(items=[(bus.id_, values)])
self._add_requests(request)

def set_node(self, node: Node, **settings: SupportsFloat) -> None:
def set_node(
self,
node: Node,
*indexed_settings: Tuple[int, Union[SupportsFloat, Sequence[SupportsFloat]]],
**settings: Union[SupportsFloat, Sequence[SupportsFloat]],
) -> None:
"""
Set a node's controls.

Emit ``/n_set`` requests.

:param node: The node whose controls will be set.
:param indexed_settings: A sequence of control indices to values.
:param settings: A mapping of control names to values.
"""
self._validate_can_request()
coerced_settings: Dict[Union[int, str], float] = {}
for key, value in settings.items():
coerced_settings[key] = float(value)
request = SetNodeControl(
node_id=node.id_, items=sorted(coerced_settings.items())
coerced_settings: Dict[Union[int, str], Union[float, Sequence[float]]] = {}
for index, values in sorted(indexed_settings):
if isinstance(values, Sequence):
coerced_settings[index] = [float(value) for value in values]
else:
coerced_settings[index] = float(values)
for key, values in sorted(settings.items()):
if isinstance(values, Sequence):
coerced_settings[key] = [float(value) for value in values]
else:
coerced_settings[key] = float(values)
request = SetNodeControl(node_id=node.id_, items=list(coerced_settings.items()))
self._add_requests(request)

def set_node_range(
self,
node: Node,
*indexed_settings: Tuple[int, Sequence[SupportsFloat]],
**settings: Sequence[SupportsFloat],
) -> None:
"""
Set a range of node controls.

Emit ``/n_setn`` requests.

:param node: The node whose controls will be set.
:param indexed_settings: A sequence of control indices to values.
:param settings: A mapping of control names to values.
"""
self._validate_can_request()
coerced_settings: Dict[Union[int, str], Sequence[float]] = {}
for index, values in sorted(indexed_settings):
coerced_settings[index] = [float(value) for value in values]
for key, values in sorted(settings.items()):
coerced_settings[key] = [float(value) for value in values]
request = SetNodeControlRange(
node_id=node.id_, items=list(coerced_settings.items())
)
self._add_requests(request)

Expand Down
22 changes: 19 additions & 3 deletions supriya/contexts/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,15 +697,31 @@ def query(
self, sync=sync
)

def set(self, **settings: SupportsFloat) -> None:
def set(
self,
*indexed_settings: Tuple[int, Union[SupportsFloat, Sequence[SupportsFloat]]],
**settings: Union[SupportsFloat, Sequence[SupportsFloat]],
) -> None:
"""
Set the node's controls.

Emit ``/n_set <node.id_> gate 0`` for synths with ``gate`` controls.
:param indexed_settings: A sequence of control indices to values.
:param settings: A mapping of control names to values.
"""
self.context.set_node(self, *indexed_settings, **settings)

def set_range(
self,
*indexed_settings: Tuple[int, Sequence[SupportsFloat]],
**settings: Sequence[SupportsFloat],
) -> None:
"""
Set a range of the node's controls.

:param indexed_settings: A sequence of control indices to values.
:param settings: A mapping of control names to values.
"""
self.context.set_node(self, **settings)
self.context.set_node_range(self, *indexed_settings, **settings)

def unpause(self) -> None:
"""
Expand Down
26 changes: 17 additions & 9 deletions supriya/contexts/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1846,21 +1846,26 @@ class SetNodeControl(Request):
... items=[
... ("frequency", 440.0),
... ("amplitude", 1.0),
... (3, 1.234),
... ("positions", [0.5, 0.25, 0.75]),
... (4, [0.1, 0.2]),
... ],
... )
>>> request.to_osc()
OscMessage('/n_set', 1000, 'frequency', 440.0, 'amplitude', 1.0)
OscMessage('/n_set', 1000, 'frequency', 440.0, 'amplitude', 1.0, 3, 1.234, 'positions', [0.5, 0.25, 0.75], 4, [0.1, 0.2])
"""

node_id: SupportsInt
items: Sequence[Tuple[Union[int, str], float]]
items: Sequence[Tuple[Union[int, str], Union[float, Sequence[float]]]]

def to_osc(self) -> OscMessage:
contents: List[Union[float, str]] = [int(self.node_id)]
for control, value in self.items:
contents.extend(
[control if isinstance(control, str) else int(control), float(value)]
)
contents: List[Union[float, str, List[float]]] = [int(self.node_id)]
for control, values in self.items:
contents.append(control if isinstance(control, str) else int(control))
if isinstance(values, Sequence):
contents.append([float(value) for value in values])
else:
contents.append(float(values))
return OscMessage(RequestName.NODE_SET, *contents)


Expand All @@ -1874,10 +1879,13 @@ class SetNodeControlRange(Request):
>>> from supriya.contexts.requests import SetNodeControlRange
>>> request = SetNodeControlRange(
... node_id=1000,
... items=[("frequency", (440.0, 441.0, 432.0))],
... items=[
... ("frequency", (440.0, 441.0, 432.0)),
... (3, (0.5, 0.25, 0.75)),
... ],
... )
>>> request.to_osc()
OscMessage('/n_setn', 1000, 'frequency', 3, 440.0, 441.0, 432.0)
OscMessage('/n_setn', 1000, 'frequency', 3, 440.0, 441.0, 432.0, 3, 3, 0.5, 0.25, 0.75)
"""

node_id: SupportsInt
Expand Down
6 changes: 3 additions & 3 deletions supriya/synthdefs/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import inspect
import threading
import uuid
from typing import Dict, List, Optional, Tuple, Union
from typing import Callable, Dict, List, Optional, Tuple, Union

from uqbar.objects import new

Expand Down Expand Up @@ -176,9 +176,9 @@ def name(self) -> Optional[str]:
return self._name


def synthdef(*args: Union[str, Tuple[str, float]]):
def synthdef(*args: Union[str, Tuple[str, float]]) -> Callable[[Callable], SynthDef]:
"""
Decorate for quickly constructing SynthDefs from functions.
Decorator for quickly constructing SynthDefs from functions.

::

Expand Down
32 changes: 30 additions & 2 deletions tests/contexts/test_Score_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,40 @@ def test_pause_node(context):
def test_set_node(context):
with context.at(0):
group = context.add_group()
group.set(foo=3.145, bar=4.5)
group.set((1, 2.3), (2, [3.4, 4.5]), foo=3.145, bar=4.5, baz=[1.23, 4.56])
assert list(context.iterate_osc_bundles()) == [
OscBundle(
contents=(
OscMessage("/g_new", 1000, 0, 0),
OscMessage("/n_set", 1000, "bar", 4.5, "foo", 3.145),
OscMessage(
"/n_set",
1000,
1,
2.3,
2,
[3.4, 4.5],
"bar",
4.5,
"baz",
[1.23, 4.56],
"foo",
3.145,
),
),
timestamp=0.0,
)
]


def test_set_node_range(context):
with context.at(0):
group = context.add_group()
group.set_range((2, [3.4, 4.5]), baz=[1.23, 4.56])
assert list(context.iterate_osc_bundles()) == [
OscBundle(
contents=(
OscMessage("/g_new", 1000, 0, 0),
OscMessage("/n_setn", 1000, 2, 2, 3.4, 4.5, "baz", 2, 1.23, 4.56),
),
timestamp=0.0,
)
Expand Down
27 changes: 25 additions & 2 deletions tests/contexts/test_Server_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,32 @@ async def test_query_node(context):
async def test_set_node(context):
group = context.add_group()
with context.osc_protocol.capture() as transcript:
group.set(foo=3.145, bar=4.5)
group.set((1, 2.3), (2, [3.4, 4.5]), foo=3.145, bar=4.5, baz=[1.23, 4.56])
assert transcript.filtered(received=False, status=False) == [
OscMessage("/n_set", 1000, "bar", 4.5, "foo", 3.145)
OscMessage(
"/n_set",
1000,
1,
2.3,
2,
[3.4, 4.5],
"bar",
4.5,
"baz",
[1.23, 4.56],
"foo",
3.145,
)
]


@pytest.mark.asyncio
async def test_set_node_range(context):
group = context.add_group()
with context.osc_protocol.capture() as transcript:
group.set_range((2, [3.4, 4.5]), baz=[1.23, 4.56])
assert transcript.filtered(received=False, status=False) == [
OscMessage("/n_setn", 1000, 2, 2, 3.4, 4.5, "baz", 2, 1.23, 4.56)
]


Expand Down