@devaloop/devalang 0.0.1-alpha.7 → 0.0.1-alpha.9
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/Cargo.toml +1 -1
- package/README.md +31 -16
- package/docs/CHANGELOG.md +49 -2
- package/docs/ROADMAP.md +2 -2
- package/docs/SYNTAX.md +41 -7
- package/docs/TODO.md +3 -3
- package/examples/condition.deva +20 -0
- package/examples/group.deva +3 -3
- package/examples/index.deva +9 -8
- package/examples/loop.deva +10 -8
- package/examples/synth.deva +14 -0
- package/examples/variables.deva +2 -2
- package/out-tsc/bin/devalang.exe +0 -0
- package/out-tsc/scripts/version/fetch.js +1 -5
- package/package.json +1 -1
- package/project-version.json +3 -3
- package/rust/cli/build.rs +6 -1
- package/rust/core/audio/engine.rs +89 -12
- package/rust/core/audio/evaluator.rs +31 -0
- package/rust/core/audio/interpreter/arrow_call.rs +129 -0
- package/rust/core/audio/interpreter/call.rs +64 -0
- package/rust/core/audio/interpreter/condition.rs +69 -0
- package/rust/core/audio/interpreter/driver.rs +216 -0
- package/rust/core/audio/interpreter/let_.rs +19 -0
- package/rust/core/audio/interpreter/load.rs +18 -0
- package/rust/core/audio/interpreter/loop_.rs +67 -0
- package/rust/core/audio/interpreter/mod.rs +12 -0
- package/rust/core/audio/interpreter/sleep.rs +36 -0
- package/rust/core/audio/interpreter/spawn.rs +66 -0
- package/rust/core/audio/interpreter/tempo.rs +16 -0
- package/rust/core/audio/interpreter/trigger.rs +69 -0
- package/rust/core/audio/loader/mod.rs +1 -0
- package/rust/core/audio/{loader.rs → loader/trigger.rs} +3 -1
- package/rust/core/audio/mod.rs +2 -1
- package/rust/core/audio/renderer.rs +54 -0
- package/rust/core/builder/mod.rs +1 -1
- package/rust/core/debugger/lexer.rs +1 -1
- package/rust/core/debugger/mod.rs +1 -0
- package/rust/core/debugger/store.rs +25 -0
- package/rust/core/error/mod.rs +1 -1
- package/rust/core/lexer/handler/arrow.rs +31 -0
- package/rust/core/lexer/handler/driver.rs +226 -0
- package/rust/core/lexer/handler/identifier.rs +3 -0
- package/rust/core/lexer/handler/mod.rs +4 -227
- package/rust/core/lexer/handler/operator.rs +44 -0
- package/rust/core/lexer/mod.rs +25 -4
- package/rust/core/lexer/token.rs +40 -9
- package/rust/core/parser/driver.rs +331 -0
- package/rust/core/parser/handler/arrow_call.rs +126 -0
- package/rust/core/parser/handler/at.rs +3 -7
- package/rust/core/parser/handler/bank.rs +5 -2
- package/rust/core/parser/handler/condition.rs +74 -0
- package/rust/core/parser/handler/dot.rs +1 -1
- package/rust/core/parser/handler/identifier/call.rs +41 -0
- package/rust/core/parser/handler/identifier/group.rs +75 -0
- package/rust/core/parser/handler/identifier/let_.rs +133 -0
- package/rust/core/parser/handler/identifier/mod.rs +51 -0
- package/rust/core/parser/handler/identifier/sleep.rs +33 -0
- package/rust/core/parser/handler/identifier/spawn.rs +41 -0
- package/rust/core/parser/handler/identifier/synth.rs +65 -0
- package/rust/core/parser/handler/loop_.rs +25 -19
- package/rust/core/parser/handler/mod.rs +3 -1
- package/rust/core/parser/handler/tempo.rs +1 -1
- package/rust/core/parser/mod.rs +3 -237
- package/rust/core/parser/statement.rs +36 -35
- package/rust/core/preprocessor/loader.rs +64 -49
- package/rust/core/preprocessor/module.rs +3 -6
- package/rust/core/preprocessor/processor.rs +13 -4
- package/rust/core/preprocessor/resolver/call.rs +123 -0
- package/rust/core/preprocessor/resolver/condition.rs +92 -0
- package/rust/core/preprocessor/resolver/driver.rs +227 -0
- package/rust/core/preprocessor/resolver/group.rs +35 -87
- package/rust/core/preprocessor/resolver/let_.rs +31 -0
- package/rust/core/preprocessor/resolver/loop_.rs +62 -116
- package/rust/core/preprocessor/resolver/mod.rs +9 -153
- package/rust/core/preprocessor/resolver/spawn.rs +58 -0
- package/rust/core/preprocessor/resolver/synth.rs +50 -0
- package/rust/core/preprocessor/resolver/trigger.rs +51 -50
- package/rust/core/preprocessor/resolver/value.rs +78 -0
- package/rust/core/utils/path.rs +17 -32
- package/rust/core/utils/validation.rs +30 -28
- package/typescript/scripts/version/fetch.ts +1 -6
- package/rust/core/audio/interpreter.rs +0 -317
- package/rust/core/audio/render.rs +0 -53
- package/rust/core/lexer/handler/equal.rs +0 -32
- package/rust/core/parser/handler/identifier.rs +0 -260
package/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "devalang"
|
|
3
|
-
version = "0.0.1-alpha.
|
|
3
|
+
version = "0.0.1-alpha.9"
|
|
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"
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
## 🎼 Devalang, by **Devaloop Labs**
|
|
20
20
|
|
|
21
|
-
🎶 Compose music with code —
|
|
21
|
+
🎶 Compose music with code — structured, expressive, and fast.
|
|
22
22
|
|
|
23
23
|
Devalang is a tiny domain-specific language (DSL) for music makers, sound designers, and audio hackers.
|
|
24
24
|
Compose loops, control samples, render and play audio — all in clean, readable text.
|
|
@@ -27,11 +27,13 @@ Compose loops, control samples, render and play audio — all in clean, readable
|
|
|
27
27
|
|
|
28
28
|
From studio sketches to live sets, Devalang gives you rhythmic control — with the elegance of code.
|
|
29
29
|
|
|
30
|
-
> 🚧 **v0.0.1-alpha.
|
|
30
|
+
> 🚧 **v0.0.1-alpha.9 Notice** 🚧
|
|
31
31
|
>
|
|
32
32
|
> NEW: Devalang VSCode extension is now available !
|
|
33
33
|
> [Get it here](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode).
|
|
34
34
|
>
|
|
35
|
+
> Includes synthesis, playback, and rendering features, but is still in early development.
|
|
36
|
+
>
|
|
35
37
|
> Currently, Devalang CLI is only available for **Windows**.
|
|
36
38
|
> Linux and macOS binaries will be added in future releases via cross-platform builds.
|
|
37
39
|
|
|
@@ -45,6 +47,15 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
|
|
|
45
47
|
- [🎨 Prettier Plugin](https://www.npmjs.com/package/@devaloop/prettier-plugin-devalang)
|
|
46
48
|
- [🌐 Project Website](https://devalang.com)
|
|
47
49
|
|
|
50
|
+
## ❓ Why Devalang?
|
|
51
|
+
|
|
52
|
+
- 🎹 Prototype audio ideas without opening a DAW
|
|
53
|
+
- 💻 Integrate sound into code-based workflows
|
|
54
|
+
- 🎛️ Control audio parameters through readable syntax
|
|
55
|
+
- 🧪 Build musical logic with variables and conditions
|
|
56
|
+
|
|
57
|
+
> Producer, coder, or both — Devalang gives you musical structure, instantly.
|
|
58
|
+
|
|
48
59
|
## 🚀 Features
|
|
49
60
|
|
|
50
61
|
- 🎵 **Audio Engine**: Integrated audio playback and rendering
|
|
@@ -53,6 +64,8 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
|
|
|
53
64
|
- 🔢 **Basic data types**: strings, numbers, booleans, maps, arrays
|
|
54
65
|
- 👁️ **Watch mode** for `build`, `check` and `play` commands
|
|
55
66
|
- 📂 **Project templates** for quick setup
|
|
67
|
+
- 🎛️ **Custom samples**: easily load and trigger your own audio files
|
|
68
|
+
- 🔄 **Looping and grouping**: create complex patterns with ease
|
|
56
69
|
|
|
57
70
|
## 📆 Installation
|
|
58
71
|
|
|
@@ -63,13 +76,13 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
|
|
|
63
76
|
Install the package globally (NPM)
|
|
64
77
|
|
|
65
78
|
```bash
|
|
66
|
-
npm install -g @devaloop/devalang
|
|
79
|
+
npm install -g @devaloop/devalang@latest
|
|
67
80
|
```
|
|
68
81
|
|
|
69
82
|
Usage without install (NPX)
|
|
70
83
|
|
|
71
84
|
```bash
|
|
72
|
-
npx @devaloop/devalang
|
|
85
|
+
npx @devaloop/devalang@latest
|
|
73
86
|
```
|
|
74
87
|
|
|
75
88
|
### For contributors
|
|
@@ -78,10 +91,11 @@ npx @devaloop/devalang <command>
|
|
|
78
91
|
> - ⚠️ Requires [Rust 1.70+](https://www.rust-lang.org/learn/get-started#installing-rust)
|
|
79
92
|
|
|
80
93
|
```bash
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
git clone https://github.com/devaloop-labs/devalang.git
|
|
95
|
+
|
|
96
|
+
cd devalang
|
|
97
|
+
|
|
98
|
+
npm install
|
|
85
99
|
```
|
|
86
100
|
|
|
87
101
|
Development usage (you can customize arguments in package.json)
|
|
@@ -89,6 +103,7 @@ Development usage (you can customize arguments in package.json)
|
|
|
89
103
|
```bash
|
|
90
104
|
# For syntax checking test
|
|
91
105
|
npm run rust:dev:check
|
|
106
|
+
|
|
92
107
|
# For building test
|
|
93
108
|
npm run rust:dev:build
|
|
94
109
|
```
|
|
@@ -166,25 +181,24 @@ bpm globalBpm
|
|
|
166
181
|
bank globalBank
|
|
167
182
|
# Will declare a custom instrument bank using the globalBank variable
|
|
168
183
|
|
|
169
|
-
# Loops
|
|
170
|
-
|
|
171
184
|
loop 5:
|
|
172
185
|
.customKick kickDuration {reverb=50, drive=25}
|
|
173
186
|
# Will play 5 times a custom sample for 500ms with reverb and overdrive effects
|
|
174
187
|
|
|
175
|
-
# Groups
|
|
176
|
-
|
|
177
188
|
group myGroup:
|
|
178
189
|
.customKick kickDuration {reverb=50, drive=25}
|
|
179
190
|
# Will play the same sample in a group, allowing for more complex patterns
|
|
180
191
|
|
|
181
|
-
# Will be executed line by line (sequentially)
|
|
182
192
|
call myGroup
|
|
193
|
+
# Will be executed line by line (sequentially)
|
|
183
194
|
|
|
184
|
-
# Will be executed in parallel (concurrently)
|
|
185
195
|
# spawn myGroup
|
|
196
|
+
# Will be executed in parallel (concurrently)
|
|
197
|
+
# ⚠️ Note: `spawn` runs the entire group in parallel, but the group’s internal logic remains sequential unless it uses `spawn` internally.
|
|
186
198
|
```
|
|
187
199
|
|
|
200
|
+
> 🧠 Note: `call` and `spawn` only work with `group` blocks. They do not apply to individual samples or other statements.
|
|
201
|
+
|
|
188
202
|
```deva
|
|
189
203
|
# variables.deva
|
|
190
204
|
|
|
@@ -197,14 +211,15 @@ let kickDuration = 500
|
|
|
197
211
|
|
|
198
212
|
## 🧯 Known issues
|
|
199
213
|
|
|
200
|
-
- No
|
|
214
|
+
- No smart modules yet, all groups, variables, and samples must be explicitly imported where used
|
|
215
|
+
- No support yet for `pattern`, `function`, ... statements
|
|
201
216
|
- No support yet for cross-platform builds (Linux, macOS)
|
|
202
217
|
|
|
203
218
|
## 🧪 Roadmap Highlights
|
|
204
219
|
|
|
205
220
|
For more info, see [docs/ROADMAP.md](./docs/ROADMAP.md)
|
|
206
221
|
|
|
207
|
-
- ⏳ Other statements (e.g `
|
|
222
|
+
- ⏳ Other statements (e.g `function`, `pattern`, ...)
|
|
208
223
|
- ⏳ Cross-platform support (Linux, macOS)
|
|
209
224
|
- ⏳ More built-in instruments (e.g. snare, hi-hat, etc.)
|
|
210
225
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,58 @@
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
-
## Version 0.0.1-alpha.
|
|
7
|
+
## Version 0.0.1-alpha.9 (2025-07-14)
|
|
8
|
+
|
|
9
|
+
### ✨ Syntax
|
|
10
|
+
|
|
11
|
+
- Added support for `synth` directives to define synthesizer sounds directly in code
|
|
12
|
+
→ Example:
|
|
13
|
+
|
|
14
|
+
```deva
|
|
15
|
+
let mySynth = synth sine
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 🧠 Core Engine
|
|
19
|
+
|
|
20
|
+
- ✅ **Major refactor** of the resolution layer:
|
|
21
|
+
|
|
22
|
+
- `condition`, `loop`, `group`, `trigger`, `call`, `spawn`, and `driver` were fully rewritten
|
|
23
|
+
- Ensures **correct variable scoping**, **cross-module references**, and **imported symbol resolution**
|
|
24
|
+
|
|
25
|
+
- ✅ Audio interpreter updated:
|
|
26
|
+
- `call` and `spawn` now execute correctly in parallel, preserving **temporal alignment** across groups
|
|
27
|
+
|
|
28
|
+
### 🧩 Language Features
|
|
29
|
+
|
|
30
|
+
- Improved `@import` behavior:
|
|
31
|
+
- Modules can now be imported using **relative paths only**
|
|
32
|
+
- No need for absolute or root-based imports
|
|
33
|
+
|
|
34
|
+
## Version 0.0.1-alpha.8 (2025-07-12)
|
|
35
|
+
|
|
36
|
+
### Syntax
|
|
37
|
+
|
|
38
|
+
- Implemented `if` directive to conditionally execute blocks of code.
|
|
39
|
+
- Implemented `else` directive to provide an alternative block of code when the `if` condition is not met.
|
|
40
|
+
- Implemented `else if` directive to provide additional conditions for the `if` directive.
|
|
41
|
+
|
|
42
|
+
### Core Components
|
|
43
|
+
|
|
44
|
+
- Implemented evaluator for audio statements, to execute conditional statements.
|
|
45
|
+
- Fixed `group` resolution and export issues.
|
|
46
|
+
- Implemented `Global Store` debugger to inspect variables by module for build command.
|
|
47
|
+
- Organized `TokenKind` and `StatementKind` enums for better clarity and maintainability.
|
|
48
|
+
- Refactored audio interpreter to handle the new syntax and directives.
|
|
49
|
+
- Refactored lexer to handle new directives and improve tokenization.
|
|
50
|
+
- Refactored parser to handle new directives and improve parsing logic.
|
|
51
|
+
- Added support for `call` and `spawn` execution of imported groups.
|
|
52
|
+
- Enforced scoped resolution of groups in `spawn` and `call` (must be imported in current module).
|
|
53
|
+
|
|
54
|
+
## Version 0.0.1-alpha.7 (2025-07-11)
|
|
8
55
|
|
|
9
56
|
## Examples
|
|
10
57
|
|
|
11
|
-
- Added
|
|
58
|
+
- Added examples in `examples` folder (group, loop, variables, index).
|
|
12
59
|
|
|
13
60
|
## Structure
|
|
14
61
|
|
package/docs/ROADMAP.md
CHANGED
|
@@ -24,8 +24,8 @@ Devalang is a work in progress. Here’s what we’re planning next:
|
|
|
24
24
|
|
|
25
25
|
## Upcoming
|
|
26
26
|
|
|
27
|
+
- ⏳ **Smart modules**: Let Devalang detect and use groups, samples, and variables without needing to import them manually.
|
|
27
28
|
- ⏳ **VSCode extension**: Create a VSCode extension for syntax highlighting and code completion.
|
|
28
|
-
- ⏳ **Other statements**: Implement `
|
|
29
|
-
- ⏳ **Pattern and group statements**: Add support for `@pattern` and `@group` to organize code.
|
|
29
|
+
- ⏳ **Other statements**: Implement `pattern`, `function`, and other control structures.
|
|
30
30
|
- ⏳ **Functions**: Add support for defining and calling functions.
|
|
31
31
|
- ⏳ **Testing**: Expand test coverage for all features.
|
package/docs/SYNTAX.md
CHANGED
|
@@ -58,7 +58,7 @@ let boolean = false
|
|
|
58
58
|
Maps are key-value pairs defined using curly braces. Keys are strings, and values can be of any type (string, number, boolean, map, or array).
|
|
59
59
|
|
|
60
60
|
```deva
|
|
61
|
-
let map = {myKey: 99}
|
|
61
|
+
let map = { myKey: 99 }
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
### Array
|
|
@@ -93,13 +93,17 @@ bank 808
|
|
|
93
93
|
|
|
94
94
|
Modules can be imported and exported to share variables between different files.
|
|
95
95
|
|
|
96
|
+
> ⚠️ The import/export system is still experimental and may change in the future.
|
|
97
|
+
>
|
|
98
|
+
> You must explicitly declare imports and exports in each file — Devalang does not automatically detect or resolve them.
|
|
99
|
+
|
|
96
100
|
Exporting variables from a module :
|
|
97
101
|
|
|
98
102
|
```deva
|
|
99
103
|
# exported.deva
|
|
100
104
|
|
|
101
105
|
let exportedIterator = 10
|
|
102
|
-
let exportedParams = {drive: 50, decay: 30}
|
|
106
|
+
let exportedParams = { drive: 50, decay: 30 }
|
|
103
107
|
|
|
104
108
|
@export { exportedIterator, exportedParams }
|
|
105
109
|
```
|
|
@@ -115,7 +119,7 @@ loop exportedIterator:
|
|
|
115
119
|
.kick auto exportedParams
|
|
116
120
|
```
|
|
117
121
|
|
|
118
|
-
###
|
|
122
|
+
### Loading Samples
|
|
119
123
|
|
|
120
124
|
You can load your own samples and use them in your music.
|
|
121
125
|
|
|
@@ -129,7 +133,7 @@ Trigger usage : `.<name> <duration> <params>`
|
|
|
129
133
|
.mySample auto {reverb: 50, drive: 25}
|
|
130
134
|
```
|
|
131
135
|
|
|
132
|
-
###
|
|
136
|
+
### Variables
|
|
133
137
|
|
|
134
138
|
Variables are defined using the `let` keyword, followed by the variable name and its value. The value can be of any type (string, number, boolean, map, or array).
|
|
135
139
|
|
|
@@ -137,7 +141,7 @@ Variables are defined using the `let` keyword, followed by the variable name and
|
|
|
137
141
|
let number = 0
|
|
138
142
|
let boolean = true
|
|
139
143
|
let string = "string"
|
|
140
|
-
let map = {myKey: 200}
|
|
144
|
+
let map = { myKey: 200 }
|
|
141
145
|
let array = [0, 1, 2]
|
|
142
146
|
```
|
|
143
147
|
|
|
@@ -154,14 +158,40 @@ loop 10:
|
|
|
154
158
|
|
|
155
159
|
Groups are defined using the `group` keyword, followed by the group name. The body of the group is indented.
|
|
156
160
|
|
|
161
|
+
Groups allow you to organize your code into reusable blocks. They can be called or spawned later in the code.
|
|
162
|
+
|
|
157
163
|
```deva
|
|
158
164
|
group myGroup:
|
|
159
165
|
# ...
|
|
160
166
|
```
|
|
161
167
|
|
|
168
|
+
### Conditions
|
|
169
|
+
|
|
170
|
+
Conditions are defined using the `if` keyword, followed by a condition. The body of the condition is indented.
|
|
171
|
+
|
|
172
|
+
```deva
|
|
173
|
+
if myCondition:
|
|
174
|
+
# ...
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
You can also use `else` and `else if` for alternative branches:
|
|
178
|
+
|
|
179
|
+
```deva
|
|
180
|
+
if myCondition:
|
|
181
|
+
# ...
|
|
182
|
+
else if anotherCondition:
|
|
183
|
+
# ...
|
|
184
|
+
else:
|
|
185
|
+
# ...
|
|
186
|
+
```
|
|
187
|
+
|
|
162
188
|
### Calling Groups (Sequential Execution)
|
|
163
189
|
|
|
164
|
-
Groups can be called using the `call` keyword, which executes the group in sequence.
|
|
190
|
+
Groups can be called using the `call` keyword, which executes only the group in sequence.
|
|
191
|
+
|
|
192
|
+
> ⚠️ `call` only works on `group` declarations. It does not apply to other statements.
|
|
193
|
+
|
|
194
|
+
This executes the entire group in the current execution thread, following a sequential order.
|
|
165
195
|
|
|
166
196
|
```deva
|
|
167
197
|
call myGroup
|
|
@@ -169,7 +199,11 @@ call myGroup
|
|
|
169
199
|
|
|
170
200
|
### Spawning Groups (Parallel Execution)
|
|
171
201
|
|
|
172
|
-
Groups can be spawned using the `spawn` keyword, which executes the group in parallel.
|
|
202
|
+
Groups can be spawned using the `spawn` keyword, which executes only the group in parallel.
|
|
203
|
+
|
|
204
|
+
> ⚠️ spawn also only works on group declarations. It does not make the group’s content parallel unless it explicitly uses spawn inside.
|
|
205
|
+
|
|
206
|
+
This runs the entire group in a separate execution thread, allowing it to play alongside other actions.
|
|
173
207
|
|
|
174
208
|
```deva
|
|
175
209
|
spawn myGroup
|
package/docs/TODO.md
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This file demonstrates the use of conditional blocks in Devalang.
|
|
2
|
+
|
|
3
|
+
@import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
|
|
4
|
+
@import { myGroup } from "./group.deva"
|
|
5
|
+
|
|
6
|
+
@load "./samples/kick-808.wav" as kickCustom
|
|
7
|
+
@load "./samples/hat-808.wav" as hatCustom
|
|
8
|
+
|
|
9
|
+
group conditionBlock:
|
|
10
|
+
if tempo > 120:
|
|
11
|
+
# Will be executed if the condition is true
|
|
12
|
+
.kickCustom auto
|
|
13
|
+
else if tempo > 155:
|
|
14
|
+
# Will be executed if the condition is false
|
|
15
|
+
.hatCustom auto
|
|
16
|
+
else:
|
|
17
|
+
.kickCustom auto
|
|
18
|
+
.hatCustom auto
|
|
19
|
+
|
|
20
|
+
@export { conditionBlock }
|
package/examples/group.deva
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# This file demonstrates the use of grouping in Devalang.
|
|
2
2
|
|
|
3
|
-
@import { duration, default_bank, params, loopCount, tempo } from "./
|
|
3
|
+
@import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
|
|
4
4
|
|
|
5
|
-
@load "./
|
|
6
|
-
@load "./
|
|
5
|
+
@load "./samples/kick-808.wav" as kickCustom
|
|
6
|
+
@load "./samples/hat-808.wav" as hatCustom
|
|
7
7
|
|
|
8
8
|
group myGroup:
|
|
9
9
|
.kickCustom duration params
|
package/examples/index.deva
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# This file demonstrates the use of main features in Devalang.
|
|
2
2
|
|
|
3
|
-
@import { duration, default_bank, params, loopCount, tempo } from "./
|
|
4
|
-
@import {
|
|
3
|
+
@import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
|
|
4
|
+
@import { myLead } from "./synth.deva"
|
|
5
|
+
@import { myLoop } from "./loop.deva"
|
|
5
6
|
|
|
6
|
-
@load "./
|
|
7
|
-
@load "./
|
|
7
|
+
@load "./samples/kick-808.wav" as kickCustom
|
|
8
|
+
@load "./samples/hat-808.wav" as hatCustom
|
|
8
9
|
|
|
9
10
|
bpm tempo
|
|
10
11
|
|
|
11
12
|
bank default_bank
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
group myTrack:
|
|
15
|
+
spawn myLoop
|
|
16
|
+
spawn myLead
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
# spawn myGroup
|
|
18
|
+
call myTrack
|
package/examples/loop.deva
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# This file demonstrates the use of a loop in Devalang.
|
|
2
2
|
|
|
3
|
-
@import { duration, default_bank, params, loopCount, tempo } from "./
|
|
3
|
+
@import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
|
|
4
4
|
|
|
5
|
-
@load "./
|
|
6
|
-
@load "./
|
|
5
|
+
@load "./samples/kick-808.wav" as kickCustom
|
|
6
|
+
@load "./samples/hat-808.wav" as hatCustom
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
group myLoop:
|
|
9
|
+
loop loopCount:
|
|
10
|
+
.kickCustom duration params
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
# Uncomment the next line (.hat) while executing "play" command
|
|
13
|
+
# with `--repeat` option to see magic happen !
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
.hatCustom duration params
|
|
15
16
|
|
|
17
|
+
@export { myLoop }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# This file defines a simple synth lead using Devalang's syntax.
|
|
2
|
+
|
|
3
|
+
group myLead:
|
|
4
|
+
let mySynth = synth sine
|
|
5
|
+
|
|
6
|
+
mySynth -> note(C4, { duration: 400 })
|
|
7
|
+
mySynth -> note(G4, { duration: 400 })
|
|
8
|
+
mySynth -> note(E4, { duration: 600 })
|
|
9
|
+
mySynth -> note(A4, { duration: 400 })
|
|
10
|
+
mySynth -> note(F4, { duration: 800 })
|
|
11
|
+
mySynth -> note(D4, { duration: 400 })
|
|
12
|
+
mySynth -> note(B3, { duration: 600 })
|
|
13
|
+
|
|
14
|
+
@export { myLead }
|
package/examples/variables.deva
CHANGED
package/out-tsc/bin/devalang.exe
CHANGED
|
Binary file
|
|
@@ -16,19 +16,15 @@ exports.fetchVersion = void 0;
|
|
|
16
16
|
const fs_1 = __importDefault(require("fs"));
|
|
17
17
|
const child_process_1 = require("child_process");
|
|
18
18
|
const fetchVersion = (projectVersionPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
-
// Lire le fichier
|
|
20
19
|
const data = JSON.parse(fs_1.default.readFileSync(projectVersionPath, "utf-8"));
|
|
21
|
-
// Incrémenter le numéro de build
|
|
22
20
|
data.build = (data.build || 0) + 1;
|
|
23
|
-
// Récupérer le dernier hash git
|
|
24
21
|
try {
|
|
25
22
|
const commit = (0, child_process_1.execSync)("git rev-parse HEAD").toString().trim();
|
|
26
23
|
data.lastCommit = commit;
|
|
27
24
|
}
|
|
28
25
|
catch (err) {
|
|
29
|
-
console.warn("⚠️
|
|
26
|
+
console.warn("⚠️ Unable to fetch git commit hash. Ensure you are in a git repository.");
|
|
30
27
|
}
|
|
31
|
-
// Écrire la mise à jour
|
|
32
28
|
fs_1.default.writeFileSync(projectVersionPath, JSON.stringify(data, null, 2));
|
|
33
29
|
});
|
|
34
30
|
exports.fetchVersion = fetchVersion;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devaloop/devalang",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.1-alpha.
|
|
4
|
+
"version": "0.0.1-alpha.9",
|
|
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
|
"main": "out-tsc/index.js",
|
|
7
7
|
"bin": {
|
package/project-version.json
CHANGED
package/rust/cli/build.rs
CHANGED
|
@@ -2,7 +2,11 @@ use crate::{
|
|
|
2
2
|
config::Config,
|
|
3
3
|
core::{
|
|
4
4
|
builder::Builder,
|
|
5
|
-
debugger::{
|
|
5
|
+
debugger::{
|
|
6
|
+
lexer::write_lexer_log_file,
|
|
7
|
+
preprocessor::write_preprocessor_log_file,
|
|
8
|
+
store::write_store_log_file,
|
|
9
|
+
},
|
|
6
10
|
preprocessor::loader::ModuleLoader,
|
|
7
11
|
store::global::GlobalStore,
|
|
8
12
|
utils::path::{ find_entry_file, normalize_path },
|
|
@@ -113,6 +117,7 @@ fn begin_build(entry: String, output: String) {
|
|
|
113
117
|
"resolved_statements.log",
|
|
114
118
|
modules_statements.clone()
|
|
115
119
|
);
|
|
120
|
+
write_store_log_file(&normalized_output_dir, "global_store.log", global_store.modules.clone());
|
|
116
121
|
|
|
117
122
|
// SECTION Building AST and Audio
|
|
118
123
|
let builder = Builder::new();
|
|
@@ -2,7 +2,10 @@ use std::{ collections::HashMap, fs::File, io::BufReader };
|
|
|
2
2
|
use hound::{ SampleFormat, WavSpec, WavWriter };
|
|
3
3
|
use rodio::{ Decoder, Source };
|
|
4
4
|
|
|
5
|
-
use crate::core::{
|
|
5
|
+
use crate::core::{
|
|
6
|
+
store::variable::VariableTable,
|
|
7
|
+
utils::path::{ normalize_path, resolve_relative_path },
|
|
8
|
+
};
|
|
6
9
|
|
|
7
10
|
const SAMPLE_RATE: u32 = 44100;
|
|
8
11
|
const CHANNELS: u16 = 2;
|
|
@@ -12,14 +15,16 @@ pub struct AudioEngine {
|
|
|
12
15
|
pub volume: f32,
|
|
13
16
|
pub variables: VariableTable,
|
|
14
17
|
pub buffer: Vec<i16>,
|
|
18
|
+
pub module_name: String,
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
impl AudioEngine {
|
|
18
|
-
pub fn new() -> Self {
|
|
22
|
+
pub fn new(module_name: String) -> Self {
|
|
19
23
|
AudioEngine {
|
|
20
24
|
volume: 1.0,
|
|
21
25
|
buffer: vec![],
|
|
22
26
|
variables: VariableTable::new(),
|
|
27
|
+
module_name,
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
|
|
@@ -32,14 +37,28 @@ impl AudioEngine {
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
pub fn
|
|
36
|
-
|
|
40
|
+
pub fn merge_with(&mut self, other: AudioEngine) {
|
|
41
|
+
if other.buffer.iter().all(|&s| s == 0) {
|
|
42
|
+
eprintln!("⚠️ Skipping merge: other buffer is silent");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
if
|
|
39
|
-
|
|
46
|
+
if self.buffer.iter().all(|&s| s == 0) {
|
|
47
|
+
self.buffer = other.buffer;
|
|
48
|
+
self.variables.variables.extend(other.variables.variables);
|
|
49
|
+
return;
|
|
40
50
|
}
|
|
41
51
|
|
|
42
|
-
self.
|
|
52
|
+
self.mix(&other);
|
|
53
|
+
self.variables.variables.extend(other.variables.variables);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn set_duration(&mut self, duration_secs: f32) {
|
|
57
|
+
let total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
|
|
58
|
+
|
|
59
|
+
if self.buffer.len() < total_samples {
|
|
60
|
+
self.buffer.resize(total_samples, 0);
|
|
61
|
+
}
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
pub fn set_variables(&mut self, variables: VariableTable) {
|
|
@@ -72,18 +91,76 @@ impl AudioEngine {
|
|
|
72
91
|
Ok(())
|
|
73
92
|
}
|
|
74
93
|
|
|
75
|
-
pub fn
|
|
94
|
+
pub fn insert_note(
|
|
95
|
+
&mut self,
|
|
96
|
+
waveform: String,
|
|
97
|
+
freq: f32,
|
|
98
|
+
amp: f32,
|
|
99
|
+
start_time_ms: f32,
|
|
100
|
+
duration_ms: f32
|
|
101
|
+
) {
|
|
102
|
+
let sample_rate = SAMPLE_RATE as f32;
|
|
103
|
+
let channels = CHANNELS as usize;
|
|
104
|
+
|
|
105
|
+
let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
|
|
106
|
+
let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
|
|
107
|
+
let amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0);
|
|
108
|
+
|
|
109
|
+
let mut samples = Vec::with_capacity(total_samples);
|
|
110
|
+
let fade_len = (sample_rate * 0.01) as usize; // 10 ms fade
|
|
111
|
+
|
|
112
|
+
for i in 0..total_samples {
|
|
113
|
+
let t = ((start_sample + i) as f32) / sample_rate;
|
|
114
|
+
let phase = 2.0 * std::f32::consts::PI * freq * t;
|
|
115
|
+
|
|
116
|
+
let mut value = match waveform.as_str() {
|
|
117
|
+
"sine" => phase.sin(),
|
|
118
|
+
"square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
|
|
119
|
+
"saw" => 2.0 * (freq * t - (freq * t + 0.5).floor()),
|
|
120
|
+
"triangle" => (2.0 * (2.0 * (freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
|
|
121
|
+
_ => 0.0,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Fade in/out
|
|
125
|
+
if i < fade_len {
|
|
126
|
+
value *= (i as f32) / (fade_len as f32);
|
|
127
|
+
} else if i >= total_samples - fade_len {
|
|
128
|
+
value *= ((total_samples - i) as f32) / (fade_len as f32);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
samples.push((value * amplitude) as i16);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Convert to stereo
|
|
135
|
+
let stereo_samples: Vec<i16> = samples
|
|
136
|
+
.iter()
|
|
137
|
+
.flat_map(|s| vec![*s, *s])
|
|
138
|
+
.collect();
|
|
139
|
+
|
|
140
|
+
let offset = start_sample * channels;
|
|
141
|
+
let required_len = offset + stereo_samples.len();
|
|
142
|
+
|
|
143
|
+
if self.buffer.len() < required_len {
|
|
144
|
+
self.buffer.resize(required_len, 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (i, sample) in stereo_samples.iter().enumerate() {
|
|
148
|
+
if *sample != 0 {
|
|
149
|
+
}
|
|
150
|
+
self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pub fn insert_sample(
|
|
76
155
|
&mut self,
|
|
77
156
|
filepath: &str,
|
|
78
157
|
time_secs: f32,
|
|
79
158
|
dur_sec: f32,
|
|
80
159
|
effects: Option<HashMap<String, f32>>
|
|
81
160
|
) {
|
|
82
|
-
let
|
|
161
|
+
let resolved = resolve_relative_path(&self.module_name.clone(), filepath);
|
|
83
162
|
|
|
84
|
-
let file = BufReader::new(
|
|
85
|
-
File::open(normalized_filepath).expect("Failed to open audio file")
|
|
86
|
-
);
|
|
163
|
+
let file = BufReader::new(File::open(resolved).expect("Failed to open audio file"));
|
|
87
164
|
let decoder = Decoder::new(file).expect("Failed to decode audio file");
|
|
88
165
|
|
|
89
166
|
// Mono or stereo reading possible here, we will duplicate in L/R
|