@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.
Files changed (207) hide show
  1. package/.devalang +9 -10
  2. package/Cargo.toml +84 -80
  3. package/README.md +10 -7
  4. package/docs/CHANGELOG.md +83 -0
  5. package/docs/ROADMAP.md +6 -2
  6. package/docs/TODO.md +3 -14
  7. package/examples/bus.deva +10 -0
  8. package/examples/chain.deva +19 -0
  9. package/examples/effect.deva +2 -0
  10. package/examples/filter.deva +11 -0
  11. package/examples/lfo.deva +9 -0
  12. package/examples/plugin.deva +10 -10
  13. package/examples/routing.deva +23 -0
  14. package/examples/synth.deva +11 -1
  15. package/examples/synth_types.deva +17 -0
  16. package/out-tsc/bin/project-version.json +6 -0
  17. package/out-tsc/core/functions/index.d.ts +5 -0
  18. package/out-tsc/core/functions/index.js +11 -0
  19. package/out-tsc/pkg/devalang_core.d.ts +2 -0
  20. package/out-tsc/pkg/devalang_core.js +17 -2
  21. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +1 -0
  22. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  23. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  24. package/package.json +23 -10
  25. package/project-version.json +3 -3
  26. package/rust/bindings/Cargo.toml +9 -0
  27. package/rust/bindings/src/lib.rs +86 -0
  28. package/rust/cli/addon/commands.rs +35 -0
  29. package/rust/cli/addon/download.rs +234 -0
  30. package/rust/cli/addon/install.rs +33 -0
  31. package/rust/cli/addon/list.rs +224 -0
  32. package/rust/cli/addon/metadata.rs +124 -0
  33. package/rust/cli/addon/mod.rs +8 -0
  34. package/rust/cli/addon/remove.rs +271 -0
  35. package/rust/cli/addon/update.rs +305 -0
  36. package/rust/cli/{install/addon.rs → addon/utils.rs} +34 -43
  37. package/rust/cli/build/commands.rs +153 -103
  38. package/rust/cli/build/mod.rs +2 -2
  39. package/rust/cli/build/process.rs +165 -146
  40. package/rust/cli/check/mod.rs +208 -208
  41. package/rust/cli/discover/commands.rs +53 -31
  42. package/rust/cli/discover/config.rs +2 -4
  43. package/rust/cli/discover/install.rs +139 -28
  44. package/rust/cli/discover/metadata.rs +3 -3
  45. package/rust/cli/login/commands.rs +124 -124
  46. package/rust/cli/me/commands.rs +52 -0
  47. package/rust/cli/me/mod.rs +1 -0
  48. package/rust/cli/mod.rs +2 -2
  49. package/rust/cli/parser.rs +76 -70
  50. package/rust/cli/play/commands.rs +375 -324
  51. package/rust/cli/play/mod.rs +5 -5
  52. package/rust/cli/play/process.rs +159 -150
  53. package/rust/cli/play/realtime.rs +91 -91
  54. package/rust/cli/telemetry/commands.rs +22 -22
  55. package/rust/cli/telemetry/event_creator.rs +80 -80
  56. package/rust/cli/telemetry/mod.rs +3 -3
  57. package/rust/cli/telemetry/send.rs +51 -51
  58. package/rust/cli/template/commands.rs +69 -69
  59. package/rust/config/driver.rs +112 -103
  60. package/rust/config/mod.rs +3 -3
  61. package/rust/config/ops.rs +26 -26
  62. package/rust/config/settings.rs +101 -101
  63. package/rust/core/audio/engine/driver.rs +237 -0
  64. package/rust/core/audio/engine/export.rs +169 -0
  65. package/rust/core/audio/engine/helpers.rs +178 -170
  66. package/rust/core/audio/engine/mod.rs +56 -7
  67. package/rust/core/audio/engine/notes/dsp.rs +88 -0
  68. package/rust/core/audio/engine/notes/mod.rs +53 -0
  69. package/rust/core/audio/engine/notes/params.rs +294 -0
  70. package/rust/core/audio/engine/sample/insert.rs +300 -0
  71. package/rust/core/audio/engine/sample/mod.rs +40 -0
  72. package/rust/core/audio/engine/sample/padding.rs +170 -0
  73. package/rust/core/audio/evaluator/condition.rs +61 -0
  74. package/rust/core/audio/evaluator/mod.rs +9 -0
  75. package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +152 -310
  76. package/rust/core/audio/evaluator/rhs.rs +16 -0
  77. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  78. package/rust/core/audio/interpreter/driver.rs +574 -542
  79. package/rust/core/audio/interpreter/mod.rs +2 -14
  80. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +179 -0
  81. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -0
  82. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  83. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +3 -0
  84. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +371 -0
  85. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  86. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  87. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  88. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  89. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  90. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  91. package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +2 -4
  92. package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +36 -5
  93. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -71
  94. package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +24 -26
  95. package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +36 -38
  96. package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +17 -19
  97. package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +115 -114
  98. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  99. package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
  100. package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +54 -4
  101. package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
  102. package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +242 -239
  103. package/rust/core/audio/loader/trigger.rs +98 -97
  104. package/rust/core/audio/mod.rs +6 -7
  105. package/rust/core/audio/special/easing.rs +189 -189
  106. package/rust/core/audio/special/env.rs +45 -45
  107. package/rust/core/audio/special/math.rs +134 -134
  108. package/rust/core/audio/special/modulator.rs +143 -143
  109. package/rust/core/builder/mod.rs +129 -86
  110. package/rust/core/debugger/{module.rs → logs.rs} +52 -55
  111. package/rust/core/debugger/mod.rs +30 -30
  112. package/rust/core/debugger/store.rs +38 -40
  113. package/rust/core/error/mod.rs +269 -269
  114. package/rust/core/lexer/driver.rs +2 -4
  115. package/rust/core/mod.rs +9 -10
  116. package/rust/core/parser/driver/block.rs +111 -0
  117. package/rust/core/parser/driver/cursor.rs +82 -0
  118. package/rust/core/parser/driver/driver_impl.rs +159 -0
  119. package/rust/core/parser/driver/mod.rs +6 -0
  120. package/rust/core/parser/driver/parse_array.rs +120 -0
  121. package/rust/core/parser/driver/parse_map.rs +247 -0
  122. package/rust/core/parser/driver/parser.rs +160 -0
  123. package/rust/core/parser/handler/arrow_call.rs +90 -15
  124. package/rust/core/parser/handler/at.rs +279 -279
  125. package/rust/core/parser/handler/bank.rs +104 -104
  126. package/rust/core/parser/handler/condition.rs +83 -83
  127. package/rust/core/parser/handler/dot.rs +148 -148
  128. package/rust/core/parser/handler/identifier/automate.rs +254 -254
  129. package/rust/core/parser/handler/identifier/call.rs +91 -91
  130. package/rust/core/parser/handler/identifier/emit.rs +70 -70
  131. package/rust/core/parser/handler/identifier/function.rs +113 -113
  132. package/rust/core/parser/handler/identifier/group.rs +89 -89
  133. package/rust/core/parser/handler/identifier/let_.rs +173 -173
  134. package/rust/core/parser/handler/identifier/mod.rs +55 -55
  135. package/rust/core/parser/handler/identifier/on.rs +107 -107
  136. package/rust/core/parser/handler/identifier/print.rs +49 -49
  137. package/rust/core/parser/handler/identifier/sleep.rs +96 -43
  138. package/rust/core/parser/handler/identifier/spawn.rs +91 -91
  139. package/rust/core/parser/handler/identifier/synth.rs +39 -3
  140. package/rust/core/parser/handler/loop_.rs +194 -194
  141. package/rust/core/parser/handler/pattern.rs +25 -2
  142. package/rust/core/parser/handler/tempo.rs +105 -57
  143. package/rust/core/parser/statement.rs +10 -11
  144. package/rust/core/plugin/loader.rs +137 -137
  145. package/rust/core/plugin/runner/mod.rs +11 -0
  146. package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +206 -72
  147. package/rust/core/plugin/runner/wasm32.rs +44 -0
  148. package/rust/core/preprocessor/loader/inject.rs +313 -0
  149. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  150. package/rust/core/preprocessor/loader/mod.rs +235 -0
  151. package/rust/core/preprocessor/module.rs +55 -60
  152. package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +107 -114
  153. package/rust/core/preprocessor/processor/mod.rs +1 -0
  154. package/rust/core/preprocessor/resolver/function.rs +69 -69
  155. package/rust/core/preprocessor/resolver/group.rs +122 -94
  156. package/rust/core/preprocessor/resolver/pattern.rs +14 -2
  157. package/rust/core/store/global.rs +57 -61
  158. package/rust/core/store/mod.rs +1 -5
  159. package/rust/lib.rs +323 -308
  160. package/rust/macros/Cargo.toml +14 -0
  161. package/rust/macros/src/lib.rs +52 -0
  162. package/rust/main.rs +336 -143
  163. package/rust/types/Cargo.toml +1 -1
  164. package/rust/types/src/addons.rs +57 -55
  165. package/rust/types/src/config.rs +82 -74
  166. package/rust/types/src/lib.rs +15 -12
  167. package/rust/types/src/plugin.rs +20 -0
  168. package/rust/types/src/store.rs +139 -0
  169. package/rust/types/src/telemetry.rs +85 -85
  170. package/rust/utils/Cargo.toml +5 -2
  171. package/rust/utils/src/file.rs +477 -94
  172. package/rust/utils/src/first_usage.rs +97 -97
  173. package/rust/utils/src/lib.rs +9 -9
  174. package/rust/utils/src/logger.rs +200 -200
  175. package/rust/utils/src/path.rs +158 -88
  176. package/rust/utils/src/signature.rs +41 -41
  177. package/rust/utils/src/spinner.rs +20 -20
  178. package/rust/utils/src/version.rs +58 -27
  179. package/rust/utils/src/watcher.rs +46 -46
  180. package/rust/web/api.rs +5 -5
  181. package/rust/web/auth.rs +5 -0
  182. package/rust/web/cdn.rs +34 -34
  183. package/rust/web/forge.rs +5 -0
  184. package/rust/web/mod.rs +2 -0
  185. package/tests/integration.rs +21 -21
  186. package/typescript/core/functions/index.ts +11 -0
  187. package/typescript/pkg/devalang_core.ts +20 -4
  188. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  189. package/rust/cli/bank/api.rs +0 -122
  190. package/rust/cli/bank/commands.rs +0 -275
  191. package/rust/cli/bank/mod.rs +0 -29
  192. package/rust/cli/install/bank.rs +0 -53
  193. package/rust/cli/install/commands.rs +0 -35
  194. package/rust/cli/install/mod.rs +0 -4
  195. package/rust/cli/install/plugin.rs +0 -61
  196. package/rust/core/audio/engine/sample.rs +0 -366
  197. package/rust/core/audio/engine/synth.rs +0 -325
  198. package/rust/core/audio/interpreter/arrow_call.rs +0 -311
  199. package/rust/core/audio/renderer.rs +0 -54
  200. package/rust/core/parser/driver.rs +0 -584
  201. package/rust/core/preprocessor/loader.rs +0 -637
  202. package/rust/core/store/export.rs +0 -28
  203. package/rust/core/store/function.rs +0 -40
  204. package/rust/core/store/import.rs +0 -28
  205. package/rust/core/store/variable.rs +0 -51
  206. package/rust/core/utils/mod.rs +0 -1
  207. package/rust/core/utils/path.rs +0 -37
@@ -1,11 +1,10 @@
1
- use crate::core::lexer::token::Token;
2
-
3
- pub use devalang_types::{Duration, Statement, StatementKind, Value};
4
-
5
- pub fn unknown_from_token(token: &Token) -> Statement {
6
- Statement::unknown_with_pos(token.indent, token.line, token.column)
7
- }
8
-
9
- pub fn error_from_token(token: Token, message: String) -> Statement {
10
- Statement::error_with_pos(token.indent, token.line, token.column, message)
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
- #[serde(rename = "type")]
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 export: 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.export.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
+ 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
+ }
@@ -0,0 +1,11 @@
1
+ #[cfg(not(target_arch = "wasm32"))]
2
+ mod non_wasm;
3
+
4
+ #[cfg(target_arch = "wasm32")]
5
+ mod wasm32;
6
+
7
+ #[cfg(not(target_arch = "wasm32"))]
8
+ pub use non_wasm::WasmPluginRunner;
9
+
10
+ #[cfg(target_arch = "wasm32")]
11
+ pub use wasm32::WasmPluginRunner;
@@ -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 function first
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
- func_opt.ok_or_else(|| "Exported function `render_note` not found".to_string())?;
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
- let fname = format!("set_{}", k);
192
- if let Ok(setter) = instance.get_typed_func::<f32, ()>(&mut store, &fname) {
193
- let _ = setter.call(&mut store, *v);
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(ptr: i32, len: i32)
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 fname = format!("set_{}_str", k);
201
- if let Ok(setter) = instance.get_typed_func::<(i32, i32), ()>(&mut store, &fname) {
202
- // Allocate and copy UTF-8 bytes into wasm memory
203
- let bytes = v.as_bytes();
204
- let ptr = Self::alloc_temp(&mut store, &instance, &memory, bytes.len())? as i32;
205
- let mem_slice = memory
206
- .data_mut(&mut store)
207
- .get_mut(ptr as usize..(ptr as usize) + bytes.len())
208
- .ok_or_else(|| "Failed to get memory slice for string".to_string())?;
209
- mem_slice.copy_from_slice(bytes);
210
- let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
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 or generic render function
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
- let func =
234
- func_opt.ok_or_else(|| "Exported function `render_note` not found".to_string())?;
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
- }