desktop-team-doc 0.1.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/README.md +89 -0
- package/content/docs/README.md +227 -0
- package/content/docs/index.md +352 -0
- package/content/docs/instructions/coding-conventions/.clang-format +65 -0
- package/content/docs/instructions/coding-conventions/cpp.md +132 -0
- package/content/docs/instructions/coding-conventions/frontend.md +612 -0
- package/content/docs/instructions/coding-conventions/team-wide.md +176 -0
- package/content/docs/instructions/workflows/assets/jira-1.png +0 -0
- package/content/docs/instructions/workflows/assets/jira-comment.png +0 -0
- package/content/docs/instructions/workflows/assets/jira-release-note.png +0 -0
- package/content/docs/instructions/workflows/assets/jira-tag.png +0 -0
- package/content/docs/instructions/workflows/code-review.md +451 -0
- package/content/docs/instructions/workflows/git-branch-convention.md +246 -0
- package/content/docs/instructions/workflows/git-commit.md +95 -0
- package/content/docs/instructions/workflows/jira-process.md +173 -0
- package/content/docs/instructions/workflows/jira-ticket-guide.md +105 -0
- package/content/docs/instructions/workflows/pull-request-generation.md +319 -0
- package/content/docs/instructions/workflows/scrum-process.md +104 -0
- package/content/docs/instructions/workflows/survey-project-setup.md +76 -0
- package/content/docs/knowledge/architecture/README.md +11 -0
- package/content/docs/knowledge/architecture/audio-plugin-architecture.md +213 -0
- package/content/docs/knowledge/architecture/cross-platform-design.md +176 -0
- package/content/docs/knowledge/architecture/frontend-native-bridge.md +193 -0
- package/content/docs/knowledge/architecture/native-command.md +189 -0
- package/content/docs/knowledge/architecture/state-management-architecture.md +105 -0
- package/content/docs/knowledge/component-library/ControlComponent/README.md +281 -0
- package/content/docs/knowledge/component-library/ControlComponent/accessibility/accessibility-implementation.md +503 -0
- package/content/docs/knowledge/component-library/ControlComponent/common-mechanisms.md +278 -0
- package/content/docs/knowledge/component-library/ControlComponent/core/error-handling.md +451 -0
- package/content/docs/knowledge/component-library/ControlComponent/core/native-interface.md +515 -0
- package/content/docs/knowledge/component-library/ControlComponent/core/state-management.md +509 -0
- package/content/docs/knowledge/component-library/ControlComponent/creating-new-controls.md +654 -0
- package/content/docs/knowledge/component-library/ControlComponent/design/api-design-reference.md +1142 -0
- package/content/docs/knowledge/component-library/ControlComponent/design/design-principles.md +336 -0
- package/content/docs/knowledge/component-library/ControlComponent/design/styling-architecture.md +595 -0
- package/content/docs/knowledge/component-library/ControlComponent/design/visual-feedback.md +456 -0
- package/content/docs/knowledge/component-library/ControlComponent/development-environment.md +213 -0
- package/content/docs/knowledge/component-library/ControlComponent/interaction/gesture-algorithms.md +705 -0
- package/content/docs/knowledge/component-library/ControlComponent/interaction/touch-support.md +525 -0
- package/content/docs/knowledge/component-library/ControlComponent/interaction/value-processing-patterns.md +801 -0
- package/content/docs/knowledge/component-library/ControlComponent/interaction/velocity-damping-systems.md +741 -0
- package/content/docs/knowledge/component-library/ControlComponent/knob/architecture.md +490 -0
- package/content/docs/knowledge/component-library/ControlComponent/knob/how-to-use.md +304 -0
- package/content/docs/knowledge/component-library/ControlComponent/knob/index.md +105 -0
- package/content/docs/knowledge/component-library/ControlComponent/optimization/performance-benchmarks.md +535 -0
- package/content/docs/knowledge/component-library/ControlComponent/optimization/performance-optimization.md +1092 -0
- package/content/docs/knowledge/component-library/ControlComponent/quick-start.md +345 -0
- package/content/docs/knowledge/component-library/ControlComponent/slider/architecture.md +444 -0
- package/content/docs/knowledge/component-library/ControlComponent/slider/how-to-use.md +470 -0
- package/content/docs/knowledge/component-library/ControlComponent/slider/index.md +107 -0
- package/content/docs/knowledge/component-library/ControlComponent/testing-guide.md +950 -0
- package/content/docs/knowledge/component-library/ControlComponent/troubleshooting.md +657 -0
- package/content/docs/knowledge/component-library/frontend-develop/LICENSE.txt +176 -0
- package/content/docs/knowledge/component-library/frontend-develop/SKILL.md +124 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/code-organization.md +620 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/coding-standards.md +275 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/component-reusability.md +559 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/examples.md +554 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/layout-separation.md +638 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/performance-optimization.md +678 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/state-management.md +331 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/styling-guidelines.md +349 -0
- package/content/docs/knowledge/component-library/frontend-develop/references/type-safety.md +493 -0
- package/content/docs/knowledge/development/assets/cyberduck-aws-credentials.png +0 -0
- package/content/docs/knowledge/development/assets/postman-environment-setup.png +0 -0
- package/content/docs/knowledge/development/aws-storage.md +95 -0
- package/content/docs/knowledge/development/crm-system.md +22 -0
- package/content/docs/knowledge/development/glossary.md +246 -0
- package/content/docs/knowledge/development/pg-api-guide.md +71 -0
- package/content/docs/knowledge/development/staging-license-management.md +44 -0
- package/content/docs/knowledge/development/tech-stack.md +240 -0
- package/content/docs/knowledge/domain/popup-system.md +106 -0
- package/content/docs/knowledge/domain/sigpath.md +264 -0
- package/content/docs/knowledge/environment-setup/aax-signing-update.md +149 -0
- package/content/docs/knowledge/environment-setup/assets/aax-1.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-2.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-3.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-4.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-5.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-6.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/aax-7.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-1.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-10.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-11.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-12.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-13.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-14.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-2.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-3.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-4.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-5.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-6.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-7.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-8.png +0 -0
- package/content/docs/knowledge/environment-setup/assets/buildmachine-9.png +0 -0
- package/content/docs/knowledge/environment-setup/build-machine-setup.md +224 -0
- package/content/docs/knowledge/environment-setup/build-machine-troubleshooting.md +193 -0
- package/content/docs/knowledge/implementation-guides/adding-amp.md +190 -0
- package/content/docs/knowledge/implementation-guides/adding-fx.md +111 -0
- package/content/docs/knowledge/implementation-guides/cab-integration.md +194 -0
- package/content/docs/knowledge/implementation-guides/custom-pedal-integration.md +309 -0
- package/content/docs/knowledge/projects/BIAS_ONE_GUI/README.md +17 -0
- package/content/manifest.json +122 -0
- package/content/rules/cpp.mdc +135 -0
- package/content/rules/frontend.mdc +615 -0
- package/content/rules/index.mdc +256 -0
- package/content/rules/knowledge.mdc +46 -0
- package/content/rules/team-wide.mdc +179 -0
- package/content/rules/workflows.mdc +43 -0
- package/content/tools/agents/context-compressor.md +357 -0
- package/content/tools/agents/context-writer.md +328 -0
- package/content/tools/agents/release-notes-generator.md +389 -0
- package/content/tools/agents/srs-writer-agent.md +63 -0
- package/content/tools/mcp/README.md +25 -0
- package/content/tools/mcp/mcp-desktop-team.example.json +13 -0
- package/content/tools/skills/frontend-develop/LICENSE.txt +176 -0
- package/content/tools/skills/frontend-develop/SKILL.md +124 -0
- package/content/tools/skills/frontend-develop/references/code-organization.md +620 -0
- package/content/tools/skills/frontend-develop/references/coding-standards.md +275 -0
- package/content/tools/skills/frontend-develop/references/component-reusability.md +559 -0
- package/content/tools/skills/frontend-develop/references/examples.md +554 -0
- package/content/tools/skills/frontend-develop/references/layout-separation.md +638 -0
- package/content/tools/skills/frontend-develop/references/performance-optimization.md +678 -0
- package/content/tools/skills/frontend-develop/references/state-management.md +331 -0
- package/content/tools/skills/frontend-develop/references/styling-guidelines.md +349 -0
- package/content/tools/skills/frontend-develop/references/type-safety.md +493 -0
- package/content/tools/slash-commands/commit.md +17 -0
- package/content/tools/slash-commands/context-compress.md +149 -0
- package/content/tools/slash-commands/context-write.md +92 -0
- package/content/tools/slash-commands/jira.md +12 -0
- package/content/tools/slash-commands/pr-gen.md +12 -0
- package/content/tools/slash-commands/pr-review.md +12 -0
- package/dist/commands/detect.d.ts +1 -0
- package/dist/commands/detect.js +33 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +100 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -0
- package/dist/lib/detect-env.d.ts +3 -0
- package/dist/lib/detect-env.js +52 -0
- package/dist/lib/prompt-env.d.ts +3 -0
- package/dist/lib/prompt-env.js +16 -0
- package/dist/lib/resolve-doc-repo.d.ts +14 -0
- package/dist/lib/resolve-doc-repo.js +61 -0
- package/dist/lib/symlink.d.ts +7 -0
- package/dist/lib/symlink.js +60 -0
- package/dist/lib/sync-from-manifest.d.ts +8 -0
- package/dist/lib/sync-from-manifest.js +64 -0
- package/package.json +46 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
# 速度感應與阻尼系統
|
|
2
|
+
|
|
3
|
+
本文檔詳細說明音訊控制項中的速度感應 (Velocity Sensitivity) 和阻尼效果 (Damping) 系統,這兩種機制是專業音訊軟體的核心特性。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Damping vs Velocity 的本質區別
|
|
8
|
+
|
|
9
|
+
### 概念區分
|
|
10
|
+
|
|
11
|
+
這兩種功能滿足使用者在不同情境下的需求:
|
|
12
|
+
|
|
13
|
+
| 特性 | 阻尼效果 (Damping) | 速度感應 (Velocity) |
|
|
14
|
+
|------|-------------------|-------------------|
|
|
15
|
+
| **觸發方式** | 修飾鍵 (Shift/Ctrl) | 滑鼠移動速度 |
|
|
16
|
+
| **效果** | 固定降低靈敏度 | 動態調整靈敏度 |
|
|
17
|
+
| **使用情境** | 刻意的精細微調 | 快速粗調 + 慢速精調 |
|
|
18
|
+
| **範例** | 調整 EQ 增益、濾波器共振 | 掃過合成器頻率範圍 |
|
|
19
|
+
|
|
20
|
+
### JUCE 論壇的關鍵洞察
|
|
21
|
+
|
|
22
|
+
> "在速度感應模式下,拖動滑桿的精確度取決於你滑鼠移動的速度...但在精細模式下,滑桿只是單純地變得更慢。"
|
|
23
|
+
> — JUCE 開發者論壇
|
|
24
|
+
|
|
25
|
+
### 正交設計原則
|
|
26
|
+
|
|
27
|
+
這兩種功能是**正交的**,可以獨立使用或同時作用:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// 最終變化量公式
|
|
31
|
+
finalChange = baseChange * dampingMultiplier * velocityMultiplier
|
|
32
|
+
|
|
33
|
+
// 當使用者按住 Shift 並快速移動時:
|
|
34
|
+
// - damping 先降低基礎變化量
|
|
35
|
+
// - velocity 再根據速度放大結果
|
|
36
|
+
// 結果:快速移動時有適度放大,但基底已被阻尼降低
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. 阻尼效果 (Damping) 系統
|
|
42
|
+
|
|
43
|
+
### 修飾鍵配置策略
|
|
44
|
+
|
|
45
|
+
不同平台有不同的慣例,應該允許配置:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
/**
|
|
49
|
+
* 阻尼配置
|
|
50
|
+
*/
|
|
51
|
+
interface DampingConfig {
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
key?: 'shift' | 'ctrl' | 'alt' | 'meta';
|
|
54
|
+
factor?: number; // 0.1 = 1/10 靈敏度
|
|
55
|
+
dynamic?: {
|
|
56
|
+
adaptToRange?: boolean; // 根據參數範圍自動調整
|
|
57
|
+
adaptToPrecision?: boolean; // 根據當前精確度調整
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 預設配置 (可覆蓋)
|
|
63
|
+
*/
|
|
64
|
+
const DEFAULT_DAMPING: DampingConfig = {
|
|
65
|
+
enabled: true,
|
|
66
|
+
key: 'shift', // 業界最常見
|
|
67
|
+
factor: 0.1, // 1/10 靈敏度
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### BIAS ONE 現有實作
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// BIAS ONE 現有的精確模式檢測 (來自 common-mechanisms.md)
|
|
75
|
+
const usePreciseMode = () => {
|
|
76
|
+
const agent = useAgent();
|
|
77
|
+
|
|
78
|
+
const isPreciseMode = useCallback((e: MouseEvent | WheelEvent) => {
|
|
79
|
+
return (agent.isMac && e.metaKey) || (agent.isWin && e.ctrlKey);
|
|
80
|
+
}, [agent]);
|
|
81
|
+
|
|
82
|
+
return isPreciseMode;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// 在 Slider 中的應用
|
|
86
|
+
const PRECISE_DRAG_SENSITIVITY = 0.2; // = factor 0.2
|
|
87
|
+
const DRAG_SENSITIVITY = 1;
|
|
88
|
+
|
|
89
|
+
const sensitivity = isPreciseMode(event)
|
|
90
|
+
? PRECISE_DRAG_SENSITIVITY
|
|
91
|
+
: DRAG_SENSITIVITY;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 建議改進:可配置的阻尼系統
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
/**
|
|
98
|
+
* 阻尼處理器
|
|
99
|
+
*/
|
|
100
|
+
class DampingProcessor {
|
|
101
|
+
private config: DampingConfig;
|
|
102
|
+
|
|
103
|
+
constructor(config: DampingConfig = DEFAULT_DAMPING) {
|
|
104
|
+
this.config = config;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 檢查是否啟用阻尼
|
|
109
|
+
*/
|
|
110
|
+
isDamped(event: PointerEvent | MouseEvent | WheelEvent): boolean {
|
|
111
|
+
if (!this.config.enabled || !this.config.key) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const keyMap: Record<string, keyof PointerEvent> = {
|
|
116
|
+
shift: 'shiftKey',
|
|
117
|
+
ctrl: 'ctrlKey',
|
|
118
|
+
alt: 'altKey',
|
|
119
|
+
meta: 'metaKey',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return Boolean(event[keyMap[this.config.key]]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 獲取阻尼乘數
|
|
127
|
+
*/
|
|
128
|
+
getMultiplier(event: PointerEvent | MouseEvent | WheelEvent): number {
|
|
129
|
+
return this.isDamped(event) ? (this.config.factor ?? 0.1) : 1.0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 應用阻尼到數值變化
|
|
134
|
+
*/
|
|
135
|
+
apply(delta: number, event: PointerEvent | MouseEvent | WheelEvent): number {
|
|
136
|
+
return delta * this.getMultiplier(event);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 3. 速度感應 (Velocity) 系統
|
|
144
|
+
|
|
145
|
+
### VelocityTracker 完整實作
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
/**
|
|
149
|
+
* 速度追蹤器配置
|
|
150
|
+
*/
|
|
151
|
+
interface VelocityTrackerConfig {
|
|
152
|
+
maxSamples?: number; // 最大樣本數,預設 10
|
|
153
|
+
timeWindow?: number; // 時間窗口 (ms),預設 100
|
|
154
|
+
smoothingFactor?: number; // 平滑因子,預設 0.3
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 速度樣本
|
|
159
|
+
*/
|
|
160
|
+
interface VelocitySample {
|
|
161
|
+
position: Point;
|
|
162
|
+
timestamp: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 高精度速度追蹤器
|
|
167
|
+
*/
|
|
168
|
+
class VelocityTracker {
|
|
169
|
+
private samples: VelocitySample[] = [];
|
|
170
|
+
private config: Required<VelocityTrackerConfig>;
|
|
171
|
+
|
|
172
|
+
constructor(config: VelocityTrackerConfig = {}) {
|
|
173
|
+
this.config = {
|
|
174
|
+
maxSamples: 10,
|
|
175
|
+
timeWindow: 100,
|
|
176
|
+
smoothingFactor: 0.3,
|
|
177
|
+
...config,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 添加位置樣本
|
|
183
|
+
*/
|
|
184
|
+
addSample(position: Point, timestamp: number = performance.now()): void {
|
|
185
|
+
this.samples.push({ position, timestamp });
|
|
186
|
+
|
|
187
|
+
// 清理過期樣本
|
|
188
|
+
this.cleanupSamples(timestamp);
|
|
189
|
+
|
|
190
|
+
// 限制樣本數量
|
|
191
|
+
while (this.samples.length > this.config.maxSamples) {
|
|
192
|
+
this.samples.shift();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 重置追蹤器
|
|
198
|
+
*/
|
|
199
|
+
reset(): void {
|
|
200
|
+
this.samples = [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 計算瞬時速度 (最近兩點)
|
|
205
|
+
* 單位:像素/毫秒
|
|
206
|
+
*/
|
|
207
|
+
getInstantVelocity(): number {
|
|
208
|
+
if (this.samples.length < 2) return 0;
|
|
209
|
+
|
|
210
|
+
const latest = this.samples[this.samples.length - 1];
|
|
211
|
+
const previous = this.samples[this.samples.length - 2];
|
|
212
|
+
|
|
213
|
+
return this.calculateVelocityBetween(previous, latest);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 計算平均速度 (時間窗口內)
|
|
218
|
+
*/
|
|
219
|
+
getAverageVelocity(): number {
|
|
220
|
+
if (this.samples.length < 2) return 0;
|
|
221
|
+
|
|
222
|
+
let totalDistance = 0;
|
|
223
|
+
|
|
224
|
+
for (let i = 1; i < this.samples.length; i++) {
|
|
225
|
+
const prev = this.samples[i - 1];
|
|
226
|
+
const curr = this.samples[i];
|
|
227
|
+
totalDistance += this.calculateDistance(prev.position, curr.position);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const timeSpan =
|
|
231
|
+
this.samples[this.samples.length - 1].timestamp -
|
|
232
|
+
this.samples[0].timestamp;
|
|
233
|
+
|
|
234
|
+
return timeSpan > 0 ? totalDistance / timeSpan : 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 計算平滑速度 (指數移動平均)
|
|
239
|
+
* 最推薦使用,能有效過濾雜訊
|
|
240
|
+
*/
|
|
241
|
+
getSmoothedVelocity(): number {
|
|
242
|
+
if (this.samples.length < 2) return 0;
|
|
243
|
+
|
|
244
|
+
let smoothedVelocity = 0;
|
|
245
|
+
const alpha = this.config.smoothingFactor;
|
|
246
|
+
|
|
247
|
+
for (let i = 1; i < this.samples.length; i++) {
|
|
248
|
+
const velocity = this.calculateVelocityBetween(
|
|
249
|
+
this.samples[i - 1],
|
|
250
|
+
this.samples[i]
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// 指數移動平均: EMA = α * current + (1-α) * previous
|
|
254
|
+
smoothedVelocity = alpha * velocity + (1 - alpha) * smoothedVelocity;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return smoothedVelocity;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 計算加速度 (速度變化率)
|
|
262
|
+
*/
|
|
263
|
+
getAcceleration(): number {
|
|
264
|
+
if (this.samples.length < 3) return 0;
|
|
265
|
+
|
|
266
|
+
const velocities: number[] = [];
|
|
267
|
+
for (let i = 1; i < this.samples.length; i++) {
|
|
268
|
+
velocities.push(
|
|
269
|
+
this.calculateVelocityBetween(this.samples[i - 1], this.samples[i])
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (velocities.length < 2) return 0;
|
|
274
|
+
|
|
275
|
+
const latestVelocity = velocities[velocities.length - 1];
|
|
276
|
+
const previousVelocity = velocities[velocities.length - 2];
|
|
277
|
+
const timeDelta =
|
|
278
|
+
this.samples[this.samples.length - 1].timestamp -
|
|
279
|
+
this.samples[this.samples.length - 2].timestamp;
|
|
280
|
+
|
|
281
|
+
return timeDelta > 0 ? (latestVelocity - previousVelocity) / timeDelta : 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 獲取方向性速度 (用於判斷移動方向)
|
|
286
|
+
*/
|
|
287
|
+
getDirectionalVelocity(): Point {
|
|
288
|
+
if (this.samples.length < 2) return { x: 0, y: 0 };
|
|
289
|
+
|
|
290
|
+
const latest = this.samples[this.samples.length - 1];
|
|
291
|
+
const previous = this.samples[this.samples.length - 2];
|
|
292
|
+
const timeDelta = latest.timestamp - previous.timestamp;
|
|
293
|
+
|
|
294
|
+
if (timeDelta <= 0) return { x: 0, y: 0 };
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
x: (latest.position.x - previous.position.x) / timeDelta,
|
|
298
|
+
y: (latest.position.y - previous.position.y) / timeDelta,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private calculateVelocityBetween(
|
|
303
|
+
sample1: VelocitySample,
|
|
304
|
+
sample2: VelocitySample
|
|
305
|
+
): number {
|
|
306
|
+
const distance = this.calculateDistance(sample1.position, sample2.position);
|
|
307
|
+
const timeDelta = sample2.timestamp - sample1.timestamp;
|
|
308
|
+
|
|
309
|
+
return timeDelta > 0 ? distance / timeDelta : 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private calculateDistance(p1: Point, p2: Point): number {
|
|
313
|
+
const dx = p2.x - p1.x;
|
|
314
|
+
const dy = p2.y - p1.y;
|
|
315
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private cleanupSamples(currentTime: number): void {
|
|
319
|
+
const cutoffTime = currentTime - this.config.timeWindow;
|
|
320
|
+
this.samples = this.samples.filter(
|
|
321
|
+
(sample) => sample.timestamp >= cutoffTime
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 4. VelocityResponseCalculator 響應曲線
|
|
330
|
+
|
|
331
|
+
### 多種響應曲線實作
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
/**
|
|
335
|
+
* 速度配置
|
|
336
|
+
*/
|
|
337
|
+
interface VelocityConfig {
|
|
338
|
+
enabled?: boolean;
|
|
339
|
+
sensitivity?: number; // 速度影響強度
|
|
340
|
+
threshold?: number; // 啟動閾值
|
|
341
|
+
maxMultiplier?: number; // 最大乘數上限
|
|
342
|
+
curve?: 'linear' | 'exponential' | 'logarithmic' | 'sigmoid' | 'custom';
|
|
343
|
+
exponent?: number; // exponential 曲線的指數
|
|
344
|
+
steepness?: number; // sigmoid 曲線的陡度
|
|
345
|
+
controlPoints?: ControlPoint[]; // custom 曲線的控制點
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 控制點 (用於自訂曲線)
|
|
350
|
+
*/
|
|
351
|
+
interface ControlPoint {
|
|
352
|
+
x: number; // 0-1 範圍的速度比
|
|
353
|
+
y: number; // 對應的乘數
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 速度響應曲線計算器
|
|
358
|
+
*/
|
|
359
|
+
class VelocityResponseCalculator {
|
|
360
|
+
/**
|
|
361
|
+
* 計算速度乘數
|
|
362
|
+
* @param velocity 當前速度 (像素/毫秒)
|
|
363
|
+
* @param config 速度配置
|
|
364
|
+
* @returns 乘數 (1.0 = 無影響)
|
|
365
|
+
*/
|
|
366
|
+
static calculateMultiplier(velocity: number, config: VelocityConfig): number {
|
|
367
|
+
if (!config.enabled) {
|
|
368
|
+
return 1.0;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const threshold = config.threshold ?? 0.5;
|
|
372
|
+
|
|
373
|
+
// 低於閾值時不啟用
|
|
374
|
+
if (velocity < threshold) {
|
|
375
|
+
return 1.0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 正規化速度
|
|
379
|
+
const normalizedVelocity = velocity - threshold;
|
|
380
|
+
const maxVelocity = 10.0; // 預設最大速度參考值
|
|
381
|
+
const velocityRatio = Math.min(1, normalizedVelocity / maxVelocity);
|
|
382
|
+
|
|
383
|
+
let multiplier: number;
|
|
384
|
+
|
|
385
|
+
switch (config.curve) {
|
|
386
|
+
case 'linear':
|
|
387
|
+
multiplier = this.calculateLinearResponse(velocityRatio, config);
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case 'exponential':
|
|
391
|
+
multiplier = this.calculateExponentialResponse(velocityRatio, config);
|
|
392
|
+
break;
|
|
393
|
+
|
|
394
|
+
case 'logarithmic':
|
|
395
|
+
multiplier = this.calculateLogarithmicResponse(velocityRatio, config);
|
|
396
|
+
break;
|
|
397
|
+
|
|
398
|
+
case 'sigmoid':
|
|
399
|
+
multiplier = this.calculateSigmoidResponse(velocityRatio, config);
|
|
400
|
+
break;
|
|
401
|
+
|
|
402
|
+
case 'custom':
|
|
403
|
+
multiplier = this.calculateCustomResponse(velocityRatio, config);
|
|
404
|
+
break;
|
|
405
|
+
|
|
406
|
+
default:
|
|
407
|
+
multiplier = 1.0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 限制在合理範圍內
|
|
411
|
+
const minMultiplier = 0.1;
|
|
412
|
+
const maxMultiplier = config.maxMultiplier ?? 10.0;
|
|
413
|
+
|
|
414
|
+
return Math.max(minMultiplier, Math.min(maxMultiplier, multiplier));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* 線性響應曲線
|
|
419
|
+
* 速度與乘數成正比
|
|
420
|
+
*/
|
|
421
|
+
private static calculateLinearResponse(
|
|
422
|
+
ratio: number,
|
|
423
|
+
config: VelocityConfig
|
|
424
|
+
): number {
|
|
425
|
+
const sensitivity = config.sensitivity ?? 1;
|
|
426
|
+
return 1 + ratio * sensitivity;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 指數響應曲線
|
|
431
|
+
* 高速時乘數快速增加
|
|
432
|
+
*/
|
|
433
|
+
private static calculateExponentialResponse(
|
|
434
|
+
ratio: number,
|
|
435
|
+
config: VelocityConfig
|
|
436
|
+
): number {
|
|
437
|
+
const exponent = config.exponent ?? 2;
|
|
438
|
+
const sensitivity = config.sensitivity ?? 1;
|
|
439
|
+
return 1 + Math.pow(ratio, exponent) * sensitivity;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 對數響應曲線
|
|
444
|
+
* 低速時變化明顯,高速時趨緩
|
|
445
|
+
*/
|
|
446
|
+
private static calculateLogarithmicResponse(
|
|
447
|
+
ratio: number,
|
|
448
|
+
config: VelocityConfig
|
|
449
|
+
): number {
|
|
450
|
+
const sensitivity = config.sensitivity ?? 1;
|
|
451
|
+
// log(1 + x*(e-1)) 確保 x=0 時 y=0,x=1 時 y=1
|
|
452
|
+
return 1 + Math.log(1 + ratio * (Math.E - 1)) * sensitivity;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* S 型曲線 (Sigmoid)
|
|
457
|
+
* 中速區間變化最明顯
|
|
458
|
+
*/
|
|
459
|
+
private static calculateSigmoidResponse(
|
|
460
|
+
ratio: number,
|
|
461
|
+
config: VelocityConfig
|
|
462
|
+
): number {
|
|
463
|
+
const steepness = config.steepness ?? 5;
|
|
464
|
+
const sensitivity = config.sensitivity ?? 1;
|
|
465
|
+
|
|
466
|
+
// Sigmoid: 1 / (1 + e^(-k*(x-0.5)))
|
|
467
|
+
const sigmoid = 1 / (1 + Math.exp(-steepness * (ratio - 0.5)));
|
|
468
|
+
return 1 + sigmoid * sensitivity;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* 自訂曲線 (透過控制點插值)
|
|
473
|
+
*/
|
|
474
|
+
private static calculateCustomResponse(
|
|
475
|
+
ratio: number,
|
|
476
|
+
config: VelocityConfig
|
|
477
|
+
): number {
|
|
478
|
+
const controlPoints = config.controlPoints ?? [
|
|
479
|
+
{ x: 0, y: 1 },
|
|
480
|
+
{ x: 1, y: 1 + (config.sensitivity ?? 1) },
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
return this.interpolateControlPoints(ratio, controlPoints);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* 控制點線性插值
|
|
488
|
+
*/
|
|
489
|
+
private static interpolateControlPoints(
|
|
490
|
+
x: number,
|
|
491
|
+
points: ControlPoint[]
|
|
492
|
+
): number {
|
|
493
|
+
if (points.length === 0) return 1;
|
|
494
|
+
if (points.length === 1) return points[0].y;
|
|
495
|
+
|
|
496
|
+
// 排序控制點
|
|
497
|
+
const sortedPoints = [...points].sort((a, b) => a.x - b.x);
|
|
498
|
+
|
|
499
|
+
// 找到 x 所在的區間
|
|
500
|
+
for (let i = 0; i < sortedPoints.length - 1; i++) {
|
|
501
|
+
const p1 = sortedPoints[i];
|
|
502
|
+
const p2 = sortedPoints[i + 1];
|
|
503
|
+
|
|
504
|
+
if (x >= p1.x && x <= p2.x) {
|
|
505
|
+
// 線性插值
|
|
506
|
+
const t = (x - p1.x) / (p2.x - p1.x);
|
|
507
|
+
return p1.y + t * (p2.y - p1.y);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// 超出範圍,返回邊界值
|
|
512
|
+
if (x < sortedPoints[0].x) {
|
|
513
|
+
return sortedPoints[0].y;
|
|
514
|
+
}
|
|
515
|
+
return sortedPoints[sortedPoints.length - 1].y;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### 響應曲線視覺化參考
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
乘數
|
|
524
|
+
10 | ______ exponential
|
|
525
|
+
| ____/
|
|
526
|
+
5 | ____/
|
|
527
|
+
| ____/ ______ linear
|
|
528
|
+
| ____/ ____/
|
|
529
|
+
| ____/____/_______ logarithmic
|
|
530
|
+
1 |__________/____/
|
|
531
|
+
|_______________________________________
|
|
532
|
+
0 速度
|
|
533
|
+
threshold
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## 5. BIAS ONE 現有實作對照
|
|
539
|
+
|
|
540
|
+
### 現有常數
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
// BIAS ONE Slider 常數
|
|
544
|
+
const DRAG_SENSITIVITY = 1;
|
|
545
|
+
const PRECISE_DRAG_SENSITIVITY = 0.2; // = damping factor 0.2
|
|
546
|
+
|
|
547
|
+
// BIAS ONE Knob 常數
|
|
548
|
+
const WHEEL_DEFAULT_SENSITIVITY = 5000;
|
|
549
|
+
const WHEEL_SENSITIVITY_DIVISOR = 15000;
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### 現有精確模式實作
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// BIAS ONE 精確模式 Hook
|
|
556
|
+
const usePreciseMode = () => {
|
|
557
|
+
const agent = useAgent();
|
|
558
|
+
|
|
559
|
+
return useCallback((e: MouseEvent | WheelEvent) => {
|
|
560
|
+
return (agent.isMac && e.metaKey) || (agent.isWin && e.ctrlKey);
|
|
561
|
+
}, [agent]);
|
|
562
|
+
};
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### 建議整合方案
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
/**
|
|
569
|
+
* 整合的速度-阻尼處理器
|
|
570
|
+
* 統一處理 BIAS ONE 現有邏輯和新功能
|
|
571
|
+
*/
|
|
572
|
+
class UnifiedSensitivityProcessor {
|
|
573
|
+
private dampingProcessor: DampingProcessor;
|
|
574
|
+
private velocityTracker: VelocityTracker;
|
|
575
|
+
private velocityConfig: VelocityConfig;
|
|
576
|
+
|
|
577
|
+
constructor(
|
|
578
|
+
dampingConfig: DampingConfig,
|
|
579
|
+
velocityConfig: VelocityConfig
|
|
580
|
+
) {
|
|
581
|
+
this.dampingProcessor = new DampingProcessor(dampingConfig);
|
|
582
|
+
this.velocityTracker = new VelocityTracker();
|
|
583
|
+
this.velocityConfig = velocityConfig;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* 處理指標移動
|
|
588
|
+
*/
|
|
589
|
+
processPointerMove(
|
|
590
|
+
event: PointerEvent,
|
|
591
|
+
baseDelta: number
|
|
592
|
+
): number {
|
|
593
|
+
// 更新速度追蹤
|
|
594
|
+
this.velocityTracker.addSample(
|
|
595
|
+
{ x: event.clientX, y: event.clientY },
|
|
596
|
+
event.timeStamp
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
let result = baseDelta;
|
|
600
|
+
|
|
601
|
+
// 1. 應用阻尼 (修飾鍵)
|
|
602
|
+
result = this.dampingProcessor.apply(result, event);
|
|
603
|
+
|
|
604
|
+
// 2. 應用速度感應
|
|
605
|
+
if (this.velocityConfig.enabled) {
|
|
606
|
+
const velocity = this.velocityTracker.getSmoothedVelocity();
|
|
607
|
+
const velocityMultiplier = VelocityResponseCalculator.calculateMultiplier(
|
|
608
|
+
velocity,
|
|
609
|
+
this.velocityConfig
|
|
610
|
+
);
|
|
611
|
+
result *= velocityMultiplier;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* 處理滾輪事件
|
|
619
|
+
*/
|
|
620
|
+
processWheel(event: WheelEvent, baseDelta: number): number {
|
|
621
|
+
// 滾輪通常只應用阻尼,不應用速度感應
|
|
622
|
+
return this.dampingProcessor.apply(baseDelta, event);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* 重置 (拖曳結束時)
|
|
627
|
+
*/
|
|
628
|
+
reset(): void {
|
|
629
|
+
this.velocityTracker.reset();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* 獲取當前狀態
|
|
634
|
+
*/
|
|
635
|
+
getState(): {
|
|
636
|
+
isDamped: boolean;
|
|
637
|
+
velocity: number;
|
|
638
|
+
velocityMultiplier: number;
|
|
639
|
+
} {
|
|
640
|
+
const velocity = this.velocityTracker.getSmoothedVelocity();
|
|
641
|
+
const velocityMultiplier = this.velocityConfig.enabled
|
|
642
|
+
? VelocityResponseCalculator.calculateMultiplier(
|
|
643
|
+
velocity,
|
|
644
|
+
this.velocityConfig
|
|
645
|
+
)
|
|
646
|
+
: 1.0;
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
isDamped: false, // 需要事件才能判斷
|
|
650
|
+
velocity,
|
|
651
|
+
velocityMultiplier,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 6. 使用指南
|
|
660
|
+
|
|
661
|
+
### 基礎使用
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
// 僅阻尼
|
|
665
|
+
const config = {
|
|
666
|
+
damping: { enabled: true, key: 'shift', factor: 0.1 },
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// 僅速度感應
|
|
670
|
+
const config = {
|
|
671
|
+
velocity: { enabled: true, curve: 'logarithmic', sensitivity: 2 },
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// 兩者組合
|
|
675
|
+
const config = {
|
|
676
|
+
damping: { enabled: true, key: 'shift', factor: 0.1 },
|
|
677
|
+
velocity: { enabled: true, curve: 'logarithmic', sensitivity: 2 },
|
|
678
|
+
};
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 不同參數類型的建議配置
|
|
682
|
+
|
|
683
|
+
| 參數類型 | 阻尼因子 | 速度曲線 | 說明 |
|
|
684
|
+
|---------|---------|---------|------|
|
|
685
|
+
| 頻率 (20-20kHz) | 0.05 | logarithmic | 大範圍,需要精細微調 |
|
|
686
|
+
| 增益 (dB) | 0.1 | linear | 適中範圍 |
|
|
687
|
+
| 百分比 (0-100%) | 0.2 | linear | 小範圍 |
|
|
688
|
+
| 時間 (ms) | 0.1 | exponential | 需要快速掃過大範圍 |
|
|
689
|
+
| Pan (-100 to 100) | 0.15 | linear | 需要精確定位中心 |
|
|
690
|
+
|
|
691
|
+
### 完整使用範例
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
// 在 useAudioControl 中使用
|
|
695
|
+
function useAudioControl(options: UseAudioControlOptions) {
|
|
696
|
+
const processor = useRef(
|
|
697
|
+
new UnifiedSensitivityProcessor(
|
|
698
|
+
options.damping ?? { enabled: true, key: 'shift', factor: 0.1 },
|
|
699
|
+
options.velocity ?? { enabled: false }
|
|
700
|
+
)
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const handlePointerMove = useCallback((e: React.PointerEvent) => {
|
|
704
|
+
const rawDelta = calculateRawDelta(e);
|
|
705
|
+
const processedDelta = processor.current.processPointerMove(
|
|
706
|
+
e.nativeEvent,
|
|
707
|
+
rawDelta
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
// 更新數值...
|
|
711
|
+
}, []);
|
|
712
|
+
|
|
713
|
+
// ...
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## 總結
|
|
720
|
+
|
|
721
|
+
### 核心公式
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// 完整的數值變化計算
|
|
725
|
+
finalChange = baseDelta * sensitivity * dampingMultiplier * velocityMultiplier
|
|
726
|
+
|
|
727
|
+
// 其中:
|
|
728
|
+
// - baseDelta: 從手勢轉換得到的原始變化量
|
|
729
|
+
// - sensitivity: 基礎靈敏度
|
|
730
|
+
// - dampingMultiplier: 阻尼乘數 (0.1-1.0)
|
|
731
|
+
// - velocityMultiplier: 速度乘數 (1.0-10.0)
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### 選擇指南
|
|
735
|
+
|
|
736
|
+
| 場景 | 建議配置 |
|
|
737
|
+
|------|---------|
|
|
738
|
+
| 一般參數調整 | 僅阻尼 (Shift + 0.1) |
|
|
739
|
+
| 頻率/大範圍參數 | 阻尼 + 速度感應 (對數曲線) |
|
|
740
|
+
| 專業混音環境 | 完整功能 + 可配置修飾鍵 |
|
|
741
|
+
| 入門級應用 | 僅阻尼 (簡單易懂) |
|