pycodedj 0.1.3__tar.gz → 0.1.4__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.
- {pycodedj-0.1.3 → pycodedj-0.1.4}/.gitignore +3 -1
- {pycodedj-0.1.3 → pycodedj-0.1.4}/CHANGELOG.md +8 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/PKG-INFO +2 -2
- {pycodedj-0.1.3 → pycodedj-0.1.4}/README.ja.md +1 -1
- {pycodedj-0.1.3 → pycodedj-0.1.4}/README.md +1 -1
- {pycodedj-0.1.3 → pycodedj-0.1.4}/pyproject.toml +1 -5
- {pycodedj-0.1.3 → pycodedj-0.1.4}/sc/synths.scd +29 -26
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/__init__.py +1 -1
- pycodedj-0.1.3/docs/visualizer-concept.html +0 -438
- {pycodedj-0.1.3 → pycodedj-0.1.4}/.github/workflows/workflow.yml +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/LICENSE +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/docs/CNAME +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/docs/index.html +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/docs/manual.ja.md +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/docs/manual.md +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/examples/club_set.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/examples/demo.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/examples/hello_sc.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/__main__.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/analyzer.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/block_parser.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/engine.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/mapper.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/osc_bridge.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/src/pycodedj/watcher.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/__init__.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_analyzer.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_block_parser.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_engine.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_mapper.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_osc_bridge.py +0 -0
- {pycodedj-0.1.3 → pycodedj-0.1.4}/tests/test_watcher.py +0 -0
|
@@ -50,9 +50,10 @@ logs/
|
|
|
50
50
|
.claude/settings.local.json
|
|
51
51
|
.claude/todos/
|
|
52
52
|
.claude/mcp*.json
|
|
53
|
-
.codex
|
|
53
|
+
.codex/
|
|
54
54
|
.claude/
|
|
55
55
|
AGENT.md
|
|
56
|
+
.agents/
|
|
56
57
|
|
|
57
58
|
# Claude Code
|
|
58
59
|
CLAUDE.local.md
|
|
@@ -77,3 +78,4 @@ runs/
|
|
|
77
78
|
tmp/
|
|
78
79
|
temp/
|
|
79
80
|
.cache/
|
|
81
|
+
docs/visualizer-*.html
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.4] - 2026-05-07
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- Fix editable installs so `pycodedj.__main__` resolves from `src/pycodedj`
|
|
8
|
+
- Fix SuperCollider OSC receiving and address parsing for `/pycodedj/loop/<name>/<param>` messages
|
|
9
|
+
- Keep the SuperCollider OSC receiver active across repeated `synths.scd` reloads
|
|
10
|
+
|
|
3
11
|
## [0.1.3] - 2026-05-07
|
|
4
12
|
|
|
5
13
|
### Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycodedj
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
License: MIT License with Commons Clause
|
|
5
5
|
|
|
6
6
|
"Commons Clause" License Condition v1.0
|
|
@@ -62,7 +62,7 @@ Description-Content-Type: text/markdown
|
|
|
62
62
|
|
|
63
63
|
# PyCodeDJ
|
|
64
64
|
|
|
65
|
-
[日本語版 README はこちら](https://github.com/kanekoyuichi/pycodedj/blob/main/README.ja.md)
|
|
65
|
+
[日本語版 README はこちら](https://github.com/kanekoyuichi/pycodedj/blob/main/README.ja.md) · [Full Manual (EN)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.md) · [マニュアル (JA)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.ja.md)
|
|
66
66
|
|
|
67
67
|
A live-coding environment that translates Python code structure into music in real time. Every save changes the performance.
|
|
68
68
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PyCodeDJ
|
|
2
2
|
|
|
3
|
-
[English README](https://github.com/kanekoyuichi/pycodedj/blob/main/README.md)
|
|
3
|
+
[English README](https://github.com/kanekoyuichi/pycodedj/blob/main/README.md) · [マニュアル (JA)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.ja.md) · [Full Manual (EN)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.md)
|
|
4
4
|
|
|
5
5
|
Pythonコードの構造をリアルタイムに音楽へ変換するライブコーディング環境。ファイルを保存するたびに演奏が変わる。
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PyCodeDJ
|
|
2
2
|
|
|
3
|
-
[日本語版 README はこちら](https://github.com/kanekoyuichi/pycodedj/blob/main/README.ja.md)
|
|
3
|
+
[日本語版 README はこちら](https://github.com/kanekoyuichi/pycodedj/blob/main/README.ja.md) · [Full Manual (EN)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.md) · [マニュアル (JA)](https://github.com/kanekoyuichi/pycodedj/blob/main/docs/manual.ja.md)
|
|
4
4
|
|
|
5
5
|
A live-coding environment that translates Python code structure into music in real time. Every save changes the performance.
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pycodedj"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
requires-python = ">=3.10"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {file = "LICENSE"}
|
|
@@ -22,10 +22,6 @@ build-backend = "hatchling.build"
|
|
|
22
22
|
[tool.hatch.build.targets.wheel]
|
|
23
23
|
packages = ["src/pycodedj"]
|
|
24
24
|
|
|
25
|
-
[tool.hatch.build.targets.wheel.force-include]
|
|
26
|
-
"sc" = "pycodedj/sc"
|
|
27
|
-
"examples" = "pycodedj/examples"
|
|
28
|
-
|
|
29
25
|
[tool.ruff]
|
|
30
26
|
line-length = 100
|
|
31
27
|
target-version = "py310"
|
|
@@ -118,34 +118,37 @@ s.waitForBoot({
|
|
|
118
118
|
// OSCFunc with nil path receives every incoming OSC message and we filter by prefix.
|
|
119
119
|
// var は SC では実行文より前に宣言しなければならないため、
|
|
120
120
|
// すべての var をクロージャ先頭にまとめる。
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// parts: ["", "pycodedj", "loop", <name>, <param>]
|
|
125
|
-
if (parts.size != 5) { ^nil };
|
|
126
|
-
if (parts[1] != "pycodedj") { ^nil };
|
|
127
|
-
if (parts[2] != "loop") { ^nil };
|
|
121
|
+
if (~pycodedjOSC.notNil) {
|
|
122
|
+
thisProcess.removeOSCRecvFunc(~pycodedjOSC);
|
|
123
|
+
};
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
param
|
|
131
|
-
|
|
125
|
+
~pycodedjOSC = { |msg, time, addr, recvPort|
|
|
126
|
+
var parts, name, param, val;
|
|
127
|
+
parts = msg[0].asString.split($/).reject({ |part| part.size == 0 });
|
|
128
|
+
// parts: ["pycodedj", "loop", <name>, <param>]
|
|
129
|
+
if ((parts.size == 4) and: { parts[0] == "pycodedj" } and: { parts[1] == "loop" }) {
|
|
130
|
+
name = parts[2];
|
|
131
|
+
param = parts[3];
|
|
132
|
+
val = msg[1];
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
switch (param,
|
|
135
|
+
"voice_count", {
|
|
136
|
+
if (val.asInteger <= 0) {
|
|
137
|
+
if (~loops[name].notNil) {
|
|
138
|
+
~loops[name].do({ |synth| synth.set(\gate, 0) });
|
|
139
|
+
~loops[name] = nil;
|
|
140
|
+
};
|
|
141
|
+
} {
|
|
142
|
+
~startLoop.value(name, val.asInteger);
|
|
139
143
|
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}); // nil path = receive all OSC messages
|
|
144
|
+
},
|
|
145
|
+
"cutoff", { ~loops[name].do({ |synth| synth.set(\cutoff, val.asFloat) }) },
|
|
146
|
+
"lfo_rate", { ~loops[name].do({ |synth| synth.set(\lfoRate, val.asFloat) }) },
|
|
147
|
+
"reverb", { ~loops[name].do({ |synth| synth.set(\reverbMix, val.asFloat) }) }
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
thisProcess.addOSCRecvFunc(~pycodedjOSC);
|
|
149
152
|
|
|
150
|
-
"PyCodeDJ synths loaded. Ready.".postln;
|
|
153
|
+
("PyCodeDJ synths loaded. Ready. OSC port: " ++ NetAddr.langPort).postln;
|
|
151
154
|
});
|
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>PyCodeDJ — Visualizer Concept</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
body {
|
|
10
|
-
background: #000;
|
|
11
|
-
color: #cdd6f4;
|
|
12
|
-
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
13
|
-
overflow: hidden;
|
|
14
|
-
height: 100vh;
|
|
15
|
-
display: flex;
|
|
16
|
-
flex-direction: column;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
#canvas { display: block; flex: 1; }
|
|
20
|
-
|
|
21
|
-
/* ── HUD overlay ── */
|
|
22
|
-
#hud {
|
|
23
|
-
position: fixed;
|
|
24
|
-
top: 0; left: 0; right: 0; bottom: 0;
|
|
25
|
-
pointer-events: none;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* top-left: loop name + code snapshot */
|
|
29
|
-
#loop-info {
|
|
30
|
-
position: absolute;
|
|
31
|
-
top: 1.5rem; left: 1.5rem;
|
|
32
|
-
pointer-events: none;
|
|
33
|
-
}
|
|
34
|
-
.loop-name {
|
|
35
|
-
font-size: 0.7rem;
|
|
36
|
-
letter-spacing: 0.15em;
|
|
37
|
-
text-transform: uppercase;
|
|
38
|
-
color: #585b70;
|
|
39
|
-
margin-bottom: 0.25rem;
|
|
40
|
-
}
|
|
41
|
-
.loop-code {
|
|
42
|
-
font-size: 0.75rem;
|
|
43
|
-
line-height: 1.6;
|
|
44
|
-
color: rgba(166,227,161,0.7);
|
|
45
|
-
white-space: pre;
|
|
46
|
-
}
|
|
47
|
-
.loop-code .kw { color: #cba6f7; }
|
|
48
|
-
.loop-code .fn { color: #89b4fa; }
|
|
49
|
-
.loop-code .cm { color: #45475a; font-style: italic; }
|
|
50
|
-
.loop-code .num { color: #f9e2af; }
|
|
51
|
-
|
|
52
|
-
/* top-right: param meters */
|
|
53
|
-
#params {
|
|
54
|
-
position: absolute;
|
|
55
|
-
top: 1.5rem; right: 1.5rem;
|
|
56
|
-
display: flex;
|
|
57
|
-
flex-direction: column;
|
|
58
|
-
gap: 0.75rem;
|
|
59
|
-
width: 180px;
|
|
60
|
-
}
|
|
61
|
-
.param {
|
|
62
|
-
display: flex;
|
|
63
|
-
flex-direction: column;
|
|
64
|
-
gap: 0.2rem;
|
|
65
|
-
}
|
|
66
|
-
.param-header {
|
|
67
|
-
display: flex;
|
|
68
|
-
justify-content: space-between;
|
|
69
|
-
font-size: 0.65rem;
|
|
70
|
-
letter-spacing: 0.1em;
|
|
71
|
-
}
|
|
72
|
-
.param-label { color: #585b70; text-transform: uppercase; }
|
|
73
|
-
.param-value { color: #cdd6f4; }
|
|
74
|
-
.param-track {
|
|
75
|
-
height: 3px;
|
|
76
|
-
background: rgba(255,255,255,0.06);
|
|
77
|
-
border-radius: 2px;
|
|
78
|
-
overflow: hidden;
|
|
79
|
-
}
|
|
80
|
-
.param-fill {
|
|
81
|
-
height: 100%;
|
|
82
|
-
border-radius: 2px;
|
|
83
|
-
transition: width 1.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* bottom: scene label */
|
|
87
|
-
#scene-label {
|
|
88
|
-
position: absolute;
|
|
89
|
-
bottom: 1.75rem;
|
|
90
|
-
left: 50%;
|
|
91
|
-
transform: translateX(-50%);
|
|
92
|
-
font-size: 0.65rem;
|
|
93
|
-
letter-spacing: 0.2em;
|
|
94
|
-
text-transform: uppercase;
|
|
95
|
-
color: rgba(88,91,112,0.8);
|
|
96
|
-
text-align: center;
|
|
97
|
-
}
|
|
98
|
-
#scene-label span {
|
|
99
|
-
display: block;
|
|
100
|
-
font-size: 0.5rem;
|
|
101
|
-
margin-top: 0.3rem;
|
|
102
|
-
color: rgba(88,91,112,0.4);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/* bottom-right: PyCodeDJ badge */
|
|
106
|
-
#badge {
|
|
107
|
-
position: absolute;
|
|
108
|
-
bottom: 1.5rem; right: 1.5rem;
|
|
109
|
-
font-size: 0.6rem;
|
|
110
|
-
letter-spacing: 0.15em;
|
|
111
|
-
color: rgba(166,227,161,0.3);
|
|
112
|
-
}
|
|
113
|
-
</style>
|
|
114
|
-
</head>
|
|
115
|
-
<body>
|
|
116
|
-
<canvas id="canvas"></canvas>
|
|
117
|
-
|
|
118
|
-
<div id="hud">
|
|
119
|
-
<!-- loop code -->
|
|
120
|
-
<div id="loop-info">
|
|
121
|
-
<div class="loop-name" id="loopName">loop: bass</div>
|
|
122
|
-
<div class="loop-code" id="loopCode"></div>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<!-- params -->
|
|
126
|
-
<div id="params">
|
|
127
|
-
<div class="param">
|
|
128
|
-
<div class="param-header">
|
|
129
|
-
<span class="param-label">cutoff</span>
|
|
130
|
-
<span class="param-value" id="vCutoff">580 Hz</span>
|
|
131
|
-
</div>
|
|
132
|
-
<div class="param-track"><div class="param-fill" id="fCutoff" style="width:10%;background:#a6e3a1"></div></div>
|
|
133
|
-
</div>
|
|
134
|
-
<div class="param">
|
|
135
|
-
<div class="param-header">
|
|
136
|
-
<span class="param-label">lfo rate</span>
|
|
137
|
-
<span class="param-value" id="vLfo">0.10 Hz</span>
|
|
138
|
-
</div>
|
|
139
|
-
<div class="param-track"><div class="param-fill" id="fLfo" style="width:0%;background:#89b4fa"></div></div>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="param">
|
|
142
|
-
<div class="param-header">
|
|
143
|
-
<span class="param-label">reverb</span>
|
|
144
|
-
<span class="param-value" id="vReverb">0.27</span>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="param-track"><div class="param-fill" id="fReverb" style="width:34%;background:#cba6f7"></div></div>
|
|
147
|
-
</div>
|
|
148
|
-
<div class="param">
|
|
149
|
-
<div class="param-header">
|
|
150
|
-
<span class="param-label">voices</span>
|
|
151
|
-
<span class="param-value" id="vVoices">1</span>
|
|
152
|
-
</div>
|
|
153
|
-
<div class="param-track"><div class="param-fill" id="fVoices" style="width:25%;background:#f9e2af"></div></div>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
<div id="scene-label">
|
|
158
|
-
<div id="sceneTitle">minimal</div>
|
|
159
|
-
<span id="sceneDesc">def bass(): pass</span>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div id="badge">PyCodeDJ / visualizer concept</div>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
<script>
|
|
166
|
-
const canvas = document.getElementById('canvas');
|
|
167
|
-
const ctx = canvas.getContext('2d');
|
|
168
|
-
|
|
169
|
-
function resize() {
|
|
170
|
-
canvas.width = window.innerWidth;
|
|
171
|
-
canvas.height = window.innerHeight;
|
|
172
|
-
}
|
|
173
|
-
resize();
|
|
174
|
-
window.addEventListener('resize', resize);
|
|
175
|
-
|
|
176
|
-
// ── Scene definitions ──────────────────────────────────────────────────────
|
|
177
|
-
const scenes = [
|
|
178
|
-
{
|
|
179
|
-
name: 'bass',
|
|
180
|
-
title: 'minimal',
|
|
181
|
-
desc: 'def bass(): pass',
|
|
182
|
-
cutoff: 0.10, lfo: 0.00, reverb: 0.34, voices: 1,
|
|
183
|
-
cutoffHz: '580 Hz', lfoHz: '0.10 Hz', reverbVal: '0.27',
|
|
184
|
-
code: '<span class="cm"># @loop bass interval=2.0</span>\n<span class="kw">def</span> <span class="fn">bass</span>():\n <span class="kw">pass</span>',
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: 'bass',
|
|
188
|
-
title: 'filter opens',
|
|
189
|
-
desc: 'deeper nesting → brighter cutoff',
|
|
190
|
-
cutoff: 0.40, lfo: 0.20, reverb: 0.20, voices: 1,
|
|
191
|
-
cutoffHz: '1720 Hz', lfoHz: '1.08 Hz', reverbVal: '0.16',
|
|
192
|
-
code: '<span class="cm"># @loop bass interval=2.0</span>\n<span class="kw">def</span> <span class="fn">bass</span>():\n <span class="kw">for</span> i <span class="kw">in</span> <span class="fn">range</span>(<span class="num">8</span>):\n <span class="kw">if</span> i % <span class="num">2</span> == <span class="num">0</span>:\n <span class="kw">pass</span>',
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'bass',
|
|
196
|
-
title: 'deep structure',
|
|
197
|
-
desc: 'triple nesting → wide open filter',
|
|
198
|
-
cutoff: 0.60, lfo: 0.30, reverb: 0.16, voices: 1,
|
|
199
|
-
cutoffHz: '2480 Hz', lfoHz: '1.57 Hz', reverbVal: '0.13',
|
|
200
|
-
code: '<span class="cm"># @loop bass interval=2.0</span>\n<span class="kw">def</span> <span class="fn">bass</span>():\n <span class="kw">for</span> i <span class="kw">in</span> <span class="fn">range</span>(<span class="num">8</span>):\n <span class="kw">for</span> j <span class="kw">in</span> <span class="fn">range</span>(<span class="num">4</span>):\n <span class="kw">if</span> i == j:\n <span class="kw">pass</span>',
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
name: 'chord',
|
|
204
|
-
title: '4 voices',
|
|
205
|
-
desc: 'four functions → four layers',
|
|
206
|
-
cutoff: 0.10, lfo: 0.00, reverb: 0.20, voices: 4,
|
|
207
|
-
cutoffHz: '580 Hz', lfoHz: '0.10 Hz', reverbVal: '0.16',
|
|
208
|
-
code: '<span class="cm"># @loop chord interval=1.0</span>\n<span class="kw">def</span> <span class="fn">voice_a</span>(): <span class="kw">pass</span>\n<span class="kw">def</span> <span class="fn">voice_b</span>(): <span class="kw">pass</span>\n<span class="kw">def</span> <span class="fn">voice_c</span>(): <span class="kw">pass</span>\n<span class="kw">def</span> <span class="fn">voice_d</span>(): <span class="kw">pass</span>',
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'pad',
|
|
212
|
-
title: 'warehouse air',
|
|
213
|
-
desc: 'comments only → deep reverb space',
|
|
214
|
-
cutoff: 0.10, lfo: 0.00, reverb: 0.84, voices: 1,
|
|
215
|
-
cutoffHz: '580 Hz', lfoHz: '0.10 Hz', reverbVal: '0.67',
|
|
216
|
-
code: '<span class="cm"># @loop pad interval=4.0</span>\n<span class="cm"># smoke above the kick</span>\n<span class="cm"># late reflections</span>\n<span class="cm"># concrete room tail</span>\n<span class="kw">def</span> <span class="fn">pad</span>(): <span class="kw">pass</span>',
|
|
217
|
-
},
|
|
218
|
-
];
|
|
219
|
-
|
|
220
|
-
// ── State ──────────────────────────────────────────────────────────────────
|
|
221
|
-
let state = {
|
|
222
|
-
cutoff: 0.10, // 0-1
|
|
223
|
-
lfo: 0.00, // 0-1
|
|
224
|
-
reverb: 0.34, // 0-1
|
|
225
|
-
voices: 1,
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// smoothed state for rendering
|
|
229
|
-
let smooth = { ...state };
|
|
230
|
-
|
|
231
|
-
let sceneIdx = 0;
|
|
232
|
-
let t = 0;
|
|
233
|
-
const SCENE_DURATION = 4000; // ms
|
|
234
|
-
let lastSwitch = performance.now();
|
|
235
|
-
|
|
236
|
-
// ── Particles ──────────────────────────────────────────────────────────────
|
|
237
|
-
const MAX_PARTICLES = 320;
|
|
238
|
-
const particles = [];
|
|
239
|
-
|
|
240
|
-
class Particle {
|
|
241
|
-
constructor(cx, cy) {
|
|
242
|
-
this.reset(cx, cy);
|
|
243
|
-
}
|
|
244
|
-
reset(cx, cy) {
|
|
245
|
-
const a = Math.random() * Math.PI * 2;
|
|
246
|
-
const r = 40 + Math.random() * 160;
|
|
247
|
-
this.x = cx + Math.cos(a) * r;
|
|
248
|
-
this.y = cy + Math.sin(a) * r;
|
|
249
|
-
this.vx = (Math.random() - 0.5) * 0.6;
|
|
250
|
-
this.vy = (Math.random() - 0.5) * 0.6;
|
|
251
|
-
this.life = 0;
|
|
252
|
-
this.maxLife = 120 + Math.random() * 200;
|
|
253
|
-
this.size = 1 + Math.random() * 2.5;
|
|
254
|
-
this.hue = 120 + Math.random() * 200; // green→mauve
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ── Rings ──────────────────────────────────────────────────────────────────
|
|
259
|
-
const rings = Array.from({ length: 6 }, (_, i) => ({
|
|
260
|
-
phase: (i / 6) * Math.PI * 2,
|
|
261
|
-
baseR: 60 + i * 55,
|
|
262
|
-
speed: 0.003 + i * 0.001,
|
|
263
|
-
}));
|
|
264
|
-
|
|
265
|
-
// ── Lissajous trails ───────────────────────────────────────────────────────
|
|
266
|
-
const lissajousHistory = [];
|
|
267
|
-
const LISSA_LEN = 320;
|
|
268
|
-
|
|
269
|
-
// ── HUD update ─────────────────────────────────────────────────────────────
|
|
270
|
-
function applyScene(s) {
|
|
271
|
-
state = { cutoff: s.cutoff, lfo: s.lfo, reverb: s.reverb, voices: s.voices };
|
|
272
|
-
|
|
273
|
-
document.getElementById('loopName').textContent = `loop: ${s.name}`;
|
|
274
|
-
document.getElementById('loopCode').innerHTML = s.code;
|
|
275
|
-
document.getElementById('sceneTitle').textContent = s.title;
|
|
276
|
-
document.getElementById('sceneDesc').textContent = s.desc;
|
|
277
|
-
|
|
278
|
-
document.getElementById('vCutoff').textContent = s.cutoffHz;
|
|
279
|
-
document.getElementById('vLfo').textContent = s.lfoHz;
|
|
280
|
-
document.getElementById('vReverb').textContent = s.reverbVal;
|
|
281
|
-
document.getElementById('vVoices').textContent = String(s.voices);
|
|
282
|
-
|
|
283
|
-
document.getElementById('fCutoff').style.width = (s.cutoff * 100) + '%';
|
|
284
|
-
document.getElementById('fLfo').style.width = (s.lfo * 100) + '%';
|
|
285
|
-
document.getElementById('fReverb').style.width = (s.reverb * 100) + '%';
|
|
286
|
-
document.getElementById('fVoices').style.width = ((s.voices / 4) * 100) + '%';
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
applyScene(scenes[0]);
|
|
290
|
-
|
|
291
|
-
// ── Main render loop ───────────────────────────────────────────────────────
|
|
292
|
-
function lerp(a, b, k) { return a + (b - a) * k; }
|
|
293
|
-
|
|
294
|
-
let last = performance.now();
|
|
295
|
-
|
|
296
|
-
function frame(now) {
|
|
297
|
-
const dt = Math.min(now - last, 32);
|
|
298
|
-
last = now;
|
|
299
|
-
t += dt * 0.001;
|
|
300
|
-
|
|
301
|
-
// scene switch
|
|
302
|
-
if (now - lastSwitch > SCENE_DURATION) {
|
|
303
|
-
sceneIdx = (sceneIdx + 1) % scenes.length;
|
|
304
|
-
applyScene(scenes[sceneIdx]);
|
|
305
|
-
lastSwitch = now;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// smooth params
|
|
309
|
-
const k = 1 - Math.pow(0.92, dt);
|
|
310
|
-
smooth.cutoff = lerp(smooth.cutoff, state.cutoff, k);
|
|
311
|
-
smooth.lfo = lerp(smooth.lfo, state.lfo, k);
|
|
312
|
-
smooth.reverb = lerp(smooth.reverb, state.reverb, k);
|
|
313
|
-
smooth.voices = lerp(smooth.voices, state.voices, k);
|
|
314
|
-
|
|
315
|
-
const W = canvas.width, H = canvas.height;
|
|
316
|
-
const cx = W / 2, cy = H / 2;
|
|
317
|
-
|
|
318
|
-
// ── background ──
|
|
319
|
-
const trailAlpha = 0.04 + smooth.reverb * 0.1; // more reverb = longer trails
|
|
320
|
-
ctx.fillStyle = `rgba(0,0,0,${trailAlpha < 0.06 ? 0.06 : trailAlpha})`;
|
|
321
|
-
ctx.fillRect(0, 0, W, H);
|
|
322
|
-
|
|
323
|
-
// ── base radial glow ──
|
|
324
|
-
const glowR = 180 + smooth.cutoff * 220;
|
|
325
|
-
const hue = 120 + smooth.cutoff * 160; // green(120) → mauve(280)
|
|
326
|
-
const grd = ctx.createRadialGradient(cx, cy, 0, cx, cy, glowR);
|
|
327
|
-
grd.addColorStop(0, `hsla(${hue},60%,55%,${0.04 + smooth.cutoff * 0.06})`);
|
|
328
|
-
grd.addColorStop(0.5, `hsla(${hue},50%,40%,${0.02})`);
|
|
329
|
-
grd.addColorStop(1, 'transparent');
|
|
330
|
-
ctx.fillStyle = grd;
|
|
331
|
-
ctx.fillRect(0, 0, W, H);
|
|
332
|
-
|
|
333
|
-
// ── Lissajous ──
|
|
334
|
-
const lfoSpeed = 0.3 + smooth.lfo * 3.5;
|
|
335
|
-
const lx = Math.sin(t * lfoSpeed * 1.00) * (cx * 0.55);
|
|
336
|
-
const ly = Math.sin(t * lfoSpeed * 1.31 + 0.7) * (cy * 0.45);
|
|
337
|
-
|
|
338
|
-
lissajousHistory.push({ x: cx + lx, y: cy + ly });
|
|
339
|
-
if (lissajousHistory.length > LISSA_LEN) lissajousHistory.shift();
|
|
340
|
-
|
|
341
|
-
if (lissajousHistory.length > 2) {
|
|
342
|
-
ctx.beginPath();
|
|
343
|
-
ctx.moveTo(lissajousHistory[0].x, lissajousHistory[0].y);
|
|
344
|
-
for (let i = 1; i < lissajousHistory.length; i++) {
|
|
345
|
-
const p = lissajousHistory[i];
|
|
346
|
-
const alpha = i / lissajousHistory.length;
|
|
347
|
-
ctx.lineTo(p.x, p.y);
|
|
348
|
-
}
|
|
349
|
-
ctx.strokeStyle = `hsla(${hue},70%,65%,0.45)`;
|
|
350
|
-
ctx.lineWidth = 1.2;
|
|
351
|
-
ctx.stroke();
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ── Rings (voice_count layers) ──
|
|
355
|
-
const voiceLayers = Math.round(smooth.voices);
|
|
356
|
-
for (let v = 0; v < voiceLayers; v++) {
|
|
357
|
-
const ring = rings[v % rings.length];
|
|
358
|
-
const phaseOffset = (v / voiceLayers) * Math.PI * 2;
|
|
359
|
-
const pulse = 1 + Math.sin(t * lfoSpeed + ring.phase + phaseOffset) * (0.08 + smooth.cutoff * 0.12);
|
|
360
|
-
const r = ring.baseR * pulse * (0.7 + smooth.cutoff * 0.5);
|
|
361
|
-
const ringHue = hue + v * 30;
|
|
362
|
-
const alpha = 0.25 + smooth.cutoff * 0.35;
|
|
363
|
-
|
|
364
|
-
ctx.beginPath();
|
|
365
|
-
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
366
|
-
ctx.strokeStyle = `hsla(${ringHue},65%,60%,${alpha})`;
|
|
367
|
-
ctx.lineWidth = 1 + smooth.reverb * 2;
|
|
368
|
-
ctx.stroke();
|
|
369
|
-
|
|
370
|
-
// inner glow dot at ring intersection with lissajous angle
|
|
371
|
-
const ang = t * ring.speed + ring.phase + phaseOffset;
|
|
372
|
-
const dx = cx + Math.cos(ang) * r;
|
|
373
|
-
const dy = cy + Math.sin(ang) * r;
|
|
374
|
-
ctx.beginPath();
|
|
375
|
-
ctx.arc(dx, dy, 3 + smooth.cutoff * 4, 0, Math.PI * 2);
|
|
376
|
-
ctx.fillStyle = `hsla(${ringHue},80%,70%,${0.6 + smooth.cutoff * 0.3})`;
|
|
377
|
-
ctx.fill();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ── Particles ──
|
|
381
|
-
// spawn
|
|
382
|
-
const spawnRate = Math.round(1 + smooth.lfo * 5 + smooth.voices * 1.5);
|
|
383
|
-
for (let i = 0; i < spawnRate; i++) {
|
|
384
|
-
if (particles.length < MAX_PARTICLES) {
|
|
385
|
-
particles.push(new Particle(cx, cy));
|
|
386
|
-
} else {
|
|
387
|
-
// recycle oldest dead particle
|
|
388
|
-
const dead = particles.find(p => p.life >= p.maxLife);
|
|
389
|
-
if (dead) dead.reset(cx, cy);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// update & draw
|
|
394
|
-
for (const p of particles) {
|
|
395
|
-
p.life++;
|
|
396
|
-
const progress = p.life / p.maxLife;
|
|
397
|
-
if (progress > 1) continue;
|
|
398
|
-
|
|
399
|
-
const speed = 0.4 + smooth.lfo * 2.5;
|
|
400
|
-
p.x += p.vx * speed;
|
|
401
|
-
p.y += p.vy * speed;
|
|
402
|
-
|
|
403
|
-
const alpha = Math.sin(progress * Math.PI) * (0.4 + smooth.cutoff * 0.4);
|
|
404
|
-
const size = p.size * (1 - progress * 0.5);
|
|
405
|
-
|
|
406
|
-
ctx.beginPath();
|
|
407
|
-
ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
|
|
408
|
-
ctx.fillStyle = `hsla(${p.hue + smooth.cutoff * 60},70%,65%,${alpha})`;
|
|
409
|
-
ctx.fill();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// ── Reverb shimmer — horizontal scan lines ──
|
|
413
|
-
if (smooth.reverb > 0.1) {
|
|
414
|
-
const lineCount = Math.round(smooth.reverb * 18);
|
|
415
|
-
for (let i = 0; i < lineCount; i++) {
|
|
416
|
-
const y = ((t * 60 * (i % 3 === 0 ? 1 : -0.7) + i * (H / lineCount)) % H + H) % H;
|
|
417
|
-
const a = smooth.reverb * 0.06 * (1 - Math.abs(y / H - 0.5) * 2);
|
|
418
|
-
ctx.fillStyle = `hsla(${hue + i * 12},50%,65%,${a})`;
|
|
419
|
-
ctx.fillRect(0, y, W, 1);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// ── Center crosshair ──
|
|
424
|
-
const chAlpha = 0.12 + smooth.cutoff * 0.08;
|
|
425
|
-
ctx.strokeStyle = `rgba(166,227,161,${chAlpha})`;
|
|
426
|
-
ctx.lineWidth = 0.5;
|
|
427
|
-
ctx.setLineDash([4, 8]);
|
|
428
|
-
ctx.beginPath(); ctx.moveTo(cx - 30, cy); ctx.lineTo(cx + 30, cy); ctx.stroke();
|
|
429
|
-
ctx.beginPath(); ctx.moveTo(cx, cy - 30); ctx.lineTo(cx, cy + 30); ctx.stroke();
|
|
430
|
-
ctx.setLineDash([]);
|
|
431
|
-
|
|
432
|
-
requestAnimationFrame(frame);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
requestAnimationFrame(frame);
|
|
436
|
-
</script>
|
|
437
|
-
</body>
|
|
438
|
-
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|