Skip to content

Commit 690c64f

Browse files
authored
Merge pull request #7 from mugulmd/dev/cleanupV0
Clean-up for v0
2 parents ddb68e3 + aa116f5 commit 690c64f

File tree

8 files changed

+181
-41
lines changed

8 files changed

+181
-41
lines changed

README.md

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,57 +9,56 @@ Claude is built around a standard *client-server* architecture, in which the ser
99
More specifically, the Claude server creates a window in which it renders a simple rectangle using OpenGL.
1010
Hence, the content of the window can be customized using a *fragment shader* provided as input.
1111

12+
You can modify the fragment shader while Claude is running:
13+
whenever you save this file, the changes will apply in the window.
14+
1215
The Claude server - as its name indicates - also creates a server process that listens for any incoming messages.
1316
These messages are used to modify the fragment shader's *uniforms*.
1417
Claude clients connect to the server process and send messages to change uniform values.
1518

16-
> Note: for now only uniforms of type *float* are supported. This should change soon.
17-
1819
How does that make it an audio-visual synchronization tool?
1920
Several audio live-coding environments work in the exact same way: by connecting to a server in charge of "making sounds" and regularly sending it messages using a *clock* system.
2021
Therefore, by creating Claude clients in these audio live-coding environments, we can use the same clock to update our fragment shader's uniform, giving the impression that the visuals are synchronized with the audio.
2122

22-
Currently Claude is available in only one live-coding environment: **Sardine**.
23+
Currently Claude is available in only one live-coding environment: [Sardine](https://github.com/Bubobubobubobubo/sardine).
2324
It can be used as a regular *sender*, thus allowing to take advantage of the *Sardine Pattern Language* for writing uniform values to send.
2425

2526
## Install
2627

2728
See [INSTALL](INSTALL.md).
2829

29-
## First test with telnet
30+
## Usage
3031

31-
To test the installation of Claude, start by simply launching a Claude server:
32+
### First test with telnet
3233

34+
To test the installation of Claude, start by simply launching a Claude server:
3335
```
3436
cd Claude
3537
python -m claude_server
3638
```
39+
A window should open, displaying an animated wave.
3740

3841
Then, in another terminal, connect to the server using the *telnet* utility:
39-
4042
```
4143
telnet 127.0.0.1 65432
4244
```
4345

4446
You can now send messages to the server, try the following ones and see what happens in Claude's window:
45-
4647
```
47-
freq 5
48-
amp .1
49-
amp .4
50-
freq 1
51-
...
48+
f freq 5
49+
f amp .1
50+
f water 1 0 0
51+
i nb_waves 3
5252
```
5353

54-
By default the Claude server uses the [wave](resources/wave.frag) shader, which has two uniforms: `freq` (frequency) and `amp` (amplitude).
55-
These two parameters allow you to tweak the sine wave you are visualizing on screen.
54+
By default the Claude server uses the [wave](resources/wave.frag) shader, which has a few uniforms for controlling the output.
55+
These parameters allow you to tweak the wave(s) you are visualizing on screen.
5656

57-
## Using Claude in Sardine
57+
### Using Claude in Sardine
5858

5959
Once the Claude extension is installed in Sardine, a Claude client will be initialized when you start a new session.
6060

6161
You can then use the `Claude(...)` alias to send messages to the Claude server:
62-
6362
```python
6463
@swim
6564
def wave(p=2, i=0):
@@ -70,3 +69,51 @@ def wave(p=2, i=0):
7069

7170
This alias takes two positional arguments, the uniform name and value, followed by the usual Sardine send parameters (iterator, divisor, rate).
7271
Note that you can use patterns for the value argument, thus giving great flexibility during live-coding sessions.
72+
73+
In the previous example, the uniforms sent were simply floats, but we can also send vectors (up to 4 dimensions):
74+
```python
75+
@swim
76+
def wave(p=2, i=0):
77+
Claude('water', ['.2 1 .5', 0, '1 .2'], i=i)
78+
again(wave, p=2, i=i+1)
79+
```
80+
81+
Moreover, another optional argument lets us specify the *datatype* that we want to use.
82+
This argument is called `datatype` but it can be replaced by its alias `dt`:
83+
```python
84+
@swim
85+
def wave(p=2, i=0):
86+
Claude('nb_waves', '1 2 3 4 3 2', dt='i', i=i)
87+
again(wave, p=2, i=i+1)
88+
```
89+
90+
> Note: for now only the float and integer datatypes are supported ('f' and 'i').
91+
> The default datatype is float, hence it is not necessary to specify it.
92+
93+
### Shader live-coding
94+
95+
The `claude_server` application has a few configuration options that can be detailed with:
96+
```
97+
python -m claude_server -h
98+
```
99+
100+
The two most important options are `--res` and `--frag`, which allow you to pass in and use your own fragment shader.
101+
102+
Claude is designed for live-coding: you can edit your fragment shader while Claude is running, and every time you save it Claude will update its content.
103+
104+
Feel free to use the [template shader](resources/template.frag) and [utilities](resources/utils/glsl) provided.
105+
106+
## Contributions
107+
108+
Claude is at an early development stage, and we're actively seeking contributors to help enhance the project.
109+
110+
Contributions can take many forms:
111+
- general feedback
112+
- bug reports
113+
- features requests
114+
- code improvements
115+
- documentation
116+
- tool design ideas
117+
- demos and tutorials.
118+
119+
There is no official communication channel yet, so please use GitHub issues.

claude_client/ClaudeHandler.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77

88
class ClaudeHandler(Sender):
9+
"""Sardine client for sending messages to the Claude server.
10+
11+
Attributes:
12+
params['ip']: The IP address to connect to the Claude server.
13+
params['port']: The port to connect to the Claude server.
14+
"""
915

1016
def __init__(self, params: dict):
1117
super().__init__()
@@ -19,6 +25,7 @@ def __init__(self, params: dict):
1925
self._socket = None
2026

2127
def _send(self, message: str):
28+
# Send message encoded as raw bytes to Claude server
2229
if self._socket:
2330
self._socket.send(message.encode())
2431

@@ -36,8 +43,11 @@ def send(
3643
rate: NumericElement = 1,
3744
**pattern: ParsableElement,
3845
):
46+
# Names of the 1st to 4th components of the received vector
3947
dims = ['x', 'y', 'z', 'w']
4048

49+
# Add each component of the received vector
50+
# to the pattern dict for parsing
4151
if isinstance(value, list):
4252
if len(value) > 4:
4353
return
@@ -46,13 +56,16 @@ def send(
4656
else:
4757
pattern[dims[0]] = value
4858

59+
# Parse each dimension separately
4960
reduced = self.pattern_reduce(pattern, iterator, divisor, rate)
5061

5162
deadline = self.env.clock.shifted_time
5263
for item in reduced:
64+
# Build message to send to the Claude server
5365
message = f'{datatype} {name}'
5466
for dim in dims:
5567
if not dim in item:
5668
break;
5769
message += f' {item[dim]}'
70+
# Schedule sending the message
5871
self.call_timed(deadline, self._send, message)

claude_server/__main__.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
from claude_server.app import ClaudeApp
2+
from claude_server.cli import create_parser
23

34
from moderngl_window import run_window_config
45
from moderngl_window.resources import register_dir
56

6-
from argparse import ArgumentParser
77
from pathlib import Path
88

99

1010
# Command line interface
11-
parser = ArgumentParser(
12-
prog='claude'
13-
)
14-
15-
parser.add_argument('--res', type=str, default=None)
16-
parser.add_argument('--frag', type=str, default='wave.frag')
17-
parser.add_argument('--ip', type=str, default='127.0.0.1')
18-
parser.add_argument('--port', type=int, default=65432)
19-
parser.add_argument('--title', type=str, default='Claude')
20-
parser.add_argument('--size', type=int, nargs=2, metavar=('W', 'H'), default=[800, 600])
21-
11+
parser = create_parser()
2212
args = parser.parse_args()
2313

24-
# Register resources directories
25-
ClaudeApp.resource_dir = (Path(__file__).parents[1] / 'resources').resolve()
14+
# Register resources directory
15+
internal_resources_dir = (Path(__file__).parents[1] / 'resources').resolve()
16+
register_dir(internal_resources_dir)
17+
ClaudeApp.resource_dir = internal_resources_dir
2618
if args.res:
19+
register_dir(args.res)
2720
ClaudeApp.resource_dir = args.res
2821

2922
# Configuration
@@ -33,5 +26,5 @@
3326
ClaudeApp.title = args.title
3427
ClaudeApp.window_size = (args.size[0], args.size[1])
3528

36-
# Start application
29+
# Start application using a pyglet window
3730
run_window_config(ClaudeApp, args=['-wnd', 'pyglet'])

claude_server/app.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@
1010

1111

1212
class ClaudeApp(mglw.WindowConfig):
13+
"""Class centralizing the Claude server application.
1314
15+
This class is in charge of:
16+
* window management
17+
* rendering with OpenGL
18+
* updating uniforms and the shading program when needed.
19+
20+
It also manages the server process and filewatcher which allow interactivity.
21+
"""
22+
23+
# OpenGL version
1424
gl_version = (3, 3)
1525

1626
# Window configuration
@@ -19,7 +29,7 @@ class ClaudeApp(mglw.WindowConfig):
1929

2030
# Shader configuration
2131
vertex_shader = 'default.vert'
22-
fragment_shader = 'wave.frag'
32+
fragment_shader = 'template.frag'
2333

2434
# Server configuration
2535
ip = '127.0.0.1'
@@ -44,7 +54,7 @@ def __init__(self, **kwargs):
4454
self.vbo = self.ctx.buffer(vertices.astype('f4').tobytes())
4555
self.vao = self.ctx.vertex_array(self.program, self.vbo, 'in_vert')
4656

47-
# Queue for sending messages from server process to application process
57+
# Queue for sending messages from server process to rendering process
4858
self.queue = Queue()
4959

5060
# Create and launch server process
@@ -73,20 +83,27 @@ def __init__(self, **kwargs):
7383
self.observer.start()
7484

7585
# Uniform cache
86+
# Used to reset uniforms to their latest value
87+
# after fragment shader is reloaded
7688
self.uniform_cache = {}
7789

7890
def on_frag_changed(self, event):
91+
# Notify rendering process that fragment shader must be reloaded
7992
self.reload_frag = True
8093

8194
def write_uniform(self, np_dtype, name, value, caching = True):
8295
try:
96+
# Retrieve uniform object using its name
8397
uniform = self.program.get(name, None)
8498
if uniform:
99+
# Send value as raw bytes
85100
uniform.write(np.array(value).astype(np_dtype).tobytes())
86101
except Exception as e:
87102
print(e)
88103
return
89104

105+
# Uniform value was sent successfully
106+
# Store this value in the uniform cache
90107
if caching:
91108
self.uniform_cache[name] = {
92109
'np_dtype': np_dtype,
@@ -105,28 +122,35 @@ def render(self, time, frametime):
105122
vertex_shader=ClaudeApp.vertex_shader,
106123
fragment_shader=ClaudeApp.fragment_shader
107124
)
125+
# Loading was successful
126+
# Reset OpenGL objects
127+
self.program = program
128+
self.vao = self.ctx.vertex_array(self.program, self.vbo, 'in_vert')
129+
# Send last known uniform values
130+
for name, content in self.uniform_cache.items():
131+
self.write_uniform(content['np_dtype'], name, content['value'], False)
108132
except Exception as e:
109133
print(e)
110-
self.program = program
111-
self.vao = self.ctx.vertex_array(self.program, self.vbo, 'in_vert')
112-
for name, content in self.uniform_cache.items():
113-
self.write_uniform(content['np_dtype'], name, content['value'], False)
114134

115135
# Read messages fed from server and update uniforms accordingly
116136
while not self.queue.empty():
117137
try:
118138
item = self.queue.get_nowait()
119139
self.write_uniform(item['np_dtype'], item['name'], item['value'])
120-
except Exception:
140+
except Exception as e:
141+
print(e)
121142
pass
122143

123144
# Update time
145+
# No need to cache the time uniform value
146+
# as it must be sent between each frame
124147
self.write_uniform('f4', 'time', time, False)
125148

126149
# Render frame
127150
self.vao.render()
128151

129152
def resize(self, width, height):
153+
# Update resolution uniform with new dimensions
130154
self.write_uniform('f4', 'resolution', (width, height))
131155

132156
def close(self):

claude_server/cli.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from argparse import ArgumentParser
2+
3+
4+
def create_parser():
5+
"""Command line interface parser for the Claude server application."""
6+
parser = ArgumentParser(
7+
prog='claude_server',
8+
description='Start the Claude server application with a given fragment shader.'
9+
)
10+
11+
parser.add_argument(
12+
'--res', type=str, default=None,
13+
help="Filepath to the resource folder containing the fragment shader.\n"
14+
"If left to default value, Claude's internal resources folder will be used."
15+
)
16+
parser.add_argument(
17+
'--frag', type=str, default='wave.frag',
18+
help="Filename of the fragment shader to use.\n"
19+
"Default: %(default)s"
20+
)
21+
22+
parser.add_argument(
23+
'--ip', type=str, default='127.0.0.1',
24+
help="Server IP address.\n"
25+
"Default: %(default)s"
26+
)
27+
parser.add_argument(
28+
'--port', type=int, default=65432,
29+
help="Server port.\n"
30+
"Default: %(default)s"
31+
)
32+
33+
parser.add_argument(
34+
'--title', type=str, default='Claude',
35+
help="Window title.\n"
36+
"Default: %(default)s"
37+
)
38+
parser.add_argument(
39+
'--size', type=int, nargs=2, metavar=('W', 'H'), default=[800, 600],
40+
help="Window initial size.\n"
41+
"Default: %(default)s"
42+
)
43+
44+
return parser

0 commit comments

Comments
 (0)