@viji-dev/sdk 1.0.0 → 1.0.2
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 +70 -63
- package/bin/viji.js +9 -29
- package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
- package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
- package/dist/assets/core-CiQx3w0t.js +12 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
- package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
- package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
- package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
- package/dist/assets/glsl-DMyvO4G4.js +1 -0
- package/dist/assets/index-BhFxsauQ.js +215 -0
- package/dist/assets/index-BqhVeA7U.css +1 -0
- package/dist/assets/index-T4TOjvD0.js +1 -0
- package/dist/assets/index-Wz9WqGqz.js +52 -0
- package/dist/assets/index-t24aGwla.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +31 -35
- package/src/cli/commands/build.js +50 -99
- package/src/cli/commands/create.js +49 -46
- package/src/cli/commands/dev.js +30 -97
- package/src/cli/server/dev-server.js +233 -0
- package/src/cli/server/scene-scanner.js +93 -0
- package/src/cli/server/vite-scene-plugin.d.ts +2 -0
- package/src/cli/server/vite-scene-plugin.js +134 -0
- package/src/cli/utils/cli-utils.js +29 -139
- package/src/cli/utils/scene-compiler.js +10 -17
- package/src/templates/scene-templates.js +85 -0
- package/.gitignore +0 -29
- package/eslint.config.js +0 -37
- package/postcss.config.js +0 -6
- package/scenes/audio-visualizer/main.js +0 -287
- package/scenes/core-demo/main.js +0 -532
- package/scenes/demo-scene/main.js +0 -619
- package/scenes/global.d.ts +0 -15
- package/scenes/particle-system/main.js +0 -349
- package/scenes/tsconfig.json +0 -12
- package/scenes/video-mirror/main.ts +0 -436
- package/src/App.css +0 -42
- package/src/App.tsx +0 -279
- package/src/cli/commands/init.js +0 -262
- package/src/components/SDKPage.tsx +0 -337
- package/src/components/core/CoreContainer.tsx +0 -126
- package/src/components/ui/DeviceSelectionList.tsx +0 -137
- package/src/components/ui/FPSCounter.tsx +0 -78
- package/src/components/ui/FileDropzonePanel.tsx +0 -120
- package/src/components/ui/FileListPanel.tsx +0 -285
- package/src/components/ui/InputExpansionPanel.tsx +0 -31
- package/src/components/ui/MediaPlayerControls.tsx +0 -191
- package/src/components/ui/MenuContainer.tsx +0 -71
- package/src/components/ui/ParametersMenu.tsx +0 -797
- package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
- package/src/components/ui/QuickInputControls.tsx +0 -542
- package/src/components/ui/SDKMenuSystem.tsx +0 -96
- package/src/components/ui/SettingsMenu.tsx +0 -346
- package/src/components/ui/SimpleInputControls.tsx +0 -137
- package/src/index.css +0 -68
- package/src/main.tsx +0 -10
- package/src/scenes-hmr.ts +0 -158
- package/src/services/project-filesystem.ts +0 -436
- package/src/stores/scene-player/index.ts +0 -3
- package/src/stores/scene-player/input-manager.store.ts +0 -1045
- package/src/stores/scene-player/scene-session.store.ts +0 -659
- package/src/styles/globals.css +0 -111
- package/src/templates/minimal-template.js +0 -11
- package/src/utils/debounce.js +0 -34
- package/src/vite-env.d.ts +0 -1
- package/tailwind.config.js +0 -18
- package/tsconfig.app.json +0 -27
- package/tsconfig.json +0 -27
- package/tsconfig.node.json +0 -27
- package/vite.config.ts +0 -54
- /package/{public → dist}/favicon.png +0 -0
|
@@ -149,16 +149,8 @@ export class SceneCompiler {
|
|
|
149
149
|
* Ensure a directory exists, create it if it doesn't
|
|
150
150
|
*/
|
|
151
151
|
async ensureDirectory(dirPath) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// For now, we'll use a simple check
|
|
155
|
-
if (!existsSync(dirPath)) {
|
|
156
|
-
// In a real implementation: await mkdir(dirPath, { recursive: true });
|
|
157
|
-
console.log(`📁 Directory would be created: ${dirPath}`);
|
|
158
|
-
}
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.warn(`Could not create directory: ${dirPath}`, error);
|
|
161
|
-
}
|
|
152
|
+
const { mkdir } = await import('fs/promises');
|
|
153
|
+
await mkdir(dirPath, { recursive: true });
|
|
162
154
|
}
|
|
163
155
|
|
|
164
156
|
async analyzeScene(code, filePath) {
|
|
@@ -183,12 +175,15 @@ export class SceneCompiler {
|
|
|
183
175
|
const exportMatches = [...code.matchAll(/export\s+(?:const|function|class|let|var)\s+(\w+)/g)];
|
|
184
176
|
analysis.exports = exportMatches.map(match => match[1]);
|
|
185
177
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
analysis.validation.hasRender =
|
|
179
|
+
analysis.exports.includes('render') ||
|
|
180
|
+
/function\s+render\s*\(/.test(code);
|
|
181
|
+
analysis.validation.hasInit =
|
|
182
|
+
analysis.exports.includes('init') ||
|
|
183
|
+
/function\s+(?:setup|init)\s*\(/.test(code);
|
|
184
|
+
|
|
190
185
|
if (!analysis.validation.hasRender) {
|
|
191
|
-
analysis.validation.issues.push('Missing required
|
|
186
|
+
analysis.validation.issues.push('Missing required function: render');
|
|
192
187
|
analysis.validation.isValid = false;
|
|
193
188
|
}
|
|
194
189
|
|
|
@@ -343,12 +338,10 @@ export class SceneCompiler {
|
|
|
343
338
|
|
|
344
339
|
// No compatibility wrapper; emit plain concatenated code
|
|
345
340
|
|
|
346
|
-
// Add bundle metadata
|
|
347
341
|
output += `\n// === Bundle Metadata ===\n`;
|
|
348
342
|
output += `const BUNDLE_METADATA = {\n`;
|
|
349
343
|
output += ` version: "1.0.0",\n`;
|
|
350
344
|
output += ` generatedAt: "${timestamp}",\n`;
|
|
351
|
-
output += ` parameters: SCENE_PARAMETERS,\n`;
|
|
352
345
|
output += ` exports: ${JSON.stringify(analysis.exports)},\n`;
|
|
353
346
|
output += ` hasAssets: ${bundle.assets.size > 0},\n`;
|
|
354
347
|
output += ` hasShaders: ${bundle.shaders.size > 0},\n`;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const TEMPLATES = {
|
|
2
|
+
native: `// @renderer native
|
|
3
|
+
|
|
4
|
+
const speed = viji.slider(1.0, { min: 0.1, max: 5.0, label: 'Speed' });
|
|
5
|
+
const baseColor = viji.color('#4488ff', { label: 'Color' });
|
|
6
|
+
|
|
7
|
+
function render(viji) {
|
|
8
|
+
const ctx = viji.useContext('2d');
|
|
9
|
+
const { width, height, time } = viji;
|
|
10
|
+
|
|
11
|
+
ctx.fillStyle = '#000000';
|
|
12
|
+
ctx.fillRect(0, 0, width, height);
|
|
13
|
+
|
|
14
|
+
const x = width / 2 + Math.cos(time * speed.value) * 100;
|
|
15
|
+
const y = height / 2 + Math.sin(time * speed.value) * 100;
|
|
16
|
+
|
|
17
|
+
ctx.beginPath();
|
|
18
|
+
ctx.arc(x, y, 40, 0, Math.PI * 2);
|
|
19
|
+
ctx.fillStyle = baseColor.value;
|
|
20
|
+
ctx.fill();
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
|
|
24
|
+
p5: `// @renderer p5
|
|
25
|
+
|
|
26
|
+
const speed = viji.slider(1.0, { min: 0.1, max: 5.0, label: 'Speed' });
|
|
27
|
+
const baseColor = viji.color('#4488ff', { label: 'Color' });
|
|
28
|
+
|
|
29
|
+
function render(viji, p5) {
|
|
30
|
+
p5.background(0);
|
|
31
|
+
|
|
32
|
+
const x = viji.width / 2 + p5.cos(viji.time * speed.value) * 100;
|
|
33
|
+
const y = viji.height / 2 + p5.sin(viji.time * speed.value) * 100;
|
|
34
|
+
|
|
35
|
+
p5.noStroke();
|
|
36
|
+
p5.fill(baseColor.value);
|
|
37
|
+
p5.ellipse(x, y, 80, 80);
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
|
|
41
|
+
shader: `// @renderer shader
|
|
42
|
+
precision highp float;
|
|
43
|
+
|
|
44
|
+
uniform vec2 u_resolution;
|
|
45
|
+
uniform float u_time;
|
|
46
|
+
uniform vec2 u_mouse;
|
|
47
|
+
|
|
48
|
+
void main() {
|
|
49
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
50
|
+
vec2 center = u_mouse / u_resolution;
|
|
51
|
+
|
|
52
|
+
float d = length(uv - center);
|
|
53
|
+
float ring = smoothstep(0.3, 0.29, d) - smoothstep(0.25, 0.24, d);
|
|
54
|
+
|
|
55
|
+
vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0.0, 2.0, 4.0));
|
|
56
|
+
col = mix(col * 0.2, col, ring);
|
|
57
|
+
|
|
58
|
+
gl_FragColor = vec4(col, 1.0);
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const GLOBAL_DTS_FILE = {
|
|
64
|
+
native: 'artist-global.d.ts',
|
|
65
|
+
p5: 'artist-global-p5.d.ts',
|
|
66
|
+
shader: null,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function getSceneTemplate(renderer = 'native') {
|
|
70
|
+
const template = TEMPLATES[renderer];
|
|
71
|
+
if (!template) {
|
|
72
|
+
throw new Error(`Unknown renderer: "${renderer}". Valid options: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
return template;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getGlobalDtsFileName(renderer = 'native') {
|
|
78
|
+
return GLOBAL_DTS_FILE[renderer] ?? GLOBAL_DTS_FILE.native;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getSceneFileExtension(renderer = 'native') {
|
|
82
|
+
return renderer === 'shader' ? '.glsl' : '.js';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const VALID_RENDERERS = Object.keys(TEMPLATES);
|
package/.gitignore
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
npm-debug.log*
|
|
5
|
-
yarn-debug.log*
|
|
6
|
-
yarn-error.log*
|
|
7
|
-
pnpm-debug.log*
|
|
8
|
-
lerna-debug.log*
|
|
9
|
-
|
|
10
|
-
node_modules
|
|
11
|
-
dist
|
|
12
|
-
dist-ssr
|
|
13
|
-
*.local
|
|
14
|
-
|
|
15
|
-
# Editor directories and files
|
|
16
|
-
.vscode/*
|
|
17
|
-
!.vscode/extensions.json
|
|
18
|
-
.idea
|
|
19
|
-
.DS_Store
|
|
20
|
-
*.suo
|
|
21
|
-
*.ntvs*
|
|
22
|
-
*.njsproj
|
|
23
|
-
*.sln
|
|
24
|
-
*.sw?
|
|
25
|
-
|
|
26
|
-
# Documentation for other project parts
|
|
27
|
-
/core_docs
|
|
28
|
-
/backend_docs
|
|
29
|
-
/sdk_docs
|
package/eslint.config.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import globals from 'globals'
|
|
3
|
-
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
-
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
-
import tseslint from 'typescript-eslint'
|
|
6
|
-
import { globalIgnores } from 'eslint/config'
|
|
7
|
-
|
|
8
|
-
export default tseslint.config([
|
|
9
|
-
globalIgnores(['dist']),
|
|
10
|
-
{
|
|
11
|
-
files: ['scenes/**/*.{js,ts}'],
|
|
12
|
-
languageOptions: {
|
|
13
|
-
ecmaVersion: 2020,
|
|
14
|
-
globals: {
|
|
15
|
-
...globals.browser,
|
|
16
|
-
viji: 'readonly',
|
|
17
|
-
render: 'readonly',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
rules: {
|
|
21
|
-
'no-undef': 'off',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
files: ['**/*.{ts,tsx}'],
|
|
26
|
-
extends: [
|
|
27
|
-
js.configs.recommended,
|
|
28
|
-
tseslint.configs.recommended,
|
|
29
|
-
reactHooks.configs['recommended-latest'],
|
|
30
|
-
reactRefresh.configs.vite,
|
|
31
|
-
],
|
|
32
|
-
languageOptions: {
|
|
33
|
-
ecmaVersion: 2020,
|
|
34
|
-
globals: globals.browser,
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
])
|
package/postcss.config.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
// Audio Reactive Visualizer Scene - Parameter Object API
|
|
2
|
-
console.log('🎵 AUDIO VISUALIZER SCENE LOADED');
|
|
3
|
-
|
|
4
|
-
// Define parameters using helper functions (returns parameter objects)
|
|
5
|
-
const primaryColor = viji.color('#ff6b6b', {
|
|
6
|
-
label: 'Primary Color',
|
|
7
|
-
description: 'Main color for frequency bars',
|
|
8
|
-
group: 'colors'
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const secondaryColor = viji.color('#4ecdc4', {
|
|
12
|
-
label: 'Secondary Color',
|
|
13
|
-
description: 'Background and accent color',
|
|
14
|
-
group: 'colors'
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const backgroundColor = viji.color('#1a1a2e', {
|
|
18
|
-
label: 'Background Color',
|
|
19
|
-
description: 'Scene background color',
|
|
20
|
-
group: 'colors'
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const audioSensitivity = viji.slider(2.0, {
|
|
24
|
-
min: 0.1,
|
|
25
|
-
max: 5.0,
|
|
26
|
-
step: 0.1,
|
|
27
|
-
label: 'Audio Sensitivity',
|
|
28
|
-
description: 'Overall sensitivity to audio input',
|
|
29
|
-
group: 'audio',
|
|
30
|
-
category: 'audio'
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const bassBoost = viji.slider(1.5, {
|
|
34
|
-
min: 0.5,
|
|
35
|
-
max: 3.0,
|
|
36
|
-
step: 0.1,
|
|
37
|
-
label: 'Bass Boost',
|
|
38
|
-
description: 'Amplification for bass frequencies',
|
|
39
|
-
group: 'audio',
|
|
40
|
-
category: 'audio'
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const trebleBoost = viji.slider(1.2, {
|
|
44
|
-
min: 0.5,
|
|
45
|
-
max: 3.0,
|
|
46
|
-
step: 0.1,
|
|
47
|
-
label: 'Treble Boost',
|
|
48
|
-
description: 'Amplification for treble frequencies',
|
|
49
|
-
group: 'audio',
|
|
50
|
-
category: 'audio'
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const barCount = viji.slider(64, {
|
|
54
|
-
min: 8,
|
|
55
|
-
max: 128,
|
|
56
|
-
step: 8,
|
|
57
|
-
label: 'Bar Count',
|
|
58
|
-
description: 'Number of frequency analysis bars',
|
|
59
|
-
group: 'visual'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const barSpacing = viji.slider(2, {
|
|
63
|
-
min: 0,
|
|
64
|
-
max: 10,
|
|
65
|
-
step: 1,
|
|
66
|
-
label: 'Bar Spacing',
|
|
67
|
-
description: 'Space between frequency bars',
|
|
68
|
-
group: 'visual'
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const showWaveform = viji.toggle(true, {
|
|
72
|
-
label: 'Show Waveform',
|
|
73
|
-
description: 'Display audio waveform overlay',
|
|
74
|
-
group: 'visual'
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const showCenterCircle = viji.toggle(true, {
|
|
78
|
-
label: 'Show Center Circle',
|
|
79
|
-
description: 'Show reactive circle in center',
|
|
80
|
-
group: 'visual'
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const rotationSpeed = viji.slider(0.5, {
|
|
84
|
-
min: -2.0,
|
|
85
|
-
max: 2.0,
|
|
86
|
-
step: 0.1,
|
|
87
|
-
label: 'Rotation Speed',
|
|
88
|
-
description: 'Speed of circular rotation',
|
|
89
|
-
group: 'animation'
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const pulseIntensity = viji.slider(0.8, {
|
|
93
|
-
min: 0.0,
|
|
94
|
-
max: 2.0,
|
|
95
|
-
step: 0.1,
|
|
96
|
-
label: 'Pulse Intensity',
|
|
97
|
-
description: 'Intensity of audio-driven pulsing',
|
|
98
|
-
group: 'animation'
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const smoothing = viji.slider(0.7, {
|
|
102
|
-
min: 0.0,
|
|
103
|
-
max: 0.95,
|
|
104
|
-
step: 0.05,
|
|
105
|
-
label: 'Smoothing',
|
|
106
|
-
description: 'Temporal smoothing of audio data',
|
|
107
|
-
group: 'animation'
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const mouseInfluence = viji.toggle(true, {
|
|
111
|
-
label: 'Mouse Influence',
|
|
112
|
-
description: 'Allow mouse to affect visualization',
|
|
113
|
-
group: 'interaction',
|
|
114
|
-
category: 'interaction'
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const mouseRadius = viji.slider(100, {
|
|
118
|
-
min: 50,
|
|
119
|
-
max: 300,
|
|
120
|
-
step: 10,
|
|
121
|
-
label: 'Mouse Radius',
|
|
122
|
-
description: 'Radius of mouse influence',
|
|
123
|
-
group: 'interaction',
|
|
124
|
-
category: 'interaction'
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Render function using parameter object API with interactions, audio, and video
|
|
128
|
-
function render(viji) {
|
|
129
|
-
const ctx = viji.useContext('2d');
|
|
130
|
-
|
|
131
|
-
// Get interaction state
|
|
132
|
-
const mouse = viji.mouse;
|
|
133
|
-
const keyboard = viji.keyboard;
|
|
134
|
-
const touches = viji.touches;
|
|
135
|
-
|
|
136
|
-
// Get audio state
|
|
137
|
-
const audio = viji.audio;
|
|
138
|
-
|
|
139
|
-
// Get video state
|
|
140
|
-
const video = viji.video;
|
|
141
|
-
|
|
142
|
-
// Clear background
|
|
143
|
-
ctx.fillStyle = backgroundColor.value;
|
|
144
|
-
ctx.fillRect(0, 0, viji.width, viji.height);
|
|
145
|
-
|
|
146
|
-
// Only proceed if audio is connected
|
|
147
|
-
if (!audio || !audio.isConnected) {
|
|
148
|
-
// Show instructions when no audio
|
|
149
|
-
ctx.fillStyle = primaryColor.value;
|
|
150
|
-
ctx.font = '24px Arial';
|
|
151
|
-
ctx.textAlign = 'center';
|
|
152
|
-
ctx.fillText('🎵 Connect audio to see visualization', viji.width / 2, viji.height / 2);
|
|
153
|
-
|
|
154
|
-
ctx.font = '16px Arial';
|
|
155
|
-
ctx.fillStyle = secondaryColor.value;
|
|
156
|
-
ctx.fillText('Use the audio controls to connect microphone, file, or screen audio', viji.width / 2, viji.height / 2 + 40);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const centerX = viji.width / 2;
|
|
161
|
-
const centerY = viji.height / 2;
|
|
162
|
-
|
|
163
|
-
// Get audio data with parameters
|
|
164
|
-
const currentVolume = (((audio && audio.volume && audio.volume.rms) || 0)) * audioSensitivity.value;
|
|
165
|
-
const bassEnergy = ((((audio && audio.bands && audio.bands.bass) || 0) + ((audio && audio.bands && audio.bands.subBass) || 0)) / 2) * bassBoost.value;
|
|
166
|
-
const trebleEnergy = ((((audio && audio.bands && audio.bands.treble) || 0) + ((audio && audio.bands && audio.bands.presence) || 0)) / 2) * trebleBoost.value;
|
|
167
|
-
|
|
168
|
-
// Center reactive circle
|
|
169
|
-
if (showCenterCircle.value) {
|
|
170
|
-
const circleRadius = 50 + (currentVolume * pulseIntensity.value * 100);
|
|
171
|
-
const circleAlpha = Math.min(1, currentVolume * 2);
|
|
172
|
-
|
|
173
|
-
// Outer circle (bass reactive)
|
|
174
|
-
ctx.beginPath();
|
|
175
|
-
ctx.arc(centerX, centerY, circleRadius + (bassEnergy * 50), 0, Math.PI * 2);
|
|
176
|
-
ctx.strokeStyle = primaryColor.value + Math.floor(circleAlpha * 128).toString(16).padStart(2, '0');
|
|
177
|
-
ctx.lineWidth = 3;
|
|
178
|
-
ctx.stroke();
|
|
179
|
-
|
|
180
|
-
// Inner circle (treble reactive)
|
|
181
|
-
ctx.beginPath();
|
|
182
|
-
ctx.arc(centerX, centerY, circleRadius * 0.6 + (trebleEnergy * 30), 0, Math.PI * 2);
|
|
183
|
-
ctx.fillStyle = secondaryColor.value + Math.floor(circleAlpha * 64).toString(16).padStart(2, '0');
|
|
184
|
-
ctx.fill();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Frequency bars in circular arrangement
|
|
188
|
-
const radius = Math.min(viji.width, viji.height) * 0.3;
|
|
189
|
-
const bands = ['subBass', 'bass', 'lowMid', 'mid', 'highMid', 'presence', 'brilliance', 'treble'];
|
|
190
|
-
|
|
191
|
-
for (let i = 0; i < barCount.value; i++) {
|
|
192
|
-
const angle = (i / barCount.value) * Math.PI * 2 + (viji.time * rotationSpeed.value);
|
|
193
|
-
const bandIndex = Math.floor((i / barCount.value) * bands.length);
|
|
194
|
-
const bandValue = (audio && audio.bands ? (audio.bands[bands[bandIndex]] || 0) : 0);
|
|
195
|
-
|
|
196
|
-
// Apply audio sensitivity
|
|
197
|
-
const barHeight = bandValue * audioSensitivity.value * 200;
|
|
198
|
-
|
|
199
|
-
// Mouse influence
|
|
200
|
-
let mouseInfluenceValue = 1;
|
|
201
|
-
if (mouseInfluence.value && mouse && mouse.isInCanvas) {
|
|
202
|
-
const barX = centerX + Math.cos(angle) * radius;
|
|
203
|
-
const barY = centerY + Math.sin(angle) * radius;
|
|
204
|
-
const distToMouse = Math.sqrt((barX - mouse.x) ** 2 + (barY - mouse.y) ** 2);
|
|
205
|
-
|
|
206
|
-
if (distToMouse < mouseRadius.value) {
|
|
207
|
-
mouseInfluenceValue = 1 + (1 - distToMouse / mouseRadius.value) * 2;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const finalBarHeight = barHeight * mouseInfluenceValue;
|
|
212
|
-
|
|
213
|
-
// Calculate bar position
|
|
214
|
-
const startX = centerX + Math.cos(angle) * radius;
|
|
215
|
-
const startY = centerY + Math.sin(angle) * radius;
|
|
216
|
-
const endX = centerX + Math.cos(angle) * (radius + finalBarHeight);
|
|
217
|
-
const endY = centerY + Math.sin(angle) * (radius + finalBarHeight);
|
|
218
|
-
|
|
219
|
-
// Color based on frequency band
|
|
220
|
-
const hue = (bandIndex / bands.length) * 360;
|
|
221
|
-
const intensity = Math.min(1, bandValue * audioSensitivity.value * 3);
|
|
222
|
-
ctx.strokeStyle = `hsla(${hue}, 80%, ${50 + intensity * 30}%, ${0.6 + intensity * 0.4})`;
|
|
223
|
-
ctx.lineWidth = Math.max(1, (viji.width / barCount.value) - barSpacing.value);
|
|
224
|
-
|
|
225
|
-
// Draw frequency bar
|
|
226
|
-
ctx.beginPath();
|
|
227
|
-
ctx.moveTo(startX, startY);
|
|
228
|
-
ctx.lineTo(endX, endY);
|
|
229
|
-
ctx.stroke();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Waveform overlay
|
|
233
|
-
if (showWaveform.value && audio.isConnected) {
|
|
234
|
-
ctx.strokeStyle = primaryColor.value + '60';
|
|
235
|
-
ctx.lineWidth = 2;
|
|
236
|
-
ctx.beginPath();
|
|
237
|
-
|
|
238
|
-
// Simulate waveform
|
|
239
|
-
for (let x = 0; x < viji.width; x += 4) {
|
|
240
|
-
const waveY = centerY + Math.sin(x * 0.02 + viji.time * 3 + bassEnergy * 10) *
|
|
241
|
-
(currentVolume * 50) +
|
|
242
|
-
Math.sin(x * 0.05 + viji.time * 5 + trebleEnergy * 15) *
|
|
243
|
-
(currentVolume * 25);
|
|
244
|
-
|
|
245
|
-
if (x === 0) {
|
|
246
|
-
ctx.moveTo(x, waveY);
|
|
247
|
-
} else {
|
|
248
|
-
ctx.lineTo(x, waveY);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
ctx.stroke();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Audio info display
|
|
255
|
-
ctx.fillStyle = primaryColor.value + 'CC';
|
|
256
|
-
ctx.font = '14px monospace';
|
|
257
|
-
ctx.textAlign = 'left';
|
|
258
|
-
|
|
259
|
-
let infoY = 20;
|
|
260
|
-
ctx.fillText('🎵 AUDIO REACTIVE VISUALIZER', 20, infoY);
|
|
261
|
-
infoY += 20;
|
|
262
|
-
ctx.fillText(`Volume: ${(currentVolume * 100).toFixed(1)}%`, 20, infoY);
|
|
263
|
-
infoY += 16;
|
|
264
|
-
ctx.fillText(`Bass: ${(bassEnergy * 100).toFixed(1)}%`, 20, infoY);
|
|
265
|
-
infoY += 16;
|
|
266
|
-
ctx.fillText(`Treble: ${(trebleEnergy * 100).toFixed(1)}%`, 20, infoY);
|
|
267
|
-
infoY += 16;
|
|
268
|
-
ctx.fillText(`Bars: ${barCount.value}`, 20, infoY);
|
|
269
|
-
|
|
270
|
-
// Mouse interaction indicator
|
|
271
|
-
if (mouseInfluence.value && mouse && mouse.isInCanvas) {
|
|
272
|
-
ctx.strokeStyle = secondaryColor.value + '80';
|
|
273
|
-
ctx.lineWidth = 2;
|
|
274
|
-
ctx.setLineDash([5, 5]);
|
|
275
|
-
ctx.beginPath();
|
|
276
|
-
ctx.arc(mouse.x, mouse.y, mouseRadius.value, 0, Math.PI * 2);
|
|
277
|
-
ctx.stroke();
|
|
278
|
-
ctx.setLineDash([]);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Performance info
|
|
282
|
-
ctx.fillStyle = secondaryColor.value + 'AA';
|
|
283
|
-
ctx.font = '12px monospace';
|
|
284
|
-
ctx.textAlign = 'right';
|
|
285
|
-
ctx.fillText(`Frame: ${viji.frameCount}`, viji.width - 20, viji.height - 25);
|
|
286
|
-
ctx.fillText(`Time: ${viji.time.toFixed(1)}s`, viji.width - 20, viji.height - 10);
|
|
287
|
-
}
|