stagedings 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stagedings-0.1.0/LICENSE.md +7 -0
- stagedings-0.1.0/PKG-INFO +150 -0
- stagedings-0.1.0/README.md +125 -0
- stagedings-0.1.0/pyproject.toml +59 -0
- stagedings-0.1.0/setup.cfg +4 -0
- stagedings-0.1.0/src/stagedings/__init__.py +0 -0
- stagedings-0.1.0/src/stagedings/cli.py +296 -0
- stagedings-0.1.0/src/stagedings/core/__init__.py +0 -0
- stagedings-0.1.0/src/stagedings/core/connection.py +33 -0
- stagedings-0.1.0/src/stagedings/core/control.py +59 -0
- stagedings-0.1.0/src/stagedings/core/osc.py +53 -0
- stagedings-0.1.0/src/stagedings/core/scene.py +59 -0
- stagedings-0.1.0/src/stagedings/models/__init__.py +0 -0
- stagedings-0.1.0/src/stagedings/models/base_model.py +14 -0
- stagedings-0.1.0/src/stagedings/static/config.json +7 -0
- stagedings-0.1.0/src/stagedings/static/css/cyborg/bootstrap.min.css +12 -0
- stagedings-0.1.0/src/stagedings/static/js/bootstrap.min.js +7 -0
- stagedings-0.1.0/src/stagedings/static/js/bootstrap.min.js.map +1 -0
- stagedings-0.1.0/src/stagedings/static/js/jquery-4.0.0.slim.min.js +2 -0
- stagedings-0.1.0/src/stagedings/templates/base.html +23 -0
- stagedings-0.1.0/src/stagedings/templates/nav.html +25 -0
- stagedings-0.1.0/src/stagedings/templates/no_context.html +18 -0
- stagedings-0.1.0/src/stagedings/templates/ui.html +310 -0
- stagedings-0.1.0/src/stagedings.egg-info/PKG-INFO +150 -0
- stagedings-0.1.0/src/stagedings.egg-info/SOURCES.txt +27 -0
- stagedings-0.1.0/src/stagedings.egg-info/dependency_links.txt +1 -0
- stagedings-0.1.0/src/stagedings.egg-info/entry_points.txt +2 -0
- stagedings-0.1.0/src/stagedings.egg-info/requires.txt +6 -0
- stagedings-0.1.0/src/stagedings.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stagedings
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An API to navigate scenes and subscenes that has been configured in a mididings script
|
|
5
|
+
Author-email: Stéphane Gagnon <stephane-gagnon@hotmail.com>
|
|
6
|
+
Maintainer-email: Stéphane Gagnon <stephane-gagnon@hotmail.com>
|
|
7
|
+
License-Expression: GPL-2.0-or-later
|
|
8
|
+
Project-URL: Homepage, https://github.com/mididings
|
|
9
|
+
Project-URL: Documentation, https://mididings.github.io/stagedings/
|
|
10
|
+
Project-URL: Repository, https://github.com/mididings/stagedings
|
|
11
|
+
Keywords: mididings,midi,live-performance,music,control-surface,fastapi,websocket,osc
|
|
12
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE.md
|
|
18
|
+
Requires-Dist: fastapi
|
|
19
|
+
Requires-Dist: uvicorn[standard]
|
|
20
|
+
Requires-Dist: jinja2
|
|
21
|
+
Requires-Dist: pydantic
|
|
22
|
+
Requires-Dist: scalar-fastapi
|
|
23
|
+
Requires-Dist: mididings[autorestart,osc]
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# stagedings
|
|
27
|
+
An API to navigate scenes and subscenes that has been configured in a [mididings](https://github.com/mididings/mididings) script
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/stagedings/)
|
|
30
|
+

|
|
31
|
+
[](https://swagger.io/specification/)
|
|
32
|
+
[](https://mididings.discourse.group/)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
### What does stagedings allow?
|
|
37
|
+
* A web-based interface
|
|
38
|
+
* Alternative of the legacy **`livedings UI`**, which was based on Tkinter 🪓
|
|
39
|
+
* A HTTP layer that facilitates control and navigation allowing the abstraction of OSC subcalls
|
|
40
|
+
* An OpenAPI specification making possible to generate a client SDK in multiple language with a code generator like [Kiota](https://github.com/microsoft/kiota) making possible to use the API in .NET, Go, Java, PHP, Python, Ruby and TypeScript.
|
|
41
|
+
|
|
42
|
+
⚠️ *A scene patch dictionary defined in the `run` section of your mididings script is required to work correctly, check the [`run` function documentation for more information](https://mididings.github.io/mididings/main.html#mididings.run) on how to structure your patch.*
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## 📘 API documentation
|
|
46
|
+
- [stagedings manual](https://mididings.github.io/stagedings)
|
|
47
|
+
- [mididings manual](https://mididings.github.io/mididings)
|
|
48
|
+
|
|
49
|
+
## Frontend
|
|
50
|
+
|
|
51
|
+
### A responsive multiclient, real-time interface for scene/subscene navigation
|
|
52
|
+
|
|
53
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/frontend.png" alt="stagedings UI screenshot" width="700"/>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
## Features
|
|
57
|
+
- Web UI with real-time scene/subscene updates
|
|
58
|
+
- FastAPI backend with full REST and WebSocket support
|
|
59
|
+
- Multiple clients supported
|
|
60
|
+
- Use the mididings OSC interface
|
|
61
|
+
- It exposes a **fully compliant OpenAPI spec** for easy generation of SDK clients in any language, enabling flexible remote control of mididings
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### The frontend allow
|
|
66
|
+
* Direct navigation through scenes and subscenes
|
|
67
|
+
* Exposes the Restart, Panic, Query and Quit commands
|
|
68
|
+
|
|
69
|
+
### The backend allow
|
|
70
|
+
* Endpoints for direct navigation through scenes and subscenes
|
|
71
|
+
* Endpoints to the Restart, Panic, Query and Quit commands
|
|
72
|
+
|
|
73
|
+
#### ℹ️ About commands
|
|
74
|
+
* ***Restart*** will restart mididings process
|
|
75
|
+
* ***Panic*** send not off to all ports and all channels
|
|
76
|
+
* ***Quit*** stop mididings, be carefull
|
|
77
|
+
* *Query is a work in progress*
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## ⚒️ Installation from PyPI
|
|
83
|
+
**NOTE:** This will also install mididings with OSC and AutoRestart support allowing a full working stack.
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
# Create a Python virtual environment
|
|
87
|
+
$ python3 -m venv .venv
|
|
88
|
+
$ source .venv/bin/activate
|
|
89
|
+
|
|
90
|
+
# Install stagedings including mididings as a dependency
|
|
91
|
+
$ pip install stagedings
|
|
92
|
+
```
|
|
93
|
+
## ▶️ Running the application
|
|
94
|
+
```sh
|
|
95
|
+
$ stagedings [--host HOST] [--port PORT]
|
|
96
|
+
```
|
|
97
|
+
## Options
|
|
98
|
+
* --host
|
|
99
|
+
* FastAPI server bind address
|
|
100
|
+
* Default: localhost
|
|
101
|
+
* Use 0.0.0.0 to allow network access or the server IP address
|
|
102
|
+
* --port
|
|
103
|
+
* FastAPI + WebSocket server port
|
|
104
|
+
* Default: 5000
|
|
105
|
+
|
|
106
|
+
## Use cases
|
|
107
|
+
### Local development (single machine)
|
|
108
|
+
```sh
|
|
109
|
+
$ stagedings
|
|
110
|
+
```
|
|
111
|
+
This runs everything locally on:
|
|
112
|
+
```sh
|
|
113
|
+
http://localhost:5000
|
|
114
|
+
```
|
|
115
|
+
### Network / multi-client setup
|
|
116
|
+
When clients access the server from other machines, you must expose the backend:
|
|
117
|
+
```sh
|
|
118
|
+
$ stagedings --host 0.0.0.0
|
|
119
|
+
```
|
|
120
|
+
or:
|
|
121
|
+
```sh
|
|
122
|
+
$ stagedings --host 192.168.1.100
|
|
123
|
+
```
|
|
124
|
+
### Accessing the UI
|
|
125
|
+
Once running, open in a browser:
|
|
126
|
+
```sh
|
|
127
|
+
http://dings.local.com:5000
|
|
128
|
+
```
|
|
129
|
+
or:
|
|
130
|
+
```sh
|
|
131
|
+
http://192.168.1.100:5000
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## High Level Overview
|
|
135
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/overview.png" alt="stagedings UI screenshot" width="800"/>
|
|
136
|
+
|
|
137
|
+
## 🔗 Communication Workflow
|
|
138
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/workflow.png" alt="stagedings UI screenshot" width="800"/>
|
|
139
|
+
|
|
140
|
+
### 💬 Feedback & Contributions
|
|
141
|
+
|
|
142
|
+
We welcome bug reports, feature ideas, and contributions! Please open an issue or discussion
|
|
143
|
+
|
|
144
|
+
### 📜 License
|
|
145
|
+
|
|
146
|
+
All files in this repository are released under the terms of the GNU
|
|
147
|
+
General Public License as published by the Free Software Foundation;
|
|
148
|
+
either version 2 or later of the License.
|
|
149
|
+
|
|
150
|
+
Made in Québec 🇨🇦
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# stagedings
|
|
2
|
+
An API to navigate scenes and subscenes that has been configured in a [mididings](https://github.com/mididings/mididings) script
|
|
3
|
+
|
|
4
|
+
[](https://pypi.org/project/stagedings/)
|
|
5
|
+

|
|
6
|
+
[](https://swagger.io/specification/)
|
|
7
|
+
[](https://mididings.discourse.group/)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
### What does stagedings allow?
|
|
12
|
+
* A web-based interface
|
|
13
|
+
* Alternative of the legacy **`livedings UI`**, which was based on Tkinter 🪓
|
|
14
|
+
* A HTTP layer that facilitates control and navigation allowing the abstraction of OSC subcalls
|
|
15
|
+
* An OpenAPI specification making possible to generate a client SDK in multiple language with a code generator like [Kiota](https://github.com/microsoft/kiota) making possible to use the API in .NET, Go, Java, PHP, Python, Ruby and TypeScript.
|
|
16
|
+
|
|
17
|
+
⚠️ *A scene patch dictionary defined in the `run` section of your mididings script is required to work correctly, check the [`run` function documentation for more information](https://mididings.github.io/mididings/main.html#mididings.run) on how to structure your patch.*
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## 📘 API documentation
|
|
21
|
+
- [stagedings manual](https://mididings.github.io/stagedings)
|
|
22
|
+
- [mididings manual](https://mididings.github.io/mididings)
|
|
23
|
+
|
|
24
|
+
## Frontend
|
|
25
|
+
|
|
26
|
+
### A responsive multiclient, real-time interface for scene/subscene navigation
|
|
27
|
+
|
|
28
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/frontend.png" alt="stagedings UI screenshot" width="700"/>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
## Features
|
|
32
|
+
- Web UI with real-time scene/subscene updates
|
|
33
|
+
- FastAPI backend with full REST and WebSocket support
|
|
34
|
+
- Multiple clients supported
|
|
35
|
+
- Use the mididings OSC interface
|
|
36
|
+
- It exposes a **fully compliant OpenAPI spec** for easy generation of SDK clients in any language, enabling flexible remote control of mididings
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### The frontend allow
|
|
41
|
+
* Direct navigation through scenes and subscenes
|
|
42
|
+
* Exposes the Restart, Panic, Query and Quit commands
|
|
43
|
+
|
|
44
|
+
### The backend allow
|
|
45
|
+
* Endpoints for direct navigation through scenes and subscenes
|
|
46
|
+
* Endpoints to the Restart, Panic, Query and Quit commands
|
|
47
|
+
|
|
48
|
+
#### ℹ️ About commands
|
|
49
|
+
* ***Restart*** will restart mididings process
|
|
50
|
+
* ***Panic*** send not off to all ports and all channels
|
|
51
|
+
* ***Quit*** stop mididings, be carefull
|
|
52
|
+
* *Query is a work in progress*
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## ⚒️ Installation from PyPI
|
|
58
|
+
**NOTE:** This will also install mididings with OSC and AutoRestart support allowing a full working stack.
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
# Create a Python virtual environment
|
|
62
|
+
$ python3 -m venv .venv
|
|
63
|
+
$ source .venv/bin/activate
|
|
64
|
+
|
|
65
|
+
# Install stagedings including mididings as a dependency
|
|
66
|
+
$ pip install stagedings
|
|
67
|
+
```
|
|
68
|
+
## ▶️ Running the application
|
|
69
|
+
```sh
|
|
70
|
+
$ stagedings [--host HOST] [--port PORT]
|
|
71
|
+
```
|
|
72
|
+
## Options
|
|
73
|
+
* --host
|
|
74
|
+
* FastAPI server bind address
|
|
75
|
+
* Default: localhost
|
|
76
|
+
* Use 0.0.0.0 to allow network access or the server IP address
|
|
77
|
+
* --port
|
|
78
|
+
* FastAPI + WebSocket server port
|
|
79
|
+
* Default: 5000
|
|
80
|
+
|
|
81
|
+
## Use cases
|
|
82
|
+
### Local development (single machine)
|
|
83
|
+
```sh
|
|
84
|
+
$ stagedings
|
|
85
|
+
```
|
|
86
|
+
This runs everything locally on:
|
|
87
|
+
```sh
|
|
88
|
+
http://localhost:5000
|
|
89
|
+
```
|
|
90
|
+
### Network / multi-client setup
|
|
91
|
+
When clients access the server from other machines, you must expose the backend:
|
|
92
|
+
```sh
|
|
93
|
+
$ stagedings --host 0.0.0.0
|
|
94
|
+
```
|
|
95
|
+
or:
|
|
96
|
+
```sh
|
|
97
|
+
$ stagedings --host 192.168.1.100
|
|
98
|
+
```
|
|
99
|
+
### Accessing the UI
|
|
100
|
+
Once running, open in a browser:
|
|
101
|
+
```sh
|
|
102
|
+
http://dings.local.com:5000
|
|
103
|
+
```
|
|
104
|
+
or:
|
|
105
|
+
```sh
|
|
106
|
+
http://192.168.1.100:5000
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## High Level Overview
|
|
110
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/overview.png" alt="stagedings UI screenshot" width="800"/>
|
|
111
|
+
|
|
112
|
+
## 🔗 Communication Workflow
|
|
113
|
+
<img src="https://raw.githubusercontent.com/mididings/stagedings/main/docs/workflow.png" alt="stagedings UI screenshot" width="800"/>
|
|
114
|
+
|
|
115
|
+
### 💬 Feedback & Contributions
|
|
116
|
+
|
|
117
|
+
We welcome bug reports, feature ideas, and contributions! Please open an issue or discussion
|
|
118
|
+
|
|
119
|
+
### 📜 License
|
|
120
|
+
|
|
121
|
+
All files in this repository are released under the terms of the GNU
|
|
122
|
+
General Public License as published by the Free Software Foundation;
|
|
123
|
+
either version 2 or later of the License.
|
|
124
|
+
|
|
125
|
+
Made in Québec 🇨🇦
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stagedings"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An API to navigate scenes and subscenes that has been configured in a mididings script"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "GPL-2.0-or-later"
|
|
12
|
+
license-files = ["LICENSE.md"]
|
|
13
|
+
keywords = [
|
|
14
|
+
"mididings",
|
|
15
|
+
"midi",
|
|
16
|
+
"live-performance",
|
|
17
|
+
"music",
|
|
18
|
+
"control-surface",
|
|
19
|
+
"fastapi",
|
|
20
|
+
"websocket",
|
|
21
|
+
"osc"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
authors = [
|
|
25
|
+
{ name = "Stéphane Gagnon", email = "stephane-gagnon@hotmail.com" }
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
maintainers = [
|
|
29
|
+
{ name = "Stéphane Gagnon", email = "stephane-gagnon@hotmail.com" }
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
dependencies = [
|
|
33
|
+
"fastapi",
|
|
34
|
+
"uvicorn[standard]",
|
|
35
|
+
"jinja2",
|
|
36
|
+
"pydantic",
|
|
37
|
+
"scalar-fastapi",
|
|
38
|
+
"mididings[osc,autorestart]",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
classifiers = [
|
|
42
|
+
"Topic :: Multimedia :: Sound/Audio :: MIDI",
|
|
43
|
+
"Programming Language :: Python :: 3",
|
|
44
|
+
"Development Status :: 5 - Production/Stable",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
stagedings = [
|
|
49
|
+
"templates/**/*",
|
|
50
|
+
"static/**/*"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
stagedings = "stagedings.cli:main"
|
|
55
|
+
|
|
56
|
+
[project.urls]
|
|
57
|
+
Homepage = "https://github.com/mididings"
|
|
58
|
+
Documentation = "https://mididings.github.io/stagedings/"
|
|
59
|
+
Repository = "https://github.com/mididings/stagedings"
|
|
File without changes
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import asyncio
|
|
10
|
+
import uvicorn
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from importlib import resources
|
|
14
|
+
import stagedings
|
|
15
|
+
|
|
16
|
+
from stagedings.core.control import Controller
|
|
17
|
+
from stagedings.core.connection import ConnectionManager
|
|
18
|
+
|
|
19
|
+
from fastapi import Request, FastAPI, WebSocket, WebSocketDisconnect, Response
|
|
20
|
+
from fastapi.responses import HTMLResponse
|
|
21
|
+
from fastapi.staticfiles import StaticFiles
|
|
22
|
+
from fastapi.templating import Jinja2Templates
|
|
23
|
+
from fastapi.openapi.utils import get_openapi
|
|
24
|
+
from scalar_fastapi import get_scalar_api_reference
|
|
25
|
+
|
|
26
|
+
BASE_DIR = Path(stagedings.__file__).resolve().parent
|
|
27
|
+
|
|
28
|
+
description = """
|
|
29
|
+
### You will be able to:
|
|
30
|
+
|
|
31
|
+
* **Navigating Scenes and Subscenes**
|
|
32
|
+
* **Control mididings**
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
app = FastAPI()
|
|
36
|
+
|
|
37
|
+
def custom_openapi():
|
|
38
|
+
if app.openapi_schema:
|
|
39
|
+
return app.openapi_schema
|
|
40
|
+
app.openapi_schema = get_openapi(
|
|
41
|
+
title="stagedings",
|
|
42
|
+
version="1.0.0",
|
|
43
|
+
description=description,
|
|
44
|
+
routes=app.routes,
|
|
45
|
+
openapi_version="3.1.0",
|
|
46
|
+
summary="An UI and API for mididings community version"
|
|
47
|
+
)
|
|
48
|
+
app.openapi_schema["info"]["x-logo"] = {
|
|
49
|
+
"url": "https://avatars.githubusercontent.com/u/121540801?s=400&u=2d3daf12927631aecd807b2d6dfb90652cc22ae8&v=4"
|
|
50
|
+
}
|
|
51
|
+
return app.openapi_schema
|
|
52
|
+
|
|
53
|
+
app.openapi = custom_openapi
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
""" Configuration """
|
|
57
|
+
|
|
58
|
+
app.mount(
|
|
59
|
+
"/static",
|
|
60
|
+
StaticFiles(directory=str(BASE_DIR / "static")),
|
|
61
|
+
name="static"
|
|
62
|
+
)
|
|
63
|
+
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
64
|
+
|
|
65
|
+
config = resources.files(stagedings) / "static" / "config.json"
|
|
66
|
+
with open(config) as FILE:
|
|
67
|
+
configuration = json.load(FILE)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Mididings and OSC context
|
|
71
|
+
controller = Controller(configuration["osc_server"])
|
|
72
|
+
|
|
73
|
+
# WebSocket connection manager
|
|
74
|
+
connection_manager = ConnectionManager()
|
|
75
|
+
|
|
76
|
+
async def mididings_context_update():
|
|
77
|
+
await controller.set_dirty(False)
|
|
78
|
+
await connection_manager.broadcast(
|
|
79
|
+
{"action": "mididings_context_update", "payload": controller.scene_controller.payload}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# UI enpoints
|
|
83
|
+
@app.get("/scalar", include_in_schema=False)
|
|
84
|
+
async def scalar_html():
|
|
85
|
+
return get_scalar_api_reference(
|
|
86
|
+
# Your OpenAPI document
|
|
87
|
+
openapi_url=app.openapi_url,
|
|
88
|
+
# Avoid CORS issues (optional)
|
|
89
|
+
# scalar_proxy_url="https://proxy.scalar.com",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
|
94
|
+
async def entry_point(request: Request):
|
|
95
|
+
return templates.TemplateResponse(
|
|
96
|
+
name="ui.html" if controller.scene_controller.scenes else "no_context.html",
|
|
97
|
+
context={
|
|
98
|
+
"request": request
|
|
99
|
+
},
|
|
100
|
+
request=request,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Navigation endpoints
|
|
104
|
+
# -----
|
|
105
|
+
@app.post("/api/scenes/{sceneId}/subscenes/{subsceneId}/activate",
|
|
106
|
+
description="Switch to the given scene and subscene number.",
|
|
107
|
+
summary="Switch to the given scene and subscene number.",
|
|
108
|
+
tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
109
|
+
)
|
|
110
|
+
async def switch_scene(sceneId: int, subsceneId: int):
|
|
111
|
+
await controller.switch_scene(sceneId)
|
|
112
|
+
await controller.switch_subscene(subsceneId)
|
|
113
|
+
return Response(status_code=204)
|
|
114
|
+
|
|
115
|
+
# -----
|
|
116
|
+
@app.post("/api/scenes/{sceneId}/activate",
|
|
117
|
+
description="Switch to the given scene number.",
|
|
118
|
+
summary="Switch to the given scene number.",
|
|
119
|
+
tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
120
|
+
)
|
|
121
|
+
async def switch_scene(sceneId: int):
|
|
122
|
+
await controller.switch_scene(sceneId)
|
|
123
|
+
return Response(status_code=204)
|
|
124
|
+
|
|
125
|
+
# -----
|
|
126
|
+
@app.post("/api/subscenes/{subsceneId}/activate",
|
|
127
|
+
description="Switch to the given subscene number.",
|
|
128
|
+
summary="Switch to the given subscene number.",
|
|
129
|
+
tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
130
|
+
)
|
|
131
|
+
async def switch_subscene(subsceneId: int):
|
|
132
|
+
await controller.switch_subscene(subsceneId)
|
|
133
|
+
return Response(status_code=204)
|
|
134
|
+
|
|
135
|
+
# -----
|
|
136
|
+
@app.post("/api/scenes/prev", description="Switch to the previous scene.",
|
|
137
|
+
summary="Switch to the previous scene.", tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
138
|
+
)
|
|
139
|
+
async def prev_scene():
|
|
140
|
+
await controller.prev_scene()
|
|
141
|
+
return Response(status_code=204)
|
|
142
|
+
|
|
143
|
+
# -----
|
|
144
|
+
@app.post("/api/scenes/next", description="Switch to the next scene.",
|
|
145
|
+
summary="Switch to the next scene.", tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
146
|
+
)
|
|
147
|
+
async def next_scene():
|
|
148
|
+
await controller.next_scene()
|
|
149
|
+
return Response(status_code=204)
|
|
150
|
+
|
|
151
|
+
# -----
|
|
152
|
+
@app.post("/api/subscenes/prev", description="Switch to the previous subscene.",
|
|
153
|
+
summary="Switch to the previous subscene.",
|
|
154
|
+
tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
155
|
+
)
|
|
156
|
+
async def prev_subscene():
|
|
157
|
+
await controller.prev_subscene()
|
|
158
|
+
return Response(status_code=204)
|
|
159
|
+
|
|
160
|
+
# -----
|
|
161
|
+
@app.post("/api/subscenes/next", description="Switch to the next subscene.",
|
|
162
|
+
summary="Switch to the next subscene.", tags=["Navigation"], responses={204: {"description": "No content"}}
|
|
163
|
+
)
|
|
164
|
+
async def next_subscene():
|
|
165
|
+
await controller.next_subscene()
|
|
166
|
+
return Response(status_code=204)
|
|
167
|
+
|
|
168
|
+
# System endpoints
|
|
169
|
+
# -----
|
|
170
|
+
@app.post("/api/system/panic", description="Send all-notes-off on all channels and on all output ports.",
|
|
171
|
+
summary="Send all-notes-off on all channels and on all output ports.",
|
|
172
|
+
tags=["System"], responses={204: {"description": "No content"}})
|
|
173
|
+
async def panic():
|
|
174
|
+
await controller.panic()
|
|
175
|
+
return Response(status_code=204)
|
|
176
|
+
|
|
177
|
+
# -----
|
|
178
|
+
@app.post("/api/system/quit", description="Terminate mididings.", summary="Terminate mididings.",
|
|
179
|
+
tags=["System"], responses={204: {"description": "No content"}}
|
|
180
|
+
)
|
|
181
|
+
async def quit():
|
|
182
|
+
await controller.quit()
|
|
183
|
+
return Response(status_code=204)
|
|
184
|
+
|
|
185
|
+
# -----
|
|
186
|
+
@app.post("/api/system/restart", description="Restart mididings.", summary="Restart mididings.",
|
|
187
|
+
tags=["System"], responses={204: {"description": "No content"}}
|
|
188
|
+
)
|
|
189
|
+
async def restart():
|
|
190
|
+
await controller.restart()
|
|
191
|
+
return Response(status_code=204)
|
|
192
|
+
|
|
193
|
+
# -----
|
|
194
|
+
@app.post("/api/system/query", description="Send config, current scene/subscene to all notify ports.",
|
|
195
|
+
summary="Send config, current scene/subscene to all notify ports.",
|
|
196
|
+
tags=["System"], responses={204: {"description": "No content"}}
|
|
197
|
+
)
|
|
198
|
+
async def query():
|
|
199
|
+
await controller.query()
|
|
200
|
+
return Response(status_code=204)
|
|
201
|
+
|
|
202
|
+
""" Websocket handler """
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.websocket("/ws")
|
|
206
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
207
|
+
await connection_manager.connect(websocket)
|
|
208
|
+
try:
|
|
209
|
+
while websocket in connection_manager.active_connections:
|
|
210
|
+
# Send status periodic task
|
|
211
|
+
await connection_manager.broadcast(
|
|
212
|
+
{"action": "on_start" if await controller.is_running() else "on_exit"}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# Handle incoming messages from the client
|
|
217
|
+
data = await asyncio.wait_for(websocket.receive_json(), timeout=0.1)
|
|
218
|
+
action = data["action"]
|
|
219
|
+
if action in delegates:
|
|
220
|
+
(
|
|
221
|
+
await delegates[action]()
|
|
222
|
+
if not "id" in data
|
|
223
|
+
else await delegates[action](int(data["id"]))
|
|
224
|
+
)
|
|
225
|
+
except asyncio.TimeoutError:
|
|
226
|
+
# No message received during the timeout, continue the loop
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
if await controller.is_dirty():
|
|
230
|
+
await delegates["mididings_context_update"]()
|
|
231
|
+
|
|
232
|
+
except WebSocketDisconnect:
|
|
233
|
+
connection_manager.disconnect(websocket)
|
|
234
|
+
except asyncio.exceptions.CancelledError:
|
|
235
|
+
print("asyncio CancelledError exception")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"Unexpected WebSocket error: {e}")
|
|
238
|
+
connection_manager.disconnect(websocket)
|
|
239
|
+
finally:
|
|
240
|
+
print("exit")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def on_quit(websocket: WebSocket = None):
|
|
244
|
+
await connection_manager.broadcast(
|
|
245
|
+
{
|
|
246
|
+
"action": "on_terminate",
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def on_connect(websocket: WebSocket = None):
|
|
252
|
+
await controller.set_dirty(True)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
delegates = {
|
|
256
|
+
|
|
257
|
+
"on_connect": on_connect,
|
|
258
|
+
|
|
259
|
+
"quit": controller.quit,
|
|
260
|
+
"panic": controller.panic,
|
|
261
|
+
"query": controller.query,
|
|
262
|
+
"restart": controller.restart,
|
|
263
|
+
|
|
264
|
+
"next_scene": controller.next_scene,
|
|
265
|
+
"prev_scene": controller.prev_scene,
|
|
266
|
+
"next_subscene": controller.next_subscene,
|
|
267
|
+
"prev_subscene": controller.prev_subscene,
|
|
268
|
+
|
|
269
|
+
"switch_scene": controller.switch_scene,
|
|
270
|
+
"switch_subscene": controller.switch_subscene,
|
|
271
|
+
|
|
272
|
+
"mididings_context_update": mididings_context_update,
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def main():
|
|
277
|
+
|
|
278
|
+
parser = argparse.ArgumentParser(description="Run stagedings FastAPI server")
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--host",
|
|
281
|
+
default="localhost",
|
|
282
|
+
help="FastAPI listen host",
|
|
283
|
+
)
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
"--port",
|
|
286
|
+
type=int,
|
|
287
|
+
default=5000,
|
|
288
|
+
help="FastAPI listen port",
|
|
289
|
+
)
|
|
290
|
+
args = parser.parse_args()
|
|
291
|
+
|
|
292
|
+
uvicorn.run(
|
|
293
|
+
"stagedings.cli:app",
|
|
294
|
+
host=args.host,
|
|
295
|
+
port=args.port,
|
|
296
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
5
|
+
|
|
6
|
+
from fastapi import WebSocket
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
class ConnectionManager:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.active_connections: List[WebSocket] = []
|
|
12
|
+
|
|
13
|
+
async def connect(self, websocket: WebSocket):
|
|
14
|
+
await websocket.accept()
|
|
15
|
+
if websocket not in self.active_connections:
|
|
16
|
+
print(f"Connecting: {websocket.client}")
|
|
17
|
+
self.active_connections.append(websocket)
|
|
18
|
+
|
|
19
|
+
def disconnect(self, websocket: WebSocket):
|
|
20
|
+
if websocket in self.active_connections:
|
|
21
|
+
print(f"Disconnecting: {websocket.client}")
|
|
22
|
+
try:
|
|
23
|
+
self.active_connections.remove(websocket)
|
|
24
|
+
except:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
async def broadcast(self, message: dict):
|
|
28
|
+
for websocket in self.active_connections[:]:
|
|
29
|
+
try:
|
|
30
|
+
await websocket.send_json(message)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"WebSocket error: {e} for {websocket.client}")
|
|
33
|
+
self.disconnect(websocket)
|