@devaloop/devalang 0.0.1-beta.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -152
- package/out-tsc/api.d.ts +180 -0
- package/out-tsc/api.d.ts.map +1 -0
- package/out-tsc/api.js +286 -0
- package/out-tsc/api.js.map +1 -0
- package/out-tsc/bin/index.d.ts +12 -0
- package/out-tsc/bin/index.d.ts.map +1 -0
- package/out-tsc/bin/index.js +20 -54
- package/out-tsc/bin/index.js.map +1 -0
- package/out-tsc/examples/basic-usage.d.ts +8 -0
- package/out-tsc/examples/basic-usage.d.ts.map +1 -0
- package/out-tsc/examples/basic-usage.js +113 -0
- package/out-tsc/examples/basic-usage.js.map +1 -0
- package/out-tsc/index.d.ts +19 -5
- package/out-tsc/index.d.ts.map +1 -0
- package/out-tsc/index.js +24 -6
- package/out-tsc/index.js.map +1 -0
- package/out-tsc/scripts/copy-wasm-dts.d.ts +7 -0
- package/out-tsc/scripts/copy-wasm-dts.d.ts.map +1 -0
- package/out-tsc/scripts/copy-wasm-dts.js +36 -32
- package/out-tsc/scripts/copy-wasm-dts.js.map +1 -0
- package/out-tsc/scripts/postinstall.d.ts +1 -0
- package/out-tsc/scripts/postinstall.d.ts.map +1 -0
- package/out-tsc/scripts/postinstall.js +4 -1
- package/out-tsc/scripts/postinstall.js.map +1 -0
- package/out-tsc/scripts/version/bump.d.ts +5 -1
- package/out-tsc/scripts/version/bump.d.ts.map +1 -0
- package/out-tsc/scripts/version/bump.js +114 -44
- package/out-tsc/scripts/version/bump.js.map +1 -0
- package/out-tsc/scripts/version/fetch.d.ts +12 -1
- package/out-tsc/scripts/version/fetch.d.ts.map +1 -0
- package/out-tsc/scripts/version/fetch.js +68 -24
- package/out-tsc/scripts/version/fetch.js.map +1 -0
- package/out-tsc/scripts/version/index.d.ts +6 -0
- package/out-tsc/scripts/version/index.d.ts.map +1 -0
- package/out-tsc/scripts/version/index.js +44 -22
- package/out-tsc/scripts/version/index.js.map +1 -0
- package/out-tsc/scripts/version/sync.d.ts +5 -1
- package/out-tsc/scripts/version/sync.d.ts.map +1 -0
- package/out-tsc/scripts/version/sync.js +78 -29
- package/out-tsc/scripts/version/sync.js.map +1 -0
- package/out-tsc/types.d.ts +68 -0
- package/out-tsc/types.d.ts.map +1 -0
- package/out-tsc/{core/types/value.js → types.js} +4 -0
- package/out-tsc/types.js.map +1 -0
- package/out-tsc/wasm.d.ts +8 -0
- package/out-tsc/wasm.d.ts.map +1 -0
- package/out-tsc/{core/index.js → wasm.js} +9 -6
- package/out-tsc/wasm.js.map +1 -0
- package/package.json +42 -42
- package/.cargo/config.toml +0 -2
- package/.devalang +0 -9
- package/.github/workflows/ci.yml +0 -103
- package/Cargo.toml +0 -84
- package/docs/CHANGELOG.md +0 -622
- package/docs/CONTRIBUTING.md +0 -101
- package/docs/ROADMAP.md +0 -38
- package/docs/TODO.md +0 -71
- package/examples/automation.deva +0 -42
- package/examples/bank.deva +0 -7
- package/examples/bus.deva +0 -10
- package/examples/chain.deva +0 -19
- package/examples/condition.deva +0 -20
- package/examples/duration.deva +0 -9
- package/examples/effect.deva +0 -2
- package/examples/events.deva +0 -12
- package/examples/filter.deva +0 -11
- package/examples/function.deva +0 -15
- package/examples/group.deva +0 -12
- package/examples/index.deva +0 -63
- package/examples/lfo.deva +0 -9
- package/examples/loop.deva +0 -10
- package/examples/pattern.deva +0 -8
- package/examples/plugin.deva +0 -16
- package/examples/routing.deva +0 -23
- package/examples/samples/hat-808.wav +0 -0
- package/examples/samples/kick-808.wav +0 -0
- package/examples/synth.deva +0 -24
- package/examples/synth_types.deva +0 -17
- package/examples/variables.deva +0 -9
- package/out-tsc/bin/project-version.json +0 -6
- package/out-tsc/core/functions/index.d.ts +0 -42
- package/out-tsc/core/functions/index.js +0 -87
- package/out-tsc/core/index.d.ts +0 -6
- package/out-tsc/core/types/index.d.ts +0 -4
- package/out-tsc/core/types/index.js +0 -20
- package/out-tsc/core/types/plugin.d.ts +0 -18
- package/out-tsc/core/types/plugin.js +0 -2
- package/out-tsc/core/types/result.d.ts +0 -27
- package/out-tsc/core/types/result.js +0 -2
- package/out-tsc/core/types/statement.d.ts +0 -106
- package/out-tsc/core/types/statement.js +0 -2
- package/out-tsc/core/types/value.d.ts +0 -43
- package/out-tsc/pkg/devalang_core.d.ts +0 -15
- package/out-tsc/pkg/devalang_core.js +0 -65
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +0 -34
- package/out-tsc/scripts/version/copy-to-binary.d.ts +0 -1
- package/out-tsc/scripts/version/copy-to-binary.js +0 -79
- package/project-version.json +0 -6
- package/rust/bindings/Cargo.toml +0 -9
- package/rust/bindings/src/lib.rs +0 -86
- package/rust/cli/addon/commands.rs +0 -35
- package/rust/cli/addon/download.rs +0 -234
- package/rust/cli/addon/install.rs +0 -33
- package/rust/cli/addon/list.rs +0 -224
- package/rust/cli/addon/metadata.rs +0 -124
- package/rust/cli/addon/mod.rs +0 -8
- package/rust/cli/addon/remove.rs +0 -271
- package/rust/cli/addon/update.rs +0 -305
- package/rust/cli/addon/utils.rs +0 -109
- package/rust/cli/build/commands.rs +0 -153
- package/rust/cli/build/mod.rs +0 -2
- package/rust/cli/build/process.rs +0 -165
- package/rust/cli/check/mod.rs +0 -208
- package/rust/cli/discover/commands.rs +0 -275
- package/rust/cli/discover/config.rs +0 -109
- package/rust/cli/discover/fs.rs +0 -19
- package/rust/cli/discover/install.rs +0 -214
- package/rust/cli/discover/metadata.rs +0 -48
- package/rust/cli/discover/mod.rs +0 -5
- package/rust/cli/init/commands.rs +0 -88
- package/rust/cli/init/mod.rs +0 -1
- package/rust/cli/login/commands.rs +0 -124
- package/rust/cli/login/mod.rs +0 -1
- package/rust/cli/me/commands.rs +0 -52
- package/rust/cli/me/mod.rs +0 -1
- package/rust/cli/mod.rs +0 -12
- package/rust/cli/parser.rs +0 -320
- package/rust/cli/play/commands.rs +0 -375
- package/rust/cli/play/io.rs +0 -17
- package/rust/cli/play/mod.rs +0 -5
- package/rust/cli/play/process.rs +0 -159
- package/rust/cli/play/realtime.rs +0 -91
- package/rust/cli/play/utils.rs +0 -23
- package/rust/cli/telemetry/commands.rs +0 -22
- package/rust/cli/telemetry/event_creator.rs +0 -80
- package/rust/cli/telemetry/mod.rs +0 -3
- package/rust/cli/telemetry/send.rs +0 -51
- package/rust/cli/template/commands.rs +0 -69
- package/rust/cli/template/mod.rs +0 -1
- package/rust/cli/update/commands.rs +0 -6
- package/rust/cli/update/mod.rs +0 -1
- package/rust/config/driver.rs +0 -112
- package/rust/config/mod.rs +0 -3
- package/rust/config/ops.rs +0 -26
- package/rust/config/settings.rs +0 -101
- package/rust/core/audio/engine/driver.rs +0 -237
- package/rust/core/audio/engine/export.rs +0 -169
- package/rust/core/audio/engine/helpers.rs +0 -178
- package/rust/core/audio/engine/mod.rs +0 -56
- package/rust/core/audio/engine/notes/dsp.rs +0 -88
- package/rust/core/audio/engine/notes/mod.rs +0 -53
- package/rust/core/audio/engine/notes/params.rs +0 -294
- package/rust/core/audio/engine/sample/insert.rs +0 -300
- package/rust/core/audio/engine/sample/mod.rs +0 -40
- package/rust/core/audio/engine/sample/padding.rs +0 -170
- package/rust/core/audio/evaluator/condition.rs +0 -61
- package/rust/core/audio/evaluator/mod.rs +0 -9
- package/rust/core/audio/evaluator/numeric.rs +0 -152
- package/rust/core/audio/evaluator/rhs.rs +0 -16
- package/rust/core/audio/evaluator/string_expr.rs +0 -94
- package/rust/core/audio/interpreter/driver.rs +0 -574
- package/rust/core/audio/interpreter/mod.rs +0 -2
- package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +0 -179
- package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +0 -398
- package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +0 -323
- package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +0 -3
- package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +0 -371
- package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +0 -3
- package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +0 -192
- package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +0 -24
- package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +0 -116
- package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +0 -97
- package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +0 -100
- package/rust/core/audio/interpreter/statements/automate.rs +0 -16
- package/rust/core/audio/interpreter/statements/call.rs +0 -325
- package/rust/core/audio/interpreter/statements/condition.rs +0 -72
- package/rust/core/audio/interpreter/statements/function.rs +0 -24
- package/rust/core/audio/interpreter/statements/let_.rs +0 -36
- package/rust/core/audio/interpreter/statements/load.rs +0 -17
- package/rust/core/audio/interpreter/statements/loop_.rs +0 -115
- package/rust/core/audio/interpreter/statements/mod.rs +0 -12
- package/rust/core/audio/interpreter/statements/sleep.rs +0 -28
- package/rust/core/audio/interpreter/statements/spawn.rs +0 -302
- package/rust/core/audio/interpreter/statements/tempo.rs +0 -40
- package/rust/core/audio/interpreter/statements/trigger.rs +0 -242
- package/rust/core/audio/loader/mod.rs +0 -1
- package/rust/core/audio/loader/trigger.rs +0 -98
- package/rust/core/audio/mod.rs +0 -6
- package/rust/core/audio/player.rs +0 -70
- package/rust/core/audio/special/easing.rs +0 -189
- package/rust/core/audio/special/env.rs +0 -45
- package/rust/core/audio/special/math.rs +0 -134
- package/rust/core/audio/special/mod.rs +0 -9
- package/rust/core/audio/special/modulator.rs +0 -143
- package/rust/core/builder/mod.rs +0 -129
- package/rust/core/debugger/lexer.rs +0 -27
- package/rust/core/debugger/logs.rs +0 -52
- package/rust/core/debugger/mod.rs +0 -30
- package/rust/core/debugger/preprocessor.rs +0 -27
- package/rust/core/debugger/store.rs +0 -38
- package/rust/core/error/mod.rs +0 -269
- package/rust/core/lexer/driver.rs +0 -59
- package/rust/core/lexer/handler/arrow.rs +0 -82
- package/rust/core/lexer/handler/at.rs +0 -21
- package/rust/core/lexer/handler/brace.rs +0 -41
- package/rust/core/lexer/handler/colon.rs +0 -21
- package/rust/core/lexer/handler/comment.rs +0 -30
- package/rust/core/lexer/handler/dot.rs +0 -21
- package/rust/core/lexer/handler/driver.rs +0 -337
- package/rust/core/lexer/handler/identifier.rs +0 -47
- package/rust/core/lexer/handler/indent.rs +0 -66
- package/rust/core/lexer/handler/mod.rs +0 -15
- package/rust/core/lexer/handler/newline.rs +0 -23
- package/rust/core/lexer/handler/number.rs +0 -31
- package/rust/core/lexer/handler/operator.rs +0 -46
- package/rust/core/lexer/handler/parenthesis.rs +0 -41
- package/rust/core/lexer/handler/slash.rs +0 -21
- package/rust/core/lexer/handler/string.rs +0 -63
- package/rust/core/lexer/mod.rs +0 -3
- package/rust/core/lexer/token.rs +0 -91
- package/rust/core/mod.rs +0 -9
- package/rust/core/parser/driver/block.rs +0 -111
- package/rust/core/parser/driver/cursor.rs +0 -82
- package/rust/core/parser/driver/driver_impl.rs +0 -159
- package/rust/core/parser/driver/mod.rs +0 -6
- package/rust/core/parser/driver/parse_array.rs +0 -120
- package/rust/core/parser/driver/parse_map.rs +0 -247
- package/rust/core/parser/driver/parser.rs +0 -160
- package/rust/core/parser/handler/arrow_call.rs +0 -328
- package/rust/core/parser/handler/at.rs +0 -279
- package/rust/core/parser/handler/bank.rs +0 -104
- package/rust/core/parser/handler/condition.rs +0 -83
- package/rust/core/parser/handler/dot.rs +0 -148
- package/rust/core/parser/handler/identifier/automate.rs +0 -254
- package/rust/core/parser/handler/identifier/call.rs +0 -91
- package/rust/core/parser/handler/identifier/emit.rs +0 -70
- package/rust/core/parser/handler/identifier/function.rs +0 -113
- package/rust/core/parser/handler/identifier/group.rs +0 -89
- package/rust/core/parser/handler/identifier/let_.rs +0 -173
- package/rust/core/parser/handler/identifier/mod.rs +0 -55
- package/rust/core/parser/handler/identifier/on.rs +0 -107
- package/rust/core/parser/handler/identifier/print.rs +0 -49
- package/rust/core/parser/handler/identifier/sleep.rs +0 -96
- package/rust/core/parser/handler/identifier/spawn.rs +0 -91
- package/rust/core/parser/handler/identifier/synth.rs +0 -171
- package/rust/core/parser/handler/loop_.rs +0 -194
- package/rust/core/parser/handler/mod.rs +0 -9
- package/rust/core/parser/handler/pattern.rs +0 -97
- package/rust/core/parser/handler/tempo.rs +0 -105
- package/rust/core/parser/mod.rs +0 -3
- package/rust/core/parser/statement.rs +0 -10
- package/rust/core/plugin/loader.rs +0 -137
- package/rust/core/plugin/mod.rs +0 -2
- package/rust/core/plugin/runner/mod.rs +0 -11
- package/rust/core/plugin/runner/non_wasm.rs +0 -481
- package/rust/core/plugin/runner/wasm32.rs +0 -44
- package/rust/core/preprocessor/loader/inject.rs +0 -313
- package/rust/core/preprocessor/loader/loader_helpers.rs +0 -110
- package/rust/core/preprocessor/loader/mod.rs +0 -235
- package/rust/core/preprocessor/mod.rs +0 -4
- package/rust/core/preprocessor/module.rs +0 -55
- package/rust/core/preprocessor/processor/handlers.rs +0 -107
- package/rust/core/preprocessor/processor/mod.rs +0 -1
- package/rust/core/preprocessor/resolver/bank.rs +0 -49
- package/rust/core/preprocessor/resolver/call.rs +0 -124
- package/rust/core/preprocessor/resolver/condition.rs +0 -95
- package/rust/core/preprocessor/resolver/driver.rs +0 -324
- package/rust/core/preprocessor/resolver/function.rs +0 -69
- package/rust/core/preprocessor/resolver/group.rs +0 -122
- package/rust/core/preprocessor/resolver/let_.rs +0 -32
- package/rust/core/preprocessor/resolver/loop_.rs +0 -318
- package/rust/core/preprocessor/resolver/mod.rs +0 -16
- package/rust/core/preprocessor/resolver/pattern.rs +0 -95
- package/rust/core/preprocessor/resolver/spawn.rs +0 -99
- package/rust/core/preprocessor/resolver/synth.rs +0 -54
- package/rust/core/preprocessor/resolver/tempo.rs +0 -48
- package/rust/core/preprocessor/resolver/trigger.rs +0 -116
- package/rust/core/preprocessor/resolver/value.rs +0 -176
- package/rust/core/store/global.rs +0 -57
- package/rust/core/store/mod.rs +0 -1
- package/rust/lib.rs +0 -323
- package/rust/macros/Cargo.toml +0 -14
- package/rust/macros/src/lib.rs +0 -52
- package/rust/main.rs +0 -557
- package/rust/types/Cargo.toml +0 -11
- package/rust/types/src/addons.rs +0 -57
- package/rust/types/src/ast.rs +0 -202
- package/rust/types/src/config.rs +0 -82
- package/rust/types/src/lib.rs +0 -15
- package/rust/types/src/plugin.rs +0 -20
- package/rust/types/src/store.rs +0 -139
- package/rust/types/src/telemetry.rs +0 -85
- package/rust/utils/Cargo.toml +0 -29
- package/rust/utils/src/error.rs +0 -186
- package/rust/utils/src/file.rs +0 -477
- package/rust/utils/src/first_usage.rs +0 -97
- package/rust/utils/src/lib.rs +0 -9
- package/rust/utils/src/logger.rs +0 -200
- package/rust/utils/src/path.rs +0 -158
- package/rust/utils/src/signature.rs +0 -41
- package/rust/utils/src/spinner.rs +0 -20
- package/rust/utils/src/version.rs +0 -58
- package/rust/utils/src/watcher.rs +0 -46
- package/rust/web/api.rs +0 -5
- package/rust/web/auth.rs +0 -5
- package/rust/web/cdn.rs +0 -34
- package/rust/web/forge.rs +0 -5
- package/rust/web/mod.rs +0 -5
- package/rust/web/sso.rs +0 -5
- package/templates/minimal/.devalang +0 -5
- package/templates/minimal/README.md +0 -218
- package/templates/minimal/src/index.deva +0 -2
- package/templates/welcome/.devalang +0 -5
- package/templates/welcome/README.md +0 -218
- package/templates/welcome/samples/kick-808.wav +0 -0
- package/templates/welcome/src/index.deva +0 -61
- package/templates/welcome/src/variables.deva +0 -3
- package/tests/integration.rs +0 -21
- package/tests/rust/cli_check_build.rs +0 -21
- package/tests/rust/cli_help.rs +0 -12
- package/tests/rust/cli_template_list.rs +0 -10
- package/tests/rust/cli_version.rs +0 -11
- package/tests/typescript/index.spec.ts +0 -136
- package/tests/typescript/playhead.spec.ts +0 -36
- package/tests/typescript/render_e2e.spec.ts +0 -77
- package/tsconfig.json +0 -115
- package/typescript/bin/index.ts +0 -28
- package/typescript/core/functions/index.ts +0 -94
- package/typescript/core/index.ts +0 -6
- package/typescript/core/types/index.ts +0 -4
- package/typescript/core/types/plugin.ts +0 -19
- package/typescript/core/types/result.ts +0 -29
- package/typescript/core/types/statement.ts +0 -47
- package/typescript/core/types/value.ts +0 -29
- package/typescript/index.ts +0 -8
- package/typescript/pkg/devalang_core.d.ts +0 -4
- package/typescript/pkg/devalang_core.ts +0 -65
- package/typescript/scripts/copy-wasm-dts.ts +0 -41
- package/typescript/scripts/postinstall.ts +0 -85
- package/typescript/scripts/version/bump.ts +0 -44
- package/typescript/scripts/version/copy-to-binary.ts +0 -82
- package/typescript/scripts/version/fetch.ts +0 -18
- package/typescript/scripts/version/index.ts +0 -25
- package/typescript/scripts/version/sync.ts +0 -24
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
use crate::core::audio::engine::AudioEngine;
|
|
2
|
-
use devalang_types::Value;
|
|
3
|
-
use devalang_utils::logger::{LogLevel, Logger};
|
|
4
|
-
use std::collections::HashMap;
|
|
5
|
-
|
|
6
|
-
fn parse_value_to_f32(v: &Value) -> Option<f32> {
|
|
7
|
-
match v {
|
|
8
|
-
Value::Number(n) => Some(*n as f32),
|
|
9
|
-
Value::String(s) => s.parse::<f32>().ok(),
|
|
10
|
-
Value::Identifier(s) => s.parse::<f32>().ok(),
|
|
11
|
-
Value::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
|
|
12
|
-
_ => None,
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Simple nearest-neighbour resampler that produces an output with the same frame count
|
|
17
|
-
// while applying a time-scaling factor per frame. channels = interleaved channel count.
|
|
18
|
-
fn resample_segment_nearest(
|
|
19
|
-
src: &[i16],
|
|
20
|
-
channels: usize,
|
|
21
|
-
start_rate: f32,
|
|
22
|
-
end_rate: f32,
|
|
23
|
-
) -> Vec<i16> {
|
|
24
|
-
if src.is_empty() || channels == 0 {
|
|
25
|
-
return Vec::new();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let frames = src.len() / channels;
|
|
29
|
-
if frames == 0 {
|
|
30
|
-
return Vec::new();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Copy source into frames x channels matrix access via frame*channels + ch
|
|
34
|
-
let mut out = vec![0i16; frames * channels];
|
|
35
|
-
|
|
36
|
-
for f in 0..frames {
|
|
37
|
-
let t = if frames > 1 {
|
|
38
|
-
f as f32 / (frames - 1) as f32
|
|
39
|
-
} else {
|
|
40
|
-
0.0
|
|
41
|
-
};
|
|
42
|
-
let rate = start_rate + t * (end_rate - start_rate);
|
|
43
|
-
let inv_rate = if rate == 0.0 { 1.0 } else { 1.0 / rate };
|
|
44
|
-
|
|
45
|
-
// determine source frame position (nearest)
|
|
46
|
-
let src_frame_pos = (f as f32 * inv_rate).clamp(0.0, (frames - 1) as f32);
|
|
47
|
-
let src_idx = src_frame_pos.round() as usize;
|
|
48
|
-
|
|
49
|
-
for ch in 0..channels {
|
|
50
|
-
let s_idx = src_idx.saturating_mul(channels).saturating_add(ch);
|
|
51
|
-
let o_idx = f.saturating_mul(channels).saturating_add(ch);
|
|
52
|
-
let sample = if s_idx < src.len() { src[s_idx] } else { 0 };
|
|
53
|
-
out[o_idx] = sample;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
out
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Basic effect application for chainable effects. This is intentionally minimal:
|
|
61
|
-
// - Accepts a target synth name and args parsed from the arrow call.
|
|
62
|
-
// - Applies effects by mutating the AudioEngine or scheduling transforms.
|
|
63
|
-
// Current effects implemented: echo, reverb, slide (as parameter modifiers).
|
|
64
|
-
|
|
65
|
-
pub fn apply_effect_chain(
|
|
66
|
-
method: &str,
|
|
67
|
-
args: &Vec<Value>,
|
|
68
|
-
target: &str,
|
|
69
|
-
audio_engine: &mut AudioEngine,
|
|
70
|
-
variable_table: &devalang_types::VariableTable,
|
|
71
|
-
) {
|
|
72
|
-
match method {
|
|
73
|
-
"echo" => apply_echo(args, target, audio_engine, variable_table),
|
|
74
|
-
"reverb" => apply_reverb(args, target, audio_engine, variable_table),
|
|
75
|
-
"slide" => apply_slide(args, target, audio_engine, variable_table),
|
|
76
|
-
"arp" => apply_arp_effect(args, target, audio_engine, variable_table),
|
|
77
|
-
_ => {
|
|
78
|
-
let logger = Logger::new();
|
|
79
|
-
logger.log_message(
|
|
80
|
-
LogLevel::Error,
|
|
81
|
-
&format!("Unknown chainable effect '{}' on '{}'.", method, target),
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
fn parse_map_arg(args: &Vec<Value>) -> Option<HashMap<String, Value>> {
|
|
88
|
-
// Typical usage: method({ key: value }) -> args[0] is a Map
|
|
89
|
-
if let Some(Value::Map(m)) = args.first() {
|
|
90
|
-
return Some(m.clone());
|
|
91
|
-
}
|
|
92
|
-
None
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fn apply_echo(
|
|
96
|
-
args: &Vec<Value>,
|
|
97
|
-
_target: &str,
|
|
98
|
-
engine: &mut AudioEngine,
|
|
99
|
-
_variable_table: &devalang_types::VariableTable,
|
|
100
|
-
) {
|
|
101
|
-
let map = parse_map_arg(args);
|
|
102
|
-
let mut delay_ms = 250.0_f32;
|
|
103
|
-
let mut feedback = 0.5_f32;
|
|
104
|
-
|
|
105
|
-
if let Some(m) = map {
|
|
106
|
-
if let Some(Value::Number(n)) = m.get("delay") {
|
|
107
|
-
delay_ms = *n as f32;
|
|
108
|
-
}
|
|
109
|
-
if let Some(Value::Number(n)) = m.get("feedback") {
|
|
110
|
-
feedback = *n as f32;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Very small and cheap echo: we will add a delayed, attenuated copy of the buffer
|
|
115
|
-
let sample_rate = engine.sample_rate as f32;
|
|
116
|
-
let channels = engine.channels as usize;
|
|
117
|
-
let delay_samples = ((delay_ms / 1000.0) * sample_rate) as usize * channels;
|
|
118
|
-
|
|
119
|
-
if delay_samples == 0 || engine.buffer.is_empty() {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Mix a single echo pass
|
|
124
|
-
let mut out = engine.buffer.clone();
|
|
125
|
-
for i in delay_samples..engine.buffer.len() {
|
|
126
|
-
let src = engine.buffer[i - delay_samples] as f32;
|
|
127
|
-
let added = (src * feedback)
|
|
128
|
-
.round()
|
|
129
|
-
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
130
|
-
out[i] = out[i].saturating_add(added);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
engine.buffer = out;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
fn apply_reverb(
|
|
137
|
-
args: &Vec<Value>,
|
|
138
|
-
_target: &str,
|
|
139
|
-
engine: &mut AudioEngine,
|
|
140
|
-
_variable_table: &devalang_types::VariableTable,
|
|
141
|
-
) {
|
|
142
|
-
let map = parse_map_arg(args);
|
|
143
|
-
let mut room_size = 0.5_f32;
|
|
144
|
-
if let Some(m) = map {
|
|
145
|
-
if let Some(Value::Number(n)) = m.get("room_size") {
|
|
146
|
-
room_size = *n as f32;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Cheap reverb: multiple short comb filters (very approximate)
|
|
151
|
-
if engine.buffer.is_empty() {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let sample_rate = engine.sample_rate as f32;
|
|
156
|
-
let channels = engine.channels as usize;
|
|
157
|
-
let reverb_delay_samples = ((0.03 * room_size) * sample_rate) as usize * channels;
|
|
158
|
-
if reverb_delay_samples == 0 {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let mut out = engine.buffer.clone();
|
|
163
|
-
for i in reverb_delay_samples..engine.buffer.len() {
|
|
164
|
-
let src = engine.buffer[i - reverb_delay_samples] as f32;
|
|
165
|
-
let added = (src * room_size * 0.5)
|
|
166
|
-
.round()
|
|
167
|
-
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
168
|
-
out[i] = out[i].saturating_add(added);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
engine.buffer = out;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
fn apply_slide(
|
|
175
|
-
args: &Vec<Value>,
|
|
176
|
-
_target: &str,
|
|
177
|
-
_engine: &mut AudioEngine,
|
|
178
|
-
_variable_table: &devalang_types::VariableTable,
|
|
179
|
-
) {
|
|
180
|
-
// Slide: apply a linear pitch glide across the most recently recorded note ranges
|
|
181
|
-
let map = parse_map_arg(args);
|
|
182
|
-
let mut from_semitones = 0.0_f32;
|
|
183
|
-
let mut to_semitones = 0.0_f32;
|
|
184
|
-
|
|
185
|
-
if let Some(m) = map {
|
|
186
|
-
if let Some(v) = m.get("from") {
|
|
187
|
-
if let Some(f) = parse_value_to_f32(v) {
|
|
188
|
-
from_semitones = f;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if let Some(v) = m.get("to") {
|
|
192
|
-
if let Some(t) = parse_value_to_f32(v) {
|
|
193
|
-
to_semitones = t;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Compute rate multipliers from semitone offsets
|
|
199
|
-
let start_rate = 2f32.powf(from_semitones / 12.0);
|
|
200
|
-
let end_rate = 2f32.powf(to_semitones / 12.0);
|
|
201
|
-
|
|
202
|
-
let channels = _engine.channels as usize;
|
|
203
|
-
if channels == 0 {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// For each recorded last note range for the target, apply a per-frame resample glide
|
|
208
|
-
if let Some(ranges) = _engine.last_notes.get(_target) {
|
|
209
|
-
for (start_sample, total_samples) in ranges.iter() {
|
|
210
|
-
if *total_samples == 0 {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
// clamp range
|
|
214
|
-
let end = (*start_sample)
|
|
215
|
-
.saturating_add(*total_samples)
|
|
216
|
-
.min(_engine.buffer.len());
|
|
217
|
-
if *start_sample >= end {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// work on a copy of the segment
|
|
222
|
-
let seg = _engine.buffer[*start_sample..end].to_vec();
|
|
223
|
-
let processed = resample_segment_nearest(&seg, channels, start_rate, end_rate);
|
|
224
|
-
|
|
225
|
-
// Mix processed back into buffer (replace to preserve duration)
|
|
226
|
-
for i in 0..processed.len().min(seg.len()) {
|
|
227
|
-
_engine.buffer[*start_sample + i] = processed[i];
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} else {
|
|
231
|
-
let logger = Logger::new();
|
|
232
|
-
logger.log_message(
|
|
233
|
-
LogLevel::Warning,
|
|
234
|
-
"Slide requested but no recent notes found for target",
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
fn apply_arp_effect(
|
|
240
|
-
args: &Vec<Value>,
|
|
241
|
-
_target: &str,
|
|
242
|
-
_engine: &mut AudioEngine,
|
|
243
|
-
_variable_table: &devalang_types::VariableTable,
|
|
244
|
-
) {
|
|
245
|
-
// Arp effect: split the last note into N slices and re-pitch each slice across a spread
|
|
246
|
-
let map = parse_map_arg(args);
|
|
247
|
-
let mut steps: usize = 4;
|
|
248
|
-
let mut spread_semitones: f32 = 0.0;
|
|
249
|
-
|
|
250
|
-
if let Some(m) = map {
|
|
251
|
-
if let Some(v) = m.get("steps") {
|
|
252
|
-
if let Some(s) = parse_value_to_f32(v) {
|
|
253
|
-
steps = (s as usize).max(1);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if let Some(v) = m.get("spread") {
|
|
257
|
-
if let Some(s) = parse_value_to_f32(v) {
|
|
258
|
-
spread_semitones = s;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
let channels = _engine.channels as usize;
|
|
264
|
-
if channels == 0 {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if let Some(ranges) = _engine.last_notes.get(_target) {
|
|
269
|
-
for (start_sample, total_samples) in ranges.iter() {
|
|
270
|
-
if *total_samples == 0 {
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
let end_sample = (*start_sample)
|
|
274
|
-
.saturating_add(*total_samples)
|
|
275
|
-
.min(_engine.buffer.len());
|
|
276
|
-
if *start_sample >= end_sample {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
let seg = _engine.buffer[*start_sample..end_sample].to_vec();
|
|
281
|
-
let frames = seg.len() / channels;
|
|
282
|
-
if frames == 0 {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// For each step, compute semitone and pitch multiplier and place slice at computed offset
|
|
287
|
-
for step in 0..steps {
|
|
288
|
-
let t = if steps > 1 {
|
|
289
|
-
step as f32 / (steps - 1) as f32
|
|
290
|
-
} else {
|
|
291
|
-
0.0
|
|
292
|
-
};
|
|
293
|
-
let semis = t * spread_semitones;
|
|
294
|
-
let rate = 2f32.powf(semis / 12.0);
|
|
295
|
-
|
|
296
|
-
// Resample the entire segment to the same duration using nearest approach with rate
|
|
297
|
-
let processed = resample_segment_nearest(&seg, channels, rate, rate);
|
|
298
|
-
|
|
299
|
-
// place the processed slice starting at fractional positions across original segment
|
|
300
|
-
let offset_frames =
|
|
301
|
-
((t * frames as f32).round() as usize).min(frames.saturating_sub(1));
|
|
302
|
-
let offset_samples = offset_frames.saturating_mul(channels);
|
|
303
|
-
|
|
304
|
-
// mix into engine buffer
|
|
305
|
-
for i in 0..processed.len() {
|
|
306
|
-
let dst_idx = *start_sample + offset_samples + i;
|
|
307
|
-
if dst_idx >= _engine.buffer.len() {
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
// simple additive mix and clamp
|
|
311
|
-
let sum = (_engine.buffer[dst_idx] as i32) + (processed[i] as i32);
|
|
312
|
-
_engine.buffer[dst_idx] = sum.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} else {
|
|
317
|
-
let logger = Logger::new();
|
|
318
|
-
logger.log_message(
|
|
319
|
-
LogLevel::Warning,
|
|
320
|
-
"Arp effect requested but no recent notes found for target",
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
use crate::core::{audio::engine::AudioEngine, plugin::runner::WasmPluginRunner};
|
|
2
|
-
use devalang_types::Value;
|
|
3
|
-
use devalang_utils::logger::{LogLevel, Logger};
|
|
4
|
-
use std::collections::HashMap;
|
|
5
|
-
|
|
6
|
-
pub fn interprete_note_method(
|
|
7
|
-
args: &Vec<devalang_types::Value>,
|
|
8
|
-
target: &str,
|
|
9
|
-
audio_engine: &mut AudioEngine,
|
|
10
|
-
variable_table: &devalang_types::VariableTable,
|
|
11
|
-
global_store: &crate::core::store::global::GlobalStore,
|
|
12
|
-
waveform_str: &str,
|
|
13
|
-
synth_params: &HashMap<String, devalang_types::Value>,
|
|
14
|
-
amp: f32,
|
|
15
|
-
base_bpm: f32,
|
|
16
|
-
base_duration: f32,
|
|
17
|
-
max_end_time: &mut f32,
|
|
18
|
-
mut cursor_time: Option<&mut f32>,
|
|
19
|
-
cursor_copy: f32,
|
|
20
|
-
update_cursor: bool,
|
|
21
|
-
) -> (f32, f32) {
|
|
22
|
-
let filtered_args: Vec<_> = args
|
|
23
|
-
.iter()
|
|
24
|
-
.filter(|arg| !matches!(arg, Value::Unknown))
|
|
25
|
-
.collect();
|
|
26
|
-
|
|
27
|
-
let Some(Value::Identifier(note_name)) = filtered_args.first().map(|v| (*v).clone()) else {
|
|
28
|
-
println!(
|
|
29
|
-
"❌ Invalid or missing argument for 'note' method on '{}'.",
|
|
30
|
-
target
|
|
31
|
-
);
|
|
32
|
-
return (*max_end_time, cursor_copy);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
let mut note_params = HashMap::new();
|
|
36
|
-
if let Some(arg1) = filtered_args.get(1) {
|
|
37
|
-
if let Value::Map(map) = (*arg1).clone() {
|
|
38
|
-
for (key, value) in map {
|
|
39
|
-
note_params.insert(key, value);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Note parameters and calculations
|
|
45
|
-
let amp_note = extract_f32(¬e_params, "amp", base_bpm).unwrap_or(amp);
|
|
46
|
-
let duration_ms =
|
|
47
|
-
extract_f32(¬e_params, "duration", base_bpm).unwrap_or(base_duration * 1000.0);
|
|
48
|
-
|
|
49
|
-
let duration_secs = duration_ms / 1000.0;
|
|
50
|
-
let final_freq = note_to_freq(¬e_name);
|
|
51
|
-
let start_time = cursor_copy;
|
|
52
|
-
let end_time = start_time + duration_secs;
|
|
53
|
-
|
|
54
|
-
// Fetch automation map if present:
|
|
55
|
-
// - Global (per-synth): key "<target>__automation" => map with key "params"
|
|
56
|
-
// - Per-note: note parameter "automate" => map
|
|
57
|
-
let auto_key = format!("{}__automation", target);
|
|
58
|
-
let synth_automation = match variable_table.get(&auto_key) {
|
|
59
|
-
Some(Value::Map(map)) => match map.get("params") {
|
|
60
|
-
Some(Value::Map(p)) => Some(p.clone()),
|
|
61
|
-
_ => None,
|
|
62
|
-
},
|
|
63
|
-
_ => None,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
let note_automation = match note_params.get("automate") {
|
|
67
|
-
Some(Value::Map(m)) => Some(m.clone()),
|
|
68
|
-
_ => None,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Merge: per-note overrides synth automation per key (volume/pan/pitch)
|
|
72
|
-
let automation = match (synth_automation, note_automation) {
|
|
73
|
-
(Some(mut a), Some(n)) => {
|
|
74
|
-
for (k, v) in n {
|
|
75
|
-
a.insert(k, v);
|
|
76
|
-
}
|
|
77
|
-
Some(a)
|
|
78
|
-
}
|
|
79
|
-
(None, Some(n)) => Some(n),
|
|
80
|
-
(Some(a), None) => Some(a),
|
|
81
|
-
_ => None,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// If waveform references a plugin alias (e.g., alias.synth), use the WASM plugin runner
|
|
85
|
-
if waveform_str.contains('.') && waveform_str.ends_with(".synth") {
|
|
86
|
-
let alias = waveform_str.split('.').next().unwrap_or("");
|
|
87
|
-
if let Some(Value::String(uri)) = variable_table.get(alias) {
|
|
88
|
-
if let Some(id) = uri.strip_prefix("devalang://plugin/") {
|
|
89
|
-
let mut parts = id.split('/');
|
|
90
|
-
let author = parts.next().unwrap_or("");
|
|
91
|
-
let name = parts.next().unwrap_or("");
|
|
92
|
-
let key = format!("{}:{}", author, name);
|
|
93
|
-
if let Some((info, wasm_bytes)) = global_store.plugins.get(&key) {
|
|
94
|
-
// Prepare buffer (stereo f32)
|
|
95
|
-
let sample_rate = 44100.0_f32;
|
|
96
|
-
let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
|
|
97
|
-
let channels = 2usize;
|
|
98
|
-
let start_index = ((start_time * sample_rate) as usize) * channels;
|
|
99
|
-
let required_len = start_index + total_samples * channels;
|
|
100
|
-
if audio_engine.buffer.len() < required_len {
|
|
101
|
-
audio_engine.buffer.resize(required_len, 0);
|
|
102
|
-
}
|
|
103
|
-
let mut fbuf = vec![0.0f32; total_samples * channels];
|
|
104
|
-
let runner = WasmPluginRunner::new();
|
|
105
|
-
let mut params_num: std::collections::HashMap<String, f32> =
|
|
106
|
-
std::collections::HashMap::new();
|
|
107
|
-
let mut params_str: std::collections::HashMap<String, String> =
|
|
108
|
-
std::collections::HashMap::new();
|
|
109
|
-
for (k, v) in synth_params.iter() {
|
|
110
|
-
match v {
|
|
111
|
-
Value::Number(n) => {
|
|
112
|
-
params_num.insert(k.clone(), *n);
|
|
113
|
-
}
|
|
114
|
-
Value::String(s) => {
|
|
115
|
-
params_str.insert(k.clone(), s.clone());
|
|
116
|
-
}
|
|
117
|
-
Value::Identifier(s) => {
|
|
118
|
-
params_str.insert(k.clone(), s.clone());
|
|
119
|
-
}
|
|
120
|
-
_ => {}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// collect exported names to pass to the runner (preference list)
|
|
125
|
-
let exported_names_vec: Vec<String> =
|
|
126
|
-
info.exports.iter().map(|e| e.name.clone()).collect();
|
|
127
|
-
|
|
128
|
-
// Debug log: exported names and synth param keys
|
|
129
|
-
{
|
|
130
|
-
let logger = devalang_utils::logger::Logger::new();
|
|
131
|
-
logger.log_message(
|
|
132
|
-
devalang_utils::logger::LogLevel::Debug,
|
|
133
|
-
&format!(
|
|
134
|
-
"Calling plugin runner for '{}' with {} exported names and {} synth params",
|
|
135
|
-
key,
|
|
136
|
-
exported_names_vec.len(),
|
|
137
|
-
synth_params.len()
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let _ = runner.render_note_with_params_in_place(
|
|
143
|
-
wasm_bytes,
|
|
144
|
-
&mut fbuf,
|
|
145
|
-
None,
|
|
146
|
-
final_freq,
|
|
147
|
-
amp_note,
|
|
148
|
-
duration_ms as i32,
|
|
149
|
-
44100,
|
|
150
|
-
2,
|
|
151
|
-
¶ms_num,
|
|
152
|
-
Some(¶ms_str),
|
|
153
|
-
Some(&exported_names_vec),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
for (i, sample) in fbuf.iter().enumerate().take(total_samples * channels) {
|
|
157
|
-
let s = (sample.clamp(-1.0, 1.0) * (i16::MAX as f32)) as i16;
|
|
158
|
-
let idx = start_index + i;
|
|
159
|
-
audio_engine.buffer[idx] = audio_engine.buffer[idx].saturating_add(s);
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
let logger = Logger::new();
|
|
163
|
-
logger.log_message(
|
|
164
|
-
LogLevel::Warning,
|
|
165
|
-
&format!(
|
|
166
|
-
"Plugin bytes not found for key '{}' (alias '{}').",
|
|
167
|
-
key, alias
|
|
168
|
-
),
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
let logger = Logger::new();
|
|
173
|
-
logger.log_message(
|
|
174
|
-
LogLevel::Warning,
|
|
175
|
-
&format!("Invalid plugin URI in alias '{}': {}", alias, uri),
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
let logger = Logger::new();
|
|
180
|
-
logger.log_message(
|
|
181
|
-
LogLevel::Warning,
|
|
182
|
-
&format!("Plugin alias '{}' not found in variable table.", alias),
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
186
|
-
// Allow types to adjust per-note scheduling/params
|
|
187
|
-
let start_ms = start_time * 1000.0;
|
|
188
|
-
let mut final_amp = amp_note;
|
|
189
|
-
let mut final_note_params = note_params.clone();
|
|
190
|
-
|
|
191
|
-
let mut handled = false;
|
|
192
|
-
if let Some(tval) = synth_params.get("type") {
|
|
193
|
-
let tname = match tval {
|
|
194
|
-
Value::String(s) => s.as_str(),
|
|
195
|
-
Value::Identifier(s) => s.as_str(),
|
|
196
|
-
_ => "",
|
|
197
|
-
};
|
|
198
|
-
match tname {
|
|
199
|
-
"arp" => {
|
|
200
|
-
// compute a step (ms) from synth params (rate/step). compute_arp_step
|
|
201
|
-
// will interpret `rate` as number of notes across the provided duration
|
|
202
|
-
let step_ms =
|
|
203
|
-
crate::core::audio::interpreter::statements::arrow_call::types::arp::compute_arp_step(
|
|
204
|
-
duration_ms,
|
|
205
|
-
1,
|
|
206
|
-
&synth_params
|
|
207
|
-
);
|
|
208
|
-
let steps = if step_ms > 0.0 {
|
|
209
|
-
((duration_ms / step_ms).ceil() as usize).max(1)
|
|
210
|
-
} else {
|
|
211
|
-
1usize
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// For each arp step, call prepare_note to get per-step params and schedule it
|
|
215
|
-
for idx in 0..steps {
|
|
216
|
-
let (start_abs_ms, freq_step, amp_out, params_out) =
|
|
217
|
-
crate::core::audio::interpreter::statements::arrow_call::types::arp::prepare_note(
|
|
218
|
-
¬e_name,
|
|
219
|
-
idx,
|
|
220
|
-
steps,
|
|
221
|
-
start_ms,
|
|
222
|
-
duration_ms,
|
|
223
|
-
amp_note,
|
|
224
|
-
&synth_params,
|
|
225
|
-
&final_note_params,
|
|
226
|
-
&automation
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// sub-note duration: default to step_ms so arp steps are audible and sequenced
|
|
230
|
-
let sub_duration_ms = if step_ms > 0.0 { step_ms } else { duration_ms };
|
|
231
|
-
|
|
232
|
-
let _ranges = audio_engine.insert_note(
|
|
233
|
-
Some(target.to_string()),
|
|
234
|
-
waveform_str.to_string(),
|
|
235
|
-
freq_step,
|
|
236
|
-
amp_out,
|
|
237
|
-
start_abs_ms,
|
|
238
|
-
sub_duration_ms,
|
|
239
|
-
synth_params.clone(),
|
|
240
|
-
params_out.clone(),
|
|
241
|
-
automation.clone(),
|
|
242
|
-
);
|
|
243
|
-
// Apply per-note effects if present in synth_params or note params
|
|
244
|
-
if let Some(ev) = params_out.get("effects") {
|
|
245
|
-
// for now expect effects as array of maps or identifiers
|
|
246
|
-
match ev {
|
|
247
|
-
Value::Array(arr) => {
|
|
248
|
-
for eff in arr.iter() {
|
|
249
|
-
if let Value::Map(_m) = eff {
|
|
250
|
-
// each map may have single key -> value
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
_ => {}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// mark handled to avoid the unconditional insert below
|
|
260
|
-
handled = true;
|
|
261
|
-
}
|
|
262
|
-
"pluck" => {
|
|
263
|
-
let (_s, _f, amp_out, params_out) =
|
|
264
|
-
crate::core::audio::interpreter::statements::arrow_call::types::pluck::prepare_note(
|
|
265
|
-
¬e_name,
|
|
266
|
-
0,
|
|
267
|
-
1,
|
|
268
|
-
start_ms,
|
|
269
|
-
duration_ms,
|
|
270
|
-
amp_note,
|
|
271
|
-
&synth_params,
|
|
272
|
-
&final_note_params,
|
|
273
|
-
&automation
|
|
274
|
-
);
|
|
275
|
-
final_amp = amp_out;
|
|
276
|
-
final_note_params = params_out;
|
|
277
|
-
}
|
|
278
|
-
"pad" => {
|
|
279
|
-
let (_s, _f, amp_out, params_out) =
|
|
280
|
-
crate::core::audio::interpreter::statements::arrow_call::types::pad::prepare_note(
|
|
281
|
-
¬e_name,
|
|
282
|
-
0,
|
|
283
|
-
1,
|
|
284
|
-
start_ms,
|
|
285
|
-
duration_ms,
|
|
286
|
-
amp_note,
|
|
287
|
-
&synth_params,
|
|
288
|
-
&final_note_params,
|
|
289
|
-
&automation
|
|
290
|
-
);
|
|
291
|
-
final_amp = amp_out;
|
|
292
|
-
final_note_params = params_out;
|
|
293
|
-
}
|
|
294
|
-
_ => {}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if !handled {
|
|
299
|
-
let ranges = audio_engine.insert_note(
|
|
300
|
-
Some(target.to_string()),
|
|
301
|
-
waveform_str.to_string(),
|
|
302
|
-
final_freq,
|
|
303
|
-
final_amp,
|
|
304
|
-
start_ms,
|
|
305
|
-
duration_ms,
|
|
306
|
-
synth_params.clone(),
|
|
307
|
-
final_note_params.clone(),
|
|
308
|
-
automation.clone(),
|
|
309
|
-
);
|
|
310
|
-
// apply per-note effects specified in final_note_params
|
|
311
|
-
if let Some(Value::Map(eff_map)) = final_note_params.get("effects") {
|
|
312
|
-
// delegate to effects module per range
|
|
313
|
-
for (_start, _len) in ranges.iter() {
|
|
314
|
-
// for simplicity apply using engine buffer ranges via effects module
|
|
315
|
-
crate::core::audio::interpreter::statements::arrow_call::methods::effects::apply_effect_chain(
|
|
316
|
-
"echo",
|
|
317
|
-
&vec![Value::Map(eff_map.clone())],
|
|
318
|
-
target,
|
|
319
|
-
audio_engine,
|
|
320
|
-
variable_table
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
*max_end_time = (*max_end_time).max(end_time);
|
|
328
|
-
|
|
329
|
-
if update_cursor {
|
|
330
|
-
if let Some(c) = cursor_time.as_mut() {
|
|
331
|
-
**c = end_time;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return (*max_end_time, end_time);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
fn extract_f32(map: &HashMap<String, Value>, key: &str, base_bpm: f32) -> Option<f32> {
|
|
339
|
-
map.get(key).and_then(|v| match v {
|
|
340
|
-
Value::Number(n) => Some(*n),
|
|
341
|
-
Value::Beat(beat_str) => {
|
|
342
|
-
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
343
|
-
if parts.len() == 2 {
|
|
344
|
-
let numerator = parts[0].parse::<f32>().ok()?;
|
|
345
|
-
let denominator = parts[1].parse::<f32>().ok()?;
|
|
346
|
-
|
|
347
|
-
Some((numerator / denominator) * ((60.0 / base_bpm) * 1000.0))
|
|
348
|
-
} else {
|
|
349
|
-
None
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
_ => None,
|
|
353
|
-
})
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
fn note_to_freq(note: &str) -> f32 {
|
|
357
|
-
let notes = [
|
|
358
|
-
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
|
|
359
|
-
];
|
|
360
|
-
|
|
361
|
-
if note.len() < 2 || note.len() > 3 {
|
|
362
|
-
return 440.0;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
let (name, octave_str) = note.split_at(note.len() - 1);
|
|
366
|
-
let semitone = notes.iter().position(|&n| n == name).unwrap_or(9) as i32;
|
|
367
|
-
let octave = octave_str.parse::<i32>().unwrap_or(4);
|
|
368
|
-
let midi_note = (octave + 1) * 12 + semitone;
|
|
369
|
-
|
|
370
|
-
440.0 * (2.0_f32).powf(((midi_note as f32) - 69.0) / 12.0)
|
|
371
|
-
}
|