@brett_lamy/docstream 1.11.0 → 1.12.0
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.
- package/dist/strudel/StrudelRack.d.ts.map +1 -1
- package/dist/strudel/StrudelRack.js +2 -2
- package/dist/strudel/StrudelRack.js.map +1 -1
- package/dist/strudel/controls.d.ts +2 -0
- package/dist/strudel/controls.d.ts.map +1 -1
- package/dist/strudel/controls.js +27 -21
- package/dist/strudel/controls.js.map +1 -1
- package/dist/styles.css +346 -118
- package/package.json +1 -1
- package/src/strudel/StrudelRack.tsx +13 -3
- package/src/strudel/controls.tsx +65 -28
- package/src/styles.css +346 -118
package/package.json
CHANGED
|
@@ -120,7 +120,8 @@ function Device({
|
|
|
120
120
|
return (
|
|
121
121
|
<section className="strudel-device" data-kind={track.kind} data-audible={audible} aria-label={name}>
|
|
122
122
|
<header className="strudel-device__head">
|
|
123
|
-
<
|
|
123
|
+
<span className="strudel-device__led" aria-hidden="true" />
|
|
124
|
+
<Icon size={14} aria-hidden="true" />
|
|
124
125
|
<span className="strudel-device__name" title={code.slice(track.start, track.end)}>
|
|
125
126
|
{name}
|
|
126
127
|
</span>
|
|
@@ -145,7 +146,7 @@ function Device({
|
|
|
145
146
|
/>
|
|
146
147
|
<div className="strudel-device__main">
|
|
147
148
|
{(synth || scaleMatch) && (
|
|
148
|
-
<div className="strudel-device__selects">
|
|
149
|
+
<div className="strudel-device__selects strudel-lcd">
|
|
149
150
|
{synth && (
|
|
150
151
|
<label>
|
|
151
152
|
<span>Sound</span>
|
|
@@ -268,8 +269,16 @@ export function StrudelRack({ code, onChange, onPlayableChange, className }: Str
|
|
|
268
269
|
return (
|
|
269
270
|
<div className={`strudel-rack ${className ?? ""}`.trim()}>
|
|
270
271
|
<div className="strudel-rack__master" role="group" aria-label="Master">
|
|
271
|
-
<span className="strudel-rack__title">
|
|
272
|
+
<span className="strudel-rack__title">
|
|
273
|
+
Master
|
|
274
|
+
<small>Strudel</small>
|
|
275
|
+
</span>
|
|
276
|
+
<output className="strudel-lcd strudel-lcd--bpm" aria-label="Tempo">
|
|
277
|
+
{Math.round((cps && Number.isFinite(cps) ? cps : 0.5) * 2400) / 10}
|
|
278
|
+
<small>BPM</small>
|
|
279
|
+
</output>
|
|
272
280
|
<Knob
|
|
281
|
+
size="lg"
|
|
273
282
|
label="Tempo"
|
|
274
283
|
value={cps && Number.isFinite(cps) ? cps : 0.5}
|
|
275
284
|
min={0.1}
|
|
@@ -280,6 +289,7 @@ export function StrudelRack({ code, onChange, onPlayableChange, className }: Str
|
|
|
280
289
|
/>
|
|
281
290
|
{master && (
|
|
282
291
|
<Knob
|
|
292
|
+
size="lg"
|
|
283
293
|
label="Output"
|
|
284
294
|
value={postgain ?? 1}
|
|
285
295
|
min={0}
|
package/src/strudel/controls.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/* The rack's hardware: a rotary knob
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* The rack's hardware: a rotary knob (rubber skirt, machined cap, LED ring) and a channel fader (slotted
|
|
2
|
+
track, tick scale, chunky cap). Both are ARIA sliders — drag (Shift for fine), arrow keys, Page Up/Down,
|
|
3
|
+
Home/End — and double-click resets to the default. Values map logarithmically when `log`. */
|
|
4
|
+
import { useId, useRef, type KeyboardEvent, type PointerEvent } from "react"
|
|
4
5
|
|
|
5
6
|
export interface ControlProps {
|
|
6
7
|
label: string
|
|
@@ -16,6 +17,8 @@ export interface ControlProps {
|
|
|
16
17
|
/** Shown instead of the value, e.g. "∿" for a modulated parameter. */
|
|
17
18
|
display?: string
|
|
18
19
|
title?: string
|
|
20
|
+
/** Knob only: "lg" for master controls. */
|
|
21
|
+
size?: "md" | "lg"
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))
|
|
@@ -38,20 +41,19 @@ export function defaultFormat(v: number): string {
|
|
|
38
41
|
return v.toFixed(2)
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function useDrag(props: ControlProps,
|
|
44
|
+
function useDrag(props: ControlProps, travel: number) {
|
|
42
45
|
const start = useRef<{ pos: number; unit: number } | null>(null)
|
|
43
46
|
const set = (unit: number) => props.onChange(fromUnit(unit, props))
|
|
44
47
|
const onPointerDown = (e: PointerEvent<HTMLElement>) => {
|
|
45
48
|
if (props.disabled) return
|
|
46
49
|
e.currentTarget.setPointerCapture(e.pointerId)
|
|
47
50
|
e.currentTarget.focus()
|
|
48
|
-
start.current = { pos:
|
|
51
|
+
start.current = { pos: e.clientY, unit: toUnit(props.value, props) }
|
|
49
52
|
}
|
|
50
53
|
const onPointerMove = (e: PointerEvent<HTMLElement>) => {
|
|
51
54
|
const s = start.current
|
|
52
55
|
if (!s) return
|
|
53
|
-
|
|
54
|
-
set(s.unit + (delta / travel) * (e.shiftKey ? 0.2 : 1))
|
|
56
|
+
set(s.unit + ((s.pos - e.clientY) / travel) * (e.shiftKey ? 0.2 : 1))
|
|
55
57
|
}
|
|
56
58
|
const onPointerUp = () => {
|
|
57
59
|
start.current = null
|
|
@@ -95,31 +97,57 @@ function aria(props: ControlProps, format: (v: number) => string) {
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
const ARC = 270
|
|
98
|
-
const
|
|
100
|
+
const LEDS = 15
|
|
101
|
+
const polar = (deg: number, r: number, c = 30) => {
|
|
99
102
|
const a = ((deg - 90) * Math.PI) / 180
|
|
100
|
-
return [
|
|
101
|
-
}
|
|
102
|
-
const arc = (from: number, to: number, r: number) => {
|
|
103
|
-
const [x1, y1] = polar(from, r)
|
|
104
|
-
const [x2, y2] = polar(to, r)
|
|
105
|
-
return `M ${x1} ${y1} A ${r} ${r} 0 ${to - from > 180 ? 1 : 0} 1 ${x2} ${y2}`
|
|
103
|
+
return [c + r * Math.cos(a), c + r * Math.sin(a)] as const
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
export function Knob(props: ControlProps) {
|
|
107
|
+
const id = useId().replace(/:/g, "")
|
|
109
108
|
const format = props.format ?? defaultFormat
|
|
110
|
-
const handlers = useDrag(props,
|
|
109
|
+
const handlers = useDrag(props, 160)
|
|
111
110
|
const u = toUnit(props.value, props)
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const
|
|
111
|
+
const angle = -ARC / 2 + ARC * u
|
|
112
|
+
// A bipolar parameter (pan, speed) lights from the centre; the rest light from the left.
|
|
113
|
+
const bipolar = props.min < 0 || props.label === "Pan"
|
|
114
|
+
const zero = bipolar ? toUnit(props.min < 0 ? 0 : (props.min + props.max) / 2, props) : 0
|
|
115
|
+
const lit = (i: number) => {
|
|
116
|
+
const t = i / (LEDS - 1)
|
|
117
|
+
return !props.disabled && (bipolar ? (t >= Math.min(zero, u) - 0.001 && t <= Math.max(zero, u) + 0.001) : t <= u + 0.001)
|
|
118
|
+
}
|
|
115
119
|
return (
|
|
116
|
-
<div className="strudel-knob" data-disabled={props.disabled || undefined}>
|
|
120
|
+
<div className="strudel-knob" data-size={props.size ?? "md"} data-disabled={props.disabled || undefined}>
|
|
117
121
|
<div className="strudel-knob__dial" {...aria(props, format)} {...handlers}>
|
|
118
|
-
<svg viewBox="0 0
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
<svg viewBox="0 0 60 60" aria-hidden="true">
|
|
123
|
+
<defs>
|
|
124
|
+
<radialGradient id={`${id}-skirt`} cx="50%" cy="35%" r="70%">
|
|
125
|
+
<stop offset="0" stopColor="#4a4d55" />
|
|
126
|
+
<stop offset=".6" stopColor="#1d1f24" />
|
|
127
|
+
<stop offset="1" stopColor="#0b0c0e" />
|
|
128
|
+
</radialGradient>
|
|
129
|
+
<linearGradient id={`${id}-cap`} x1="0" y1="0" x2="0" y2="1">
|
|
130
|
+
<stop offset="0" stopColor="#f2f3f5" />
|
|
131
|
+
<stop offset=".45" stopColor="#a9adb5" />
|
|
132
|
+
<stop offset=".55" stopColor="#8a8f98" />
|
|
133
|
+
<stop offset="1" stopColor="#d7dae0" />
|
|
134
|
+
</linearGradient>
|
|
135
|
+
</defs>
|
|
136
|
+
{Array.from({ length: LEDS }, (_, i) => {
|
|
137
|
+
const [x, y] = polar(-ARC / 2 + (ARC * i) / (LEDS - 1), 27)
|
|
138
|
+
return <circle key={i} cx={x} cy={y} r="1.7" className="strudel-knob__led" data-on={lit(i) || undefined} />
|
|
139
|
+
})}
|
|
140
|
+
<circle cx="30" cy="31.5" r="19.5" className="strudel-knob__shadow" />
|
|
141
|
+
<circle cx="30" cy="30" r="19" fill={`url(#${id}-skirt)`} className="strudel-knob__skirt" />
|
|
142
|
+
{/* Grip ridges on the skirt, then the machined cap and its indicator. */}
|
|
143
|
+
<g transform={`rotate(${angle} 30 30)`}>
|
|
144
|
+
{Array.from({ length: 24 }, (_, i) => (
|
|
145
|
+
<line key={i} x1="30" y1="11.6" x2="30" y2="14" transform={`rotate(${i * 15} 30 30)`} className="strudel-knob__ridge" />
|
|
146
|
+
))}
|
|
147
|
+
<circle cx="30" cy="30" r="12.5" fill={`url(#${id}-cap)`} className="strudel-knob__cap" />
|
|
148
|
+
<circle cx="30" cy="30" r="12.5" className="strudel-knob__brush" />
|
|
149
|
+
<line x1="30" y1="19.5" x2="30" y2="26" className="strudel-knob__pointer" />
|
|
150
|
+
</g>
|
|
123
151
|
</svg>
|
|
124
152
|
</div>
|
|
125
153
|
<span className="strudel-knob__value">{props.display ?? format(props.value)}</span>
|
|
@@ -128,16 +156,25 @@ export function Knob(props: ControlProps) {
|
|
|
128
156
|
)
|
|
129
157
|
}
|
|
130
158
|
|
|
159
|
+
const TICKS = [10, 8, 6, 4, 2, 0]
|
|
160
|
+
|
|
131
161
|
export function Fader(props: ControlProps) {
|
|
132
162
|
const format = props.format ?? defaultFormat
|
|
133
|
-
const handlers = useDrag(props,
|
|
163
|
+
const handlers = useDrag(props, 104)
|
|
134
164
|
const u = toUnit(props.value, props)
|
|
135
165
|
return (
|
|
136
166
|
<div className="strudel-fader" data-disabled={props.disabled || undefined}>
|
|
137
167
|
<span className="strudel-fader__value">{props.display ?? format(props.value)}</span>
|
|
138
|
-
<div className="strudel-
|
|
139
|
-
<div className="strudel-
|
|
140
|
-
|
|
168
|
+
<div className="strudel-fader__body">
|
|
169
|
+
<div className="strudel-fader__scale" aria-hidden="true">
|
|
170
|
+
{TICKS.map((t) => (
|
|
171
|
+
<span key={t}>{t}</span>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
<div className="strudel-fader__track" {...aria(props, format)} {...handlers} aria-orientation="vertical">
|
|
175
|
+
<div className="strudel-fader__slot" />
|
|
176
|
+
<div className="strudel-fader__cap" style={{ bottom: `calc(${u} * (100% - 22px))` }} />
|
|
177
|
+
</div>
|
|
141
178
|
</div>
|
|
142
179
|
<span className="strudel-fader__label">{props.label}</span>
|
|
143
180
|
</div>
|