@viji-dev/sdk 1.0.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/.gitignore +29 -0
- package/LICENSE +13 -0
- package/README.md +103 -0
- package/bin/viji.js +75 -0
- package/eslint.config.js +37 -0
- package/index.html +20 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/public/favicon.png +0 -0
- package/scenes/audio-visualizer/main.js +287 -0
- package/scenes/core-demo/main.js +532 -0
- package/scenes/demo-scene/main.js +619 -0
- package/scenes/global.d.ts +15 -0
- package/scenes/particle-system/main.js +349 -0
- package/scenes/tsconfig.json +12 -0
- package/scenes/video-mirror/main.ts +436 -0
- package/src/App.css +42 -0
- package/src/App.tsx +279 -0
- package/src/cli/commands/build.js +147 -0
- package/src/cli/commands/create.js +71 -0
- package/src/cli/commands/dev.js +108 -0
- package/src/cli/commands/init.js +262 -0
- package/src/cli/utils/cli-utils.js +208 -0
- package/src/cli/utils/scene-compiler.js +432 -0
- package/src/components/SDKPage.tsx +337 -0
- package/src/components/core/CoreContainer.tsx +126 -0
- package/src/components/ui/DeviceSelectionList.tsx +137 -0
- package/src/components/ui/FPSCounter.tsx +78 -0
- package/src/components/ui/FileDropzonePanel.tsx +120 -0
- package/src/components/ui/FileListPanel.tsx +285 -0
- package/src/components/ui/InputExpansionPanel.tsx +31 -0
- package/src/components/ui/MediaPlayerControls.tsx +191 -0
- package/src/components/ui/MenuContainer.tsx +71 -0
- package/src/components/ui/ParametersMenu.tsx +797 -0
- package/src/components/ui/ProjectSwitcherMenu.tsx +192 -0
- package/src/components/ui/QuickInputControls.tsx +542 -0
- package/src/components/ui/SDKMenuSystem.tsx +96 -0
- package/src/components/ui/SettingsMenu.tsx +346 -0
- package/src/components/ui/SimpleInputControls.tsx +137 -0
- package/src/index.css +68 -0
- package/src/main.tsx +10 -0
- package/src/scenes-hmr.ts +158 -0
- package/src/services/project-filesystem.ts +436 -0
- package/src/stores/scene-player/index.ts +3 -0
- package/src/stores/scene-player/input-manager.store.ts +1045 -0
- package/src/stores/scene-player/scene-session.store.ts +659 -0
- package/src/styles/globals.css +111 -0
- package/src/templates/minimal-template.js +11 -0
- package/src/utils/debounce.js +34 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +18 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +27 -0
- package/vite.config.ts +54 -0
package/.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2025 Artem Verkhovskiy and Dmitry Manoilenko.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Permission is granted to install and use this software for the purpose of
|
|
5
|
+
developing creative scenes or related content for the VIJI platform.
|
|
6
|
+
|
|
7
|
+
Artists may freely use the SDK to create, test, and share their own scenes
|
|
8
|
+
without restriction. Redistribution or modification of the SDK itself, or
|
|
9
|
+
use of the SDK for purposes unrelated to developing for the VIJI platform,
|
|
10
|
+
is prohibited without prior written permission from the copyright holders.
|
|
11
|
+
|
|
12
|
+
This license does not grant any rights to copy, sublicense, or commercialize
|
|
13
|
+
the SDK itself, nor to use the VIJI name or brand without permission.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Viji SDK
|
|
2
|
+
|
|
3
|
+
Professional toolkit for creating interactive Viji scenes. Includes a CLI, workspace management, local development UI (Vite-based), and a production build pipeline that outputs a single deployable `scene.js`. Uses `@viji-dev/core` under the hood.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @viji-dev/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Create a new workspace
|
|
15
|
+
mkdir my-viji-workspace
|
|
16
|
+
cd my-viji-workspace
|
|
17
|
+
viji init .
|
|
18
|
+
|
|
19
|
+
# Create your first scene
|
|
20
|
+
viji create my-scene --lang=ts
|
|
21
|
+
|
|
22
|
+
# Start development server
|
|
23
|
+
viji dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI Commands
|
|
27
|
+
|
|
28
|
+
### `viji init [workspace-name]`
|
|
29
|
+
Initialize a new Viji workspace with complete development environment.
|
|
30
|
+
- Use `viji init .` to initialize in current directory
|
|
31
|
+
- Use `viji init my-workspace` to create new directory
|
|
32
|
+
|
|
33
|
+
### `viji create <scene-name> [--lang=js|ts]`
|
|
34
|
+
Create a new scene in the current workspace.
|
|
35
|
+
- Must be run inside a Viji workspace
|
|
36
|
+
- Creates scene in `scenes/<scene-name>/` folder
|
|
37
|
+
|
|
38
|
+
### `viji dev [--port=5173] [--host=localhost] [--open]`
|
|
39
|
+
Start development server with hot reload.
|
|
40
|
+
- Must be run inside a Viji workspace
|
|
41
|
+
- Opens browser-based development UI
|
|
42
|
+
|
|
43
|
+
### `viji build <scene-name> [--output=dist/scene.js]`
|
|
44
|
+
Build a scene for platform deployment.
|
|
45
|
+
- Compiles multi-file project to single JS file
|
|
46
|
+
- Ready for copy-paste to Viji platform
|
|
47
|
+
|
|
48
|
+
## Workspace Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
my-workspace/
|
|
52
|
+
├── src/ # Development UI (React + Vite)
|
|
53
|
+
├── scenes/ # Your scenes
|
|
54
|
+
│ ├── my-scene/
|
|
55
|
+
│ │ └── main.ts # Scene entry point
|
|
56
|
+
│ └── another-scene/
|
|
57
|
+
├── vite.config.ts # Vite configuration
|
|
58
|
+
├── package.json # Dependencies
|
|
59
|
+
└── ... # TypeScript configs, etc.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Scene Development
|
|
63
|
+
|
|
64
|
+
Each scene is a folder in `scenes/` with a `main.js` or `main.ts` entry point:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// scenes/my-scene/main.ts
|
|
68
|
+
export const parameters = [
|
|
69
|
+
// Parameter definitions
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export function init(canvas: HTMLCanvasElement, audioContext: AudioContext) {
|
|
73
|
+
// Initialization code
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function render(state: any, inputs: any, params: any) {
|
|
77
|
+
// Main render loop
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Features
|
|
82
|
+
|
|
83
|
+
- **🎨 Complete Development Environment**: Full React UI for scene development
|
|
84
|
+
- **🔥 Hot Reload**: Instant updates when you save scene files
|
|
85
|
+
- **📁 Workspace Management**: Organize multiple scenes in one project
|
|
86
|
+
- **🛠️ TypeScript Support**: Full type safety and IDE integration
|
|
87
|
+
- **📦 Single-File Build**: Compile to platform-ready scene.js
|
|
88
|
+
- **🎯 Platform Compatible**: Uses same Core runtime as Viji platform
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
- SDK package: `docs/02-sdk-package.md`
|
|
93
|
+
- Implementation guide: `docs/06-cursor-implementation-guide.md`
|
|
94
|
+
|
|
95
|
+
## 📄 License
|
|
96
|
+
|
|
97
|
+
Copyright (c) 2025 Artem Verkhovskiy and Dmitry Manoilenko.
|
|
98
|
+
All rights reserved - see the LICENSE file for details.
|
|
99
|
+
|
|
100
|
+
## Contributor License Agreement
|
|
101
|
+
|
|
102
|
+
By contributing, you agree to the [CLA](./CLA.md).
|
|
103
|
+
Please also confirm your agreement by filling out this short [form](https://forms.gle/A49WTz8nj5b99Yev7).
|
package/bin/viji.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Viji SDK Command Line Interface
|
|
5
|
+
* Professional toolkit for creating interactive Viji scenes
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const { program } = require('commander');
|
|
11
|
+
import { createCommand } from '../src/cli/commands/create.js';
|
|
12
|
+
import { devCommand } from '../src/cli/commands/dev.js';
|
|
13
|
+
import { buildCommand } from '../src/cli/commands/build.js';
|
|
14
|
+
import { initCommand } from '../src/cli/commands/init.js';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { dirname, join } from 'path';
|
|
17
|
+
import { readFileSync } from 'fs';
|
|
18
|
+
|
|
19
|
+
// Get package.json version
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const packagePath = join(__dirname, '..', 'package.json');
|
|
23
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.name('viji')
|
|
27
|
+
.description('🎨 Viji SDK - Professional toolkit for creating interactive scenes')
|
|
28
|
+
.version(packageJson.version);
|
|
29
|
+
|
|
30
|
+
// Command: Initialize new workspace
|
|
31
|
+
program
|
|
32
|
+
.command('init')
|
|
33
|
+
.argument('[workspace-name]', 'Name of the workspace (use "." for current directory)', '.')
|
|
34
|
+
.description('Initialize a new Viji workspace')
|
|
35
|
+
.action(initCommand);
|
|
36
|
+
|
|
37
|
+
// Command: Create new scene
|
|
38
|
+
program
|
|
39
|
+
.command('create')
|
|
40
|
+
.argument('<scene-name>', 'Name of the new scene')
|
|
41
|
+
.option('--lang <lang>', 'Scene language (js|ts)', 'js')
|
|
42
|
+
.description('Create a new scene in the current workspace')
|
|
43
|
+
.action(createCommand);
|
|
44
|
+
|
|
45
|
+
// Command: Start development server
|
|
46
|
+
program
|
|
47
|
+
.command('dev')
|
|
48
|
+
.option('-p, --port <port>', 'Development server port', '5173')
|
|
49
|
+
.option('-h, --host <host>', 'Development server host', 'localhost')
|
|
50
|
+
.option('--open', 'Open browser automatically', false)
|
|
51
|
+
.description('Start development server with hot reload')
|
|
52
|
+
.action(devCommand);
|
|
53
|
+
|
|
54
|
+
// Command: Build for production
|
|
55
|
+
program
|
|
56
|
+
.command('build')
|
|
57
|
+
.argument('<scene-name>', 'Name of the scene to build')
|
|
58
|
+
.option('-o, --output <file>', 'Output file path', 'dist/scene.js')
|
|
59
|
+
.description('Build scene: bundle multi-file projects into one, no minify')
|
|
60
|
+
.action(buildCommand);
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Global error handling
|
|
64
|
+
process.on('uncaughtException', (error) => {
|
|
65
|
+
console.error('💥 Unexpected error:', error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
process.on('unhandledRejection', (reason) => {
|
|
70
|
+
console.error('💥 Unhandled promise rejection:', reason);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Parse and execute
|
|
75
|
+
program.parse();
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>VIJI SDK - Creative Development Toolkit</title>
|
|
8
|
+
|
|
9
|
+
<!-- Security headers for @viji-dev/core workers and iframes -->
|
|
10
|
+
<meta http-equiv="Cross-Origin-Embedder-Policy" content="credentialless" />
|
|
11
|
+
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin" />
|
|
12
|
+
|
|
13
|
+
<!-- Permissions for media access -->
|
|
14
|
+
<meta http-equiv="Feature-Policy" content="camera 'self'; microphone 'self'; display-capture 'self'" />
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@viji-dev/sdk",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Professional toolkit for creating interactive Viji scenes",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"viji",
|
|
9
|
+
"interactive",
|
|
10
|
+
"visualization",
|
|
11
|
+
"art",
|
|
12
|
+
"sdk",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"author": "Viji Team",
|
|
16
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
17
|
+
"bin": {
|
|
18
|
+
"viji": "./bin/viji.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "vite",
|
|
22
|
+
"build": "tsc -b && vite build",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"preview": "vite preview",
|
|
25
|
+
"cli:dev": "node bin/viji.js dev",
|
|
26
|
+
"cli:build": "node bin/viji.js build",
|
|
27
|
+
"cli:create": "node bin/viji.js create"
|
|
28
|
+
},
|
|
29
|
+
"main": "src/main.tsx",
|
|
30
|
+
"files": [
|
|
31
|
+
"bin/",
|
|
32
|
+
"src/",
|
|
33
|
+
"scenes/",
|
|
34
|
+
"public/",
|
|
35
|
+
"index.html",
|
|
36
|
+
"vite.config.ts",
|
|
37
|
+
"tsconfig*.json",
|
|
38
|
+
"tailwind.config.js",
|
|
39
|
+
"postcss.config.js",
|
|
40
|
+
"eslint.config.js",
|
|
41
|
+
".gitignore",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/viji-dev/sdk.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/viji-dev/sdk#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/viji-dev/sdk/issues"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@heroicons/react": "^2.2.0",
|
|
55
|
+
"@heroui/react": "^2.7.11",
|
|
56
|
+
"@viji-dev/core": "^0.2.4",
|
|
57
|
+
"autoprefixer": "^10.4.21",
|
|
58
|
+
"chokidar": "^4.0.0",
|
|
59
|
+
"clsx": "^2.1.1",
|
|
60
|
+
"commander": "^12.0.0",
|
|
61
|
+
"framer-motion": "^12.18.1",
|
|
62
|
+
"react": "^19.1.1",
|
|
63
|
+
"react-dom": "^19.1.1",
|
|
64
|
+
"tailwind-merge": "^3.3.1",
|
|
65
|
+
"tailwindcss": "^3.4.17",
|
|
66
|
+
"zustand": "^5.0.6"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@eslint/js": "^9.33.0",
|
|
70
|
+
"@types/node": "^24.3.0",
|
|
71
|
+
"@types/react": "^19.1.10",
|
|
72
|
+
"@types/react-dom": "^19.1.7",
|
|
73
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
74
|
+
"eslint": "^9.33.0",
|
|
75
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
76
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
77
|
+
"globals": "^16.3.0",
|
|
78
|
+
"typescript": "~5.8.3",
|
|
79
|
+
"typescript-eslint": "^8.39.1",
|
|
80
|
+
"vite": "^6.3.5"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,287 @@
|
|
|
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
|
+
}
|