@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.
- package/.devalang +1 -1
- package/Cargo.toml +9 -7
- package/README.md +49 -25
- package/docs/CHANGELOG.md +30 -0
- package/docs/COMMANDS.md +31 -0
- package/docs/CONFIG.md +6 -4
- package/docs/ROADMAP.md +4 -4
- package/docs/TODO.md +4 -4
- package/examples/index.deva +9 -2
- package/examples/samples/hat-808.wav +0 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +44 -42
- package/project-version.json +6 -6
- package/rust/audio/engine.rs +126 -0
- package/rust/audio/interpreter.rs +143 -0
- package/rust/audio/loader.rs +46 -0
- package/rust/audio/mod.rs +5 -0
- package/rust/audio/player.rs +54 -0
- package/rust/audio/render.rs +57 -0
- package/rust/cli/build.rs +17 -5
- package/rust/cli/check.rs +2 -1
- package/rust/cli/init.rs +4 -2
- package/rust/cli/mod.rs +29 -0
- package/rust/cli/play.rs +192 -0
- package/rust/cli/template.rs +2 -1
- package/rust/config/loader.rs +0 -1
- package/rust/config/mod.rs +3 -2
- package/rust/core/builder/mod.rs +54 -6
- package/rust/core/debugger/lexer.rs +20 -5
- package/rust/core/debugger/preprocessor.rs +9 -5
- package/rust/core/lexer/handler/mod.rs +2 -2
- package/rust/core/lexer/handler/newline.rs +5 -1
- package/rust/core/lexer/mod.rs +10 -5
- package/rust/core/parser/handler/loop_.rs +11 -0
- package/rust/core/parser/mod.rs +0 -1
- package/rust/core/preprocessor/loader.rs +89 -16
- package/rust/core/preprocessor/module.rs +2 -0
- package/rust/core/preprocessor/resolver/bank.rs +46 -0
- package/rust/core/preprocessor/resolver/loop_.rs +148 -0
- package/rust/core/preprocessor/resolver/mod.rs +151 -0
- package/rust/core/preprocessor/resolver/tempo.rs +49 -0
- package/rust/core/preprocessor/resolver/trigger.rs +114 -0
- package/rust/lib.rs +118 -0
- package/rust/main.rs +8 -0
- package/rust/utils/logger.rs +45 -6
- package/rust/utils/spinner.rs +2 -0
- package/rust/utils/watcher.rs +10 -2
- package/templates/minimal/.devalang +2 -1
- package/templates/minimal/README.md +202 -0
- package/templates/welcome/.devalang +2 -1
- package/templates/welcome/README.md +48 -31
- package/rust/core/preprocessor/resolver.rs +0 -372
package/.devalang
CHANGED
package/Cargo.toml
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "devalang"
|
|
3
|
-
version = "0.0.1-alpha.
|
|
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", "
|
|
9
|
+
categories = ["command-line-utilities", "development-tools", "parser-implementations"]
|
|
10
10
|
readme = "README.md"
|
|
11
|
-
homepage = "https://
|
|
12
|
-
documentation = "https://docs.
|
|
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
|

|
|
12
12
|

|
|
13
13
|
|
|
14
|
-

|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
[](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
|
|
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.
|
|
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
|
-
>
|
|
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
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
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
|
-
|
|
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 --
|
|
124
|
+
devalang check --watch
|
|
113
125
|
```
|
|
114
126
|
|
|
115
127
|
### Building output files
|
|
116
128
|
|
|
117
129
|
```bash
|
|
118
|
-
devalang build --
|
|
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
|
-
.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
27
|
+
- [x] Play
|
|
28
|
+
- [x] Implement Audio Engine
|
|
29
|
+
- [x] Implement loop mode
|
|
30
30
|
|
|
31
31
|
## Core components
|
|
32
32
|
|
|
33
|
-
- [
|
|
33
|
+
- [x] Audio Engine
|
|
34
34
|
- [x] Lexer
|
|
35
35
|
- [x] Parser
|
|
36
36
|
- [x] Preprocessor
|
package/examples/index.deva
CHANGED
|
@@ -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
|
|
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
|
-
.
|
|
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
|
package/out-tsc/bin/devalang.exe
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@devaloop/devalang",
|
|
3
|
-
"private": false,
|
|
4
|
-
"version": "0.0.1-alpha.
|
|
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
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
package/project-version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.0.1-alpha.
|
|
3
|
-
"channel": "alpha",
|
|
4
|
-
"lastCommit": "
|
|
5
|
-
"build":
|
|
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
|
+
}
|