@devaloop/devalang 0.0.1-beta.1 → 0.0.1-beta.3
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/.devalang +9 -10
- package/Cargo.toml +84 -80
- package/README.md +10 -7
- package/docs/CHANGELOG.md +83 -0
- package/docs/ROADMAP.md +6 -2
- package/docs/TODO.md +3 -14
- package/examples/bus.deva +10 -0
- package/examples/chain.deva +19 -0
- package/examples/effect.deva +2 -0
- package/examples/filter.deva +11 -0
- package/examples/lfo.deva +9 -0
- package/examples/plugin.deva +10 -10
- package/examples/routing.deva +23 -0
- package/examples/synth.deva +11 -1
- package/examples/synth_types.deva +17 -0
- package/out-tsc/bin/project-version.json +6 -0
- package/out-tsc/core/functions/index.d.ts +5 -0
- package/out-tsc/core/functions/index.js +11 -0
- package/out-tsc/pkg/devalang_core.d.ts +2 -0
- package/out-tsc/pkg/devalang_core.js +17 -2
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.js +79 -0
- package/package.json +23 -10
- package/project-version.json +3 -3
- package/rust/bindings/Cargo.toml +9 -0
- package/rust/bindings/src/lib.rs +86 -0
- package/rust/cli/addon/commands.rs +35 -0
- package/rust/cli/addon/download.rs +234 -0
- package/rust/cli/addon/install.rs +33 -0
- package/rust/cli/addon/list.rs +224 -0
- package/rust/cli/addon/metadata.rs +124 -0
- package/rust/cli/addon/mod.rs +8 -0
- package/rust/cli/addon/remove.rs +271 -0
- package/rust/cli/addon/update.rs +305 -0
- package/rust/cli/{install/addon.rs → addon/utils.rs} +34 -43
- package/rust/cli/build/commands.rs +153 -103
- package/rust/cli/build/mod.rs +2 -2
- package/rust/cli/build/process.rs +165 -146
- package/rust/cli/check/mod.rs +208 -208
- package/rust/cli/discover/commands.rs +53 -31
- package/rust/cli/discover/config.rs +2 -4
- package/rust/cli/discover/install.rs +139 -28
- package/rust/cli/discover/metadata.rs +3 -3
- package/rust/cli/login/commands.rs +124 -124
- package/rust/cli/me/commands.rs +52 -0
- package/rust/cli/me/mod.rs +1 -0
- package/rust/cli/mod.rs +2 -2
- package/rust/cli/parser.rs +76 -70
- package/rust/cli/play/commands.rs +375 -324
- package/rust/cli/play/mod.rs +5 -5
- package/rust/cli/play/process.rs +159 -150
- package/rust/cli/play/realtime.rs +91 -91
- package/rust/cli/telemetry/commands.rs +22 -22
- package/rust/cli/telemetry/event_creator.rs +80 -80
- package/rust/cli/telemetry/mod.rs +3 -3
- package/rust/cli/telemetry/send.rs +51 -51
- package/rust/cli/template/commands.rs +69 -69
- package/rust/config/driver.rs +112 -103
- package/rust/config/mod.rs +3 -3
- package/rust/config/ops.rs +26 -26
- package/rust/config/settings.rs +101 -101
- package/rust/core/audio/engine/driver.rs +237 -0
- package/rust/core/audio/engine/export.rs +169 -0
- package/rust/core/audio/engine/helpers.rs +178 -170
- package/rust/core/audio/engine/mod.rs +56 -7
- package/rust/core/audio/engine/notes/dsp.rs +88 -0
- package/rust/core/audio/engine/notes/mod.rs +53 -0
- package/rust/core/audio/engine/notes/params.rs +294 -0
- package/rust/core/audio/engine/sample/insert.rs +300 -0
- package/rust/core/audio/engine/sample/mod.rs +40 -0
- package/rust/core/audio/engine/sample/padding.rs +170 -0
- package/rust/core/audio/evaluator/condition.rs +61 -0
- package/rust/core/audio/evaluator/mod.rs +9 -0
- package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +152 -310
- package/rust/core/audio/evaluator/rhs.rs +16 -0
- package/rust/core/audio/evaluator/string_expr.rs +94 -0
- package/rust/core/audio/interpreter/driver.rs +574 -542
- package/rust/core/audio/interpreter/mod.rs +2 -14
- package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +179 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +3 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +371 -0
- package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
- package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +2 -4
- package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +36 -5
- package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -71
- package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +24 -26
- package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +36 -38
- package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +17 -19
- package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +115 -114
- package/rust/core/audio/interpreter/statements/mod.rs +12 -0
- package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
- package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +54 -4
- package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
- package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +242 -239
- package/rust/core/audio/loader/trigger.rs +98 -97
- package/rust/core/audio/mod.rs +6 -7
- package/rust/core/audio/special/easing.rs +189 -189
- package/rust/core/audio/special/env.rs +45 -45
- package/rust/core/audio/special/math.rs +134 -134
- package/rust/core/audio/special/modulator.rs +143 -143
- package/rust/core/builder/mod.rs +129 -86
- package/rust/core/debugger/{module.rs → logs.rs} +52 -55
- package/rust/core/debugger/mod.rs +30 -30
- package/rust/core/debugger/store.rs +38 -40
- package/rust/core/error/mod.rs +269 -269
- package/rust/core/lexer/driver.rs +2 -4
- package/rust/core/mod.rs +9 -10
- package/rust/core/parser/driver/block.rs +111 -0
- package/rust/core/parser/driver/cursor.rs +82 -0
- package/rust/core/parser/driver/driver_impl.rs +159 -0
- package/rust/core/parser/driver/mod.rs +6 -0
- package/rust/core/parser/driver/parse_array.rs +120 -0
- package/rust/core/parser/driver/parse_map.rs +247 -0
- package/rust/core/parser/driver/parser.rs +160 -0
- package/rust/core/parser/handler/arrow_call.rs +90 -15
- package/rust/core/parser/handler/at.rs +279 -279
- package/rust/core/parser/handler/bank.rs +104 -104
- package/rust/core/parser/handler/condition.rs +83 -83
- package/rust/core/parser/handler/dot.rs +148 -148
- package/rust/core/parser/handler/identifier/automate.rs +254 -254
- package/rust/core/parser/handler/identifier/call.rs +91 -91
- package/rust/core/parser/handler/identifier/emit.rs +70 -70
- package/rust/core/parser/handler/identifier/function.rs +113 -113
- package/rust/core/parser/handler/identifier/group.rs +89 -89
- package/rust/core/parser/handler/identifier/let_.rs +173 -173
- package/rust/core/parser/handler/identifier/mod.rs +55 -55
- package/rust/core/parser/handler/identifier/on.rs +107 -107
- package/rust/core/parser/handler/identifier/print.rs +49 -49
- package/rust/core/parser/handler/identifier/sleep.rs +96 -43
- package/rust/core/parser/handler/identifier/spawn.rs +91 -91
- package/rust/core/parser/handler/identifier/synth.rs +39 -3
- package/rust/core/parser/handler/loop_.rs +194 -194
- package/rust/core/parser/handler/pattern.rs +25 -2
- package/rust/core/parser/handler/tempo.rs +105 -57
- package/rust/core/parser/statement.rs +10 -11
- package/rust/core/plugin/loader.rs +137 -137
- package/rust/core/plugin/runner/mod.rs +11 -0
- package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +206 -72
- package/rust/core/plugin/runner/wasm32.rs +44 -0
- package/rust/core/preprocessor/loader/inject.rs +313 -0
- package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
- package/rust/core/preprocessor/loader/mod.rs +235 -0
- package/rust/core/preprocessor/module.rs +55 -60
- package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +107 -114
- package/rust/core/preprocessor/processor/mod.rs +1 -0
- package/rust/core/preprocessor/resolver/function.rs +69 -69
- package/rust/core/preprocessor/resolver/group.rs +122 -94
- package/rust/core/preprocessor/resolver/pattern.rs +14 -2
- package/rust/core/store/global.rs +57 -61
- package/rust/core/store/mod.rs +1 -5
- package/rust/lib.rs +323 -308
- package/rust/macros/Cargo.toml +14 -0
- package/rust/macros/src/lib.rs +52 -0
- package/rust/main.rs +336 -143
- package/rust/types/Cargo.toml +1 -1
- package/rust/types/src/addons.rs +57 -55
- package/rust/types/src/config.rs +82 -74
- package/rust/types/src/lib.rs +15 -12
- package/rust/types/src/plugin.rs +20 -0
- package/rust/types/src/store.rs +139 -0
- package/rust/types/src/telemetry.rs +85 -85
- package/rust/utils/Cargo.toml +5 -2
- package/rust/utils/src/file.rs +477 -94
- package/rust/utils/src/first_usage.rs +97 -97
- package/rust/utils/src/lib.rs +9 -9
- package/rust/utils/src/logger.rs +200 -200
- package/rust/utils/src/path.rs +158 -88
- package/rust/utils/src/signature.rs +41 -41
- package/rust/utils/src/spinner.rs +20 -20
- package/rust/utils/src/version.rs +58 -27
- package/rust/utils/src/watcher.rs +46 -46
- package/rust/web/api.rs +5 -5
- package/rust/web/auth.rs +5 -0
- package/rust/web/cdn.rs +34 -34
- package/rust/web/forge.rs +5 -0
- package/rust/web/mod.rs +2 -0
- package/tests/integration.rs +21 -21
- package/typescript/core/functions/index.ts +11 -0
- package/typescript/pkg/devalang_core.ts +20 -4
- package/typescript/scripts/version/copy-to-binary.ts +82 -0
- package/rust/cli/bank/api.rs +0 -122
- package/rust/cli/bank/commands.rs +0 -275
- package/rust/cli/bank/mod.rs +0 -29
- package/rust/cli/install/bank.rs +0 -53
- package/rust/cli/install/commands.rs +0 -35
- package/rust/cli/install/mod.rs +0 -4
- package/rust/cli/install/plugin.rs +0 -61
- package/rust/core/audio/engine/sample.rs +0 -366
- package/rust/core/audio/engine/synth.rs +0 -325
- package/rust/core/audio/interpreter/arrow_call.rs +0 -311
- package/rust/core/audio/renderer.rs +0 -54
- package/rust/core/parser/driver.rs +0 -584
- package/rust/core/preprocessor/loader.rs +0 -637
- package/rust/core/store/export.rs +0 -28
- package/rust/core/store/function.rs +0 -40
- package/rust/core/store/import.rs +0 -28
- package/rust/core/store/variable.rs +0 -51
- package/rust/core/utils/mod.rs +0 -1
- package/rust/core/utils/path.rs +0 -37
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
use crate::core::lexer::token::Token;
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
1
|
+
use crate::core::lexer::token::Token;
|
|
2
|
+
pub use devalang_types::{Duration, Statement, StatementKind, Value};
|
|
3
|
+
|
|
4
|
+
pub fn unknown_from_token(token: &Token) -> Statement {
|
|
5
|
+
Statement::unknown_with_pos(token.indent, token.line, token.column)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
pub fn error_from_token(token: Token, message: String) -> Statement {
|
|
9
|
+
Statement::error_with_pos(token.indent, token.line, token.column, message)
|
|
10
|
+
}
|
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
use devalang_types::{PluginExport, PluginInfo as SharedPluginInfo};
|
|
2
|
-
use devalang_utils::path as path_utils;
|
|
3
|
-
use serde::Deserialize;
|
|
4
|
-
use toml::Value as TomlValue;
|
|
5
|
-
|
|
6
|
-
#[derive(Debug, Deserialize, Clone)]
|
|
7
|
-
struct LocalExportEntry {
|
|
8
|
-
pub name: String,
|
|
9
|
-
|
|
10
|
-
pub kind: String,
|
|
11
|
-
#[serde(default)]
|
|
12
|
-
pub default: Option<TomlValue>,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
#[derive(Debug, Deserialize, Clone)]
|
|
16
|
-
struct LocalPluginFile {
|
|
17
|
-
pub plugin: LocalPluginInfo,
|
|
18
|
-
#[serde(default)]
|
|
19
|
-
pub
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
#[derive(Debug, Deserialize, Clone)]
|
|
23
|
-
struct LocalPluginInfo {
|
|
24
|
-
pub name: String,
|
|
25
|
-
pub version: Option<String>,
|
|
26
|
-
pub description: Option<String>,
|
|
27
|
-
pub author: Option<String>,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
pub fn load_plugin(author: &str, name: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
31
|
-
let root = path_utils::get_deva_dir()?;
|
|
32
|
-
let plugin_dir_preferred = root.join("plugins").join(format!("{}.{}", author, name));
|
|
33
|
-
let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
|
|
34
|
-
let wasm_path_preferred_bg = plugin_dir_preferred.join(format!("{}_bg.wasm", name));
|
|
35
|
-
let wasm_path_preferred_plain = plugin_dir_preferred.join(format!("{}.wasm", name));
|
|
36
|
-
|
|
37
|
-
// Legacy layout (fallback): ./.deva/plugin/<author>/<name>/
|
|
38
|
-
let plugin_dir_fallback = root.join("plugins").join(author).join(name);
|
|
39
|
-
let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
|
|
40
|
-
let wasm_path_fallback_bg = plugin_dir_fallback.join(format!("{}_bg.wasm", name));
|
|
41
|
-
let wasm_path_fallback_plain = plugin_dir_fallback.join(format!("{}.wasm", name));
|
|
42
|
-
|
|
43
|
-
// Resolve actual paths to use
|
|
44
|
-
let (toml_path, wasm_path) = if toml_path_preferred.exists() && wasm_path_preferred_bg.exists()
|
|
45
|
-
{
|
|
46
|
-
(toml_path_preferred, wasm_path_preferred_bg)
|
|
47
|
-
} else if toml_path_preferred.exists() && wasm_path_preferred_plain.exists() {
|
|
48
|
-
(toml_path_preferred, wasm_path_preferred_plain)
|
|
49
|
-
} else if toml_path_fallback.exists() && wasm_path_fallback_bg.exists() {
|
|
50
|
-
(toml_path_fallback, wasm_path_fallback_bg)
|
|
51
|
-
} else if toml_path_fallback.exists() && wasm_path_fallback_plain.exists() {
|
|
52
|
-
(toml_path_fallback, wasm_path_fallback_plain)
|
|
53
|
-
} else {
|
|
54
|
-
// If either file is missing in both layouts, produce specific errors for missing files in preferred layout
|
|
55
|
-
if !toml_path_preferred.exists() {
|
|
56
|
-
return Err(format!(
|
|
57
|
-
"❌ Plugin file not found: {}",
|
|
58
|
-
toml_path_preferred.display()
|
|
59
|
-
));
|
|
60
|
-
}
|
|
61
|
-
if !wasm_path_preferred_bg.exists() && !wasm_path_preferred_plain.exists() {
|
|
62
|
-
return Err(format!(
|
|
63
|
-
"❌ Plugin wasm not found: '{}' or '{}'",
|
|
64
|
-
wasm_path_preferred_bg.display(),
|
|
65
|
-
wasm_path_preferred_plain.display()
|
|
66
|
-
));
|
|
67
|
-
}
|
|
68
|
-
unreachable!();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
let toml_content = std::fs::read_to_string(&toml_path)
|
|
72
|
-
.map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
|
|
73
|
-
let plugin_file: LocalPluginFile = toml::from_str(&toml_content)
|
|
74
|
-
.map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
|
|
75
|
-
|
|
76
|
-
let wasm_bytes = std::fs::read(&wasm_path)
|
|
77
|
-
.map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
|
|
78
|
-
|
|
79
|
-
// Map local parsed plugin info to shared PluginInfo
|
|
80
|
-
let mut exports: Vec<PluginExport> = Vec::new();
|
|
81
|
-
for e in plugin_file.
|
|
82
|
-
exports.push(PluginExport {
|
|
83
|
-
name: e.name.clone(),
|
|
84
|
-
kind: e.kind.clone(),
|
|
85
|
-
default: e.default.clone(),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let info = SharedPluginInfo {
|
|
90
|
-
author: plugin_file
|
|
91
|
-
.plugin
|
|
92
|
-
.author
|
|
93
|
-
.unwrap_or_else(|| author.to_string()),
|
|
94
|
-
name: plugin_file.plugin.name.clone(),
|
|
95
|
-
version: plugin_file.plugin.version.clone(),
|
|
96
|
-
description: plugin_file.plugin.description.clone(),
|
|
97
|
-
exports,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
Ok((info, wasm_bytes))
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Load a plugin from dot notation: "author.name"
|
|
104
|
-
pub fn load_plugin_from_dot(dot: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
105
|
-
let mut parts = dot.split('.');
|
|
106
|
-
let author = parts
|
|
107
|
-
.next()
|
|
108
|
-
.ok_or_else(|| "Invalid plugin name, missing author".to_string())?;
|
|
109
|
-
let name = parts
|
|
110
|
-
.next()
|
|
111
|
-
.ok_or_else(|| "Invalid plugin name, missing name".to_string())?;
|
|
112
|
-
if parts.next().is_some() {
|
|
113
|
-
return Err("Invalid plugin name format, expected <author>.<name>".into());
|
|
114
|
-
}
|
|
115
|
-
load_plugin(author, name)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
pub fn load_plugin_from_uri(uri: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
119
|
-
if !uri.starts_with("devalang://plugin/") {
|
|
120
|
-
return Err("Invalid plugin URI".into());
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Expect format: devalang://plugin/author.name
|
|
124
|
-
let payload = uri.trim_start_matches("devalang://plugin/");
|
|
125
|
-
let mut parts = payload.split('.');
|
|
126
|
-
let author = parts
|
|
127
|
-
.next()
|
|
128
|
-
.ok_or_else(|| "Invalid plugin URI, missing author".to_string())?;
|
|
129
|
-
let name = parts
|
|
130
|
-
.next()
|
|
131
|
-
.ok_or_else(|| "Invalid plugin URI, missing name".to_string())?;
|
|
132
|
-
if parts.next().is_some() {
|
|
133
|
-
return Err("Invalid plugin URI format, expected devalang://plugin/<author>.<name>".into());
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
load_plugin(author, name)
|
|
137
|
-
}
|
|
1
|
+
use devalang_types::{plugin::PluginExport, plugin::PluginInfo as SharedPluginInfo};
|
|
2
|
+
use devalang_utils::path as path_utils;
|
|
3
|
+
use serde::Deserialize;
|
|
4
|
+
use toml::Value as TomlValue;
|
|
5
|
+
|
|
6
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
7
|
+
struct LocalExportEntry {
|
|
8
|
+
pub name: String,
|
|
9
|
+
// Local plugin.toml uses 'kind' to describe export type (e.g. "func", "number")
|
|
10
|
+
pub kind: String,
|
|
11
|
+
#[serde(default)]
|
|
12
|
+
pub default: Option<TomlValue>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
16
|
+
struct LocalPluginFile {
|
|
17
|
+
pub plugin: LocalPluginInfo,
|
|
18
|
+
#[serde(rename = "exports", default)]
|
|
19
|
+
pub exports: Vec<LocalExportEntry>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
23
|
+
struct LocalPluginInfo {
|
|
24
|
+
pub name: String,
|
|
25
|
+
pub version: Option<String>,
|
|
26
|
+
pub description: Option<String>,
|
|
27
|
+
pub author: Option<String>,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn load_plugin(author: &str, name: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
31
|
+
let root = path_utils::get_deva_dir()?;
|
|
32
|
+
let plugin_dir_preferred = root.join("plugins").join(format!("{}.{}", author, name));
|
|
33
|
+
let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
|
|
34
|
+
let wasm_path_preferred_bg = plugin_dir_preferred.join(format!("{}_bg.wasm", name));
|
|
35
|
+
let wasm_path_preferred_plain = plugin_dir_preferred.join(format!("{}.wasm", name));
|
|
36
|
+
|
|
37
|
+
// Legacy layout (fallback): ./.deva/plugin/<author>/<name>/
|
|
38
|
+
let plugin_dir_fallback = root.join("plugins").join(author).join(name);
|
|
39
|
+
let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
|
|
40
|
+
let wasm_path_fallback_bg = plugin_dir_fallback.join(format!("{}_bg.wasm", name));
|
|
41
|
+
let wasm_path_fallback_plain = plugin_dir_fallback.join(format!("{}.wasm", name));
|
|
42
|
+
|
|
43
|
+
// Resolve actual paths to use
|
|
44
|
+
let (toml_path, wasm_path) = if toml_path_preferred.exists() && wasm_path_preferred_bg.exists()
|
|
45
|
+
{
|
|
46
|
+
(toml_path_preferred, wasm_path_preferred_bg)
|
|
47
|
+
} else if toml_path_preferred.exists() && wasm_path_preferred_plain.exists() {
|
|
48
|
+
(toml_path_preferred, wasm_path_preferred_plain)
|
|
49
|
+
} else if toml_path_fallback.exists() && wasm_path_fallback_bg.exists() {
|
|
50
|
+
(toml_path_fallback, wasm_path_fallback_bg)
|
|
51
|
+
} else if toml_path_fallback.exists() && wasm_path_fallback_plain.exists() {
|
|
52
|
+
(toml_path_fallback, wasm_path_fallback_plain)
|
|
53
|
+
} else {
|
|
54
|
+
// If either file is missing in both layouts, produce specific errors for missing files in preferred layout
|
|
55
|
+
if !toml_path_preferred.exists() {
|
|
56
|
+
return Err(format!(
|
|
57
|
+
"❌ Plugin file not found: {}",
|
|
58
|
+
toml_path_preferred.display()
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
if !wasm_path_preferred_bg.exists() && !wasm_path_preferred_plain.exists() {
|
|
62
|
+
return Err(format!(
|
|
63
|
+
"❌ Plugin wasm not found: '{}' or '{}'",
|
|
64
|
+
wasm_path_preferred_bg.display(),
|
|
65
|
+
wasm_path_preferred_plain.display()
|
|
66
|
+
));
|
|
67
|
+
}
|
|
68
|
+
unreachable!();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let toml_content = std::fs::read_to_string(&toml_path)
|
|
72
|
+
.map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
|
|
73
|
+
let plugin_file: LocalPluginFile = toml::from_str(&toml_content)
|
|
74
|
+
.map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
|
|
75
|
+
|
|
76
|
+
let wasm_bytes = std::fs::read(&wasm_path)
|
|
77
|
+
.map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
|
|
78
|
+
|
|
79
|
+
// Map local parsed plugin info to shared PluginInfo
|
|
80
|
+
let mut exports: Vec<PluginExport> = Vec::new();
|
|
81
|
+
for e in plugin_file.exports.iter() {
|
|
82
|
+
exports.push(PluginExport {
|
|
83
|
+
name: e.name.clone(),
|
|
84
|
+
kind: e.kind.clone(),
|
|
85
|
+
default: e.default.clone(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let info = SharedPluginInfo {
|
|
90
|
+
author: plugin_file
|
|
91
|
+
.plugin
|
|
92
|
+
.author
|
|
93
|
+
.unwrap_or_else(|| author.to_string()),
|
|
94
|
+
name: plugin_file.plugin.name.clone(),
|
|
95
|
+
version: plugin_file.plugin.version.clone(),
|
|
96
|
+
description: plugin_file.plugin.description.clone(),
|
|
97
|
+
exports,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Ok((info, wasm_bytes))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Load a plugin from dot notation: "author.name"
|
|
104
|
+
pub fn load_plugin_from_dot(dot: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
105
|
+
let mut parts = dot.split('.');
|
|
106
|
+
let author = parts
|
|
107
|
+
.next()
|
|
108
|
+
.ok_or_else(|| "Invalid plugin name, missing author".to_string())?;
|
|
109
|
+
let name = parts
|
|
110
|
+
.next()
|
|
111
|
+
.ok_or_else(|| "Invalid plugin name, missing name".to_string())?;
|
|
112
|
+
if parts.next().is_some() {
|
|
113
|
+
return Err("Invalid plugin name format, expected <author>.<name>".into());
|
|
114
|
+
}
|
|
115
|
+
load_plugin(author, name)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pub fn load_plugin_from_uri(uri: &str) -> Result<(SharedPluginInfo, Vec<u8>), String> {
|
|
119
|
+
if !uri.starts_with("devalang://plugin/") {
|
|
120
|
+
return Err("Invalid plugin URI".into());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Expect format: devalang://plugin/author.name
|
|
124
|
+
let payload = uri.trim_start_matches("devalang://plugin/");
|
|
125
|
+
let mut parts = payload.split('.');
|
|
126
|
+
let author = parts
|
|
127
|
+
.next()
|
|
128
|
+
.ok_or_else(|| "Invalid plugin URI, missing author".to_string())?;
|
|
129
|
+
let name = parts
|
|
130
|
+
.next()
|
|
131
|
+
.ok_or_else(|| "Invalid plugin URI, missing name".to_string())?;
|
|
132
|
+
if parts.next().is_some() {
|
|
133
|
+
return Err("Invalid plugin URI format, expected devalang://plugin/<author>.<name>".into());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
load_plugin(author, name)
|
|
137
|
+
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
|
+
use devalang_utils::logger::{LogLevel, Logger};
|
|
1
2
|
use std::collections::HashMap;
|
|
2
3
|
|
|
3
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
4
4
|
use wasmtime::{Engine, Instance, Linker, Module, Store, TypedFunc};
|
|
5
5
|
|
|
6
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
7
6
|
type RenderFunc = TypedFunc<(i32, i32, f32, f32, i32, i32, i32), ()>;
|
|
8
7
|
|
|
9
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
10
8
|
pub struct WasmPluginRunner {
|
|
11
9
|
engine: Engine,
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
15
12
|
impl Default for WasmPluginRunner {
|
|
16
13
|
fn default() -> Self {
|
|
17
14
|
Self::new()
|
|
18
15
|
}
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
22
18
|
impl WasmPluginRunner {
|
|
23
19
|
pub fn new() -> Self {
|
|
24
20
|
let engine = Engine::default();
|
|
@@ -95,7 +91,11 @@ impl WasmPluginRunner {
|
|
|
95
91
|
.get_memory(&mut store, "memory")
|
|
96
92
|
.ok_or_else(|| "WASM memory export not found".to_string())?;
|
|
97
93
|
|
|
98
|
-
// Try specific
|
|
94
|
+
// Try specific + generic entry points in order of preference.
|
|
95
|
+
// 1) render_note_<name>
|
|
96
|
+
// 2) render_note
|
|
97
|
+
// 3) synth_<name>
|
|
98
|
+
// 4) synth
|
|
99
99
|
let mut func_opt: Option<RenderFunc> = None;
|
|
100
100
|
if let Some(name) = synth_name {
|
|
101
101
|
let specific = format!("render_note_{}", name);
|
|
@@ -106,7 +106,6 @@ impl WasmPluginRunner {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
if func_opt.is_none() {
|
|
109
|
-
// fallback to generic name
|
|
110
109
|
if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
|
|
111
110
|
&mut store,
|
|
112
111
|
"render_note",
|
|
@@ -114,9 +113,27 @@ impl WasmPluginRunner {
|
|
|
114
113
|
func_opt = Some(f);
|
|
115
114
|
}
|
|
116
115
|
}
|
|
116
|
+
if func_opt.is_none() {
|
|
117
|
+
if let Some(name) = synth_name {
|
|
118
|
+
let specific = format!("synth_{}", name);
|
|
119
|
+
if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
|
|
120
|
+
&mut store, &specific,
|
|
121
|
+
) {
|
|
122
|
+
func_opt = Some(f);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if func_opt.is_none() {
|
|
127
|
+
if let Ok(f) = instance
|
|
128
|
+
.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, "synth")
|
|
129
|
+
{
|
|
130
|
+
func_opt = Some(f);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
117
133
|
|
|
118
|
-
let func =
|
|
119
|
-
|
|
134
|
+
let func = func_opt.ok_or_else(|| {
|
|
135
|
+
"Exported function not found: tried render_note[_<name>] and synth[_<name>]".to_string()
|
|
136
|
+
})?;
|
|
120
137
|
|
|
121
138
|
// Copy host buffer into wasm memory
|
|
122
139
|
let byte_len = std::mem::size_of_val(buffer) as i32;
|
|
@@ -171,6 +188,7 @@ impl WasmPluginRunner {
|
|
|
171
188
|
channels: i32,
|
|
172
189
|
params_num: &HashMap<String, f32>,
|
|
173
190
|
params_str: Option<&HashMap<String, String>>,
|
|
191
|
+
exported_names: Option<&[String]>,
|
|
174
192
|
) -> Result<(), String> {
|
|
175
193
|
let module = Module::new(&self.engine, wasm_bytes)
|
|
176
194
|
.map_err(|e| format!("Failed to compile wasm: {e}"))?;
|
|
@@ -187,32 +205,175 @@ impl WasmPluginRunner {
|
|
|
187
205
|
.ok_or_else(|| "WASM memory export not found".to_string())?;
|
|
188
206
|
|
|
189
207
|
// Call numeric setters if present: set_<param>(f32)
|
|
208
|
+
let logger = Logger::new();
|
|
190
209
|
for (k, v) in params_num.iter() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
// Candidate patterns in order of preference
|
|
211
|
+
let candidates = [
|
|
212
|
+
format!("set_synth_{}", k),
|
|
213
|
+
format!("set_{}", k),
|
|
214
|
+
format!("set_note_{}", k),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
let mut any_called = false;
|
|
218
|
+
|
|
219
|
+
// If exported_names provided, try only declared exports first
|
|
220
|
+
if let Some(exports) = exported_names {
|
|
221
|
+
logger.log_message(
|
|
222
|
+
LogLevel::Debug,
|
|
223
|
+
&format!("Plugin exports provided ({} names)", exports.len()),
|
|
224
|
+
);
|
|
225
|
+
for c in &candidates {
|
|
226
|
+
if exports.iter().any(|e| e == c) {
|
|
227
|
+
match instance.get_typed_func::<f32, ()>(&mut store, c) {
|
|
228
|
+
Ok(setter) => {
|
|
229
|
+
let _ = setter.call(&mut store, *v);
|
|
230
|
+
any_called = true;
|
|
231
|
+
logger.log_message(
|
|
232
|
+
LogLevel::Debug,
|
|
233
|
+
&format!("Called setter '{}' with {}", c, v),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
Err(_) => {
|
|
237
|
+
logger.log_message(
|
|
238
|
+
LogLevel::Debug,
|
|
239
|
+
&format!("Export '{}' declared but signature lookup failed", c),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// If nothing was called using the metadata, fall back to dynamic lookup
|
|
248
|
+
if !any_called {
|
|
249
|
+
for fname in &candidates {
|
|
250
|
+
match instance.get_typed_func::<f32, ()>(&mut store, fname) {
|
|
251
|
+
Ok(setter) => {
|
|
252
|
+
let _ = setter.call(&mut store, *v);
|
|
253
|
+
any_called = true;
|
|
254
|
+
logger.log_message(
|
|
255
|
+
LogLevel::Debug,
|
|
256
|
+
&format!("Dynamically called setter '{}' with {}", fname, v),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
Err(_) => {
|
|
260
|
+
// no-op, continue
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if !any_called {
|
|
267
|
+
logger.log_message(
|
|
268
|
+
LogLevel::Warning,
|
|
269
|
+
&format!("No setter found/called for numeric param '{}'", k),
|
|
270
|
+
);
|
|
194
271
|
}
|
|
195
272
|
}
|
|
196
273
|
|
|
197
|
-
// Call string setters if present: set_<param>_str
|
|
274
|
+
// Call string setters if present: support set_synth_<param>_str, set_<param>_str, set_note_<param>_str
|
|
198
275
|
if let Some(smap) = params_str {
|
|
199
276
|
for (k, v) in smap.iter() {
|
|
200
|
-
let
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
277
|
+
let candidates = [
|
|
278
|
+
format!("set_synth_{}_str", k),
|
|
279
|
+
format!("set_{}_str", k),
|
|
280
|
+
format!("set_note_{}_str", k),
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
let logger = Logger::new();
|
|
284
|
+
for (k, v) in smap.iter() {
|
|
285
|
+
let candidates = [
|
|
286
|
+
format!("set_synth_{}_str", k),
|
|
287
|
+
format!("set_{}_str", k),
|
|
288
|
+
format!("set_note_{}_str", k),
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
let mut any_called = false;
|
|
292
|
+
|
|
293
|
+
if let Some(exports) = exported_names {
|
|
294
|
+
for c in &candidates {
|
|
295
|
+
if exports.iter().any(|e| e == c) {
|
|
296
|
+
match instance.get_typed_func::<(i32, i32), ()>(&mut store, c) {
|
|
297
|
+
Ok(setter) => {
|
|
298
|
+
let bytes = v.as_bytes();
|
|
299
|
+
let ptr = Self::alloc_temp(
|
|
300
|
+
&mut store,
|
|
301
|
+
&instance,
|
|
302
|
+
&memory,
|
|
303
|
+
bytes.len(),
|
|
304
|
+
)? as i32;
|
|
305
|
+
let mem_slice = memory
|
|
306
|
+
.data_mut(&mut store)
|
|
307
|
+
.get_mut(ptr as usize..(ptr as usize) + bytes.len())
|
|
308
|
+
.ok_or_else(|| {
|
|
309
|
+
"Failed to get memory slice for string".to_string()
|
|
310
|
+
})?;
|
|
311
|
+
mem_slice.copy_from_slice(bytes);
|
|
312
|
+
let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
|
|
313
|
+
any_called = true;
|
|
314
|
+
logger.log_message(
|
|
315
|
+
LogLevel::Debug,
|
|
316
|
+
&format!("Called string setter '{}' with '{}'", c, v),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
Err(_) => {
|
|
320
|
+
logger.log_message(
|
|
321
|
+
LogLevel::Debug,
|
|
322
|
+
&format!(
|
|
323
|
+
"Export '{}' declared but signature lookup failed",
|
|
324
|
+
c
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if !any_called {
|
|
334
|
+
for fname in &candidates {
|
|
335
|
+
if let Ok(setter) =
|
|
336
|
+
instance.get_typed_func::<(i32, i32), ()>(&mut store, fname)
|
|
337
|
+
{
|
|
338
|
+
let bytes = v.as_bytes();
|
|
339
|
+
let ptr =
|
|
340
|
+
Self::alloc_temp(&mut store, &instance, &memory, bytes.len())?
|
|
341
|
+
as i32;
|
|
342
|
+
let mem_slice = memory
|
|
343
|
+
.data_mut(&mut store)
|
|
344
|
+
.get_mut(ptr as usize..(ptr as usize) + bytes.len())
|
|
345
|
+
.ok_or_else(|| {
|
|
346
|
+
"Failed to get memory slice for string".to_string()
|
|
347
|
+
})?;
|
|
348
|
+
mem_slice.copy_from_slice(bytes);
|
|
349
|
+
let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
|
|
350
|
+
any_called = true;
|
|
351
|
+
logger.log_message(
|
|
352
|
+
LogLevel::Debug,
|
|
353
|
+
&format!(
|
|
354
|
+
"Dynamically called string setter '{}' with '{}'",
|
|
355
|
+
fname, v
|
|
356
|
+
),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if !any_called {
|
|
363
|
+
logger.log_message(
|
|
364
|
+
LogLevel::Warning,
|
|
365
|
+
&format!("No string setter found/called for param '{}'", k),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
211
368
|
}
|
|
212
369
|
}
|
|
213
370
|
}
|
|
214
371
|
|
|
215
|
-
// Try specific
|
|
372
|
+
// Try specific + generic entry points in order of preference.
|
|
373
|
+
// 1) render_note_<name>
|
|
374
|
+
// 2) render_note
|
|
375
|
+
// 3) synth_<name>
|
|
376
|
+
// 4) synth
|
|
216
377
|
let mut func_opt: Option<RenderFunc> = None;
|
|
217
378
|
if let Some(name) = synth_name {
|
|
218
379
|
let specific = format!("render_note_{}", name);
|
|
@@ -230,8 +391,26 @@ impl WasmPluginRunner {
|
|
|
230
391
|
func_opt = Some(f);
|
|
231
392
|
}
|
|
232
393
|
}
|
|
233
|
-
|
|
234
|
-
|
|
394
|
+
if func_opt.is_none() {
|
|
395
|
+
if let Some(name) = synth_name {
|
|
396
|
+
let specific = format!("synth_{}", name);
|
|
397
|
+
if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
|
|
398
|
+
&mut store, &specific,
|
|
399
|
+
) {
|
|
400
|
+
func_opt = Some(f);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if func_opt.is_none() {
|
|
405
|
+
if let Ok(f) = instance
|
|
406
|
+
.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, "synth")
|
|
407
|
+
{
|
|
408
|
+
func_opt = Some(f);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
let func = func_opt.ok_or_else(|| {
|
|
412
|
+
"Exported function not found: tried render_note[_<name>] and synth[_<name>]".to_string()
|
|
413
|
+
})?;
|
|
235
414
|
|
|
236
415
|
// Copy host buffer into wasm memory
|
|
237
416
|
let byte_len = std::mem::size_of_val(buffer) as i32;
|
|
@@ -300,48 +479,3 @@ impl WasmPluginRunner {
|
|
|
300
479
|
Ok(current_len)
|
|
301
480
|
}
|
|
302
481
|
}
|
|
303
|
-
|
|
304
|
-
// Provide a minimal stub for wasm32 target so the crate compiles there.
|
|
305
|
-
#[cfg(target_arch = "wasm32")]
|
|
306
|
-
pub struct WasmPluginRunner;
|
|
307
|
-
|
|
308
|
-
#[cfg(target_arch = "wasm32")]
|
|
309
|
-
impl WasmPluginRunner {
|
|
310
|
-
pub fn new() -> Self {
|
|
311
|
-
WasmPluginRunner
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
pub fn process_in_place(&self, _wasm_bytes: &[u8], _buffer: &mut [f32]) -> Result<(), String> {
|
|
315
|
-
Err("Wasm plugin execution is not available in wasm builds".to_string())
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
pub fn render_note_in_place(
|
|
319
|
-
&self,
|
|
320
|
-
_wasm_bytes: &[u8],
|
|
321
|
-
_buffer: &mut [f32],
|
|
322
|
-
_synth_name: Option<&str>,
|
|
323
|
-
_freq: f32,
|
|
324
|
-
_amp: f32,
|
|
325
|
-
_duration_ms: i32,
|
|
326
|
-
_sample_rate: i32,
|
|
327
|
-
_channels: i32,
|
|
328
|
-
) -> Result<(), String> {
|
|
329
|
-
Err("Wasm plugin rendering is not available in wasm builds".to_string())
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
pub fn render_note_with_params_in_place(
|
|
333
|
-
&self,
|
|
334
|
-
_wasm_bytes: &[u8],
|
|
335
|
-
_buffer: &mut [f32],
|
|
336
|
-
_synth_name: Option<&str>,
|
|
337
|
-
_freq: f32,
|
|
338
|
-
_amp: f32,
|
|
339
|
-
_duration_ms: i32,
|
|
340
|
-
_sample_rate: i32,
|
|
341
|
-
_channels: i32,
|
|
342
|
-
_params_num: &HashMap<String, f32>,
|
|
343
|
-
_params_str: Option<&HashMap<String, String>>,
|
|
344
|
-
) -> Result<(), String> {
|
|
345
|
-
Err("Wasm plugin rendering is not available in wasm builds".to_string())
|
|
346
|
-
}
|
|
347
|
-
}
|