@devaloop/devalang 0.0.1-alpha.3 → 0.0.1-alpha.5

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 (52) hide show
  1. package/.devalang +1 -1
  2. package/Cargo.toml +9 -7
  3. package/README.md +49 -25
  4. package/docs/CHANGELOG.md +30 -0
  5. package/docs/COMMANDS.md +31 -0
  6. package/docs/CONFIG.md +6 -4
  7. package/docs/ROADMAP.md +4 -4
  8. package/docs/TODO.md +4 -4
  9. package/examples/index.deva +9 -2
  10. package/examples/samples/hat-808.wav +0 -0
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +44 -42
  13. package/project-version.json +6 -6
  14. package/rust/audio/engine.rs +126 -0
  15. package/rust/audio/interpreter.rs +143 -0
  16. package/rust/audio/loader.rs +46 -0
  17. package/rust/audio/mod.rs +5 -0
  18. package/rust/audio/player.rs +54 -0
  19. package/rust/audio/render.rs +57 -0
  20. package/rust/cli/build.rs +17 -5
  21. package/rust/cli/check.rs +2 -1
  22. package/rust/cli/init.rs +4 -2
  23. package/rust/cli/mod.rs +29 -0
  24. package/rust/cli/play.rs +192 -0
  25. package/rust/cli/template.rs +2 -1
  26. package/rust/config/loader.rs +0 -1
  27. package/rust/config/mod.rs +3 -2
  28. package/rust/core/builder/mod.rs +54 -6
  29. package/rust/core/debugger/lexer.rs +20 -5
  30. package/rust/core/debugger/preprocessor.rs +9 -5
  31. package/rust/core/lexer/handler/mod.rs +2 -2
  32. package/rust/core/lexer/handler/newline.rs +5 -1
  33. package/rust/core/lexer/mod.rs +10 -5
  34. package/rust/core/parser/handler/loop_.rs +11 -0
  35. package/rust/core/parser/mod.rs +0 -1
  36. package/rust/core/preprocessor/loader.rs +89 -16
  37. package/rust/core/preprocessor/module.rs +2 -0
  38. package/rust/core/preprocessor/resolver/bank.rs +46 -0
  39. package/rust/core/preprocessor/resolver/loop_.rs +148 -0
  40. package/rust/core/preprocessor/resolver/mod.rs +151 -0
  41. package/rust/core/preprocessor/resolver/tempo.rs +49 -0
  42. package/rust/core/preprocessor/resolver/trigger.rs +114 -0
  43. package/rust/lib.rs +118 -0
  44. package/rust/main.rs +8 -0
  45. package/rust/utils/logger.rs +45 -6
  46. package/rust/utils/spinner.rs +2 -0
  47. package/rust/utils/watcher.rs +10 -2
  48. package/templates/minimal/.devalang +2 -1
  49. package/templates/minimal/README.md +202 -0
  50. package/templates/welcome/.devalang +2 -1
  51. package/templates/welcome/README.md +48 -31
  52. package/rust/core/preprocessor/resolver.rs +0 -372
package/.devalang CHANGED
@@ -1,4 +1,4 @@
1
1
  [defaults]
2
2
  entry = "./examples"
3
3
  output = "./output"
4
- watch = true
4
+ watch = false
package/Cargo.toml CHANGED
@@ -1,20 +1,22 @@
1
1
  [package]
2
2
  name = "devalang"
3
- version = "0.0.1-alpha.3"
3
+ version = "0.0.1-alpha.5"
4
4
  authors = ["Devaloop <contact@devaloop.com>"]
5
5
  description = "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text."
6
6
  license = "MIT"
7
7
  repository = "https://github.com/devaloop-labs/devalang"
8
8
  keywords = ["music", "dsl", "audio", "cli"]
9
- categories = ["command-line-utilities", "audio", "development-tools"]
9
+ categories = ["command-line-utilities", "development-tools", "parser-implementations"]
10
10
  readme = "README.md"
11
- homepage = "https://devaloop.com"
12
- documentation = "https://docs.rs/devalang"
11
+ homepage = "https://devalang.com"
12
+ documentation = "https://docs.devalang.com/"
13
+ license-file = "LICENSE"
13
14
  edition = "2024"
14
15
 
15
16
  [[bin]]
16
17
  name = "devalang"
17
18
  path = "rust/main.rs"
19
+ required-features = ["cli"]
18
20
 
19
21
  [lib]
20
22
  path = "rust/lib.rs"
@@ -25,7 +27,7 @@ opt-level = "s"
25
27
 
26
28
  [features]
27
29
  default = ["cli"]
28
- cli = ["crossterm"]
30
+ cli = ["crossterm", "indicatif", "inquire"]
29
31
 
30
32
  [dependencies]
31
33
  clap = { version = "4.5", features = ["derive"] }
@@ -42,5 +44,5 @@ serde-wasm-bindgen = "0.4"
42
44
  nom_locate = "4.0.0"
43
45
  chrono = "0.4"
44
46
  crossterm = { version = "0.27", optional = true }
45
- indicatif = "0.17"
46
- inquire = "0.7.5"
47
+ indicatif = { version = "0.17", optional = true }
48
+ inquire = { version = "0.7.5", optional = true }
package/README.md CHANGED
@@ -11,43 +11,49 @@
11
11
  ![License: MIT](https://img.shields.io/badge/license-MIT-green)
12
12
  ![Platform](https://img.shields.io/badge/platform-Windows-blue)
13
13
 
14
- ![npm](https://img.shields.io/npm/dm/@devaloop/devalang)
14
+ ![npm](https://img.shields.io/npm/dt/@devaloop/devalang)
15
+ ![crates](https://img.shields.io/crates/d/devalang)
16
+
17
+ [![VSCode Extension](https://img.shields.io/visual-studio-marketplace/v/devaloop.devalang-vscode?label=VSCode%20Extension)](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode)
18
+
15
19
 
16
20
  ## 🎼 Devalang, by **Devaloop Labs**
17
21
 
18
22
  🎶 Compose music with code — simple, structured, sonic.
19
23
 
20
24
  Devalang is a tiny domain-specific language (DSL) for music makers, sound designers, and audio hackers.
21
- Compose loops, control samples, and automate parameters — all in clean, readable text.
25
+ Compose loops, control samples, render and play audio — all in clean, readable text.
22
26
 
23
27
  🦊 Whether you're building a track, shaping textures, or performing live, Devalang helps you think in rhythms. It’s designed to be simple, expressive, and fast — because your ideas shouldn’t wait.
24
28
 
25
29
  From studio sketches to live sets, Devalang gives you rhythmic control — with the elegance of code.
26
30
 
27
- > 🚧 **v0.0.1-alpha.1 Notice** 🚧
28
- >
29
- > Devalang is still in early development. This version does not yet include **sound rendering**.
31
+ > 🚧 **v0.0.1-alpha.5 Notice** 🚧
30
32
  >
31
- > You can parse code, generate the AST, and validate syntax — all essential building blocks for the upcoming audio engine.
32
- >
33
- > Custom instruments can be defined with `@load`, allowing any sound sample to be triggered with the same syntax.
33
+ > NEW: Devalang VSCode extension is now available !
34
+ > [Get it here](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode).
34
35
  >
35
36
  > Currently, Devalang CLI is only available for **Windows**.
36
37
  > Linux and macOS binaries will be added in future releases via cross-platform builds.
37
38
 
39
+ ---
40
+
41
+ ## 📚 Quick Access
42
+
43
+ - [📖 Documentation](./docs/)
44
+ - [💡 Examples](./examples/)
45
+ - [🧩 VSCode Extension](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode)
46
+ - [🎨 Prettier Plugin](https://www.npmjs.com/package/@devaloop/prettier-plugin-devalang)
47
+ - [🌐 Project Website](https://devalang.com)
48
+
38
49
  ## 🚀 Features
39
50
 
40
- - 🧩 Module system for importing and exporting variables between files (`@import`, `@export`)
41
- - 📜 Structured AST generation for debugging and future compilation
42
- - 🔢 Basic data types: strings, numbers, booleans, maps, arrays
43
- - 👁️ Watch mode for `build` and `check` commands
44
- - ⏱️ `bpm` assignment for setting tempo
45
- - 🧱 `bank` declaration to define the instrument set
46
- - 🔁 Looping system with fixed repetitions (`loop 4:`)
47
- - 🧪 Instruction calls with parameters (e.g. `.kick auto {reverb:10, decay:20}`) for testing pattern syntax
48
- - 📄 `let` assignments for storing reusable values
49
- - 🔄 `@load` assignment to load a sample (.mp3, .wav) to use it as a value
50
- - 🛠️ CLI tools for syntax checking (`check`), AST output (`build`)
51
+ - 🎵 **Audio Engine**: Integrated audio playback and rendering
52
+ - 🧩 **Module system** for importing and exporting variables between files
53
+ - 📜 **Structured AST** generation for debugging and future compilation
54
+ - 🔢 **Basic data types**: strings, numbers, booleans, maps, arrays
55
+ - 👁️ **Watch mode** for `build`, `check` and `play` commands
56
+ - 📂 **Project templates** for quick setup
51
57
 
52
58
  ## 📆 Installation
53
59
 
@@ -79,7 +85,7 @@ npx @devaloop/devalang <command>
79
85
  > cargo install --path .
80
86
  ```
81
87
 
82
- Usage for development (feel free to change arguments in package.json)
88
+ Development usage (you can customize arguments in package.json)
83
89
 
84
90
  ```bash
85
91
  # For syntax checking test
@@ -90,6 +96,12 @@ npm run rust:dev:build
90
96
 
91
97
  ## ❔ Usage
92
98
 
99
+ NOTE: Commands are available via `devalang` or `npx @devaloop/devalang`.
100
+
101
+ NOTE: Arguments can be passed to commands using `--<argument>` syntax. You can also use a configuration file to set default values for various settings, making it easier to manage your Devalang project.
102
+
103
+ NOTE: Some commands require a mandatory `--entry` argument to specify the input folder, and a `--output` argument to specify the output folder. If not specified, they default to `./src` and `./output` respectively.
104
+
93
105
  For more examples, see [docs/COMMANDS.md](./docs/COMMANDS.md)
94
106
 
95
107
  ### Initialize a new project
@@ -109,13 +121,25 @@ devalang init --name <project-name> --template <template-name>
109
121
  ### Checking syntax only
110
122
 
111
123
  ```bash
112
- devalang check --entry <entry-directory> --output <output-directory> --watch
124
+ devalang check --watch
113
125
  ```
114
126
 
115
127
  ### Building output files
116
128
 
117
129
  ```bash
118
- devalang build --entry <entry-directory> --output <output-directory> --watch
130
+ devalang build --watch
131
+ ```
132
+
133
+ ### Playing audio files (once by file change)
134
+
135
+ ```bash
136
+ devalang play --watch
137
+ ```
138
+
139
+ ### Playing audio files (continuous playback, even without file changes)
140
+
141
+ ```bash
142
+ devalang play --repeat
119
143
  ```
120
144
 
121
145
  ## ⚙️ Configuration
@@ -135,6 +159,8 @@ For more examples, see [docs/SYNTAX.md](./docs/SYNTAX.md)
135
159
 
136
160
  @import { globalBpm, globalBank, kickDuration } from "global.deva"
137
161
 
162
+ @load "./examples/samples/kick-808.wav" as customKick
163
+
138
164
  bpm globalBpm
139
165
  # Will declare the tempo at the globalBpm variable beats per minute
140
166
 
@@ -142,7 +168,7 @@ bank globalBank
142
168
  # Will declare a custom instrument bank using the globalBank variable
143
169
 
144
170
  loop 5:
145
- .kick kickDuration {reverb=50, drive=25}
171
+ .customKick kickDuration {reverb=50, drive=25}
146
172
  # Will play 5 times a kick for the duration of the kickDuration variable with reverb and drive effects
147
173
  ```
148
174
 
@@ -158,7 +184,6 @@ let kickDuration = 500
158
184
 
159
185
  ## 🧯 Known issues
160
186
 
161
- - No support yet for Audio Engine
162
187
  - No support yet for `if`, `else`, `else if` statements
163
188
  - No support yet for `@group`, `@pattern`, `@function` statements
164
189
  - No support yet for cross-platform builds (Linux, macOS)
@@ -167,7 +192,6 @@ let kickDuration = 500
167
192
 
168
193
  For more info, see [docs/ROADMAP.md](./docs/ROADMAP.md)
169
194
 
170
- - ⏳ Audio engine integration (priority for alpha.4)
171
195
  - ⏳ Other statements (e.g `if`, `@group`, ...)
172
196
  - ⏳ Cross-platform support (Linux, macOS)
173
197
  - ⏳ More built-in instruments (e.g. snare, hi-hat, etc.)
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,36 @@
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## Version 0.0.1-alpha.5 (2025-07-05)
8
+
9
+ ### Syntax
10
+
11
+ - Fixed block parsing issues caused by missing or incorrect `Indent` / `Dedent` token detection.
12
+ - Indentation handling now triggers correctly at each newline.
13
+ - Improved reliability of nested blocks (e.g., inside `loop`) with consistent `Dedent` termination.
14
+
15
+ ### Core Components
16
+
17
+ - Added full **WebAssembly (WASM)** support — Devalang can now be compiled for browser or Node.js environments.
18
+ - Prepared the ground for future IDE integrations (e.g., VSCode extension) by stabilizing core syntax parsing.
19
+
20
+ ## Version 0.0.1-alpha.4 (2025-07-03)
21
+
22
+ ### Audio Engine
23
+
24
+ - Integrated Audio Engine to handle audio playback and rendering.
25
+ - Implemented Audio Player to play audio files.
26
+ - Added support for audio playback with the `play` command.
27
+
28
+ ### Commands
29
+
30
+ - Implemented `play` command to play Devalang files.
31
+
32
+ - Added `--watch` option to watch for changes in files and automatically rebuild and play them. (once)
33
+ - Added `--repeat` option to repeat the playback of the audio file. (infinite)
34
+
35
+ Note : You cannot use `--watch` and `--repeat` options together. Use `--repeat` instead.
36
+
7
37
  ## Version 0.0.1-alpha.3 (2025-07-01)
8
38
 
9
39
  - /!\ Major refactor of the project structure and module system /!\
package/docs/COMMANDS.md CHANGED
@@ -52,3 +52,34 @@ Available arguments :
52
52
  - `--entry`: The input folder (default to `./src`)
53
53
  - `--output`: The output folder (default to `./output`)
54
54
  - `--watch`: Whether to watch for changes and rebuild (default to `false`)
55
+
56
+
57
+ ## Playing
58
+
59
+ Playing .deva file(s) without audio playback (once)
60
+
61
+ ```bash
62
+ devalang play --entry ./examples --output ./output
63
+ ```
64
+
65
+ Playing .deva file(s) with audio playback (once by file change)
66
+
67
+ ```bash
68
+ devalang play --entry ./examples --output ./output --watch
69
+ ```
70
+
71
+ Playing .deva file(s) with audio playback (infinite loop)
72
+
73
+ ```bash
74
+ devalang play --entry ./examples --output ./output --repeat
75
+ ```
76
+
77
+ Note : You cannot use `--watch` and `--repeat` options together. Use `--repeat` instead.
78
+
79
+ Available arguments :
80
+
81
+ - `--no-config`: Whether to ignore the configuration file (default to `false`)
82
+ - `--entry`: The input folder (default to `./src`)
83
+ - `--output`: The output folder (default to `./output`)
84
+ - `--watch`: Whether to watch for changes and rebuild + play (default to `false`)
85
+ - `--repeat`: Whether to repeat the playback of the audio file (default to `false`)
package/docs/CONFIG.md CHANGED
@@ -18,11 +18,13 @@ The configuration file is a TOML (Tom's Obvious, Minimal Language) file that con
18
18
  [defaults]
19
19
  entry = "./src"
20
20
  output = "./output"
21
- watch = true
21
+ watch = false
22
+ repeat = true
22
23
  ```
23
24
 
24
25
  ### Available Settings
25
26
 
26
- - `entry`: (String) The entry point for your Devalang project
27
- - `output`: (String) The output directory for generated files
28
- - `watch`: (Boolean) Whether to watch for changes in files and automatically rebuild or check them
27
+ - `entry`: (String) The entry point for your Devalang project (default to `./src`)
28
+ - `output`: (String) The output directory for generated files (default to `./output`)
29
+ - `watch`: (Boolean) Whether to watch for changes in files and automatically rebuild or check them (default to `false`)
30
+ - `repeat`: (Boolean) Whether to repeat the playback of audio files (default to `false`)
package/docs/ROADMAP.md CHANGED
@@ -6,8 +6,9 @@
6
6
 
7
7
  Devalang is a work in progress. Here’s what we’re planning next:
8
8
 
9
- ### Stable
9
+ ## Completed
10
10
 
11
+ - ✅ **Audio engine**: Integrate the audio engine for sound playback.
11
12
  - ✅ **Basic syntax**: Implement the core syntax for Devalang, including data types and basic statements.
12
13
  - ✅ **Watch mode**: Add a watch mode to automatically rebuild on file changes.
13
14
  - ✅ **Module system**: Add support for importing and exporting variables between files using `@import` and `@export`.
@@ -19,12 +20,11 @@ Devalang is a work in progress. Here’s what we’re planning next:
19
20
  - ✅ **Instruction calls**: Add support for instruction calls with parameters (e.g. `.kick auto {reverb:10, decay:20}`).
20
21
  - ✅ **Let assignments**: Implement `let` assignments for storing reusable values.
21
22
  - ✅ **Sample loading**: Add `@load` assignment to load samples (.mp3, .wav) for use as values.
23
+ - ✅ **WASM support**: Compile Devalang to WebAssembly for use in web applications and other environments.
22
24
 
23
- ### Upcoming
25
+ ## Upcoming
24
26
 
25
- - ⏳ **Audio engine**: Integrate the audio engine for sound playback.
26
27
  - ⏳ **VSCode extension**: Create a VSCode extension for syntax highlighting and code completion.
27
- - ⏳ **WASM support**: Compile Devalang to WebAssembly for use in web applications.
28
28
  - ⏳ **Other statements**: Implement `if`, `else`, and other control structures.
29
29
  - ⏳ **Pattern and group statements**: Add support for `@pattern` and `@group` to organize code.
30
30
  - ⏳ **Functions**: Add support for defining and calling functions.
package/docs/TODO.md CHANGED
@@ -24,13 +24,13 @@ This is a list of tasks and features to be implemented in Devalang. Note that th
24
24
  - [ ] Implement debug mode
25
25
  - [ ] Implement compilation mode
26
26
  - [ ] Implement compression mode
27
- - [ ] Play
28
- - [ ] Implement Audio Engine
29
- - [ ] Implement loop mode
27
+ - [x] Play
28
+ - [x] Implement Audio Engine
29
+ - [x] Implement loop mode
30
30
 
31
31
  ## Core components
32
32
 
33
- - [ ] Audio Engine
33
+ - [x] Audio Engine
34
34
  - [x] Lexer
35
35
  - [x] Parser
36
36
  - [x] Preprocessor
@@ -1,10 +1,17 @@
1
1
  @import { duration, default_bank, params, loopCount, tempo } from "./examples/exported.deva"
2
2
 
3
- @load "./examples/samples/kick-808.wav" as sample
3
+ @load "./examples/samples/kick-808.wav" as kickCustom
4
+ @load "./examples/samples/hat-808.wav" as hatCustom
4
5
 
5
6
  bpm tempo
6
7
 
7
8
  bank default_bank
8
9
 
9
10
  loop loopCount:
10
- .sample duration params
11
+ .kickCustom duration params
12
+
13
+ # Uncomment the next line (.hat) while executing "play" command
14
+ # with `--repeat` option to see magic happen !
15
+
16
+ # .hatCustom duration params
17
+
Binary file
Binary file
package/package.json CHANGED
@@ -1,42 +1,44 @@
1
- {
2
- "name": "@devaloop/devalang",
3
- "private": false,
4
- "version": "0.0.1-alpha.3",
5
- "description": "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text.",
6
- "main": "out-tsc/index.js",
7
- "bin": {
8
- "devalang": "./out-tsc/bin/index.js"
9
- },
10
- "scripts": {
11
- "prepublish": "cargo build --release && npm run script:postbuild",
12
- "rust:dev:build": "cargo run build --entry examples --output output",
13
- "rust:dev:check": "cargo run check --entry examples --output output",
14
- "script:postbuild": "tsc && node out-tsc/scripts/postbuild.js",
15
- "script:version:bump": "tsc && node out-tsc/scripts/version/index.js"
16
- },
17
- "homepage": "https://devaloop.com",
18
- "keywords": [
19
- "devalang",
20
- "music",
21
- "sound",
22
- "domain-specific language",
23
- "dsl",
24
- "programming language",
25
- "sound design",
26
- "music hacking",
27
- "audio",
28
- "synthesis",
29
- "scripting",
30
- "sound synthesis",
31
- "music programming"
32
- ],
33
- "author": "Devaloop",
34
- "license": "MIT",
35
- "repository": {
36
- "type": "git",
37
- "url": "https://github.com/devaloop-labs/devalang.git"
38
- },
39
- "dependencies": {
40
- "@types/node": "^24.0.3"
41
- }
42
- }
1
+ {
2
+ "name": "@devaloop/devalang",
3
+ "private": false,
4
+ "version": "0.0.1-alpha.5",
5
+ "description": "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text.",
6
+ "main": "out-tsc/index.js",
7
+ "bin": {
8
+ "devalang": "./out-tsc/bin/index.js"
9
+ },
10
+ "scripts": {
11
+ "prepublish": "cargo build --release && npm run script:postbuild",
12
+ "rust:dev:build": "cargo run build --entry examples --output output",
13
+ "rust:dev:check": "cargo run check --entry examples --output output",
14
+ "rust:wasm:web": "wasm-pack build --target=web --no-default-features",
15
+ "rust:wasm:node": "wasm-pack build --target=nodejs --no-default-features",
16
+ "script:postbuild": "tsc && node out-tsc/scripts/postbuild.js",
17
+ "script:version:bump": "tsc && node out-tsc/scripts/version/index.js"
18
+ },
19
+ "homepage": "https://devalang.com",
20
+ "keywords": [
21
+ "devalang",
22
+ "music",
23
+ "sound",
24
+ "domain-specific language",
25
+ "dsl",
26
+ "programming language",
27
+ "sound design",
28
+ "music hacking",
29
+ "audio",
30
+ "synthesis",
31
+ "scripting",
32
+ "sound synthesis",
33
+ "music programming"
34
+ ],
35
+ "author": "Devaloop",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/devaloop-labs/devalang.git"
40
+ },
41
+ "dependencies": {
42
+ "@types/node": "^24.0.3"
43
+ }
44
+ }
@@ -1,6 +1,6 @@
1
- {
2
- "version": "0.0.1-alpha.3",
3
- "channel": "alpha",
4
- "lastCommit": "850fd73787ae41844d6669017ee3d01699b2df3c",
5
- "build": 2
6
- }
1
+ {
2
+ "version": "0.0.1-alpha.5",
3
+ "channel": "alpha",
4
+ "lastCommit": "1df08dbb62484586f67fbf42d34d6011cf019a12",
5
+ "build": 4
6
+ }
@@ -0,0 +1,126 @@
1
+ use std::{ collections::HashMap, fs::File, io::BufReader };
2
+ use hound::{ SampleFormat, WavSpec, WavWriter };
3
+ use rodio::{ Decoder, Source };
4
+
5
+ use crate::core::{ store::variable::VariableTable, utils::path::normalize_path };
6
+
7
+ const SAMPLE_RATE: u32 = 44100;
8
+ const CHANNELS: u16 = 2;
9
+
10
+ #[derive(Debug, Clone, PartialEq)]
11
+ pub struct AudioEngine {
12
+ pub volume: f32,
13
+ pub variables: VariableTable,
14
+ pub buffer: Vec<i16>,
15
+ }
16
+
17
+ impl AudioEngine {
18
+ pub fn new() -> Self {
19
+ AudioEngine {
20
+ volume: 1.0,
21
+ buffer: vec![],
22
+ variables: VariableTable::new(),
23
+ }
24
+ }
25
+
26
+ pub fn mix(&mut self, other: &AudioEngine) {
27
+ let max_len = self.buffer.len().max(other.buffer.len());
28
+ self.buffer.resize(max_len, 0);
29
+
30
+ for (i, &sample) in other.buffer.iter().enumerate() {
31
+ self.buffer[i] = self.buffer[i].saturating_add(sample);
32
+ }
33
+ }
34
+
35
+ pub fn set_duration(&mut self, duration_secs: f32) {
36
+ let mut total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
37
+
38
+ if total_samples % (CHANNELS as usize) != 0 {
39
+ total_samples += 1;
40
+ }
41
+
42
+ self.buffer.resize(total_samples, 0);
43
+ }
44
+
45
+ pub fn set_variables(&mut self, variables: VariableTable) {
46
+ self.variables = variables;
47
+ }
48
+
49
+ pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
50
+ if self.buffer.len() % (CHANNELS as usize) != 0 {
51
+ self.buffer.push(0);
52
+ println!("Completed buffer to respect stereo format.");
53
+ }
54
+
55
+ let spec = WavSpec {
56
+ channels: CHANNELS,
57
+ sample_rate: SAMPLE_RATE,
58
+ bits_per_sample: 16,
59
+ sample_format: SampleFormat::Int,
60
+ };
61
+
62
+ let mut writer = WavWriter::create(output_dir, spec).map_err(|e|
63
+ format!("Error creating WAV file: {}", e)
64
+ )?;
65
+
66
+ for sample in &self.buffer {
67
+ writer.write_sample(*sample).map_err(|e| format!("Error writing sample: {:?}", e))?;
68
+ }
69
+
70
+ writer.finalize().map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
71
+
72
+ Ok(())
73
+ }
74
+
75
+ pub fn insert(
76
+ &mut self,
77
+ filepath: &str,
78
+ time_secs: f32,
79
+ dur_sec: f32,
80
+ effects: Option<HashMap<String, f32>>
81
+ ) {
82
+ let normalized_filepath = normalize_path(filepath);
83
+
84
+ let file = BufReader::new(
85
+ File::open(normalized_filepath).expect("Failed to open audio file")
86
+ );
87
+ let decoder = Decoder::new(file).expect("Failed to decode audio file");
88
+
89
+ // Mono or stereo reading possible here, we will duplicate in L/R
90
+ let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
91
+ let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
92
+
93
+ if samples.is_empty() {
94
+ eprintln!("No samples found in the audio file: {}", filepath);
95
+ return;
96
+ }
97
+
98
+ // TODO Apply effects here if needed
99
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
100
+ let required_len = offset + samples.len() * (CHANNELS as usize);
101
+ let padded_required_len = if required_len % 2 == 1 {
102
+ required_len + 1
103
+ } else {
104
+ required_len
105
+ };
106
+
107
+ self.buffer.resize(padded_required_len, 0);
108
+ self.pad_samples(&samples, time_secs);
109
+ }
110
+
111
+ fn pad_samples(&mut self, samples: &[i16], time_secs: f32) {
112
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
113
+
114
+ for (i, &sample) in samples.iter().enumerate() {
115
+ let adjusted_sample = ((sample as f32) * self.volume).round() as i16;
116
+
117
+ let left_pos = offset + i * 2;
118
+ let right_pos = left_pos + 1;
119
+
120
+ if right_pos < self.buffer.len() {
121
+ self.buffer[left_pos] = self.buffer[left_pos].saturating_add(adjusted_sample); // gauche
122
+ self.buffer[right_pos] = self.buffer[right_pos].saturating_add(adjusted_sample); // droite
123
+ }
124
+ }
125
+ }
126
+ }