sonic-forge 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.
- sonic_forge-0.1.0/.gitignore +27 -0
- sonic_forge-0.1.0/MANIFEST.md +14 -0
- sonic_forge-0.1.0/PKG-INFO +27 -0
- sonic_forge-0.1.0/bytebeat-dj.sh +256 -0
- sonic_forge-0.1.0/bytebeat-rating.md +7 -0
- sonic_forge-0.1.0/chuck-music/cathedral-drift.ck +123 -0
- sonic_forge-0.1.0/chuck-music/coding-ambient.ck +186 -0
- sonic_forge-0.1.0/chuck-music/coding-flow.ck +255 -0
- sonic_forge-0.1.0/chuck-music/dark-ambient.ck +297 -0
- sonic_forge-0.1.0/chuck-music/dark-industrial.ck +330 -0
- sonic_forge-0.1.0/chuck-music/dark-space.ck +457 -0
- sonic_forge-0.1.0/chuck-music/dark-techno.ck +421 -0
- sonic_forge-0.1.0/chuck-music/euro-rave.ck +447 -0
- sonic_forge-0.1.0/chuck-music/gameboy-evolve.ck +327 -0
- sonic_forge-0.1.0/chuck-music/play.sh +52 -0
- sonic_forge-0.1.0/chuck-music/singing-bowls.ck +196 -0
- sonic_forge-0.1.0/chuck-music/space-synth.ck +261 -0
- sonic_forge-0.1.0/pattern-engine/.gitignore +4 -0
- sonic_forge-0.1.0/pattern-engine/DSL-text-2-music-yaml.md +265 -0
- sonic_forge-0.1.0/pattern-engine/HOW-WE-DID-IT.md +132 -0
- sonic_forge-0.1.0/pattern-engine/MATERIALS-BAY-PLAN.md +146 -0
- sonic_forge-0.1.0/pattern-engine/acid_session.py +144 -0
- sonic_forge-0.1.0/pattern-engine/captains_log_short.yaml +86 -0
- sonic_forge-0.1.0/pattern-engine/captains_log_song.yaml +141 -0
- sonic_forge-0.1.0/pattern-engine/dance.sh +242 -0
- sonic_forge-0.1.0/pattern-engine/demos/play-demos.sh +174 -0
- sonic_forge-0.1.0/pattern-engine/demos-vibevoice/play-demos.sh +184 -0
- sonic_forge-0.1.0/pattern-engine/midnight.yaml +88 -0
- sonic_forge-0.1.0/pattern-engine/my_song.yaml +87 -0
- sonic_forge-0.1.0/pattern-engine/render_vibevoice_demos.py +306 -0
- sonic_forge-0.1.0/pattern-engine/songfile.py +339 -0
- sonic_forge-0.1.0/pattern-engine/songs.py +350 -0
- sonic_forge-0.1.0/pattern-engine/templates.py +527 -0
- sonic_forge-0.1.0/pattern-engine/tidal.py +1072 -0
- sonic_forge-0.1.0/pattern-engine/trance.py +123 -0
- sonic_forge-0.1.0/pattern-engine/trance1_1.py +187 -0
- sonic_forge-0.1.0/pattern-engine/trance1_1.yaml +88 -0
- sonic_forge-0.1.0/pattern-engine/trance2.py +208 -0
- sonic_forge-0.1.0/pattern-engine/tts-comparison/.gitignore +6 -0
- sonic_forge-0.1.0/pattern-engine/tts-comparison/REPORT.md +160 -0
- sonic_forge-0.1.0/pattern-engine/tts-comparison/generate_samples.py +481 -0
- sonic_forge-0.1.0/pattern-engine/tts-comparison/play-comparison.sh +341 -0
- sonic_forge-0.1.0/pattern-engine/tts-comparison/robotize.py +336 -0
- sonic_forge-0.1.0/pyproject.toml +25 -0
- sonic_forge-0.1.0/sound-demo-tour.sh +89 -0
- sonic_forge-0.1.0/sound-rating.md +25 -0
- sonic_forge-0.1.0/src/sonic_forge/__init__.py +3 -0
- sonic_forge-0.1.0/src/sonic_forge/cli.py +63 -0
- sonic_forge-0.1.0/src/sonic_forge/robotize.py +336 -0
- sonic_forge-0.1.0/src/sonic_forge/songfile.py +332 -0
- sonic_forge-0.1.0/src/sonic_forge/songs.py +350 -0
- sonic_forge-0.1.0/src/sonic_forge/templates.py +527 -0
- sonic_forge-0.1.0/src/sonic_forge/tidal.py +1072 -0
- sonic_forge-0.1.0/strudel-node/REPORT.md +148 -0
- sonic_forge-0.1.0/strudel-node/inspect-events.mjs +28 -0
- sonic_forge-0.1.0/strudel-node/package-lock.json +201 -0
- sonic_forge-0.1.0/strudel-node/package.json +18 -0
- sonic_forge-0.1.0/strudel-node/play.mjs +250 -0
- sonic_forge-0.1.0/strudel-node/test-patterns.mjs +45 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Audio (generated on demand)
|
|
2
|
+
*.wav
|
|
3
|
+
*.aiff
|
|
4
|
+
*.mp3
|
|
5
|
+
*.ogg
|
|
6
|
+
|
|
7
|
+
# Models (downloaded on first use)
|
|
8
|
+
*.onnx
|
|
9
|
+
*.bin
|
|
10
|
+
*.safetensors
|
|
11
|
+
kokoro-models/
|
|
12
|
+
chattts-assets/
|
|
13
|
+
|
|
14
|
+
# Python
|
|
15
|
+
__pycache__/
|
|
16
|
+
*.pyc
|
|
17
|
+
*.egg-info/
|
|
18
|
+
dist/
|
|
19
|
+
build/
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
|
|
23
|
+
# Node
|
|
24
|
+
node_modules/
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# MATERIALS-BAY: sonic-forge
|
|
2
|
+
|
|
3
|
+
**Created:** 2026-03-11 15:12
|
|
4
|
+
**Source labs:** pattern-engine, chuck-music, strudel-node, bytebeat-dj.sh, sound-demo-tour.sh, bytebeat-rating.md, sound-rating.md
|
|
5
|
+
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- **pattern-engine/** — from `LABS/pattern-engine/`
|
|
9
|
+
- **chuck-music/** — from `LABS/chuck-music/`
|
|
10
|
+
- **strudel-node/** — from `LABS/strudel-node/`
|
|
11
|
+
- **bytebeat-dj.sh/** — from `LABS/bytebeat-dj.sh/`
|
|
12
|
+
- **sound-demo-tour.sh/** — from `LABS/sound-demo-tour.sh/`
|
|
13
|
+
- **bytebeat-rating.md/** — from `LABS/bytebeat-rating.md/`
|
|
14
|
+
- **sound-rating.md/** — from `LABS/sound-rating.md/`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sonic-forge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bytebeat music DSL + multi-engine TTS voice system — beamed from starforge
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: pyyaml
|
|
7
|
+
Requires-Dist: typer
|
|
8
|
+
Provides-Extra: all
|
|
9
|
+
Requires-Dist: f5-tts-mlx; extra == 'all'
|
|
10
|
+
Requires-Dist: kokoro-onnx; extra == 'all'
|
|
11
|
+
Requires-Dist: numpy; extra == 'all'
|
|
12
|
+
Requires-Dist: onnxruntime; extra == 'all'
|
|
13
|
+
Requires-Dist: soundfile; extra == 'all'
|
|
14
|
+
Provides-Extra: kokoro
|
|
15
|
+
Requires-Dist: kokoro-onnx; extra == 'kokoro'
|
|
16
|
+
Requires-Dist: numpy; extra == 'kokoro'
|
|
17
|
+
Requires-Dist: onnxruntime; extra == 'kokoro'
|
|
18
|
+
Requires-Dist: soundfile; extra == 'kokoro'
|
|
19
|
+
Provides-Extra: robot
|
|
20
|
+
Requires-Dist: numpy; extra == 'robot'
|
|
21
|
+
Requires-Dist: soundfile; extra == 'robot'
|
|
22
|
+
Provides-Extra: tts
|
|
23
|
+
Requires-Dist: f5-tts-mlx; extra == 'tts'
|
|
24
|
+
Requires-Dist: kokoro-onnx; extra == 'tts'
|
|
25
|
+
Requires-Dist: numpy; extra == 'tts'
|
|
26
|
+
Requires-Dist: onnxruntime; extra == 'tts'
|
|
27
|
+
Requires-Dist: soundfile; extra == 'tts'
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Bytebeat DJ — interactive player with speed, volume, and track controls
|
|
3
|
+
# Writes Python to temp file so curses gets proper terminal access
|
|
4
|
+
|
|
5
|
+
VENV_PYTHON="/Users/t/clients/starship/starforge/venv/bin/python"
|
|
6
|
+
|
|
7
|
+
# Clean up any stale temp files, then create fresh one
|
|
8
|
+
rm -f /tmp/bytebeat_dj_*.py
|
|
9
|
+
TMPSCRIPT="/tmp/bytebeat_dj_$$.py"
|
|
10
|
+
|
|
11
|
+
cat > "$TMPSCRIPT" << 'PYEOF'
|
|
12
|
+
import wave, curses, subprocess, os, sys, time, threading
|
|
13
|
+
|
|
14
|
+
TRACKS = [
|
|
15
|
+
("classic", "t*(t>>8|t>>9)&46&t>>8^(t&t>>13|t>>6)",
|
|
16
|
+
lambda t: (t*(((t>>12)|(t>>8)))&(46&(t>>8)))^((t&(t>>13))|(t>>6))),
|
|
17
|
+
("melody", "t*((t>>9)|(t>>13))&16",
|
|
18
|
+
lambda t: t*((t>>9)|(t>>13))&16),
|
|
19
|
+
("bass", "t&(t>>8)",
|
|
20
|
+
lambda t: t&(t>>8)),
|
|
21
|
+
("rhythm", "(t*(t>>5|t>>8))>>(t>>16)",
|
|
22
|
+
lambda t: (t*((t>>5)|(t>>8)))>>(t>>16) if (t>>16) < 32 else 0),
|
|
23
|
+
("chaos", "(t*5&t>>7)|(t*3&t>>10)",
|
|
24
|
+
lambda t: (t*5&(t>>7))|(t*3&(t>>10))),
|
|
25
|
+
("crowd", "(t<<1)^((t<<1)+(t>>7)&t>>12)|t>>7",
|
|
26
|
+
lambda t: ((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7),
|
|
27
|
+
("sierpinski", "t&t>>8",
|
|
28
|
+
lambda t: t&t>>8),
|
|
29
|
+
("42melody", "t*(42&t>>10)",
|
|
30
|
+
lambda t: (t*(42&t>>10))&255),
|
|
31
|
+
("glitch", "t*((t>>12|t>>8)&63&t>>4)",
|
|
32
|
+
lambda t: t*((t>>12|t>>8)&63&t>>4)),
|
|
33
|
+
("cathedral", "(t>>6|t|t>>(t>>16))*10+((t>>11)&7)",
|
|
34
|
+
lambda t: (t>>6|t|t>>(t>>16))*10+((t>>11)&7)),
|
|
35
|
+
("gameboy", "t*9&t>>4|t*5&t>>7|t*3&t>>10",
|
|
36
|
+
lambda t: (t*9&t>>4|t*5&t>>7|t*3&t>>10)-1),
|
|
37
|
+
("underwater", "(t*(t>>5|t>>8))>>(t>>16)",
|
|
38
|
+
lambda t: (t*(t>>5|t>>8))>>(t>>16)),
|
|
39
|
+
("alien", "t*(((t>>9)^((t>>9)-1)^1)%13)",
|
|
40
|
+
lambda t: t*(((t>>9)^((t>>9)-1)^1)%13)),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
SPEEDS = [
|
|
44
|
+
(2000, "0.25x"), (3000, "0.38x"), (4000, "0.5x"),
|
|
45
|
+
(5000, "0.63x"), (6000, "0.75x"), (7000, "0.88x"),
|
|
46
|
+
(8000, "1.0x"), (9000, "1.13x"), (10000, "1.25x"),
|
|
47
|
+
(12000, "1.5x"), (14000, "1.75x"), (16000, "2.0x"),
|
|
48
|
+
(20000, "2.5x"), (24000, "3.0x"), (32000, "4.0x"),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
current_track = 0
|
|
52
|
+
speed_idx = 6
|
|
53
|
+
volume = 0.5
|
|
54
|
+
afplay_proc = None
|
|
55
|
+
generating = False
|
|
56
|
+
WAV_PATH = "/tmp/bytebeat_dj.wav"
|
|
57
|
+
DURATION = 600 # 10 minutes — bytebeat is infinite math, let it ride
|
|
58
|
+
|
|
59
|
+
def generate_wav(track_idx, rate):
|
|
60
|
+
global generating
|
|
61
|
+
generating = True
|
|
62
|
+
name, _, func = TRACKS[track_idx]
|
|
63
|
+
samples = rate * DURATION
|
|
64
|
+
with wave.open(WAV_PATH, 'w') as f:
|
|
65
|
+
f.setnchannels(1)
|
|
66
|
+
f.setsampwidth(1)
|
|
67
|
+
f.setframerate(rate)
|
|
68
|
+
buf = bytearray(samples)
|
|
69
|
+
for t in range(samples):
|
|
70
|
+
try:
|
|
71
|
+
buf[t] = func(t) & 255
|
|
72
|
+
except:
|
|
73
|
+
buf[t] = 128
|
|
74
|
+
f.writeframes(bytes(buf))
|
|
75
|
+
generating = False
|
|
76
|
+
|
|
77
|
+
def kill_player():
|
|
78
|
+
global afplay_proc
|
|
79
|
+
if afplay_proc and afplay_proc.poll() is None:
|
|
80
|
+
afplay_proc.terminate()
|
|
81
|
+
try:
|
|
82
|
+
afplay_proc.wait(timeout=1)
|
|
83
|
+
except:
|
|
84
|
+
afplay_proc.kill()
|
|
85
|
+
afplay_proc = None
|
|
86
|
+
|
|
87
|
+
def start_playback():
|
|
88
|
+
global afplay_proc
|
|
89
|
+
kill_player()
|
|
90
|
+
generate_wav(current_track, SPEEDS[speed_idx][0])
|
|
91
|
+
afplay_proc = subprocess.Popen(
|
|
92
|
+
["afplay", "-v", str(volume), WAV_PATH],
|
|
93
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def adjust_volume():
|
|
97
|
+
"""Restart playback with new volume (no regeneration needed)."""
|
|
98
|
+
global afplay_proc
|
|
99
|
+
kill_player()
|
|
100
|
+
if os.path.exists(WAV_PATH):
|
|
101
|
+
afplay_proc = subprocess.Popen(
|
|
102
|
+
["afplay", "-v", str(volume), WAV_PATH],
|
|
103
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def main(stdscr):
|
|
107
|
+
global current_track, speed_idx, volume, afplay_proc
|
|
108
|
+
|
|
109
|
+
curses.curs_set(0)
|
|
110
|
+
curses.start_color()
|
|
111
|
+
curses.use_default_colors()
|
|
112
|
+
curses.init_pair(1, curses.COLOR_YELLOW, -1)
|
|
113
|
+
curses.init_pair(2, curses.COLOR_CYAN, -1)
|
|
114
|
+
curses.init_pair(3, curses.COLOR_GREEN, -1)
|
|
115
|
+
curses.init_pair(4, curses.COLOR_RED, -1)
|
|
116
|
+
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
|
|
117
|
+
curses.init_pair(6, curses.COLOR_WHITE, -1)
|
|
118
|
+
|
|
119
|
+
stdscr.nodelay(True)
|
|
120
|
+
stdscr.keypad(True)
|
|
121
|
+
|
|
122
|
+
start_playback()
|
|
123
|
+
|
|
124
|
+
while True:
|
|
125
|
+
stdscr.erase()
|
|
126
|
+
h, w = stdscr.getmaxyx()
|
|
127
|
+
name, formula_str, _ = TRACKS[current_track]
|
|
128
|
+
rate, slabel = SPEEDS[speed_idx]
|
|
129
|
+
|
|
130
|
+
# Title
|
|
131
|
+
title = " BYTEBEAT DJ "
|
|
132
|
+
cx = max(0, (w - len(title)) // 2)
|
|
133
|
+
try:
|
|
134
|
+
stdscr.addstr(1, cx, title, curses.A_BOLD | curses.color_pair(1))
|
|
135
|
+
except curses.error:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def safe_add(y, x, text, attr=0):
|
|
139
|
+
try:
|
|
140
|
+
if y < h - 1 and x < w:
|
|
141
|
+
stdscr.addstr(y, x, text[:w-x-1], attr)
|
|
142
|
+
except curses.error:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
y = 3
|
|
146
|
+
safe_add(y, 2, f"Track {current_track+1}/{len(TRACKS)}", curses.color_pair(2))
|
|
147
|
+
y += 1
|
|
148
|
+
safe_add(y, 4, name, curses.A_BOLD | curses.color_pair(3))
|
|
149
|
+
y += 1
|
|
150
|
+
fdisp = formula_str if len(formula_str) < w - 8 else formula_str[:w-11] + "..."
|
|
151
|
+
safe_add(y, 4, fdisp, curses.color_pair(6))
|
|
152
|
+
|
|
153
|
+
# Speed bar
|
|
154
|
+
y += 2
|
|
155
|
+
bar_w = min(30, w - 25)
|
|
156
|
+
if bar_w > 5:
|
|
157
|
+
safe_add(y, 2, "Speed:", curses.color_pair(5))
|
|
158
|
+
pos = int((speed_idx / max(1, len(SPEEDS)-1)) * bar_w)
|
|
159
|
+
bar = "[" + "=" * pos + "O" + "-" * (bar_w - pos) + "]"
|
|
160
|
+
safe_add(y, 9, bar, curses.color_pair(5))
|
|
161
|
+
safe_add(y, 10 + bar_w, f" {slabel} ({rate}Hz)", curses.color_pair(6))
|
|
162
|
+
|
|
163
|
+
# Volume bar
|
|
164
|
+
y += 1
|
|
165
|
+
if bar_w > 5:
|
|
166
|
+
safe_add(y, 2, "Vol: ", curses.color_pair(4))
|
|
167
|
+
vol_pos = int(min(1.0, volume) * bar_w)
|
|
168
|
+
vol_bar = "[" + "=" * vol_pos + "O" + "-" * (bar_w - vol_pos) + "]"
|
|
169
|
+
safe_add(y, 9, vol_bar, curses.color_pair(4))
|
|
170
|
+
safe_add(y, 10 + bar_w, f" {int(volume*100)}%", curses.color_pair(6))
|
|
171
|
+
|
|
172
|
+
# Status
|
|
173
|
+
y += 2
|
|
174
|
+
playing = afplay_proc and afplay_proc.poll() is None
|
|
175
|
+
if generating:
|
|
176
|
+
safe_add(y, 4, "generating...", curses.color_pair(1))
|
|
177
|
+
elif playing:
|
|
178
|
+
tick = int(time.time() * 4) % 4
|
|
179
|
+
frames = ["♪ ♫ ", " ♪ ♫ ", " ♪ ♫", " ♫ ♪ "]
|
|
180
|
+
safe_add(y, 4, f"{frames[tick]} PLAYING", curses.color_pair(3))
|
|
181
|
+
else:
|
|
182
|
+
safe_add(y, 4, "stopped — SPACE to play", curses.color_pair(4))
|
|
183
|
+
|
|
184
|
+
# Track list
|
|
185
|
+
y += 2
|
|
186
|
+
safe_add(y, 2, "Tracks:", curses.color_pair(2))
|
|
187
|
+
y += 1
|
|
188
|
+
for i, (tname, _, _) in enumerate(TRACKS):
|
|
189
|
+
if y >= h - 4:
|
|
190
|
+
break
|
|
191
|
+
marker = " >> " if i == current_track else " "
|
|
192
|
+
attr = curses.A_BOLD | curses.color_pair(3) if i == current_track else curses.color_pair(6)
|
|
193
|
+
safe_add(y, 2, f"{marker}{i+1:2d}. {tname}", attr)
|
|
194
|
+
y += 1
|
|
195
|
+
|
|
196
|
+
# Controls
|
|
197
|
+
if h > 3:
|
|
198
|
+
safe_add(h-2, 2, "< > track ^ v speed +/- vol SPACE restart 1-9 jump q quit", curses.color_pair(6))
|
|
199
|
+
|
|
200
|
+
stdscr.refresh()
|
|
201
|
+
|
|
202
|
+
# Input — drain all pending keys, act on last meaningful one
|
|
203
|
+
key = -1
|
|
204
|
+
while True:
|
|
205
|
+
k = stdscr.getch()
|
|
206
|
+
if k == -1:
|
|
207
|
+
break
|
|
208
|
+
key = k
|
|
209
|
+
|
|
210
|
+
if key == -1:
|
|
211
|
+
time.sleep(0.05)
|
|
212
|
+
# Auto-loop if track ended
|
|
213
|
+
if not generating and afplay_proc and afplay_proc.poll() is not None:
|
|
214
|
+
start_playback()
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
if key == ord('q') or key == ord('Q'):
|
|
218
|
+
kill_player()
|
|
219
|
+
break
|
|
220
|
+
elif key == curses.KEY_RIGHT or key == ord('l'):
|
|
221
|
+
current_track = (current_track + 1) % len(TRACKS)
|
|
222
|
+
start_playback()
|
|
223
|
+
elif key == curses.KEY_LEFT or key == ord('h'):
|
|
224
|
+
current_track = (current_track - 1) % len(TRACKS)
|
|
225
|
+
start_playback()
|
|
226
|
+
elif key == curses.KEY_UP or key == ord('k'):
|
|
227
|
+
if speed_idx < len(SPEEDS) - 1:
|
|
228
|
+
speed_idx += 1
|
|
229
|
+
start_playback()
|
|
230
|
+
elif key == curses.KEY_DOWN or key == ord('j'):
|
|
231
|
+
if speed_idx > 0:
|
|
232
|
+
speed_idx -= 1
|
|
233
|
+
start_playback()
|
|
234
|
+
elif key == ord('+') or key == ord('='):
|
|
235
|
+
volume = min(2.0, round(volume + 0.1, 1))
|
|
236
|
+
adjust_volume()
|
|
237
|
+
elif key == ord('-') or key == ord('_'):
|
|
238
|
+
volume = max(0.0, round(volume - 0.1, 1))
|
|
239
|
+
adjust_volume()
|
|
240
|
+
elif key == ord(' '):
|
|
241
|
+
start_playback()
|
|
242
|
+
elif ord('1') <= key <= ord('9'):
|
|
243
|
+
idx = key - ord('1')
|
|
244
|
+
if idx < len(TRACKS):
|
|
245
|
+
current_track = idx
|
|
246
|
+
start_playback()
|
|
247
|
+
elif key == ord('0'):
|
|
248
|
+
if len(TRACKS) > 9:
|
|
249
|
+
current_track = 9
|
|
250
|
+
start_playback()
|
|
251
|
+
|
|
252
|
+
curses.wrapper(main)
|
|
253
|
+
PYEOF
|
|
254
|
+
|
|
255
|
+
exec "$VENV_PYTHON" "$TMPSCRIPT"
|
|
256
|
+
rm -f "$TMPSCRIPT"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Bytebeat Rating — Mathematical Music Tour
|
|
2
|
+
|
|
3
|
+
Each formula generates audio from pure math: `f(t)` where t is a sample counter.
|
|
4
|
+
The output byte (0-255) IS the audio waveform. No instruments, no samples — just math.
|
|
5
|
+
|
|
6
|
+
| # | Name | Formula | Like? | Volume (1-10) | Notes |
|
|
7
|
+
|---|------|---------|-------|---------------|-------|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Starforge Cathedral Drift — deep reverb pads that slowly transform
|
|
2
|
+
// Inspired by your love of the "cathedral" bytebeat formula
|
|
3
|
+
// Run: chuck LABS/chuck-music/cathedral-drift.ck
|
|
4
|
+
|
|
5
|
+
Gain master => NRev reverb => dac;
|
|
6
|
+
0.15 => reverb.mix; // heavy reverb for cathedral feel
|
|
7
|
+
0.3 => master.gain;
|
|
8
|
+
|
|
9
|
+
// === DRONE: low fundamental that shifts ===
|
|
10
|
+
fun void drone() {
|
|
11
|
+
SawOsc saw1 => LPF filt => Gain droneGain => master;
|
|
12
|
+
SawOsc saw2 => filt;
|
|
13
|
+
0.06 => droneGain.gain;
|
|
14
|
+
300.0 => filt.freq;
|
|
15
|
+
4.0 => filt.Q;
|
|
16
|
+
|
|
17
|
+
[36, 38, 41, 43, 34, 31] @=> int roots[]; // deep roots
|
|
18
|
+
0 => int idx;
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
Std.mtof(roots[idx]) => float targetFreq;
|
|
22
|
+
saw1.freq() => float startFreq;
|
|
23
|
+
if (startFreq < 10.0) targetFreq => startFreq;
|
|
24
|
+
|
|
25
|
+
// Glacial glide to new note
|
|
26
|
+
for (0 => int i; i < 200; i++) {
|
|
27
|
+
startFreq + (targetFreq - startFreq) * (i / 200.0) => saw1.freq;
|
|
28
|
+
(startFreq + (targetFreq - startFreq) * (i / 200.0)) * 1.005 => saw2.freq;
|
|
29
|
+
25::ms => now;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Hold
|
|
33
|
+
Math.random2(3000, 8000)::ms => now;
|
|
34
|
+
|
|
35
|
+
(idx + Math.random2(1, 3)) % roots.size() => idx;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// === BELLS: sine harmonics that ring and fade ===
|
|
40
|
+
fun void bell(float freq, float vol, dur length) {
|
|
41
|
+
SinOsc s1 => Gain g => master;
|
|
42
|
+
SinOsc s2 => g;
|
|
43
|
+
SinOsc s3 => g;
|
|
44
|
+
freq => s1.freq;
|
|
45
|
+
freq * 2.756 => s2.freq; // inharmonic partial (bell-like)
|
|
46
|
+
freq * 5.404 => s3.freq; // higher inharmonic
|
|
47
|
+
vol => g.gain;
|
|
48
|
+
|
|
49
|
+
// Exponential decay
|
|
50
|
+
length / 50 => dur step;
|
|
51
|
+
for (0 => int i; i < 50; i++) {
|
|
52
|
+
vol * Math.pow(0.92, i $ float) => g.gain;
|
|
53
|
+
step => now;
|
|
54
|
+
}
|
|
55
|
+
0.0 => g.gain;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fun void bells() {
|
|
59
|
+
[60, 63, 67, 70, 72, 75, 79, 84] @=> int notes[];
|
|
60
|
+
|
|
61
|
+
while (true) {
|
|
62
|
+
// Sparse — sometimes play, sometimes rest
|
|
63
|
+
if (Math.random2f(0.0, 1.0) < 0.35) {
|
|
64
|
+
Math.random2(0, notes.size()-1) => int idx;
|
|
65
|
+
spork ~ bell(Std.mtof(notes[idx]),
|
|
66
|
+
Math.random2f(0.02, 0.05),
|
|
67
|
+
Math.random2(2000, 5000)::ms);
|
|
68
|
+
}
|
|
69
|
+
Math.random2(800, 3000)::ms => now;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// === MORE BELLS: second bell layer with different tuning ===
|
|
74
|
+
fun void bellsLow() {
|
|
75
|
+
// Lower register bells with longer decay
|
|
76
|
+
[48, 51, 53, 55, 58, 60] @=> int notes[];
|
|
77
|
+
|
|
78
|
+
while (true) {
|
|
79
|
+
if (Math.random2f(0.0, 1.0) < 0.25) {
|
|
80
|
+
Math.random2(0, notes.size()-1) => int idx;
|
|
81
|
+
spork ~ bell(Std.mtof(notes[idx]),
|
|
82
|
+
Math.random2f(0.015, 0.035),
|
|
83
|
+
Math.random2(3000, 7000)::ms);
|
|
84
|
+
}
|
|
85
|
+
Math.random2(1500, 5000)::ms => now;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// === SUB PULSE: very deep, slow ===
|
|
90
|
+
fun void subPulse() {
|
|
91
|
+
SinOsc sub => Gain subGain => master;
|
|
92
|
+
0.0 => subGain.gain;
|
|
93
|
+
|
|
94
|
+
[29, 31, 34, 36] @=> int notes[]; // very low
|
|
95
|
+
0 => int idx;
|
|
96
|
+
|
|
97
|
+
while (true) {
|
|
98
|
+
Std.mtof(notes[idx]) => sub.freq;
|
|
99
|
+
|
|
100
|
+
// Slow fade in/out
|
|
101
|
+
for (0 => int i; i < 60; i++) {
|
|
102
|
+
0.1 * Math.sin(Math.PI * i / 60.0) => subGain.gain;
|
|
103
|
+
50::ms => now;
|
|
104
|
+
}
|
|
105
|
+
0.0 => subGain.gain;
|
|
106
|
+
|
|
107
|
+
Math.random2(2000, 6000)::ms => now;
|
|
108
|
+
(idx + 1) % notes.size() => idx;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// === LAUNCH ===
|
|
113
|
+
<<< " Starforge Cathedral Drift", "" >>>;
|
|
114
|
+
<<< " Deep reverb ambient — slow transformation", "" >>>;
|
|
115
|
+
<<< " Ctrl-C to stop", "" >>>;
|
|
116
|
+
<<< "" >>>;
|
|
117
|
+
|
|
118
|
+
spork ~ drone();
|
|
119
|
+
spork ~ bells();
|
|
120
|
+
spork ~ bellsLow();
|
|
121
|
+
spork ~ subPulse();
|
|
122
|
+
|
|
123
|
+
while (true) 1::second => now;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Starforge Coding Ambient — evolving generative music for terminal coding
|
|
2
|
+
// Run: chuck LABS/chuck-music/coding-ambient.ck
|
|
3
|
+
// Ctrl-C to stop
|
|
4
|
+
|
|
5
|
+
// === MASTER OUTPUT ===
|
|
6
|
+
Gain master => NRev reverb => dac;
|
|
7
|
+
0.08 => reverb.mix;
|
|
8
|
+
0.35 => master.gain;
|
|
9
|
+
|
|
10
|
+
// === PAD: slow evolving chords ===
|
|
11
|
+
fun void pad() {
|
|
12
|
+
// Pentatonic minor scale degrees (dark, spacey)
|
|
13
|
+
[48, 51, 53, 55, 58, 60, 63, 65, 67, 70] @=> int notes[];
|
|
14
|
+
|
|
15
|
+
// Two detuned oscillators for thickness
|
|
16
|
+
TriOsc p1 => LPF filt1 => Gain padGain => master;
|
|
17
|
+
TriOsc p2 => LPF filt2 => padGain;
|
|
18
|
+
0.12 => padGain.gain;
|
|
19
|
+
800.0 => filt1.freq;
|
|
20
|
+
750.0 => filt2.freq;
|
|
21
|
+
3.0 => filt1.Q;
|
|
22
|
+
3.0 => filt2.Q;
|
|
23
|
+
|
|
24
|
+
while (true) {
|
|
25
|
+
// Pick two notes for a chord
|
|
26
|
+
Math.random2(0, notes.size()-1) => int idx1;
|
|
27
|
+
(idx1 + Math.random2(2, 4)) % notes.size() => int idx2;
|
|
28
|
+
|
|
29
|
+
Std.mtof(notes[idx1]) => p1.freq;
|
|
30
|
+
Std.mtof(notes[idx2]) * 1.003 => p2.freq; // slight detune
|
|
31
|
+
|
|
32
|
+
// Slow filter sweep
|
|
33
|
+
Math.random2f(400.0, 1200.0) => float targetFreq;
|
|
34
|
+
filt1.freq() => float startFreq;
|
|
35
|
+
for (0 => int i; i < 100; i++) {
|
|
36
|
+
startFreq + (targetFreq - startFreq) * (i / 100.0) => filt1.freq;
|
|
37
|
+
startFreq + (targetFreq - startFreq) * (i / 100.0) * 0.95 => filt2.freq;
|
|
38
|
+
40::ms => now;
|
|
39
|
+
}
|
|
40
|
+
Math.random2(2000, 5000)::ms => now;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// === PULSE: rhythmic element, euclidean-inspired ===
|
|
45
|
+
fun int[] bjorklund(int k, int n) {
|
|
46
|
+
// Euclidean rhythm generator
|
|
47
|
+
int result[n];
|
|
48
|
+
if (k >= n) {
|
|
49
|
+
for (0 => int i; i < n; i++) 1 => result[i];
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
if (k == 0) {
|
|
53
|
+
for (0 => int i; i < n; i++) 0 => result[i];
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
int pattern[n];
|
|
58
|
+
float bucket;
|
|
59
|
+
0.0 => bucket;
|
|
60
|
+
k $ float / n => float slope;
|
|
61
|
+
for (0 => int i; i < n; i++) {
|
|
62
|
+
bucket + slope => bucket;
|
|
63
|
+
if (bucket >= 1.0) {
|
|
64
|
+
1 => pattern[i];
|
|
65
|
+
bucket - 1.0 => bucket;
|
|
66
|
+
} else {
|
|
67
|
+
0 => pattern[i];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return pattern;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun void pulse() {
|
|
74
|
+
SqrOsc sq => LPF filt => Gain pulseGain => master;
|
|
75
|
+
0.06 => pulseGain.gain;
|
|
76
|
+
2000.0 => filt.freq;
|
|
77
|
+
2.0 => filt.Q;
|
|
78
|
+
|
|
79
|
+
// Evolving euclidean parameters
|
|
80
|
+
3 => int k;
|
|
81
|
+
8 => int n;
|
|
82
|
+
|
|
83
|
+
[36, 38, 41, 43, 46] @=> int bassNotes[]; // low pentatonic
|
|
84
|
+
0 => int noteIdx;
|
|
85
|
+
|
|
86
|
+
0 => int cycleCount;
|
|
87
|
+
|
|
88
|
+
while (true) {
|
|
89
|
+
bjorklund(k, n) @=> int rhythm[];
|
|
90
|
+
|
|
91
|
+
for (0 => int i; i < rhythm.size(); i++) {
|
|
92
|
+
if (rhythm[i]) {
|
|
93
|
+
Std.mtof(bassNotes[noteIdx]) => sq.freq;
|
|
94
|
+
0.06 => pulseGain.gain;
|
|
95
|
+
|
|
96
|
+
// Quick decay
|
|
97
|
+
for (0 => int d; d < 10; d++) {
|
|
98
|
+
pulseGain.gain() * 0.85 => pulseGain.gain;
|
|
99
|
+
15::ms => now;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
0.0 => pulseGain.gain;
|
|
103
|
+
150::ms => now;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cycleCount++;
|
|
108
|
+
// Evolve every 4 cycles
|
|
109
|
+
if (cycleCount % 4 == 0) {
|
|
110
|
+
Math.random2(2, 5) => k;
|
|
111
|
+
Math.random2(7, 13) => n;
|
|
112
|
+
(noteIdx + Math.random2(1, 3)) % bassNotes.size() => noteIdx;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// === HIHAT: gentle tick pattern ===
|
|
118
|
+
fun void hihat() {
|
|
119
|
+
Noise noise => BPF bp => Gain hhGain => master;
|
|
120
|
+
8000.0 => bp.freq;
|
|
121
|
+
4.0 => bp.Q;
|
|
122
|
+
0.0 => hhGain.gain;
|
|
123
|
+
|
|
124
|
+
while (true) {
|
|
125
|
+
// Random density: sometimes busy, sometimes sparse
|
|
126
|
+
Math.random2(2, 6) => int density;
|
|
127
|
+
|
|
128
|
+
for (0 => int i; i < 16; i++) {
|
|
129
|
+
if (Math.random2(0, 8) < density) {
|
|
130
|
+
Math.random2f(0.02, 0.06) => hhGain.gain;
|
|
131
|
+
5::ms => now;
|
|
132
|
+
0.0 => hhGain.gain;
|
|
133
|
+
Math.random2(80, 200)::ms => now;
|
|
134
|
+
} else {
|
|
135
|
+
Math.random2(100, 250)::ms => now;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// === MELODY: sparse, floating notes ===
|
|
142
|
+
fun void melody() {
|
|
143
|
+
SinOsc mel => LPF filt => Gain melGain => master;
|
|
144
|
+
0.0 => melGain.gain;
|
|
145
|
+
3000.0 => filt.freq;
|
|
146
|
+
|
|
147
|
+
[60, 63, 65, 67, 70, 72, 75, 77] @=> int notes[]; // high pentatonic
|
|
148
|
+
|
|
149
|
+
while (true) {
|
|
150
|
+
// 40% chance of playing a note
|
|
151
|
+
if (Math.random2f(0.0, 1.0) < 0.4) {
|
|
152
|
+
Math.random2(0, notes.size()-1) => int idx;
|
|
153
|
+
Std.mtof(notes[idx]) => mel.freq;
|
|
154
|
+
|
|
155
|
+
// Gentle attack
|
|
156
|
+
for (0 => int a; a < 20; a++) {
|
|
157
|
+
(a / 20.0) * Math.random2f(0.04, 0.08) => melGain.gain;
|
|
158
|
+
8::ms => now;
|
|
159
|
+
}
|
|
160
|
+
// Hold
|
|
161
|
+
Math.random2(200, 600)::ms => now;
|
|
162
|
+
// Gentle release
|
|
163
|
+
melGain.gain() => float vol;
|
|
164
|
+
for (0 => int r; r < 30; r++) {
|
|
165
|
+
vol * (1.0 - r / 30.0) => melGain.gain;
|
|
166
|
+
10::ms => now;
|
|
167
|
+
}
|
|
168
|
+
0.0 => melGain.gain;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Math.random2(500, 2000)::ms => now;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// === LAUNCH ALL SHREDS ===
|
|
176
|
+
<<< " Starforge Coding Ambient", "" >>>;
|
|
177
|
+
<<< " Ctrl-C to stop", "" >>>;
|
|
178
|
+
<<< "" >>>;
|
|
179
|
+
|
|
180
|
+
spork ~ pad();
|
|
181
|
+
spork ~ pulse();
|
|
182
|
+
spork ~ hihat();
|
|
183
|
+
spork ~ melody();
|
|
184
|
+
|
|
185
|
+
// Keep main shred alive
|
|
186
|
+
while (true) 1::second => now;
|