# Sushi - gRPC controller wrapper
A simple wrapper for controlling sushi over gRPC via a python script.
## Prerequisites
To use this wrapper, [python3.7](https://www.python.org/downloads/) or greater needs to be installed, together with the `grpcio-tools` Python package. Both are installed by default in the development releases of Elk for the various supported architectures.
## Installation
If you are running your Python program on a device running Elk Audio OS, the latest `elkpy` should already be installed.
But, if you use elkpy on another system, e.g. macOS, you can either copy the module folder to the directory where it will be used, or install it locally with `pip3 install -e elkpy` or similar.
`elkpy` is also available on Pypi: `pip install elkpy`.
## Usage
First import the sushicontroller package, e.g.:
```python
from elkpy import sushicontroller as sc
```
Then create an instance of the `SushiController` object:
```python
controller = sc.SushiController()
```
The default gRPC address is `localhost:51051`.
To connect to another address, pass it as an argument to the constructor of the controller with the format `ip-address:port`.
The second argument to the constructor of SushiController is a path to the `sushi_rpc.proto` file, which contains Sushi's Protobuf protocol definition.
If the argument is empty, the class will look for it at `usr/share/sushi/sushi_rpc.proto`, the default installation path for Sushi.
To use the controller simply use the methods of the controller objects different sections. For example:
```python
# To make sure all the sub-controllers of SushiController close properly, you can wrap them in a try except block:
try:
# Get a list of the tracks available in sushi
list_of_tracks = controller.audio_graph.get_tracks()
# Get the parameters of the track with the id passed to the method
track_id = 0
list_of_processors = controller.parameters.get_track_parameters(track_id)
# Send a note on message to a track in sushi
track_id = 0
channel = 0
note = 65
velocity = 0.8
controller.keyboard.send_note_on(track_id, channel, note, velocity)
# To ensure proper closing of SushiController, close() should be called on your instance when you're done using it
except KeyboardInterrupt:
controller.close()
```
For full documentation on all available methods, use:
```console
$ pydoc3 elkpy.sushicontroller.SushiController
```
On the terminal where the elkpy folder is located.
## Important notes on return values
To maintain proper management of the audio thread, Sushi uses an internal queue for commands passed to it via gRPC. This means that it can not return anything else than a standard -but of limited use- response.
**It is assumed that the user will use Sushi's notification system (see `notificationcontroller.py`) to confirm that their commands have been carried out correctly!**
### Elkpy asyncio events
To alleviate this burden for _simple_ use-cases, `elkpy` adopts the following behavior:
Commands that edit the audio graph:
- create_track
- delete_track
- create_processor_on_track
- delete_processor_from_track
return an `ElkpyEvent`: an asyncio.Event that will be **set** by `elkpy` whenever the corresponding notification is emitted by Sushi.
An asyncio user can elect to `await ElkpyEvent.wait()` to ensure that the command has been properly carried out before carrying on
with further rpcs.
Ignoring the event is also a valid option for cases where absolute confirmation is not critical.
If an error has occurred in grpc, `wait()` will raise a `SushiUnknownError`.
On top of ensuring completion of gRPC calls, Elkpy events related to track and processor creation bring additional convenience once there are set.
Indeed elkpy will add relevant data to those events:
* TrackCreationEvents will have the following attributes:
* TrackCreationEvent.sushi_id: the internal id in sushi that must be used to identify the track.
* TrackCreationEvent.data: the track_info object returned by *get_track_info()*
* ProcessorCreationEvent will have:
* ProcessorCreationEvent.sushi_id: the processor internal id
* ProcessorCreationEvent.data: the return value of *get_processor_info()*
* ProcessorCreationEvent.params: a list of processor parameters as returned by *get_processor_parameters()*
#### CAUTION
elkpy uses an asyncio.EventLoop to run its notification monitoring. In asyncio programs, it will simply get the current running loop. And if that fails (for instance
if you are writing a synchronous program), it will starts a new loop in a separate thread.
You MUST therefore be careful when you instantiate the main SushiController class when writing asyncio applications. Make sure that a loop **is already running**, for instance by instantiating a SushiController inside your `async def main()`.
If you fail to do that, you will end up with 2 running loops: one in the main thread and one in an elkpy thread. And that will break the ElkpyEvent system, because asyncio.Events are **not thread-safe**!
#### Synchronous programs and ElkpyEvents
Synchronous programs may also leverage ElkpyEvents but in a different way. The object *waiting* on such events can **not** wait() on them but MUST check their `event.is_set()` method instead. This
will return `True` once *elkpy* has set the event.
For more information about `asyncio.Event`: [https://docs.python.org/3/library/asyncio-sync.html]
---
## Examples
The `examples` subdirectory contains examples of how elkpy can be used.
### Sushi Control Example
This demonstrates instantiating 3 processors onto Sushi started with an “empty” config, subscribing to notifications to wait for their instantiation, and then setting their parameters once they're available.
To run:
1. Ensure you have a Python environment set up where the packages described in requirements.txt are available, globally or in a `venv`.
2. Start Sushi with the provided "sushi_control_example_config.json", and the '--base-plugin-path' set to point to where `mda-vst.vst3` plugins are available:
```commandline
$ ./sushi --portaudio \
--config-file /path/to/elkpy/examples/sushi_control_example_config.json \
--base-plugin-path=/path/to/sushi/build/debug/VST3/Debug/
```
If you've built Sushi from source, the plugins are built and accessible in the above path relative to the sushi binary.
3. Start `sushi_control_example.py`:
```commandline
$ python3 ./sushi_control_example.py --protofile "/path/to/sushi/rpc_interface/protos/sushi_rpc.proto"
```
The `--protofile` argument points elkpy to the protocol buffer file used by Sushi.
You should hear Sushi play a familiar theme tune.
### Sushi Monitor
An example passive monitor app using elkpy.
It connects to a sushi instance, subscribes to notifications and displays all the parameter, transport and audio graph changes that Sushi broadcasts.
##### Usage
Ensure there is a Sushi instance running on the same computer.
Then run:
```
$ export SUSHI_GRPC_ELKPY_PROTO=./sushi_rpc.proto
$ python3 examples/sushi_monitor.py
```
## Running Unit Tests
Before running unit tests with the unittest command-line interface, you need to export the environment variable `SUSHI_GRPC_ELKPY_PROTO`, pointing to the Sushi's `.proto` definition file.
Example:
```
$ export SUSHI_GRPC_ELKPY_PROTO=./sushi_rpc.proto
$ python3 -m unittest discover -s tests -p '*_test.py'
```
Raw data
{
"_id": null,
"home_page": "https://github.com/elkaudio/elkpy",
"name": "elkpy",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": null,
"author": "Ruben Svensson",
"author_email": "Maxime Gendebien <max@elk.audio>, Ruben Svensson <ruben@elk.audio>",
"download_url": "https://files.pythonhosted.org/packages/4b/5a/7f3966214d23deb502a35baa224d0971e983049b414e817954f815571be1/elkpy-1.2.1.tar.gz",
"platform": null,
"description": "# Sushi - gRPC controller wrapper\n\nA simple wrapper for controlling sushi over gRPC via a python script.\n\n## Prerequisites\n\nTo use this wrapper, [python3.7](https://www.python.org/downloads/) or greater needs to be installed, together with the `grpcio-tools` Python package. Both are installed by default in the development releases of Elk for the various supported architectures.\n\n## Installation\n\nIf you are running your Python program on a device running Elk Audio OS, the latest `elkpy` should already be installed.\n\nBut, if you use elkpy on another system, e.g. macOS, you can either copy the module folder to the directory where it will be used, or install it locally with `pip3 install -e elkpy` or similar.\n`elkpy` is also available on Pypi: `pip install elkpy`.\n\n## Usage\n\nFirst import the sushicontroller package, e.g.:\n\n```python\nfrom elkpy import sushicontroller as sc\n```\n\nThen create an instance of the `SushiController` object:\n\n```python\ncontroller = sc.SushiController()\n```\n\nThe default gRPC address is `localhost:51051`.\nTo connect to another address, pass it as an argument to the constructor of the controller with the format `ip-address:port`.\n\nThe second argument to the constructor of SushiController is a path to the `sushi_rpc.proto` file, which contains Sushi's Protobuf protocol definition.\nIf the argument is empty, the class will look for it at `usr/share/sushi/sushi_rpc.proto`, the default installation path for Sushi.\n\nTo use the controller simply use the methods of the controller objects different sections. For example:\n\n```python\n# To make sure all the sub-controllers of SushiController close properly, you can wrap them in a try except block:\ntry:\n # Get a list of the tracks available in sushi\n list_of_tracks = controller.audio_graph.get_tracks()\n\n # Get the parameters of the track with the id passed to the method\n track_id = 0\n list_of_processors = controller.parameters.get_track_parameters(track_id)\n\n # Send a note on message to a track in sushi\n track_id = 0\n channel = 0\n note = 65\n velocity = 0.8\n controller.keyboard.send_note_on(track_id, channel, note, velocity)\n\n# To ensure proper closing of SushiController, close() should be called on your instance when you're done using it\nexcept KeyboardInterrupt:\n controller.close()\n```\n\nFor full documentation on all available methods, use:\n\n```console\n$ pydoc3 elkpy.sushicontroller.SushiController\n```\n\nOn the terminal where the elkpy folder is located.\n\n## Important notes on return values\n\nTo maintain proper management of the audio thread, Sushi uses an internal queue for commands passed to it via gRPC. This means that it can not return anything else than a standard -but of limited use- response.\n\n**It is assumed that the user will use Sushi's notification system (see `notificationcontroller.py`) to confirm that their commands have been carried out correctly!**\n\n### Elkpy asyncio events \n\nTo alleviate this burden for _simple_ use-cases, `elkpy` adopts the following behavior:\n\nCommands that edit the audio graph:\n\n- create_track\n- delete_track\n- create_processor_on_track\n- delete_processor_from_track\n\nreturn an `ElkpyEvent`: an asyncio.Event that will be **set** by `elkpy` whenever the corresponding notification is emitted by Sushi.\n\nAn asyncio user can elect to `await ElkpyEvent.wait()` to ensure that the command has been properly carried out before carrying on\nwith further rpcs.\nIgnoring the event is also a valid option for cases where absolute confirmation is not critical.\n\nIf an error has occurred in grpc, `wait()` will raise a `SushiUnknownError`.\n\nOn top of ensuring completion of gRPC calls, Elkpy events related to track and processor creation bring additional convenience once there are set.\nIndeed elkpy will add relevant data to those events:\n* TrackCreationEvents will have the following attributes:\n * TrackCreationEvent.sushi_id: the internal id in sushi that must be used to identify the track.\n * TrackCreationEvent.data: the track_info object returned by *get_track_info()*\n* ProcessorCreationEvent will have:\n * ProcessorCreationEvent.sushi_id: the processor internal id\n * ProcessorCreationEvent.data: the return value of *get_processor_info()*\n * ProcessorCreationEvent.params: a list of processor parameters as returned by *get_processor_parameters()*\n\n\n#### CAUTION\nelkpy uses an asyncio.EventLoop to run its notification monitoring. In asyncio programs, it will simply get the current running loop. And if that fails (for instance\nif you are writing a synchronous program), it will starts a new loop in a separate thread.\n\nYou MUST therefore be careful when you instantiate the main SushiController class when writing asyncio applications. Make sure that a loop **is already running**, for instance by instantiating a SushiController inside your `async def main()`.\n\nIf you fail to do that, you will end up with 2 running loops: one in the main thread and one in an elkpy thread. And that will break the ElkpyEvent system, because asyncio.Events are **not thread-safe**!\n\n#### Synchronous programs and ElkpyEvents\nSynchronous programs may also leverage ElkpyEvents but in a different way. The object *waiting* on such events can **not** wait() on them but MUST check their `event.is_set()` method instead. This \nwill return `True` once *elkpy* has set the event.\n\nFor more information about `asyncio.Event`: [https://docs.python.org/3/library/asyncio-sync.html]\n\n---\n\n## Examples\n\nThe `examples` subdirectory contains examples of how elkpy can be used.\n\n### Sushi Control Example\n\nThis demonstrates instantiating 3 processors onto Sushi started with an \u201cempty\u201d config, subscribing to notifications to wait for their instantiation, and then setting their parameters once they're available.\n\nTo run:\n\n1. Ensure you have a Python environment set up where the packages described in requirements.txt are available, globally or in a `venv`.\n2. Start Sushi with the provided \"sushi_control_example_config.json\", and the '--base-plugin-path' set to point to where `mda-vst.vst3` plugins are available:\n\n```commandline\n$ ./sushi --portaudio \\\n --config-file /path/to/elkpy/examples/sushi_control_example_config.json \\\n --base-plugin-path=/path/to/sushi/build/debug/VST3/Debug/\n```\n\nIf you've built Sushi from source, the plugins are built and accessible in the above path relative to the sushi binary.\n\n3. Start `sushi_control_example.py`:\n\n```commandline\n$ python3 ./sushi_control_example.py --protofile \"/path/to/sushi/rpc_interface/protos/sushi_rpc.proto\"\n```\n\nThe `--protofile` argument points elkpy to the protocol buffer file used by Sushi.\n\nYou should hear Sushi play a familiar theme tune.\n\n### Sushi Monitor\n\nAn example passive monitor app using elkpy.\nIt connects to a sushi instance, subscribes to notifications and displays all the parameter, transport and audio graph changes that Sushi broadcasts.\n\n##### Usage\n\nEnsure there is a Sushi instance running on the same computer.\n\nThen run:\n\n```\n$ export SUSHI_GRPC_ELKPY_PROTO=./sushi_rpc.proto\n$ python3 examples/sushi_monitor.py\n```\n\n## Running Unit Tests\n\nBefore running unit tests with the unittest command-line interface, you need to export the environment variable `SUSHI_GRPC_ELKPY_PROTO`, pointing to the Sushi's `.proto` definition file.\n\nExample:\n\n```\n$ export SUSHI_GRPC_ELKPY_PROTO=./sushi_rpc.proto\n$ python3 -m unittest discover -s tests -p '*_test.py'\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "A basic controller for sushi using gRPC",
"version": "1.2.1",
"project_urls": {
"Bug Tracker": "https://github.com/elkaudio/elkpy/issues",
"Homepage": "https://github.com/elkaudio/elkpy"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9bd2683a5ba1f73f582adfc2559f92d15a3d25bb4cf59dce848ae1c47c40a8f8",
"md5": "bb524b4cdd97950d65952e746a6c2735",
"sha256": "dd6205c9654d29bed0b727e8d792b1fa42057f9f9e82123e560a1d1c1fe32e67"
},
"downloads": -1,
"filename": "elkpy-1.2.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bb524b4cdd97950d65952e746a6c2735",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 68516,
"upload_time": "2024-10-29T10:47:37",
"upload_time_iso_8601": "2024-10-29T10:47:37.102873Z",
"url": "https://files.pythonhosted.org/packages/9b/d2/683a5ba1f73f582adfc2559f92d15a3d25bb4cf59dce848ae1c47c40a8f8/elkpy-1.2.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4b5a7f3966214d23deb502a35baa224d0971e983049b414e817954f815571be1",
"md5": "249fa7d9b805df9be95ec0483d2b03c8",
"sha256": "c19f97808322615d942ed6340cb300f000cb85ded9ee974a8299e00e9e12f992"
},
"downloads": -1,
"filename": "elkpy-1.2.1.tar.gz",
"has_sig": false,
"md5_digest": "249fa7d9b805df9be95ec0483d2b03c8",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 53469,
"upload_time": "2024-10-29T10:47:38",
"upload_time_iso_8601": "2024-10-29T10:47:38.861810Z",
"url": "https://files.pythonhosted.org/packages/4b/5a/7f3966214d23deb502a35baa224d0971e983049b414e817954f815571be1/elkpy-1.2.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-29 10:47:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "elkaudio",
"github_project": "elkpy",
"github_not_found": true,
"lcname": "elkpy"
}